Skip to content

Commit 7a252dd

Browse files
committed
Basic time filter limiting implemented
1 parent a5f543e commit 7a252dd

File tree

4 files changed

+69
-18
lines changed

4 files changed

+69
-18
lines changed

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

Lines changed: 59 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,36 +4,31 @@ import {
44
endOfDay,
55
endOfMonth,
66
endOfWeek,
7-
isSaturday,
8-
isSunday,
9-
previousSaturday,
107
startOfDay,
118
startOfMonth,
129
startOfWeek,
13-
startOfYear,
1410
subDays,
15-
subMonths,
16-
subWeeks,
11+
subWeeks
1712
} from "date-fns";
1813
import parse from "parse-duration";
1914
import { startTransition, useCallback, useEffect, useState, type ReactNode } from "react";
15+
import simplur from "simplur";
2016
import { AppliedFilter } from "~/components/primitives/AppliedFilter";
17+
import { Callout } from "~/components/primitives/Callout";
2118
import { DateTime } from "~/components/primitives/DateTime";
2219
import { DateTimePicker } from "~/components/primitives/DateTimePicker";
2320
import { Label } from "~/components/primitives/Label";
2421
import { Paragraph } from "~/components/primitives/Paragraph";
2522
import { RadioButtonCircle } from "~/components/primitives/RadioButton";
2623
import { ComboboxProvider, SelectPopover, SelectProvider } from "~/components/primitives/Select";
24+
import { useOptionalOrganization } from "~/hooks/useOrganizations";
2725
import { useSearchParams } from "~/hooks/useSearchParam";
2826
import { type ShortcutDefinition } from "~/hooks/useShortcutKeys";
2927
import { cn } from "~/utils/cn";
30-
import { Button } from "../../primitives/Buttons";
28+
import { organizationBillingPath } from "~/utils/pathBuilder";
29+
import { Button, LinkButton } from "../../primitives/Buttons";
3130
import { filterIcon } from "./RunFilters";
3231

