Skip to content
Closed
Changes from all commits
Commits
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
139 changes: 133 additions & 6 deletions apps/webapp/app/components/runs/v3/SharedFilters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,20 @@ import * as Ariakit from "@ariakit/react";
import type { RuntimeEnvironment } from "@trigger.dev/database";
import parse from "parse-duration";
import type { ReactNode } from "react";
import { startTransition, useCallback, useState } from "react";
import { startTransition, useCallback, useEffect, useState } from "react";
import { AppliedFilter } from "~/components/primitives/AppliedFilter";
import { DateField } from "~/components/primitives/DateField";
import { DateTime } from "~/components/primitives/DateTime";
import { Input } from "~/components/primitives/Input";
import { Label } from "~/components/primitives/Label";
import { ComboboxProvider, SelectPopover, SelectProvider } from "~/components/primitives/Select";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "~/components/primitives/SimpleSelect";
import { useSearchParams } from "~/hooks/useSearchParam";
import { Button } from "../../primitives/Buttons";
import { filterIcon } from "./RunFilters";
Expand Down Expand Up @@ -95,6 +103,21 @@ const timePeriods = [
},
];

const timeUnits = [
{ label: "minutes", value: "m", singular: "minute" },
{ label: "hours", value: "h", singular: "hour" },
{ label: "days", value: "d", singular: "day" },
];

// Parse a period string (e.g., "90m", "2h", "7d") into value and unit
function parsePeriodString(period: string): { value: number; unit: string } | null {
const match = period.match(/^(\d+)([mhd])$/);
if (match) {
return { value: parseInt(match[1], 10), unit: match[2] };
}
return null;
}

const defaultPeriod = "7d";
const defaultPeriodMs = parse(defaultPeriod);
if (!defaultPeriodMs) {
Expand Down Expand Up @@ -175,9 +198,29 @@ export function timeFilterRenderValues({

let valueLabel: ReactNode;
switch (rangeType) {
case "period":
valueLabel = timePeriods.find((t) => t.value === period)?.label ?? period ?? defaultPeriod;
case "period": {
// First check if it's a preset period
const preset = timePeriods.find((t) => t.value === period);
if (preset) {
valueLabel = preset.label;
} else if (period) {
// Parse custom period and format nicely (e.g., "90m" -> "90 mins")
const parsed = parsePeriodString(period);
if (parsed) {
const unit = timeUnits.find((u) => u.value === parsed.unit);
if (unit) {
valueLabel = `${parsed.value} ${parsed.value === 1 ? unit.singular : unit.label}`;
} else {
valueLabel = period;
}
} else {
valueLabel = period;
}
} else {
valueLabel = defaultPeriod;
}
break;
}
case "range":
valueLabel = (
<span>
Expand Down Expand Up @@ -237,6 +280,17 @@ export function TimeFilter() {
);
}

// Get initial custom duration state from a period string
function getInitialCustomDuration(period?: string): { value: string; unit: string } {
if (period) {
const parsed = parsePeriodString(period);
if (parsed) {
return { value: parsed.value.toString(), unit: parsed.unit };
}
}
return { value: "", unit: "m" };
}

export function TimeDropdown({
trigger,
period,
Expand All @@ -253,7 +307,19 @@ export function TimeDropdown({
const [fromValue, setFromValue] = useState(from);
const [toValue, setToValue] = useState(to);

const apply = useCallback(() => {
// Custom duration state
const initialCustom = getInitialCustomDuration(period);
const [customValue, setCustomValue] = useState(initialCustom.value);
const [customUnit, setCustomUnit] = useState(initialCustom.unit);
Copy link

Choose a reason for hiding this comment

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

Custom duration state not synced with period prop changes

Medium Severity

The customValue and customUnit state variables are initialized from getInitialCustomDuration(period), but useState only uses this value on first mount. When the period prop changes (e.g., after clicking a preset button), these state values won't update to reflect the new period. This causes the custom input fields to display stale values that don't match the currently active period, confusing users about which time range is actually selected.

Fix in Cursor Fix in Web

Copy link
Member Author

Choose a reason for hiding this comment

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

@claude could you fix this?

Copy link

@claude claude bot Jan 8, 2026

Choose a reason for hiding this comment

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

Claude finished @ericallam's task in 56s —— View job


Fixed custom duration state sync issue

  • Read the current implementation
  • Add useEffect to sync customValue/customUnit with period prop changes
  • Commit and push the fix

Added a useEffect hook that syncs customValue and customUnit state when the period prop changes. This ensures the custom input fields display the correct values after selecting a preset time period or when the period changes externally.

Changes: apps/webapp/app/components/runs/v3/SharedFilters.tsx

  • Added useEffect import
  • Added effect to update custom duration state when period prop changes
    |


// Sync custom duration state when period prop changes
useEffect(() => {
const parsed = getInitialCustomDuration(period);
setCustomValue(parsed.value);
setCustomUnit(parsed.unit);
}, [period]);

const applyDateRange = useCallback(() => {
replace({
period: undefined,
cursor: undefined,
Expand Down Expand Up @@ -283,6 +349,20 @@ export function TimeDropdown({
[replace]
);

const applyCustomDuration = useCallback(() => {
const value = parseInt(customValue, 10);
if (isNaN(value) || value <= 0) {
return;
}
const periodString = `${value}${customUnit}`;
handlePeriodClick(periodString);
}, [customValue, customUnit, handlePeriodClick]);

const isCustomDurationValid = (() => {
const value = parseInt(customValue, 10);
return !isNaN(value) && value > 0;
})();

return (
<SelectProvider virtualFocus={true} open={open} setOpen={setOpen}>
{trigger}
Expand Down Expand Up @@ -318,7 +398,54 @@ export function TimeDropdown({
</div>
</div>

<div className="flex flex-col gap-4">
<div className="flex flex-col gap-1">
<Label>Custom duration</Label>
<div className="flex items-center gap-2">
<Input
type="number"
min="1"
step="1"
placeholder="e.g. 90"
value={customValue}
onChange={(e) => setCustomValue(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter" && isCustomDurationValid) {
e.preventDefault();
applyCustomDuration();
}
}}
variant="small"
fullWidth={false}
containerClassName="w-20"
/>
<Select value={customUnit} onValueChange={setCustomUnit}>
<SelectTrigger size="secondary/small">
<SelectValue />
</SelectTrigger>
<SelectContent>
{timeUnits.map((unit) => (
<SelectItem key={unit.value} value={unit.value}>
{unit.label}
</SelectItem>
))}
</SelectContent>
</Select>
<Button
variant="secondary/small"
disabled={!isCustomDurationValid}
onClick={(e) => {
e.preventDefault();
applyCustomDuration();
}}
type="button"
>
Apply
</Button>
</div>
</div>

<div className="flex flex-col gap-4 border-t border-grid-bright pt-4">
<Label className="text-text-dimmed">Or specify exact time range</Label>
<div className="flex flex-col gap-1">
<Label>
From <span className="text-text-dimmed">(local time)</span>
Expand Down Expand Up @@ -370,7 +497,7 @@ export function TimeDropdown({
disabled={!fromValue && !toValue}
onClick={(e) => {
e.preventDefault();
apply();
applyDateRange();
}}
type="button"
>
Expand Down