[ARVADOS-WORKBENCH2] created: 2.2.1-32-ge5dc88dd

Git user git at public.arvados.org
Wed Jul 14 23:10:59 UTC 2021


        at  e5dc88dd1a6c54610d92854d527e5048543d93ec (commit)


commit e5dc88dd1a6c54610d92854d527e5048543d93ec
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date:   Wed Jul 14 18:04:22 2021 -0300

    17573: Adds storage selection checkboxes to edit dialog.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>

diff --git a/src/store/collections/collection-update-actions.ts b/src/store/collections/collection-update-actions.ts
index 3f3b662f..a9077cfb 100644
--- a/src/store/collections/collection-update-actions.ts
+++ b/src/store/collections/collection-update-actions.ts
@@ -19,6 +19,7 @@ export interface CollectionUpdateFormDialogData {
     uuid: string;
     name: string;
     description?: string;
+    storageClassesDesired?: string[];
 }
 
 export const COLLECTION_UPDATE_FORM_NAME = 'collectionUpdateFormName';
@@ -37,6 +38,7 @@ export const updateCollection = (collection: CollectionUpdateFormDialogData) =>
 
         services.collectionService.update(uuid, {
             name: collection.name,
+            storageClassesDesired: collection.storageClassesDesired,
             description: collection.description }
         ).then(updatedCollection => {
             dispatch(collectionPanelActions.LOAD_COLLECTION_SUCCESS({ item: updatedCollection as CollectionResource }));
diff --git a/src/store/context-menu/context-menu-actions.ts b/src/store/context-menu/context-menu-actions.ts
index 038b31e2..556d83b4 100644
--- a/src/store/context-menu/context-menu-actions.ts
+++ b/src/store/context-menu/context-menu-actions.ts
@@ -39,6 +39,7 @@ export type ContextMenuResource = {
     isEditable?: boolean;
     outputUuid?: string;
     workflowUuid?: string;
+    storageClassesDesired?: string[];
 };
 
 export const isKeyboardClick = (event: React.MouseEvent<HTMLElement>) => event.nativeEvent.detail === 0;
diff --git a/src/views-components/details-panel/collection-details.tsx b/src/views-components/details-panel/collection-details.tsx
index 0e747fed..c61b3340 100644
--- a/src/views-components/details-panel/collection-details.tsx
+++ b/src/views-components/details-panel/collection-details.tsx
@@ -87,6 +87,8 @@ const mapDispatchToProps = () =>
                 dispatch<any>(openContextMenu(event, {
                     name: collection.name,
                     uuid: collection.uuid,
+                    description: collection.description,
+                    storageClassesDesired: collection.storageClassesDesired,
                     ownerUuid: collection.ownerUuid,
                     isTrashed: collection.isTrashed,
                     kind: collection.kind,
diff --git a/src/views-components/dialog-forms/update-collection-dialog.ts b/src/views-components/dialog-forms/update-collection-dialog.ts
index 36e5cc39..e5d52f0b 100644
--- a/src/views-components/dialog-forms/update-collection-dialog.ts
+++ b/src/views-components/dialog-forms/update-collection-dialog.ts
@@ -2,19 +2,23 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { compose } from "redux";
+import { compose, Dispatch } from "redux";
 import { reduxForm } from 'redux-form';
 import { withDialog } from "store/dialog/with-dialog";
 import { DialogCollectionUpdate } from 'views-components/dialog-update/dialog-collection-update';
-import { COLLECTION_UPDATE_FORM_NAME, CollectionUpdateFormDialogData, updateCollection } from 'store/collections/collection-update-actions';
+import {
+    COLLECTION_UPDATE_FORM_NAME,
+    CollectionUpdateFormDialogData,
+    updateCollection
+} from 'store/collections/collection-update-actions';
 
 export const UpdateCollectionDialog = compose(
     withDialog(COLLECTION_UPDATE_FORM_NAME),
     reduxForm<CollectionUpdateFormDialogData>({
         touchOnChange: true,
         form: COLLECTION_UPDATE_FORM_NAME,
-        onSubmit: (data, dispatch) => {
-            dispatch(updateCollection(data));
+        onSubmit: (data: CollectionUpdateFormDialogData, dispatch: Dispatch) => {
+            dispatch<any>(updateCollection(data));
         }
     })
 )(DialogCollectionUpdate);
\ No newline at end of file
diff --git a/src/views-components/dialog-update/dialog-collection-update.tsx b/src/views-components/dialog-update/dialog-collection-update.tsx
index c30ceaac..cce64d27 100644
--- a/src/views-components/dialog-update/dialog-collection-update.tsx
+++ b/src/views-components/dialog-update/dialog-collection-update.tsx
@@ -7,7 +7,11 @@ import { InjectedFormProps } from 'redux-form';
 import { WithDialogProps } from 'store/dialog/with-dialog';
 import { CollectionUpdateFormDialogData } from 'store/collections/collection-update-actions';
 import { FormDialog } from 'components/form-dialog/form-dialog';
-import { CollectionNameField, CollectionDescriptionField } from 'views-components/form-fields/collection-form-fields';
+import {
+    CollectionNameField,
+    CollectionDescriptionField,
+    CollectionStorageClassesField
+} from 'views-components/form-fields/collection-form-fields';
 
 type DialogCollectionProps = WithDialogProps<{}> & InjectedFormProps<CollectionUpdateFormDialogData>;
 
@@ -22,4 +26,5 @@ export const DialogCollectionUpdate = (props: DialogCollectionProps) =>
 const CollectionEditFields = () => <span>
     <CollectionNameField />
     <CollectionDescriptionField />
+    <CollectionStorageClassesField />
 </span>;
diff --git a/src/views-components/form-fields/collection-form-fields.tsx b/src/views-components/form-fields/collection-form-fields.tsx
index b882d684..db18f76d 100644
--- a/src/views-components/form-fields/collection-form-fields.tsx
+++ b/src/views-components/form-fields/collection-form-fields.tsx
@@ -13,6 +13,8 @@ import { ProjectTreePickerField, CollectionTreePickerField } from "views-compone
 import { PickerIdProp } from 'store/tree-picker/picker-id';
 import { connect } from "react-redux";
 import { RootState } from "store/store";
+import { MultiCheckboxField } from "components/checkbox-field/checkbox-field";
+import { getStorageClasses } from "common/config";
 
 interface CollectionNameFieldProps {
     validate: Validator[];
@@ -55,3 +57,23 @@ export const CollectionPickerField = (props: PickerIdProp) =>
         pickerId={props.pickerId}
         component={CollectionTreePickerField}
         validate={COLLECTION_PROJECT_VALIDATION} />;
+
+interface StorageClassesProps {
+    items: string[];
+}
+
+export const CollectionStorageClassesField = connect(
+    (state: RootState) => {
+        return {
+            items: getStorageClasses(state.auth.config)
+        };
+    })(
+    (props: StorageClassesProps) =>
+        <Field
+            name='storageClassesDesired'
+            label='Storage classes'
+            minSelection={1}
+            rowLayout={true}
+            helperText='At least one class should be selected'
+            component={MultiCheckboxField}
+            items={props.items} />);
\ No newline at end of file
diff --git a/src/views/collection-content-address-panel/collection-content-address-panel.tsx b/src/views/collection-content-address-panel/collection-content-address-panel.tsx
index 13e131b0..88638085 100644
--- a/src/views/collection-content-address-panel/collection-content-address-panel.tsx
+++ b/src/views/collection-content-address-panel/collection-content-address-panel.tsx
@@ -34,6 +34,9 @@ import {
     ResourceLastModifiedDate,
     ResourceStatus
 } from 'views-components/data-explorer/renderers';
+import { getResource, ResourcesState } from 'store/resources/resources';
+import { RootState } from 'store/store';
+import { CollectionResource } from 'models/collection';
 
 type CssRules = 'backLink' | 'backIcon' | 'card' | 'title' | 'iconHeader' | 'link';
 
@@ -110,18 +113,29 @@ export const collectionContentAddressPanelColumns: DataColumns<string> = [
     }
 ];
 
-export interface CollectionContentAddressPanelActionProps {
-    onContextMenu: (event: React.MouseEvent<any>, uuid: string) => void;
+interface CollectionContentAddressPanelActionProps {
+    onContextMenu: (resources: ResourcesState) => (event: React.MouseEvent<any>, uuid: string) => void;
     onItemClick: (item: string) => void;
     onItemDoubleClick: (item: string) => void;
 }
 
+interface CollectionContentAddressPanelDataProps {
+    resources: ResourcesState;
+}
+
+const mapStateToProps = ({ resources }: RootState): CollectionContentAddressPanelDataProps => ({
+    resources
+})
+
 const mapDispatchToProps = (dispatch: Dispatch): CollectionContentAddressPanelActionProps => ({
-    onContextMenu: (event, resourceUuid) => {
+    onContextMenu: (resources: ResourcesState) => (event, resourceUuid) => {
+        const resource = getResource<CollectionResource>(resourceUuid)(resources);
         const kind = dispatch<any>(resourceUuidToContextMenuKind(resourceUuid));
         if (kind) {
             dispatch<any>(openContextMenu(event, {
-                name: '',
+                name: resource ? resource.name : '',
+                description: resource ? resource.description : '',
+                storageClassesDesired: resource ? resource.storageClassesDesired : [],
                 uuid: resourceUuid,
                 ownerUuid: '',
                 kind: ResourceKind.NONE,
@@ -145,8 +159,8 @@ interface CollectionContentAddressDataProps {
 }
 
 export const CollectionsContentAddressPanel = withStyles(styles)(
-    connect(null, mapDispatchToProps)(
-        class extends React.Component<CollectionContentAddressPanelActionProps & CollectionContentAddressDataProps & WithStyles<CssRules>> {
+    connect(mapStateToProps, mapDispatchToProps)(
+        class extends React.Component<CollectionContentAddressPanelActionProps & CollectionContentAddressPanelDataProps & CollectionContentAddressDataProps & WithStyles<CssRules>> {
             render() {
                 return <Grid item xs={12}>
                     <Button
@@ -160,7 +174,7 @@ export const CollectionsContentAddressPanel = withStyles(styles)(
                         hideSearchInput
                         onRowClick={this.props.onItemClick}
                         onRowDoubleClick={this.props.onItemDoubleClick}
-                        onContextMenu={this.props.onContextMenu}
+                        onContextMenu={this.props.onContextMenu(this.props.resources)}
                         contextMenuColumn={true}
                         title={`Content address: ${this.props.match.params.id}`}
                         dataTableDefaultView={
diff --git a/src/views/favorite-panel/favorite-panel.tsx b/src/views/favorite-panel/favorite-panel.tsx
index 82336b4a..404baeb9 100644
--- a/src/views/favorite-panel/favorite-panel.tsx
+++ b/src/views/favorite-panel/favorite-panel.tsx
@@ -39,6 +39,7 @@ import { GroupContentsResource } from 'services/groups-service/groups-service';
 import { GroupClass, GroupResource } from 'models/group';
 import { getProperty } from 'store/properties/properties';
 import { PROJECT_PANEL_CURRENT_UUID } from 'store/project-panel/project-panel-action';
+import { CollectionResource } from 'models/collection';
 
 type CssRules = "toolbar" | "button";
 
@@ -151,7 +152,7 @@ export const FavoritePanel = withStyles(styles)(
 
                 const menuKind = this.props.dispatch<any>(resourceUuidToContextMenuKind(resourceUuid, readonly));
 
-                if (menuKind&& resource) {
+                if (menuKind && resource) {
                     this.props.dispatch<any>(openContextMenu(event, {
                         name: resource.name,
                         uuid: resource.uuid,
@@ -160,6 +161,7 @@ export const FavoritePanel = withStyles(styles)(
                         kind: resource.kind,
                         menuKind,
                         description: resource.description,
+                        storageClassesDesired: (resource as CollectionResource).storageClassesDesired,
                     }));
                 }
                 this.props.dispatch<any>(loadDetailsPanel(resourceUuid));
diff --git a/src/views/project-panel/project-panel.tsx b/src/views/project-panel/project-panel.tsx
index ed8a706a..67264511 100644
--- a/src/views/project-panel/project-panel.tsx
+++ b/src/views/project-panel/project-panel.tsx
@@ -45,6 +45,7 @@ import {
 } from 'store/resource-type-filters/resource-type-filters';
 import { GroupContentsResource } from 'services/groups-service/groups-service';
 import { GroupClass, GroupResource } from 'models/group';
+import { CollectionResource } from 'models/collection';
 
 type CssRules = 'root' | "button";
 
@@ -185,6 +186,7 @@ export const ProjectPanel = withStyles(styles)(
                         kind: resource.kind,
                         menuKind,
                         description: resource.description,
+                        storageClassesDesired: (resource as CollectionResource).storageClassesDesired,
                     }));
                 }
                 this.props.dispatch<any>(loadDetailsPanel(resourceUuid));
diff --git a/src/views/public-favorites-panel/public-favorites-panel.tsx b/src/views/public-favorites-panel/public-favorites-panel.tsx
index 1b7185de..ee09654a 100644
--- a/src/views/public-favorites-panel/public-favorites-panel.tsx
+++ b/src/views/public-favorites-panel/public-favorites-panel.tsx
@@ -37,6 +37,7 @@ import { PUBLIC_FAVORITE_PANEL_ID } from 'store/public-favorites-panel/public-fa
 import { PublicFavoritesState } from 'store/public-favorites/public-favorites-reducer';
 import { getResource, ResourcesState } from 'store/resources/resources';
 import { GroupContentsResource } from 'services/groups-service/groups-service';
+import { CollectionResource } from 'models/collection';
 
 type CssRules = "toolbar" | "button";
 
@@ -134,6 +135,7 @@ const mapDispatchToProps = (dispatch: Dispatch): PublicFavoritePanelActionProps
             dispatch<any>(openContextMenu(event, {
                 name: resource.name,
                 description: resource.description,
+                storageClassesDesired: (resource as CollectionResource).storageClassesDesired,
                 uuid: resourceUuid,
                 ownerUuid: '',
                 kind: ResourceKind.NONE,

commit ad956f33b83e55aadca7189d5352940aaeadd659
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date:   Wed Jul 14 17:55:15 2021 -0300

    17573: Adds redux-form controlled multi-checkbox selection component.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>

diff --git a/src/components/checkbox-field/checkbox-field.tsx b/src/components/checkbox-field/checkbox-field.tsx
index 2b2a8a03..bfa3714a 100644
--- a/src/components/checkbox-field/checkbox-field.tsx
+++ b/src/components/checkbox-field/checkbox-field.tsx
@@ -4,7 +4,14 @@
 
 import React from 'react';
 import { WrappedFieldProps } from 'redux-form';
-import { FormControlLabel, Checkbox } from '@material-ui/core';
+import {
+    FormControlLabel,
+    Checkbox,
+    FormControl,
+    FormGroup,
+    FormLabel,
+    FormHelperText
+} from '@material-ui/core';
 
 export const CheckboxField = (props: WrappedFieldProps & { label?: string }) =>
     <FormControlLabel
@@ -15,5 +22,46 @@ export const CheckboxField = (props: WrappedFieldProps & { label?: string }) =>
                 disabled={props.meta.submitting}
                 color="primary" />
         }
-        label={props.label} 
-    />;
\ No newline at end of file
+        label={props.label}
+    />;
+
+type MultiCheckboxFieldProps = {
+    items: string[];
+    label?: string;
+    minSelection?: number;
+    maxSelection?: number;
+    helperText?: string;
+    rowLayout?: boolean;
+}
+
+export const MultiCheckboxField = (props: WrappedFieldProps & MultiCheckboxFieldProps) => {
+    const isValid = (items: string[]) => (items.length >= (props.minSelection || 0)) &&
+        (items.length <= (props.maxSelection || items.length));
+    return <FormControl error={!isValid(props.input.value)}>
+        <FormLabel component='label'>{props.label}</FormLabel>
+        <FormGroup row={props.rowLayout}>
+        { props.items.map((item, idx) =>
+            <FormControlLabel
+                control={
+                    <Checkbox
+                        key={idx}
+                        name={`${props.input.name}[${idx}]`}
+                        value={item}
+                        checked={props.input.value.indexOf(item) !== -1}
+                        onChange={e => {
+                            const newValue = [...props.input.value];
+                            if (e.target.checked) {
+                                newValue.push(item);
+                            } else {
+                                newValue.splice(newValue.indexOf(item), 1);
+                            }
+                            if (!isValid(newValue)) { return; }
+                            return props.input.onChange(newValue);
+                        }}
+                        disabled={props.meta.submitting}
+                        color="primary" />
+                }
+                label={item} />) }
+        </FormGroup>
+        <FormHelperText>{props.helperText}</FormHelperText>
+    </FormControl> };
\ No newline at end of file

commit 40f03448780c8b22cba93d4eff9ca976cea45abf
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date:   Wed Jul 14 17:52:54 2021 -0300

    17573: Adds storage classes information to the collection panel.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>

diff --git a/src/views/collection-panel/collection-panel.tsx b/src/views/collection-panel/collection-panel.tsx
index 94a5af33..4270cbbd 100644
--- a/src/views/collection-panel/collection-panel.tsx
+++ b/src/views/collection-panel/collection-panel.tsx
@@ -237,13 +237,14 @@ export const CollectionPanel = withStyles(styles)(
             }
 
             handleContextMenu = (event: React.MouseEvent<any>) => {
-                const { uuid, ownerUuid, name, description, kind } = this.props.item;
+                const { uuid, ownerUuid, name, description, kind, storageClassesDesired } = this.props.item;
                 const menuKind = this.props.dispatch<any>(resourceUuidToContextMenuKind(uuid));
                 const resource = {
                     uuid,
                     ownerUuid,
                     name,
                     description,
+                    storageClassesDesired,
                     kind,
                     menuKind,
                 };
@@ -341,5 +342,9 @@ export const CollectionDetailsAttributes = (props: { item: CollectionResource, t
             <DetailsAttribute classLabel={classes.label} classValue={classes.value}
                 label='Content size' value={formatFileSize(item.fileSizeTotal)} />
         </Grid>
+        <Grid item xs={12} md={mdSize}>
+            <DetailsAttribute classLabel={classes.label} classValue={classes.value}
+                label='Storage classes' value={item.storageClassesDesired.join(', ')} />
+        </Grid>
     </Grid>;
 };

commit 3556e5483957f8479c5747e36c626e91a655cc21
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date:   Thu Jul 8 19:18:06 2021 -0300

    17573: Adds Volumes config support.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>

diff --git a/src/common/config.ts b/src/common/config.ts
index f3d06840..8a85ae00 100644
--- a/src/common/config.ts
+++ b/src/common/config.ts
@@ -91,6 +91,13 @@ export interface ClusterConfigJSON {
             }
         }
     };
+    Volumes: {
+        [key: string]: {
+            StorageClasses: {
+                [key: string]: boolean;
+            }
+        }
+    };
 }
 
 export class Config {
@@ -130,6 +137,19 @@ export const buildConfig = (clusterConfig: ClusterConfigJSON): Config => {
     return config;
 };
 
+export const getStorageClasses = (config: Config): string[] => {
+    const classes: Set<string> = new Set();
+    const volumes = config.clusterConfig.Volumes;
+    Object.keys(volumes).forEach(v => {
+        Object.keys(volumes[v].StorageClasses).forEach(sc => {
+            if (volumes[v].StorageClasses[sc]) {
+                classes.add(sc);
+            }
+        });
+    });
+    return Array.from(classes);
+};
+
 const getApiRevision = async (apiUrl: string) => {
     try {
         const dd = (await Axios.get<any>(`${apiUrl}/${DISCOVERY_DOC_PATH}`)).data;
@@ -252,6 +272,7 @@ export const mockClusterConfigJSON = (config: Partial<ClusterConfigJSON>): Clust
     Collections: {
         ForwardSlashNameSubstitution: "",
     },
+    Volumes: {},
     ...config
 });
 

-----------------------------------------------------------------------


hooks/post-receive
-- 




More information about the arvados-commits mailing list