Skip to content

[dashboard] Adjust Prebuild and Project Configurator pages to spec #5474

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

Merged
merged 8 commits into from
Sep 3, 2021
82 changes: 41 additions & 41 deletions components/dashboard/src/components/PrebuildLogs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,47 +68,47 @@ export default function PrebuildLogs(props: PrebuildLogsProps) {
case "unknown":
break;

// Preparing means that we haven't actually started the workspace instance just yet, but rather
// are still preparing for launch. This means we're building the Docker image for the workspace.
case "preparing":
getGitpodService().server.watchWorkspaceImageBuildLogs(workspace!.id);
break;

// Pending means the workspace does not yet consume resources in the cluster, but rather is looking for
// some space within the cluster. If for example the cluster needs to scale up to accomodate the
// workspace, the workspace will be in Pending state until that happened.
case "pending":
break;

// Creating means the workspace is currently being created. That includes downloading the images required
// to run the workspace over the network. The time spent in this phase varies widely and depends on the current
// network speed, image size and cache states.
case "creating":
break;

// Initializing is the phase in which the workspace is executing the appropriate workspace initializer (e.g. Git
// clone or backup download). After this phase one can expect the workspace to either be Running or Failed.
case "initializing":
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":
break;

// Interrupted is an exceptional state where the container should be running but is temporarily unavailable.
// When in this state, we expect it to become running or stopping anytime soon.
case "interrupted":
break;

// Stopping means that the workspace is currently shutting down. It could go to stopped every moment.
case "stopping":
break;

// Stopped means the workspace ended regularly because it was shut down.
case "stopped":
getGitpodService().server.watchWorkspaceImageBuildLogs(workspace!.id);
break;
// Preparing means that we haven't actually started the workspace instance just yet, but rather
// are still preparing for launch. This means we're building the Docker image for the workspace.
case "preparing":
getGitpodService().server.watchWorkspaceImageBuildLogs(workspace!.id);
break;

// Pending means the workspace does not yet consume resources in the cluster, but rather is looking for
// some space within the cluster. If for example the cluster needs to scale up to accomodate the
// workspace, the workspace will be in Pending state until that happened.
case "pending":
break;

// Creating means the workspace is currently being created. That includes downloading the images required
// to run the workspace over the network. The time spent in this phase varies widely and depends on the current
// network speed, image size and cache states.
case "creating":
break;

// Initializing is the phase in which the workspace is executing the appropriate workspace initializer (e.g. Git
// clone or backup download). After this phase one can expect the workspace to either be Running or Failed.
case "initializing":
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":
break;

// Interrupted is an exceptional state where the container should be running but is temporarily unavailable.
// When in this state, we expect it to become running or stopping anytime soon.
case "interrupted":
break;

// Stopping means that the workspace is currently shutting down. It could go to stopped every moment.
case "stopping":
break;

// Stopped means the workspace ended regularly because it was shut down.
case "stopped":
getGitpodService().server.watchWorkspaceImageBuildLogs(workspace!.id);
break;
}
if (workspaceInstance?.status.conditions.failed) {
setError(new Error(workspaceInstance.status.conditions.failed));
Expand Down
105 changes: 29 additions & 76 deletions components/dashboard/src/projects/ConfigureProject.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,18 @@

import React, { Suspense, useContext, useEffect, useState } from "react";
import { useLocation, useRouteMatch } from "react-router";
import { CreateWorkspaceMode, Project, WorkspaceCreationResult, WorkspaceInstance } from "@gitpod/gitpod-protocol";
import { Project, StartPrebuildResult, WorkspaceInstance } from "@gitpod/gitpod-protocol";
import PrebuildLogs from "../components/PrebuildLogs";
import TabMenuItem from "../components/TabMenuItem";
import { getGitpodService } from "../service/service";
import { getCurrentTeam, TeamsContext } from "../teams/teams-context";
import Header from "../components/Header";
import Spinner from "../icons/Spinner.svg";
import SpinnerDark from "../icons/SpinnerDark.svg";
import StatusDone from "../icons/StatusDone.svg";
import StatusPaused from "../icons/StatusPaused.svg";
import StatusRunning from "../icons/StatusRunning.svg";
import PrebuildLogsEmpty from "../images/prebuild-logs-empty.svg";
import PrebuildLogsEmptyDark from "../images/prebuild-logs-empty-dark.svg";
import { ThemeContext } from "../theme-context";
import { PrebuildInstanceStatus } from "./Prebuilds";

const MonacoEditor = React.lazy(() => import('../components/MonacoEditor'));

Expand Down Expand Up @@ -69,8 +67,8 @@ export default function () {
const [ isEditorDisabled, setIsEditorDisabled ] = useState<boolean>(true);
const [ isDetecting, setIsDetecting ] = useState<boolean>(true);
const [ prebuildWasTriggered, setPrebuildWasTriggered ] = useState<boolean>(false);
const [ workspaceCreationResult, setWorkspaceCreationResult ] = useState<WorkspaceCreationResult | undefined>();
const [ prebuildPhase, setPrebuildPhase ] = useState<string | undefined>();
const [ startPrebuildResult, setStartPrebuildResult ] = useState<StartPrebuildResult | undefined>();
const [ prebuildInstance, setPrebuildInstance ] = useState<WorkspaceInstance | undefined>();
const { isDark } = useContext(ThemeContext);

useEffect(() => {
Expand All @@ -97,12 +95,17 @@ export default function () {
const guessedConfigStringPromise = getGitpodService().server.guessProjectConfiguration(project.id);
const repoConfigString = await getGitpodService().server.fetchProjectRepositoryConfiguration(project.id);
if (repoConfigString) {
// TODO(janx): Link to .gitpod.yml directly instead of just the cloneUrl.
setIsDetecting(false);
setEditorMessage(<EditorMessage type="warning" heading="Configuration already exists in git." message="Run a prebuild or open a new workspace to edit project configuration."/>);
Copy link
Contributor

Choose a reason for hiding this comment

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

issue: This is probably out of the scope of these changes but noticed that it takes a while to redirect to the configuration page once you select a team. Is this expected or needed? Cc @AlexTugarev

Copy link
Contributor Author

Choose a reason for hiding this comment

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

True, @AlexTugarev noticed a UI freeze, and I did too today. Maybe it's indeed MonacoEditor? (It's loaded lazily in the Configurator, in order not to make the entire dashboard slower)

setGitpodYml(repoConfigString);
return;
}
if (project.config && project.config['.gitpod.yml']) {
setIsDetecting(false);
setIsEditorDisabled(false);
setGitpodYml(project.config['.gitpod.yml']);
return;
}
const guessedConfigString = await guessedConfigStringPromise;
setIsDetecting(false);
setIsEditorDisabled(false);
Expand All @@ -111,10 +114,6 @@ export default function () {
setGitpodYml(guessedConfigString);
return;
}
if (project.config && project.config['.gitpod.yml']) {
setGitpodYml(project.config['.gitpod.yml']);
return;
}
setEditorMessage(<EditorMessage type="warning" heading="Project type could not be detected." message="You can edit project configuration below before running a prebuild."/>);
setGitpodYml(TASKS.Other);
})();
Expand All @@ -126,25 +125,24 @@ export default function () {
}
// (event.target as HTMLButtonElement).disabled = true;
setEditorMessage(null);
if (!!workspaceCreationResult) {
setWorkspaceCreationResult(undefined);
if (!!startPrebuildResult) {
setStartPrebuildResult(undefined);
}
try {
setPrebuildWasTriggered(true);
await getGitpodService().server.setProjectConfiguration(project.id, gitpodYml);
const result = await getGitpodService().server.createWorkspace({
contextUrl: `prebuild/${project.cloneUrl}`,
mode: CreateWorkspaceMode.ForceNew,
});
setWorkspaceCreationResult(result);
if (!isEditorDisabled) {
await getGitpodService().server.setProjectConfiguration(project.id, gitpodYml);
}
const result = await getGitpodService().server.triggerPrebuild(project.id, null);
setStartPrebuildResult(result);
} catch (error) {
setPrebuildWasTriggered(false);
setEditorMessage(<EditorMessage type="warning" heading="Could not run prebuild." message={String(error).replace(/Error: Request \w+ failed with message: /, '')}/>);
}
}

const onInstanceUpdate = (instance: WorkspaceInstance) => {
setPrebuildPhase(instance.status.phase);
setPrebuildInstance(instance);
}

useEffect(() => { document.title = 'Configure Project — Gitpod' }, []);
Expand All @@ -166,23 +164,25 @@ export default function () {
</Suspense>
{isDetecting && <div className="absolute h-full w-full bg-gray-100 dark:bg-gray-700 flex items-center justify-center space-x-2">
<img className="h-5 w-5 animate-spin" src={isDark ? SpinnerDark : Spinner} />
<span className="font-semibold text-gray-400">Detecting project type ...</span>
<span className="font-semibold text-gray-400">Detecting project configuration ...</span>
Copy link
Contributor

Choose a reason for hiding this comment

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

praise: Thanks for changing this! New message makes more sense for loading an existing project with a git based configuration.

Copy link
Contributor

Choose a reason for hiding this comment

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

question: This is taking noticeably more time sometimes, like 4-5 seconds or more when there's no git based configuration. Expected?

Copy link
Member

Choose a reason for hiding this comment

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

Yes, I think we should look if we can do that ahead of time.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We could also probably optimize the "config inferrer": It currently makes repository requests one by one (e.g. "does a yarn.lock file exist? Oh... does a cargo.toml file exist? Oh... etc.)

Fetching the entire repo structure (not necessarily file content or history) in one go, and using that to respond to the "config inferrer"'s queries would already be a huge performance boost.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Now tracked as #5527

</div>}
</div>
<div className="flex-1 h-96 rounded-xl overflow-hidden bg-gray-100 dark:bg-gray-700 flex flex-col">
<div className="flex-grow flex">{workspaceCreationResult
? <PrebuildLogs workspaceId={workspaceCreationResult.createdWorkspaceId} onInstanceUpdate={onInstanceUpdate} />
: <div className="flex-grow flex flex-col items-center justify-center">
<div className="flex-grow flex">{startPrebuildResult
? <PrebuildLogs workspaceId={startPrebuildResult.wsid} onInstanceUpdate={onInstanceUpdate} />
: (!prebuildWasTriggered && <div className="flex-grow flex flex-col items-center justify-center">
<img className="w-14" role="presentation" src={isDark ? PrebuildLogsEmptyDark : PrebuildLogsEmpty} />
<h3 className="text-center text-lg text-gray-500 dark:text-gray-50 mt-4">No Recent Prebuild</h3>
<p className="text-center text-base text-gray-500 dark:text-gray-400 mt-2 w-64">Edit the project configuration on the left to get started. <a className="gp-link" href="https://www.gitpod.io/docs/config-gitpod-file/">Learn more</a></p>
</div>
</div>)
}</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">
{prebuildWasTriggered && <PrebuildStatus prebuildPhase={prebuildPhase} isDark={isDark} />}
{prebuildWasTriggered && <PrebuildInstanceStatus prebuildInstance={prebuildInstance} isDark={isDark} />}
<div className="flex-grow" />
<button className="secondary">New Workspace</button>
<button disabled={isEditorDisabled} onClick={buildProject}>Run Prebuild</button>
{(prebuildInstance?.status.phase === "stopped" && !prebuildInstance?.status.conditions.failed)
? <a className="my-auto" href={`/#${project?.cloneUrl}`}><button className="secondary">New Workspace</button></a>
: <button disabled={true} className="secondary">New Workspace</button>}
<button disabled={isDetecting || (prebuildWasTriggered && prebuildInstance?.status.phase !== "stopped")} onClick={buildProject}>Run Prebuild</button>
</div>
</div>
</div>
Expand All @@ -196,51 +196,4 @@ function EditorMessage(props: { heading: string, message: string, type: 'success
</div>;
}

function PrebuildStatus(props: { prebuildPhase?: string, isDark?: boolean }) {
let status = <></>;
let details = <></>;
switch (props.prebuildPhase) {
case undefined: // Fall through
case 'unknown':
status = <div className="flex space-x-1 items-center text-yellow-600">
<img className="h-4 w-4" src={StatusPaused} />
<span>PENDING</span>
</div>;
details = <div className="flex space-x-1 items-center text-gray-400">
<img className="h-4 w-4 animate-spin" src={props.isDark ? SpinnerDark : Spinner} />
<span>Prebuild in progress ...</span>
</div>;
break;
case 'preparing': // Fall through
case 'pending': // Fall through
case 'creating': // Fall through
case 'initializing': // Fall through
case 'running': // Fall through
case 'interrupted':
status = <div className="flex space-x-1 items-center text-blue-600">
<img className="h-4 w-4" src={StatusRunning} />
<span>RUNNING</span>
</div>;
details = <div className="flex space-x-1 items-center text-gray-400">
<img className="h-4 w-4 animate-spin" src={props.isDark ? SpinnerDark : Spinner} />
<span>Prebuild in progress ...</span>
</div>;
break;
case 'stopping': // Fall through
case 'stopped':
status = <div className="flex space-x-1 items-center text-green-600">
<img className="h-4 w-4" src={StatusDone} />
<span>READY</span>
</div>;
// TODO(janx): Calculate actual duration from prebuild instance.
details = <div className="flex space-x-1 items-center text-gray-400">
<img className="h-4 w-4 filter-grayscale" src={StatusRunning} />
<span>00:34</span>
</div>;
break;
}
return <div className="flex flex-col space-y-1 justify-center text-sm font-semibold">
<div>{status}</div>
<div>{details}</div>
</div>;
}

2 changes: 1 addition & 1 deletion components/dashboard/src/projects/NewProject.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ export default function NewProject() {

return (<div className="flex flex-col w-96 mt-24 mx-auto items-center">
<h1>New Project</h1>
<p className="text-gray-500 text-center text-base">Select a git repository on <strong>{provider}</strong>.</p>
<p className="text-gray-500 text-center text-base">Select a git repository on <strong>{provider}</strong>. (<a className="gp-link cursor-pointer" onClick={() => setShowGitProviders(true)}>change</a>)</p>
Copy link
Contributor

Choose a reason for hiding this comment

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

praise: Thanks for making this more visible!

Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion: I think using just the verb change could go unoticed and confuse some users. Following the discussion in #5120 (comment) and the second iteration of the designs for repository selection in #4948, we could use a more verbose message here or below the repositories list. What do you think?

Approach A Approach B Second iteration for repository selection
choose-a choose-b 127672398-8f3407b9-bccb-4b0a-8744-4686caab845c

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Filed as #5524


{!selectedRepo && renderSelectRepository()}

Expand Down
28 changes: 22 additions & 6 deletions components/dashboard/src/projects/Prebuild.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@
*/

import moment from "moment";
import { PrebuildWithStatus } from "@gitpod/gitpod-protocol";
import { PrebuildWithStatus, WorkspaceInstance } from "@gitpod/gitpod-protocol";
import { useContext, useEffect, useState } from "react";
import { useLocation, useRouteMatch } from "react-router";
import Header from "../components/Header";
import { getGitpodService } from "../service/service";
import { TeamsContext, getCurrentTeam } from "../teams/teams-context";
import { prebuildStatusIcon, prebuildStatusLabel } from "./Prebuilds";
import PrebuildLogs from "../components/PrebuildLogs";
import { getGitpodService, gitpodHostUrl } from "../service/service";
import { TeamsContext, getCurrentTeam } from "../teams/teams-context";
import { ThemeContext } from "../theme-context";
import { prebuildStatusIcon, prebuildStatusLabel, PrebuildInstanceStatus } from "./Prebuilds";
import { shortCommitMessage } from "./render-utils";

export default function () {
Expand All @@ -26,6 +27,8 @@ export default function () {
const prebuildId = match?.params?.prebuildId;

const [ prebuild, setPrebuild ] = useState<PrebuildWithStatus | undefined>();
const [ prebuildInstance, setPrebuildInstance ] = useState<WorkspaceInstance | undefined>();
const { isDark } = useContext(ThemeContext);

useEffect(() => {
if (!teams || !projectName || !prebuildId) {
Expand Down Expand Up @@ -78,13 +81,26 @@ export default function () {
</div>)
};

const onInstanceUpdate = (instance: WorkspaceInstance) => {
setPrebuildInstance(instance);
}

useEffect(() => { document.title = 'Prebuild — Gitpod' }, []);

return <>
<Header title={renderTitle()} subtitle={renderSubtitle()} />
<div className="lg:px-28 px-10 mt-8">
<div className="h-96 rounded-xl overflow-hidden bg-gray-100 dark:bg-gray-800 flex flex-col">
<PrebuildLogs workspaceId={prebuild?.info?.buildWorkspaceId}/>
<div className="rounded-xl overflow-hidden bg-gray-100 dark:bg-gray-800 flex flex-col">
Copy link
Contributor

Choose a reason for hiding this comment

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

issue: Could not get any prebuild log output here. Expected? 😭

Copy link
Member

Choose a reason for hiding this comment

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

It took some time the first time I opened it. But I, at last, got logs. I think this is unrelated to this PR.

Copy link
Contributor Author

@jankeromnes jankeromnes Sep 3, 2021

Choose a reason for hiding this comment

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

Yes, sometimes logs don't show up at all ("Error: could not find headless logs for 1234"). Sometimes, they don't show up live, but eventually the entire logs show up after the prebuild is complete.

This morning I also had live logs. It's kind of a shame that this isn't more reliable.

Copy link
Member

Choose a reason for hiding this comment

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

We need to look into it (in a separate PR). Can we merge this? 😁

<div className="h-96 flex">
<PrebuildLogs workspaceId={prebuild?.info?.buildWorkspaceId} onInstanceUpdate={onInstanceUpdate} />
</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">
{prebuildInstance && <PrebuildInstanceStatus prebuildInstance={prebuildInstance} isDark={isDark} />}
<div className="flex-grow" />
{prebuildInstance?.status.phase === "stopped"
? <a className="my-auto" href={gitpodHostUrl.withContext(`${prebuild?.info.changeUrl}`).toString()}><button>New Workspace</button></a>
: <button disabled={true}>New Workspace</button>}
</div>
</div>
</div>
</>;
Expand Down
Loading