Skip to content
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
18 changes: 18 additions & 0 deletions src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -761,6 +761,15 @@ export class Commands {
if (!this.workspace || !this.remoteWorkspaceClient) {
return;
}
const showUpToDate = () =>
vscode.window.showInformationMessage(
"The workspace is already up to date.",
);

if (!this.workspace.outdated) {
showUpToDate();
return;
}
const action = await vscodeProposed.window.showWarningMessage(
"Update Workspace",
{
Expand All @@ -774,6 +783,15 @@ export class Commands {
return;
}

// Re-check; workspace may have been updated or disconnected while the modal was open.
if (!this.workspace) {
return;
}
if (!this.workspace.outdated) {
showUpToDate();
return;
}

this.logger.info(
`Updating workspace ${createWorkspaceIdentifier(this.workspace)}`,
);
Expand Down
10 changes: 7 additions & 3 deletions src/workspace/workspaceMonitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,16 @@ export class WorkspaceMonitor implements vscode.Disposable {
// For logging.
private readonly name: string;

private latestWorkspace: Workspace;

private constructor(
workspace: Workspace,
private readonly client: CoderApi,
private readonly logger: Logger,
private readonly contextManager: ContextManager,
) {
this.name = createWorkspaceIdentifier(workspace);
this.latestWorkspace = workspace;

const statusBarItem = vscode.window.createStatusBarItem(
vscode.StatusBarAlignment.Left,
Expand Down Expand Up @@ -115,6 +118,7 @@ export class WorkspaceMonitor implements vscode.Disposable {

public markInitialSetupComplete(): void {
this.completedInitialSetup = true;
this.maybeNotify(this.latestWorkspace);
}

/**
Expand All @@ -130,6 +134,7 @@ export class WorkspaceMonitor implements vscode.Disposable {
}

private update(workspace: Workspace) {
this.latestWorkspace = workspace;
this.updateContext(workspace);
this.updateStatusBar(workspace);
}
Expand All @@ -139,10 +144,9 @@ export class WorkspaceMonitor implements vscode.Disposable {
if (areNotificationsDisabled(cfg)) {
return;
}
this.maybeNotifyOutdated(workspace, cfg);
this.maybeNotifyAutostop(workspace);
if (this.completedInitialSetup) {
// This instance might be created before the workspace is running
this.maybeNotifyOutdated(workspace, cfg);
this.maybeNotifyDeletion(workspace);
this.maybeNotifyNotRunning(workspace);
}
Expand Down Expand Up @@ -239,7 +243,7 @@ export class WorkspaceMonitor implements vscode.Disposable {
if (action === "Update") {
vscode.commands.executeCommand(
"coder.workspace.update",
workspace,
this.latestWorkspace,
this.client,
);
}
Expand Down
30 changes: 25 additions & 5 deletions test/unit/workspace/workspaceMonitor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,9 +153,10 @@ describe("WorkspaceMonitor", () => {
);
});

it("does not show deletion or not-running notifications before initial setup", async () => {
it("does not show deletion, outdated, or not-running notifications before initial setup", async () => {
const { stream } = await setup();

stream.pushMessage(workspaceEvent({ outdated: true }));
stream.pushMessage(
workspaceEvent({ deleting_at: minutesFromNow(12 * 60) }),
);
Expand All @@ -167,7 +168,8 @@ describe("WorkspaceMonitor", () => {
});

it("fetches template details for outdated notification", async () => {
const { stream } = await setup();
const { monitor, stream } = await setup();
monitor.markInitialSetupComplete();

stream.pushMessage(workspaceEvent({ outdated: true }));

Expand All @@ -179,6 +181,22 @@ describe("WorkspaceMonitor", () => {
});
});

it("fires outdated notification on markInitialSetupComplete", async () => {
const { monitor, stream } = await setup();

stream.pushMessage(workspaceEvent({ outdated: true }));
expect(vscode.window.showInformationMessage).not.toHaveBeenCalled();

monitor.markInitialSetupComplete();

await vi.waitFor(() => {
expect(vscode.window.showInformationMessage).toHaveBeenCalledWith(
expect.stringContaining("template v2"),
"Update",
);
});
});

it("only notifies once per event type", async () => {
const { stream } = await setup();

Expand All @@ -197,11 +215,12 @@ describe("WorkspaceMonitor", () => {

describe("disableUpdateNotifications", () => {
it("suppresses outdated notification but allows other types", async () => {
const { stream, client, config } = await setup();
const { monitor, stream, config } = await setup();
monitor.markInitialSetupComplete();
config.set("coder.disableUpdateNotifications", true);

stream.pushMessage(workspaceEvent({ outdated: true }));
expect(client.getTemplate).not.toHaveBeenCalled();
expect(vscode.window.showInformationMessage).not.toHaveBeenCalled();

stream.pushMessage(
workspaceEvent({
Expand All @@ -217,7 +236,8 @@ describe("WorkspaceMonitor", () => {
});

it("shows outdated notification after re-enabling", async () => {
const { stream, config } = await setup();
const { monitor, stream, config } = await setup();
monitor.markInitialSetupComplete();
config.set("coder.disableUpdateNotifications", true);

stream.pushMessage(workspaceEvent({ outdated: true }));
Expand Down