@@ -2,6 +2,7 @@ import * as Ariakit from "@ariakit/react";
22import {
33 CalendarIcon ,
44 ClockIcon ,
5+ CpuChipIcon ,
56 FingerPrintIcon ,
67 RectangleStackIcon ,
78 Squares2X2Icon ,
@@ -182,6 +183,9 @@ export const TaskRunListSearchFilters = z.object({
182183 `Machine presets to filter by (${ machines . join ( ", " ) } )`
183184 ) ,
184185 errorId : z . string ( ) . optional ( ) . describe ( "Error ID to filter runs by (e.g. error_abc123)" ) ,
186+ sources : StringOrStringArray . describe (
187+ "Task trigger sources to filter by (STANDARD, SCHEDULED, AGENT)"
188+ ) ,
185189} ) ;
186190
187191export type TaskRunListSearchFilters = z . infer < typeof TaskRunListSearchFilters > ;
@@ -223,6 +227,8 @@ export function filterTitle(filterKey: string) {
223227 return "Version" ;
224228 case "errorId" :
225229 return "Error ID" ;
230+ case "sources" :
231+ return "Source" ;
226232 default :
227233 return filterKey ;
228234 }
@@ -263,6 +269,8 @@ export function filterIcon(filterKey: string): ReactNode | undefined {
263269 return < IconRotateClockwise2 className = "size-4" /> ;
264270 case "errorId" :
265271 return < IconBugFilled className = "size-4" /> ;
272+ case "sources" :
273+ return < CpuChipIcon className = "size-4" /> ;
266274 default :
267275 return undefined ;
268276 }
@@ -310,6 +318,10 @@ export function getRunFiltersFromSearchParams(
310318 ? searchParams . getAll ( "versions" )
311319 : undefined ,
312320 errorId : searchParams . get ( "errorId" ) ?? undefined ,
321+ sources :
322+ searchParams . getAll ( "sources" ) . filter ( ( v ) => v . length > 0 ) . length > 0
323+ ? searchParams . getAll ( "sources" )
324+ : undefined ,
313325 } ;
314326
315327 const parsed = TaskRunListSearchFilters . safeParse ( params ) ;
@@ -351,7 +363,8 @@ export function RunsFilters(props: RunFiltersProps) {
351363 searchParams . has ( "queues" ) ||
352364 searchParams . has ( "machines" ) ||
353365 searchParams . has ( "versions" ) ||
354- searchParams . has ( "errorId" ) ;
366+ searchParams . has ( "errorId" ) ||
367+ searchParams . has ( "sources" ) ;
355368
356369 return (
357370 < div className = "flex flex-row flex-wrap items-center gap-1" >
@@ -388,6 +401,7 @@ const filterTypes = [
388401 { name : "schedule" , title : "Schedule ID" , icon : < ClockIcon className = "size-4" /> } ,
389402 { name : "bulk" , title : "Bulk action" , icon : < ListCheckedIcon className = "size-4" /> } ,
390403 { name : "error" , title : "Error ID" , icon : < IconBugFilled className = "size-4" /> } ,
404+ { name : "source" , title : "Source" , icon : < CpuChipIcon className = "size-4" /> } ,
391405] as const ;
392406
393407type FilterType = ( typeof filterTypes ) [ number ] [ "name" ] ;
@@ -443,6 +457,7 @@ function AppliedFilters({ possibleTasks, bulkActions }: RunFiltersProps) {
443457 < AppliedScheduleIdFilter />
444458 < AppliedBulkActionsFilter bulkActions = { bulkActions } />
445459 < AppliedErrorIdFilter />
460+ < AppliedSourceFilter />
446461 </ >
447462 ) ;
448463}
@@ -481,6 +496,8 @@ function Menu(props: MenuProps) {
481496 return < VersionsDropdown onClose = { ( ) => props . setFilterType ( undefined ) } { ...props } /> ;
482497 case "error" :
483498 return < ErrorIdDropdown onClose = { ( ) => props . setFilterType ( undefined ) } { ...props } /> ;
499+ case "source" :
500+ return < SourceDropdown onClose = { ( ) => props . setFilterType ( undefined ) } { ...props } /> ;
484501 }
485502}
486503
@@ -1869,3 +1886,101 @@ function AppliedErrorIdFilter() {
18691886 </ FilterMenuProvider >
18701887 ) ;
18711888}
1889+
1890+ const sourceOptions : { value : TaskTriggerSource ; title : string } [ ] = [
1891+ { value : "STANDARD" , title : "Standard" } ,
1892+ { value : "SCHEDULED" , title : "Scheduled" } ,
1893+ { value : "AGENT" , title : "Agent" } ,
1894+ ] ;
1895+
1896+ function SourceDropdown ( {
1897+ trigger,
1898+ clearSearchValue,
1899+ searchValue,
1900+ onClose,
1901+ } : {
1902+ trigger : ReactNode ;
1903+ clearSearchValue : ( ) => void ;
1904+ searchValue : string ;
1905+ onClose ?: ( ) => void ;
1906+ } ) {
1907+ const { values, replace } = useSearchParams ( ) ;
1908+
1909+ const handleChange = ( values : string [ ] ) => {
1910+ clearSearchValue ( ) ;
1911+ replace ( { sources : values , cursor : undefined , direction : undefined } ) ;
1912+ } ;
1913+
1914+ const filtered = useMemo ( ( ) => {
1915+ return sourceOptions . filter ( ( item ) =>
1916+ item . title . toLowerCase ( ) . includes ( searchValue . toLowerCase ( ) )
1917+ ) ;
1918+ } , [ searchValue ] ) ;
1919+
1920+ return (
1921+ < SelectProvider value = { values ( "sources" ) } setValue = { handleChange } virtualFocus = { true } >
1922+ { trigger }
1923+ < SelectPopover
1924+ className = "min-w-0 max-w-[min(240px,var(--popover-available-width))]"
1925+ hideOnEscape = { ( ) => {
1926+ if ( onClose ) {
1927+ onClose ( ) ;
1928+ return false ;
1929+ }
1930+ return true ;
1931+ } }
1932+ >
1933+ < ComboBox placeholder = { "Filter by source..." } value = { searchValue } />
1934+ < SelectList >
1935+ { filtered . map ( ( item , index ) => (
1936+ < SelectItem
1937+ key = { item . value }
1938+ value = { item . value }
1939+ icon = {
1940+ < TaskTriggerSourceIcon source = { item . value } className = "size-4 flex-none" />
1941+ }
1942+ shortcut = { shortcutFromIndex ( index , { shortcutsEnabled : true } ) }
1943+ >
1944+ { item . title }
1945+ </ SelectItem >
1946+ ) ) }
1947+ </ SelectList >
1948+ </ SelectPopover >
1949+ </ SelectProvider >
1950+ ) ;
1951+ }
1952+
1953+ function AppliedSourceFilter ( ) {
1954+ const { values, del } = useSearchParams ( ) ;
1955+ const sources = values ( "sources" ) ;
1956+
1957+ if ( sources . length === 0 || sources . every ( ( v ) => v === "" ) ) {
1958+ return null ;
1959+ }
1960+
1961+ return (
1962+ < FilterMenuProvider >
1963+ { ( search , setSearch ) => (
1964+ < SourceDropdown
1965+ trigger = {
1966+ < Ariakit . Select render = { < div className = "group cursor-pointer focus-custom" /> } >
1967+ < AppliedFilter
1968+ label = "Source"
1969+ icon = { < CpuChipIcon className = "size-4" /> }
1970+ value = { appliedSummary (
1971+ sources . map (
1972+ ( v ) => sourceOptions . find ( ( o ) => o . value === v ) ?. title ?? v
1973+ )
1974+ ) }
1975+ onRemove = { ( ) => del ( [ "sources" , "cursor" , "direction" ] ) }
1976+ variant = "secondary/small"
1977+ />
1978+ </ Ariakit . Select >
1979+ }
1980+ searchValue = { search }
1981+ clearSearchValue = { ( ) => setSearch ( "" ) }
1982+ />
1983+ ) }
1984+ </ FilterMenuProvider >
1985+ ) ;
1986+ }
0 commit comments