Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
206f755
saving
May 8, 2025
8a00cc7
latest
May 8, 2025
592f5e4
saving
May 12, 2025
c93574e
latest
May 12, 2025
f22304e
latest
May 14, 2025
14c2f00
latest
May 14, 2025
4a74063
latest
May 14, 2025
3be40ca
latest
May 15, 2025
4ba04b3
latest
May 16, 2025
a4548c5
merge
May 16, 2025
50e2ce7
latest
May 16, 2025
45e623c
latest
May 16, 2025
c9dea3b
latest
May 16, 2025
8dc3883
latest
May 16, 2025
440b674
latest
May 16, 2025
8dd1341
latest
May 16, 2025
23d3f4d
latest
May 16, 2025
32b8d19
latest
May 16, 2025
87e2313
latest
May 16, 2025
7ccda94
latest
May 16, 2025
99d9612
latest
May 16, 2025
028835b
saving
May 22, 2025
624b205
latest
May 22, 2025
5d77bb1
latest
May 23, 2025
7fdc18a
latest
May 23, 2025
d585c96
latest
May 23, 2025
3cb8f92
saving
May 26, 2025
343766f
saving
May 26, 2025
bb0235d
latest
May 26, 2025
5149ea0
latest
May 26, 2025
37b0e36
merge
May 26, 2025
cab5b3a
latest
May 27, 2025
a071ae0
latest
May 27, 2025
5a5f1cc
latest
May 27, 2025
4390c80
latest
May 27, 2025
3a76904
latest
May 27, 2025
eb98287
latest
May 27, 2025
ea803b2
fix lint
May 28, 2025
4a2b527
latest
May 28, 2025
b29e66a
latest
May 28, 2025
89dc988
latest
May 28, 2025
5882b75
latest
May 28, 2025
bce8841
latest
May 28, 2025
79dfa0b
latest
May 28, 2025
665d589
latest
May 29, 2025
35ef34a
saving
May 29, 2025
0a2dcde
latest
May 29, 2025
80c0632
latest
May 29, 2025
d44b888
merge
May 29, 2025
f11754c
latest
May 29, 2025
a28860e
latest
May 29, 2025
239773d
latest
May 29, 2025
f7d2695
latest
May 29, 2025
86f3a40
latest
May 29, 2025
13247d7
latest
May 29, 2025
ae8ebaa
fix lint
May 29, 2025
340073b
latest
May 30, 2025
9215797
Merge branch 'main' into serveTimeline
davemarco May 30, 2025
9d4691e
lint
May 30, 2025
b944ea4
Merge branch 'main' into serveTimeline
kirkrodrigues May 30, 2025
d56f0c1
Merge branch 'main' into serveTimeline
davemarco Jun 2, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,13 @@ import {
TooltipItem,
} from "chart.js";
import zoomPlugin from "chartjs-plugin-zoom";
import dayjs from "dayjs";
import dayjs, {Dayjs} from "dayjs";

