[ARVADOS-WORKBENCH2] updated: 1.4.1-282-gecd7fabb

Git user git at public.arvados.org
Fri Feb 14 19:32:32 UTC 2020


Summary of changes:
 .../collection-panel/collection-panel-action.ts    | 58 ++++++-------
 src/store/details-panel/details-panel-action.ts    | 49 +++++------
 src/store/search-bar/search-bar-actions.test.ts    | 96 ++++++++++++++++++++--
 src/store/search-bar/search-bar-actions.ts         | 39 ++++-----
 .../resource-properties-form/property-chip.tsx     |  2 +-
 .../search-bar-advanced-properties-view.tsx        | 18 +++-
 6 files changed, 174 insertions(+), 88 deletions(-)

       via  ecd7fabba5f2989ec577c3313dcc82c94f924eae (commit)
       via  519cf0aa43e6ac3085974506ff4eb1f0a70156c4 (commit)
       via  104779a402f76678f0f2e903f8771288dda0d006 (commit)
       via  cc2640c6ed0c06cf0b7b0a8cb311f50e50b01865 (commit)
      from  227ffcbd2efe8cccd4a9025344b09632630cff14 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.


commit ecd7fabba5f2989ec577c3313dcc82c94f924eae
Author: Lucas Di Pentima <lucas at di-pentima.com.ar>
Date:   Fri Feb 14 16:31:44 2020 -0300

    15781: Avoids showing duplicate property 'chips' on search editor.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas at di-pentima.com.ar>

diff --git a/src/views-components/search-bar/search-bar-advanced-properties-view.tsx b/src/views-components/search-bar/search-bar-advanced-properties-view.tsx
index 8add4b02..f3509a02 100644
--- a/src/views-components/search-bar/search-bar-advanced-properties-view.tsx
+++ b/src/views-components/search-bar/search-bar-advanced-properties-view.tsx
@@ -20,6 +20,7 @@ import { Chips } from '~/components/chips/chips';
 import { formatPropertyValue } from "~/common/formatters";
 import { Vocabulary } from '~/models/vocabulary';
 import { connectVocabulary } from '../resource-properties-form/property-field-common';
+import * as _ from 'lodash';
 
 type CssRules = 'label' | 'button';
 
