-
Notifications
You must be signed in to change notification settings - Fork 886
feat(site): add stop and start batch actions #10565
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
7038c8b
07c1d29
8994d1b
dd32703
894629d
85696d3
59ed10f
eebbdd4
78aa58f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
import TextField from "@mui/material/TextField"; | ||
import { Box } from "@mui/system"; | ||
import { deleteWorkspace, startWorkspace, stopWorkspace } from "api/api"; | ||
import { Workspace } from "api/typesGenerated"; | ||
import { ConfirmDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog"; | ||
import { displayError } from "components/GlobalSnackbar/utils"; | ||
import { useState } from "react"; | ||
import { useMutation } from "react-query"; | ||
import { MONOSPACE_FONT_FAMILY } from "theme/constants"; | ||
|
||
export const useBatchActions = (options: { | ||
onSuccess: () => Promise<void>; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this should be typed as There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This component has a very specific use case and should only be used in one place so I would not try to make its interface generic to be re-used |
||
}) => { | ||
const { onSuccess } = options; | ||
|
||
const startAllMutation = useMutation({ | ||
mutationFn: async (workspaces: Workspace[]) => { | ||
return Promise.all( | ||
workspaces.map((w) => | ||
startWorkspace(w.id, w.latest_build.template_version_id), | ||
), | ||
); | ||
}, | ||
onSuccess, | ||
onError: () => { | ||
displayError("Failed to start workspaces"); | ||
}, | ||
}); | ||
|
||
const stopAllMutation = useMutation({ | ||
mutationFn: async (workspaces: Workspace[]) => { | ||
return Promise.all(workspaces.map((w) => stopWorkspace(w.id))); | ||
}, | ||
onSuccess, | ||
onError: () => { | ||
displayError("Failed to stop workspaces"); | ||
}, | ||
}); | ||
|
||
const deleteAllMutation = useMutation({ | ||
mutationFn: async (workspaces: Workspace[]) => { | ||
return Promise.all(workspaces.map((w) => deleteWorkspace(w.id))); | ||
}, | ||
onSuccess, | ||
onError: () => { | ||
displayError("Failed to delete workspaces"); | ||
}, | ||
}); | ||
|
||
return { | ||
startAll: startAllMutation.mutateAsync, | ||
stopAll: stopAllMutation.mutateAsync, | ||
deleteAll: deleteAllMutation.mutateAsync, | ||
isLoading: | ||
startAllMutation.isLoading || | ||
stopAllMutation.isLoading || | ||
deleteAllMutation.isLoading, | ||
}; | ||
}; | ||
|
||
type BatchDeleteConfirmationProps = { | ||
checkedWorkspaces: Workspace[]; | ||
open: boolean; | ||
isLoading: boolean; | ||
onClose: () => void; | ||
onConfirm: () => void; | ||
}; | ||
|
||
export const BatchDeleteConfirmation = ( | ||
props: BatchDeleteConfirmationProps, | ||
) => { | ||
const { checkedWorkspaces, open, onClose, onConfirm, isLoading } = props; | ||
const [confirmation, setConfirmation] = useState({ value: "", error: false }); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What values is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is a good idea but it does not work well for "dynamic" values since it can be whatever the user types and not only the "final" result. Makes sense? The values can be "D", "DE", "DEL", "DELE" or anything like "OTHER". |
||
|
||
const confirmDeletion = () => { | ||
setConfirmation((c) => ({ ...c, error: false })); | ||
|
||
if (confirmation.value !== "DELETE") { | ||
setConfirmation((c) => ({ ...c, error: true })); | ||
return; | ||
} | ||
|
||
onConfirm(); | ||
}; | ||
|
||
return ( | ||
<ConfirmDialog | ||
type="delete" | ||
open={open} | ||
confirmLoading={isLoading} | ||
onConfirm={confirmDeletion} | ||
onClose={() => { | ||
onClose(); | ||
setConfirmation({ value: "", error: false }); | ||
}} | ||
title={`Delete ${checkedWorkspaces?.length} ${ | ||
checkedWorkspaces.length === 1 ? "workspace" : "workspaces" | ||
}`} | ||
description={ | ||
<form | ||
onSubmit={async (e) => { | ||
e.preventDefault(); | ||
confirmDeletion(); | ||
}} | ||
> | ||
<Box> | ||
Deleting these workspaces is irreversible! Are you sure you want to | ||
proceed? Type{" "} | ||
<Box | ||
component="code" | ||
sx={{ | ||
fontFamily: MONOSPACE_FONT_FAMILY, | ||
color: (theme) => theme.palette.text.primary, | ||
fontWeight: 600, | ||
}} | ||
> | ||
`DELETE` | ||
</Box>{" "} | ||
to confirm. | ||
</Box> | ||
<TextField | ||
value={confirmation.value} | ||
required | ||
autoFocus | ||
fullWidth | ||
inputProps={{ | ||
"aria-label": "Type DELETE to confirm", | ||
}} | ||
placeholder="Type DELETE to confirm" | ||
sx={{ mt: 2 }} | ||
onChange={(e) => { | ||
const value = e.currentTarget?.value; | ||
setConfirmation((c) => ({ ...c, value })); | ||
}} | ||
error={confirmation.error} | ||
helperText={confirmation.error && "Please type DELETE to confirm"} | ||
/> | ||
</form> | ||
} | ||
/> | ||
); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this type-safe? My worry is that it looks like there's nothing restricting the type of children at the type level, so if someone accidentally passes in a string or a primitive as a prop, they would get a more cryptic runtime error
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I swear I tried my best to get a correct type for this without success :(