Skip to content

Commit a8be3ae

Browse files
committed
feat(webapp,clickhouse): add bounded global log search
1 parent 1114d9d commit a8be3ae

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
@@ -823,7 +823,7 @@ export function SideMenu({
823823
});
824824
}
825825

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

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
@@ -1925,20 +1925,13 @@ const EnvironmentSchema = z
19251925
.nonnegative()
19261926
.optional(),
19271927

1928-
// Logs list pagination tuning (page sizing + recent-first probe windows).
1928+
// v2 is populated forward-only. Keep reads on v1 until v2 has enough history or has been
1929+
// backfilled, then opt in explicitly per deployment.
1930+
LOGS_SEARCH_TABLE_VERSION: z.enum(["v1", "v2"]).default("v1"),
1931+
1932+
// Logs list pagination tuning.
19291933
LOGS_LIST_DEFAULT_PAGE_SIZE: z.coerce.number().int().positive().default(50),
19301934
LOGS_LIST_MAX_PAGE_SIZE: z.coerce.number().int().positive().default(100),
1931-
// Days back from the page ceiling to probe before widening to the full requested window,
1932-
// comma-separated. Empty disables narrowing (a single full-window query).
1933-
LOGS_LIST_RECENT_FIRST_PROBE_DAYS: z
1934-
.string()
1935-
.default("1,7")
1936-
.transform((s) =>
1937-
s
1938-
.split(",")
1939-
.map((v) => Number(v.trim()))
1940-
.filter((n) => Number.isFinite(n) && n > 0)
1941-
),
19421935

19431936
// Query feature flag
19441937
QUERY_FEATURE_ENABLED: z.string().default("1"),

0 commit comments

Comments
 (0)