8000 wip(site): Add support for informing of updates · coder/coder@8e47aed · GitHub
[go: up one dir, main page]

Skip to content

Commit 8e47aed

Browse files
committed
wip(site): Add support for informing of updates
1 parent 23a2811 commit 8e47aed

File tree

6 files changed

+131
-0
lines changed

6 files changed

+131
-0
lines changed

site/src/api/api.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,12 @@ export const getBuildInfo = async (): Promise<TypesGen.BuildInfoResponse> => {
372372
return response.data
373373
}
374374

375+
export const getUpdateCheck =
376+
async (): Promise<TypesGen.UpdateCheckResponse> => {
377+
const response = await axios.get("/api/v2/updatecheck")
378+
return response.data
379+
}
380+
375381
export const putWorkspaceAutostart = async (
376382
workspaceID: string,
377383
autostart: TypesGen.UpdateWorkspaceAutostartRequest,

site/src/api/typesGenerated.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,7 @@ export interface DeploymentConfig {
298298
readonly auto_import_templates: DeploymentConfigField<string[]>
299299
readonly metrics_cache_refresh_interval: DeploymentConfigField<number>
300300
readonly agent_stat_refresh_interval: DeploymentConfigField<number>
301+
readonly update_check: DeploymentConfigField<boolean>
301302
readonly audit_logging: DeploymentConfigField<boolean>
302303
readonly browser_only: DeploymentConfigField<boolean>
303304
readonly scim_api_key: DeploymentConfigField<string>
@@ -670,6 +671,13 @@ export interface UpdateActiveTemplateVersion {
670671
readonly id: string
671672
}
672673

674+
// From codersdk/updatecheck.go
675+
export interface UpdateCheckResponse {
676+
readonly current: boolean
677+
readonly version: string
678+
readonly url: string
679+
}
680+
673681
// From codersdk/users.go
674682
export interface UpdateRoles {
675683
readonly roles: string[]

site/src/testHelpers/entities.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ export const MockBuildInfo: TypesGen.BuildInfoResponse = {
2323
version: "v99.999.9999+c9cdf14",
2424
}
2525

26+
export const MockUpdateCheck: TypesGen.UpdateCheckResponse = {
27+
current: true,
28+
url: "file:///mock-url",
29+
version: "v99.999.9999+c9cdf14",
30+
}
31+
2632
export const MockOwnerRole: TypesGen.Role = {
2733
name: "owner",
2834
display_name: "Owner",

site/src/testHelpers/handlers.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ export const handlers = [
1515
return res(ctx.status(200), ctx.json(M.MockBuildInfo))
1616
}),
1717

18+
// update check
19+
rest.get("/api/v2/updatecheck", async (req, res, ctx) => {
20+
return res(ctx.status(200), ctx.json(M.MockUpdateCheck))
21+
}),
22+
1823
// organizations
1924
rest.get("/api/v2/organizations/:organizationId", async (req, res, ctx) => {
2025
return res(ctx.status(200), ctx.json(M.MockOrganization))

site/src/xServices/StateContext.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { createContext, FC, ReactNode } from "react"
33
import { ActorRefFrom } from "xstate"
44
import { authMachine } from "./auth/authXService"
55
import { buildInfoMachine } from "./buildInfo/buildInfoXService"
6+
import { updateCheckMachine } from "./updateCheck/updateCheckXService"
67
import { deploymentConfigMachine } from "./deploymentConfig/deploymentConfigMachine"
78
import { entitlementsMachine } from "./entitlements/entitlementsXService"
89
import { siteRolesMachine } from "./roles/siteRolesXService"
@@ -14,6 +15,7 @@ interface XServiceContextType {
1415
siteRolesXService: ActorRefFrom<typeof siteRolesMachine>
1516
// Since the info here is used by multiple deployment settings page and we don't want to refetch them every time
1617
deploymentConfigXService: ActorRefFrom<typeof deploymentConfigMachine>
18+
updateCheckXService: ActorRefFrom<typeof updateCheckMachine>
1719
}
1820

1921
/**
@@ -35,6 +37,7 @@ export const XServiceProvider: FC<{ children: ReactNode }> = ({ children }) => {
3537
entitlementsXService: useInterpret(entitlementsMachine),
3638
siteRolesXService: useInterpret(siteRolesMachine),
3739
deploymentConfigXService: useInterpret(deploymentConfigMachine),
40+
updateCheckXService: useInterpret(updateCheckMachine),
3841
}}
3942
>
4043
{children}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import { assign, createMachine } from "xstate"
2+
import * as API from "../../api/api"
3+
import * as TypesGen from "../../api/typesGenerated"
4+
import { displayMsg } from "../../components/GlobalSnackbar/utils"
5+
6+
export const Language = {
7+
updateAvailable: "New version available",
8+
updateAvailableMessage: (
9+
version: string,
10+
url: string,
11+
upgrade_instructions_url: string,
12+
): string =>
13+
`Coder ${version} is now available at ${url}. See ${upgrade_instructions_url} for information on how to upgrade.`,
14+
}
15+
16+
export interface UpdateCheckContext {
17+
getUpdateCheckError?: Error | unknown
18+
updateCheck?: TypesGen.UpdateCheckResponse
19+
}
20+
21+
export const updateCheckMachine = createMachine(
22+
{
23+
id: "updateCheckState",
24+
predictableActionArguments: true,
25+
tsTypes: {} as import("./updateCheckXService.typegen").Typegen0,
26+
schema: {
27+
context: {} as UpdateCheckContext,
28+
services: {} as {
29+
getUpdateCheck: {
30+
data: TypesGen.UpdateCheckResponse
31+
}
32+
},
33+
},
34+
context: {
35+
updateCheck: undefined,
36+
},
37+
initial: "gettingUpdateCheck",
38+
states: {
39+
gettingUpdateCheck: {
40+
invoke: {
41+
src: "getUpdateCheck",
42+
id: "getUpdateCheck",
43+
onDone: [
44+
{
45+
actions: [
46+
"assignUpdateCheck",
47+
"clearGetUpdateCheckError",
48+
"notifyUpdateAvailable",
49+
],
50+
target: "#updateCheckState.success",
51+
},
52+
],
53+
onError: [
54+
{
55+
actions: ["assignGetUpdateCheckError", "clearUpdateCheck"],
56+
target: "#updateCheckState.failure",
57+
},
58+
],
59+
},
60+
},
61+
success: {
62+
type: "final",
63+
},
64+
failure: {
65+
type: "final",
66+
},
67+
},
68+
},
69+
{
70+
services: {
71+
getUpdateCheck: API.getUpdateCheck,
72+
},
73+
actions: {
74+
assignUpdateCheck: assign({
75+
updateCheck: (_, event) => event.data,
76+
}),
77+
clearUpdateCheck: assign((context: UpdateCheckContext) => ({
78+
...context,
79+
updateCheck: undefined,
80+
})),
81+
assignGetUpdateCheckError: assign({
82+
getUpdateCheckError: (_, event) => event.data,
83+
}),
84+
clearGetUpdateCheckError: assign((context: UpdateCheckContext) => ({
85+
...context,
86+
getUpdateCheckError: undefined,
87+
})),
88+
notifyUpdateAvailable: (context: UpdateCheckContext) => {
89+
if (context.updateCheck && !context.updateCheck.current) {
90+
// TODO(mafredri): HTML formatting, persistance?
91+
displayMsg(
92+
Language.updateAvailable,
93+
Language.updateAvailableMessage(
94+
context.updateCheck.version,
95+
context.updateCheck.url,
96+
"https://coder.com/docs/coder-oss/latest/admin/upgrade",
97+
),
98+
)
99+
}
100+
},
101+
},
102+
},
103+
)

0 commit comments

Comments
 (0)
0