From 0dfab6a92ed012a85eaad75ff55fe284d2765489 Mon Sep 17 00:00:00 2001 From: Scr4tch587 Date: Sun, 12 Jul 2026 14:50:49 -0400 Subject: [PATCH] feat: build trigger ui --- frontend/src/components/build-panel.tsx | 148 +++++++++++++++++++++ frontend/src/components/dataset-detail.tsx | 3 + 2 files changed, 151 insertions(+) create mode 100644 frontend/src/components/build-panel.tsx diff --git a/frontend/src/components/build-panel.tsx b/frontend/src/components/build-panel.tsx new file mode 100644 index 0000000..c8863c7 --- /dev/null +++ b/frontend/src/components/build-panel.tsx @@ -0,0 +1,148 @@ +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { useState } from "react"; +import { toast } from "sonner"; + +import type { BuildResponse, DataRow, DryRunBuildResponse } from "@/lib/api"; + +import { DataTable } from "@/components/data-table"; +import { JsonModal } from "@/components/json-modal"; +import { Button } from "@/components/ui/button"; +import { Checkbox } from "@/components/ui/checkbox"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { dryRunBuild, triggerBuild } from "@/lib/api"; +import { toISODate } from "@/lib/format"; + +// dry-run output is previewed inline, not paginated; cap it to keep the dom sane +const DRY_RUN_PREVIEW_LIMIT = 50; + +// builds are heavier than reads, so default to a month instead of the 5-year browse window +function defaultBuildRange(): { start: string; end: string } { + const end = new Date(); + const start = new Date(end); + start.setDate(start.getDate() - 30); + return { start: toISODate(start), end: toISODate(end) }; +} + +interface BuildPanelProps { + name: string; + version: string; +} + +/** + * triggers builds for a date range. real builds write missing timestamps to + * the db and refresh the data table; dry runs preview the produced rows + * without writing anything. + */ +export function BuildPanel({ name, version }: BuildPanelProps) { + const [startDate, setStartDate] = useState(() => defaultBuildRange().start); + const [endDate, setEndDate] = useState(() => defaultBuildRange().end); + const [dryRun, setDryRun] = useState(false); + const [dryRunRows, setDryRunRows] = useState(null); + const [selectedRow, setSelectedRow] = useState | null>(null); + + const queryClient = useQueryClient(); + + const mutation = useMutation< + BuildResponse | DryRunBuildResponse, + Error, + { dryRun: boolean } + >({ + mutationFn: (opts) => + opts.dryRun + ? dryRunBuild(name, version, startDate, endDate) + : triggerBuild(name, version, startDate, endDate), + onSuccess: (result) => { + if ("rows" in result) { + setDryRunRows(result.rows); + toast.success(`dry run produced ${result.rows.length} timestamps`); + return; + } + setDryRunRows(null); + toast.success(`build complete for ${name}/${version}`); + // refetch the data table and the has_data dot on the list view + void queryClient.invalidateQueries({ queryKey: ["data", name, version] }); + void queryClient.invalidateQueries({ queryKey: ["datasets"] }); + }, + }); + + const previewRows = dryRunRows?.slice(0, DRY_RUN_PREVIEW_LIMIT); + + return ( +
+

build data

+
+
+ + setStartDate(e.target.value)} + className="w-40" + /> +
+
+ + setEndDate(e.target.value)} + className="w-40" + /> +
+
+ setDryRun(checked === true)} + /> + +
+ +
+ + {mutation.isPending && ( +

+ builds run synchronously on the server — large ranges can take a + while... +

+ )} + + {mutation.isError && ( +
+

{mutation.error.message}

+
+ )} + + {previewRows && dryRunRows && ( +
+

+ dry run produced {dryRunRows.length} timestamps — nothing was + written to the database + {dryRunRows.length > DRY_RUN_PREVIEW_LIMIT && + ` (showing first ${DRY_RUN_PREVIEW_LIMIT})`} +

+ +
+ )} + + setSelectedRow(null)} /> +
+ ); +} diff --git a/frontend/src/components/dataset-detail.tsx b/frontend/src/components/dataset-detail.tsx index 1d7c9f4..c0f5853 100644 --- a/frontend/src/components/dataset-detail.tsx +++ b/frontend/src/components/dataset-detail.tsx @@ -1,6 +1,7 @@ import { ArrowLeftIcon } from "lucide-react"; import { useMemo, useState } from "react"; +import { BuildPanel } from "@/components/build-panel"; import { DataTable } from "@/components/data-table"; import { JsonModal } from "@/components/json-modal"; import { Button } from "@/components/ui/button"; @@ -54,6 +55,8 @@ export function DatasetDetail({ name, version, onBack }: DatasetDetailProps) { + + {isPending && (

loading data...

)}