Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit713b2bd

Browse files
Kira-PilotKira Pilotcode-asher
authored
feat: add VS code notifications for workspace actions (#111)
Co-authored-by: Kira Pilot <kirapilot@Kiras-MacBook-Pro.local>Co-authored-by: Asher <ash@coder.com>
1 parente2eb13a commit713b2bd

File tree

6 files changed

+224
-4
lines changed

6 files changed

+224
-4
lines changed

‎package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@
211211
"@vscode/test-electron":"^1.6.2",
212212
"@vscode/vsce":"^2.16.0",
213213
"bufferutil":"^4.0.7",
214-
"coder":"https://github.com/coder/coder",
214+
"coder":"https://github.com/coder/coder#main",
215215
"dayjs":"^1.11.7",
216216
"eslint":"^7.19.0",
217217
"eslint-config-prettier":"^8.3.0",
@@ -231,7 +231,9 @@
231231
"webpack-cli":"^5.0.1"
232232
},
233233
"dependencies": {
234+
"@types/ua-parser-js":"^0.7.36",
234235
"axios":"0.26.1",
236+
"date-fns":"^2.30.0",
235237
"eventsource":"^2.0.2",
236238
"find-process":"^1.4.7",
237239
"fs-extra":"^11.1.0",
@@ -241,9 +243,10 @@
241243
"pretty-bytes":"^6.0.0",
242244
"semver":"^7.3.8",
243245
"tar-fs":"^2.1.1",
246+
"ua-parser-js":"^1.0.35",
244247
"which":"^2.0.2",
245248
"ws":"^8.11.0",
246249
"yaml":"^1.10.0",
247250
"zod":"^3.21.4"
248251
}
249-
}
252+
}

