From f8b5031c52f3d91089355cc881de926a1b73f516 Mon Sep 17 00:00:00 2001 From: Kira Pilot Date: Tue, 20 Jun 2023 20:28:26 -0400 Subject: [PATCH 01/17] feat: add VS code notifications for autostop --- src/remote.ts | 40 +++++++++++++++++++++++++++++++++++++++ src/storage.ts | 2 ++ src/workspacesProvider.ts | 11 ++++++++--- 3 files changed, 50 insertions(+), 3 deletions(-) diff --git a/src/remote.ts b/src/remote.ts index 5d2f1134..aa647d0c 100644 --- a/src/remote.ts +++ b/src/remote.ts @@ -3,6 +3,7 @@ import { getBuildInfo, getTemplate, getWorkspace, + getWorkspaces, getWorkspaceBuildLogs, getWorkspaceByOwnerAndName, startWorkspace, @@ -126,6 +127,44 @@ export class Remote { this.registerLabelFormatter(remoteAuthority, this.storage.workspace.owner_name, this.storage.workspace.name), ) + const notifyWorkspacesEligibleForAutostop = () => { + const eligibleWorkspaces = this.storage.ownedWorkspaces?.filter((workspace: Workspace) => { + if (workspace.latest_build.transition !== "start" || !workspace.latest_build.deadline) { + return false + } + + const hoursMilli = 1000 * 60 * 60 + // return workspaces with a deadline that is in 1 hr or less + return Math.abs(new Date().getTime() - new Date(workspace.latest_build.deadline).getTime()) <= hoursMilli + }) + + eligibleWorkspaces?.forEach((workspace: Workspace) => { + if (this.storage.workspaceIdsEligibleForAutostop?.includes(workspace.id)) { + // don't message the user; we've already messaged + return + } + // we display individual notifications for each workspace as VS Code + // intentionally strips new lines from the message text + // https://github.com/Microsoft/vscode/issues/48900 + this.vscodeProposed.window.showInformationMessage(`${workspace.name} is scheduled to shut down in 1 hour.`) + this.storage.workspaceIdsEligibleForAutostop?.push(workspace.id) + }) + } + + let errorCount = 0 + const fetchWorkspacesInterval = setInterval(async () => { + try { + const workspacesResult = await getWorkspaces({ q: "owner:me" }) + this.storage.ownedWorkspaces = workspacesResult.workspaces + notifyWorkspacesEligibleForAutostop() + } catch (error) { + if (errorCount === 3) { + clearInterval(fetchWorkspacesInterval) + } + errorCount++ + } + }, 1000 * 5) + let buildComplete: undefined | (() => void) if (this.storage.workspace.latest_build.status === "stopped") { this.vscodeProposed.window.withProgress( @@ -427,6 +466,7 @@ export class Remote { return { dispose: () => { eventSource.close() + clearInterval(fetchWorkspacesInterval) disposables.forEach((d) => d.dispose()) }, } diff --git a/src/storage.ts b/src/storage.ts index 588f3408..d1aab723 100644 --- a/src/storage.ts +++ b/src/storage.ts @@ -14,6 +14,8 @@ import * as vscode from "vscode" export class Storage { public workspace?: Workspace + public ownedWorkspaces?: Workspace[] = [] + public workspaceIdsEligibleForAutostop?: string[] = [] constructor( private readonly output: vscode.OutputChannel, diff --git a/src/workspacesProvider.ts b/src/workspacesProvider.ts index 115d3f80..5353298f 100644 --- a/src/workspacesProvider.ts +++ b/src/workspacesProvider.ts @@ -20,15 +20,20 @@ export class WorkspaceProvider implements vscode.TreeDataProvider { const workspacesTreeItem: WorkspaceTreeItem[] = [] workspaces.workspaces.forEach((workspace) => { - const showMetadata = this.getWorkspacesQuery === WorkspaceQuery.Mine - if (showMetadata) { + const isOwnedWorkspace = this.getWorkspacesQuery === WorkspaceQuery.Mine + if (isOwnedWorkspace) { + // update ownedWorkspaces list in storage such that we can display to the user + // notifications about their own workspaces + this.storage.ownedWorkspaces?.push(workspace) + + // Show metadata for workspaces owned by the user const agents = extractAgents(workspace) agents.forEach((agent) => this.monitorMetadata(agent.id)) // monitor metadata for all agents } const treeItem = new WorkspaceTreeItem( workspace, this.getWorkspacesQuery === WorkspaceQuery.All, - showMetadata, + isOwnedWorkspace, ) workspacesTreeItem.push(treeItem) }) From a810f989f7bbfab304c803f2cd355610aa86a56e Mon Sep 17 00:00:00 2001 From: Kira Pilot Date: Wed, 21 Jun 2023 16:38:42 -0400 Subject: [PATCH 02/17] feat: add VS code notifications for workspace actions --- src/WorkspaceAction.ts | 122 ++++++++++++++++++++++++++++++++++++++ src/remote.ts | 45 ++------------ src/storage.ts | 2 - src/workspacesProvider.ts | 11 +--- 4 files changed, 130 insertions(+), 50 deletions(-) create mode 100644 src/WorkspaceAction.ts diff --git a/src/WorkspaceAction.ts b/src/WorkspaceAction.ts new file mode 100644 index 00000000..25e0232d --- /dev/null +++ b/src/WorkspaceAction.ts @@ -0,0 +1,122 @@ +import { Workspace, WorkspacesResponse } from "coder/site/src/api/typesGenerated" +import { getWorkspaces } from "coder/site/src/api/api" +import * as vscode from "vscode" + +interface NotifiedWorkspace { + workspace: Workspace + wasNotified: boolean +} + +export class WorkspaceAction { + #fetchWorkspacesInterval?: ReturnType + + #ownedWorkspaces: Workspace[] = [] + #workspacesApproachingAutostop: NotifiedWorkspace[] = [] + #workspacesApproachingDeletion: NotifiedWorkspace[] = [] + + private constructor(private readonly vscodeProposed: typeof vscode, ownedWorkspaces: Workspace[]) { + this.#ownedWorkspaces = ownedWorkspaces + + // seed initial lists + this.seedNotificationLists() + + // set up polling so we get current workspaces data + this.pollGetWorkspaces() + } + + static async init(vscodeProposed: typeof vscode) { + // fetch all workspaces owned by the user and set initial public class fields + let ownedWorkspacesResponse: WorkspacesResponse + try { + ownedWorkspacesResponse = await getWorkspaces({ q: "owner:me" }) + } catch (error) { + ownedWorkspacesResponse = { workspaces: [], count: 0 } + } + + return new WorkspaceAction(vscodeProposed, ownedWorkspacesResponse.workspaces) + } + + seedNotificationLists() { + this.#workspacesApproachingAutostop = this.#ownedWorkspaces + .filter(this.filterImpendingAutostopWorkspaces) + .map((workspace: Workspace) => { + const wasNotified = + this.#workspacesApproachingAutostop.find((wn) => wn.workspace.id === workspace.id)?.wasNotified ?? false + return { workspace, wasNotified } + }) + + // NOTE: this feature is currently in-progess; however, we're including scaffolding for it + // to exemplify the class pattern used for Workspace Actions + this.#workspacesApproachingDeletion = [] + } + + filterImpendingAutostopWorkspaces(workspace: Workspace) { + if (workspace.latest_build.transition !== "start" || !workspace.latest_build.deadline) { + return false + } + + const hoursMilli = 1000 * 60 * 60 + // return workspaces with a deadline that is in 1 hr or less + return Math.abs(new Date().getTime() - new Date(workspace.latest_build.deadline).getTime()) <= hoursMilli + } + + async pollGetWorkspaces() { + let errorCount = 0 + this.#fetchWorkspacesInterval = setInterval(async () => { + try { + const workspacesResult = await getWorkspaces({ q: "owner:me" }) + this.#ownedWorkspaces = workspacesResult.workspaces + this.seedNotificationLists() + this.notifyAll() + } catch (error) { + if (errorCount === 3) { + clearInterval(this.#fetchWorkspacesInterval) + } + errorCount++ + } + }, 1000 * 5) + } + + notifyAll() { + this.notifyImpendingAutostop() + this.notifyImpendingDeletion() + } + + notifyImpendingAutostop() { + this.#workspacesApproachingAutostop?.forEach((notifiedWorkspace: NotifiedWorkspace) => { + if (notifiedWorkspace.wasNotified) { + // don't message the user; we've already messaged + return + } + + // we display individual notifications for each workspace as VS Code + // intentionally strips new lines from the message text + // https://github.com/Microsoft/vscode/issues/48900 + this.vscodeProposed.window.showInformationMessage( + `${notifiedWorkspace.workspace.name} is scheduled to shut down in 1 hour.`, + ) + notifiedWorkspace.wasNotified = true + }) + } + + notifyImpendingDeletion() { + this.#workspacesApproachingDeletion?.forEach((notifiedWorkspace: NotifiedWorkspace) => { + if (notifiedWorkspace.wasNotified) { + // don't message the user; we've already messaged + return + } + + // we display individual notifications for each workspace as VS Code + // intentionally strips new lines from the message text + // https://github.com/Microsoft/vscode/issues/48900 + this.vscodeProposed.window.showInformationMessage( + `${notifiedWorkspace.workspace.name} is scheduled for deletion.`, + ) + notifiedWorkspace.wasNotified = true + }) + } + + cleanupWorkspaceActions() { + clearInterval(this.#fetchWorkspacesInterval) + } +} diff --git a/src/remote.ts b/src/remote.ts index aa647d0c..1d8f2976 100644 --- a/src/remote.ts +++ b/src/remote.ts @@ -3,7 +3,6 @@ import { getBuildInfo, getTemplate, getWorkspace, - getWorkspaces, getWorkspaceBuildLogs, getWorkspaceByOwnerAndName, startWorkspace, @@ -23,6 +22,7 @@ import * as ws from "ws" import { SSHConfig, SSHValues, defaultSSHConfigResponse, mergeSSHConfigValues } from "./sshConfig" import { computeSSHProperties, sshSupportsSetEnv } from "./sshSupport" import { Storage } from "./storage" +import { WorkspaceAction } from "./WorkspaceAction" export class Remote { // Prefix is a magic string that is prepended to SSH hosts to indicate that @@ -127,43 +127,8 @@ export class Remote { this.registerLabelFormatter(remoteAuthority, this.storage.workspace.owner_name, this.storage.workspace.name), ) - const notifyWorkspacesEligibleForAutostop = () => { - const eligibleWorkspaces = this.storage.ownedWorkspaces?.filter((workspace: Workspace) => { - if (workspace.latest_build.transition !== "start" || !workspace.latest_build.deadline) { - return false - } - - const hoursMilli = 1000 * 60 * 60 - // return workspaces with a deadline that is in 1 hr or less - return Math.abs(new Date().getTime() - new Date(workspace.latest_build.deadline).getTime()) <= hoursMilli - }) - - eligibleWorkspaces?.forEach((workspace: Workspace) => { - if (this.storage.workspaceIdsEligibleForAutostop?.includes(workspace.id)) { - // don't message the user; we've already messaged - return - } - // we display individual notifications for each workspace as VS Code - // intentionally strips new lines from the message text - // https://github.com/Microsoft/vscode/issues/48900 - this.vscodeProposed.window.showInformationMessage(`${workspace.name} is scheduled to shut down in 1 hour.`) - this.storage.workspaceIdsEligibleForAutostop?.push(workspace.id) - }) - } - - let errorCount = 0 - const fetchWorkspacesInterval = setInterval(async () => { - try { - const workspacesResult = await getWorkspaces({ q: "owner:me" }) - this.storage.ownedWorkspaces = workspacesResult.workspaces - notifyWorkspacesEligibleForAutostop() - } catch (error) { - if (errorCount === 3) { - clearInterval(fetchWorkspacesInterval) - } - errorCount++ - } - }, 1000 * 5) + // Initialize any WorkspaceAction notifications (auto-off, upcoming deletion) + const Action = await WorkspaceAction.init(this.vscodeProposed) let buildComplete: undefined | (() => void) if (this.storage.workspace.latest_build.status === "stopped") { @@ -466,7 +431,7 @@ export class Remote { return { dispose: () => { eventSource.close() - clearInterval(fetchWorkspacesInterval) + Action.cleanupWorkspaceActions() disposables.forEach((d) => d.dispose()) }, } @@ -533,7 +498,7 @@ export class Remote { await sshConfig.load() let binaryPath: string | undefined - if (this.mode === vscode.ExtensionMode.Production) { + if (this.mode !== vscode.ExtensionMode.Production) { binaryPath = await this.storage.fetchBinary() } else { binaryPath = path.join(os.tmpdir(), "coder") diff --git a/src/storage.ts b/src/storage.ts index d1aab723..588f3408 100644 --- a/src/storage.ts +++ b/src/storage.ts @@ -14,8 +14,6 @@ import * as vscode from "vscode" export class Storage { public workspace?: Workspace - public ownedWorkspaces?: Workspace[] = [] - public workspaceIdsEligibleForAutostop?: string[] = [] constructor( private readonly output: vscode.OutputChannel, diff --git a/src/workspacesProvider.ts b/src/workspacesProvider.ts index 5353298f..115d3f80 100644 --- a/src/workspacesProvider.ts +++ b/src/workspacesProvider.ts @@ -20,20 +20,15 @@ export class WorkspaceProvider implements vscode.TreeDataProvider { const workspacesTreeItem: WorkspaceTreeItem[] = [] workspaces.workspaces.forEach((workspace) => { - const isOwnedWorkspace = this.getWorkspacesQuery === WorkspaceQuery.Mine - if (isOwnedWorkspace) { - // update ownedWorkspaces list in storage such that we can display to the user - // notifications about their own workspaces - this.storage.ownedWorkspaces?.push(workspace) - - // Show metadata for workspaces owned by the user + const showMetadata = this.getWorkspacesQuery === WorkspaceQuery.Mine + if (showMetadata) { const agents = extractAgents(workspace) agents.forEach((agent) => this.monitorMetadata(agent.id)) // monitor metadata for all agents } const treeItem = new WorkspaceTreeItem( workspace, this.getWorkspacesQuery === WorkspaceQuery.All, - isOwnedWorkspace, + showMetadata, ) workspacesTreeItem.push(treeItem) }) From 196ce2ee60b3dd5607ad7131a89c69d4a828d68d Mon Sep 17 00:00:00 2001 From: Kira Pilot Date: Wed, 21 Jun 2023 16:43:18 -0400 Subject: [PATCH 03/17] resetting boolean invert --- src/remote.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/remote.ts b/src/remote.ts index 1d8f2976..3b633a7b 100644 --- a/src/remote.ts +++ b/src/remote.ts @@ -498,7 +498,7 @@ export class Remote { await sshConfig.load() let binaryPath: string | undefined - if (this.mode !== vscode.ExtensionMode.Production) { + if (this.mode === vscode.ExtensionMode.Production) { binaryPath = await this.storage.fetchBinary() } else { binaryPath = path.join(os.tmpdir(), "coder") From e9f3ad080735cdc796e4a71e2935d5ddf24f1da9 Mon Sep 17 00:00:00 2001 From: Kira Pilot Date: Wed, 21 Jun 2023 16:44:23 -0400 Subject: [PATCH 04/17] fix lint --- src/WorkspaceAction.ts | 2 +- src/remote.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/WorkspaceAction.ts b/src/WorkspaceAction.ts index 25e0232d..b395c500 100644 --- a/src/WorkspaceAction.ts +++ b/src/WorkspaceAction.ts @@ -1,5 +1,5 @@ -import { Workspace, WorkspacesResponse } from "coder/site/src/api/typesGenerated" import { getWorkspaces } from "coder/site/src/api/api" +import { Workspace, WorkspacesResponse } from "coder/site/src/api/typesGenerated" import * as vscode from "vscode" interface NotifiedWorkspace { diff --git a/src/remote.ts b/src/remote.ts index 3b633a7b..4a6975b8 100644 --- a/src/remote.ts +++ b/src/remote.ts @@ -19,10 +19,10 @@ import prettyBytes from "pretty-bytes" import * as semver from "semver" import * as vscode from "vscode" import * as ws from "ws" +import { WorkspaceAction } from "./WorkspaceAction" import { SSHConfig, SSHValues, defaultSSHConfigResponse, mergeSSHConfigValues } from "./sshConfig" import { computeSSHProperties, sshSupportsSetEnv } from "./sshSupport" import { Storage } from "./storage" -import { WorkspaceAction } from "./WorkspaceAction" export class Remote { // Prefix is a magic string that is prepended to SSH hosts to indicate that From 867b841aa3165fb2e9385152caedfec1f17264c7 Mon Sep 17 00:00:00 2001 From: Kira Pilot Date: Thu, 22 Jun 2023 10:09:49 -0400 Subject: [PATCH 05/17] check job --- src/WorkspaceAction.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/WorkspaceAction.ts b/src/WorkspaceAction.ts index b395c500..f86d5a3b 100644 --- a/src/WorkspaceAction.ts +++ b/src/WorkspaceAction.ts @@ -51,7 +51,14 @@ export class WorkspaceAction { } filterImpendingAutostopWorkspaces(workspace: Workspace) { - if (workspace.latest_build.transition !== "start" || !workspace.latest_build.deadline) { + // a workspace is eligible for autostop if the last build was successful, + // and the workspace is started, + // and it has a deadline + if ( + workspace.latest_build.job.status !== "succeeded" || + workspace.latest_build.transition !== "start" || + !workspace.latest_build.deadline + ) { return false } From 4baaccfd7581099727bf2edcd839e230fa23c4ab Mon Sep 17 00:00:00 2001 From: Kira Pilot Date: Thu, 22 Jun 2023 12:36:31 -0400 Subject: [PATCH 06/17] added notifications for workspace deletion --- package.json | 3 ++- src/WorkspaceAction.ts | 52 +++++++++++++++++++++++++++++------------- yarn.lock | 19 +++++++++++++++ 3 files changed, 57 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index 837c47f5..7600f416 100644 --- a/package.json +++ b/package.json @@ -232,6 +232,7 @@ }, "dependencies": { "axios": "0.26.1", + "date-fns": "^2.30.0", "eventsource": "^2.0.2", "find-process": "^1.4.7", "fs-extra": "^11.1.0", @@ -246,4 +247,4 @@ "yaml": "^1.10.0", "zod": "^3.21.4" } -} \ No newline at end of file +} diff --git a/src/WorkspaceAction.ts b/src/WorkspaceAction.ts index f86d5a3b..33256155 100644 --- a/src/WorkspaceAction.ts +++ b/src/WorkspaceAction.ts @@ -1,10 +1,12 @@ import { getWorkspaces } from "coder/site/src/api/api" import { Workspace, WorkspacesResponse } from "coder/site/src/api/typesGenerated" import * as vscode from "vscode" +import { formatDistanceToNowStrict } from "date-fns" interface NotifiedWorkspace { workspace: Workspace wasNotified: boolean + impendingActionDeadline: string } export class WorkspaceAction { @@ -20,6 +22,8 @@ export class WorkspaceAction { // seed initial lists this.seedNotificationLists() + this.notifyAll() + // set up polling so we get current workspaces data this.pollGetWorkspaces() } @@ -32,25 +36,20 @@ export class WorkspaceAction { } catch (error) { ownedWorkspacesResponse = { workspaces: [], count: 0 } } - return new WorkspaceAction(vscodeProposed, ownedWorkspacesResponse.workspaces) } seedNotificationLists() { this.#workspacesApproachingAutostop = this.#ownedWorkspaces - .filter(this.filterImpendingAutostopWorkspaces) - .map((workspace: Workspace) => { - const wasNotified = - this.#workspacesApproachingAutostop.find((wn) => wn.workspace.id === workspace.id)?.wasNotified ?? false - return { workspace, wasNotified } - }) - - // NOTE: this feature is currently in-progess; however, we're including scaffolding for it - // to exemplify the class pattern used for Workspace Actions - this.#workspacesApproachingDeletion = [] + .filter(this.filterWorkspacesImpendingAutostop) + .map((workspace: Workspace) => this.transformWorkspaceObjects(workspace, workspace.latest_build.deadline)) + + this.#workspacesApproachingDeletion = this.#ownedWorkspaces + .filter(this.filterWorkspacesImpendingDeletion) + .map((workspace: Workspace) => this.transformWorkspaceObjects(workspace, workspace.deleting_at)) } - filterImpendingAutostopWorkspaces(workspace: Workspace) { + filterWorkspacesImpendingAutostop(workspace: Workspace) { // a workspace is eligible for autostop if the last build was successful, // and the workspace is started, // and it has a deadline @@ -62,9 +61,30 @@ export class WorkspaceAction { return false } - const hoursMilli = 1000 * 60 * 60 + const hourMilli = 1000 * 60 * 60 // return workspaces with a deadline that is in 1 hr or less - return Math.abs(new Date().getTime() - new Date(workspace.latest_build.deadline).getTime()) <= hoursMilli + return Math.abs(new Date().getTime() - new Date(workspace.latest_build.deadline).getTime()) <= hourMilli + } + + filterWorkspacesImpendingDeletion(workspace: Workspace) { + if (!workspace.deleting_at) { + return + } + + const dayMilli = 1000 * 60 * 60 * 24 + + // return workspaces with a deleting_at that is 24 hrs or less + return Math.abs(new Date().getTime() - new Date(workspace.deleting_at).getTime()) <= dayMilli + } + + transformWorkspaceObjects(workspace: Workspace, deadlineField?: string) { + // the below line is to satisfy TS; we should always pass a deadlineField, e.g + // workspace,deleting_at or workspace.latest_build.deadline + if (!deadlineField) return { workspace, wasNotified: true, impendingActionDeadline: "" } + const wasNotified = + this.#workspacesApproachingAutostop.find((wn) => wn.workspace.id === workspace.id)?.wasNotified ?? false + const impendingActionDeadline = formatDistanceToNowStrict(new Date(deadlineField)) + return { workspace, wasNotified, impendingActionDeadline } } async pollGetWorkspaces() { @@ -100,7 +120,7 @@ export class WorkspaceAction { // intentionally strips new lines from the message text // https://github.com/Microsoft/vscode/issues/48900 this.vscodeProposed.window.showInformationMessage( - `${notifiedWorkspace.workspace.name} is scheduled to shut down in 1 hour.`, + `${notifiedWorkspace.workspace.name} is scheduled to shut down in ${notifiedWorkspace.impendingActionDeadline}.`, ) notifiedWorkspace.wasNotified = true }) @@ -117,7 +137,7 @@ export class WorkspaceAction { // intentionally strips new lines from the message text // https://github.com/Microsoft/vscode/issues/48900 this.vscodeProposed.window.showInformationMessage( - `${notifiedWorkspace.workspace.name} is scheduled for deletion.`, + `${notifiedWorkspace.workspace.name} is scheduled for deletion in ${notifiedWorkspace.impendingActionDeadline}.`, ) notifiedWorkspace.wasNotified = true }) diff --git a/yarn.lock b/yarn.lock index 4b5a6a8e..ca0c1ba8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -163,6 +163,13 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.20.13.tgz#ddf1eb5a813588d2fb1692b70c6fce75b945c088" integrity sha512-gFDLKMfpiXCsjt4za2JA9oTMn70CeseCehb11kRZgvd7+F67Hih3OHOK24cRrWECJ/ljfPGac6ygXAs/C8kIvw== +"@babel/runtime@^7.21.0": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.5.tgz#8564dd588182ce0047d55d7a75e93921107b57ec" + integrity sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA== + dependencies: + regenerator-runtime "^0.13.11" + "@babel/template@^7.18.10", "@babel/template@^7.20.7": version "7.20.7" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.20.7.tgz#a15090c2839a83b02aa996c0b4994005841fd5a8" @@ -1530,6 +1537,13 @@ css-what@^6.1.0: resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4" integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== +date-fns@^2.30.0: + version "2.30.0" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.30.0.tgz#f367e644839ff57894ec6ac480de40cae4b0f4d0" + integrity sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw== + dependencies: + "@babel/runtime" "^7.21.0" + dayjs@^1.11.7: version "1.11.7" resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.7.tgz#4b296922642f70999544d1144a2c25730fce63e2" @@ -3887,6 +3901,11 @@ rechoir@^0.8.0: dependencies: resolve "^1.20.0" +regenerator-runtime@^0.13.11: + version "0.13.11" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" + integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== + regexp.prototype.flags@^1.4.3: version "1.4.3" resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz#87cab30f80f66660181a3bb7bf5981a872b367ac" From 752bbd2914fc2930114ff9e18c8f33cb291a32dd Mon Sep 17 00:00:00 2001 From: Kira Pilot Date: Thu, 22 Jun 2023 12:37:35 -0400 Subject: [PATCH 07/17] fix lint --- src/WorkspaceAction.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/WorkspaceAction.ts b/src/WorkspaceAction.ts index 33256155..83fce43e 100644 --- a/src/WorkspaceAction.ts +++ b/src/WorkspaceAction.ts @@ -1,7 +1,7 @@ import { getWorkspaces } from "coder/site/src/api/api" import { Workspace, WorkspacesResponse } from "coder/site/src/api/typesGenerated" -import * as vscode from "vscode" import { formatDistanceToNowStrict } from "date-fns" +import * as vscode from "vscode" interface NotifiedWorkspace { workspace: Workspace @@ -80,7 +80,9 @@ export class WorkspaceAction { transformWorkspaceObjects(workspace: Workspace, deadlineField?: string) { // the below line is to satisfy TS; we should always pass a deadlineField, e.g // workspace,deleting_at or workspace.latest_build.deadline - if (!deadlineField) return { workspace, wasNotified: true, impendingActionDeadline: "" } + if (!deadlineField) { + return { workspace, wasNotified: true, impendingActionDeadline: "" } + } const wasNotified = this.#workspacesApproachingAutostop.find((wn) => wn.workspace.id === workspace.id)?.wasNotified ?? false const impendingActionDeadline = formatDistanceToNowStrict(new Date(deadlineField)) From 4d9d3e2ab1f62defb3e0575bd3f690ff74aa56ff Mon Sep 17 00:00:00 2001 From: Kira Pilot Date: Fri, 23 Jun 2023 11:34:41 -0400 Subject: [PATCH 08/17] writing to coder output channel when requests fail --- src/WorkspaceAction.ts | 25 +++++++++++++++++++++---- src/remote.ts | 2 +- src/storage.ts | 5 +++++ 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/src/WorkspaceAction.ts b/src/WorkspaceAction.ts index 83fce43e..a95091a8 100644 --- a/src/WorkspaceAction.ts +++ b/src/WorkspaceAction.ts @@ -1,6 +1,8 @@ import { getWorkspaces } from "coder/site/src/api/api" import { Workspace, WorkspacesResponse } from "coder/site/src/api/typesGenerated" import { formatDistanceToNowStrict } from "date-fns" +import { Storage } from "./storage" +import axios from "axios" import * as vscode from "vscode" interface NotifiedWorkspace { @@ -16,7 +18,11 @@ export class WorkspaceAction { #workspacesApproachingAutostop: NotifiedWorkspace[] = [] #workspacesApproachingDeletion: NotifiedWorkspace[] = [] - private constructor(private readonly vscodeProposed: typeof vscode, ownedWorkspaces: Workspace[]) { + private constructor( + private readonly vscodeProposed: typeof vscode, + private readonly storage: Storage, + ownedWorkspaces: Workspace[], + ) { this.#ownedWorkspaces = ownedWorkspaces // seed initial lists @@ -28,15 +34,21 @@ export class WorkspaceAction { this.pollGetWorkspaces() } - static async init(vscodeProposed: typeof vscode) { + static async init(vscodeProposed: typeof vscode, storage: Storage) { // fetch all workspaces owned by the user and set initial public class fields let ownedWorkspacesResponse: WorkspacesResponse try { ownedWorkspacesResponse = await getWorkspaces({ q: "owner:me" }) } catch (error) { + if (!axios.isAxiosError(error) || error.response?.status !== 401) { + storage.writeToCoderOutputChannel( + `Failed to fetch owned workspaces. Some workspace notifications may be missing: ${error}`, + ) + } + ownedWorkspacesResponse = { workspaces: [], count: 0 } } - return new WorkspaceAction(vscodeProposed, ownedWorkspacesResponse.workspaces) + return new WorkspaceAction(vscodeProposed, storage, ownedWorkspacesResponse.workspaces) } seedNotificationLists() { @@ -98,10 +110,15 @@ export class WorkspaceAction { this.seedNotificationLists() this.notifyAll() } catch (error) { + errorCount++ + if (!axios.isAxiosError(error) || error.response?.status !== 401) { + this.storage.writeToCoderOutputChannel( + `Failed to poll owned workspaces. Some workspace notifications may be missing: ${error}`, + ) + } if (errorCount === 3) { clearInterval(this.#fetchWorkspacesInterval) } - errorCount++ } }, 1000 * 5) } diff --git a/src/remote.ts b/src/remote.ts index 4a6975b8..65ca69b8 100644 --- a/src/remote.ts +++ b/src/remote.ts @@ -128,7 +128,7 @@ export class Remote { ) // Initialize any WorkspaceAction notifications (auto-off, upcoming deletion) - const Action = await WorkspaceAction.init(this.vscodeProposed) + const Action = await WorkspaceAction.init(this.vscodeProposed, this.storage) let buildComplete: undefined | (() => void) if (this.storage.workspace.latest_build.status === "stopped") { diff --git a/src/storage.ts b/src/storage.ts index 588f3408..e6bc8473 100644 --- a/src/storage.ts +++ b/src/storage.ts @@ -298,6 +298,11 @@ export class Storage { }) } + public writeToCoderOutputChannel(message: string) { + this.output.appendLine(message) + this.output.show(true) + } + private async updateURL(): Promise { const url = this.getURL() axios.defaults.baseURL = url From 1c65a2f433894dacd5f484f4b7f7df209aa541b1 Mon Sep 17 00:00:00 2001 From: Kira Pilot Date: Fri, 23 Jun 2023 12:57:00 -0400 Subject: [PATCH 09/17] updating wasNotified for upcoming deletions --- src/WorkspaceAction.ts | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/WorkspaceAction.ts b/src/WorkspaceAction.ts index a95091a8..242e66e1 100644 --- a/src/WorkspaceAction.ts +++ b/src/WorkspaceAction.ts @@ -26,7 +26,7 @@ export class WorkspaceAction { this.#ownedWorkspaces = ownedWorkspaces // seed initial lists - this.seedNotificationLists() + this.updateNotificationLists() this.notifyAll() @@ -51,14 +51,18 @@ export class WorkspaceAction { return new WorkspaceAction(vscodeProposed, storage, ownedWorkspacesResponse.workspaces) } - seedNotificationLists() { + updateNotificationLists() { this.#workspacesApproachingAutostop = this.#ownedWorkspaces .filter(this.filterWorkspacesImpendingAutostop) - .map((workspace: Workspace) => this.transformWorkspaceObjects(workspace, workspace.latest_build.deadline)) + .map((workspace: Workspace) => + this.transformWorkspaceObjects(workspace, this.#workspacesApproachingAutostop, workspace.latest_build.deadline), + ) this.#workspacesApproachingDeletion = this.#ownedWorkspaces .filter(this.filterWorkspacesImpendingDeletion) - .map((workspace: Workspace) => this.transformWorkspaceObjects(workspace, workspace.deleting_at)) + .map((workspace: Workspace) => + this.transformWorkspaceObjects(workspace, this.#workspacesApproachingDeletion, workspace.deleting_at), + ) } filterWorkspacesImpendingAutostop(workspace: Workspace) { @@ -89,14 +93,13 @@ export class WorkspaceAction { return Math.abs(new Date().getTime() - new Date(workspace.deleting_at).getTime()) <= dayMilli } - transformWorkspaceObjects(workspace: Workspace, deadlineField?: string) { + transformWorkspaceObjects(workspace: Workspace, workspaceList: NotifiedWorkspace[], deadlineField?: string) { // the below line is to satisfy TS; we should always pass a deadlineField, e.g // workspace,deleting_at or workspace.latest_build.deadline if (!deadlineField) { return { workspace, wasNotified: true, impendingActionDeadline: "" } } - const wasNotified = - this.#workspacesApproachingAutostop.find((wn) => wn.workspace.id === workspace.id)?.wasNotified ?? false + const wasNotified = workspaceList.find((nw) => nw.workspace.id === workspace.id)?.wasNotified ?? false const impendingActionDeadline = formatDistanceToNowStrict(new Date(deadlineField)) return { workspace, wasNotified, impendingActionDeadline } } @@ -107,7 +110,7 @@ export class WorkspaceAction { try { const workspacesResult = await getWorkspaces({ q: "owner:me" }) this.#ownedWorkspaces = workspacesResult.workspaces - this.seedNotificationLists() + this.updateNotificationLists() this.notifyAll() } catch (error) { errorCount++ From 7ad0d7173c82f05b21d0270b3fbe796ce48ef072 Mon Sep 17 00:00:00 2001 From: Kira Pilot Date: Fri, 23 Jun 2023 14:48:37 -0400 Subject: [PATCH 10/17] added nifty TS fix --- src/WorkspaceAction.ts | 24 ++++++++++++------------ src/remote.ts | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/WorkspaceAction.ts b/src/WorkspaceAction.ts index 242e66e1..a1a6e314 100644 --- a/src/WorkspaceAction.ts +++ b/src/WorkspaceAction.ts @@ -1,5 +1,5 @@ import { getWorkspaces } from "coder/site/src/api/api" -import { Workspace, WorkspacesResponse } from "coder/site/src/api/typesGenerated" +import { Workspace, WorkspacesResponse, WorkspaceBuild } from "coder/site/src/api/typesGenerated" import { formatDistanceToNowStrict } from "date-fns" import { Storage } from "./storage" import axios from "axios" @@ -11,6 +11,11 @@ interface NotifiedWorkspace { impendingActionDeadline: string } +type WithRequired = T & Required> + +type WorkspaceWithDeadline = Workspace & { latest_build: WithRequired } +type WorkspaceWithDeletingAt = Workspace & WithRequired + export class WorkspaceAction { #fetchWorkspacesInterval?: ReturnType @@ -54,18 +59,18 @@ export class WorkspaceAction { updateNotificationLists() { this.#workspacesApproachingAutostop = this.#ownedWorkspaces .filter(this.filterWorkspacesImpendingAutostop) - .map((workspace: Workspace) => + .map((workspace) => this.transformWorkspaceObjects(workspace, this.#workspacesApproachingAutostop, workspace.latest_build.deadline), ) this.#workspacesApproachingDeletion = this.#ownedWorkspaces .filter(this.filterWorkspacesImpendingDeletion) - .map((workspace: Workspace) => + .map((workspace) => this.transformWorkspaceObjects(workspace, this.#workspacesApproachingDeletion, workspace.deleting_at), ) } - filterWorkspacesImpendingAutostop(workspace: Workspace) { + filterWorkspacesImpendingAutostop(workspace: Workspace): workspace is WorkspaceWithDeadline { // a workspace is eligible for autostop if the last build was successful, // and the workspace is started, // and it has a deadline @@ -82,9 +87,9 @@ export class WorkspaceAction { return Math.abs(new Date().getTime() - new Date(workspace.latest_build.deadline).getTime()) <= hourMilli } - filterWorkspacesImpendingDeletion(workspace: Workspace) { + filterWorkspacesImpendingDeletion(workspace: Workspace): workspace is WorkspaceWithDeletingAt { if (!workspace.deleting_at) { - return + return false } const dayMilli = 1000 * 60 * 60 * 24 @@ -93,12 +98,7 @@ export class WorkspaceAction { return Math.abs(new Date().getTime() - new Date(workspace.deleting_at).getTime()) <= dayMilli } - transformWorkspaceObjects(workspace: Workspace, workspaceList: NotifiedWorkspace[], deadlineField?: string) { - // the below line is to satisfy TS; we should always pass a deadlineField, e.g - // workspace,deleting_at or workspace.latest_build.deadline - if (!deadlineField) { - return { workspace, wasNotified: true, impendingActionDeadline: "" } - } + transformWorkspaceObjects(workspace: Workspace, workspaceList: NotifiedWorkspace[], deadlineField: string) { const wasNotified = workspaceList.find((nw) => nw.workspace.id === workspace.id)?.wasNotified ?? false const impendingActionDeadline = formatDistanceToNowStrict(new Date(deadlineField)) return { workspace, wasNotified, impendingActionDeadline } diff --git a/src/remote.ts b/src/remote.ts index 65ca69b8..b0a049f1 100644 --- a/src/remote.ts +++ b/src/remote.ts @@ -498,7 +498,7 @@ export class Remote { await sshConfig.load() let binaryPath: string | undefined - if (this.mode === vscode.ExtensionMode.Production) { + if (this.mode !== vscode.ExtensionMode.Production) { binaryPath = await this.storage.fetchBinary() } else { binaryPath = path.join(os.tmpdir(), "coder") From 9727783f39bcef9f6e89ec17fde4cf2f2793c89b Mon Sep 17 00:00:00 2001 From: Kira Pilot Date: Fri, 23 Jun 2023 14:58:00 -0400 Subject: [PATCH 11/17] fixed casing --- src/WorkspaceAction.ts | 4 +++- src/remote.ts | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/WorkspaceAction.ts b/src/WorkspaceAction.ts index a1a6e314..1f5c58b7 100644 --- a/src/WorkspaceAction.ts +++ b/src/WorkspaceAction.ts @@ -17,6 +17,8 @@ type WorkspaceWithDeadline = Workspace & { latest_build: WithRequired export class WorkspaceAction { + // We use this same interval in the Dashboard to poll for updates on the Workspaces page. + #POLL_INTERVAL: number = 1000 * 5 #fetchWorkspacesInterval?: ReturnType #ownedWorkspaces: Workspace[] = [] @@ -123,7 +125,7 @@ export class WorkspaceAction { clearInterval(this.#fetchWorkspacesInterval) } } - }, 1000 * 5) + }, this.#POLL_INTERVAL) } notifyAll() { diff --git a/src/remote.ts b/src/remote.ts index b0a049f1..4ae08740 100644 --- a/src/remote.ts +++ b/src/remote.ts @@ -128,7 +128,7 @@ export class Remote { ) // Initialize any WorkspaceAction notifications (auto-off, upcoming deletion) - const Action = await WorkspaceAction.init(this.vscodeProposed, this.storage) + const action = await WorkspaceAction.init(this.vscodeProposed, this.storage) let buildComplete: undefined | (() => void) if (this.storage.workspace.latest_build.status === "stopped") { @@ -431,7 +431,7 @@ export class Remote { return { dispose: () => { eventSource.close() - Action.cleanupWorkspaceActions() + action.cleanupWorkspaceActions() disposables.forEach((d) => d.dispose()) }, } @@ -498,7 +498,7 @@ export class Remote { await sshConfig.load() let binaryPath: string | undefined - if (this.mode !== vscode.ExtensionMode.Production) { + if (this.mode === vscode.ExtensionMode.Production) { binaryPath = await this.storage.fetchBinary() } else { binaryPath = path.join(os.tmpdir(), "coder") From ac0ce3ad4cd661d59a2cbc7e977bd12826755e53 Mon Sep 17 00:00:00 2001 From: Kira Pilot Date: Fri, 23 Jun 2023 15:23:49 -0400 Subject: [PATCH 12/17] update Coder dependency --- src/WorkspaceAction.ts | 2 +- yarn.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/WorkspaceAction.ts b/src/WorkspaceAction.ts index 1f5c58b7..339ba4df 100644 --- a/src/WorkspaceAction.ts +++ b/src/WorkspaceAction.ts @@ -96,7 +96,7 @@ export class WorkspaceAction { const dayMilli = 1000 * 60 * 60 * 24 - // return workspaces with a deleting_at that is 24 hrs or less + // return workspaces with a deleting_at that is 24 hrs or less return Math.abs(new Date().getTime() - new Date(workspace.deleting_at).getTime()) <= dayMilli } diff --git a/yarn.lock b/yarn.lock index ca0c1ba8..25461703 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1425,7 +1425,7 @@ co@3.1.0: "coder@https://github.com/coder/coder": version "0.0.0" - resolved "https://github.com/coder/coder#a6fa8cac582f2fc54eca0191bd54fd43d6d67ac2" + resolved "https://github.com/coder/coder#5d48122f12ddec2f5a34bf8d0a7d2dc4dd94b0d3" collapse-white-space@^1.0.2: version "1.0.6" From dd1aa9c9c18ea887d3d15a1271130e98de6f44c0 Mon Sep 17 00:00:00 2001 From: Kira Pilot Date: Fri, 23 Jun 2023 15:27:38 -0400 Subject: [PATCH 13/17] fix lint --- src/WorkspaceAction.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/WorkspaceAction.ts b/src/WorkspaceAction.ts index 339ba4df..7ab4d2e5 100644 --- a/src/WorkspaceAction.ts +++ b/src/WorkspaceAction.ts @@ -1,9 +1,9 @@ +import axios from "axios" import { getWorkspaces } from "coder/site/src/api/api" import { Workspace, WorkspacesResponse, WorkspaceBuild } from "coder/site/src/api/typesGenerated" import { formatDistanceToNowStrict } from "date-fns" -import { Storage } from "./storage" -import axios from "axios" import * as vscode from "vscode" +import { Storage } from "./storage" interface NotifiedWorkspace { workspace: Workspace From 7aca73401f91819980737c8d5ae6053f1e583d0c Mon Sep 17 00:00:00 2001 From: Kira Pilot Date: Mon, 26 Jun 2023 15:04:03 -0400 Subject: [PATCH 14/17] fixed relative path importing for coder dependency --- package.json | 4 +++- webpack.config.js | 2 ++ yarn.lock | 14 ++++++++++++-- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 7600f416..12243dad 100644 --- a/package.json +++ b/package.json @@ -211,7 +211,7 @@ "@vscode/test-electron": "^1.6.2", "@vscode/vsce": "^2.16.0", "bufferutil": "^4.0.7", - "coder": "https://github.com/coder/coder", + "coder": "https://github.com/coder/coder#main", "dayjs": "^1.11.7", "eslint": "^7.19.0", "eslint-config-prettier": "^8.3.0", @@ -231,6 +231,7 @@ "webpack-cli": "^5.0.1" }, "dependencies": { + "@types/ua-parser-js": "^0.7.36", "axios": "0.26.1", "date-fns": "^2.30.0", "eventsource": "^2.0.2", @@ -242,6 +243,7 @@ "pretty-bytes": "^6.0.0", "semver": "^7.3.8", "tar-fs": "^2.1.1", + "ua-parser-js": "^1.0.35", "which": "^2.0.2", "ws": "^8.11.0", "yaml": "^1.10.0", diff --git a/webpack.config.js b/webpack.config.js index 1943a85e..7aa71696 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -23,6 +23,8 @@ const config = { resolve: { // support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader extensions: [".ts", ".js"], + // the Coder dependency uses absolute paths + modules: ["./node_modules", "./node_modules/coder/site/src"], }, module: { rules: [ diff --git a/yarn.lock b/yarn.lock index 25461703..d2a0e272 100644 --- a/yarn.lock +++ b/yarn.lock @@ -549,6 +549,11 @@ dependencies: "@types/node" "*" +"@types/ua-parser-js@^0.7.36": + version "0.7.36" + resolved "https://registry.yarnpkg.com/@types/ua-parser-js/-/ua-parser-js-0.7.36.tgz#9bd0b47f26b5a3151be21ba4ce9f5fa457c5f190" + integrity sha512-N1rW+njavs70y2cApeIw1vLMYXRwfBy+7trgavGuuTfOd7j1Yh7QTRc/yqsPl6ncokt72ZXuxEU0PiCp9bSwNQ== + "@types/unist@^2.0.0", "@types/unist@^2.0.2": version "2.0.6" resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d" @@ -1423,9 +1428,9 @@ co@3.1.0: resolved "https://registry.yarnpkg.com/co/-/co-3.1.0.tgz#4ea54ea5a08938153185e15210c68d9092bc1b78" integrity sha512-CQsjCRiNObI8AtTsNIBDRMQ4oMR83CzEswHYahClvul7gKk+lDQiOKv+5qh7LQWf5sh6jkZNispz/QlsZxyNgA== -"coder@https://github.com/coder/coder": +"coder@https://github.com/coder/coder#main": version "0.0.0" - resolved "https://github.com/coder/coder#5d48122f12ddec2f5a34bf8d0a7d2dc4dd94b0d3" + resolved "https://github.com/coder/coder#140683813d794081a0c0dbab70ec7eee16c5f5c4" collapse-white-space@^1.0.2: version "1.0.6" @@ -5269,6 +5274,11 @@ typescript@^4.1.3: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.4.tgz#a2a3d2756c079abda241d75f149df9d561091e78" integrity sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg== +ua-parser-js@^1.0.35: + version "1.0.35" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.35.tgz#c4ef44343bc3db0a3cbefdf21822f1b1fc1ab011" + integrity sha512-fKnGuqmTBnIE+/KXSzCn4db8RTigUzw1AN0DmdU6hJovUTbYJKyqj+8Mt1c4VfRDnOVJnENmfYkIPZ946UrSAA== + uc.micro@^1.0.1, uc.micro@^1.0.5: version "1.0.6" resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac" From 2d9e7f63cbf68f6f749e5224c2a22083cc85f302 Mon Sep 17 00:00:00 2001 From: Kira Pilot Date: Tue, 27 Jun 2023 09:46:25 -0400 Subject: [PATCH 15/17] Update src/WorkspaceAction.ts Co-authored-by: Asher --- src/WorkspaceAction.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/WorkspaceAction.ts b/src/WorkspaceAction.ts index 7ab4d2e5..503620d0 100644 --- a/src/WorkspaceAction.ts +++ b/src/WorkspaceAction.ts @@ -14,7 +14,7 @@ interface NotifiedWorkspace { type WithRequired = T & Required> type WorkspaceWithDeadline = Workspace & { latest_build: WithRequired } -type WorkspaceWithDeletingAt = Workspace & WithRequired +type WorkspaceWithDeletingAt = WithRequired export class WorkspaceAction { // We use this same interval in the Dashboard to poll for updates on the Workspaces page. From 53704d0f2111ab8b81e91f895c750fe6d3d0f7a6 Mon Sep 17 00:00:00 2001 From: Kira Pilot Date: Tue, 27 Jun 2023 10:03:02 -0400 Subject: [PATCH 16/17] simplify build status gate --- src/WorkspaceAction.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/WorkspaceAction.ts b/src/WorkspaceAction.ts index 503620d0..46e154a5 100644 --- a/src/WorkspaceAction.ts +++ b/src/WorkspaceAction.ts @@ -73,14 +73,8 @@ export class WorkspaceAction { } filterWorkspacesImpendingAutostop(workspace: Workspace): workspace is WorkspaceWithDeadline { - // a workspace is eligible for autostop if the last build was successful, - // and the workspace is started, - // and it has a deadline - if ( - workspace.latest_build.job.status !== "succeeded" || - workspace.latest_build.transition !== "start" || - !workspace.latest_build.deadline - ) { + // a workspace is eligible for autostop if the workspace is running and it has a deadline + if (workspace.latest_build.status !== "running" || !workspace.latest_build.deadline) { return false } From 450d0540193ca94a5f8c9bd632240c4d9e37b71b Mon Sep 17 00:00:00 2001 From: Kira Pilot Date: Tue, 27 Jun 2023 10:36:34 -0400 Subject: [PATCH 17/17] capture all errors --- src/WorkspaceAction.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/WorkspaceAction.ts b/src/WorkspaceAction.ts index 46e154a5..b32ed175 100644 --- a/src/WorkspaceAction.ts +++ b/src/WorkspaceAction.ts @@ -47,7 +47,11 @@ export class WorkspaceAction { try { ownedWorkspacesResponse = await getWorkspaces({ q: "owner:me" }) } catch (error) { - if (!axios.isAxiosError(error) || error.response?.status !== 401) { + let status + if (axios.isAxiosError(error)) { + status = error.response?.status + } + if (status !== 401) { storage.writeToCoderOutputChannel( `Failed to fetch owned workspaces. Some workspace notifications may be missing: ${error}`, ) @@ -110,7 +114,12 @@ export class WorkspaceAction { this.notifyAll() } catch (error) { errorCount++ - if (!axios.isAxiosError(error) || error.response?.status !== 401) { + + let status + if (axios.isAxiosError(error)) { + status = error.response?.status + } + if (status !== 401) { this.storage.writeToCoderOutputChannel( `Failed to poll owned workspaces. Some workspace notifications may be missing: ${error}`, )