-
Notifications
You must be signed in to change notification settings - Fork 22
Support canceling imports #3387
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
base: main
Are you sure you want to change the base?
Changes from all commits
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 |
|---|---|---|
|
|
@@ -9,6 +9,7 @@ import { useQuery } from '@tanstack/react-query' | |
| import { createColumnHelper } from '@tanstack/react-table' | ||
| import { useCallback, useMemo } from 'react' | ||
| import { Outlet, type LoaderFunctionArgs } from 'react-router' | ||
| import { match } from 'ts-pattern' | ||
|
|
||
| import { | ||
| api, | ||
|
|
@@ -28,6 +29,7 @@ import { DiskStateBadge, DiskTypeBadge, ReadOnlyBadge } from '~/components/State | |
| import { makeCrumb } from '~/hooks/use-crumbs' | ||
| import { getProjectSelector, useProjectSelector } from '~/hooks/use-params' | ||
| import { useQuickActions } from '~/hooks/use-quick-actions' | ||
| import { confirmAction } from '~/stores/confirm-action' | ||
| import { confirmDelete } from '~/stores/confirm-delete' | ||
| import { addToast } from '~/stores/toast' | ||
| import { DiskSourceName } from '~/table/cells/DiskSourceCell' | ||
|
|
@@ -113,6 +115,17 @@ export default function DisksPage() { | |
| }, | ||
| }) | ||
|
|
||
| const { mutateAsync: finalize } = useApiMutation(api.diskFinalizeImport, { | ||
| onSuccess() { | ||
| queryClient.invalidateEndpoint('diskList') | ||
| }, | ||
| }) | ||
| const { mutateAsync: stopBulkWriteImport } = useApiMutation(api.diskBulkWriteImportStop, { | ||
| onSuccess() { | ||
| queryClient.invalidateEndpoint('diskList') | ||
| }, | ||
| }) | ||
|
|
||
| const makeActions = useCallback( | ||
| (disk: Disk): MenuAction[] => [ | ||
| { | ||
|
|
@@ -131,23 +144,55 @@ export default function DisksPage() { | |
| }, | ||
| disabled: snapshotDisabledReason(disk), | ||
| }, | ||
| { | ||
| label: 'Delete', | ||
| onActivate: confirmDelete({ | ||
| doDelete: () => deleteDisk({ path: { disk: disk.name }, query: { project } }), | ||
| label: disk.name, | ||
| resourceKind: 'disk', | ||
| }), | ||
| disabled: | ||
| !diskCan.delete(disk) && | ||
| (disk.state.state === 'attached' ? ( | ||
| 'Disk must be detached before it can be deleted' | ||
| ) : ( | ||
| <>Only disks in state {fancifyStates(diskCan.delete.states)} can be deleted</> | ||
| )), | ||
| }, | ||
| match(disk.state.state) | ||
| .with('import_ready', 'importing_from_bulk_writes', () => ({ | ||
| label: 'Cancel import', | ||
| onActivate() { | ||
| confirmAction({ | ||
| doAction: async () => { | ||
| if (disk.state.state === 'importing_from_bulk_writes') { | ||
| await stopBulkWriteImport({ | ||
| path: { disk: disk.name }, | ||
| query: { project }, | ||
| }) | ||
| } | ||
|
|
||
| await finalize({ | ||
| path: { disk: disk.name }, | ||
| query: { project }, | ||
| body: {}, | ||
| }) | ||
|
|
||
| addToast( | ||
| <> | ||
| Import canceled for <HL>{disk.name}</HL> | ||
| </> | ||
| ) | ||
| }, | ||
| modalTitle: 'Cancel import', | ||
| modalContent: `Are you sure you want to cancel import for ${disk.name}?`, | ||
|
Collaborator
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 could also explain what's going to happen. Not sure about the level of detail, could be "It's going to end up detached" or "First it's going to stop the import and then it's going to finalize it." Second seems like too much info.
Contributor
Author
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. "Are you sure you want to cancel import for disk-1? This will detach your disk" seems confusing. Maybe we flip the script: the action here is generally referred to as "detaching", and the modal says "Are you sure you want to detach disk-1? This will cancel any ongoing import."
Contributor
Author
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. that helps benefit the case we were discussing on the issue: understanding that what you're seeing could be a disk mid-healthy-upload
Collaborator
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. "Detach" as a verb doesn't work here because the importing state is distinct from the attached state, which means attached to an instance. Here there's nothing it's being detached from. What I was trying to do is sort of telegraph the state it would end up in, which is kind of the generic Doing Nothing state, hence its terminal position in the beautiful state graph. Still, unless the user understands that that's what
|
||
| errorTitle: 'Failed to cancel import', | ||
| actionType: 'danger', | ||
| }) | ||
| }, | ||
| })) | ||
| .otherwise(() => ({ | ||
| label: 'Delete', | ||
| onActivate: confirmDelete({ | ||
| doDelete: () => deleteDisk({ path: { disk: disk.name }, query: { project } }), | ||
| label: disk.name, | ||
| resourceKind: 'disk', | ||
| }), | ||
| disabled: | ||
| !diskCan.delete(disk) && | ||
| (disk.state.state === 'attached' ? ( | ||
| 'Disk must be detached before it can be deleted' | ||
| ) : ( | ||
| <>Only disks in state {fancifyStates(diskCan.delete.states)} can be deleted</> | ||
| )), | ||
| })), | ||
|
Collaborator
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. Most of the time we like to leave delete there but disabled with a nice message explaining why. I think that would be helpful in the importing case just as it is in the rest of the non-detached states. |
||
| ], | ||
| [createSnapshot, deleteDisk, project] | ||
| [createSnapshot, deleteDisk, stopBulkWriteImport, finalize, project] | ||
| ) | ||
|
|
||
| const columns = useColsWithActions( | ||
|
|
||

Uh oh!
There was an error while loading. Please reload this page.
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.
Fable had an interesting point: if the disk has changed state, you might get an error trying to cancel import on an already-detached disk, when you could have just done a noop and said "tada!" It suggested fetching the disk again before doing anything to get the latest state. It's not bad, it's kinda cute. And I kind of like the match over the conditional skipping of the first step. Not sure about keeping the message the same whether we did anything or not. It's fine I guess?
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.
certainly this is the case with any action, right? I assume you can't stop a stopped instance, delete a deleted disk, etc.
beyond that, if we do refetch state, we should do the exact opposite: nothing, and say we did nothing. for instance, the state may have changed because the upload succeeded!
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.
In that code, if it does something it's only because the new state is still importing. But yeah, that's a good point, though I think the idea is that some other resources that are liable to change do polling to keep the state relatively up to date. But those are more actively transitional, like instance starting or support bundle collecting. I could definitely live with doing nothing here instead of adding the latency of an extra fetch up front.