Skip to content

Commit 9d92df2

Browse files
committed
Add update action
1 parent e5cbcd8 commit 9d92df2

File tree

5 files changed

+254
-87
lines changed

5 files changed

+254
-87
lines changed

site/src/components/HelpTooltip/HelpTooltip.tsx

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import Popover from "@material-ui/core/Popover"
33
import { makeStyles } from "@material-ui/core/styles"
44
import HelpIcon from "@material-ui/icons/HelpOutline"
55
import OpenInNewIcon from "@material-ui/icons/OpenInNew"
6-
import React, { useState } from "react"
6+
import React, { createContext, useContext, useState } from "react"
77
import { Stack } from "../Stack/Stack"
88

99
type Icon = typeof HelpIcon
@@ -15,25 +15,46 @@ export interface HelpTooltipProps {
1515
size?: Size
1616
}
1717

18+
const HelpTooltipContext = createContext<{ open: boolean; onClose: () => void } | undefined>(undefined)
19+
20+
const useHelpTooltip = () => {
21+
const helpTooltipContext = useContext(HelpTooltipContext)
22+
23+
if (!helpTooltipContext) {
24+
throw new Error("This hook should be used in side of the HelpTooltipContext.")
25+
}
26+
27+
return helpTooltipContext
28+
}
29+
1830
export const HelpTooltip: React.FC<HelpTooltipProps> = ({ children, open, size = "medium" }) => {
1931
const styles = useStyles({ size })
2032
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null)
2133
open = open ?? Boolean(anchorEl)
2234
const id = open ? "help-popover" : undefined
2335