‎src/WorkspaceAction.ts

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
importaxiosfrom"axios"
2+
import{getWorkspaces}from"coder/site/src/api/api"
3+
import{Workspace,WorkspacesResponse,WorkspaceBuild}from"coder/site/src/api/typesGenerated"
4+
import{formatDistanceToNowStrict}from"date-fns"
5+
import*asvscodefrom"vscode"
6+
import{Storage}from"./storage"
7+
8+
interfaceNotifiedWorkspace{
9+
workspace:Workspace
10+
wasNotified:boolean
11+
impendingActionDeadline:string
12+
}
13+
14+
typeWithRequired<T,KextendskeyofT>=T&Required<Pick<T,K>>
15+
16+
typeWorkspaceWithDeadline=Workspace&{latest_build:WithRequired<WorkspaceBuild,"deadline">}
17+
typeWorkspaceWithDeletingAt=WithRequired<Workspace,"deleting_at">
18+
19+
exportclassWorkspaceAction{
20+
// We use this same interval in the Dashboard to poll for updates on the Workspaces page.
21+
#POLL_INTERVAL:number=1000*5
22+
#fetchWorkspacesInterval?:ReturnType<typeofsetInterval>
23+
24+
#ownedWorkspaces:Workspace[]=[]
25+
#workspacesApproachingAutostop:NotifiedWorkspace[]=[]
26+
#workspacesApproachingDeletion:NotifiedWorkspace[]=[]
27+
28+
privateconstructor(
29+
privatereadonlyvscodeProposed:typeofvscode,
30+
privatereadonlystorage:Storage,
31+
ownedWorkspaces:Workspace[],
32+
){
33+
this.#ownedWorkspaces=ownedWorkspaces
34+
35+
// seed initial lists
36+
this.updateNotificationLists()
37+
38+
this.notifyAll()
39+
40+
// set up polling so we get current workspaces data
41+
this.pollGetWorkspaces()
42+
}
43+
44+
staticasyncinit(vscodeProposed:typeofvscode,storage:Storage){
45+
// fetch all workspaces owned by the user and set initial public class fields
46+
letownedWorkspacesResponse:WorkspacesResponse
47+
try{
48+
ownedWorkspacesResponse=awaitgetWorkspaces({q:"owner:me"})
49+
}catch(error){
50+
letstatus
51+
if(axios.isAxiosError(error)){
52+
status=error.response?.status
53+
}
54+
if(status!==401){
55+
storage.writeToCoderOutputChannel(
56+
`Failed to fetch owned workspaces. Some workspace notifications may be missing:${error}`,
57+
)
58+
}
59+
60+
ownedWorkspacesResponse={workspaces:[],count:0}
61+
}
62+
returnnewWorkspaceAction(vscodeProposed,storage,ownedWorkspacesResponse.workspaces)
63+
}
64+
65+
updateNotificationLists(){
66+
this.#workspacesApproachingAutostop=this.#ownedWorkspaces
67+
.filter(this.filterWorkspacesImpendingAutostop)
68+
.map((workspace)=>
69+
this.transformWorkspaceObjects(workspace,this.#workspacesApproachingAutostop,workspace.latest_build.deadline),
70+
)
71+
72+
this.#workspacesApproachingDeletion=this.#ownedWorkspaces
73+
.filter(this.filterWorkspacesImpendingDeletion)
74+
.map((workspace)=>
75+
this.transformWorkspaceObjects(workspace,this.#workspacesApproachingDeletion,workspace.deleting_at),
76+
)
77+
}
78+
79+
filterWorkspacesImpendingAutostop(workspace:Workspace):workspace isWorkspaceWithDeadline{
80+
// a workspace is eligible for autostop if the workspace is running and it has a deadline
81+
if(workspace.latest_build.status!=="running"||!workspace.latest_build.deadline){
82+
returnfalse
83+
}
84+
85+
consthourMilli=1000*60*60
86+
// return workspaces with a deadline that is in 1 hr or less
87+
returnMath.abs(newDate().getTime()-newDate(workspace.latest_build.deadline).getTime())<=hourMilli
88+
}
89+
90+
filterWorkspacesImpendingDeletion(workspace:Workspace):workspace isWorkspaceWithDeletingAt{
91+
if(!workspace.deleting_at){
92+
returnfalse
93+
}
94+
95+
constdayMilli=1000*60*60*24
96+
97+
// return workspaces with a deleting_at that is 24 hrs or less
98+
returnMath.abs(newDate().getTime()-newDate(workspace.deleting_at).getTime())<=dayMilli
99+
}
100+
101+
transformWorkspaceObjects(workspace:Workspace,workspaceList:NotifiedWorkspace[],deadlineField:string){
102+
constwasNotified=workspaceList.find((nw)=>nw.workspace.id===workspace.id)?.wasNotified??false
103+
constimpendingActionDeadline=formatDistanceToNowStrict(newDate(deadlineField))
104+
return{ workspace, wasNotified, impendingActionDeadline}
105+
}
106+
107+
asyncpollGetWorkspaces(){
108+
leterrorCount=0
109+
this.#fetchWorkspacesInterval=setInterval(async()=>{
110+
try{
111+
constworkspacesResult=awaitgetWorkspaces({q:"owner:me"})
112+
this.#ownedWorkspaces=workspacesResult.workspaces
113+
this.updateNotificationLists()
114+
this.notifyAll()
115+
}catch(error){
116+
errorCount++
117+
118+
letstatus
119+
if(axios.isAxiosError(error)){
120+
status=error.response?.status
121+
}
122+
if(status!==401){
123+
this.storage.writeToCoderOutputChannel(
124+
`Failed to poll owned workspaces. Some workspace notifications may be missing:${error}`,
125+
)
126+
}
127+
if(errorCount===3){
128+
clearInterval(this.#fetchWorkspacesInterval)
129+
}
130+
}
131+
},this.#POLL_INTERVAL)
132+
}
133+
134+
notifyAll(){
135+
this.notifyImpendingAutostop()
136+
this.notifyImpendingDeletion()
137+
}
138+
139+
notifyImpendingAutostop(){
140+
this.#workspacesApproachingAutostop?.forEach((notifiedWorkspace:NotifiedWorkspace)=>{
141+
if(notifiedWorkspace.wasNotified){
142+
// don't message the user; we've already messaged
143+
return
144+
}
145+
146+
// we display individual notifications for each workspace as VS Code
147+
// intentionally strips new lines from the message text
148+
// https://github.com/Microsoft/vscode/issues/48900
149+
this.vscodeProposed.window.showInformationMessage(
150+
`${notifiedWorkspace.workspace.name} is scheduled to shut down in${notifiedWorkspace.impendingActionDeadline}.`,
151+
)
152+
notifiedWorkspace.wasNotified=true
153+
})
154+
}
155+
156+
notifyImpendingDeletion(){
157+
this.#workspacesApproachingDeletion?.forEach((notifiedWorkspace:NotifiedWorkspace)=>{
158+
if(notifiedWorkspace.wasNotified){
159+
// don't message the user; we've already messaged
160+
return
161+
}
162+
163+
// we display individual notifications for each workspace as VS Code
164+
// intentionally strips new lines from the message text
165+
// https://github.com/Microsoft/vscode/issues/48900
166+
this.vscodeProposed.window.showInformationMessage(
167+
`${notifiedWorkspace.workspace.name} is scheduled for deletion in${notifiedWorkspace.impendingActionDeadline}.`,
168+
)
169+
notifiedWorkspace.wasNotified=true
170+
})
171+
}
172+
173+
cleanupWorkspaceActions(){
174+
clearInterval(this.#fetchWorkspacesInterval)
175+
}
176+
}

