20846: Merge branch '19213-ubuntu2204-support' into 20846-ubuntu2204
[arvados.git] / services / workbench2 / src / store / collections / collection-partial-copy-actions.ts
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 import { Dispatch } from 'redux';
6 import { RootState } from 'store/store';
7 import { initialize, startSubmit, stopSubmit } from 'redux-form';
8 import { resetPickerProjectTree } from 'store/project-tree-picker/project-tree-picker-actions';
9 import { dialogActions } from 'store/dialog/dialog-actions';
10 import { ServiceRepository } from 'services/services';
11 import { CollectionFileSelection, CollectionPanelDirectory, CollectionPanelFile, filterCollectionFilesBySelection, getCollectionSelection } from '../collection-panel/collection-panel-files/collection-panel-files-state';
12 import { snackbarActions, SnackbarKind } from 'store/snackbar/snackbar-actions';
13 import { getCommonResourceServiceError, CommonResourceServiceError } from 'services/common-service/common-resource-service';
14 import { progressIndicatorActions } from "store/progress-indicator/progress-indicator-actions";
15 import { FileOperationLocation } from "store/tree-picker/tree-picker-actions";
16 import { updateResources } from 'store/resources/resources-actions';
17 import { navigateTo } from 'store/navigation/navigation-action';
18 import { ContextMenuResource } from 'store/context-menu/context-menu-actions';
19 import { CollectionResource } from 'models/collection';
20
21 export const COLLECTION_PARTIAL_COPY_FORM_NAME = 'COLLECTION_PARTIAL_COPY_DIALOG';
22 export const COLLECTION_PARTIAL_COPY_TO_SELECTED_COLLECTION = 'COLLECTION_PARTIAL_COPY_TO_SELECTED_DIALOG';
23 export const COLLECTION_PARTIAL_COPY_TO_SEPARATE_COLLECTIONS = 'COLLECTION_PARTIAL_COPY_TO_SEPARATE_DIALOG';
24
25 export interface CollectionPartialCopyToNewCollectionFormData {
26     name: string;
27     description: string;
28     projectUuid: string;
29 }
30
31 export interface CollectionPartialCopyToExistingCollectionFormData {
32     destination: FileOperationLocation;
33 }
34
35 export interface CollectionPartialCopyToSeparateCollectionsFormData {
36     name: string;
37     projectUuid: string;
38 }
39
40 export const openCollectionPartialCopyToNewCollectionDialog = (resource: ContextMenuResource) =>
41     (dispatch: Dispatch, getState: () => RootState) => {
42         const sourceCollection = getState().collectionPanel.item;
43
44         if (sourceCollection) {
45             openCopyToNewDialog(dispatch, sourceCollection, [resource]);
46         }
47     };
48
49 export const openCollectionPartialCopyMultipleToNewCollectionDialog = () =>
50     (dispatch: Dispatch, getState: () => RootState) => {
51         const sourceCollection = getState().collectionPanel.item;
52         const selectedItems = filterCollectionFilesBySelection(getState().collectionPanelFiles, true);
53
54         if (sourceCollection && selectedItems.length) {
55             openCopyToNewDialog(dispatch, sourceCollection, selectedItems);
56         }
57     };
58
59 const openCopyToNewDialog = (dispatch: Dispatch, sourceCollection: CollectionResource, selectedItems: (CollectionPanelDirectory | CollectionPanelFile | ContextMenuResource)[]) => {
60     // Get selected files
61     const collectionFileSelection = getCollectionSelection(sourceCollection, selectedItems);
62     // Populate form initial state
63     const initialFormData = {
64         name: `Files extracted from: ${sourceCollection.name}`,
65         description: sourceCollection.description,
66         projectUuid: undefined
67     };
68     dispatch(initialize(COLLECTION_PARTIAL_COPY_FORM_NAME, initialFormData));
69     dispatch<any>(resetPickerProjectTree());
70     dispatch(dialogActions.OPEN_DIALOG({ id: COLLECTION_PARTIAL_COPY_FORM_NAME, data: collectionFileSelection }));
71 };
72
73 export const copyCollectionPartialToNewCollection = (fileSelection: CollectionFileSelection, formData: CollectionPartialCopyToNewCollectionFormData) =>
74     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
75         if (fileSelection.collection) {
76             try {
77                 dispatch(startSubmit(COLLECTION_PARTIAL_COPY_FORM_NAME));
78                 dispatch(progressIndicatorActions.START_WORKING(COLLECTION_PARTIAL_COPY_FORM_NAME));
79
80                 // Copy files
81                 const updatedCollection = await services.collectionService.copyFiles(
82                     fileSelection.collection.portableDataHash,
83                     fileSelection.selectedPaths,
84                     {
85                         name: formData.name,
86                         description: formData.description,
87                         ownerUuid: formData.projectUuid,
88                         uuid: undefined,
89                     },
90                     '/',
91                     false
92                 );
93                 dispatch(updateResources([updatedCollection]));
94                 dispatch<any>(navigateTo(updatedCollection.uuid));
95
96                 dispatch(dialogActions.CLOSE_DIALOG({ id: COLLECTION_PARTIAL_COPY_FORM_NAME }));
97                 dispatch(snackbarActions.OPEN_SNACKBAR({
98                     message: 'New collection created.',
99                     hideDuration: 2000,
100                     kind: SnackbarKind.SUCCESS
101                 }));
102             } catch (e) {
103                 const error = getCommonResourceServiceError(e);
104                 if (error === CommonResourceServiceError.UNIQUE_NAME_VIOLATION) {
105                     dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Collection with this name already exists', hideDuration: 2000, kind: SnackbarKind.ERROR }));
106                 } else if (error === CommonResourceServiceError.UNKNOWN) {
107                     dispatch(dialogActions.CLOSE_DIALOG({ id: COLLECTION_PARTIAL_COPY_FORM_NAME }));
108                     dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Could not create a copy of collection', hideDuration: 2000, kind: SnackbarKind.ERROR }));
109                 } else {
110                     dispatch(dialogActions.CLOSE_DIALOG({ id: COLLECTION_PARTIAL_COPY_FORM_NAME }));
111                     dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Collection has been copied but may contain incorrect files.', hideDuration: 2000, kind: SnackbarKind.ERROR }));
112                 }
113             } finally {
114                 dispatch(stopSubmit(COLLECTION_PARTIAL_COPY_FORM_NAME));
115                 dispatch(progressIndicatorActions.STOP_WORKING(COLLECTION_PARTIAL_COPY_FORM_NAME));
116             }
117         }
118     };
119
120 export const openCollectionPartialCopyToExistingCollectionDialog = (resource: ContextMenuResource) =>
121     (dispatch: Dispatch, getState: () => RootState) => {
122         const sourceCollection = getState().collectionPanel.item;
123
124         if (sourceCollection) {
125             openCopyToExistingDialog(dispatch, sourceCollection, [resource]);
126         }
127     };
128
129 export const openCollectionPartialCopyMultipleToExistingCollectionDialog = () =>
130     (dispatch: Dispatch, getState: () => RootState) => {
131         const sourceCollection = getState().collectionPanel.item;
132         const selectedItems = filterCollectionFilesBySelection(getState().collectionPanelFiles, true);
133
134         if (sourceCollection && selectedItems.length) {
135             openCopyToExistingDialog(dispatch, sourceCollection, selectedItems);
136         }
137     };
138
139 const openCopyToExistingDialog = (dispatch: Dispatch, sourceCollection: CollectionResource, selectedItems: (CollectionPanelDirectory | CollectionPanelFile | ContextMenuResource)[]) => {
140     // Get selected files
141     const collectionFileSelection = getCollectionSelection(sourceCollection, selectedItems);
142     // Populate form initial state
143     const initialFormData = {
144         destination: {uuid: sourceCollection.uuid, destinationPath: ''}
145     };
146     dispatch(initialize(COLLECTION_PARTIAL_COPY_TO_SELECTED_COLLECTION, initialFormData));
147     dispatch<any>(resetPickerProjectTree());
148     dispatch(dialogActions.OPEN_DIALOG({ id: COLLECTION_PARTIAL_COPY_TO_SELECTED_COLLECTION, data: collectionFileSelection }));
149 }
150
151 export const copyCollectionPartialToExistingCollection = (fileSelection: CollectionFileSelection, formData: CollectionPartialCopyToExistingCollectionFormData) =>
152     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
153         if (fileSelection.collection && formData.destination && formData.destination.uuid) {
154             try {
155                 dispatch(startSubmit(COLLECTION_PARTIAL_COPY_TO_SELECTED_COLLECTION));
156                 dispatch(progressIndicatorActions.START_WORKING(COLLECTION_PARTIAL_COPY_TO_SELECTED_COLLECTION));
157
158                 // Copy files
159                 const updatedCollection = await services.collectionService.copyFiles(
160                     fileSelection.collection.portableDataHash,
161                     fileSelection.selectedPaths,
162                     {uuid: formData.destination.uuid},
163                     formData.destination.subpath || '/',
164                     false
165                 );
166                 dispatch(updateResources([updatedCollection]));
167
168                 dispatch(dialogActions.CLOSE_DIALOG({ id: COLLECTION_PARTIAL_COPY_TO_SELECTED_COLLECTION }));
169                 dispatch(snackbarActions.OPEN_SNACKBAR({
170                     message: 'Files has been copied to selected collection.',
171                     hideDuration: 2000,
172                     kind: SnackbarKind.SUCCESS
173                 }));
174             } catch (e) {
175                 const error = getCommonResourceServiceError(e);
176                 if (error === CommonResourceServiceError.UNKNOWN) {
177                     dispatch(dialogActions.CLOSE_DIALOG({ id: COLLECTION_PARTIAL_COPY_TO_SELECTED_COLLECTION }));
178                     dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Could not copy this files to selected collection', hideDuration: 2000, kind: SnackbarKind.ERROR }));
179                 }
180             } finally {
181                 dispatch(stopSubmit(COLLECTION_PARTIAL_COPY_TO_SELECTED_COLLECTION));
182                 dispatch(progressIndicatorActions.STOP_WORKING(COLLECTION_PARTIAL_COPY_TO_SELECTED_COLLECTION));
183             }
184         }
185     };
186
187 export const openCollectionPartialCopyToSeparateCollectionsDialog = () =>
188     (dispatch: Dispatch, getState: () => RootState) => {
189         const sourceCollection = getState().collectionPanel.item;
190         const selectedItems = filterCollectionFilesBySelection(getState().collectionPanelFiles, true);
191
192         if (sourceCollection && selectedItems.length) {
193             // Get selected files
194             const collectionFileSelection = getCollectionSelection(sourceCollection, selectedItems);
195             // Populate form initial state
196             const initialFormData = {
197                 name: sourceCollection.name,
198                 projectUuid: undefined
199             };
200             dispatch(initialize(COLLECTION_PARTIAL_COPY_TO_SEPARATE_COLLECTIONS, initialFormData));
201             dispatch<any>(resetPickerProjectTree());
202             dispatch(dialogActions.OPEN_DIALOG({ id: COLLECTION_PARTIAL_COPY_TO_SEPARATE_COLLECTIONS, data: collectionFileSelection }));
203         }
204     };
205
206 export const copyCollectionPartialToSeparateCollections = (fileSelection: CollectionFileSelection, formData: CollectionPartialCopyToSeparateCollectionsFormData) =>
207     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
208         if (fileSelection.collection) {
209             try {
210                 dispatch(startSubmit(COLLECTION_PARTIAL_COPY_TO_SEPARATE_COLLECTIONS));
211                 dispatch(progressIndicatorActions.START_WORKING(COLLECTION_PARTIAL_COPY_TO_SEPARATE_COLLECTIONS));
212
213                 // Copy files
214                 const collections = await Promise.all(fileSelection.selectedPaths.map((path) =>
215                     services.collectionService.copyFiles(
216                         fileSelection.collection.portableDataHash,
217                         [path],
218                         {
219                             name: `File copied from collection ${formData.name}${path}`,
220                             ownerUuid: formData.projectUuid,
221                             uuid: undefined,
222                         },
223                         '/',
224                         false
225                     )
226                 ));
227                 dispatch(updateResources(collections));
228                 dispatch<any>(navigateTo(formData.projectUuid));
229
230                 dispatch(dialogActions.CLOSE_DIALOG({ id: COLLECTION_PARTIAL_COPY_TO_SEPARATE_COLLECTIONS }));
231                 dispatch(snackbarActions.OPEN_SNACKBAR({
232                     message: 'New collections created.',
233                     hideDuration: 2000,
234                     kind: SnackbarKind.SUCCESS
235                 }));
236             } catch (e) {
237                 const error = getCommonResourceServiceError(e);
238                 if (error === CommonResourceServiceError.UNIQUE_NAME_VIOLATION) {
239                     dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Collection from one or more files already exists', hideDuration: 2000, kind: SnackbarKind.ERROR }));
240                 } else if (error === CommonResourceServiceError.UNKNOWN) {
241                     dispatch(dialogActions.CLOSE_DIALOG({ id: COLLECTION_PARTIAL_COPY_TO_SEPARATE_COLLECTIONS }));
242                     dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Could not create a copy of collection', hideDuration: 2000, kind: SnackbarKind.ERROR }));
243                 } else {
244                     dispatch(dialogActions.CLOSE_DIALOG({ id: COLLECTION_PARTIAL_COPY_TO_SEPARATE_COLLECTIONS }));
245                     dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Collection has been copied but may contain incorrect files.', hideDuration: 2000, kind: SnackbarKind.ERROR }));
246                 }
247             } finally {
248                 dispatch(stopSubmit(COLLECTION_PARTIAL_COPY_TO_SEPARATE_COLLECTIONS));
249                 dispatch(progressIndicatorActions.STOP_WORKING(COLLECTION_PARTIAL_COPY_TO_SEPARATE_COLLECTIONS));
250             }
251         }
252     };