Skip to content

Commit 07bf4bd

Browse files
committed
fix(webapp): preserve columns on clear-filters and read escaped sample paths
- Carry the current cols/hide/sc params through the clear-filters form as hidden inputs so clearing filters no longer wipes a customized column layout. - Make getAtPath (and labelFromPath) understand the backslash escaping that childPath emits for bracket keys, so picking a sample value whose key contains a quote or backslash resolves instead of showing an empty column. Adds a round-trip test.
1 parent c5756f5 commit 07bf4bd

3 files changed

Lines changed: 25 additions & 5 deletions

File tree

apps/webapp/app/components/runs/v3/RunFilters.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,15 @@ export function RunsFilters(props: RunFiltersProps) {
406406
<FilterMenu {...props} />
407407
{hasFilters && (
408408
<Form className="-ml-1 h-6">
409+
{searchParams.getAll("cols").map((v, i) => (
410+
<input key={`cols-${i}`} type="hidden" name="cols" value={v} />
411+
))}
412+
{searchParams.getAll("hide").map((v, i) => (
413+
<input key={`hide-${i}`} type="hidden" name="hide" value={v} />
414+
))}
415+
{searchParams.getAll("sc").map((v, i) => (
416+
<input key={`sc-${i}`} type="hidden" name="sc" value={v} />
417+
))}
409418
<Button
410419
variant="minimal/small"
411420
LeadingIcon={XMarkIcon}

apps/webapp/app/components/runs/v3/smartColumnData.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,11 @@ describe("getAtPath", () => {
9494
expect(getAtPath(obj, "$.a[b]")).toBeUndefined();
9595
});
9696

97+
it("reads bracket keys with escaped quotes and backslashes (the form childPath emits)", () => {
98+
expect(getAtPath({ "a'b": 1 }, "$['a\\'b']")).toBe(1);
99+
expect(getAtPath({ "a\\b": 2 }, "$['a\\\\b']")).toBe(2);
100+
});
101+
97102
it("computes a dot-accessed .length for arrays, strings, and objects", () => {
98103
const data = { tags: ["a", "b", "c"], name: "hello", info: { x: 1, y: 2 }, count: 5 };
99104
expect(getAtPath(data, "$.tags.length")).toBe(3);

apps/webapp/app/components/runs/v3/smartColumnData.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,12 @@ export function extractSmartValue(parsed: ParsedSource, path: string): SmartCell
5555
return { state: "value", value };
5656
}
5757

58-
const PATH_TOKEN_RE = /\.([^.[\]]+)|\[(\d+)\]|\['([^']*)'\]|\["([^"]*)"\]/g;
58+
const PATH_TOKEN_RE = /\.([^.[\]]+)|\[(\d+)\]|\['((?:\\.|[^'\\])*)'\]|\["((?:\\.|[^"\\])*)"\]/g;
59+
60+
/** Reverse the backslash escaping applied to bracket-notation keys (e.g. `\'` -> `'`). */
61+
function unescapeBracketKey(raw: string): string {
62+
return raw.replace(/\\(.)/g, "$1");
63+
}
5964

6065
type PathToken =
6166
| { kind: "dot"; key: string }
@@ -90,8 +95,8 @@ export function getAtPath(root: unknown, path: string): unknown {
9095

9196
if (match[1] !== undefined) tokens.push({ kind: "dot", key: match[1] });
9297
else if (match[2] !== undefined) tokens.push({ kind: "index", index: Number(match[2]) });
93-
else if (match[3] !== undefined) tokens.push({ kind: "key", key: match[3] });
94-
else if (match[4] !== undefined) tokens.push({ kind: "key", key: match[4] });
98+
else if (match[3] !== undefined) tokens.push({ kind: "key", key: unescapeBracketKey(match[3]) });
99+
else if (match[4] !== undefined) tokens.push({ kind: "key", key: unescapeBracketKey(match[4]) });
95100
}
96101
if (lastIndex !== normalized.length) return undefined;
97102

@@ -129,12 +134,13 @@ export function labelFromPath(path: string): string {
129134
normalized = `.${normalized}`;
130135
}
131136

132-
const re = /\.([^.[\]]+)|\[(\d+)\]|\['([^']*)'\]|\["([^"]*)"\]/g;
137+
const re = /\.([^.[\]]+)|\[(\d+)\]|\['((?:\\.|[^'\\])*)'\]|\["((?:\\.|[^"\\])*)"\]/g;
133138
let lastKey: string | undefined;
134139
let lastSegment: string | undefined;
135140
let match: RegExpExecArray | null;
136141
while ((match = re.exec(normalized)) !== null) {
137-
const key = match[1] ?? match[3] ?? match[4];
142+
const bracketKey = match[3] ?? match[4];
143+
const key = match[1] ?? (bracketKey !== undefined ? unescapeBracketKey(bracketKey) : undefined);
138144
if (key !== undefined) {
139145
lastKey = key;
140146
lastSegment = key;

0 commit comments

Comments
 (0)