Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions ui/src/api/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ export interface FetchResult<T> {
status: number
}

// Append a cache-busting query parameter rounded to the current minute.
// Requests within the same minute share the same cache key.
function cacheBustUrl(url: string): string {
// Append a cache-busting query parameter rounded to the given interval.
// Requests within the same interval share the same cache key.
function cacheBustUrl(url: string, intervalSec = 60): string {
const separator = url.includes('?') ? '&' : '?'
return `${url}${separator}_t=${Math.floor(Date.now() / 60000)}`
return `${url}${separator}_t=${Math.floor(Date.now() / (intervalSec * 1000))}`
}

// Check if the content type indicates JSON
Expand All @@ -23,22 +23,23 @@ function isJsonContentType(response: Response): boolean {
export async function fetchViaS3(
url: string,
init?: RequestInit,
cacheBustInterval?: number,
): Promise<Response> {
const resp = await fetch(cacheBustUrl(url), { credentials: 'include' })
const resp = await fetch(cacheBustUrl(url, cacheBustInterval), { credentials: 'include' })
if (!resp.ok) return resp

const { url: presignedUrl } = await resp.json()

return fetch(presignedUrl, init)
}

export async function fetchData<T>(path: string): Promise<FetchResult<T>> {
export async function fetchData<T>(path: string, opts?: { cacheBustInterval?: number }): Promise<FetchResult<T>> {
const config = await loadRuntimeConfig()
const url = getDataUrl(path, config)

const response = isS3Mode(config)
? await fetchViaS3(url)
: await fetch(cacheBustUrl(url))
? await fetchViaS3(url, undefined, opts?.cacheBustInterval)
: await fetch(cacheBustUrl(url, opts?.cacheBustInterval))

if (!response.ok) {
return { data: null, status: response.status }
Expand Down
2 changes: 1 addition & 1 deletion ui/src/api/hooks/useSuite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export function useSuite(suiteHash: string | undefined) {
return useQuery({
queryKey: ['suite', suiteHash],
queryFn: async () => {
const { data, status } = await fetchData<SuiteInfo>(`suites/${suiteHash}/summary.json`)
const { data, status } = await fetchData<SuiteInfo>(`suites/${suiteHash}/summary.json`, { cacheBustInterval: 3600 })
if (!data) {
throw new Error(`Failed to fetch suite: ${status}`)
}
Expand Down
17 changes: 12 additions & 5 deletions ui/src/components/runs/RunFilters.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Listbox, ListboxButton, ListboxOption, ListboxOptions } from '@headlessui/react'
import clsx from 'clsx'
import { JDenticon } from '@/components/shared/JDenticon'

export type TestStatusFilter = 'all' | 'passing' | 'failing'

Expand All @@ -12,7 +13,7 @@ interface RunFiltersProps {
onImageChange: (image: string | undefined) => void
selectedStatus: TestStatusFilter
onStatusChange: (status: TestStatusFilter) => void
suites?: string[]
suites?: { hash: string; name?: string }[]
selectedSuite?: string | undefined
onSuiteChange?: (suite: string | undefined) => void
}
Expand Down Expand Up @@ -40,7 +41,7 @@ function FilterDropdown<T extends string>({
label: string
value: T | ''
onChange: (value: T | '') => void
options: { value: T | ''; label: string }[]
options: { value: T | ''; label: string; icon?: React.ReactNode }[]
allLabel: string
width?: string
}) {
Expand All @@ -56,7 +57,10 @@ function FilterDropdown<T extends string>({
width,
)}
>
<span className="block truncate">{selectedOption?.label ?? allLabel}</span>
<span className="flex items-center gap-1.5 truncate">
{selectedOption?.icon}
{selectedOption?.label ?? allLabel}
</span>
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
<ChevronIcon />
</span>
Expand All @@ -73,7 +77,10 @@ function FilterDropdown<T extends string>({
)
}
>
{option.label}
<span className="flex items-center gap-1.5">
{option.icon}
{option.label}
</span>
</ListboxOption>
))}
</ListboxOptions>
Expand Down Expand Up @@ -103,7 +110,7 @@ export function RunFilters({
{ value: 'passing', label: 'Passing only' },
{ value: 'failing', label: 'Has failures' },
]
const suiteOptions = suites ? [{ value: '' as const, label: 'All suites' }, ...suites.map((s) => ({ value: s, label: s }))] : []
const suiteOptions = suites ? [{ value: '' as const, label: 'All suites' }, ...suites.map((s) => ({ value: s.hash, label: s.name ? `${s.name} (${s.hash.slice(0, 4)})` : s.hash, icon: <JDenticon value={s.hash} size={16} /> }))] : []

return (
<div className="flex flex-wrap items-center gap-4">
Expand Down
Loading