15781: Fixes property removal on the advanced search editor.
[arvados-workbench2.git] / src / store / search-bar / search-bar-actions.ts
index b91dc9d10fdbc1c8a6b0a063a30b9a20fbe96418..54678b504b52c0f22ffd83c77071033b74e70821 100644 (file)
@@ -212,7 +212,7 @@ const searchGroups = (searchValue: string, limit: number) =>
             const { cluster: clusterId } = getAdvancedDataFromQuery(searchValue);
             const sessions = getSearchSessions(clusterId, getState().auth.sessions);
             const lists: ListResults<GroupContentsResource>[] = await Promise.all(sessions.map(session => {
-                const filters = queryToFilters(searchValue);
+                const filters = queryToFilters(searchValue, session.apiRevision);
                 return services.groupsService.contents('', {
                     filters,
                     limit,
@@ -225,34 +225,36 @@ 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)) {
-            const pattern = v === false
-                ? `${field.replace(':', '\\:\\s*')}\\s*`
-                : `${field.replace(':', '\\:\\s*')}\\:\\s*"[\\w|\\#|\\-|\\/]*"\\s*`;
+            let pattern: string;
+            if (v === false) {
+                pattern = `${field.replace(':', '\\:\\s*')}\\s*`;
+            } else if (key.startsWith('prop-')) {
+                // On properties, only remove key:value duplicates, allowing
+                // multiple properties with the same key.
+                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;
 };
 
@@ -269,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;
     };
 
@@ -282,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;
@@ -329,7 +329,7 @@ export const getSearchSessions = (clusterId: string | undefined, sessions: Sessi
     return sessions.filter(s => s.loggedIn && (!clusterId || s.clusterId === clusterId));
 };
 
-export const queryToFilters = (query: string) => {
+export const queryToFilters = (query: string, apiRevision: number) => {
     const data = getAdvancedDataFromQuery(query);
     const filter = new FilterBuilder();
     const resourceKind = data.type;
@@ -352,9 +352,15 @@ export const queryToFilters = (query: string) => {
 
     data.properties.forEach(p => {
         if (p.value) {
-            filter
-                .addILike(`properties.${p.key}`, p.value, GroupContentsResourcePrefix.PROJECT)
-                .addILike(`properties.${p.key}`, p.value, GroupContentsResourcePrefix.COLLECTION);
+            if (apiRevision < 20200212) {
+                filter
+                    .addILike(`properties.${p.key}`, p.value, GroupContentsResourcePrefix.PROJECT)
+                    .addILike(`properties.${p.key}`, p.value, GroupContentsResourcePrefix.COLLECTION);
+            } else {
+                filter
+                    .addContains(`properties.${p.key}`, p.value, GroupContentsResourcePrefix.PROJECT)
+                    .addContains(`properties.${p.key}`, p.value, GroupContentsResourcePrefix.COLLECTION);
+            }
         }
         filter.addExists(p.key);
     });