11import { BookOpenIcon , PlusIcon } from "@heroicons/react/20/solid" ;
2- import {
3- useFetcher ,
4- useLocation ,
5- useNavigation ,
6- useRevalidator ,
7- type MetaFunction ,
8- } from "@remix-run/react" ;
2+ import { useFetcher , useRevalidator , type MetaFunction } from "@remix-run/react" ;
93import { type LoaderFunctionArgs } from "@remix-run/server-runtime" ;
10- import {
11- type MutableRefObject ,
12- Suspense ,
13- useCallback ,
14- useEffect ,
15- useMemo ,
16- useRef ,
17- useState ,
18- } from "react" ;
19- import {
20- TypedAwait ,
21- typeddefer ,
22- type UseDataFunctionReturn ,
23- useTypedFetcher ,
24- useTypedLoaderData ,
25- } from "remix-typedjson" ;
4+ import { Suspense , useCallback , useEffect , useMemo , useRef , useState } from "react" ;
5+ import { TypedAwait , typeddefer , useTypedFetcher , useTypedLoaderData } from "remix-typedjson" ;
266import { z } from "zod" ;
277import { BeakerIcon } from "~/assets/icons/BeakerIcon" ;
288import { ClockIcon } from "~/assets/icons/ClockIcon" ;
@@ -52,7 +32,6 @@ import { InfoPanel } from "~/components/primitives/InfoPanel";
5232import { NavBar , PageTitle } from "~/components/primitives/PageHeader" ;
5333import { PaginationControls } from "~/components/primitives/Pagination" ;
5434import { Paragraph } from "~/components/primitives/Paragraph" ;
55- import { PulsingDot } from "~/components/primitives/PulsingDot" ;
5635import * as Property from "~/components/primitives/PropertyTable" ;
5736import {
5837 ResizableHandle ,
@@ -77,7 +56,6 @@ import { EnabledStatus } from "~/components/runs/v3/EnabledStatus";
7756import type { TaskRunListSearchFilters } from "~/components/runs/v3/RunFilters" ;
7857import { ScheduleTypeIcon , scheduleTypeName } from "~/components/runs/v3/ScheduleType" ;
7958import { TimeFilter , timeFilterFromTo } from "~/components/runs/v3/SharedFilters" ;
80- import { TaskRunsTable } from "~/components/runs/v3/TaskRunsTable" ;
8159import { ScheduleInspector } from "~/components/schedules/ScheduleInspector" ;
8260import { ScheduleLimitActions } from "~/components/schedules/ScheduleLimitActions" ;
8361import { SchedulesUsageBar } from "~/components/schedules/SchedulesUsageBar" ;
@@ -116,7 +94,7 @@ import type { loader as scheduleEditLoader } from "../_app.orgs.$organizationSlu
11694import type { loader as scheduleNewLoader } from "../_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.schedules.new/route" ;
11795import { useCurrentPlan } from "../_app.orgs.$organizationSlug/route" ;
11896import { UpsertScheduleForm } from "../resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.schedules.new/route" ;
119- import { useRunsLiveReload } from "../_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam. runs._index/useRunsLiveReload " ;
97+ import { NewRunsButton , TaskRunsList } from "~/components/ runs/v3/TaskRunsList " ;
12098
12199export const meta : MetaFunction < typeof loader > = ( { data } ) => {
122100 const slug = ( data as { task ?: TaskDetail | null } | undefined ) ?. task ?. slug ;
@@ -425,113 +403,6 @@ export default function Page() {
425403 ) ;
426404}
427405
428- type TaskRunList = NonNullable < Awaited < UseDataFunctionReturn < typeof loader > [ "runList" ] > > ;
429-
430- /**
431- * Compact "N new runs" button, shown in the page top bar to the left of the
432- * time filter when the live-reload hook has detected newer runs.
433- */
434- function NewRunsButton ( { count, onClick } : { count : number ; onClick : ( ) => void } ) {
435- return (
436- < span className = "flex duration-150 animate-in fade-in-0" >
437- < Button
438- variant = "secondary/small"
439- className = "text-text-bright"
440- onClick = { onClick }
441- LeadingIcon = { < PulsingDot className = "h-2 w-2" /> }
442- tooltip = "Refresh to see new runs"
443- aria-label = "New runs created. Refresh to see new runs."
444- >
445- { count >= 100 ? "99+ new runs" : `${ count } new ${ count === 1 ? "run" : "runs" } ` }
446- </ Button >
447- </ span >
448- ) ;
449- }
450-
451- /**
452- * Runs table with live updating. Mirrors the Runs list page: active rows are
453- * patched in place (status/timing/cost). The "N new runs" count is surfaced to
454- * the top-bar button via `onNewRunsCountChange` (count → visibility) and
455- * `showNewRunsRef` (the latest click action), since the button lives outside
456- * this deferred boundary. The task lives in the route path rather than a
457- * `tasks` filter, so we pass `taskSlug` to scope new-run detection to this task.
458- */
459- function TaskRunsList ( {
460- list,
461- taskSlug,
462- onNewRunsCountChange,
463- showNewRunsRef,
464- } : {
465- list : TaskRunList ;
466- taskSlug : string ;
467- onNewRunsCountChange : ( count : number ) => void ;
468- showNewRunsRef : MutableRefObject < ( ) => void > ;
469- } ) {
470- const organization = useOrganization ( ) ;
471- const project = useProject ( ) ;
472- const environment = useEnvironment ( ) ;
473- const navigation = useNavigation ( ) ;
474- const location = useLocation ( ) ;
475- const { has, replace } = useSearchParams ( ) ;
476- const revalidator = useRevalidator ( ) ;
477-
478- // Loading a new version of this same page (time filter / pagination change).
479- const isLoading =
480- navigation . state === "loading" &&
481- navigation . location !== undefined &&
482- navigation . location . pathname === location . pathname &&
483- navigation . location . search !== location . search ;
484-
485- const { visibleRuns, newRunsCount, dismissNewRuns, childrenStatusesBasePath } = useRunsLiveReload (
486- {
487- runs : list . runs ,
488- hasAnyRuns : list . hasAnyRuns ,
489- isLoading,
490- organizationSlug : organization . slug ,
491- projectSlug : project . slug ,
492- environmentSlug : environment . slug ,
493- taskSlug,
494- }
495- ) ;
496-
497- const onClickShowNewRuns = ( ) => {
498- const isPaginated = has ( "cursor" ) || has ( "direction" ) ;
499- dismissNewRuns ( ) ;
500- if ( isPaginated ) {
501- replace ( { cursor : undefined , direction : undefined } ) ;
502- return ;
503- }
504- revalidator . revalidate ( ) ;
505- } ;
506-
507- // Surface the banner to the top-bar button rendered by Page: keep the ref's
508- // action current, mirror the count up, and clear it when this boundary
509- // unmounts (e.g. the table re-suspends on a filter change).
510- useEffect ( ( ) => {
511- showNewRunsRef . current = onClickShowNewRuns ;
512- } , [ onClickShowNewRuns , showNewRunsRef ] ) ;
513- useEffect ( ( ) => {
514- onNewRunsCountChange ( newRunsCount ) ;
515- } , [ newRunsCount , onNewRunsCountChange ] ) ;
516- useEffect ( ( ) => ( ) => onNewRunsCountChange ( 0 ) , [ onNewRunsCountChange ] ) ;
517-
518- return (
519- < div className = "h-full overflow-y-auto scrollbar-thin scrollbar-track-transparent scrollbar-thumb-surface-control" >
520- < TaskRunsTable
521- total = { visibleRuns . length }
522- hasFilters = { list . hasFilters }
523- filters = { list . filters }
524- runs = { visibleRuns }
525- childrenStatusesBasePath = { childrenStatusesBasePath }
526- isLoading = { isLoading }
527- variant = "dimmed"
528- showTopBorder = { false }
529- stickyHeader
530- />
531- </ div >
532- ) ;
533- }
534-
535406/**
536407 * "Create schedule" button with a limit-exceeded intercept. When the project
537408 * is already at its schedules limit, clicking opens a dialog explaining the
0 commit comments