33-
export type DisplayableEnvironment = Pick<RuntimeEnvironment, "type" | "id"> & {
34-
userName?: string;
35-
};
36-
3732
export function FilterMenuProvider({
3833
children,
3934
onClose,
@@ -128,6 +123,22 @@ function parsePeriodString(period: string): { value: number; unit: string } | nu
128123
return null;
129124
}
130125

126+
const MS_PER_DAY = 1000 * 60 * 60 * 24;
127+
128+
// Convert a period string to days using parse-duration
129+
function periodToDays(period: string): number {
130+
const ms = parse(period);
131+
if (!ms) return 0;
132+
return ms / MS_PER_DAY;
133+
}
134+
135+
// Calculate the number of days a date range spans from now
136+
function dateRangeToDays(from?: Date): number {
137+
if (!from) return 0;
138+
const now = new Date();
139+
return Math.ceil((now.getTime() - from.getTime()) / MS_PER_DAY);
140+
}
141+
131142
const DEFAULT_PERIOD = "7d";
132143
const defaultPeriodMs = parse(DEFAULT_PERIOD);
133144
if (!defaultPeriodMs) {
@@ -292,6 +303,8 @@ export interface TimeFilterProps {
292303
applyShortcut?: ShortcutDefinition | undefined;
293304
/** Callback when the user applies a time filter selection, receives the applied values */
294305
onValueChange?: (values: TimeFilterApplyValues) => void;
306+
/** When set an upgrade message will be shown if you select a period further back than this number of days */
307+
maxPeriodDays?: number;
295308
}
296309

297310
export function TimeFilter({
@@ -303,6 +316,7 @@ export function TimeFilter({
303316
hideLabel = false,
304317
applyShortcut,
305318
onValueChange,
319+
maxPeriodDays,
306320
}: TimeFilterProps = {}) {
307321
const { value } = useSearchParams();
308322
const periodValue = period ?? value("period");
@@ -339,6 +353,7 @@ export function TimeFilter({
339353
labelName={labelName}
340354
applyShortcut={applyShortcut}
341355
onValueChange={onValueChange}
356+
maxPeriodDays={maxPeriodDays}
342357
/>
343358
)}
344359
</FilterMenuProvider>
@@ -368,7 +383,7 @@ export function TimeDropdown({
368383
applyShortcut,
369384
onApply,
370385
onValueChange,
371-
maxPeriodDays
386+
maxPeriodDays,
372387
}: {
373388
trigger: ReactNode;
374389
period?: string;
@@ -383,6 +398,7 @@ export function TimeDropdown({
383398
/** When set an upgrade message will be shown if you select a period further back than this number of days */
384399
maxPeriodDays?: number;
385400
}) {
401+
const organization = useOptionalOrganization();
386402
const [open, setOpen] = useState<boolean | undefined>();
387403
const { replace } = useSearchParams();
388404
const [fromValue, setFromValue] = useState(from);
@@ -422,9 +438,28 @@ export function TimeDropdown({
422438
return !isNaN(value) && value > 0;
423439
})();
424440

441+
// Calculate if the current selection exceeds maxPeriodDays
442+
const exceedsMaxPeriod = (() => {
443+
if (!maxPeriodDays) return false;
444+
445+
if (activeSection === "duration") {
446+
const periodToCheck = selectedPeriod === "custom" ? `${customValue}${customUnit}` : selectedPeriod;
447+
if (!periodToCheck) return false;
448+
return periodToDays(periodToCheck) > maxPeriodDays;
449+
} else {
450+
// For date range, check if fromValue is further back than maxPeriodDays
451+
return dateRangeToDays(fromValue) > maxPeriodDays;
452+
}
453+
})();
454+
425455
const applySelection = useCallback(() => {
426456
setValidationError(null);
427457

458+
if (exceedsMaxPeriod) {
459+
setValidationError(`Your plan allows a maximum of ${maxPeriodDays} days. Upgrade for longer retention.`);
460+
return;
461+
}
462+
428463
if (activeSection === "duration") {
429464
// Validate custom duration
430465
if (selectedPeriod === "custom" && !isCustomDurationValid) {
@@ -759,6 +794,17 @@ export function TimeDropdown({
759794
</div>
760795
</div>
761796

797+
{/* Upgrade callout when exceeding maxPeriodDays */}
798+
{exceedsMaxPeriod && organization && (
799+
<Callout
800+
variant="pricing"
801+
cta={<LinkButton variant="primary/small" to={organizationBillingPath({ slug: organization.slug })}>Upgrade</LinkButton>}
802+
className="items-center"
803+
>
804+
{simplur`Your plan allows a maximum of ${maxPeriodDays} day[|s].`}
805+
</Callout>
806+
)}
807+
762808
{/* Action buttons */}
763809
<div className="flex justify-between gap-1 border-t border-grid-bright px-0 pt-3">
764810
<Button
@@ -786,6 +832,7 @@ export function TimeDropdown({
786832
applySelection();
787833
}}
788834
type="button"
835+
disabled={exceedsMaxPeriod}
789836
>
790837
Apply
791838
</Button>

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.query/route.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ import parse from "parse-duration";
7272
import { SimpleTooltip } from "~/components/primitives/Tooltip";
7373
import { Dialog, DialogContent, DialogHeader, DialogPortal, DialogTrigger } from "~/components/primitives/Dialog";
7474
import { DialogOverlay } from "@radix-ui/react-dialog";
75+
import { useCurrentPlan } from "../_app.orgs.$organizationSlug/route";
7576

7677
/** Convert a Date or ISO string to ISO string format */
7778
function toISOString(value: Date | string): string {
@@ -388,6 +389,8 @@ const QueryEditorForm = forwardRef<
388389
const [scope, setScope] = useState<QueryScope>(defaultScope);
389390
const formRef = useRef<HTMLFormElement>(null);
390391
const prevFetcherState = useRef(fetcher.state);
392+
const plan = useCurrentPlan();
393+
const maxPeriodDays = plan?.v3Subscription?.plan?.limits?.queryPeriodDays?.number;
391394

392395
// Notify parent when query is submitted (for title generation)
393396
useEffect(() => {
@@ -509,6 +512,7 @@ const QueryEditorForm = forwardRef<
509512
fetcher.submit(formRef.current);
510513
}
511514
}}
515+
maxPeriodDays={maxPeriodDays}
512516
/>
513517
)}
514518
<Button

apps/webapp/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@
121121
"@trigger.dev/core": "workspace:*",
122122
"@trigger.dev/database": "workspace:*",
123123
"@trigger.dev/otlp-importer": "workspace:*",
124-
"@trigger.dev/platform": "1.0.21",
124+
"@trigger.dev/platform": "1.0.22",
125125
"@trigger.dev/redis-worker": "workspace:*",
126126
"@trigger.dev/sdk": "workspace:*",
127127
"@types/pg": "8.6.6",

pnpm-lock.yaml

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)