From e6d1b85e8dbe874e13bc9ae4b43c1e1af228fe42 Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Wed, 4 Oct 2023 14:26:54 +0000 Subject: [PATCH 1/7] Add based structure on queries --- site/src/api/api.ts | 8 +- site/src/api/queries/workspaceBuilds.ts | 15 ++- site/src/api/typesGenerated.ts | 9 +- .../WorkspaceBuildPage/WorkspaceBuildPage.tsx | 11 +- .../src/pages/WorkspacePage/WorkspacePage.tsx | 12 ++ .../WorkspacePage/WorkspaceReadyPage.tsx | 8 +- .../xServices/workspace/workspaceXService.ts | 104 ++---------------- 7 files changed, 55 insertions(+), 112 deletions(-) diff --git a/site/src/api/api.ts b/site/src/api/api.ts index 09a9eb760bf3c..415e66d817933 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -775,10 +775,10 @@ export const regenerateUserSSHKey = async ( export const getWorkspaceBuilds = async ( workspaceId: string, - since: Date, -): Promise => { - const response = await axios.get( - `/api/v2/workspaces/${workspaceId}/builds?since=${since.toISOString()}`, + req?: TypesGen.WorkspaceBuildsRequest, +) => { + const response = await axios.get( + getURLWithSearchParams(`/api/v2/workspaces/${workspaceId}/builds`, req), ); return response.data; }; diff --git a/site/src/api/queries/workspaceBuilds.ts b/site/src/api/queries/workspaceBuilds.ts index 207e7374f39f5..891b0615ccf85 100644 --- a/site/src/api/queries/workspaceBuilds.ts +++ b/site/src/api/queries/workspaceBuilds.ts @@ -1,4 +1,5 @@ import * as API from "api/api"; +import { WorkspaceBuildsRequest } from "api/typesGenerated"; export const workspaceBuildByNumber = ( username: string, @@ -6,8 +7,20 @@ export const workspaceBuildByNumber = ( buildNumber: number, ) => { return { - queryKey: [username, workspaceName, "workspaceBuild", buildNumber], + queryKey: ["workspaceBuild", username, workspaceName, buildNumber], queryFn: () => API.getWorkspaceBuildByNumber(username, workspaceName, buildNumber), }; }; + +export const workspaceBuilds = ( + workspaceId: string, + req?: WorkspaceBuildsRequest, +) => { + return { + queryKey: ["workspaceBuilds", workspaceId, req], + queryFn: () => { + return API.getWorkspaceBuilds(workspaceId, req); + }, + }; +}; diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 3ce72f96dbfd4..aca820389e363 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -1479,8 +1479,13 @@ export interface WorkspaceBuildParameter { // From codersdk/workspaces.go export interface WorkspaceBuildsRequest extends Pagination { - readonly WorkspaceID: string; - readonly Since: string; + readonly since?: string; +} + +// From codersdk/workspaces.go +export interface WorkspaceBuildsResponse { + readonly builds: WorkspaceBuild[]; + readonly count: number; } // From codersdk/deployment.go diff --git a/site/src/pages/WorkspaceBuildPage/WorkspaceBuildPage.tsx b/site/src/pages/WorkspaceBuildPage/WorkspaceBuildPage.tsx index d4056f2e76459..4a54cd456c30b 100644 --- a/site/src/pages/WorkspaceBuildPage/WorkspaceBuildPage.tsx +++ b/site/src/pages/WorkspaceBuildPage/WorkspaceBuildPage.tsx @@ -23,13 +23,12 @@ export const WorkspaceBuildPage: FC = () => { keepPreviousData: true, }); const build = wsBuildQuery.data; - const { data: builds } = useQuery({ + const buildsQuery = useQuery({ queryKey: ["builds", username, build?.workspace_id], queryFn: () => { - return getWorkspaceBuilds( - build?.workspace_id ?? "", - dayjs().add(-30, "day").toDate(), - ); + return getWorkspaceBuilds(build?.workspace_id ?? "", { + since: dayjs().add(-30, "day").toISOString(), + }); }, enabled: Boolean(build), }); @@ -50,7 +49,7 @@ export const WorkspaceBuildPage: FC = () => { diff --git a/site/src/pages/WorkspacePage/WorkspacePage.tsx b/site/src/pages/WorkspacePage/WorkspacePage.tsx index 44ab8e0fbe1ae..846b98ec342ac 100644 --- a/site/src/pages/WorkspacePage/WorkspacePage.tsx +++ b/site/src/pages/WorkspacePage/WorkspacePage.tsx @@ -11,6 +11,7 @@ import { isAxiosError } from "axios"; import { Margins } from "components/Margins/Margins"; import { workspaceQuota } from "api/queries/workspaceQuota"; import { useQuery } from "@tanstack/react-query"; +import { workspaceBuilds } from "api/queries/workspaceBuilds"; export const WorkspacePage: FC = () => { const params = useParams() as { @@ -26,10 +27,19 @@ export const WorkspacePage: FC = () => { workspaceName, username, }, + actions: { + refreshBuilds: () => { + console.log("REFETCH!"); + }, + }, }); const { workspace, error } = workspaceState.context; const quotaQuery = useQuery(workspaceQuota(username)); const pageError = error ?? quotaQuery.error; + const buildsQuery = useQuery({ + ...workspaceBuilds(workspace?.id ?? ""), + enabled: Boolean(workspace), + }); if (pageError) { return ( @@ -53,6 +63,8 @@ export const WorkspacePage: FC = () => { workspaceState={workspaceState} quota={quotaQuery.data} workspaceSend={workspaceSend} + builds={buildsQuery.data?.builds} + buildsError={buildsQuery.error} /> ); diff --git a/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx b/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx index 1316a743c4c52..8a5dcd5306af7 100644 --- a/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx +++ b/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx @@ -39,12 +39,16 @@ interface WorkspaceReadyPageProps { workspaceState: StateFrom; workspaceSend: (event: WorkspaceEvent) => void; quota?: TypesGen.WorkspaceQuota; + builds: TypesGen.WorkspaceBuild[] | undefined; + buildsError: unknown; } export const WorkspaceReadyPage = ({ workspaceState, workspaceSend, quota, + builds, + buildsError, }: WorkspaceReadyPageProps): JSX.Element => { const [_, bannerSend] = useActor( workspaceState.children["scheduleBannerMachine"], @@ -56,8 +60,6 @@ export const WorkspaceReadyPage = ({ template, templateVersion: currentVersion, deploymentValues, - builds, - getBuildsError, buildError, cancellationError, sshPrefix, @@ -175,7 +177,7 @@ export const WorkspaceReadyPage = ({ hideSSHButton={featureVisibility["browser_only"]} hideVSCodeDesktopButton={featureVisibility["browser_only"]} workspaceErrors={{ - [WorkspaceErrors.GET_BUILDS_ERROR]: getBuildsError, + [WorkspaceErrors.GET_BUILDS_ERROR]: buildsError, [WorkspaceErrors.BUILD_ERROR]: buildError || restartBuildError, [WorkspaceErrors.CANCELLATION_ERROR]: cancellationError, }} diff --git a/site/src/xServices/workspace/workspaceXService.ts b/site/src/xServices/workspace/workspaceXService.ts index 26e96dbf06750..baeec591e7fc6 100644 --- a/site/src/xServices/workspace/workspaceXService.ts +++ b/site/src/xServices/workspace/workspaceXService.ts @@ -1,43 +1,10 @@ import { getErrorMessage } from "api/errors"; -import dayjs from "dayjs"; import { workspaceScheduleBannerMachine } from "xServices/workspaceSchedule/workspaceScheduleBannerXService"; -import { assign, createMachine, send } from "xstate"; +import { assign, createMachine } from "xstate"; import * as API from "api/api"; import * as TypesGen from "api/typesGenerated"; import { displayError, displaySuccess } from "components/GlobalSnackbar/utils"; -const latestBuild = (builds: TypesGen.WorkspaceBuild[]) => { - // Cloning builds to not change the origin object with the sort() - return [...builds].sort((a, b) => { - return new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime(); - })[0]; -}; - -const moreBuildsAvailable = ( - context: WorkspaceContext, - event: { - type: "REFRESH_TIMELINE"; - checkRefresh?: boolean; - data?: TypesGen.ServerSentEvent["data"]; - }, -) => { - // No need to refresh the timeline if it is not loaded - if (!context.builds) { - return false; - } - - if (!event.checkRefresh) { - return true; - } - - // After we refresh a workspace, we want to check if the latest - // build was updated before refreshing the timeline so as to not over fetch the builds - const latestBuildInTimeline = latestBuild(context.builds); - return ( - event.data.latest_build.updated_at !== latestBuildInTimeline.updated_at - ); -}; - type Permissions = Record, boolean>; export interface WorkspaceContext { @@ -87,8 +54,6 @@ export type WorkspaceEvent = | { type: "CANCEL" } | { type: "REFRESH_TIMELINE"; - checkRefresh?: boolean; - data?: TypesGen.ServerSentEvent["data"]; } | { type: "EVENT_SOURCE_ERROR"; error: unknown } | { type: "INCREASE_DEADLINE"; hours: number } @@ -201,6 +166,11 @@ export const workspaceMachine = createMachine( }, ready: { type: "parallel", + on: { + REFRESH_TIMELINE: { + actions: ["refreshBuilds"], + }, + }, states: { listening: { initial: "gettingEvents", @@ -422,36 +392,6 @@ export const workspaceMachine = createMachine( }, }, }, - timeline: { - initial: "gettingBuilds", - states: { - gettingBuilds: { - invoke: { - src: "getBuilds", - onDone: [ - { - actions: ["assignBuilds", "clearGetBuildsError"], - target: "loadedBuilds", - }, - ], - onError: [ - { - actions: "assignGetBuildsError", - target: "loadedBuilds", - }, - ], - }, - }, - loadedBuilds: { - on: { - REFRESH_TIMELINE: { - target: "#workspaceState.ready.timeline.gettingBuilds", - cond: "moreBuildsAvailable", - }, - }, - }, - }, - }, sshConfig: { initial: "gettingSshConfig", states: { @@ -553,16 +493,6 @@ export const workspaceMachine = createMachine( logWatchWorkspaceWarning: (_, event) => { console.error("Watch workspace error:", event); }, - // Timeline - assignBuilds: assign({ - builds: (_, event) => event.data, - }), - assignGetBuildsError: assign({ - getBuildsError: (_, event) => event.data, - }), - clearGetBuildsError: assign({ - getBuildsError: (_) => undefined, - }), // SSH assignSSHPrefix: assign({ sshPrefix: (_, { data }) => data.hostname_prefix, @@ -599,7 +529,6 @@ export const workspaceMachine = createMachine( }), }, guards: { - moreBuildsAvailable, isMissingBuildParameterError: (_, { data }) => { return data instanceof API.MissingBuildParameters; }, @@ -670,7 +599,7 @@ export const workspaceMachine = createMachine( throw Error("Cannot stop workspace without workspace id"); } }, - deleteWorkspace: async (context) => { + deleteWorkspace: (context) => async (send) => { if (context.workspace) { const deleteWorkspacePromise = await API.deleteWorkspace( context.workspace.id, @@ -715,12 +644,7 @@ export const workspaceMachine = createMachine( // refresh our workspace with each SSE send({ type: "REFRESH_WORKSPACE", data: JSON.parse(event.data) }); // refresh our timeline - send({ - type: "REFRESH_TIMELINE", - checkRefresh: true, - data: JSON.parse(event.data), - }); - // refresh + send({ type: "REFRESH_TIMELINE" }); }); // handle any error events returned by our sse @@ -737,18 +661,6 @@ export const workspaceMachine = createMachine( context.eventSource?.close(); }; }, - getBuilds: async (context) => { - if (context.workspace) { - // For now, we only retrieve the last month of builds to minimize - // page bloat. We should add pagination in the future. - return await API.getWorkspaceBuilds( - context.workspace.id, - dayjs().add(-30, "day").toDate(), - ); - } else { - throw Error("Cannot get builds without id"); - } - }, scheduleBannerMachine: workspaceScheduleBannerMachine, getSSHPrefix: async () => { return API.getDeploymentSSHConfig(); From fc41851c69b528c5cedd0bd3d8f73b77fa4a3b69 Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Wed, 4 Oct 2023 14:41:39 +0000 Subject: [PATCH 2/7] Fix types --- codersdk/workspaces.go | 4 ++-- site/src/api/api.ts | 2 +- site/src/api/typesGenerated.ts | 6 ------ site/src/pages/WorkspaceBuildPage/WorkspaceBuildPage.tsx | 2 +- site/src/pages/WorkspacePage/WorkspacePage.tsx | 2 +- 5 files changed, 5 insertions(+), 11 deletions(-) diff --git a/codersdk/workspaces.go b/codersdk/workspaces.go index 05e1a156d1122..0ebf9cdba70e3 100644 --- a/codersdk/workspaces.go +++ b/codersdk/workspaces.go @@ -135,9 +135,9 @@ func (c *Client) getWorkspace(ctx context.Context, id uuid.UUID, opts ...Request } type WorkspaceBuildsRequest struct { - WorkspaceID uuid.UUID + WorkspaceID uuid.UUID `json:"workspace_id" typescript:"-"` Pagination - Since time.Time + Since time.Time `json:"since,omitempty"` } func (c *Client) WorkspaceBuilds(ctx context.Context, req WorkspaceBuildsRequest) ([]WorkspaceBuild, error) { diff --git a/site/src/api/api.ts b/site/src/api/api.ts index 415e66d817933..953ebf1dfa833 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -777,7 +777,7 @@ export const getWorkspaceBuilds = async ( workspaceId: string, req?: TypesGen.WorkspaceBuildsRequest, ) => { - const response = await axios.get( + const response = await axios.get( getURLWithSearchParams(`/api/v2/workspaces/${workspaceId}/builds`, req), ); return response.data; diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index aca820389e363..240d9ba3b90e0 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -1482,12 +1482,6 @@ export interface WorkspaceBuildsRequest extends Pagination { readonly since?: string; } -// From codersdk/workspaces.go -export interface WorkspaceBuildsResponse { - readonly builds: WorkspaceBuild[]; - readonly count: number; -} - // From codersdk/deployment.go export interface WorkspaceConnectionLatencyMS { readonly P50: number; diff --git a/site/src/pages/WorkspaceBuildPage/WorkspaceBuildPage.tsx b/site/src/pages/WorkspaceBuildPage/WorkspaceBuildPage.tsx index 4a54cd456c30b..1d8707026799f 100644 --- a/site/src/pages/WorkspaceBuildPage/WorkspaceBuildPage.tsx +++ b/site/src/pages/WorkspaceBuildPage/WorkspaceBuildPage.tsx @@ -49,7 +49,7 @@ export const WorkspaceBuildPage: FC = () => { diff --git a/site/src/pages/WorkspacePage/WorkspacePage.tsx b/site/src/pages/WorkspacePage/WorkspacePage.tsx index 846b98ec342ac..911d09913ac16 100644 --- a/site/src/pages/WorkspacePage/WorkspacePage.tsx +++ b/site/src/pages/WorkspacePage/WorkspacePage.tsx @@ -63,7 +63,7 @@ export const WorkspacePage: FC = () => { workspaceState={workspaceState} quota={quotaQuery.data} workspaceSend={workspaceSend} - builds={buildsQuery.data?.builds} + builds={buildsQuery.data} buildsError={buildsQuery.error} /> From cc4806dc739d3f898317a59020e285e773722efb Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Thu, 5 Oct 2023 12:59:54 +0000 Subject: [PATCH 3/7] feat(site): load previous builds --- site/src/api/queries/workspaceBuilds.ts | 19 +++-- .../WorkspacePage/BuildsTable.stories.tsx | 8 ++ site/src/pages/WorkspacePage/BuildsTable.tsx | 77 +++++++++++++------ site/src/pages/WorkspacePage/Workspace.tsx | 15 +++- .../src/pages/WorkspacePage/WorkspacePage.tsx | 15 ++-- .../WorkspacePage/WorkspaceReadyPage.tsx | 9 +++ .../xServices/workspace/workspaceXService.ts | 17 +++- 7 files changed, 120 insertions(+), 40 deletions(-) diff --git a/site/src/api/queries/workspaceBuilds.ts b/site/src/api/queries/workspaceBuilds.ts index 891b0615ccf85..c23f625b40835 100644 --- a/site/src/api/queries/workspaceBuilds.ts +++ b/site/src/api/queries/workspaceBuilds.ts @@ -1,5 +1,6 @@ +import { UseInfiniteQueryOptions } from "@tanstack/react-query"; import * as API from "api/api"; -import { WorkspaceBuildsRequest } from "api/typesGenerated"; +import { WorkspaceBuild, WorkspaceBuildsRequest } from "api/typesGenerated"; export const workspaceBuildByNumber = ( username: string, @@ -13,14 +14,22 @@ export const workspaceBuildByNumber = ( }; }; -export const workspaceBuilds = ( +export const infiniteWorkspaceBuilds = ( workspaceId: string, req?: WorkspaceBuildsRequest, -) => { +): UseInfiniteQueryOptions => { + const limit = req?.limit ?? 25; + return { queryKey: ["workspaceBuilds", workspaceId, req], - queryFn: () => { - return API.getWorkspaceBuilds(workspaceId, req); + getNextPageParam: (lastPage, pages) => { + return pages.length + 1; + }, + queryFn: ({ pageParam = 0 }) => { + return API.getWorkspaceBuilds(workspaceId, { + limit, + offset: pageParam <= 0 ? 0 : (pageParam - 1) * limit, + }); }, }; }; diff --git a/site/src/pages/WorkspacePage/BuildsTable.stories.tsx b/site/src/pages/WorkspacePage/BuildsTable.stories.tsx index d454b5e9a88c4..0c5f1adf64ac2 100644 --- a/site/src/pages/WorkspacePage/BuildsTable.stories.tsx +++ b/site/src/pages/WorkspacePage/BuildsTable.stories.tsx @@ -13,6 +13,7 @@ type Story = StoryObj; export const Example: Story = { args: { builds: MockBuilds, + hasMoreBuilds: true, }, }; @@ -21,3 +22,10 @@ export const Empty: Story = { builds: [], }, }; + +export const NoMoreBuilds: Story = { + args: { + builds: MockBuilds, + hasMoreBuilds: false, + }, +}; diff --git a/site/src/pages/WorkspacePage/BuildsTable.tsx b/site/src/pages/WorkspacePage/BuildsTable.tsx index d74a971c642a2..b6b725eb72290 100644 --- a/site/src/pages/WorkspacePage/BuildsTable.tsx +++ b/site/src/pages/WorkspacePage/BuildsTable.tsx @@ -10,43 +10,70 @@ import * as TypesGen from "api/typesGenerated"; import { EmptyState } from "components/EmptyState/EmptyState"; import { TableLoader } from "components/TableLoader/TableLoader"; import { BuildRow } from "./BuildRow"; +import { Stack } from "components/Stack/Stack"; +import LoadingButton from "@mui/lab/LoadingButton"; +import ArrowDownwardOutlined from "@mui/icons-material/ArrowDownwardOutlined"; export const Language = { emptyMessage: "No builds found", }; export interface BuildsTableProps { - builds?: TypesGen.WorkspaceBuild[]; + builds: TypesGen.WorkspaceBuild[] | undefined; + onLoadMoreBuilds: () => void; + isLoadingMoreBuilds: boolean; + hasMoreBuilds: boolean; } export const BuildsTable: FC> = ({ builds, + onLoadMoreBuilds, + isLoadingMoreBuilds, + hasMoreBuilds, }) => { return ( - - - - {builds ? ( - new Date(build.created_at)} - row={(build) => } - /> - ) : ( - - )} + + +
+ + {builds ? ( + new Date(build.created_at)} + row={(build) => } + /> + ) : ( + + )} - {builds && builds.length === 0 && ( - - - - - - - - )} - -
-
+ {builds && builds.length === 0 && ( + + + + + + + + )} + + + + } + css={{ + display: "inline-flex", + margin: "auto", + borderRadius: "9999px", + }} + > + Load previous builds + + ); }; diff --git a/site/src/pages/WorkspacePage/Workspace.tsx b/site/src/pages/WorkspacePage/Workspace.tsx index c2d178d7d1839..ebfab404172d4 100644 --- a/site/src/pages/WorkspacePage/Workspace.tsx +++ b/site/src/pages/WorkspacePage/Workspace.tsx @@ -55,7 +55,6 @@ export interface WorkspaceProps { isRestarting: boolean; workspace: TypesGen.Workspace; resources?: TypesGen.WorkspaceResource[]; - builds?: TypesGen.WorkspaceBuild[]; templateWarnings?: TypesGen.TemplateVersionWarning[]; canUpdateWorkspace: boolean; updateMessage?: string; @@ -70,6 +69,10 @@ export interface WorkspaceProps { quotaBudget?: number; handleBuildRetry: () => void; buildLogs?: React.ReactNode; + builds: TypesGen.WorkspaceBuild[] | undefined; + onLoadMoreBuilds: () => void; + isLoadingMoreBuilds: boolean; + hasMoreBuilds: boolean; } /** @@ -105,6 +108,9 @@ export const Workspace: FC> = ({ handleBuildRetry, templateWarnings, buildLogs, + onLoadMoreBuilds, + isLoadingMoreBuilds, + hasMoreBuilds, }) => { const styles = useStyles(); const navigate = useNavigate(); @@ -345,7 +351,12 @@ export const Workspace: FC> = ({ error={workspaceErrors[WorkspaceErrors.GET_BUILDS_ERROR]} /> ) : ( - + )} diff --git a/site/src/pages/WorkspacePage/WorkspacePage.tsx b/site/src/pages/WorkspacePage/WorkspacePage.tsx index 911d09913ac16..e37a31a6bfbc2 100644 --- a/site/src/pages/WorkspacePage/WorkspacePage.tsx +++ b/site/src/pages/WorkspacePage/WorkspacePage.tsx @@ -10,8 +10,8 @@ import { useOrganizationId } from "hooks"; import { isAxiosError } from "axios"; import { Margins } from "components/Margins/Margins"; import { workspaceQuota } from "api/queries/workspaceQuota"; -import { useQuery } from "@tanstack/react-query"; -import { workspaceBuilds } from "api/queries/workspaceBuilds"; +import { useInfiniteQuery, useQuery } from "@tanstack/react-query"; +import { infiniteWorkspaceBuilds } from "api/queries/workspaceBuilds"; export const WorkspacePage: FC = () => { const params = useParams() as { @@ -36,8 +36,8 @@ export const WorkspacePage: FC = () => { const { workspace, error } = workspaceState.context; const quotaQuery = useQuery(workspaceQuota(username)); const pageError = error ?? quotaQuery.error; - const buildsQuery = useQuery({ - ...workspaceBuilds(workspace?.id ?? ""), + const buildsQuery = useInfiniteQuery({ + ...infiniteWorkspaceBuilds(workspace?.id ?? ""), enabled: Boolean(workspace), }); @@ -63,8 +63,13 @@ export const WorkspacePage: FC = () => { workspaceState={workspaceState} quota={quotaQuery.data} workspaceSend={workspaceSend} - builds={buildsQuery.data} + builds={buildsQuery.data?.pages.flat()} buildsError={buildsQuery.error} + isLoadingMoreBuilds={buildsQuery.isFetchingNextPage} + onLoadMoreBuilds={async () => { + await buildsQuery.fetchNextPage(); + }} + hasMoreBuilds={Boolean(buildsQuery.hasNextPage)} /> ); diff --git a/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx b/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx index 8a5dcd5306af7..53ef8a1ecc876 100644 --- a/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx +++ b/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx @@ -41,6 +41,9 @@ interface WorkspaceReadyPageProps { quota?: TypesGen.WorkspaceQuota; builds: TypesGen.WorkspaceBuild[] | undefined; buildsError: unknown; + onLoadMoreBuilds: () => void; + isLoadingMoreBuilds: boolean; + hasMoreBuilds: boolean; } export const WorkspaceReadyPage = ({ @@ -49,6 +52,9 @@ export const WorkspaceReadyPage = ({ quota, builds, buildsError, + onLoadMoreBuilds, + isLoadingMoreBuilds, + hasMoreBuilds, }: WorkspaceReadyPageProps): JSX.Element => { const [_, bannerSend] = useActor( workspaceState.children["scheduleBannerMachine"], @@ -170,6 +176,9 @@ export const WorkspaceReadyPage = ({ handleDormantActivate={() => workspaceSend({ type: "ACTIVATE" })} resources={workspace.latest_build.resources} builds={builds} + onLoadMoreBuilds={onLoadMoreBuilds} + isLoadingMoreBuilds={isLoadingMoreBuilds} + hasMoreBuilds={hasMoreBuilds} canUpdateWorkspace={canUpdateWorkspace} updateMessage={latestVersion?.message} canRetryDebugMode={canRetryDebugMode} diff --git a/site/src/xServices/workspace/workspaceXService.ts b/site/src/xServices/workspace/workspaceXService.ts index baeec591e7fc6..74161d95fed6d 100644 --- a/site/src/xServices/workspace/workspaceXService.ts +++ b/site/src/xServices/workspace/workspaceXService.ts @@ -641,10 +641,21 @@ export const workspaceMachine = createMachine( } context.eventSource.addEventListener("data", (event) => { + const newWorkspaceData = JSON.parse(event.data) as TypesGen.Workspace; // refresh our workspace with each SSE - send({ type: "REFRESH_WORKSPACE", data: JSON.parse(event.data) }); - // refresh our timeline - send({ type: "REFRESH_TIMELINE" }); + send({ type: "REFRESH_WORKSPACE", data: newWorkspaceData }); + + const currentWorkspace = context.workspace!; + const hasNewBuild = + newWorkspaceData.latest_build.id !== + currentWorkspace.latest_build.id; + const lastBuildHasChanged = + newWorkspaceData.latest_build.status !== + currentWorkspace.latest_build.status; + + if (hasNewBuild || lastBuildHasChanged) { + send({ type: "REFRESH_TIMELINE" }); + } }); // handle any error events returned by our sse From 611e733c65d07e196416969614e73ecc61fcb419 Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Thu, 5 Oct 2023 13:15:22 +0000 Subject: [PATCH 4/7] Fix missed console.log --- site/src/pages/WorkspacePage/WorkspacePage.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/site/src/pages/WorkspacePage/WorkspacePage.tsx b/site/src/pages/WorkspacePage/WorkspacePage.tsx index e37a31a6bfbc2..53487210ca453 100644 --- a/site/src/pages/WorkspacePage/WorkspacePage.tsx +++ b/site/src/pages/WorkspacePage/WorkspacePage.tsx @@ -28,8 +28,8 @@ export const WorkspacePage: FC = () => { username, }, actions: { - refreshBuilds: () => { - console.log("REFETCH!"); + refreshBuilds: async () => { + await buildsQuery.refetch(); }, }, }); From c56a0f6aec552b974a3ee855f5361dc35b280f38 Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Thu, 5 Oct 2023 13:31:51 +0000 Subject: [PATCH 5/7] Try to fix go warnings --- codersdk/workspaces.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codersdk/workspaces.go b/codersdk/workspaces.go index 0ebf9cdba70e3..9b713d6dbb8ef 100644 --- a/codersdk/workspaces.go +++ b/codersdk/workspaces.go @@ -135,7 +135,7 @@ func (c *Client) getWorkspace(ctx context.Context, id uuid.UUID, opts ...Request } type WorkspaceBuildsRequest struct { - WorkspaceID uuid.UUID `json:"workspace_id" typescript:"-"` + WorkspaceID uuid.UUID `json:"workspace_id" format:"uuid" typescript:"-"` Pagination Since time.Time `json:"since,omitempty"` } From f9c1218cae5fbc8c2e978c28ac270f7b173b8ae7 Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Thu, 5 Oct 2023 15:28:54 +0000 Subject: [PATCH 6/7] Add format date-time --- codersdk/workspaces.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codersdk/workspaces.go b/codersdk/workspaces.go index 9b713d6dbb8ef..fe858603bff0f 100644 --- a/codersdk/workspaces.go +++ b/codersdk/workspaces.go @@ -137,7 +137,7 @@ func (c *Client) getWorkspace(ctx context.Context, id uuid.UUID, opts ...Request type WorkspaceBuildsRequest struct { WorkspaceID uuid.UUID `json:"workspace_id" format:"uuid" typescript:"-"` Pagination - Since time.Time `json:"since,omitempty"` + Since time.Time `json:"since,omitempty" format:"date-time"` } func (c *Client) WorkspaceBuilds(ctx context.Context, req WorkspaceBuildsRequest) ([]WorkspaceBuild, error) { From 38036fac1d9a61f51b925fc9121a9ac3925fc59e Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Thu, 5 Oct 2023 21:10:22 +0000 Subject: [PATCH 7/7] Only show button if there are more build to be loaded --- site/src/pages/WorkspacePage/BuildsTable.tsx | 33 ++++++++++---------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/site/src/pages/WorkspacePage/BuildsTable.tsx b/site/src/pages/WorkspacePage/BuildsTable.tsx index b6b725eb72290..2cf4dd5524d34 100644 --- a/site/src/pages/WorkspacePage/BuildsTable.tsx +++ b/site/src/pages/WorkspacePage/BuildsTable.tsx @@ -58,22 +58,23 @@ export const BuildsTable: FC> = ({ - } - css={{ - display: "inline-flex", - margin: "auto", - borderRadius: "9999px", - }} - > - Load previous builds - + {hasMoreBuilds && ( + } + css={{ + display: "inline-flex", + margin: "auto", + borderRadius: "9999px", + }} + > + Load previous builds + + )} ); };