15814: Now handles secret inputs properly.
[arvados.git] / services / workbench2 / src / store / resources / resources-actions.ts
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 import { unionize, ofType, UnionOf } from 'common/unionize';
6 import { extractUuidKind, Resource, ResourceWithProperties } from 'models/resource';
7 import { Dispatch } from 'redux';
8 import { RootState } from 'store/store';
9 import { ServiceRepository } from 'services/services';
10 import { getResourceService } from 'services/services';
11 import { addProperty, deleteProperty } from 'lib/resource-properties';
12 import { snackbarActions, SnackbarKind } from 'store/snackbar/snackbar-actions';
13 import { getResource } from './resources';
14 import { TagProperty } from 'models/tag';
15 import { change, formValueSelector } from 'redux-form';
16 import { ResourcePropertiesFormData } from 'views-components/resource-properties-form/resource-properties-form';
17
18 export type ResourceWithDescription = Resource & { description?: string }
19
20 export const resourcesActions = unionize({
21     SET_RESOURCES: ofType<ResourceWithDescription[] >(),
22     DELETE_RESOURCES: ofType<string[]>()
23 });
24
25 export type ResourcesAction = UnionOf<typeof resourcesActions>;
26
27 export const updateResources = (resources: Resource[]) => resourcesActions.SET_RESOURCES(resources);
28
29 export const deleteResources = (resources: string[]) => resourcesActions.DELETE_RESOURCES(resources);
30
31 export const loadResource = (uuid: string, showErrors?: boolean) =>
32     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
33         try {
34             const kind = extractUuidKind(uuid);
35             const service = getResourceService(kind)(services);
36             if (service) {
37                 const resource = await service.get(uuid, showErrors);
38                 dispatch<any>(updateResources([resource]));
39                 return resource;
40             }
41         } catch {}
42         return undefined;
43     };
44
45 export const deleteResourceProperty = (uuid: string, key: string, value: string) =>
46     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
47         const { resources } = getState();
48
49         const rsc = getResource(uuid)(resources) as ResourceWithProperties;
50         if (!rsc) { return; }
51
52         const kind = extractUuidKind(uuid);
53         const service = getResourceService(kind)(services);
54         if (!service) { return; }
55
56         const properties = Object.assign({}, rsc.properties);
57
58         try {
59             let updatedRsc = await service.update(
60                 uuid, {
61                     properties: deleteProperty(properties, key, value),
62                 });
63             updatedRsc = {...rsc, ...updatedRsc};
64             dispatch<any>(updateResources([updatedRsc]));
65             dispatch(snackbarActions.OPEN_SNACKBAR({ message: "Property has been successfully deleted.", hideDuration: 2000, kind: SnackbarKind.SUCCESS }));
66         } catch (e) {
67             dispatch(snackbarActions.OPEN_SNACKBAR({ message: e.errors[0], hideDuration: 2000, kind: SnackbarKind.ERROR }));
68         }
69     };
70
71 export const createResourceProperty = (data: TagProperty) =>
72     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
73         const { uuid } = data;
74         const { resources } = getState();
75
76         const rsc = getResource(uuid)(resources) as ResourceWithProperties;
77         if (!rsc) { return; }
78
79         const kind = extractUuidKind(uuid);
80         const service = getResourceService(kind)(services);
81         if (!service) { return; }
82
83         try {
84             const key = data.keyID || data.key;
85             const value = data.valueID || data.value;
86             const properties = Object.assign({}, rsc.properties);
87             let updatedRsc = await service.update(
88                 rsc.uuid, {
89                     properties: addProperty(properties, key, value),
90                 }
91             );
92             updatedRsc = {...rsc, ...updatedRsc};
93             dispatch<any>(updateResources([updatedRsc]));
94             dispatch(snackbarActions.OPEN_SNACKBAR({ message: "Property has been successfully added.", hideDuration: 2000, kind: SnackbarKind.SUCCESS }));
95         } catch (e) {
96             const errorMsg = e.errors && e.errors.length > 0 ? e.errors[0] : "Error while adding property";
97             dispatch(snackbarActions.OPEN_SNACKBAR({ message: errorMsg, hideDuration: 2000, kind: SnackbarKind.ERROR }));
98         }
99     };
100
101 export const addPropertyToResourceForm = (data: ResourcePropertiesFormData, formName: string) =>
102     (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
103         const properties = { ...formValueSelector(formName)(getState(), 'properties') };
104         const vocabulary = getState().properties.vocabulary?.tags;
105         const dataTags = getTagsIfExist(data.key, data.value, vocabulary);
106         const key = data.keyID || dataTags.key || data.key;
107         const value =  data.valueID || dataTags.value || data.value;
108         dispatch(change(
109             formName,
110             'properties',
111             addProperty(properties, key, value)));
112     };
113
114 export const removePropertyFromResourceForm = (key: string, value: string, formName: string) =>
115     (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
116         const properties = { ...formValueSelector(formName)(getState(), 'properties') };
117         dispatch(change(
118             formName,
119             'properties',
120             deleteProperty(properties, key, value)));
121     };
122
123
124 const getTagsIfExist = (dataKey: string, dataValue: string, vocabulary: any) => {
125     let k, v;
126     for (const key in vocabulary) {
127         if (vocabulary[key].labels.find(l=>l.label === dataKey)) {
128             k = key;
129             const { values } = vocabulary[key];
130             for (const val in values) {
131                 if (values[val].labels.find(l=>l.label === dataValue)) {
132                     v = val;
133                     break;
134                 }
135             }
136         }
137     }
138     return { key: k, value: v };
139 };