Implement resource moving
[arvados.git] / src / views-components / move-to-dialog / move-to-dialog.tsx
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 import * as React from "react";
6 import { Dispatch, compose } from "redux";
7 import { withDialog } from "~/store/dialog/with-dialog";
8 import { dialogActions } from "~/store/dialog/dialog-actions";
9 import { reduxForm, startSubmit, stopSubmit, InjectedFormProps, initialize, Field, WrappedFieldProps } from 'redux-form';
10 import { WithDialogProps } from '~/store/dialog/with-dialog';
11 import { FormDialog } from '~/components/form-dialog/form-dialog';
12 import { ProjectTreePicker } from '~/views-components/project-tree-picker/project-tree-picker';
13 import { MOVE_TO_VALIDATION } from '~/validators/validators';
14 import { Typography } from "@material-ui/core";
15 import { ResourceKind } from '~/models/resource';
16 import { ServiceRepository, getResourceService } from '~/services/services';
17 import { RootState } from '~/store/store';
18 import { getCommonResourceServiceError, CommonResourceServiceError } from "~/common/api/common-resource-service";
19 import { snackbarActions } from '../../store/snackbar/snackbar-actions';
20
21 export const MOVE_TO_DIALOG = 'moveToDialog';
22
23 export interface MoveToDialogResource {
24     name: string;
25     uuid: string;
26     ownerUuid: string;
27     kind: ResourceKind;
28 }
29
30 export const openMoveToDialog = (resource: { name: string, uuid: string, kind: ResourceKind }) =>
31     (dispatch: Dispatch) => {
32         dispatch(initialize(MOVE_TO_DIALOG, resource));
33         dispatch(dialogActions.OPEN_DIALOG({ id: MOVE_TO_DIALOG, data: {} }));
34     };
35
36 export const moveResource = (resource: MoveToDialogResource) =>
37     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
38         const service = getResourceService(resource.kind, services);
39         dispatch(startSubmit(MOVE_TO_DIALOG));
40         if (service) {
41             try {
42                 const originalResource = await service.get(resource.uuid);
43                 await service.update(resource.uuid, { ...originalResource, owner_uuid: resource.ownerUuid });
44                 dispatch(dialogActions.CLOSE_DIALOG({ id: MOVE_TO_DIALOG }));
45                 dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Resource has been moved', hideDuration: 2000 }));
46             } catch (e) {
47                 const error = getCommonResourceServiceError(e);
48                 if (error === CommonResourceServiceError.UNIQUE_VIOLATION) {
49                     dispatch(stopSubmit(MOVE_TO_DIALOG, { ownerUuid: 'A resource with the same name already exists in the target project' }));
50                 } else {
51                     dispatch(dialogActions.CLOSE_DIALOG({ id: MOVE_TO_DIALOG }));
52                     dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Could not move the resource.', hideDuration: 2000 }));
53                 }
54             }
55         }
56     };
57
58 export const MoveToProjectDialog = compose(
59     withDialog(MOVE_TO_DIALOG),
60     reduxForm<MoveToDialogResource>({
61         form: MOVE_TO_DIALOG,
62         onSubmit: (data, dispatch) => {
63             dispatch(moveResource(data));
64         }
65     })
66 )((props: WithDialogProps<string> & InjectedFormProps<MoveToDialogResource>) =>
67     <FormDialog
68         dialogTitle='Move to'
69         formFields={MoveToDialogFields}
70         submitLabel='Move'
71         {...props}
72     />);
73
74 const MoveToDialogFields = () =>
75     <Field
76         name="ownerUuid"
77         component={Picker}
78         validate={MOVE_TO_VALIDATION} />;
79
80
81 const Picker = (props: WrappedFieldProps) =>
82     <div style={{ height: '144px', display: 'flex', flexDirection: 'column' }}>
83         <ProjectTreePicker onChange={projectUuid => props.input.onChange(projectUuid)} />
84         {props.meta.dirty && props.meta.error &&
85             <Typography variant='caption' color='error'>
86                 {props.meta.error}
87             </Typography>}
88     </div>;