diff --git a/coderd/coderd.go b/coderd/coderd.go
index 6e88759fa340a..2cd94d1eac646 100644
--- a/coderd/coderd.go
+++ b/coderd/coderd.go
@@ -270,7 +270,10 @@ func New(options *Options) *API {
r.Get("/", api.organizationsByUser)
r.Get("/{organizationname}", api.organizationByUserAndName)
})
- r.Get("/workspace/{workspacename}", api.workspaceByOwnerAndName)
+ r.Route("/workspace/{workspacename}", func(r chi.Router) {
+ r.Get("/", api.workspaceByOwnerAndName)
+ r.Get("/builds/{buildnumber}", api.workspaceBuildByBuildNumber)
+ })
r.Get("/gitsshkey", api.gitSSHKey)
r.Put("/gitsshkey", api.regenerateGitSSHKey)
})
diff --git a/coderd/coderd_test.go b/coderd/coderd_test.go
index 02fba90966992..7053b4ff56cb8 100644
--- a/coderd/coderd_test.go
+++ b/coderd/coderd_test.go
@@ -4,6 +4,7 @@ import (
"context"
"io"
"net/http"
+ "strconv"
"strings"
"testing"
"time"
@@ -163,6 +164,10 @@ func TestAuthorizeAllEndpoints(t *testing.T) {
AssertObject: rbac.ResourceWorkspace,
AssertAction: rbac.ActionRead,
},
+ "GET:/api/v2/users/me/workspace/{workspacename}/builds/{buildnumber}": {
+ AssertObject: rbac.ResourceWorkspace,
+ AssertAction: rbac.ActionRead,
+ },
"GET:/api/v2/workspaces/{workspace}/builds/{workspacebuildname}": {
AssertAction: rbac.ActionRead,
AssertObject: workspaceRBACObj,
@@ -388,6 +393,7 @@ func TestAuthorizeAllEndpoints(t *testing.T) {
route = strings.ReplaceAll(route, "{workspacename}", workspace.Name)
route = strings.ReplaceAll(route, "{workspacebuildname}", workspace.LatestBuild.Name)
route = strings.ReplaceAll(route, "{workspaceagent}", workspaceResources[0].Agents[0].ID.String())
+ route = strings.ReplaceAll(route, "{buildnumber}", strconv.FormatInt(int64(workspace.LatestBuild.BuildNumber), 10))
route = strings.ReplaceAll(route, "{template}", template.ID.String())
route = strings.ReplaceAll(route, "{hash}", file.Hash)
route = strings.ReplaceAll(route, "{workspaceresource}", workspaceResources[0].ID.String())
diff --git a/coderd/database/databasefake/databasefake.go b/coderd/database/databasefake/databasefake.go
index 78f85e71c597c..3409d3041e739 100644
--- a/coderd/database/databasefake/databasefake.go
+++ b/coderd/database/databasefake/databasefake.go
@@ -616,6 +616,22 @@ func (q *fakeQuerier) GetWorkspaceBuildByWorkspaceIDAndName(_ context.Context, a
return database.WorkspaceBuild{}, sql.ErrNoRows
}
+func (q *fakeQuerier) GetWorkspaceBuildByWorkspaceIDAndBuildNumber(_ context.Context, arg database.GetWorkspaceBuildByWorkspaceIDAndBuildNumberParams) (database.WorkspaceBuild, error) {
+ q.mutex.RLock()
+ defer q.mutex.RUnlock()
+
+ for _, workspaceBuild := range q.workspaceBuilds {
+ if workspaceBuild.WorkspaceID.String() != arg.WorkspaceID.String() {
+ continue
+ }
+ if workspaceBuild.BuildNumber != arg.BuildNumber {
+ continue
+ }
+ return workspaceBuild, nil
+ }
+ return database.WorkspaceBuild{}, sql.ErrNoRows
+}
+
func (q *fakeQuerier) GetWorkspacesByOrganizationIDs(_ context.Context, req database.GetWorkspacesByOrganizationIDsParams) ([]database.Workspace, error) {
q.mutex.RLock()
defer q.mutex.RUnlock()
diff --git a/coderd/database/querier.go b/coderd/database/querier.go
index 59e8f4577ef8e..c3f57d3a9f795 100644
--- a/coderd/database/querier.go
+++ b/coderd/database/querier.go
@@ -70,6 +70,7 @@ type querier interface {
GetWorkspaceBuildByID(ctx context.Context, id uuid.UUID) (WorkspaceBuild, error)
GetWorkspaceBuildByJobID(ctx context.Context, jobID uuid.UUID) (WorkspaceBuild, error)
GetWorkspaceBuildByWorkspaceID(ctx context.Context, arg GetWorkspaceBuildByWorkspaceIDParams) ([]WorkspaceBuild, error)
+ GetWorkspaceBuildByWorkspaceIDAndBuildNumber(ctx context.Context, arg GetWorkspaceBuildByWorkspaceIDAndBuildNumberParams) (WorkspaceBuild, error)
GetWorkspaceBuildByWorkspaceIDAndName(ctx context.Context, arg GetWorkspaceBuildByWorkspaceIDAndNameParams) (WorkspaceBuild, error)
GetWorkspaceByID(ctx context.Context, id uuid.UUID) (Workspace, error)
GetWorkspaceByOwnerIDAndName(ctx context.Context, arg GetWorkspaceByOwnerIDAndNameParams) (Workspace, error)
diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go
index 06e5f61105269..e83051d65e667 100644
--- a/coderd/database/queries.sql.go
+++ b/coderd/database/queries.sql.go
@@ -3212,6 +3212,41 @@ func (q *sqlQuerier) GetWorkspaceBuildByWorkspaceID(ctx context.Context, arg Get
return items, nil
}
+const getWorkspaceBuildByWorkspaceIDAndBuildNumber = `-- name: GetWorkspaceBuildByWorkspaceIDAndBuildNumber :one
+SELECT
+ id, created_at, updated_at, workspace_id, template_version_id, name, build_number, transition, initiator_id, provisioner_state, job_id, deadline
+FROM
+ workspace_builds
+WHERE
+ workspace_id = $1
+ AND build_number = $2
+`
+
+type GetWorkspaceBuildByWorkspaceIDAndBuildNumberParams struct {
+ WorkspaceID uuid.UUID `db:"workspace_id" json:"workspace_id"`
+ BuildNumber int32 `db:"build_number" json:"build_number"`
+}
+
+func (q *sqlQuerier) GetWorkspaceBuildByWorkspaceIDAndBuildNumber(ctx context.Context, arg GetWorkspaceBuildByWorkspaceIDAndBuildNumberParams) (WorkspaceBuild, error) {
+ row := q.db.QueryRowContext(ctx, getWorkspaceBuildByWorkspaceIDAndBuildNumber, arg.WorkspaceID, arg.BuildNumber)
+ var i WorkspaceBuild
+ err := row.Scan(
+ &i.ID,
+ &i.CreatedAt,
+ &i.UpdatedAt,
+ &i.WorkspaceID,
+ &i.TemplateVersionID,
+ &i.Name,
+ &i.BuildNumber,
+ &i.Transition,
+ &i.InitiatorID,
+ &i.ProvisionerState,
+ &i.JobID,
+ &i.Deadline,
+ )
+ return i, err
+}
+
const getWorkspaceBuildByWorkspaceIDAndName = `-- name: GetWorkspaceBuildByWorkspaceIDAndName :one
SELECT
id, created_at, updated_at, workspace_id, template_version_id, name, build_number, transition, initiator_id, provisioner_state, job_id, deadline
diff --git a/coderd/database/queries/workspacebuilds.sql b/coderd/database/queries/workspacebuilds.sql
index 5b53a874060e8..4bace271cb125 100644
--- a/coderd/database/queries/workspacebuilds.sql
+++ b/coderd/database/queries/workspacebuilds.sql
@@ -27,6 +27,15 @@ WHERE
workspace_id = $1
AND "name" = $2;
+-- name: GetWorkspaceBuildByWorkspaceIDAndBuildNumber :one
+SELECT
+ *
+FROM
+ workspace_builds
+WHERE
+ workspace_id = $1
+ AND build_number = $2;
+
-- name: GetWorkspaceBuildByWorkspaceID :many
SELECT
*
diff --git a/coderd/workspacebuilds.go b/coderd/workspacebuilds.go
index e6505a5395c83..cefe4ded6cdc6 100644
--- a/coderd/workspacebuilds.go
+++ b/coderd/workspacebuilds.go
@@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"net/http"
+ "strconv"
"github.com/go-chi/chi/v5"
"github.com/google/uuid"
@@ -160,6 +161,82 @@ func (api *API) workspaceBuilds(rw http.ResponseWriter, r *http.Request) {
httpapi.Write(rw, http.StatusOK, apiBuilds)
}
+func (api *API) workspaceBuildByBuildNumber(rw http.ResponseWriter, r *http.Request) {
+ owner := httpmw.UserParam(r)
+ workspaceName := chi.URLParam(r, "workspacename")
+ buildNumber, err := strconv.ParseInt(chi.URLParam(r, "buildnumber"), 10, 32)
+ if err != nil {
+ httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{
+ Message: "Failed to parse build number as integer.",
+ Detail: err.Error(),
+ })
+ return
+ }
+
+ workspace, err := api.Database.GetWorkspaceByOwnerIDAndName(r.Context(), database.GetWorkspaceByOwnerIDAndNameParams{
+ OwnerID: owner.ID,
+ Name: workspaceName,
+ })
+ if errors.Is(err, sql.ErrNoRows) {
+ httpapi.Write(rw, http.StatusNotFound, httpapi.Response{
+ Message: fmt.Sprintf("Workspace %q does not exist.", workspaceName),
+ })
+ return
+ }
+ if err != nil {
+ httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
+ Message: "Internal error fetching workspace by name.",
+ Detail: err.Error(),
+ })
+ return
+ }
+
+ if !api.Authorize(rw, r, rbac.ActionRead, rbac.ResourceWorkspace.
+ InOrg(workspace.OrganizationID).WithOwner(workspace.OwnerID.String()).WithID(workspace.ID.String())) {
+ return
+ }
+
+ workspaceBuild, err := api.Database.GetWorkspaceBuildByWorkspaceIDAndBuildNumber(r.Context(), database.GetWorkspaceBuildByWorkspaceIDAndBuildNumberParams{
+ WorkspaceID: workspace.ID,
+ BuildNumber: int32(buildNumber),
+ })
+ if errors.Is(err, sql.ErrNoRows) {
+ httpapi.Write(rw, http.StatusNotFound, httpapi.Response{
+ Message: fmt.Sprintf("Workspace %q Build %d does not exist.", workspaceName, buildNumber),
+ })
+ return
+ }
+ if err != nil {
+ httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
+ Message: "Internal error fetching workspace build.",
+ Detail: err.Error(),
+ })
+ return
+ }
+
+ job, err := api.Database.GetProvisionerJobByID(r.Context(), workspaceBuild.JobID)
+ if err != nil {
+ httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
+ Message: "Internal error fetching provisioner job.",
+ Detail: err.Error(),
+ })
+ return
+ }
+
+ users, err := api.Database.GetUsersByIDs(r.Context(), []uuid.UUID{workspace.OwnerID, workspaceBuild.InitiatorID})
+ if err != nil {
+ httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
+ Message: "Internal error fetching user.",
+ Detail: err.Error(),
+ })
+ return
+ }
+
+ httpapi.Write(rw, http.StatusOK,
+ convertWorkspaceBuild(findUser(workspace.OwnerID, users), findUser(workspaceBuild.InitiatorID, users),
+ workspace, workspaceBuild, job))
+}
+
func (api *API) workspaceBuildByName(rw http.ResponseWriter, r *http.Request) {
workspace := httpmw.WorkspaceParam(r)
if !api.Authorize(rw, r, rbac.ActionRead, rbac.ResourceWorkspace.
diff --git a/coderd/workspacebuilds_test.go b/coderd/workspacebuilds_test.go
index 1734f52b836f9..3833cee09171c 100644
--- a/coderd/workspacebuilds_test.go
+++ b/coderd/workspacebuilds_test.go
@@ -2,7 +2,9 @@ package coderd_test
import (
"context"
+ "fmt"
"net/http"
+ "strconv"
"testing"
"time"
@@ -28,6 +30,94 @@ func TestWorkspaceBuild(t *testing.T) {
require.NoError(t, err)
}
+func TestWorkspaceBuildByBuildNumber(t *testing.T) {
+ t.Parallel()
+ t.Run("Successful", func(t *testing.T) {
+ t.Parallel()
+ client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true})
+ first := coderdtest.CreateFirstUser(t, client)
+ user, err := client.User(context.Background(), codersdk.Me)
+ require.NoError(t, err, "fetch me")
+ version := coderdtest.CreateTemplateVersion(t, client, first.OrganizationID, nil)
+ template := coderdtest.CreateTemplate(t, client, first.OrganizationID, version.ID)
+ coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
+ workspace := coderdtest.CreateWorkspace(t, client, first.OrganizationID, template.ID)
+ _, err = client.WorkspaceBuildByUsernameAndWorkspaceNameAndBuildNumber(
+ context.Background(),
+ user.Username,
+ workspace.Name,
+ strconv.FormatInt(int64(workspace.LatestBuild.BuildNumber), 10),
+ )
+ require.NoError(t, err)
+ })
+
+ t.Run("BuildNumberNotInt", func(t *testing.T) {
+ t.Parallel()
+ client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true})
+ first := coderdtest.CreateFirstUser(t, client)
+ user, err := client.User(context.Background(), codersdk.Me)
+ require.NoError(t, err, "fetch me")
+ version := coderdtest.CreateTemplateVersion(t, client, first.OrganizationID, nil)
+ template := coderdtest.CreateTemplate(t, client, first.OrganizationID, version.ID)
+ coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
+ workspace := coderdtest.CreateWorkspace(t, client, first.OrganizationID, template.ID)
+ _, err = client.WorkspaceBuildByUsernameAndWorkspaceNameAndBuildNumber(
+ context.Background(),
+ user.Username,
+ workspace.Name,
+ "buildNumber",
+ )
+ var apiError *codersdk.Error
+ require.ErrorAs(t, err, &apiError)
+ require.Equal(t, http.StatusBadRequest, apiError.StatusCode())
+ require.ErrorContains(t, apiError, "Failed to parse build number as integer.")
+ })
+
+ t.Run("WorkspaceNotFound", func(t *testing.T) {
+ t.Parallel()
+ client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true})
+ first := coderdtest.CreateFirstUser(t, client)
+ user, err := client.User(context.Background(), codersdk.Me)
+ require.NoError(t, err, "fetch me")
+ version := coderdtest.CreateTemplateVersion(t, client, first.OrganizationID, nil)
+ template := coderdtest.CreateTemplate(t, client, first.OrganizationID, version.ID)
+ coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
+ workspace := coderdtest.CreateWorkspace(t, client, first.OrganizationID, template.ID)
+ _, err = client.WorkspaceBuildByUsernameAndWorkspaceNameAndBuildNumber(
+ context.Background(),
+ user.Username,
+ "workspaceName",
+ strconv.FormatInt(int64(workspace.LatestBuild.BuildNumber), 10),
+ )
+ var apiError *codersdk.Error
+ require.ErrorAs(t, err, &apiError)
+ require.Equal(t, http.StatusNotFound, apiError.StatusCode())
+ require.ErrorContains(t, apiError, "Workspace \"workspaceName\" does not exist.")
+ })
+
+ t.Run("WorkspaceBuildNotFound", func(t *testing.T) {
+ t.Parallel()
+ client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true})
+ first := coderdtest.CreateFirstUser(t, client)
+ user, err := client.User(context.Background(), codersdk.Me)
+ require.NoError(t, err, "fetch me")
+ version := coderdtest.CreateTemplateVersion(t, client, first.OrganizationID, nil)
+ template := coderdtest.CreateTemplate(t, client, first.OrganizationID, version.ID)
+ coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
+ workspace := coderdtest.CreateWorkspace(t, client, first.OrganizationID, template.ID)
+ _, err = client.WorkspaceBuildByUsernameAndWorkspaceNameAndBuildNumber(
+ context.Background(),
+ user.Username,
+ workspace.Name,
+ "200",
+ )
+ var apiError *codersdk.Error
+ require.ErrorAs(t, err, &apiError)
+ require.Equal(t, http.StatusNotFound, apiError.StatusCode())
+ require.ErrorContains(t, apiError, fmt.Sprintf("Workspace %q Build 200 does not exist.", workspace.Name))
+ })
+}
+
func TestWorkspaceBuilds(t *testing.T) {
t.Parallel()
t.Run("Single", func(t *testing.T) {
diff --git a/codersdk/workspacebuilds.go b/codersdk/workspacebuilds.go
index ec5dfcbc63ccd..79dae16d8f12c 100644
--- a/codersdk/workspacebuilds.go
+++ b/codersdk/workspacebuilds.go
@@ -103,3 +103,16 @@ func (c *Client) WorkspaceBuildState(ctx context.Context, build uuid.UUID) ([]by
}
return io.ReadAll(res.Body)
}
+
+func (c *Client) WorkspaceBuildByUsernameAndWorkspaceNameAndBuildNumber(ctx context.Context, username string, workspaceName string, buildNumber string) (WorkspaceBuild, error) {
+ res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/users/%s/workspace/%s/builds/%s", username, workspaceName, buildNumber), nil)
+ if err != nil {
+ return WorkspaceBuild{}, err
+ }
+ defer res.Body.Close()
+ if res.StatusCode != http.StatusOK {
+ return WorkspaceBuild{}, readBodyAsError(res)
+ }
+ var workspaceBuild WorkspaceBuild
+ return workspaceBuild, json.NewDecoder(res.Body).Decode(&workspaceBuild)
+}
diff --git a/site/src/AppRouter.tsx b/site/src/AppRouter.tsx
index 8c8880c9de408..253a3c8e8c63f 100644
--- a/site/src/AppRouter.tsx
+++ b/site/src/AppRouter.tsx
@@ -113,15 +113,6 @@ export const AppRouter: FC = () => (
} />
-
-
-
- }
- />
-
(
}
/>
+
+
+
+
+ }
+ />
diff --git a/site/src/api/api.ts b/site/src/api/api.ts
index 39de3d7aff046..205f42fef8c23 100644
--- a/site/src/api/api.ts
+++ b/site/src/api/api.ts
@@ -268,8 +268,14 @@ export const getWorkspaceBuilds = async (workspaceId: string): Promise => {
- const response = await axios.get(`/api/v2/workspacebuilds/${workspaceId}`)
+export const getWorkspaceBuildByNumber = async (
+ username = "me",
+ workspaceName: string,
+ buildNumber: string,
+): Promise => {
+ const response = await axios.get(
+ `/api/v2/users/${username}/workspace/${workspaceName}/builds/${buildNumber}`,
+ )
return response.data
}
diff --git a/site/src/components/BuildsTable/BuildsTable.tsx b/site/src/components/BuildsTable/BuildsTable.tsx
index 2e924c8157f4f..c9de9bd6d14ea 100644
--- a/site/src/components/BuildsTable/BuildsTable.tsx
+++ b/site/src/components/BuildsTable/BuildsTable.tsx
@@ -8,7 +8,7 @@ import TableRow from "@material-ui/core/TableRow"
import KeyboardArrowRight from "@material-ui/icons/KeyboardArrowRight"
import useTheme from "@material-ui/styles/useTheme"
import { FC } from "react"
-import { useNavigate } from "react-router-dom"
+import { useNavigate, useParams } from "react-router-dom"
import * as TypesGen from "../../api/typesGenerated"
import { displayWorkspaceBuildDuration, getDisplayWorkspaceBuildStatus } from "../../util/workspace"
import { EmptyState } from "../EmptyState/EmptyState"
@@ -29,6 +29,7 @@ export interface BuildsTableProps {
}
export const BuildsTable: FC = ({ builds, className }) => {
+ const { username, workspace: workspaceName } = useParams()
const isLoading = !builds
const theme: Theme = useTheme()
const navigate = useNavigate()
@@ -52,7 +53,7 @@ export const BuildsTable: FC = ({ builds, className }) => {
const status = getDisplayWorkspaceBuildStatus(theme, build)
const navigateToBuildPage = () => {
- navigate(`/builds/${build.id}`)
+ navigate(`/@${username}/${workspaceName}/builds/${build.build_number}`)
}
return (
diff --git a/site/src/pages/WorkspaceBuildPage/WorkspaceBuildPage.test.tsx b/site/src/pages/WorkspaceBuildPage/WorkspaceBuildPage.test.tsx
index cebff84da793d..45d13caa88496 100644
--- a/site/src/pages/WorkspaceBuildPage/WorkspaceBuildPage.test.tsx
+++ b/site/src/pages/WorkspaceBuildPage/WorkspaceBuildPage.test.tsx
@@ -1,6 +1,11 @@
import { screen } from "@testing-library/react"
import * as API from "../../api/api"
-import { MockWorkspaceBuild, MockWorkspaceBuildLogs, renderWithAuth } from "../../testHelpers/renderHelpers"
+import {
+ MockWorkspace,
+ MockWorkspaceBuild,
+ MockWorkspaceBuildLogs,
+ renderWithAuth,
+} from "../../testHelpers/renderHelpers"
import { WorkspaceBuildPage } from "./WorkspaceBuildPage"
describe("WorkspaceBuildPage", () => {
@@ -16,7 +21,10 @@ describe("WorkspaceBuildPage", () => {
closed: Promise.resolve(undefined),
cancel: jest.fn(),
})
- renderWithAuth(, { route: `/builds/${MockWorkspaceBuild.id}`, path: "/builds/:buildId" })
+ renderWithAuth(, {
+ route: `/@${MockWorkspace.owner_name}/${MockWorkspace.name}/builds/${MockWorkspace.latest_build.build_number}`,
+ path: "/@:username/:workspace/builds/:buildNumber",
+ })
await screen.findByText(MockWorkspaceBuild.workspace_name)
await screen.findByText(MockWorkspaceBuildLogs[0].stage)
diff --git a/site/src/pages/WorkspaceBuildPage/WorkspaceBuildPage.tsx b/site/src/pages/WorkspaceBuildPage/WorkspaceBuildPage.tsx
index 46550ac62225a..9d570db458dfe 100644
--- a/site/src/pages/WorkspaceBuildPage/WorkspaceBuildPage.tsx
+++ b/site/src/pages/WorkspaceBuildPage/WorkspaceBuildPage.tsx
@@ -6,19 +6,9 @@ import { pageTitle } from "../../util/page"
import { workspaceBuildMachine } from "../../xServices/workspaceBuild/workspaceBuildXService"
import { WorkspaceBuildPageView } from "./WorkspaceBuildPageView"
-const useBuildId = () => {
- const { buildId } = useParams()
-
- if (!buildId) {
- throw new Error("buildId param is required.")
- }
-
- return buildId
-}
-
export const WorkspaceBuildPage: FC = () => {
- const buildId = useBuildId()
- const [buildState] = useMachine(workspaceBuildMachine, { context: { buildId } })
+ const { username, workspace: workspaceName, buildNumber } = useParams()
+ const [buildState] = useMachine(workspaceBuildMachine, { context: { username, workspaceName, buildNumber } })
const { logs, build } = buildState.context
const isWaitingForLogs = !buildState.matches("logs.loaded")
diff --git a/site/src/testHelpers/handlers.ts b/site/src/testHelpers/handlers.ts
index b77dd09c11aca..bfb8e9be0f4ff 100644
--- a/site/src/testHelpers/handlers.ts
+++ b/site/src/testHelpers/handlers.ts
@@ -115,7 +115,7 @@ export const handlers = [
rest.get("/api/v2/workspaces/:workspaceId/builds", async (req, res, ctx) => {
return res(ctx.status(200), ctx.json(M.MockBuilds))
}),
- rest.get("/api/v2/workspacebuilds/:workspaceBuildId", (req, res, ctx) => {
+ rest.get("/api/v2/users/:username/workspace/:workspaceName/builds/:buildNumber", (req, res, ctx) => {
return res(ctx.status(200), ctx.json(M.MockWorkspaceBuild))
}),
rest.get("/api/v2/workspacebuilds/:workspaceBuildId/resources", (req, res, ctx) => {
diff --git a/site/src/xServices/workspaceBuild/workspaceBuildXService.ts b/site/src/xServices/workspaceBuild/workspaceBuildXService.ts
index 88008892fe251..81883b3eaaa08 100644
--- a/site/src/xServices/workspaceBuild/workspaceBuildXService.ts
+++ b/site/src/xServices/workspaceBuild/workspaceBuildXService.ts
@@ -4,6 +4,9 @@ import { ProvisionerJobLog, WorkspaceBuild } from "../../api/typesGenerated"
type LogsContext = {
// Build
+ username: string
+ workspaceName: string
+ buildNumber: string
buildId: string
build?: WorkspaceBuild
getBuildError?: Error | unknown
@@ -36,28 +39,23 @@ export const workspaceBuildMachine = createMachine(
},
},
tsTypes: {} as import("./workspaceBuildXService.typegen").Typegen0,
- type: "parallel",
+ initial: "gettingBuild",
states: {
- build: {
- initial: "gettingBuild",
- states: {
- gettingBuild: {
- entry: "clearGetBuildError",
- invoke: {
- src: "getWorkspaceBuild",
- onDone: {
- target: "idle",
- actions: "assignBuild",
- },
- onError: {
- target: "idle",
- actions: "assignGetBuildError",
- },
- },
+ gettingBuild: {
+ entry: "clearGetBuildError",
+ invoke: {
+ src: "getWorkspaceBuild",
+ onDone: {
+ target: "logs",
+ actions: ["assignBuild", "assignBuildId"],
+ },
+ onError: {
+ target: "idle",
+ actions: "assignGetBuildError",
},
- idle: {},
},
},
+ idle: {},
logs: {
initial: "gettingExistentLogs",
states: {
@@ -95,6 +93,10 @@ export const workspaceBuildMachine = createMachine(
},
{
actions: {
+ // Build ID
+ assignBuildId: assign({
+ buildId: (_, event) => event.data.id,
+ }),
// Build
assignBuild: assign({
build: (_, event) => event.data,
@@ -117,7 +119,7 @@ export const workspaceBuildMachine = createMachine(
}),
},
services: {
- getWorkspaceBuild: (ctx) => API.getWorkspaceBuild(ctx.buildId),
+ getWorkspaceBuild: (ctx) => API.getWorkspaceBuildByNumber(ctx.username, ctx.workspaceName, ctx.buildNumber),
getLogs: async (ctx) => API.getWorkspaceBuildLogs(ctx.buildId),
streamWorkspaceBuildLogs: (ctx) => async (callback) => {
const reader = await API.streamWorkspaceBuildLogs(ctx.buildId)