import {Nullable} from "../../typings/common";
import {DATETIME_FORMAT_TEMPLATE} from "../../typings/datetime";
import {
convertUtcDatetimeToSameLocalDate,
convertZoomTimestampToUtcDatetime,
} from "./datetime";
import {TimeRange} from "./datetime/typings";
import styles from "./index.module.css";
import {
TimelineBucket,
Expand All @@ -45,8 +43,8 @@ ChartJs.register(

interface ResultsTimelineProps {
isInputDisabled: boolean;
onTimelineZoom: (newTimeRange: TimeRange) => void;
timelineBuckets: Nullable<TimelineBucket[]>;
onTimelineZoom: (newTimeRange: [Dayjs, Dayjs]) => void;
timelineBuckets: TimelineBucket[];
timelineConfig: TimelineConfig;
}

Expand Down Expand Up @@ -78,10 +76,6 @@ const ResultsTimeline = ({
);
}, [isInputDisabled]);

if (null === timelineBuckets) {
return <div/>;
}

const data = {
datasets: [
{
Expand Down Expand Up @@ -179,10 +173,10 @@ const ResultsTimeline = ({
return;
}
const {min, max} = xAxis;
const newTimeRange = {
begin: convertZoomTimestampToUtcDatetime(min),
end: convertZoomTimestampToUtcDatetime(max),
};
const newTimeRange: [Dayjs, Dayjs] = [
convertZoomTimestampToUtcDatetime(min),
convertZoomTimestampToUtcDatetime(max),
];

onTimelineZoom(newTimeRange);
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,28 +19,29 @@ import styles from "./index.module.css";
* @return
*/
const SubmitButton = () => {
const {searchUiState, timeRange, queryString} = useSearchStore();
const isQueryStringEmpty = queryString === SEARCH_STATE_DEFAULT.queryString;
const {searchUiState, timeRange, queryString, updateTimelineConfig} = useSearchStore();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

purely stylish - we can address all in another PR

instead of retrieving the states / actions here, we can write

    const handleSubmitButtonClick = useCallback(() => {
        const {queryString, timeRange, updateTimelineConfig} = useSearchStore.get();
        // Update timeline to match range picker selection.
        const newTimelineConfig = computeTimelineConfig(timeRange);
        updateTimelineConfig(newTimelineConfig);

        handleQuerySubmit({
            ignoreCase: false,
            queryString: queryString,
            timeRangeBucketSizeMillis: newTimelineConfig.bucketDuration.asMilliseconds(),
            timestampBegin: timeRange[0].valueOf(),
            timestampEnd: timeRange[1].valueOf(),
        });
    }, []);

so the definition of handleSubmitButtonClick won't change at all

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

k i will do in later PR


/**
* Submits search query.
*/
const handleSubmitButtonClick = useCallback(() => {
const timelineConfig = computeTimelineConfig(
timeRange[0].valueOf(),
timeRange[1].valueOf()
);
// Update timeline to match range picker selection.
const newTimelineConfig = computeTimelineConfig(timeRange);
updateTimelineConfig(newTimelineConfig);

handleQuerySubmit({
ignoreCase: false,
queryString: queryString,
timeRangeBucketSizeMillis: timelineConfig.bucketDuration.asMilliseconds(),
timeRangeBucketSizeMillis: newTimelineConfig.bucketDuration.asMilliseconds(),
timestampBegin: timeRange[0].valueOf(),
timestampEnd: timeRange[1].valueOf(),
});
}, [queryString,
updateTimelineConfig,
timeRange]);

const isQueryStringEmpty = queryString === SEARCH_STATE_DEFAULT.queryString;

return (
<Tooltip
title={isQueryStringEmpty ?
Expand Down
Original file line number Diff line number Diff line change
@@ -1,78 +1,67 @@
import {Card} from "antd";
import dayjs from "dayjs";
import {Dayjs} from "dayjs";
import {TimelineConfig} from "src/components/ResultsTimeline/typings";

import {TimeRange} from "../../../../components/ResultsTimeline/datetime/typings";
import ResultsTimeline from "../../../../components/ResultsTimeline/index";
import {TimelineBucket} from "../../../../components/ResultsTimeline/typings";
import {handleQuerySubmit} from "../../SearchControls/search-requests";
import {TIME_RANGE_OPTION} from "../../SearchControls/TimeRangeInput/utils";
import useSearchStore from "../../SearchState";
import {
computeTimelineConfig,
expandTimeRangeToDurationMultiple,
} from "./utils";
import useSearchStore, {SEARCH_STATE_DEFAULT} from "../../SearchState/index";
import {SEARCH_UI_STATE} from "../../SearchState/typings";
import {useAggregationResults} from "./useAggregationResults";
import {computeTimelineConfig} from "./utils";


// eslint-disable-next-line no-warning-comments
// TODO: Replace with values from database once api implemented.
const DUMMY_BUCKETS: TimelineBucket[] = [
{
count: 2,
timestamp: dayjs().subtract(10, "day")
.valueOf(),
},
{
count: 3,
// eslint-disable-next-line no-magic-numbers
timestamp: dayjs().subtract(5, "day")
.valueOf(),
},
{
count: 5,
timestamp: dayjs().valueOf(),
},
];

/**
* Renders timeline visualization of search results.
*
* @return
*/
const SearchResultsTimeline = () => {
const {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto - see the Zustand state / action retrieval styling sugguestion

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will do this also in future PR

queryString,
updateTimeRange,
timeRange: [beginTime, endTime],
updateTimeRangeOption,
timelineConfig,
searchUiState,
updateTimelineConfig,
} = useSearchStore();
const timestampBeginUnixMillis = beginTime.utc().valueOf();
const timestampEndUnixMillis = endTime.utc().valueOf();

const timelineConfig = computeTimelineConfig(timestampBeginUnixMillis, timestampEndUnixMillis);
const aggregationResults = useAggregationResults();


const handleTimelineZoom = (newTimeRange: TimeRange) => {
// Expand the time range to the granularity of buckets so if the user
// pans across at least one bar in the graph, we will zoom into a region
// that still contains log events.
const expandedTimeRange = expandTimeRangeToDurationMultiple(
timelineConfig.bucketDuration,
newTimeRange,
);
const handleTimelineZoom = (newTimeRange: [Dayjs, Dayjs]) => {
const newTimelineConfig: TimelineConfig = computeTimelineConfig(newTimeRange);

updateTimeRange([expandedTimeRange.begin,
expandedTimeRange.end]);
// Update range picker selection to match zoomed range.
updateTimeRange(newTimeRange);
updateTimeRangeOption(TIME_RANGE_OPTION.CUSTOM);
updateTimelineConfig(newTimelineConfig);

const isQueryStringEmpty = queryString === SEARCH_STATE_DEFAULT.queryString;
if (isQueryStringEmpty) {
return;
}

// eslint-disable-next-line no-warning-comments
// TODO: submit query based on timelineConfig.
handleQuerySubmit({
ignoreCase: false,
queryString: queryString,
timeRangeBucketSizeMillis: newTimelineConfig.bucketDuration.asMilliseconds(),
timestampBegin: newTimeRange[0].valueOf(),
timestampEnd: newTimeRange[1].valueOf(),
});
Comment on lines +45 to +51

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Consider using the actual case sensitivity preference.

The ignoreCase parameter is hardcoded to false. If the search UI has a case sensitivity toggle, this should use the actual user preference from the search state.

Let me check if there's a case sensitivity setting in the search state:


🏁 Script executed:

#!/bin/bash
# Description: Check for case sensitivity settings in the search state and UI

# Search for ignoreCase or case sensitivity related state
rg -A 5 "ignoreCase|caseSensitive|caseInsensitive" --glob "**/*SearchState*" --glob "**/*Search*"

Length of output: 1987


Use actual case sensitivity preference for timeline zoom queries

The call to handleQuerySubmit in
components/log-viewer-webui/client/src/pages/SearchPage/SearchResults/SearchResultsTimeline/index.tsx (lines 47–53)
is hardcoding ignoreCase: false, which overrides the user’s case-sensitivity toggle in SearchView.jsx. To fix:

• Ensure this component receives the ignoreCase prop from the search state.
• Replace the hardcoded value with that prop when submitting the zoomed query.

Suggested diff:

--- a/components/log-viewer-webui/client/src/pages/SearchPage/SearchResults/SearchResultsTimeline/index.tsx
+++ b/components/log-viewer-webui/client/src/pages/SearchPage/SearchResults/SearchResultsTimeline/index.tsx
@@ 47,7
-       handleQuerySubmit({
-           ignoreCase: false,
+       handleQuerySubmit({
+           ignoreCase: ignoreCase,
            queryString: queryString,
            timeRangeBucketSizeMillis: newTimelineConfig.bucketDuration.asMilliseconds(),
            timestampBegin: newTimeRange[0].valueOf(),

This will align the timeline-zoom search with the UI’s case-sensitivity setting.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
handleQuerySubmit({
ignoreCase: false,
queryString: queryString,
timeRangeBucketSizeMillis: newTimelineConfig.bucketDuration.asMilliseconds(),
timestampBegin: newTimeRange[0].valueOf(),
timestampEnd: newTimeRange[1].valueOf(),
});
handleQuerySubmit({
ignoreCase: ignoreCase,
queryString: queryString,
timeRangeBucketSizeMillis: newTimelineConfig.bucketDuration.asMilliseconds(),
timestampBegin: newTimeRange[0].valueOf(),
timestampEnd: newTimeRange[1].valueOf(),
});
🤖 Prompt for AI Agents
In
components/log-viewer-webui/client/src/pages/SearchPage/SearchResults/SearchResultsTimeline/index.tsx
around lines 47 to 53, the ignoreCase parameter in handleQuerySubmit is
hardcoded to false, ignoring the user's case sensitivity preference. To fix
this, modify the component to accept the ignoreCase value from the search state
as a prop and replace the hardcoded false with this prop when calling
handleQuerySubmit, ensuring the timeline zoom queries respect the user's case
sensitivity setting.

};

// eslint-disable-next-line no-warning-comments
// TODO: fix `isInputDisabled` .
return (
<Card>
<ResultsTimeline
isInputDisabled={false}
timelineBuckets={DUMMY_BUCKETS}
timelineConfig={timelineConfig}
isInputDisabled={
searchUiState === SEARCH_UI_STATE.QUERYING ||
searchUiState === SEARCH_UI_STATE.QUERY_ID_PENDING
}
timelineBuckets={aggregationResults ?
aggregationResults :
[]}
onTimelineZoom={handleTimelineZoom}/>
</Card>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import MongoCollectionSocket from "../../../../api/socket/MongoCollectionSocket";
import {useCursor} from "../../../../api/socket/useCursor";
import {TimelineBucket} from "../../../../components/ResultsTimeline/typings";
import useSearchStore, {SEARCH_STATE_DEFAULT} from "../../SearchState/index";


/**
* Custom hook to get aggregation results for the current aggregationJobId.
*
* @return
*/
const useAggregationResults = () => {
const {aggregationJobId} = useSearchStore();

const aggregationResultsCursor = useCursor<TimelineBucket>(
() => {
// If there is no active aggregation job, there are no results to fetch. The cursor will
// return null.
if (aggregationJobId === SEARCH_STATE_DEFAULT.aggregationJobId) {
return null;
}

const collection = new MongoCollectionSocket(aggregationJobId.toString());
return collection.find({}, {});
},
[aggregationJobId]
);

return aggregationResultsCursor;
};

export {useAggregationResults};
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import dayjs from "dayjs";
import DayjsDuration, {DurationUnitType} from "dayjs/plugin/duration";
import DayjsUtc from "dayjs/plugin/utc";

import {TimeRange} from "../../../../components/ResultsTimeline/datetime/typings";
import {
Expand All @@ -8,6 +9,7 @@ import {
} from "../../../../components/ResultsTimeline/typings";


dayjs.extend(DayjsUtc);
dayjs.extend(DayjsDuration);

/**
Expand Down Expand Up @@ -36,14 +38,14 @@ const expandTimeRangeToDurationMultiple = (duration: DayjsDuration.Duration, {
* Computes the timestamp range and bucket duration necessary to render the bars in the timeline
* chart.
*
* @param timestampBeginUnixMillis
* @param timestampEndUnixMillis
* @param timeRange
* @return
*/
const computeTimelineConfig = (
timestampBeginUnixMillis: number,
timestampEndUnixMillis: number
timeRange: [dayjs.Dayjs, dayjs.Dayjs],
): TimelineConfig => {
const timestampBeginUnixMillis = dayjs.utc(timeRange[0]).valueOf();
const timestampEndUnixMillis = dayjs.utc(timeRange[1]).valueOf();
const timeRangeMillis = timestampEndUnixMillis - timestampBeginUnixMillis;
const exactTimelineBucketMillis = timeRangeMillis / MAX_DATA_POINTS_PER_TIMELINE;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import dayjs from "dayjs";
import {create} from "zustand";

import {TimelineConfig} from "../../../components/ResultsTimeline/typings";
import {
DEFAULT_TIME_RANGE,
TIME_RANGE_OPTION,
TIME_RANGE_OPTION_DAYJS_MAP,
} from "../SearchControls/TimeRangeInput/utils";
import {computeTimelineConfig} from "../SearchResults/SearchResultsTimeline/utils";
import {SEARCH_UI_STATE} from "./typings";


Expand All @@ -21,6 +23,7 @@ const SEARCH_STATE_DEFAULT = Object.freeze({
searchUiState: SEARCH_UI_STATE.DEFAULT,
timeRange: TIME_RANGE_OPTION_DAYJS_MAP[DEFAULT_TIME_RANGE],
timeRangeOption: DEFAULT_TIME_RANGE,
timelineConfig: computeTimelineConfig(TIME_RANGE_OPTION_DAYJS_MAP[DEFAULT_TIME_RANGE]),
});

interface SearchState {
Expand Down Expand Up @@ -59,13 +62,21 @@ interface SearchState {
*/
timeRangeOption: TIME_RANGE_OPTION;

/**
* Time range and bucket duration for the timeline. The timeline config should
* only be updated when queries are submitted and not when the range picker
* selection is changed.
*/
timelineConfig: TimelineConfig;

updateAggregationJobId: (id: string | null) => void;
updateQueryIsCaseSensitive: (newValue: boolean) => void;
updateQueryString: (query: string) => void;
updateSearchJobId: (id: string | null) => void;
updateSearchUiState: (state: SEARCH_UI_STATE) => void;
updateTimeRange: (range: [dayjs.Dayjs, dayjs.Dayjs]) => void;
updateTimeRangeOption: (option: TIME_RANGE_OPTION) => void;
updateTimelineConfig: (config: TimelineConfig) => void;
}

const useSearchStore = create<SearchState>((set) => ({
Expand All @@ -91,6 +102,9 @@ const useSearchStore = create<SearchState>((set) => ({
updateTimeRangeOption: (option: TIME_RANGE_OPTION) => {
set({timeRangeOption: option});
},
updateTimelineConfig: (config) => {
set({timelineConfig: config});
},
}));


Expand Down