Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[prebuilds] prebuild detail view in create workspace #10696

Merged
merged 3 commits into from
Jun 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 28 additions & 4 deletions components/dashboard/src/components/PrebuildLogs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,25 @@ import {
WorkspaceImageBuild,
HEADLESS_LOG_STREAM_STATUS_CODE_REGEX,
Disposable,
PrebuildWithStatus,
} from "@gitpod/gitpod-protocol";
import { getGitpodService } from "../service/service";
import { PrebuildStatus } from "../projects/Prebuilds";

const WorkspaceLogs = React.lazy(() => import("./WorkspaceLogs"));

export interface PrebuildLogsProps {
workspaceId?: string;
workspaceId: string | undefined;
onIgnorePrebuild?: () => void;
children?: React.ReactNode;
}

export default function PrebuildLogs(props: PrebuildLogsProps) {
const [workspace, setWorkspace] = useState<Workspace | undefined>();
const [workspaceInstance, setWorkspaceInstance] = useState<WorkspaceInstance | undefined>();
const [error, setError] = useState<Error | undefined>();
const [logsEmitter] = useState(new EventEmitter());
const [prebuild, setPrebuild] = useState<PrebuildWithStatus | undefined>();

useEffect(() => {
const disposables = new DisposableCollection();
Expand All @@ -37,10 +42,15 @@ export default function PrebuildLogs(props: PrebuildLogsProps) {
}
try {
const info = await getGitpodService().server.getWorkspace(props.workspaceId);
const pbws = await getGitpodService().server.findPrebuildByWorkspaceID(props.workspaceId);
if (info.latestInstance) {
setWorkspace(info.workspace);
setWorkspaceInstance(info.latestInstance);
}
if (pbws) {
const foundPrebuild = await getGitpodService().server.getPrebuild(pbws.id);
setPrebuild(foundPrebuild);
}
disposables.push(
getGitpodService().registerClient({
onInstanceUpdate: (instance) => {
Expand All @@ -57,6 +67,11 @@ export default function PrebuildLogs(props: PrebuildLogsProps) {
}
logsEmitter.emit("logs", content.text);
},
laushinka marked this conversation as resolved.
Show resolved Hide resolved
onPrebuildUpdate(update: PrebuildWithStatus) {
if (update.info && update.info.buildWorkspaceId === props.workspaceId) {
setPrebuild(update);
}
},
}),
);
if (info.latestInstance) {
Expand Down Expand Up @@ -91,9 +106,18 @@ export default function PrebuildLogs(props: PrebuildLogsProps) {
}, [props.workspaceId, workspaceInstance?.status.phase]);

return (
<Suspense fallback={<div />}>
<WorkspaceLogs classes="h-full w-full" logsEmitter={logsEmitter} errorMessage={error?.message} />
</Suspense>
<div className="rounded-xl overflow-hidden bg-gray-100 dark:bg-gray-800 flex flex-col">
<div className="h-96 flex">
<Suspense fallback={<div />}>
<WorkspaceLogs classes="h-full w-full" logsEmitter={logsEmitter} errorMessage={error?.message} />
</Suspense>
</div>
<div className="h-20 px-6 bg-gray-50 dark:bg-gray-800 border-t border-gray-200 dark:border-gray-600 flex space-x-2">
{prebuild && <PrebuildStatus prebuild={prebuild} />}
<div className="flex-grow" />
{props.children}
</div>
</div>
);
}

Expand Down
76 changes: 34 additions & 42 deletions components/dashboard/src/projects/Prebuild.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import PrebuildLogs from "../components/PrebuildLogs";
import Spinner from "../icons/Spinner.svg";
import { getGitpodService, gitpodHostUrl } from "../service/service";
import { TeamsContext, getCurrentTeam } from "../teams/teams-context";
import { PrebuildStatus } from "./Prebuilds";
import { shortCommitMessage } from "./render-utils";

export default function () {
Expand Down Expand Up @@ -156,47 +155,40 @@ export default function () {
<>
<Header title={renderTitle()} subtitle={renderSubtitle()} />
<div className="app-container mt-8">
<div className="rounded-xl overflow-hidden bg-gray-100 dark:bg-gray-800 flex flex-col">
<div className="h-96 flex">
<PrebuildLogs workspaceId={prebuild?.info?.buildWorkspaceId} />
</div>
<div className="h-20 px-6 bg-gray-50 dark:bg-gray-800 border-t border-gray-200 dark:border-gray-600 flex space-x-2">
{prebuild && <PrebuildStatus prebuild={prebuild} />}
<div className="flex-grow" />
{["aborted", "timeout", "failed"].includes(prebuild?.status || "") || !!prebuild?.error ? (
<button
className="flex items-center space-x-2"
disabled={isRerunningPrebuild}
onClick={rerunPrebuild}
>
{isRerunningPrebuild && (
<img className="h-4 w-4 animate-spin filter brightness-150" src={Spinner} />
)}
<span>Rerun Prebuild ({prebuild?.info.branch})</span>
</button>
) : ["building", "queued"].includes(prebuild?.status || "") ? (
<button
className="danger flex items-center space-x-2"
disabled={isCancellingPrebuild}
onClick={cancelPrebuild}
>
{isCancellingPrebuild && (
<img className="h-4 w-4 animate-spin filter brightness-150" src={Spinner} />
)}
<span>Cancel Prebuild</span>
</button>
) : prebuild?.status === "available" ? (
<a
className="my-auto"
href={gitpodHostUrl.withContext(`${prebuild?.info.changeUrl}`).toString()}
>
<button>New Workspace ({prebuild?.info.branch})</button>
</a>
) : (
<button disabled={true}>New Workspace ({prebuild?.info.branch})</button>
)}
</div>
</div>
<PrebuildLogs workspaceId={prebuild?.info?.buildWorkspaceId}>
{["aborted", "timeout", "failed"].includes(prebuild?.status || "") || !!prebuild?.error ? (
<button
className="flex items-center space-x-2"
disabled={isRerunningPrebuild}
onClick={rerunPrebuild}
>
{isRerunningPrebuild && (
<img className="h-4 w-4 animate-spin filter brightness-150" src={Spinner} />
)}
<span>Rerun Prebuild ({prebuild?.info.branch})</span>
</button>
) : ["building", "queued"].includes(prebuild?.status || "") ? (
<button
className="danger flex items-center space-x-2"
disabled={isCancellingPrebuild}
onClick={cancelPrebuild}
>
{isCancellingPrebuild && (
<img className="h-4 w-4 animate-spin filter brightness-150" src={Spinner} />
)}
<span>Cancel Prebuild</span>
</button>
) : prebuild?.status === "available" ? (
<a
className="my-auto"
href={gitpodHostUrl.withContext(`${prebuild?.info.changeUrl}`).toString()}
>
<button>New Workspace ({prebuild?.info.branch})</button>
</a>
) : (
<button disabled={true}>New Workspace ({prebuild?.info.branch})</button>
)}
</PrebuildLogs>
</div>
</>
);
Expand Down
25 changes: 10 additions & 15 deletions components/dashboard/src/start/CreateWorkspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/

import EventEmitter from "events";
import React, { useEffect, Suspense, useContext, useState } from "react";
import React, { useEffect, useContext, useState } from "react";
import {
CreateWorkspaceMode,
WorkspaceCreationResult,
Expand All @@ -21,13 +21,11 @@ import StartWorkspace, { parseProps } from "./StartWorkspace";
import { openAuthorizeWindow } from "../provider-utils";
import { SelectAccountPayload } from "@gitpod/gitpod-protocol/lib/auth";
import { SelectAccountModal } from "../settings/SelectAccountModal";
import { watchHeadlessLogs } from "../components/PrebuildLogs";
import PrebuildLogs, { watchHeadlessLogs } from "../components/PrebuildLogs";
import CodeText from "../components/CodeText";
import FeedbackComponent from "../feedback-form/FeedbackComponent";
import { isGitpodIo } from "../utils";

const WorkspaceLogs = React.lazy(() => import("../components/WorkspaceLogs"));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if PrebuildLogs should also be lazy-loaded here?

E.g. no need to load all of xTermjs if you're creating a workspace that doesn't show any logs.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, good idea! But my workspace has timed out, so: let's do as a follow-up 😉


export interface CreateWorkspaceProps {
contextUrl: string;
}
Expand Down Expand Up @@ -490,17 +488,14 @@ function RunningPrebuildView(props: RunningPrebuildViewProps) {

return (
<StartPage title="Prebuild in Progress">
<Suspense fallback={<div />}>
<WorkspaceLogs logsEmitter={logsEmitter} />
</Suspense>
<button
className="mt-6 secondary"
onClick={() => {
props.onIgnorePrebuild();
}}
>
Don't Wait for Prebuild
</button>
{/* TODO(gpl) Copied around in Start-/CreateWorkspace. This should properly go somewhere central. */}
<div className="mt-6 w-11/12 lg:w-3/5 overflow-hidden">
<PrebuildLogs workspaceId={props.runningPrebuild.workspaceID} onIgnorePrebuild={props.onIgnorePrebuild}>
<button className="secondary" onClick={() => props.onIgnorePrebuild && props.onIgnorePrebuild()}>
Skip Prebuild
</button>
</PrebuildLogs>
</div>
</StartPage>
);
}
54 changes: 20 additions & 34 deletions components/dashboard/src/start/StartWorkspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,12 @@ import { v4 } from "uuid";
import Arrow from "../components/Arrow";
import ContextMenu from "../components/ContextMenu";
import PendingChangesDropdown from "../components/PendingChangesDropdown";
import { watchHeadlessLogs } from "../components/PrebuildLogs";
import PrebuildLogs from "../components/PrebuildLogs";
import { getGitpodService, gitpodHostUrl } from "../service/service";
import { StartPage, StartPhase, StartWorkspaceError } from "./StartPage";
import ConnectToSSHModal from "../workspaces/ConnectToSSHModal";
import Alert from "../components/Alert";

const sessionId = v4();

const WorkspaceLogs = React.lazy(() => import("../components/WorkspaceLogs"));
Expand Down Expand Up @@ -391,8 +392,8 @@ export default class StartWorkspace extends React.Component<StartWorkspaceProps,

render() {
const { error } = this.state;
const isHeadless = this.state.workspace?.type !== "regular";
const isPrebuilt = WithPrebuild.is(this.state.workspace?.context);
const isPrebuild = this.state.workspace?.type === "prebuild";
const withPrebuild = WithPrebuild.is(this.state.workspace?.context);
laushinka marked this conversation as resolved.
Show resolved Hide resolved
let phase: StartPhase | undefined = StartPhase.Preparing;
let title = undefined;
let statusMessage = !!error ? undefined : <p className="text-base text-gray-400">Preparing workspace …</p>;
Expand Down Expand Up @@ -433,16 +434,21 @@ export default class StartWorkspace extends React.Component<StartWorkspaceProps,
phase = StartPhase.Starting;
statusMessage = (
<p className="text-base text-gray-400">
{isPrebuilt ? "Loading prebuild …" : "Initializing content …"}
{withPrebuild ? "Loading prebuild …" : "Initializing content …"}
</p>
);
break;

// Running means the workspace is able to actively perform work, either by serving a user through Theia,
// or as a headless workspace.
case "running":
if (isHeadless) {
return <HeadlessWorkspaceView instanceId={this.state.workspaceInstance.id} />;
if (isPrebuild) {
return (
<div className="mt-6 w-11/12 lg:w-3/5 overflow-hidden">
{/* TODO(gpl) These classes are copied around in Start-/CreateWorkspace. This should properly go somewhere central. */}
<PrebuildLogs workspaceId={this.props.workspaceId} />
</div>
);
}
if (!this.state.desktopIde) {
phase = StartPhase.Running;
Expand Down Expand Up @@ -568,8 +574,13 @@ export default class StartWorkspace extends React.Component<StartWorkspaceProps,

// Stopping means that the workspace is currently shutting down. It could go to stopped every moment.
case "stopping":
if (isHeadless) {
return <HeadlessWorkspaceView instanceId={this.state.workspaceInstance.id} />;
if (isPrebuild) {
return (
<div className="mt-6 w-11/12 lg:w-3/5 overflow-hidden">
{/* TODO(gpl) These classes are copied around in Start-/CreateWorkspace. This should properly go somewhere central. */}
<PrebuildLogs workspaceId={this.props.workspaceId} />
</div>
);
}
phase = StartPhase.Stopping;
statusMessage = (
Expand Down Expand Up @@ -613,7 +624,7 @@ export default class StartWorkspace extends React.Component<StartWorkspaceProps,
/>
);
}
if (!isHeadless && this.state.workspaceInstance.status.conditions.timeout) {
if (!isPrebuild && this.state.workspaceInstance.status.conditions.timeout) {
title = "Timed Out";
}
statusMessage = (
Expand Down Expand Up @@ -719,28 +730,3 @@ function ImageBuildView(props: ImageBuildViewProps) {
</StartPage>
);
}

function HeadlessWorkspaceView(props: { instanceId: string }) {
const [logsEmitter] = useState(new EventEmitter());

useEffect(() => {
const disposables = watchHeadlessLogs(
props.instanceId,
(chunk) => logsEmitter.emit("logs", chunk),
async () => {
return false;
},
);
return function cleanup() {
disposables.dispose();
};
}, []);

return (
<StartPage title="Prebuild in Progress">
<Suspense fallback={<div />}>
<WorkspaceLogs logsEmitter={logsEmitter} />
</Suspense>
</StartPage>
);
}
3 changes: 3 additions & 0 deletions components/gitpod-protocol/src/gitpod-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
GuessGitTokenScopesParams,
GuessedGitTokenScopes,
ProjectEnvVar,
PrebuiltWorkspace,
} from "./protocol";
import {
Team,
Expand Down Expand Up @@ -172,6 +173,8 @@ export interface GitpodServer extends JsonRpcServer<GitpodClient>, AdminServer,
getUserProjects(): Promise<Project[]>;
getProjectOverview(projectId: string): Promise<Project.Overview | undefined>;
findPrebuilds(params: FindPrebuildsParams): Promise<PrebuildWithStatus[]>;
findPrebuildByWorkspaceID(workspaceId: string): Promise<PrebuiltWorkspace | undefined>;
getPrebuild(prebuildId: string): Promise<PrebuildWithStatus | undefined>;
triggerPrebuild(projectId: string, branchName: string | null): Promise<StartPrebuildResult>;
cancelPrebuild(projectId: string, prebuildId: string): Promise<void>;
fetchProjectRepositoryConfiguration(projectId: string): Promise<string | undefined>;
Expand Down
2 changes: 2 additions & 0 deletions components/server/src/auth/rate-limiter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ function getConfig(config: RateLimiterConfig): RateLimiterConfig {
getUserProjects: { group: "default", points: 1 },
deleteProject: { group: "default", points: 1 },
findPrebuilds: { group: "default", points: 1 },
getPrebuild: { group: "default", points: 1 },
findPrebuildByWorkspaceID: { group: "default", points: 1 },
getProjectOverview: { group: "default", points: 1 },
triggerPrebuild: { group: "default", points: 1 },
cancelPrebuild: { group: "default", points: 1 },
Expand Down
Loading