diff --git a/apps/studio/components/interfaces/Workers/WorkersList.test.tsx b/apps/studio/components/interfaces/Workers/WorkersList.test.tsx index e78acd3b302..09bd808ad2b 100644 --- a/apps/studio/components/interfaces/Workers/WorkersList.test.tsx +++ b/apps/studio/components/interfaces/Workers/WorkersList.test.tsx @@ -18,8 +18,16 @@ const worker = (name: string, overrides: Partial = {}): Worker => ({ ...overrides, }) -const renderList = (workers: Worker[]) => - customRender() +const renderList = (workers: Worker[], onRefresh = vi.fn()) => + customRender( + + ) const rowNames = () => screen @@ -48,12 +56,19 @@ describe('WorkersList', () => { await userEvent.type(screen.getByPlaceholderText('Search by name'), 'resize') expect(rowNames()).toEqual(['resize-images']) - expect(screen.getByText('1 worker')).toBeVisible() - await userEvent.type(screen.getByPlaceholderText('Search by name'), '-nope') expect(screen.getByText('No workers match your filters')).toBeVisible() }) + it('refreshes the workers list on request', async () => { + const onRefresh = vi.fn() + renderList([worker('embed')], onRefresh) + + await userEvent.click(screen.getByRole('button', { name: 'Refresh' })) + + expect(onRefresh).toHaveBeenCalledOnce() + }) + it('pages through the workers ten at a time', async () => { const workers = Array.from({ length: 12 }, (_, index) => worker(`worker-${index}`)) renderList(workers) diff --git a/apps/studio/components/interfaces/Workers/WorkersList.tsx b/apps/studio/components/interfaces/Workers/WorkersList.tsx index 7503d8c1533..ca2aa4b9d46 100644 --- a/apps/studio/components/interfaces/Workers/WorkersList.tsx +++ b/apps/studio/components/interfaces/Workers/WorkersList.tsx @@ -1,4 +1,4 @@ -import { ChevronLeft, ChevronRight, Terminal } from 'lucide-react' +import { ChevronLeft, ChevronRight, RefreshCw, Terminal } from 'lucide-react' import Link from 'next/link' import { useRouter } from 'next/router' import { useState } from 'react' @@ -32,6 +32,8 @@ interface WorkersListProps { projectRef: string workers: Worker[] onDeploy: () => void + onRefresh: () => void + isRefreshing: boolean } const STATE_FILTERS: { value: WorkerBuildState | 'all'; label: string }[] = [ @@ -55,7 +57,13 @@ const parseStateFilter = (value: string): WorkerBuildState | 'all' => const parseAccessFilter = (value: string): WorkerAccess | 'all' => ACCESS_FILTERS.find((option) => option.value === value)?.value ?? 'all' -export const WorkersList = ({ projectRef, workers, onDeploy }: WorkersListProps) => { +export const WorkersList = ({ + projectRef, + workers, + onDeploy, + onRefresh, + isRefreshing, +}: WorkersListProps) => { const router = useRouter() const [search, setSearch] = useState('') const [stateFilter, setStateFilter] = useState('all') @@ -124,9 +132,9 @@ export const WorkersList = ({ projectRef, workers, onDeploy }: WorkersListProps)
- - {filtered.length} worker{filtered.length === 1 ? '' : 's'} - + diff --git a/apps/studio/data/workers/workers-query.ts b/apps/studio/data/workers/workers-query.ts index 5aaed1734df..a74453ffa63 100644 --- a/apps/studio/data/workers/workers-query.ts +++ b/apps/studio/data/workers/workers-query.ts @@ -28,4 +28,5 @@ export const workersQueryOptions = ({ projectRef }: WorkersVariables) => queryKey: workersKeys.list(projectRef), queryFn: ({ signal }) => getWorkers({ projectRef }, signal), enabled: IS_PLATFORM && typeof projectRef !== 'undefined', + refetchOnWindowFocus: 'always', }) diff --git a/apps/studio/pages/project/[ref]/workers/index.tsx b/apps/studio/pages/project/[ref]/workers/index.tsx index 73debf88890..327dfd8e732 100644 --- a/apps/studio/pages/project/[ref]/workers/index.tsx +++ b/apps/studio/pages/project/[ref]/workers/index.tsx @@ -1,6 +1,8 @@ import { useQuery } from '@tanstack/react-query' import { useParams } from 'common' +import { RefreshCw } from 'lucide-react' import { useState } from 'react' +import { Button } from 'ui' import { Admonition } from 'ui-patterns/Admonition' import { PageContainer } from 'ui-patterns/PageContainer' import { @@ -38,6 +40,8 @@ const WorkersPage: NextPageWithLayout = () => { isPending, isError, isSuccess, + isFetching, + refetch, } = useQuery(workersQueryOptions({ projectRef: ref })) const isNotEnrolled = isError && isWorkersUnavailable(error) @@ -74,7 +78,22 @@ const WorkersPage: NextPageWithLayout = () => { /> )} {isMissingPermission && } - {isUnexpectedError && } + {isUnexpectedError && ( + } + loading={isFetching} + onClick={() => refetch()} + > + Refresh + + } + /> + )} {isSuccess && workers.length === 0 && ( setIsDeployInstructionsOpen(true)} /> )} @@ -83,6 +102,8 @@ const WorkersPage: NextPageWithLayout = () => { projectRef={ref} workers={workers} onDeploy={() => setIsDeployInstructionsOpen(true)} + onRefresh={() => refetch()} + isRefreshing={isFetching} /> )} diff --git a/apps/studio/tests/pages/project/[ref]/workers/index.test.tsx b/apps/studio/tests/pages/project/[ref]/workers/index.test.tsx index a30ff8ba80b..72111ae36b9 100644 --- a/apps/studio/tests/pages/project/[ref]/workers/index.test.tsx +++ b/apps/studio/tests/pages/project/[ref]/workers/index.test.tsx @@ -1,5 +1,5 @@ import { QueryClient } from '@tanstack/react-query' -import { screen } from '@testing-library/react' +import { fireEvent, screen } from '@testing-library/react' import type { components } from 'api-types' import { HttpResponse } from 'msw' import { beforeEach, describe, expect, it } from 'vitest' @@ -90,6 +90,17 @@ describe('/project/[ref]/workers', () => { expect(screen.queryByRole('table')).not.toBeInTheDocument() }) + it('refreshes the workers list on request', async () => { + mockWorkersList([workerDatum('existing')]) + + await renderWorkersPage() + + mockWorkersList([workerDatum('embed')]) + fireEvent.click(screen.getByRole('button', { name: 'Refresh' })) + + expect(await screen.findByRole('link', { name: 'embed' })).toBeVisible() + }) + it('explains that a project outside the alpha is not enrolled', async () => { mockWorkersListFailure(404) @@ -109,4 +120,25 @@ describe('/project/[ref]/workers', () => { ).toBeVisible() expect(screen.queryByRole('table')).not.toBeInTheDocument() }) + + it('allows retrying an unexpected error', async () => { + let requestCount = 0 + addAPIMock({ + method: 'get', + path: '/v2/projects/:ref/workers', + response: (): HttpResponse | HttpResponse => { + if (requestCount++ === 0) { + return HttpResponse.json({ message: 'Unavailable' }, { status: 500 }) + } + + return HttpResponse.json({ data: [workerDatum('embed')] }) + }, + }) + + await renderWorkersPage() + + fireEvent.click(screen.getByRole('button', { name: 'Refresh' })) + + expect(await screen.findByRole('link', { name: 'embed' })).toBeVisible() + }) })