‎src/remote.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import prettyBytes from "pretty-bytes"
1919
import*assemverfrom"semver"
2020
import*asvscodefrom"vscode"
2121
import*aswsfrom"ws"
22+
import{WorkspaceAction}from"./WorkspaceAction"
2223
import{SSHConfig,SSHValues,defaultSSHConfigResponse,mergeSSHConfigValues}from"./sshConfig"
2324
import{computeSSHProperties,sshSupportsSetEnv}from"./sshSupport"
2425
import{Storage}from"./storage"
@@ -126,6 +127,9 @@ export class Remote {
126127
this.registerLabelFormatter(remoteAuthority,this.storage.workspace.owner_name,this.storage.workspace.name),
127128
)
128129

130+
// Initialize any WorkspaceAction notifications (auto-off, upcoming deletion)
131+
constaction=awaitWorkspaceAction.init(this.vscodeProposed,this.storage)
132+
129133
letbuildComplete:undefined|(()=>void)
130134
if(this.storage.workspace.latest_build.status==="stopped"){
131135
this.vscodeProposed.window.withProgress(
@@ -427,6 +431,7 @@ export class Remote {
427431
return{
428432
dispose:()=>{
429433
eventSource.close()
434+
action.cleanupWorkspaceActions()
430435
disposables.forEach((d)=>d.dispose())
431436
},
432437
}

‎src/storage.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,11 @@ export class Storage {
298298
})
299299
}
300300

301+
publicwriteToCoderOutputChannel(message:string){
302+
this.output.appendLine(message)
303+
this.output.show(true)
304+
}
305+
301306
privateasyncupdateURL():Promise<void>{
302307
consturl=this.getURL()
303308
axios.defaults.baseURL=url

‎webpack.config.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ const config = {
2323
resolve:{
2424
// support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader
2525
extensions:[".ts",".js"],
26+
// the Coder dependency uses absolute paths
27+
modules:["./node_modules","./node_modules/coder/site/src"],
2628
},
2729
module:{
2830
rules:[

‎yarn.lock

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,13 @@
163163
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.20.13.tgz#ddf1eb5a813588d2fb1692b70c6fce75b945c088"
164164
integrity sha512-gFDLKMfpiXCsjt4za2JA9oTMn70CeseCehb11kRZgvd7+F67Hih3OHOK24cRrWECJ/ljfPGac6ygXAs/C8kIvw==
165165

166+
"@babel/runtime@^7.21.0":
167+
version "7.22.5"
168+
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.5.tgz#8564dd588182ce0047d55d7a75e93921107b57ec"
169+
integrity sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==
170+
dependencies:
171+
regenerator-runtime "^0.13.11"
172+
166173
"@babel/template@^7.18.10", "@babel/template@^7.20.7":
167174
version "7.20.7"
168175
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.20.7.tgz#a15090c2839a83b02aa996c0b4994005841fd5a8"
@@ -542,6 +549,11 @@
542549
dependencies:
543550
"@types/node""*"
544551

552+
"@types/ua-parser-js@^0.7.36":
553+
version "0.7.36"
554+
resolved "https://registry.yarnpkg.com/@types/ua-parser-js/-/ua-parser-js-0.7.36.tgz#9bd0b47f26b5a3151be21ba4ce9f5fa457c5f190"
555+
integrity sha512-N1rW+njavs70y2cApeIw1vLMYXRwfBy+7trgavGuuTfOd7j1Yh7QTRc/yqsPl6ncokt72ZXuxEU0PiCp9bSwNQ==
556+
545557
"@types/unist@^2.0.0", "@types/unist@^2.0.2":
546558
version "2.0.6"
547559
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d"
@@ -1416,9 +1428,9 @@ co@3.1.0:
14161428
resolved "https://registry.yarnpkg.com/co/-/co-3.1.0.tgz#4ea54ea5a08938153185e15210c68d9092bc1b78"
14171429
integrity sha512-CQsjCRiNObI8AtTsNIBDRMQ4oMR83CzEswHYahClvul7gKk+lDQiOKv+5qh7LQWf5sh6jkZNispz/QlsZxyNgA==
14181430

1419-
"coder@https://github.com/coder/coder":
1431+
"coder@https://github.com/coder/coder#main":
14201432
version "0.0.0"
1421-
resolved "https://github.com/coder/coder#a6fa8cac582f2fc54eca0191bd54fd43d6d67ac2"
1433+
resolved "https://github.com/coder/coder#140683813d794081a0c0dbab70ec7eee16c5f5c4"
14221434

14231435
collapse-white-space@^1.0.2:
14241436
version "1.0.6"
@@ -1530,6 +1542,13 @@ css-what@^6.1.0:
15301542
resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4"
15311543
integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==
15321544

1545+
date-fns@^2.30.0:
1546+
version "2.30.0"
1547+
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.30.0.tgz#f367e644839ff57894ec6ac480de40cae4b0f4d0"
1548+
integrity sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==
1549+
dependencies:
1550+
"@babel/runtime""^7.21.0"
1551+
15331552
dayjs@^1.11.7:
15341553
version "1.11.7"
15351554
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.7.tgz#4b296922642f70999544d1144a2c25730fce63e2"
@@ -3887,6 +3906,11 @@ rechoir@^0.8.0:
38873906
dependencies:
38883907
resolve "^1.20.0"
38893908

3909+
regenerator-runtime@^0.13.11:
3910+
version "0.13.11"
3911+
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9"
3912+
integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==
3913+
38903914
regexp.prototype.flags@^1.4.3:
38913915
version "1.4.3"
38923916
resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz#87cab30f80f66660181a3bb7bf5981a872b367ac"
@@ -5250,6 +5274,11 @@ typescript@^4.1.3:
52505274
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.4.tgz#a2a3d2756c079abda241d75f149df9d561091e78"
52515275
integrity sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==
52525276

5277+
ua-parser-js@^1.0.35:
5278+
version "1.0.35"
5279+
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.35.tgz#c4ef44343bc3db0a3cbefdf21822f1b1fc1ab011"
5280+
integrity sha512-fKnGuqmTBnIE+/KXSzCn4db8RTigUzw1AN0DmdU6hJovUTbYJKyqj+8Mt1c4VfRDnOVJnENmfYkIPZ946UrSAA==
5281+
52535282
uc.micro@^1.0.1, uc.micro@^1.0.5:
52545283
version "1.0.6"
52555284
resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac"

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp