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) pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy