Skip to content

Commit afc53fc

Browse files
committed
feat(webapp,clickhouse): add bounded global log search
1 parent 53ca44d commit afc53fc

16 files changed

Lines changed: 513 additions & 300 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
area: webapp
3+
type: improvement
4+
---
5+
6+
Global log search now supports a bounded search index and clearer time-range expansion while keeping existing search history available during rollout.

apps/webapp/app/components/navigation/SideMenu.tsx

Lines changed: 54 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -825,7 +825,7 @@ export function SideMenu({
825825
});
826826
}
827827

828-
if (isAdmin || featureFlags.hasQueryAccess) {
828+
if (isAdmin || featureFlags.hasQueryAccess || featureFlags.hasLogsPageAccess) {
829829
staticSections.push({
830830
id: "metrics",
831831
title: "Observability",
@@ -843,55 +843,59 @@ export function SideMenu({
843843
} satisfies SideMenuItemConfig,
844844
]
845845
: []),
846-
{
847-
id: "errors",
848-
name: "Errors",
849-
icon: BugIcon,
850-
activeIconColor: "text-errors",
851-
to: v3ErrorsPath(organization, project, environment),
852-
dataAction: "errors",
853-
},
854-
{
855-
id: "query",
856-
name: "Query",
857-
icon: CodeSquareIcon,
858-
activeIconColor: "text-query",
859-
to: queryPath(organization, project, environment),
860-
dataAction: "query",
861-
},
862-
{
863-
id: "queues",
864-
name: "Queues",
865-
icon: QueuesIcon,
866-
activeIconColor: "text-queues",
867-
to: v3QueuesPath(organization, project, environment),
868-
dataAction: "queues",
869-
},
870-
{
871-
id: "dashboards",
872-
name: "Dashboards",
873-
icon: ChartBarIcon,
874-
activeIconColor: "text-metrics",
875-
to: v3DashboardsLandingPath(organization, project, environment),
876-
dataAction: "dashboards-landing",
877-
action: (
878-
<CreateDashboardButton
879-
organization={organization}
880-
project={project}
881-
environment={environment}
882-
isCollapsed={isCollapsed}
883-
/>
884-
),
885-
after: (
886-
<DashboardList
887-
organization={organization}
888-
project={project}
889-
environment={environment}
890-
isCollapsed={isCollapsed}
891-
user={user}
892-
/>
893-
),
894-
},
846+
...(isAdmin || featureFlags.hasQueryAccess
847+
? [
848+
{
849+
id: "errors",
850+
name: "Errors",
851+
icon: BugIcon,
852+
activeIconColor: "text-errors",
853+
to: v3ErrorsPath(organization, project, environment),
854+
dataAction: "errors",
855+
},
856+
{
857+
id: "query",
858+
name: "Query",
859+
icon: CodeSquareIcon,
860+
activeIconColor: "text-query",
861+
to: queryPath(organization, project, environment),
862+
dataAction: "query",
863+
},
864+
{
865+
id: "queues",
866+
name: "Queues",
867+
icon: QueuesIcon,
868+
activeIconColor: "text-queues",
869+
to: v3QueuesPath(organization, project, environment),
870+
dataAction: "queues",
871+
},
872+
{
873+
id: "dashboards",
874+
name: "Dashboards",
875+
icon: ChartBarIcon,
876+
activeIconColor: "text-metrics",
877+
to: v3DashboardsLandingPath(organization, project, environment),
878+
dataAction: "dashboards-landing",
879+
action: (
880+
<CreateDashboardButton
881+
organization={organization}
882+
project={project}
883+
environment={environment}
884+
isCollapsed={isCollapsed}
885+
/>
886+
),
887+
after: (
888+
<DashboardList
889+
organization={organization}
890+
project={project}
891+
environment={environment}
892+
isCollapsed={isCollapsed}
893+
user={user}
894+
/>
895+
),
896+
},
897+
]
898+
: []),
895899
],
896900
});
897901
}

apps/webapp/app/components/primitives/SearchInput.tsx

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export type SearchInputProps = {
1414
/** Additional URL params to reset when searching or clearing (e.g. pagination). Defaults to ["cursor", "direction"]. */
1515
resetParams?: string[];
1616
autoFocus?: boolean;
17+
minLength?: number;
1718
/**
1819
* Controlled value. When provided alongside `onValueChange`, the input
1920
* skips URL params entirely and acts as a controlled component — useful
@@ -34,6 +35,7 @@ export function SearchInput({
3435
paramName = "search",
3536
resetParams = ["cursor", "direction"],
3637
autoFocus,
38+
minLength,
3739
value: controlledValue,
3840
onValueChange,
3941
}: SearchInputProps) {
@@ -77,13 +79,20 @@ export function SearchInput({
7779
};
7880

7981
const handleSubmit = () => {
82+
const trimmedText = text.trim();
83+
if (minLength !== undefined && trimmedText.length > 0 && [...trimmedText].length < minLength) {
84+
inputRef.current?.setCustomValidity(`Enter at least ${minLength} characters`);
85+
inputRef.current?.reportValidity();
86+
return;
87+
}
88+
inputRef.current?.setCustomValidity("");
8089
if (isControlled) {
8190
// Live updates already fired through onValueChange; submit is a no-op.
8291
return;
8392
}
8493
const resetValues = Object.fromEntries(resetParams.map((p) => [p, undefined]));
85-
if (text.trim()) {
86-
replace({ [paramName]: text.trim(), ...resetValues });
94+
if (trimmedText) {
95+
replace({ [paramName]: trimmedText, ...resetValues });
8796
} else {
8897
del([paramName, ...resetParams]);
8998
}
@@ -116,7 +125,10 @@ export function SearchInput({
116125
variant="secondary-small"
117126
placeholder={placeholder}
118127
value={text}
119-
onChange={(e) => updateText(e.target.value)}
128+
onChange={(e) => {
129+
e.currentTarget.setCustomValidity("");
130+
updateText(e.target.value);
131+
}}
120132
fullWidth
121133
autoFocus={autoFocus}
122134
className={cn("", isFocused && "placeholder:text-text-dimmed/70")}

apps/webapp/app/env.server.ts

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2094,20 +2094,13 @@ const EnvironmentSchema = z
20942094
.nonnegative()
20952095
.optional(),
20962096

2097-
// Logs list pagination tuning (page sizing + recent-first probe windows).
2097+
// v2 is populated forward-only. Keep reads on v1 until v2 has enough history or has been
2098+
// backfilled, then opt in explicitly per deployment.
2099+
LOGS_SEARCH_TABLE_VERSION: z.enum(["v1", "v2"]).default("v1"),
2100+
2101+
// Logs list pagination tuning.
20982102
LOGS_LIST_DEFAULT_PAGE_SIZE: z.coerce.number().int().positive().default(50),
20992103
LOGS_LIST_MAX_PAGE_SIZE: z.coerce.number().int().positive().default(100),
2100-
// Days back from the page ceiling to probe before widening to the full requested window,
2101-
// comma-separated. Empty disables narrowing (a single full-window query).
2102-
LOGS_LIST_RECENT_FIRST_PROBE_DAYS: z
2103-
.string()
2104-
.default("1,7")
2105-
.transform((s) =>
2106-
s
2107-
.split(",")
2108-
.map((v) => Number(v.trim()))
2109-
.filter((n) => Number.isFinite(n) && n > 0)
2110-
),
21112104

21122105
// Query feature flag
21132106
QUERY_FEATURE_ENABLED: z.string().default("1"),

0 commit comments

Comments
 (0)