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}`, ) pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy