@@ -2,6 +2,7 @@ import * as Ariakit from "@ariakit/react";
22import {
33 CalendarIcon ,
44 ClockIcon ,
5+ CpuChipIcon ,
56 FingerPrintIcon ,
67 RectangleStackIcon ,
78 Squares2X2Icon ,
@@ -184,6 +185,9 @@ export const TaskRunListSearchFilters = z.object({
184185 `Machine presets to filter by (${ machines . join ( ", " ) } )`
185186 ) ,
186187 errorId : z . string ( ) . optional ( ) . describe ( "Error ID to filter runs by (e.g. error_abc123)" ) ,
188+ sources : StringOrStringArray . describe (
189+ "Task trigger sources to filter by (STANDARD, SCHEDULED, AGENT)"
190+ ) ,
187191} ) ;
188192
189193export type TaskRunListSearchFilters = z . infer < typeof TaskRunListSearchFilters > ;
@@ -225,6 +229,8 @@ export function filterTitle(filterKey: string) {
225229 return "Version" ;
226230 case "errorId" :
227231 return "Error ID" ;
232+ case "sources" :
233+ return "Source" ;
228234 default :
229235 return filterKey ;
230236 }
@@ -265,6 +271,8 @@ export function filterIcon(filterKey: string): ReactNode | undefined {
265271 return < IconRotateClockwise2 className = "size-4" /> ;
266272 case "errorId" :
267273 return < IconBugFilled className = "size-4" /> ;
274+ case "sources" :
275+ return < CpuChipIcon className = "size-4" /> ;
268276 default :
269277 return undefined ;
270278 }
@@ -312,6 +320,10 @@ export function getRunFiltersFromSearchParams(
312320 ? searchParams . getAll ( "versions" )
313321 : undefined ,
314322 errorId : searchParams . get ( "errorId" ) ?? undefined ,
323+ sources :
324+ searchParams . getAll ( "sources" ) . filter ( ( v ) => v . length > 0 ) . length > 0
325+ ? searchParams . getAll ( "sources" )
326+ : undefined ,
315327 } ;
316328
317329 const parsed = TaskRunListSearchFilters . safeParse ( params ) ;
@@ -353,7 +365,8 @@ export function RunsFilters(props: RunFiltersProps) {
353365 searchParams . has ( "queues" ) ||
354366 searchParams . has ( "machines" ) ||
355367 searchParams . has ( "versions" ) ||
356- searchParams . has ( "errorId" ) ;
368+ searchParams . has ( "errorId" ) ||
369+ searchParams . has ( "sources" ) ;
357370
358371 return (
359372 < div className = "flex flex-row flex-wrap items-center gap-1" >
@@ -390,6 +403,7 @@ const filterTypes = [
390403 { name : "schedule" , title : "Schedule ID" , icon : < ClockIcon className = "size-4" /> } ,
391404 { name : "bulk" , title : "Bulk action" , icon : < ListCheckedIcon className = "size-4" /> } ,
392405 { name : "error" , title : "Error ID" , icon : < IconBugFilled className = "size-4" /> } ,
406+ { name : "source" , title : "Source" , icon : < CpuChipIcon className = "size-4" /> } ,
393407] as const ;
394408
395409type FilterType = ( typeof filterTypes ) [ number ] [ "name" ] ;
@@ -445,6 +459,7 @@ function AppliedFilters({ possibleTasks, bulkActions }: RunFiltersProps) {
445459 < AppliedScheduleIdFilter />
446460 < AppliedBulkActionsFilter bulkActions = { bulkActions } />
447461 < AppliedErrorIdFilter />
462+ < AppliedSourceFilter />
448463 </ >
449464 ) ;
450465}
@@ -483,6 +498,8 @@ function Menu(props: MenuProps) {
483498 return < VersionsDropdown onClose = { ( ) => props . setFilterType ( undefined ) } { ...props } /> ;
484499 case "error" :
485500 return < ErrorIdDropdown onClose = { ( ) => props . setFilterType ( undefined ) } { ...props } /> ;
501+ case "source" :
502+ return < SourceDropdown onClose = { ( ) => props . setFilterType ( undefined ) } { ...props } /> ;
486503 }
487504}
488505
@@ -1896,3 +1913,101 @@ function AppliedErrorIdFilter() {
18961913 </ FilterMenuProvider >
18971914 ) ;
18981915}
1916+
1917+ const sourceOptions : { value : TaskTriggerSource ; title : string } [ ] = [
1918+ { value : "STANDARD" , title : "Standard" } ,
1919+ { value : "SCHEDULED" , title : "Scheduled" } ,
1920+ { value : "AGENT" , title : "Agent" } ,
1921+ ] ;
1922+
1923+ function SourceDropdown ( {
1924+ trigger,
1925+ clearSearchValue,
1926+ searchValue,
1927+ onClose,
1928+ } : {
1929+ trigger : ReactNode ;
1930+ clearSearchValue : ( ) => void ;
1931+ searchValue : string ;
1932+ onClose ?: ( ) => void ;
1933+ } ) {
1934+ const { values, replace } = useSearchParams ( ) ;
1935+
1936+ const handleChange = ( values : string [ ] ) => {
1937+ clearSearchValue ( ) ;
1938+ replace ( { sources : values , cursor : undefined , direction : undefined } ) ;
1939+ } ;
1940+
1941+ const filtered = useMemo ( ( ) => {
1942+ return sourceOptions . filter ( ( item ) =>
1943+ item . title . toLowerCase ( ) . includes ( searchValue . toLowerCase ( ) )
1944+ ) ;
1945+ } , [ searchValue ] ) ;
1946+
1947+ return (
1948+ < SelectProvider value = { values ( "sources" ) } setValue = { handleChange } virtualFocus = { true } >
1949+ { trigger }
1950+ < SelectPopover
1951+ className = "min-w-0 max-w-[min(240px,var(--popover-available-width))]"
1952+ hideOnEscape = { ( ) => {
1953+ if ( onClose ) {
1954+ onClose ( ) ;
1955+ return false ;
1956+ }
1957+ return true ;
1958+ } }
1959+ >
1960+ < ComboBox placeholder = { "Filter by source..." } value = { searchValue } />
1961+ < SelectList >
1962+ { filtered . map ( ( item , index ) => (
1963+ < SelectItem
1964+ key = { item . value }
1965+ value = { item . value }
1966+ icon = {
1967+ < TaskTriggerSourceIcon source = { item . value } className = "size-4 flex-none" />
1968+ }
1969+ shortcut = { shortcutFromIndex ( index , { shortcutsEnabled : true } ) }
1970+ >
1971+ { item . title }
1972+ </ SelectItem >
1973+ ) ) }
1974+ </ SelectList >
1975+ </ SelectPopover >
1976+ </ SelectProvider >
1977+ ) ;
1978+ }
1979+
1980+ function AppliedSourceFilter ( ) {
1981+ const { values, del } = useSearchParams ( ) ;
1982+ const sources = values ( "sources" ) ;
1983+
1984+ if ( sources . length === 0 || sources . every ( ( v ) => v === "" ) ) {
1985+ return null ;
1986+ }
1987+
1988+ return (
1989+ < FilterMenuProvider >
1990+ { ( search , setSearch ) => (
1991+ < SourceDropdown
1992+ trigger = {
1993+ < Ariakit . Select render = { < div className = "group cursor-pointer focus-custom" /> } >
1994+ < AppliedFilter
1995+ label = "Source"
1996+ icon = { < CpuChipIcon className = "size-4" /> }
1997+ value = { appliedSummary (
1998+ sources . map (
1999+ ( v ) => sourceOptions . find ( ( o ) => o . value === v ) ?. title ?? v
2000+ )
2001+ ) }
2002+ onRemove = { ( ) => del ( [ "sources" , "cursor" , "direction" ] ) }
2003+ variant = "secondary/small"
2004+ />
2005+ </ Ariakit . Select >
2006+ }
2007+ searchValue = { search }
2008+ clearSearchValue = { ( ) => setSearch ( "" ) }
2009+ />
2010+ ) }
2011+ </ FilterMenuProvider >
2012+ ) ;
2013+ }
0 commit comments