From 0bf22ee6fc95e114452a3634e8141e0cab6e0a95 Mon Sep 17 00:00:00 2001 From: Francesco Sansalvadore Date: Thu, 10 Sep 2026 16:26:48 +0200 Subject: [PATCH] chore(studio): update product naming (#50208) workers -> compute ## Summary by CodeRabbit - **New Features** - Added the Compute experience for deploying, viewing, managing, and monitoring compute instances. - Added Compute navigation, instance detail pages, secrets, logs, deployment dialogs, generated snippets, and CLI commands. - Added filtering, status, availability, and data-loading support for compute instances. - **Updates** - Updated labels, icons, links, feature controls, unified logs, and secret-deletion messaging to use Compute terminology. - Compute routes now replace the previous Workers routes and pages. --- apps/studio/TANSTACK_MIGRATION.md | 10 +- .../Compute.constants.ts} | 28 +-- .../interfaces/Compute/Compute.types.ts | 25 +++ .../Compute.utils.test.ts} | 127 ++++++------ .../Compute.utils.ts} | 44 +++-- .../ComputeEmptyState.tsx} | 21 +- .../ComputeInstanceCommandLine.tsx} | 7 +- .../ComputeInstanceDetail.tsx} | 58 +++--- .../ComputeInstanceLogsTab.tsx} | 36 ++-- .../ComputeInstanceOverviewTab.tsx} | 105 +++++----- .../ComputeInstanceSnippetTabs.tsx} | 33 ++-- .../ComputeInstanceStatePill.tsx} | 17 +- .../ComputeList.test.tsx} | 46 ++--- .../ComputeList.tsx} | 67 ++++--- .../DeployComputeInstanceDialog.tsx} | 55 +++--- .../{Workers => Compute}/RuntimeBadge.tsx | 2 +- .../Compute/computeInstanceSnippets.test.ts | 31 +++ .../computeInstanceSnippets.ts} | 48 ++--- .../EdgeFunctionSecrets.tsx | 8 +- ...Render.tsx => ComputeLogsColumnRender.tsx} | 6 +- apps/studio/components/interfaces/Sidebar.tsx | 4 +- .../UnifiedLogs/UnifiedLogs.constants.tsx | 4 +- .../UnifiedLogs/UnifiedLogs.hooks.test.tsx | 4 +- .../UnifiedLogs/UnifiedLogs.queries.test.ts | 22 +-- .../UnifiedLogs/UnifiedLogs.queries.ts | 6 +- .../interfaces/UnifiedLogs/UnifiedLogs.tsx | 22 +-- .../UnifiedLogs/UnifiedLogs.utils.test.ts | 76 ++++---- .../UnifiedLogs/UnifiedLogs.utils.ts | 16 +- .../UnifiedLogs/components/LogTypeIcon.tsx | 4 +- .../interfaces/Workers/Workers.types.ts | 24 --- .../interfaces/Workers/workerSnippets.test.ts | 27 --- .../Compute.Commands.tsx} | 8 +- .../ComputeLayout.tsx} | 36 ++-- .../NavigationBar.utils.test.tsx | 6 +- .../NavigationBar/NavigationBar.utils.tsx | 16 +- .../MobileMenuContent/MobileMenuContent.tsx | 6 +- .../layouts/useLayoutNavCommands.ts | 6 +- .../compute-instance-logs-query.test.ts} | 40 ++-- .../compute-instance-logs-query.ts} | 41 ++-- .../data/compute/compute-instance-query.ts | 35 ++++ apps/studio/data/compute/compute-query.ts | 32 +++ .../compute.utils.test.ts} | 23 ++- .../compute.utils.ts} | 17 +- apps/studio/data/{workers => compute}/keys.ts | 8 +- .../data/logs/unified-logs.utils.test.ts | 34 ++-- apps/studio/data/logs/unified-logs.utils.ts | 10 +- apps/studio/data/workers/worker-query.ts | 32 --- apps/studio/data/workers/workers-query.ts | 32 --- apps/studio/lib/constants/compute.ts | 10 + apps/studio/lib/constants/workers.ts | 8 - .../pages/project/[ref]/compute/[name].tsx | 14 ++ .../[ref]/{workers => compute}/index.tsx | 54 ++--- .../[ref]/{workers => compute}/secrets.tsx | 20 +- .../pages/project/[ref]/workers/[name].tsx | 14 -- apps/studio/routeTree.gen.ts | 184 +++++++++--------- apps/studio/routes/project/$ref/compute.tsx | 25 +++ .../routes/project/$ref/compute/$name.tsx | 14 ++ .../routes/project/$ref/compute/index.tsx | 15 ++ .../routes/project/$ref/compute/secrets.tsx | 20 ++ apps/studio/routes/project/$ref/workers.tsx | 25 --- .../routes/project/$ref/workers/$name.tsx | 14 -- .../routes/project/$ref/workers/index.tsx | 15 -- .../routes/project/$ref/workers/secrets.tsx | 18 -- .../[ref]/{workers => compute}/index.test.tsx | 66 +++---- packages/common/telemetry-constants.ts | 2 +- packages/icons/__registry__/index.tsx | 20 +- .../src/icons/{workers.ts => compute.ts} | 8 +- packages/icons/src/icons/index.ts | 2 +- .../raw-icons/{workers.svg => compute.svg} | 0 69 files changed, 1003 insertions(+), 910 deletions(-) rename apps/studio/components/interfaces/{Workers/Workers.constants.ts => Compute/Compute.constants.ts} (72%) create mode 100644 apps/studio/components/interfaces/Compute/Compute.types.ts rename apps/studio/components/interfaces/{Workers/Workers.utils.test.ts => Compute/Compute.utils.test.ts} (50%) rename apps/studio/components/interfaces/{Workers/Workers.utils.ts => Compute/Compute.utils.ts} (58%) rename apps/studio/components/interfaces/{Workers/WorkersEmptyState.tsx => Compute/ComputeEmptyState.tsx} (57%) rename apps/studio/components/interfaces/{Workers/WorkerCommandLine.tsx => Compute/ComputeInstanceCommandLine.tsx} (80%) rename apps/studio/components/interfaces/{Workers/WorkerDetail/WorkerDetail.tsx => Compute/ComputeInstanceDetail/ComputeInstanceDetail.tsx} (64%) rename apps/studio/components/interfaces/{Workers/WorkerDetail/WorkerLogsTab.tsx => Compute/ComputeInstanceDetail/ComputeInstanceLogsTab.tsx} (80%) rename apps/studio/components/interfaces/{Workers/WorkerDetail/WorkerOverviewTab.tsx => Compute/ComputeInstanceDetail/ComputeInstanceOverviewTab.tsx} (70%) rename apps/studio/components/interfaces/{Workers/WorkerSnippetTabs.tsx => Compute/ComputeInstanceSnippetTabs.tsx} (73%) rename apps/studio/components/interfaces/{Workers/WorkerStatePill.tsx => Compute/ComputeInstanceStatePill.tsx} (58%) rename apps/studio/components/interfaces/{Workers/WorkersList.test.tsx => Compute/ComputeList.test.tsx} (61%) rename apps/studio/components/interfaces/{Workers/WorkersList.tsx => Compute/ComputeList.tsx} (77%) rename apps/studio/components/interfaces/{Workers/DeployWorkerDialog.tsx => Compute/DeployComputeInstanceDialog.tsx} (79%) rename apps/studio/components/interfaces/{Workers => Compute}/RuntimeBadge.tsx (88%) create mode 100644 apps/studio/components/interfaces/Compute/computeInstanceSnippets.test.ts rename apps/studio/components/interfaces/{Workers/workerSnippets.ts => Compute/computeInstanceSnippets.ts} (69%) rename apps/studio/components/interfaces/Settings/Logs/LogColumnRenderers/{WorkersLogsColumnRender.tsx => ComputeLogsColumnRender.tsx} (81%) delete mode 100644 apps/studio/components/interfaces/Workers/Workers.types.ts delete mode 100644 apps/studio/components/interfaces/Workers/workerSnippets.test.ts rename apps/studio/components/layouts/{WorkersLayout/Workers.Commands.tsx => ComputeLayout/Compute.Commands.tsx} (72%) rename apps/studio/components/layouts/{WorkersLayout/WorkersLayout.tsx => ComputeLayout/ComputeLayout.tsx} (71%) rename apps/studio/data/{workers/worker-logs-query.test.ts => compute/compute-instance-logs-query.test.ts} (63%) rename apps/studio/data/{workers/worker-logs-query.ts => compute/compute-instance-logs-query.ts} (70%) create mode 100644 apps/studio/data/compute/compute-instance-query.ts create mode 100644 apps/studio/data/compute/compute-query.ts rename apps/studio/data/{workers/workers.utils.test.ts => compute/compute.utils.test.ts} (65%) rename apps/studio/data/{workers/workers.utils.ts => compute/compute.utils.ts} (71%) rename apps/studio/data/{workers => compute}/keys.ts (62%) delete mode 100644 apps/studio/data/workers/worker-query.ts delete mode 100644 apps/studio/data/workers/workers-query.ts create mode 100644 apps/studio/lib/constants/compute.ts delete mode 100644 apps/studio/lib/constants/workers.ts create mode 100644 apps/studio/pages/project/[ref]/compute/[name].tsx rename apps/studio/pages/project/[ref]/{workers => compute}/index.tsx (68%) rename apps/studio/pages/project/[ref]/{workers => compute}/secrets.tsx (87%) delete mode 100644 apps/studio/pages/project/[ref]/workers/[name].tsx create mode 100644 apps/studio/routes/project/$ref/compute.tsx create mode 100644 apps/studio/routes/project/$ref/compute/$name.tsx create mode 100644 apps/studio/routes/project/$ref/compute/index.tsx create mode 100644 apps/studio/routes/project/$ref/compute/secrets.tsx delete mode 100644 apps/studio/routes/project/$ref/workers.tsx delete mode 100644 apps/studio/routes/project/$ref/workers/$name.tsx delete mode 100644 apps/studio/routes/project/$ref/workers/index.tsx delete mode 100644 apps/studio/routes/project/$ref/workers/secrets.tsx rename apps/studio/tests/pages/project/[ref]/{workers => compute}/index.test.tsx (68%) rename packages/icons/src/icons/{workers.ts => compute.ts} (97%) rename packages/icons/src/raw-icons/{workers.svg => compute.svg} (100%) diff --git a/apps/studio/TANSTACK_MIGRATION.md b/apps/studio/TANSTACK_MIGRATION.md index 26fb6b35189..36861a50399 100644 --- a/apps/studio/TANSTACK_MIGRATION.md +++ b/apps/studio/TANSTACK_MIGRATION.md @@ -93,7 +93,7 @@ These are the layout-only TanStack files. Most hold a single product layout comp - [x] `routes/project/$ref/branches.tsx` — BranchLayout only. **Delta vs plan:** the per-page `PageLayout` (with different titles + primary/secondary actions) stays in each leaf. Hoisted `BranchesPageWrapper` and `MergeRequestsPageWrapper` to top-level exports in their respective `pages/...` files so the route files can import + re-use the same wrapping. - [x] `routes/project/$ref/logs.tsx` — LogsLayout (reads `logsLayoutTitle` from leaf staticData). Honours `skipLogsLayout: true` for `logs/index` (page handles its own ProjectLayout-wrapped content for the UnifiedLogs / no-permission cases). Refactored `pages/.../logs/index.tsx` to move the inline `` into `getLayout` so it isn't duplicated when the TanStack project shell already provides DefaultLayout. - [x] `routes/project/$ref/observability.tsx` — ObservabilityLayout (reads `observabilityLayoutTitle` from leaf staticData) -- [x] `routes/project/$ref/workers.tsx` — WorkersLayout (reads `workersLayoutTitle` from leaf `staticData`). Flag-gated: `WorkersLayout` itself redirects to the project home when `useFlag('workers')` is off, so the shell needs no extra guard. +- [x] `routes/project/$ref/compute.tsx` — ComputeLayout (reads `computeLayoutTitle` from leaf `staticData`). Flag-gated: `ComputeLayout` itself redirects to the project home when `useFlag('compute')` is off, so the shell needs no extra guard. - [x] `routes/project/$ref/advisors.tsx` — AdvisorsLayout (reads `advisorsLayoutTitle` from leaf staticData). Honours `skipAdvisorsLayout: true` opt-out for the rules sub-shell, which provides its own AdvisorsLayout-less-DefaultLayout wrap. Scans whole match chain (same pattern as functions.tsx). - [x] `routes/project/$ref/advisors/rules.tsx` — sub-shell that inlines the inner body of `AdvisorRulesLayout` (AdvisorsLayout + PageLayout with title/tabs/feature-preview badge), minus the outer DefaultLayout (already provided by the parent project shell). Sets `skipAdvisorsLayout: true` on its own staticData. **Delta vs plan:** the existing `AdvisorRulesLayout` component wraps in DefaultLayout + AdvisorsLayout internally, so reusing it as-is would double-wrap both. Inlined the inner part; the Next-side component is untouched. - [x] `routes/project/$ref/settings.tsx` — SettingsLayout (reads `settingsLayoutTitle` from leaf staticData). Honours `skipSettingsLayout: true` for `settings/api` (redirect-only page). Adds a sub-shell at `routes/project/$ref/settings/api-keys.tsx` providing `ApiKeysLayout` for both api-keys leaves; `jwt/index` wraps in `JWTKeysLayout` inline since `jwt/legacy` doesn't share it. @@ -229,11 +229,11 @@ These are the layout-only TanStack files. Most hold a single product layout comp - [x] A `routes/project/$ref/realtime/policies.tsx` ← `pages/project/[ref]/realtime/policies.tsx` - [x] A `routes/project/$ref/realtime/settings.tsx` ← `pages/project/[ref]/realtime/settings.tsx` -### Project shell — `/workers/*` +### Project shell — `/compute/*` -- [x] A `routes/project/$ref/workers/index.tsx` ← `pages/project/[ref]/workers/index.tsx` -- [x] A `routes/project/$ref/workers/$name.tsx` ← `pages/project/[ref]/workers/[name].tsx` -- [x] A `routes/project/$ref/workers/secrets.tsx` ← `pages/project/[ref]/workers/secrets.tsx` +- [x] A `routes/project/$ref/compute/index.tsx` ← `pages/project/[ref]/compute/index.tsx` +- [x] A `routes/project/$ref/compute/$name.tsx` ← `pages/project/[ref]/compute/[name].tsx` +- [x] A `routes/project/$ref/compute/secrets.tsx` ← `pages/project/[ref]/compute/secrets.tsx` ### Project shell — `/functions/*` diff --git a/apps/studio/components/interfaces/Workers/Workers.constants.ts b/apps/studio/components/interfaces/Compute/Compute.constants.ts similarity index 72% rename from apps/studio/components/interfaces/Workers/Workers.constants.ts rename to apps/studio/components/interfaces/Compute/Compute.constants.ts index f61b6e3e5a3..e0424f7eb84 100644 --- a/apps/studio/components/interfaces/Workers/Workers.constants.ts +++ b/apps/studio/components/interfaces/Compute/Compute.constants.ts @@ -1,14 +1,14 @@ -import type { Worker, WorkerBuildState } from './Workers.types' +import type { ComputeInstance, ComputeInstanceBuildState } from './Compute.types' // Locked to one region at alpha, so nothing in the UI lets you change it. -export const WORKERS_REGION = 'us-west-2' -export const WORKERS_REGION_LABEL = 'US West (Oregon)' -export const WORKERS_REGION_SHORT = 'US West' +export const COMPUTE_REGION = 'us-west-2' +export const COMPUTE_REGION_LABEL = 'US West (Oregon)' +export const COMPUTE_REGION_SHORT = 'US West' // Sizes are fixed at deploy time — matches the `size` values the API accepts. -export const WORKER_SIZES = ['2gb-1vcpu', '4gb-2vcpu'] as const +export const COMPUTE_INSTANCE_SIZES = ['2gb-1vcpu', '4gb-2vcpu'] as const -export const WORKER_NAME_WORDS = [ +export const COMPUTE_INSTANCE_NAME_WORDS = [ 'swift', 'nimble', 'brisk', @@ -61,7 +61,7 @@ export const WORKER_NAME_WORDS = [ 'stellar', ] as const -export const workerUrl = ({ +export const computeInstanceUrl = ({ endpoint, protocol = 'https', name, @@ -69,7 +69,7 @@ export const workerUrl = ({ endpoint: string | undefined protocol?: string name: string -}) => (endpoint ? `${protocol}://${endpoint}/workers/v1/${name}` : undefined) +}) => (endpoint ? `${protocol}://${endpoint}/compute/v1/${name}` : undefined) export const LISTENING_PORT = 8080 @@ -114,15 +114,15 @@ export const RUNTIMES: Record = { }, } -export const WORKER_DEPLOYABLE_RUNTIMES = ['node', 'deno', 'dockerfile'] as const +export const COMPUTE_INSTANCE_DEPLOYABLE_RUNTIMES = ['node', 'deno', 'dockerfile'] as const -interface WorkerStateMeta { +interface ComputeInstanceStateMeta { label: string dotClassName: string textClassName: string } -const BUILD_STATE_META: Record = { +const BUILD_STATE_META: Record = { building: { label: 'Building', dotClassName: 'bg-warning', @@ -136,11 +136,11 @@ const BUILD_STATE_META: Record = { }, } -const DELETING_STATE_META: WorkerStateMeta = { +const DELETING_STATE_META: ComputeInstanceStateMeta = { label: 'Deleting', dotClassName: 'bg-foreground-muted', textClassName: 'text-foreground-lighter', } -export const getWorkerStateMeta = (worker: Worker): WorkerStateMeta => - worker.isDeleting ? DELETING_STATE_META : BUILD_STATE_META[worker.buildState] +export const getComputeInstanceStateMeta = (instance: ComputeInstance): ComputeInstanceStateMeta => + instance.isDeleting ? DELETING_STATE_META : BUILD_STATE_META[instance.buildState] diff --git a/apps/studio/components/interfaces/Compute/Compute.types.ts b/apps/studio/components/interfaces/Compute/Compute.types.ts new file mode 100644 index 00000000000..89bdc005372 --- /dev/null +++ b/apps/studio/components/interfaces/Compute/Compute.types.ts @@ -0,0 +1,25 @@ +import type { components } from 'api-types' + +type ComputeInstanceAttributes = + components['schemas']['V2WorkerResponse_Output']['data']['attributes'] + +export type ComputeInstanceBuildState = ComputeInstanceAttributes['build_state'] + +export type ComputeInstanceTally = NonNullable + +// The API types `spec.exposure` as a free-form string; the UI only handles these two values. +export type ComputeInstanceAccess = 'public' | 'private' + +export interface ComputeInstance { + name: string + buildState: ComputeInstanceBuildState + isDeleting: boolean + runtime?: string + size: string + access: ComputeInstanceAccess + declaredInstances: number + instances?: ComputeInstanceTally + imageVersion?: string + stateReason?: string + instancesError?: string +} diff --git a/apps/studio/components/interfaces/Workers/Workers.utils.test.ts b/apps/studio/components/interfaces/Compute/Compute.utils.test.ts similarity index 50% rename from apps/studio/components/interfaces/Workers/Workers.utils.test.ts rename to apps/studio/components/interfaces/Compute/Compute.utils.test.ts index 735d86fe26c..38ff38ed307 100644 --- a/apps/studio/components/interfaces/Workers/Workers.utils.test.ts +++ b/apps/studio/components/interfaces/Compute/Compute.utils.test.ts @@ -1,20 +1,26 @@ import { describe, expect, it } from 'vitest' -import { getWorkerStateMeta, WORKER_NAME_WORDS, workerUrl } from './Workers.constants' -import type { Worker } from './Workers.types' import { - filterWorkers, + COMPUTE_INSTANCE_NAME_WORDS, + computeInstanceUrl, + getComputeInstanceStateMeta, +} from './Compute.constants' +import type { ComputeInstance } from './Compute.types' +import { + filterComputeInstances, formatResources, formatRuntime, formatSize, - generateWorkerName, + generateComputeInstanceName, getPage, - isWorkersForbidden, - isWorkersUnavailable, -} from './Workers.utils' + isComputeForbidden, + isComputeUnavailable, +} from './Compute.utils' import { ResponseError } from '@/types' -const worker = (overrides: Partial & Pick): Worker => ({ +const instance = ( + overrides: Partial & Pick +): ComputeInstance => ({ buildState: 'active', isDeleting: false, runtime: 'node', @@ -26,39 +32,43 @@ const worker = (overrides: Partial & Pick): Worker => ({ const NO_FILTERS = { search: '', state: 'all', access: 'all' } as const -describe('filterWorkers', () => { - const workers = [ - worker({ name: 'embed', buildState: 'active', access: 'public' }), - worker({ name: 'resize-images', buildState: 'building', access: 'private' }), - worker({ name: 'embed-batch', buildState: 'failed', access: 'public' }), +describe('filterComputeInstances', () => { + const instances = [ + instance({ name: 'embed', buildState: 'active', access: 'public' }), + instance({ name: 'resize-images', buildState: 'building', access: 'private' }), + instance({ name: 'embed-batch', buildState: 'failed', access: 'public' }), ] - it('returns every worker when no filters are set', () => { - expect(filterWorkers(workers, NO_FILTERS)).toHaveLength(3) + it('returns every instance when no filters are set', () => { + expect(filterComputeInstances(instances, NO_FILTERS)).toHaveLength(3) }) it('matches names case-insensitively on a partial term', () => { - const names = filterWorkers(workers, { ...NO_FILTERS, search: 'EMBED' }).map((w) => w.name) + const names = filterComputeInstances(instances, { ...NO_FILTERS, search: 'EMBED' }).map( + (i) => i.name + ) expect(names).toEqual(['embed', 'embed-batch']) }) it('ignores surrounding whitespace in the search term', () => { - expect(filterWorkers(workers, { ...NO_FILTERS, search: ' resize ' })).toHaveLength(1) - }) - - it('filters by build state and by access', () => { - expect(filterWorkers(workers, { ...NO_FILTERS, state: 'failed' }).map((w) => w.name)).toEqual([ - 'embed-batch', - ]) - expect(filterWorkers(workers, { ...NO_FILTERS, access: 'private' }).map((w) => w.name)).toEqual( - ['resize-images'] + expect(filterComputeInstances(instances, { ...NO_FILTERS, search: ' resize ' })).toHaveLength( + 1 ) }) + it('filters by build state and by access', () => { + expect( + filterComputeInstances(instances, { ...NO_FILTERS, state: 'failed' }).map((i) => i.name) + ).toEqual(['embed-batch']) + expect( + filterComputeInstances(instances, { ...NO_FILTERS, access: 'private' }).map((i) => i.name) + ).toEqual(['resize-images']) + }) + it('combines filters', () => { - expect(filterWorkers(workers, { search: 'embed', state: 'active', access: 'public' })).toEqual([ - workers[0], - ]) + expect( + filterComputeInstances(instances, { search: 'embed', state: 'active', access: 'public' }) + ).toEqual([instances[0]]) }) }) @@ -119,74 +129,81 @@ describe('formatRuntime', () => { describe('formatResources', () => { it('combines size and declared instance count', () => { - expect(formatResources(worker({ name: 'embed', declaredInstances: 3 }))).toBe( + expect(formatResources(instance({ name: 'embed', declaredInstances: 3 }))).toBe( '2 GB · 1 vCPU · 3 inst' ) }) }) -describe('workers error classification', () => { +describe('compute error classification', () => { const responseError = (code?: number) => new ResponseError('Denied', code) it('reads a 404 as the project being outside the alpha allow-list', () => { - expect(isWorkersUnavailable(responseError(404))).toBe(true) - expect(isWorkersForbidden(responseError(404))).toBe(false) + expect(isComputeUnavailable(responseError(404))).toBe(true) + expect(isComputeForbidden(responseError(404))).toBe(false) }) - it('reads a 403 as a missing workers permission, not a missing enrollment', () => { - expect(isWorkersForbidden(responseError(403))).toBe(true) - expect(isWorkersUnavailable(responseError(403))).toBe(false) + it('reads a 403 as a missing compute permission, not a missing enrollment', () => { + expect(isComputeForbidden(responseError(403))).toBe(true) + expect(isComputeUnavailable(responseError(403))).toBe(false) }) it('leaves other failures to the generic error UI', () => { for (const error of [responseError(500), responseError(undefined), new Error('boom'), null]) { - expect(isWorkersUnavailable(error)).toBe(false) - expect(isWorkersForbidden(error)).toBe(false) + expect(isComputeUnavailable(error)).toBe(false) + expect(isComputeForbidden(error)).toBe(false) } }) }) -describe('workerUrl', () => { - it('answers on the project domain alongside /functions/v1', () => { - expect(workerUrl({ endpoint: 'abcdefgh.supabase.co', name: 'embed' })).toBe( - 'https://abcdefgh.supabase.co/workers/v1/embed' +describe('computeInstanceUrl', () => { + it('answers on the project domain alongside /compute/v1', () => { + expect(computeInstanceUrl({ endpoint: 'abcdefgh.supabase.co', name: 'embed' })).toBe( + 'https://abcdefgh.supabase.co/compute/v1/embed' ) }) it('honors a non-https protocol', () => { - expect(workerUrl({ endpoint: 'localhost:8000', protocol: 'http', name: 'embed' })).toBe( - 'http://localhost:8000/workers/v1/embed' - ) + expect( + computeInstanceUrl({ endpoint: 'localhost:8000', protocol: 'http', name: 'embed' }) + ).toBe('http://localhost:8000/compute/v1/embed') }) it('has no url until the project settings resolve', () => { - expect(workerUrl({ endpoint: undefined, name: 'embed' })).toBeUndefined() + expect(computeInstanceUrl({ endpoint: undefined, name: 'embed' })).toBeUndefined() }) }) -describe('generateWorkerName', () => { +describe('generateComputeInstanceName', () => { it('produces a name that already passes the CLI naming rules', () => { - const name = generateWorkerName() - expect(name).toMatch(/^worker-[a-z]+-\d{6}$/) - expect(WORKER_NAME_WORDS).toContain(name.split('-')[1]) + const name = generateComputeInstanceName() + expect(name).toMatch(/^compute-[a-z]+-\d{6}$/) + expect(COMPUTE_INSTANCE_NAME_WORDS).toContain(name.split('-')[1]) }) it('varies across calls', () => { - const names = new Set(Array.from({ length: 20 }, () => generateWorkerName())) + const names = new Set(Array.from({ length: 20 }, () => generateComputeInstanceName())) expect(names.size).toBeGreaterThan(1) }) }) -describe('getWorkerStateMeta', () => { +describe('getComputeInstanceStateMeta', () => { it('labels every build state', () => { - expect(getWorkerStateMeta(worker({ name: 'a', buildState: 'building' })).label).toBe('Building') - expect(getWorkerStateMeta(worker({ name: 'a', buildState: 'active' })).label).toBe('Active') - expect(getWorkerStateMeta(worker({ name: 'a', buildState: 'failed' })).label).toBe('Failed') + expect(getComputeInstanceStateMeta(instance({ name: 'a', buildState: 'building' })).label).toBe( + 'Building' + ) + expect(getComputeInstanceStateMeta(instance({ name: 'a', buildState: 'active' })).label).toBe( + 'Active' + ) + expect(getComputeInstanceStateMeta(instance({ name: 'a', buildState: 'failed' })).label).toBe( + 'Failed' + ) }) it('reports deleting over the build state', () => { expect( - getWorkerStateMeta(worker({ name: 'a', buildState: 'active', isDeleting: true })).label + getComputeInstanceStateMeta(instance({ name: 'a', buildState: 'active', isDeleting: true })) + .label ).toBe('Deleting') }) }) diff --git a/apps/studio/components/interfaces/Workers/Workers.utils.ts b/apps/studio/components/interfaces/Compute/Compute.utils.ts similarity index 58% rename from apps/studio/components/interfaces/Workers/Workers.utils.ts rename to apps/studio/components/interfaces/Compute/Compute.utils.ts index 11678149636..07158328883 100644 --- a/apps/studio/components/interfaces/Workers/Workers.utils.ts +++ b/apps/studio/components/interfaces/Compute/Compute.utils.ts @@ -1,19 +1,26 @@ -import { RUNTIMES, WORKER_NAME_WORDS, type RuntimeMeta } from './Workers.constants' -import type { Worker, WorkerAccess, WorkerBuildState } from './Workers.types' +import { COMPUTE_INSTANCE_NAME_WORDS, RUNTIMES, type RuntimeMeta } from './Compute.constants' +import type { + ComputeInstance, + ComputeInstanceAccess, + ComputeInstanceBuildState, +} from './Compute.types' import { ResponseError } from '@/types' -export interface WorkerFilters { +export interface ComputeInstanceFilters { search: string - state: WorkerBuildState | 'all' - access: WorkerAccess | 'all' + state: ComputeInstanceBuildState | 'all' + access: ComputeInstanceAccess | 'all' } -export const filterWorkers = (workers: Worker[], filters: WorkerFilters): Worker[] => { +export const filterComputeInstances = ( + instances: ComputeInstance[], + filters: ComputeInstanceFilters +): ComputeInstance[] => { const search = filters.search.trim().toLowerCase() - return workers.filter((worker) => { - const matchesSearch = worker.name.toLowerCase().includes(search) - const matchesState = filters.state === 'all' || worker.buildState === filters.state - const matchesAccess = filters.access === 'all' || worker.access === filters.access + return instances.filter((instance) => { + const matchesSearch = instance.name.toLowerCase().includes(search) + const matchesState = filters.state === 'all' || instance.buildState === filters.state + const matchesAccess = filters.access === 'all' || instance.access === filters.access return matchesSearch && matchesState && matchesAccess }) } @@ -51,20 +58,21 @@ export const formatSize = (size: string): string => { return `${match[1]} GB · ${match[2]} vCPU` } -export const formatResources = (worker: Worker): string => - `${formatSize(worker.size)} · ${worker.declaredInstances} inst` +export const formatResources = (instance: ComputeInstance): string => + `${formatSize(instance.size)} · ${instance.declaredInstances} inst` // Suggests a friendly, already-valid starting name so the deploy dialog isn't blank. -export const generateWorkerName = (): string => { - const word = WORKER_NAME_WORDS[Math.floor(Math.random() * WORKER_NAME_WORDS.length)] +export const generateComputeInstanceName = (): string => { + const word = + COMPUTE_INSTANCE_NAME_WORDS[Math.floor(Math.random() * COMPUTE_INSTANCE_NAME_WORDS.length)] const number = Math.floor(Math.random() * 900000) + 100000 - return `worker-${word}-${number}` + return `compute-${word}-${number}` } // A project outside the alpha allow-list gets a 404, not a 403. -export const isWorkersUnavailable = (error: Error | null): boolean => +export const isComputeUnavailable = (error: Error | null): boolean => error instanceof ResponseError && error.code === 404 -// An enrolled project still answers 403 when the caller lacks the workers permission. -export const isWorkersForbidden = (error: Error | null): boolean => +// An enrolled project still answers 403 when the caller lacks the compute permission. +export const isComputeForbidden = (error: Error | null): boolean => error instanceof ResponseError && error.code === 403 diff --git a/apps/studio/components/interfaces/Workers/WorkersEmptyState.tsx b/apps/studio/components/interfaces/Compute/ComputeEmptyState.tsx similarity index 57% rename from apps/studio/components/interfaces/Workers/WorkersEmptyState.tsx rename to apps/studio/components/interfaces/Compute/ComputeEmptyState.tsx index e5b29c52723..8e4fd6ab374 100644 --- a/apps/studio/components/interfaces/Workers/WorkersEmptyState.tsx +++ b/apps/studio/components/interfaces/Compute/ComputeEmptyState.tsx @@ -6,35 +6,38 @@ import { EmptyStatePresentational } from 'ui-patterns/EmptyStatePresentational' import { ButtonTooltip } from '@/components/ui/ButtonTooltip' import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' -interface WorkersEmptyStateProps { +interface ComputeEmptyStateProps { onDeploy: () => void } -export const WorkersEmptyState = ({ onDeploy }: WorkersEmptyStateProps) => { - const { can: canDeployWorkers } = useAsyncCheckPermissions(PermissionAction.FUNCTIONS_WRITE, '*') +export const ComputeEmptyState = ({ onDeploy }: ComputeEmptyStateProps) => { + const { can: canDeployInstances } = useAsyncCheckPermissions( + PermissionAction.FUNCTIONS_WRITE, + '*' + ) return ( } - disabled={!canDeployWorkers} + disabled={!canDeployInstances} onClick={onDeploy} tooltip={{ content: { side: 'bottom', - text: canDeployWorkers + text: canDeployInstances ? undefined - : 'You need additional permissions to deploy workers', + : 'You need additional permissions to deploy compute instances', }, }} > - Deploy a worker + Deploy ) diff --git a/apps/studio/components/interfaces/Workers/WorkerCommandLine.tsx b/apps/studio/components/interfaces/Compute/ComputeInstanceCommandLine.tsx similarity index 80% rename from apps/studio/components/interfaces/Workers/WorkerCommandLine.tsx rename to apps/studio/components/interfaces/Compute/ComputeInstanceCommandLine.tsx index def836b48c3..2c69ce9e0a9 100644 --- a/apps/studio/components/interfaces/Workers/WorkerCommandLine.tsx +++ b/apps/studio/components/interfaces/Compute/ComputeInstanceCommandLine.tsx @@ -1,11 +1,14 @@ import CopyButton from '@/components/ui/CopyButton' -interface WorkerCommandLineProps { +interface ComputeInstanceCommandLineProps { command: string comment?: string } -export const WorkerCommandLine = ({ command, comment }: WorkerCommandLineProps) => ( +export const ComputeInstanceCommandLine = ({ + command, + comment, +}: ComputeInstanceCommandLineProps) => (
{comment &&

{`> ${comment}`}

}
diff --git a/apps/studio/components/interfaces/Workers/WorkerDetail/WorkerDetail.tsx b/apps/studio/components/interfaces/Compute/ComputeInstanceDetail/ComputeInstanceDetail.tsx similarity index 64% rename from apps/studio/components/interfaces/Workers/WorkerDetail/WorkerDetail.tsx rename to apps/studio/components/interfaces/Compute/ComputeInstanceDetail/ComputeInstanceDetail.tsx index cca4bac374e..c43e275e63b 100644 --- a/apps/studio/components/interfaces/Workers/WorkerDetail/WorkerDetail.tsx +++ b/apps/studio/components/interfaces/Compute/ComputeInstanceDetail/ComputeInstanceDetail.tsx @@ -26,60 +26,60 @@ import { import { PageNav } from 'ui-patterns/PageNav' import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader' +import { ComputeInstanceStatePill } from '../ComputeInstanceStatePill' import { RuntimeBadge } from '../RuntimeBadge' -import { WorkerStatePill } from '../WorkerStatePill' -import { WorkerLogsTab } from './WorkerLogsTab' -import { WorkerOverviewTab } from './WorkerOverviewTab' +import { ComputeInstanceLogsTab } from './ComputeInstanceLogsTab' +import { ComputeInstanceOverviewTab } from './ComputeInstanceOverviewTab' import { AlertError } from '@/components/ui/AlertError' -import type { WorkerLogStream } from '@/data/workers/worker-logs-query' -import { workerQueryOptions } from '@/data/workers/worker-query' -import { PRODUCT_NAME } from '@/lib/constants/workers' +import type { ComputeInstanceLogStream } from '@/data/compute/compute-instance-logs-query' +import { computeInstanceQueryOptions } from '@/data/compute/compute-instance-query' +import { PRODUCT_NAME } from '@/lib/constants/compute' -type WorkerTab = 'overview' | 'invocations' | 'logs' | 'activity' -const WORKER_TABS: WorkerTab[] = ['overview', 'invocations', 'logs', 'activity'] +type ComputeInstanceTab = 'overview' | 'invocations' | 'logs' | 'activity' +const COMPUTE_INSTANCE_TABS: ComputeInstanceTab[] = ['overview', 'invocations', 'logs', 'activity'] -const TAB_LABEL: Record = { +const TAB_LABEL: Record = { overview: 'Overview', invocations: 'Invocations', logs: 'Logs', activity: 'Activity', } -const TAB_STREAM: Partial> = { +const TAB_STREAM: Partial> = { invocations: 'requests', logs: 'output', activity: 'builds', } -export const WorkerDetail = () => { - const { ref: projectRef, name: workerName } = useParams() +export const ComputeInstanceDetail = () => { + const { ref: projectRef, name: instanceName } = useParams() const [tab, setTab] = useQueryState( 'tab', - parseAsStringEnum(WORKER_TABS) + parseAsStringEnum(COMPUTE_INSTANCE_TABS) .withDefault('overview') .withOptions({ history: 'push' }) ) const { - data: worker, + data: instance, error, isPending, isError, - } = useQuery(workerQueryOptions({ projectRef, name: workerName })) + } = useQuery(computeInstanceQueryOptions({ projectRef, name: instanceName })) if (!projectRef) return null if (isPending) return - if (isError) return + if (isError) return - if (!worker) { + if (!instance) { return (

- Worker "{workerName}" does not exist in this project + Instance "{instanceName}" does not exist in this project

) @@ -93,12 +93,12 @@ export const WorkerDetail = () => { - {PRODUCT_NAME} + {PRODUCT_NAME} - {worker.name} + {instance.name} @@ -111,14 +111,14 @@ export const WorkerDetail = () => { - {worker.name} + {instance.name} - - - {worker.imageVersion !== undefined && ( + + + {instance.imageVersion !== undefined && ( - Version {worker.imageVersion} + Version {instance.imageVersion} )} @@ -127,7 +127,7 @@ export const WorkerDetail = () => { - {WORKER_TABS.map((item) => ( + {COMPUTE_INSTANCE_TABS.map((item) => (
diff --git a/apps/studio/components/interfaces/Workers/WorkerDetail/WorkerLogsTab.tsx b/apps/studio/components/interfaces/Compute/ComputeInstanceDetail/ComputeInstanceLogsTab.tsx similarity index 80% rename from apps/studio/components/interfaces/Workers/WorkerDetail/WorkerLogsTab.tsx rename to apps/studio/components/interfaces/Compute/ComputeInstanceDetail/ComputeInstanceLogsTab.tsx index 183af913a42..6698f9a2a87 100644 --- a/apps/studio/components/interfaces/Workers/WorkerDetail/WorkerLogsTab.tsx +++ b/apps/studio/components/interfaces/Compute/ComputeInstanceDetail/ComputeInstanceLogsTab.tsx @@ -5,8 +5,8 @@ import { useState } from 'react' import { Button, InputGroup, InputGroupAddon, InputGroupInput } from 'ui' import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader' -import { WorkerCommandLine } from '../WorkerCommandLine' -import { WorkersLogsColumnRender } from '@/components/interfaces/Settings/Logs/LogColumnRenderers/WorkersLogsColumnRender' +import { ComputeInstanceCommandLine } from '../ComputeInstanceCommandLine' +import { ComputeLogsColumnRender } from '@/components/interfaces/Settings/Logs/LogColumnRenderers/ComputeLogsColumnRender' import { EXPLORER_DATEPICKER_HELPERS } from '@/components/interfaces/Settings/Logs/Logs.constants' import { LogsDatePicker, @@ -16,16 +16,16 @@ import type { LogData } from '@/components/interfaces/Settings/Logs/Logs.types' import { LogTable } from '@/components/interfaces/Settings/Logs/LogTable' import { AlertError } from '@/components/ui/AlertError' import { - WORKER_LOG_STREAM_LABEL, - workerLogsQueryOptions, - type WorkerLogStream, -} from '@/data/workers/worker-logs-query' + COMPUTE_INSTANCE_LOG_STREAM_LABEL, + computeInstanceLogsQueryOptions, + type ComputeInstanceLogStream, +} from '@/data/compute/compute-instance-logs-query' import { useDebouncedValue } from '@/hooks/misc/useDebouncedValue' -import { CLI_NAME } from '@/lib/constants/workers' +import { CLI_NAME } from '@/lib/constants/compute' -interface WorkerLogsTabProps { - workerName: string - stream: WorkerLogStream +interface ComputeInstanceLogsTabProps { + instanceName: string + stream: ComputeInstanceLogStream } const defaultDateRange = (): DatePickerValue => { @@ -39,7 +39,7 @@ const defaultDateRange = (): DatePickerValue => { } } -export const WorkerLogsTab = ({ workerName, stream }: WorkerLogsTabProps) => { +export const ComputeInstanceLogsTab = ({ instanceName, stream }: ComputeInstanceLogsTabProps) => { const { ref: projectRef } = useParams() const [selectedLog, setSelectedLog] = useState(null) const [dateRange, setDateRange] = useState(defaultDateRange) @@ -54,9 +54,9 @@ export const WorkerLogsTab = ({ workerName, stream }: WorkerLogsTabProps) => { isFetching, refetch, } = useQuery( - workerLogsQueryOptions({ + computeInstanceLogsQueryOptions({ projectRef, - name: workerName, + name: instanceName, stream, iso_timestamp_start: dateRange.from, iso_timestamp_end: dateRange.to, @@ -64,7 +64,7 @@ export const WorkerLogsTab = ({ workerName, stream }: WorkerLogsTabProps) => { }) ) - const label = WORKER_LOG_STREAM_LABEL[stream].toLowerCase() + const label = COMPUTE_INSTANCE_LOG_STREAM_LABEL[stream].toLowerCase() return (
@@ -101,7 +101,7 @@ export const WorkerLogsTab = ({ workerName, stream }: WorkerLogsTabProps) => { {isError && (
- +
)} @@ -115,7 +115,7 @@ export const WorkerLogsTab = ({ workerName, stream }: WorkerLogsTabProps) => {
{ Follow them from the Supabase CLI while you wait for traffic.

- +
} diff --git a/apps/studio/components/interfaces/Workers/WorkerDetail/WorkerOverviewTab.tsx b/apps/studio/components/interfaces/Compute/ComputeInstanceDetail/ComputeInstanceOverviewTab.tsx similarity index 70% rename from apps/studio/components/interfaces/Workers/WorkerDetail/WorkerOverviewTab.tsx rename to apps/studio/components/interfaces/Compute/ComputeInstanceDetail/ComputeInstanceOverviewTab.tsx index dc38d030041..c0117cfcb8e 100644 --- a/apps/studio/components/interfaces/Workers/WorkerDetail/WorkerOverviewTab.tsx +++ b/apps/studio/components/interfaces/Compute/ComputeInstanceDetail/ComputeInstanceOverviewTab.tsx @@ -12,20 +12,23 @@ import { PageSectionTitle, } from 'ui-patterns/PageSection' +import { COMPUTE_REGION_LABEL, LISTENING_PORT } from '../Compute.constants' +import type { ComputeInstance } from '../Compute.types' +import { formatSize, getRuntimeMeta } from '../Compute.utils' +import { ComputeInstanceCommandLine } from '../ComputeInstanceCommandLine' +import { buildComputeInstanceCliCommands } from '../computeInstanceSnippets' +import { + COMPUTE_INSTANCE_CALL_TABS, + ComputeInstanceSnippetTabs, +} from '../ComputeInstanceSnippetTabs' import { RuntimeBadge } from '../RuntimeBadge' -import { WorkerCommandLine } from '../WorkerCommandLine' -import { LISTENING_PORT, WORKERS_REGION_LABEL } from '../Workers.constants' -import type { Worker } from '../Workers.types' -import { formatSize, getRuntimeMeta } from '../Workers.utils' -import { buildWorkerCliCommands } from '../workerSnippets' -import { WORKER_CALL_TABS, WorkerSnippetTabs } from '../WorkerSnippetTabs' -import { CLI_NAME } from '@/lib/constants/workers' +import { CLI_NAME } from '@/lib/constants/compute' -interface WorkerOverviewTabProps { - worker: Worker +interface ComputeInstanceOverviewTabProps { + instance: ComputeInstance } -const InstanceCount = ({ +const ComputeInstanceCount = ({ label, value, tooltip, @@ -64,21 +67,21 @@ const SettingsRow = ({
) -export const WorkerOverviewTab = ({ worker }: WorkerOverviewTabProps) => { - const runtime = getRuntimeMeta(worker.runtime) - const commands = buildWorkerCliCommands(worker.name) +export const ComputeInstanceOverviewTab = ({ instance }: ComputeInstanceOverviewTabProps) => { + const runtime = getRuntimeMeta(instance.runtime) + const commands = buildComputeInstanceCliCommands(instance.name) return ( - {worker.buildState === 'failed' && ( + {instance.buildState === 'failed' && ( - +
-

{worker.stateReason ?? 'The build did not complete.'}

- {instance.stateReason ?? 'The build did not complete.'}

+
@@ -86,11 +89,11 @@ export const WorkerOverviewTab = ({ worker }: WorkerOverviewTabProps) => {
)} - {worker.instancesError !== undefined && ( + {instance.instancesError !== undefined && ( - {worker.instancesError} + {instance.instancesError} @@ -103,36 +106,36 @@ export const WorkerOverviewTab = ({ worker }: WorkerOverviewTabProps) => { - {worker.instances === undefined ? ( + {instance.instances === undefined ? (

- No instances are running for this worker yet. + No instances are running for this instance yet.

) : (

- {worker.instances.ready} of{' '} - {worker.instances.declared}{' '} + {instance.instances.ready} of{' '} + {instance.instances.declared}{' '} instances ready

- - - -
@@ -146,19 +149,19 @@ export const WorkerOverviewTab = ({ worker }: WorkerOverviewTabProps) => { Container - The runtime image and entrypoint resolved for this worker. + The runtime image and entrypoint resolved for this instance.
- + - {worker.imageVersion !== undefined && ( + {instance.imageVersion !== undefined && ( - {worker.imageVersion} + {instance.imageVersion} )} @@ -192,11 +195,11 @@ export const WorkerOverviewTab = ({ worker }: WorkerOverviewTabProps) => {
- {formatSize(worker.size)} + {formatSize(instance.size)} - {worker.declaredInstances} + {instance.declaredInstances} - {worker.access === 'public' ? ( + {instance.access === 'public' ? ( Public ) : ( Private @@ -204,7 +207,7 @@ export const WorkerOverviewTab = ({ worker }: WorkerOverviewTabProps) => { - {WORKERS_REGION_LABEL} (locked) + {COMPUTE_REGION_LABEL} (locked)
@@ -215,19 +218,19 @@ export const WorkerOverviewTab = ({ worker }: WorkerOverviewTabProps) => { How to call - Call the worker over its gateway URL. + Call the instance over its gateway URL. - @@ -237,14 +240,14 @@ export const WorkerOverviewTab = ({ worker }: WorkerOverviewTabProps) => { Develop locally - Manage this worker from the Supabase CLI. + Manage this instance from the Supabase CLI.
{commands.map((command) => ( - = { +const TAB_LABEL: Record = { ai: 'AI Prompt', config: 'config.toml', cli: 'CLI', @@ -24,7 +27,7 @@ const TAB_LABEL: Record = { python: 'Python', } -const TAB_ICON: Record = { +const TAB_ICON: Record = { ai: Sparkles, config: FileCode, cli: Terminal, @@ -33,23 +36,27 @@ const TAB_ICON: Record = { python: FileCode, } -interface WorkerSnippetTabsProps { - input: Omit - tabs?: readonly [WorkerSnippetTab, ...WorkerSnippetTab[]] +interface ComputeInstanceSnippetTabsProps { + input: Omit + tabs?: readonly [ComputeInstanceSnippetTab, ...ComputeInstanceSnippetTab[]] className?: string } -export const WorkerSnippetTabs = ({ input, tabs = ['cli'], className }: WorkerSnippetTabsProps) => { +export const ComputeInstanceSnippetTabs = ({ + input, + tabs = ['cli'], + className, +}: ComputeInstanceSnippetTabsProps) => { const { ref } = useParams() - const [active, setActive] = useState(tabs[0]) + const [active, setActive] = useState(tabs[0]) const { data: settings } = useProjectSettingsV2Query({ projectRef: ref }, { enabled: !!ref }) - const snippets = buildWorkerSnippets({ + const snippets = buildComputeInstanceSnippets({ ...input, endpoint: settings?.app_config?.endpoint, protocol: settings?.app_config?.protocol, }) - const snippetByTab: Record = { + const snippetByTab: Record = { ai: snippets.aiPrompt, config: snippets.configToml, cli: snippets.cli, diff --git a/apps/studio/components/interfaces/Workers/WorkerStatePill.tsx b/apps/studio/components/interfaces/Compute/ComputeInstanceStatePill.tsx similarity index 58% rename from apps/studio/components/interfaces/Workers/WorkerStatePill.tsx rename to apps/studio/components/interfaces/Compute/ComputeInstanceStatePill.tsx index 8ad3a227375..46f1b70b9fa 100644 --- a/apps/studio/components/interfaces/Workers/WorkerStatePill.tsx +++ b/apps/studio/components/interfaces/Compute/ComputeInstanceStatePill.tsx @@ -1,16 +1,19 @@ import { cn } from 'ui' -import { getWorkerStateMeta } from './Workers.constants' -import type { Worker } from './Workers.types' +import { getComputeInstanceStateMeta } from './Compute.constants' +import type { ComputeInstance } from './Compute.types' -interface WorkerStatePillProps { - worker: Worker +interface ComputeInstanceStatePillProps { + instance: ComputeInstance className?: string } -export const WorkerStatePill = ({ worker, className }: WorkerStatePillProps) => { - const meta = getWorkerStateMeta(worker) - const isPulsing = worker.buildState === 'building' || worker.isDeleting +export const ComputeInstanceStatePill = ({ + instance, + className, +}: ComputeInstanceStatePillProps) => { + const meta = getComputeInstanceStateMeta(instance) + const isPulsing = instance.buildState === 'building' || instance.isDeleting return ( diff --git a/apps/studio/components/interfaces/Workers/WorkersList.test.tsx b/apps/studio/components/interfaces/Compute/ComputeList.test.tsx similarity index 61% rename from apps/studio/components/interfaces/Workers/WorkersList.test.tsx rename to apps/studio/components/interfaces/Compute/ComputeList.test.tsx index 09bd808ad2b..436ec0b6fed 100644 --- a/apps/studio/components/interfaces/Workers/WorkersList.test.tsx +++ b/apps/studio/components/interfaces/Compute/ComputeList.test.tsx @@ -2,12 +2,12 @@ import { screen, within } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { beforeEach, describe, expect, it, vi } from 'vitest' -import type { Worker } from './Workers.types' -import { WorkersList } from './WorkersList' +import type { ComputeInstance } from './Compute.types' +import { ComputeList } from './ComputeList' import { customRender } from '@/tests/lib/custom-render' import { routerMock } from '@/tests/lib/route-mock' -const worker = (name: string, overrides: Partial = {}): Worker => ({ +const instance = (name: string, overrides: Partial = {}): ComputeInstance => ({ name, buildState: 'active', isDeleting: false, @@ -18,11 +18,11 @@ const worker = (name: string, overrides: Partial = {}): Worker => ({ ...overrides, }) -const renderList = (workers: Worker[], onRefresh = vi.fn()) => +const renderList = (instances: ComputeInstance[], onRefresh = vi.fn()) => customRender( - .slice(1) .map((row) => within(row).getAllByRole('cell')[0].textContent) -describe('WorkersList', () => { +describe('ComputeList', () => { beforeEach(() => { - routerMock.setCurrentUrl('/project/default/workers') + routerMock.setCurrentUrl('/project/default/compute') }) - it('renders a row per worker, linking to its detail page', () => { - renderList([worker('embed'), worker('resize', { buildState: 'building' })]) + it('renders a row per instance, linking to its detail page', () => { + renderList([instance('embed'), instance('resize', { buildState: 'building' })]) expect(rowNames()).toEqual(['embed', 'resize']) expect(screen.getByRole('link', { name: 'embed' })).toHaveAttribute( 'href', - '/project/default/workers/embed' + '/project/default/compute/embed' ) expect(screen.getByText('Building')).toBeVisible() }) it('narrows the rows as you search, and says so when nothing matches', async () => { - renderList([worker('embed'), worker('resize-images')]) + renderList([instance('embed'), instance('resize-images')]) await userEvent.type(screen.getByPlaceholderText('Search by name'), 'resize') expect(rowNames()).toEqual(['resize-images']) await userEvent.type(screen.getByPlaceholderText('Search by name'), '-nope') - expect(screen.getByText('No workers match your filters')).toBeVisible() + expect(screen.getByText('No instances match your filters')).toBeVisible() }) - it('refreshes the workers list on request', async () => { + it('refreshes the instances list on request', async () => { const onRefresh = vi.fn() - renderList([worker('embed')], onRefresh) + renderList([instance('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) + it('pages through the instances ten at a time', async () => { + const instances = Array.from({ length: 12 }, (_, index) => instance(`instance-${index}`)) + renderList(instances) expect(rowNames()).toHaveLength(10) expect(screen.getByText('Page 1 of 2')).toBeVisible() @@ -79,19 +79,19 @@ describe('WorkersList', () => { await userEvent.click(screen.getByRole('button', { name: 'Next page' })) - expect(rowNames()).toEqual(['worker-10', 'worker-11']) + expect(rowNames()).toEqual(['instance-10', 'instance-11']) expect(screen.getByRole('button', { name: 'Next page' })).toBeDisabled() }) it('returns to the first page when a search shrinks the results', async () => { - const workers = Array.from({ length: 12 }, (_, index) => worker(`worker-${index}`)) - renderList(workers) + const instances = Array.from({ length: 12 }, (_, index) => instance(`instance-${index}`)) + renderList(instances) await userEvent.click(screen.getByRole('button', { name: 'Next page' })) expect(screen.getByText('Page 2 of 2')).toBeVisible() - await userEvent.type(screen.getByPlaceholderText('Search by name'), 'worker-1') + await userEvent.type(screen.getByPlaceholderText('Search by name'), 'instance-1') expect(screen.getByText('Page 1 of 1')).toBeVisible() - expect(rowNames()).toEqual(['worker-1', 'worker-10', 'worker-11']) + expect(rowNames()).toEqual(['instance-1', 'instance-10', 'instance-11']) }) }) diff --git a/apps/studio/components/interfaces/Workers/WorkersList.tsx b/apps/studio/components/interfaces/Compute/ComputeList.tsx similarity index 77% rename from apps/studio/components/interfaces/Workers/WorkersList.tsx rename to apps/studio/components/interfaces/Compute/ComputeList.tsx index ca2aa4b9d46..30e7e716dbd 100644 --- a/apps/studio/components/interfaces/Workers/WorkersList.tsx +++ b/apps/studio/components/interfaces/Compute/ComputeList.tsx @@ -21,29 +21,33 @@ import { TableRow, } from 'ui' +import { COMPUTE_REGION_SHORT } from './Compute.constants' +import type { + ComputeInstance, + ComputeInstanceAccess, + ComputeInstanceBuildState, +} from './Compute.types' +import { filterComputeInstances, formatResources, getPage } from './Compute.utils' +import { ComputeInstanceStatePill } from './ComputeInstanceStatePill' import { RuntimeBadge } from './RuntimeBadge' -import { WORKERS_REGION_SHORT } from './Workers.constants' -import type { Worker, WorkerAccess, WorkerBuildState } from './Workers.types' -import { filterWorkers, formatResources, getPage } from './Workers.utils' -import { WorkerStatePill } from './WorkerStatePill' import { ButtonTooltip } from '@/components/ui/ButtonTooltip' -interface WorkersListProps { +interface ComputeListProps { projectRef: string - workers: Worker[] + instances: ComputeInstance[] onDeploy: () => void onRefresh: () => void isRefreshing: boolean } -const STATE_FILTERS: { value: WorkerBuildState | 'all'; label: string }[] = [ +const STATE_FILTERS: { value: ComputeInstanceBuildState | 'all'; label: string }[] = [ { value: 'all', label: 'All states' }, { value: 'active', label: 'Active' }, { value: 'building', label: 'Building' }, { value: 'failed', label: 'Failed' }, ] -const ACCESS_FILTERS: { value: WorkerAccess | 'all'; label: string }[] = [ +const ACCESS_FILTERS: { value: ComputeInstanceAccess | 'all'; label: string }[] = [ { value: 'all', label: 'All access' }, { value: 'public', label: 'Public' }, { value: 'private', label: 'Private' }, @@ -51,26 +55,26 @@ const ACCESS_FILTERS: { value: WorkerAccess | 'all'; label: string }[] = [ const PAGE_SIZE = 10 -const parseStateFilter = (value: string): WorkerBuildState | 'all' => +const parseStateFilter = (value: string): ComputeInstanceBuildState | 'all' => STATE_FILTERS.find((option) => option.value === value)?.value ?? 'all' -const parseAccessFilter = (value: string): WorkerAccess | 'all' => +const parseAccessFilter = (value: string): ComputeInstanceAccess | 'all' => ACCESS_FILTERS.find((option) => option.value === value)?.value ?? 'all' -export const WorkersList = ({ +export const ComputeList = ({ projectRef, - workers, + instances, onDeploy, onRefresh, isRefreshing, -}: WorkersListProps) => { +}: ComputeListProps) => { const router = useRouter() const [search, setSearch] = useState('') - const [stateFilter, setStateFilter] = useState('all') - const [accessFilter, setAccessFilter] = useState('all') + const [stateFilter, setStateFilter] = useState('all') + const [accessFilter, setAccessFilter] = useState('all') const [page, setPage] = useState(1) - const filtered = filterWorkers(workers, { + const filtered = filterComputeInstances(instances, { search, state: stateFilter, access: accessFilter, @@ -79,7 +83,7 @@ export const WorkersList = ({ const resetToFirstPage = () => setPage(1) - const workerPagePath = (name: string) => `/project/${projectRef}/workers/${name}` + const computeInstancePagePath = (name: string) => `/project/${projectRef}/compute/${name}` return (
@@ -136,7 +140,7 @@ export const WorkersList = ({ Refresh
@@ -157,39 +161,42 @@ export const WorkersList = ({ {filtered.length === 0 && ( - No workers match your filters + No instances match your filters )} - {paged.map((worker) => ( + {paged.map((instance) => ( router.push(workerPagePath(worker.name))} + onClick={() => router.push(computeInstancePagePath(instance.name))} > - e.stopPropagation()}> - {worker.name} + e.stopPropagation()} + > + {instance.name} - + - + - {worker.access === 'public' ? ( + {instance.access === 'public' ? ( Public ) : ( Private )} - {WORKERS_REGION_SHORT} + {COMPUTE_REGION_SHORT} - {formatResources(worker)} + {formatResources(instance)} ))} @@ -199,7 +206,7 @@ export const WorkersList = ({ {filtered.length > 0 && (

- Showing {startIndex + 1} to {startIndex + paged.length} of {filtered.length} worker + Showing {startIndex + 1} to {startIndex + paged.length} of {filtered.length} instance {filtered.length === 1 ? '' : 's'}

diff --git a/apps/studio/components/interfaces/Workers/DeployWorkerDialog.tsx b/apps/studio/components/interfaces/Compute/DeployComputeInstanceDialog.tsx similarity index 79% rename from apps/studio/components/interfaces/Workers/DeployWorkerDialog.tsx rename to apps/studio/components/interfaces/Compute/DeployComputeInstanceDialog.tsx index 0d77456324b..a2a74fc8e35 100644 --- a/apps/studio/components/interfaces/Workers/DeployWorkerDialog.tsx +++ b/apps/studio/components/interfaces/Compute/DeployComputeInstanceDialog.tsx @@ -23,53 +23,60 @@ import { Admonition } from 'ui-patterns/Admonition' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' import * as z from 'zod' +import { + COMPUTE_INSTANCE_DEPLOYABLE_RUNTIMES, + COMPUTE_INSTANCE_SIZES, + COMPUTE_REGION, +} from './Compute.constants' +import type { ComputeInstanceAccess } from './Compute.types' +import { formatSize, generateComputeInstanceName } from './Compute.utils' +import { ComputeInstanceSnippetTabs } from './ComputeInstanceSnippetTabs' import { RuntimeBadge } from './RuntimeBadge' -import { WORKER_DEPLOYABLE_RUNTIMES, WORKER_SIZES, WORKERS_REGION } from './Workers.constants' -import type { WorkerAccess } from './Workers.types' -import { formatSize, generateWorkerName } from './Workers.utils' -import { WorkerSnippetTabs } from './WorkerSnippetTabs' -const FORM_ID = 'deploy-worker-form' +const FORM_ID = 'deploy-instance-form' -const DeployWorkerFormSchema = z.object({ +const DeployComputeInstanceFormSchema = z.object({ name: z .string() .trim() .min(1, 'Name is required') .regex(/^[a-z0-9-]+$/, 'Lowercase letters, numbers, and hyphens only'), - runtime: z.enum(WORKER_DEPLOYABLE_RUNTIMES), - size: z.enum(WORKER_SIZES), + runtime: z.enum(COMPUTE_INSTANCE_DEPLOYABLE_RUNTIMES), + size: z.enum(COMPUTE_INSTANCE_SIZES), access: z.enum(['private', 'public']), instances: z .union([z.literal(''), z.coerce.number().int().gte(1).lte(10)]) .refine((value) => value !== '', 'Instances is required'), }) -type DeployWorkerFormValues = z.infer +type DeployComputeInstanceFormValues = z.infer -const DEFAULT_VALUES: DeployWorkerFormValues = { +const DEFAULT_VALUES: DeployComputeInstanceFormValues = { name: '', runtime: 'deno', - size: WORKER_SIZES[0], + size: COMPUTE_INSTANCE_SIZES[0], access: 'private', instances: 1, } -const ACCESS_OPTIONS: { value: WorkerAccess; label: string }[] = [ +const ACCESS_OPTIONS: { value: ComputeInstanceAccess; label: string }[] = [ { value: 'private', label: 'Private' }, { value: 'public', label: 'Public' }, ] -interface DeployWorkerDialogProps { +interface DeployComputeInstanceDialogProps { open: boolean onOpenChange: (open: boolean) => void } -export const DeployWorkerDialog = ({ open, onOpenChange }: DeployWorkerDialogProps) => { - const form = useForm({ +export const DeployComputeInstanceDialog = ({ + open, + onOpenChange, +}: DeployComputeInstanceDialogProps) => { + const form = useForm({ mode: 'onBlur', - resolver: zodResolver(DeployWorkerFormSchema), - defaultValues: { ...DEFAULT_VALUES, name: generateWorkerName() }, + resolver: zodResolver(DeployComputeInstanceFormSchema), + defaultValues: { ...DEFAULT_VALUES, name: generateComputeInstanceName() }, }) const [name, runtime, size, access, instances] = useWatch({ @@ -81,7 +88,7 @@ export const DeployWorkerDialog = ({ open, onOpenChange }: DeployWorkerDialogPro - Deploy a worker + Deploy @@ -89,7 +96,7 @@ export const DeployWorkerDialog = ({ open, onOpenChange }: DeployWorkerDialogPro @@ -104,7 +111,7 @@ export const DeployWorkerDialog = ({ open, onOpenChange }: DeployWorkerDialogPro render={({ field }) => ( - + )} @@ -122,7 +129,7 @@ export const DeployWorkerDialog = ({ open, onOpenChange }: DeployWorkerDialogPro - {WORKER_DEPLOYABLE_RUNTIMES.map((value) => ( + {COMPUTE_INSTANCE_DEPLOYABLE_RUNTIMES.map((value) => ( @@ -148,7 +155,7 @@ export const DeployWorkerDialog = ({ open, onOpenChange }: DeployWorkerDialogPro - {WORKER_SIZES.map((value) => ( + {COMPUTE_INSTANCE_SIZES.map((value) => ( {formatSize(value)} @@ -165,7 +172,7 @@ export const DeployWorkerDialog = ({ open, onOpenChange }: DeployWorkerDialogPro render={({ field }) => (