feat(site): native picker, [now] shortcut, and Now label in DateTimeRangeFilter - #28318
feat(site): native picker, [now] shortcut, and Now label in DateTimeRangeFilter#28318johnstcn wants to merge 3 commits into
Conversation
The examples footer is now clickable: Now fills To, the current HH:mm and a datetime 24 hours ago fill From, each derived from the current time. TimeRange bounds are now optional; a partial range (from a hand-written one-sided filter query) shows Custom in the trigger and an empty field in the popover, with Apply requiring both bounds so the popover always commits a full range.
| <input | ||
| ref={fromPickerRef} | ||
| type="datetime-local" | ||
| tabIndex={-1} | ||
| aria-hidden="true" | ||
| className="pointer-events-none absolute h-0 w-0 opacity-0" | ||
| // Chrome and WebKit fire input per pick; change fires only | ||
| // on dismissal, which is too late for live feedback. | ||
| onInput={(event) => pickFrom(event.currentTarget.value)} | ||
| /> |
There was a problem hiding this comment.
Open question: this seems to render in the OS-specific colour theme (light / dark). I haven't figured out how to make it sync with the site theme without touching index.css. I'm open to other suggestions.
There was a problem hiding this comment.
Pull request overview
This PR enhances the shared DateTimeRangeFilter used on the AI Gateway Sessions page by combining free-text time expressions with a native datetime-local picker, improving “now” UX, and correctly reflecting partial (one-sided) time ranges in the trigger and popover.
Changes:
- Introduces
FullTimeRange(committed ranges) alongsideTimeRange(optionally partial/display ranges), and updates Sessions page parsing to support partial ranges. - Adds native datetime pickers (via hidden
datetime-localinputs) next to From/To fields, plus a[now]shortcut for the To bound. - Updates trigger labeling to show
Nowfor live end bounds (with a tolerance window) andCustomfor partial ranges; updates tests/stories accordingly.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| site/src/pages/AIBridgePage/ListSessionsPage/timeRange.ts | Makes default/query builders require FullTimeRange and updates parsing to return partial ranges for display. |
| site/src/pages/AIBridgePage/ListSessionsPage/timeRange.test.ts | Updates tests for partial-range parsing and malformed-bound behavior. |
| site/src/pages/AIBridgePage/ListSessionsPage/ListSessionsPageView.stories.tsx | Updates story fixtures to use FullTimeRange where committed values are required. |
| site/src/pages/AIBridgePage/ListSessionsPage/ListSessionsFilter.tsx | Adjusts filter props to accept partial timeRange while committing FullTimeRange on change. |
| site/src/pages/AIBridgePage/ListSessionsPage/ListSessionsFilter.stories.tsx | Updates story fixtures to use FullTimeRange. |
| site/src/components/DateTimeRangeFilter/timeRange.ts | Adds FullTimeRange, supports T-separated picker format, and introduces isLiveNow + Custom labeling for partial ranges. |
| site/src/components/DateTimeRangeFilter/timeRange.test.ts | Adds coverage for picker datetime parsing, partial-range trigger label, and “Now” labeling behavior. |
| site/src/components/DateTimeRangeFilter/DateTimeRangeFilter.tsx | Implements native picker integration, [now] shortcut, partial-range UI behavior, and Apply gating requiring full valid bounds. |
| site/src/components/DateTimeRangeFilter/DateTimeRangeFilter.stories.tsx | Updates stories to reflect removed examples footer and adds stories for “Now” and partial-range behaviors. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| const openPicker = (input: HTMLInputElement | null, seed: string) => { | ||
| if (!input) { | ||
| return; | ||
| } | ||
| // Seed the picker from the current text so it opens on the right moment; | ||
| // fall back to now for empty or non-absolute expressions. The picker | ||
| // uses "YYYY-MM-DDTHH:mm" in browser-local time. | ||
| const parsed = parseTimeExpression(seed, currentTime); | ||
| input.value = dayjs(parsed ?? currentTime).format("YYYY-MM-DDTHH:mm"); | ||
| input.showPicker(); | ||
| }; |
| const pickFrom = useEffectEvent((value: string) => { | ||
| const parsed = parseTimeExpression(value, currentTime); | ||
| if (parsed !== null) { | ||
| setFromField({ text: formatDateTime(parsed), touched: true }); | ||
| } | ||
| }); | ||
|
|
||
| const pickTo = useEffectEvent((value: string) => { | ||
| const parsed = parseTimeExpression(value, currentTime); | ||
| if (parsed !== null) { | ||
| setToField({ text: formatDateTime(parsed), touched: true }); | ||
| } | ||
| }); |
| export type TimeRange = { | ||
| startedAfter?: Date; | ||
| startedBefore?: Date; | ||
| }; | ||
|
|
||
| /** A range with both bounds present, as committed by the popover. */ | ||
| export type FullTimeRange = { | ||
| startedAfter: Date; | ||
| startedBefore: Date; | ||
| }; |
There was a problem hiding this comment.
instead of two types, you could make use of Required or Partial, whichever direction makes the most sense to you: https://www.typescriptlang.org/docs/handbook/utility-types.html#requiredtype
|
Closing in favour of #28392 |
Follow-ups to #28255
Native picker alongside free text. Each From/To field keeps its free-text grammar (
now,HH:mm, ISO date/datetime) and gains a calendar icon that opens the nativedatetime-localpicker viashowPicker()on a hidden input seeded from the current text. The picker commits to the text field on theinputevent and marks the field touched so Apply enables. The calendar icons aretabIndex={-1}so Tab still moves From to To directly. The grammar also acceptsYYYY-MM-DDTHH:mm, the picker's machine format and a pasteable ISO form.[now] shortcut. A right-aligned
[now]button on the To label fills the field with the literalnowexpression, the live bound the picker cannot express. The clickable datetime examples are gone; the picker covers them, and blur auto-fill made the explanatory footer text redundant.Trigger reads "- Now" for a live bound. A shared
isLiveNow(one-minute tolerance) drives both the popover prefill and the trigger label, so a To bound at the current moment readsMMM D - Nowinstead ofMMM D - Today. A frozen earlier-today bound still readsToday.Partial ranges.
TimeRangebounds are now optional. Before, a hand-written one-sided query (onlystarted_afterin the search box) showed "Last 24 hours" on the trigger while the query stayed one-sided. Now the trigger showsCustomand the popover shows the present bound with an empty field for the missing one. Apply requires both bounds, so the popover always commits a full range; one-sided queries stay a deliberate free-text-only escape hatch. The page'sparseTimeRangereturns partial ranges so the trigger reflects the query. AFullTimeRangetype (both bounds present) covers the committed value and query builders whileTimeRangestays partial for the display value.Open questions: how best to style the native picker?
Screenshots
Generated by Coder Agents on behalf of @johnstcn.