36+
const onClose = () => {
37+
setAnchorEl(null)
38+
}
39+
2440
return (
2541
<>
26-
<button aria-describedby={id} className={styles.button} onClick={(event) => setAnchorEl(event.currentTarget)}>
42+
<button
43+
aria-describedby={id}
44+
className={styles.button}
45+
onClick={(event) => {
46+
event.stopPropagation()
47+
setAnchorEl(event.currentTarget)
48+
}}
49+
>
2750
<HelpIcon className={styles.icon} />
2851
</button>
2952
<Popover
3053
classes={{ paper: styles.popoverPaper }}
3154
id={id}
3255
open={open}
3356
anchorEl={anchorEl}
34-
onClose={() => {
35-
setAnchorEl(null)
36-
}}
57+
onClose={onClose}
3758
anchorOrigin={{
3859
vertical: "bottom",
3960
horizontal: "left",
@@ -43,7 +64,7 @@ export const HelpTooltip: React.FC<HelpTooltipProps> = ({ children, open, size =
4364
horizontal: "left",
4465
}}
4566
>
46-
{children}
67+
<HelpTooltipContext.Provider value={{ open, onClose }}>{children}</HelpTooltipContext.Provider>
4768
</Popover>
4869
</>
4970
)
@@ -74,9 +95,17 @@ export const HelpTooltipLink: React.FC<{ href: string }> = ({ children, href })
7495

7596
export const HelpTooltipAction: React.FC<{ icon: Icon; onClick: () => void }> = ({ children, icon: Icon, onClick }) => {
7697
const styles = useStyles()
98+
const tooltip = useHelpTooltip()
7799

78100
return (
79-
<button className={styles.action} onClick={onClick}>
101+
<button
102+
className={styles.action}
103+
onClick={(event) => {
104+
event.stopPropagation()
105+
onClick()
106+
tooltip.onClose()
107+
}}
108+
>
80109
<Icon className={styles.actionIcon} />
81110
{children}
82111
</button>

site/src/pages/WorkspacesPage/WorkspacesPage.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { WorkspacesPageView } from "./WorkspacesPageView"
1010
const WorkspacesPage: FC = () => {
1111
const [workspacesState, send] = useMachine(workspacesMachine)
1212
const [searchParams, setSearchParams] = useSearchParams()
13+
const { workspaceRefs } = workspacesState.context
1314

1415
useEffect(() => {
1516
const filter = searchParams.get("filter")
@@ -30,7 +31,7 @@ const WorkspacesPage: FC = () => {
3031
<WorkspacesPageView
3132
filter={workspacesState.context.filter}
3233
loading={workspacesState.hasTag("loading")}
33-
workspaces={workspacesState.context.workspaces}
34+
workspaceRefs={workspaceRefs}
3435
onFilter={(query) => {
3536
searchParams.set("filter", query)
3637
setSearchParams(searchParams)

site/src/pages/WorkspacesPage/WorkspacesPageView.stories.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { ComponentMeta, Story } from "@storybook/react"
2+
import { spawn } from "xstate"
23
import { ProvisionerJobStatus, Workspace, WorkspaceTransition } from "../../api/typesGenerated"
34
import { MockWorkspace } from "../../testHelpers/entities"
45
import { workspaceFilterQuery } from "../../util/workspace"
6+
import { workspaceItemMachine } from "../../xServices/workspaces/workspacesXService"
57
import { WorkspacesPageView, WorkspacesPageViewProps } from "./WorkspacesPageView"
68

79
export default {
@@ -43,27 +45,29 @@ const workspaces: { [key in ProvisionerJobStatus]: Workspace } = {
4345

4446
export const AllStates = Template.bind({})
4547
AllStates.args = {
46-
workspaces: [
48+
workspaceRefs: [
4749
...Object.values(workspaces),
4850
createWorkspaceWithStatus("running", "stop"),
4951
createWorkspaceWithStatus("succeeded", "stop"),
5052
createWorkspaceWithStatus("running", "delete"),
51-
],
53+
].map((data) => spawn(workspaceItemMachine.withContext({ data }))),
5254
}
5355

5456
export const Outdated = Template.bind({})
5557
Outdated.args = {
56-
workspaces: [createWorkspaceWithStatus("running", "stop", true)],
58+
workspaceRefs: [createWorkspaceWithStatus("running", "stop", true)].map((data) =>
59+
spawn(workspaceItemMachine.withContext({ data })),
60+
),
5761
}
5862

5963
export const OwnerHasNoWorkspaces = Template.bind({})
6064
OwnerHasNoWorkspaces.args = {
61-
workspaces: [],
65+
workspaceRefs: [],
6266
filter: workspaceFilterQuery.me,
6367
}
6468

6569
export const NoResults = Template.bind({})
6670
NoResults.args = {
67-
workspaces: [],
71+
workspaceRefs: [],
6872
filter: "searchtearmwithnoresults",
6973
}

site/src/pages/WorkspacesPage/WorkspacesPageView.tsx

Lines changed: 71 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@ import KeyboardArrowRight from "@material-ui/icons/KeyboardArrowRight"
1616
import RefreshIcon from "@material-ui/icons/Refresh"
1717
import SearchIcon from "@material-ui/icons/Search"
1818
import useTheme from "@material-ui/styles/useTheme"
19+
import { useActor } from "@xstate/react"
1920
import dayjs from "dayjs"
2021
import relativeTime from "dayjs/plugin/relativeTime"
2122
import { FormikErrors, useFormik } from "formik"
2223
import { FC, useState } from "react"
2324
import { Link as RouterLink, useNavigate } from "react-router-dom"
24-
import * as TypesGen from "../../api/typesGenerated"
2525
import { AvatarData } from "../../components/AvatarData/AvatarData"
2626
import { CloseDropdown, OpenDropdown } from "../../components/DropdownArrows/DropdownArrows"
2727
import { EmptyState } from "../../components/EmptyState/EmptyState"
@@ -39,6 +39,7 @@ import { Stack } from "../../components/Stack/Stack"
3939
import { TableLoader } from "../../components/TableLoader/TableLoader"
4040
import { getFormHelpers, onChangeTrimmed } from "../../util/formUtils"
4141
import { getDisplayStatus, workspaceFilterQuery } from "../../util/workspace"
42+
import { WorkspaceItemMachineRef } from "../../xServices/workspaces/workspacesXService"
4243

4344
dayjs.extend(relativeTime)
4445

@@ -97,6 +98,64 @@ const OutdatedHelpTooltip: React.FC<{ onUpdateVersion: () => void }> = ({ onUpda
9798
)
9899
}
99100

101+
const WorkspaceRow: React.FC<{ workspaceRef: WorkspaceItemMachineRef }> = ({ workspaceRef }) => {
102+
const styles = useStyles()
103+
const navigate = useNavigate()
104+
const theme: Theme = useTheme()
105+
const [workspaceState, send] = useActor(workspaceRef)
106+
const { data: workspace } = workspaceState.context
107+
const status = getDisplayStatus(theme, workspace.latest_build)
108+
const navigateToWorkspacePage = () => {
109+
navigate(`/@${workspace.owner_name}/${workspace.name}`)
110+
}
111+
return (
112+
<TableRow
113+
hover
114+
data-testid={`workspace-${workspace.id}`}
115+
tabIndex={0}
116+
onClick={navigateToWorkspacePage}
117+
onKeyDown={(event) => {
118+
if (event.key === "Enter") {
119+
navigateToWorkspacePage()
120+
}
121+
}}
122+
className={styles.clickableTableRow}
123+
>
124+
<TableCell>
125+
<AvatarData title={workspace.name} subtitle={workspace.owner_name} />
126+
</TableCell>
127+
<TableCell>{workspace.template_name}</TableCell>
128+
<TableCell>
129+
{workspace.outdated ? (
130+
<span className={styles.outdatedLabel}>
131+
{Language.outdatedLabel}
132+
<OutdatedHelpTooltip
133+
onUpdateVersion={() => {
134+
send("UPDATE_VERSION")
135+
}}
136+
/>
137+
</span>
138+
) : (
139+
<span style={{ color: theme.palette.text.secondary }}>{Language.upToDateLabel}</span>
140+
)}
141+
</TableCell>
142+
<TableCell>
143+
<span data-chromatic="ignore" style={{ color: theme.palette.text.secondary }}>
144+
{dayjs().to(dayjs(workspace.latest_build.created_at))}
145+
</span>
146+
</TableCell>
147+
<TableCell>
148+
<span style={{ color: status.color }}>{status.status}</span>
149+
</TableCell>
150+
<TableCell>
151+
<div className={styles.arrowCell}>
152+
<KeyboardArrowRight className={styles.arrowRight} />
153+
</div>
154+
</TableCell>
155+
</TableRow>
156+
)
157+
}
158+
100159
interface FilterFormValues {
101160
query: string
102161
}
@@ -105,15 +164,13 @@ export type FilterFormErrors = FormikErrors<FilterFormValues>
105164

106165
export interface WorkspacesPageViewProps {
107166
loading?: boolean
108-
workspaces?: TypesGen.Workspace[]
167+
workspaceRefs?: WorkspaceItemMachineRef[]
109168
filter?: string
110169
onFilter: (query: string) => void
111170
}
112171

113-
export const WorkspacesPageView: FC<WorkspacesPageViewProps> = ({ loading, workspaces, filter, onFilter }) => {
172+
export const WorkspacesPageView: FC<WorkspacesPageViewProps> = ({ loading, workspaceRefs, filter, onFilter }) => {
114173
const styles = useStyles()
115-
const navigate = useNavigate()
116-
const theme: Theme = useTheme()
117174

118175
const form = useFormik<FilterFormValues>({
119176
enableReinitialize: true,
@@ -216,17 +273,17 @@ export const WorkspacesPageView: FC<WorkspacesPageViewProps> = ({ loading, works
216273
<Table>
217274
<TableHead>
218275
<TableRow>
219-
<TableCell>Name</TableCell>
220-
<TableCell>Template</TableCell>
221-
<TableCell>Version</TableCell>
222-
<TableCell>Last Built</TableCell>
223-
<TableCell>Status</TableCell>
276+
<TableCell width="35%">Name</TableCell>
277+
<TableCell width="15%">Template</TableCell>
278+
<TableCell width="15%">Version</TableCell>
279+
<TableCell width="20%">Last Built</TableCell>
280+
<TableCell width="15%">Status</TableCell>
224281
<TableCell width="1%"></TableCell>
225282
</TableRow>
226283
</TableHead>
227284
<TableBody>
228-
{!workspaces && loading && <TableLoader />}
229-
{workspaces && workspaces.length === 0 && (
285+
{!workspaceRefs && loading && <TableLoader />}
286+
{workspaceRefs && workspaceRefs.length === 0 && (
230287
<>
231288
{filter === workspaceFilterQuery.me || filter === workspaceFilterQuery.all ? (
232289
<TableRow>
@@ -251,60 +308,8 @@ export const WorkspacesPageView: FC<WorkspacesPageViewProps> = ({ loading, works
251308
)}
252309
</>
253310
)}
254-
{workspaces &&
255-
workspaces.map((workspace) => {
256-
const status = getDisplayStatus(theme, workspace.latest_build)
257-
const navigateToWorkspacePage = () => {
258-
navigate(`/@${workspace.owner_name}/${workspace.name}`)
259-
}
260-
return (
261-
<TableRow
262-
key={workspace.id}
263-
hover
264-
data-testid={`workspace-${workspace.id}`}
265-
tabIndex={0}
266-
onClick={navigateToWorkspacePage}
267-
onKeyDown={(event) => {
268-
if (event.key === "Enter") {
269-
navigateToWorkspacePage()
270-
}
271-
}}
272-
className={styles.clickableTableRow}
273-
>
274-
<TableCell>
275-
<AvatarData title={workspace.name} subtitle={workspace.owner_name} />
276-
</TableCell>
277-
<TableCell>{workspace.template_name}</TableCell>
278-
<TableCell>
279-
{workspace.outdated ? (
280-
<span className={styles.outdatedLabel}>
281-
{Language.outdatedLabel}
282-
<OutdatedHelpTooltip
283-
onUpdateVersion={() => {
284-
console.log("UPDATE!!")
285-
}}
286-
/>
287-
</span>
288-
) : (
289-
<span style={{ color: theme.palette.text.secondary }}>{Language.upToDateLabel}</span>
290-
)}
291-
</TableCell>
292-
<TableCell>
293-
<span data-chromatic="ignore" style={{ color: theme.palette.text.secondary }}>
294-
{dayjs().to(dayjs(workspace.latest_build.created_at))}
295-
</span>
296-
</TableCell>
297-
<TableCell>
298-
<span style={{ color: status.color }}>{status.status}</span>
299-
</TableCell>
300-
<TableCell>
301-
<div className={styles.arrowCell}>
302-
<KeyboardArrowRight className={styles.arrowRight} />
303-
</div>
304-
</TableCell>
305-
</TableRow>
306-
)
307-
})}
311+
{workspaceRefs &&
312+
workspaceRefs.map((workspaceRef) => <WorkspaceRow workspaceRef={workspaceRef} key={workspaceRef.id} />)}
308313
</TableBody>
309314
</Table>
310315
</Margins>

0 commit comments

Comments
 (0)