Skip to content

Commit 2dbcc67

Browse files
committed
[projects] add message: Prebuilds have been paused
1 parent 9e1dac1 commit 2dbcc67

File tree

6 files changed

+69
-12
lines changed

6 files changed

+69
-12
lines changed

Diff for: components/dashboard/src/projects/Project.tsx

+46-3
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,11 @@ import { getGitpodService, gitpodHostUrl } from "../service/service";
1414
import { TeamsContext, getCurrentTeam } from "../teams/teams-context";
1515
import { prebuildStatusIcon, prebuildStatusLabel } from "./Prebuilds";
1616
import { shortCommitMessage, toRemoteURL } from "./render-utils";
17-
import Spinner from "../icons/Spinner.svg";
17+
import { ReactComponent as Spinner } from "../icons/Spinner.svg";
1818
import NoAccess from "../icons/NoAccess.svg";
1919
import { ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error";
2020
import { openAuthorizeWindow } from "../provider-utils";
21+
import Alert from "../components/Alert";
2122

2223
export default function () {
2324
const location = useLocation();
@@ -34,6 +35,8 @@ export default function () {
3435
const [isLoading, setIsLoading] = useState<boolean>(false);
3536
const [isLoadingBranches, setIsLoadingBranches] = useState<boolean>(false);
3637
const [branches, setBranches] = useState<Project.BranchDetails[]>([]);
38+
const [isConsideredInactive, setIsConsideredInactive] = useState<boolean>(false);
39+
const [isResuming, setIsResuming] = useState<boolean>(false);
3740
const [prebuilds, setPrebuilds] = useState<Map<string, PrebuildWithStatus | undefined>>(new Map());
3841
const [prebuildLoaders] = useState<Set<string>>(new Set());
3942

@@ -91,6 +94,7 @@ export default function () {
9194
// default branch on top of the rest
9295
const branches = details.branches.sort((a, b) => (b.isDefault as any) - (a.isDefault as any)) || [];
9396
setBranches(branches);
97+
setIsConsideredInactive(!!details.isConsideredInactive);
9498
}
9599
} finally {
96100
setIsLoadingBranches(false);
@@ -184,6 +188,20 @@ export default function () {
184188
return date ? dayjs(date).fromNow() : "";
185189
};
186190

191+
const resumePrebuilds = async () => {
192+
if (!project) {
193+
return;
194+
}
195+
try {
196+
const response = await getGitpodService().server.triggerPrebuild(project.id, null);
197+
setIsConsideredInactive(false);
198+
setIsResuming(false);
199+
history.push(`/${!!team ? "t/" + team.slug : "projects"}/${projectSlug}/${response.prebuildId}`);
200+
} catch (error) {
201+
console.error(error);
202+
}
203+
};
204+
187205
return (
188206
<>
189207
<Header
@@ -243,7 +261,7 @@ export default function () {
243261
<div className="flex-1" />
244262
{isLoading && (
245263
<div className="flex justify-center w-1/12">
246-
<img alt="" className="h-4 w-4 animate-spin" src={Spinner} />
264+
<Spinner className="h-4 w-4 animate-spin" />
247265
</div>
248266
)}
249267
</div>
@@ -259,9 +277,34 @@ export default function () {
259277
<span>Prebuild</span>
260278
</ItemField>
261279
</Item>
280+
{isConsideredInactive && (
281+
<Alert
282+
type={"warning"}
283+
onClose={() => {}}
284+
showIcon={true}
285+
className="flex rounded mb-2 w-full"
286+
>
287+
To reduce resource usage, prebuilds are automatically paused when not used for a
288+
workspace after 7 days.{" "}
289+
{isResuming && (
290+
<>
291+
Resuming <Spinner className="h-4 w-4 animate-spin" />
292+
</>
293+
)}
294+
{!isResuming && (
295+
<a
296+
href="javascript:void(0)"
297+
className="gp-link hover:text-gray-600"
298+
onClick={() => resumePrebuilds()}
299+
>
300+
Resume prebuilds
301+
</a>
302+
)}
303+
</Alert>
304+
)}
262305
{isLoadingBranches && (
263306
<div className="flex items-center justify-center space-x-2 text-gray-400 text-sm pt-16 pb-40">
264-
<img className="h-4 w-4 animate-spin" src={Spinner} />
307+
<Spinner className="h-4 w-4 animate-spin" />
265308
<span>Fetching repository branches...</span>
266309
</div>
267310
)}

Diff for: components/gitpod-protocol/src/teams-projects-protocol.ts

+1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ export namespace Project {
4545

4646
export interface Overview {
4747
branches: BranchDetails[];
48+
isConsideredInactive?: boolean;
4849
}
4950

5051
export namespace Overview {

Diff for: components/server/ee/src/prebuilds/prebuild-manager.ts

+1-8
Original file line numberDiff line numberDiff line change
@@ -394,14 +394,7 @@ export class PrebuildManager {
394394
}
395395

396396
private async shouldSkipInactiveProject(project: Project): Promise<boolean> {
397-
const usage = await this.projectService.getProjectUsage(project.id);
398-
if (!usage?.lastWorkspaceStart) {
399-
return false;
400-
}
401-
const now = Date.now();
402-
const lastUse = new Date(usage.lastWorkspaceStart).getTime();
403-
const inactiveProjectTime = 1000 * 60 * 60 * 24 * 7 * 1; // 1 week
404-
return now - lastUse > inactiveProjectTime;
397+
return await this.projectService.isProjectConsideredInactive(project.id);
405398
}
406399

407400
private async shouldSkipInactiveRepository(ctx: TraceContext, cloneURL: string): Promise<boolean> {

Diff for: components/server/ee/src/workspace/gitpod-server-impl.ts

+5
Original file line numberDiff line numberDiff line change
@@ -2620,6 +2620,11 @@ export class GitpodServerEEImpl extends GitpodServerImpl {
26202620

26212621
const context = (await this.contextParser.handle(ctx, user, contextURL)) as CommitContext;
26222622

2623+
// HACK: treat manual triggered prebuild as a reset for the inactivity state
2624+
await this.projectDB.updateProjectUsage(project.id, {
2625+
lastWorkspaceStart: new Date().toISOString(),
2626+
});
2627+
26232628
const prebuild = await this.prebuildManager.startPrebuild(ctx, {
26242629
context,
26252630
user,

Diff for: components/server/src/projects/projects-service.ts

+11
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,17 @@ export class ProjectsService {
279279
return this.projectDB.getProjectUsage(projectId);
280280
}
281281

282+
async isProjectConsideredInactive(projectId: string): Promise<boolean> {
283+
const usage = await this.getProjectUsage(projectId);
284+
if (!usage?.lastWorkspaceStart) {
285+
return false;
286+
}
287+
const now = Date.now();
288+
const lastUse = new Date(usage.lastWorkspaceStart).getTime();
289+
const inactiveProjectTime = 1000 * 60 * 60 * 24 * 7 * 1; // 1 week
290+
return now - lastUse > inactiveProjectTime;
291+
}
292+
282293
async getPrebuildEvents(cloneUrl: string): Promise<PrebuildEvent[]> {
283294
const events = await this.webhookEventDB.findByCloneUrl(cloneUrl, 100);
284295
return events.map((we) => ({

Diff for: components/server/src/workspace/gitpod-server-impl.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -2345,7 +2345,11 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
23452345
}
23462346
await this.guardProjectOperation(user, projectId, "get");
23472347
try {
2348-
return await this.projectsService.getProjectOverviewCached(user, project);
2348+
const result = await this.projectsService.getProjectOverviewCached(user, project);
2349+
if (result) {
2350+
result.isConsideredInactive = await this.projectsService.isProjectConsideredInactive(project.id);
2351+
}
2352+
return result;
23492353
} catch (error) {
23502354
if (UnauthorizedError.is(error)) {
23512355
throw new ResponseError(ErrorCodes.NOT_AUTHENTICATED, "Unauthorized", error.data);

0 commit comments

Comments
 (0)