@@ -45,7 +46,7 @@ interface SearchBarAdvancedPropertiesViewDataProps {
 
 interface SearchBarAdvancedPropertiesViewActionProps {
     setProps: () => void;
-    setProp: (propertyValues: PropertyValue, properties: PropertyValue[]) => void;
+    addProp: (propertyValues: PropertyValue, properties: PropertyValue[]) => void;
     getAllFields: (propertyValues: PropertyValue[]) => PropertyValue[] | [];
 }
 
@@ -64,7 +65,16 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({
     setProps: (propertyValues: PropertyValue[]) => {
         dispatch<any>(changeAdvancedFormProperty('properties', propertyValues));
     },
-    setProp: (propertyValue: PropertyValue, properties: PropertyValue[]) => {
+    addProp: (propertyValue: PropertyValue, properties: PropertyValue[]) => {
+        // Remove potential duplicates
+        properties = properties.filter(x => ! _.isEqual(
+            {
+                key: x.keyID || x.key,
+                value: x.valueID || x.value
+            }, {
+                key: propertyValue.keyID || propertyValue.key,
+                value: propertyValue.valueID || propertyValue.value
+            }));
         dispatch<any>(changeAdvancedFormProperty(
             'properties',
             [...properties, propertyValue]
@@ -83,7 +93,7 @@ export const SearchBarAdvancedPropertiesView = compose(
     connectVocabulary,
     connect(mapStateToProps, mapDispatchToProps))(
     withStyles(styles)(
-        ({ classes, fields, propertyValues, setProps, setProp, getAllFields, vocabulary }: SearchBarAdvancedPropertiesViewProps) =>
+        ({ classes, fields, propertyValues, setProps, addProp, getAllFields, vocabulary }: SearchBarAdvancedPropertiesViewProps) =>
             <Grid container item xs={12} spacing={16}>
                 <Grid item xs={2} className={classes.label}>Properties</Grid>
                 <Grid item xs={4}>
@@ -93,7 +103,7 @@ export const SearchBarAdvancedPropertiesView = compose(
                     <SearchBarValueField />
                 </Grid>
                 <Grid container item xs={2} justify='flex-end' alignItems="center">
-                    <Button className={classes.button} onClick={() => setProp(propertyValues, getAllFields(fields))}
+                    <Button className={classes.button} onClick={() => addProp(propertyValues, getAllFields(fields))}
                         color="primary"
                         size='small'
                         variant="contained"

commit 519cf0aa43e6ac3085974506ff4eb1f0a70156c4
Author: Lucas Di Pentima <lucas at di-pentima.com.ar>
Date:   Fri Feb 14 15:50:42 2020 -0300

    15781: Fixes property removal on the advanced search editor.
    
    Also, adds some tests ensuring that properties IDs are used when available.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas at di-pentima.com.ar>

diff --git a/src/store/search-bar/search-bar-actions.test.ts b/src/store/search-bar/search-bar-actions.test.ts
index 51a73cc3..68804dfb 100644
--- a/src/store/search-bar/search-bar-actions.test.ts
+++ b/src/store/search-bar/search-bar-actions.test.ts
@@ -59,14 +59,100 @@ describe('search-bar-actions', () => {
                 inTrash: true,
                 dateFrom: '2017-08-01',
                 dateTo: '',
-                properties: [{
-                    key: 'file size',
-                    value: '101mb'
-                }],
+                properties: [
+                    { key: 'file size', value: '101mb' },
+                    { key: 'Species', value: 'Human' },
+                    { key: 'Species', value: 'Canine' },
+                ],
                 saveQuery: false,
                 queryName: ''
             });
-            expect(q).toBe('document pdf type:arvados#collection cluster:c97qx is:trashed from:2017-08-01 has:"file size":"101mb"');
+            expect(q).toBe('document pdf type:arvados#collection cluster:c97qx is:trashed from:2017-08-01 has:"file size":"101mb" has:"Species":"Human" has:"Species":"Canine"');
+        });
+
+        it('should add has:"key":"value" expression to query from same property key', () => {
+            const searchValue = 'document pdf has:"file size":"101mb" has:"Species":"Canine"';
+            const prevData = {
+                searchValue,
+                type: undefined,
+                cluster: undefined,
+                projectUuid: undefined,
+                inTrash: false,
+                dateFrom: '',
+                dateTo: '',
+                properties: [
+                    { key: 'file size', value: '101mb' },
+                    { key: 'Species', value: 'Canine' },
+                ],
+                saveQuery: false,
+                queryName: ''
+            };
+            const currData = {
+                ...prevData,
+                properties: [
+                    { key: 'file size', value: '101mb' },
+                    { key: 'Species', value: 'Canine' },
+                    { key: 'Species', value: 'Human' },
+                ],
+            };
+            const q = getQueryFromAdvancedData(currData, prevData);
+            expect(q).toBe('document pdf has:"file size":"101mb" has:"Species":"Canine" has:"Species":"Human"');
+        });
+
+        it('should add has:"keyID":"valueID" expression to query when necessary', () => {
+            const searchValue = 'document pdf has:"file size":"101mb"';
+            const prevData = {
+                searchValue,
+                type: undefined,
+                cluster: undefined,
+                projectUuid: undefined,
+                inTrash: false,
+                dateFrom: '',
+                dateTo: '',
+                properties: [
+                    { key: 'file size', value: '101mb' },
+                ],
+                saveQuery: false,
+                queryName: ''
+            };
+            const currData = {
+                ...prevData,
+                properties: [
+                    { key: 'file size', value: '101mb' },
+                    { key: 'Species', keyID: 'IDTAGSPECIES', value: 'Human', valueID: 'IDVALHUMAN'},
+                ],
+            };
+            const q = getQueryFromAdvancedData(currData, prevData);
+            expect(q).toBe('document pdf has:"file size":"101mb" has:"IDTAGSPECIES":"IDVALHUMAN"');
+        });
+
+        it('should remove has:"key":"value" expression from query', () => {
+            const searchValue = 'document pdf has:"file size":"101mb" has:"Species":"Human" has:"Species":"Canine"';
+            const prevData = {
+                searchValue,
+                type: undefined,
+                cluster: undefined,
+                projectUuid: undefined,
+                inTrash: false,
+                dateFrom: '',
+                dateTo: '',
+                properties: [
+                    { key: 'file size', value: '101mb' },
+                    { key: 'Species', value: 'Canine' },
+                    { key: 'Species', value: 'Human' },
+                ],
+                saveQuery: false,
+                queryName: ''
+            };
+            const currData = {
+                ...prevData,
+                properties: [
+                    { key: 'file size', value: '101mb' },
+                    { key: 'Species', value: 'Canine' },
+                ],
+            };
+            const q = getQueryFromAdvancedData(currData, prevData);
+            expect(q).toBe('document pdf has:"file size":"101mb" has:"Species":"Canine"');
         });
     });
 });
diff --git a/src/store/search-bar/search-bar-actions.ts b/src/store/search-bar/search-bar-actions.ts
index 794ca8dc..54678b50 100644
--- a/src/store/search-bar/search-bar-actions.ts
+++ b/src/store/search-bar/search-bar-actions.ts
@@ -225,12 +225,12 @@ const searchGroups = (searchValue: string, limit: number) =>
         }
     };
 
-const buildQueryFromKeyMap = (data: any, keyMap: string[][], mode: 'rebuild' | 'reuse') => {
+const buildQueryFromKeyMap = (data: any, keyMap: string[][]) => {
     let value = data.searchValue;
 
     const addRem = (field: string, key: string) => {
         const v = data[key];
-
+        // Remove previous search expression.
         if (data.hasOwnProperty(key)) {
             let pattern: string;
             if (v === false) {
@@ -238,28 +238,23 @@ const buildQueryFromKeyMap = (data: any, keyMap: string[][], mode: 'rebuild' | '
             } else if (key.startsWith('prop-')) {
                 // On properties, only remove key:value duplicates, allowing
                 // multiple properties with the same key.
-                pattern = `${field.replace(':', '\\:\\s*')}\\:\\s*${v}\\s*`;
+                const oldValue = key.slice(5).split(':')[1];
+                pattern = `${field.replace(':', '\\:\\s*')}\\:\\s*${oldValue}\\s*`;
             } else {
                 pattern = `${field.replace(':', '\\:\\s*')}\\:\\s*[\\w|\\#|\\-|\\/]*\\s*`;
             }
             value = value.replace(new RegExp(pattern), '');
         }
-
+        // Re-add it with the current search value.
         if (v) {
             const nv = v === true
                 ? `${field}`
                 : `${field}:${v}`;
-
-            if (mode === 'rebuild') {
-                value = value + ' ' + nv;
-            } else {
-                value = nv + ' ' + value;
-            }
+            // Always append to the end to keep user-entered text at the start.
+            value = value + ' ' + nv;
         }
     };
-
     keyMap.forEach(km => addRem(km[0], km[1]));
-
     return value;
 };
 
@@ -276,7 +271,9 @@ export const getQueryFromAdvancedData = (data: SearchBarAdvancedFormData, prevDa
             dateFrom: data.dateFrom,
             dateTo: data.dateTo,
         };
-        (data.properties || []).forEach(p => fo[`prop-"${p.keyID || p.key}"`] = `"${p.valueID || p.value}"`);
+        (data.properties || []).forEach(p =>
+            fo[`prop-"${p.keyID || p.key}":"${p.valueID || p.value}"`] = `"${p.valueID || p.value}"`
+            );
         return fo;
     };
 
@@ -289,17 +286,13 @@ export const getQueryFromAdvancedData = (data: SearchBarAdvancedFormData, prevDa
         ['to', 'dateTo']
     ];
     _.union(data.properties, prevData ? prevData.properties : [])
-        .forEach(p => keyMap.push([`has:"${p.keyID || p.key}"`, `prop-"${p.keyID || p.key}"`]));
+        .forEach(p => keyMap.push(
+            [`has:"${p.keyID || p.key}"`, `prop-"${p.keyID || p.key}":"${p.valueID || p.value}"`]
+        ));
 
-    if (prevData) {
-        const obj = getModifiedKeysValues(flatData(data), flatData(prevData));
-        value = buildQueryFromKeyMap({
-            searchValue: data.searchValue,
-            ...obj
-        } as SearchBarAdvancedFormData, keyMap, "reuse");
-    } else {
-        value = buildQueryFromKeyMap(flatData(data), keyMap, "rebuild");
-    }
+    const modified = getModifiedKeysValues(flatData(data), prevData ? flatData(prevData):{});
+    value = buildQueryFromKeyMap(
+        {searchValue: data.searchValue, ...modified} as SearchBarAdvancedFormData, keyMap);
 
     value = value.trim();
     return value;

commit 104779a402f76678f0f2e903f8771288dda0d006
Author: Lucas Di Pentima <lucas at di-pentima.com.ar>
Date:   Thu Feb 13 13:38:40 2020 -0300

    15781: Fixes project tag add/delete error handling.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas at di-pentima.com.ar>

diff --git a/src/store/details-panel/details-panel-action.ts b/src/store/details-panel/details-panel-action.ts
index e0d72017..c5d472ad 100644
--- a/src/store/details-panel/details-panel-action.ts
+++ b/src/store/details-panel/details-panel-action.ts
@@ -41,16 +41,19 @@ export const deleteProjectProperty = (key: string, value: string) =>
     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
         const { detailsPanel, resources } = getState();
         const project = getResource(detailsPanel.resourceUuid)(resources) as ProjectResource;
+        if (!project) { return; }
+
+        const properties = Object.assign({}, project.properties);
+
         try {
-            if (project) {
-                project.properties = deleteProperty(project.properties, key, value);
-                const updatedProject = await services.projectService.update(project.uuid, { properties: project.properties });
-                dispatch(resourcesActions.SET_RESOURCES([updatedProject]));
-                dispatch(snackbarActions.OPEN_SNACKBAR({ message: "Property has been successfully deleted.", hideDuration: 2000, kind: SnackbarKind.SUCCESS }));
-            }
+            const updatedProject = await services.projectService.update(
+                project.uuid, {
+                    properties: deleteProperty(properties, key, value),
+                });
+            dispatch(resourcesActions.SET_RESOURCES([updatedProject]));
+            dispatch(snackbarActions.OPEN_SNACKBAR({ message: "Property has been successfully deleted.", hideDuration: 2000, kind: SnackbarKind.SUCCESS }));
         } catch (e) {
-            dispatch(dialogActions.CLOSE_DIALOG({ id: PROJECT_PROPERTIES_FORM_NAME }));
-            throw new Error('Could not remove property from the project.');
+            dispatch(snackbarActions.OPEN_SNACKBAR({ message: e.errors[0], hideDuration: 2000, kind: SnackbarKind.ERROR }));
         }
     };
 
@@ -58,25 +61,23 @@ export const createProjectProperty = (data: TagProperty) =>
     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
         const { detailsPanel, resources } = getState();
         const project = getResource(detailsPanel.resourceUuid)(resources) as ProjectResource;
+        if (!project) { return; }
+
         dispatch(startSubmit(PROJECT_PROPERTIES_FORM_NAME));
         try {
-            if (project) {
-                const key = data.keyID || data.key;
-                const value = data.valueID || data.value;
-                project.properties = addProperty(project.properties, key, value);
-                const updatedProject = await services.projectService.update(
-                    project.uuid, {
-                        properties: {...project.properties}
-                    }
-                );
-                dispatch(resourcesActions.SET_RESOURCES([updatedProject]));
-                dispatch(snackbarActions.OPEN_SNACKBAR({ message: "Property has been successfully added.", hideDuration: 2000, kind: SnackbarKind.SUCCESS }));
-                dispatch(stopSubmit(PROJECT_PROPERTIES_FORM_NAME));
-            }
-            return;
+            const key = data.keyID || data.key;
+            const value = data.valueID || data.value;
+            const properties = Object.assign({}, project.properties);
+            const updatedProject = await services.projectService.update(
+                project.uuid, {
+                    properties: addProperty(properties, key, value),
+                }
+            );
+            dispatch(resourcesActions.SET_RESOURCES([updatedProject]));
+            dispatch(snackbarActions.OPEN_SNACKBAR({ message: "Property has been successfully added.", hideDuration: 2000, kind: SnackbarKind.SUCCESS }));
+            dispatch(stopSubmit(PROJECT_PROPERTIES_FORM_NAME));
         } catch (e) {
-            dispatch(dialogActions.CLOSE_DIALOG({ id: PROJECT_PROPERTIES_FORM_NAME }));
-            throw new Error('Could not add property to the project.');
+            dispatch(snackbarActions.OPEN_SNACKBAR({ message: e.errors[0], hideDuration: 2000, kind: SnackbarKind.ERROR }));
         }
     };
 export const toggleDetailsPanel = () => (dispatch: Dispatch) => {

commit cc2640c6ed0c06cf0b7b0a8cb311f50e50b01865
Author: Lucas Di Pentima <lucas at di-pentima.com.ar>
Date:   Thu Feb 13 12:04:56 2020 -0300

    15781: Fixes collection tag add/delete error handling.
    
    Tags (chips) were added/removed to the UI even when the update call failed.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas at di-pentima.com.ar>

diff --git a/src/store/collection-panel/collection-panel-action.ts b/src/store/collection-panel/collection-panel-action.ts
index fee5bcd6..9922d8b5 100644
--- a/src/store/collection-panel/collection-panel-action.ts
+++ b/src/store/collection-panel/collection-panel-action.ts
@@ -16,7 +16,7 @@ import { unionize, ofType, UnionOf } from '~/common/unionize';
 import { SnackbarKind } from '~/store/snackbar/snackbar-actions';
 import { navigateTo } from '~/store/navigation/navigation-action';
 import { loadDetailsPanel } from '~/store/details-panel/details-panel-action';
-import { deleteProperty, addProperty } from "~/lib/resource-properties";
+import { addProperty, deleteProperty } from "~/lib/resource-properties";
 
 export const collectionPanelActions = unionize({
     SET_COLLECTION: ofType<CollectionResource>(),
@@ -43,23 +43,21 @@ export const loadCollectionPanel = (uuid: string) =>
 export const createCollectionTag = (data: TagProperty) =>
     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
         const item = getState().collectionPanel.item;
-        const uuid = item ? item.uuid : '';
+        if (!item) { return; }
+
+        const properties = Object.assign({}, item.properties);
         try {
-            if (item) {
-                const key = data.keyID || data.key;
-                const value = data.valueID || data.value;
-                item.properties = addProperty(item.properties, key, value);
-                const updatedCollection = await services.collectionService.update(
-                    uuid, {
-                        properties: {...item.properties}
-                    }
-                );
-                item.properties = updatedCollection.properties;
-                dispatch(resourcesActions.SET_RESOURCES([updatedCollection]));
-                dispatch(snackbarActions.OPEN_SNACKBAR({ message: "Tag has been successfully added.", hideDuration: 2000, kind: SnackbarKind.SUCCESS }));
-                return updatedCollection;
-            }
-            return;
+            const key = data.keyID || data.key;
+            const value = data.valueID || data.value;
+            const updatedCollection = await services.collectionService.update(
+                item.uuid, {
+                    properties: addProperty(properties, key, value)
+                }
+            );
+            dispatch(collectionPanelActions.SET_COLLECTION(updatedCollection));
+            dispatch(resourcesActions.SET_RESOURCES([updatedCollection]));
+            dispatch(snackbarActions.OPEN_SNACKBAR({ message: "Tag has been successfully added.", hideDuration: 2000, kind: SnackbarKind.SUCCESS }));
+            return updatedCollection;
         } catch (e) {
             dispatch(snackbarActions.OPEN_SNACKBAR({ message: e.errors[0], hideDuration: 2000, kind: SnackbarKind.ERROR }));
             return;
@@ -79,21 +77,19 @@ export const navigateToProcess = (uuid: string) =>
 export const deleteCollectionTag = (key: string, value: string) =>
     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
         const item = getState().collectionPanel.item;
-        const uuid = item ? item.uuid : '';
-        try {
-            if (item) {
-                item.properties = deleteProperty(item.properties, key, value);
+        if (!item) { return; }
 
-                const updatedCollection = await services.collectionService.update(
-                    uuid, {
-                        properties: {...item.properties}
-                    }
-                );
-                dispatch(resourcesActions.SET_RESOURCES([updatedCollection]));
-                dispatch(snackbarActions.OPEN_SNACKBAR({ message: "Tag has been successfully deleted.", hideDuration: 2000, kind: SnackbarKind.SUCCESS }));
-                return updatedCollection;
-            }
-            return;
+        const properties = Object.assign({}, item.properties);
+        try {
+            const updatedCollection = await services.collectionService.update(
+                item.uuid, {
+                    properties: deleteProperty(properties, key, value)
+                }
+            );
+            dispatch(collectionPanelActions.SET_COLLECTION(updatedCollection));
+            dispatch(resourcesActions.SET_RESOURCES([updatedCollection]));
+            dispatch(snackbarActions.OPEN_SNACKBAR({ message: "Tag has been successfully deleted.", hideDuration: 2000, kind: SnackbarKind.SUCCESS }));
+            return updatedCollection;
         } catch (e) {
             dispatch(snackbarActions.OPEN_SNACKBAR({ message: e.errors[0], hideDuration: 2000, kind: SnackbarKind.ERROR }));
             return;
diff --git a/src/views-components/resource-properties-form/property-chip.tsx b/src/views-components/resource-properties-form/property-chip.tsx
index f25deb70..1fba8a40 100644
--- a/src/views-components/resource-properties-form/property-chip.tsx
+++ b/src/views-components/resource-properties-form/property-chip.tsx
@@ -53,6 +53,6 @@ export const PropertyChipComponent = connect(mapStateToProps, mapDispatchToProps
 
 export const getPropertyChip = (k:string, v:string, handleDelete:any, className:string) =>
     <PropertyChipComponent
-        key={k} className={className}
+        key={`${k}-${v}`} className={className}
         onDelete={handleDelete}
         propKey={k} propValue={v} />;

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


hooks/post-receive
-- 




More information about the arvados-commits mailing list