15027: Fixes unused declarations errors.
[arvados-workbench2.git] / src / store / search-bar / search-bar-actions.ts
index a4abfed6788daaf9b184ab352dfe0163e797fea3..e1551c23f89211876054cd1140457f1f3c7ef1fc 100644 (file)
@@ -10,16 +10,19 @@ import { RootState } from '~/store/store';
 import { initUserProject, treePickerActions } from '~/store/tree-picker/tree-picker-actions';
 import { ServiceRepository } from '~/services/services';
 import { FilterBuilder } from "~/services/api/filter-builder";
-import { getResourceKind, ResourceKind } from '~/models/resource';
-import { GroupClass } from '~/models/group';
+import { ResourceKind, RESOURCE_UUID_REGEX, COLLECTION_PDH_REGEX } from '~/models/resource';
 import { SearchView } from '~/store/search-bar/search-bar-reducer';
 import { navigateTo, navigateToSearchResults } from '~/store/navigation/navigation-action';
 import { snackbarActions, SnackbarKind } from '~/store/snackbar/snackbar-actions';
-import { getClusterObjectType, PropertyValues, SearchBarAdvanceFormData } from '~/models/search-bar';
-import { debounce } from 'debounce';
+import { PropertyValue, SearchBarAdvanceFormData } from '~/models/search-bar';
 import * as _ from "lodash";
 import { getModifiedKeysValues } from "~/common/objects";
 import { activateSearchBarProject } from "~/store/search-bar/search-bar-tree-actions";
+import { Session } from "~/models/session";
+import { searchResultsPanelActions } from "~/store/search-results-panel/search-results-panel-actions";
+import { ListResults } from "~/services/common-service/common-service";
+import * as parser from './search-query/arv-parser';
+import { Keywords } from './search-query/arv-parser';
 
 export const searchBarActions = unionize({
     SET_CURRENT_VIEW: ofType<string>(),
@@ -61,6 +64,7 @@ export const loadRecentQueries = () =>
 export const searchData = (searchValue: string) =>
     async (dispatch: Dispatch, getState: () => RootState) => {
         const currentView = getState().searchBar.currentView;
+        dispatch(searchResultsPanelActions.CLEAR());
         dispatch(searchBarActions.SET_SEARCH_VALUE(searchValue));
         if (searchValue.length > 0) {
             dispatch<any>(searchGroups(searchValue, 5));
@@ -74,6 +78,7 @@ export const searchData = (searchValue: string) =>
 export const searchAdvanceData = (data: SearchBarAdvanceFormData) =>
     async (dispatch: Dispatch) => {
         dispatch<any>(saveQuery(data));
+        dispatch(searchResultsPanelActions.CLEAR());
         dispatch(searchBarActions.SET_CURRENT_VIEW(SearchView.BASIC));
         dispatch(searchBarActions.CLOSE_SEARCH_VIEW());
         dispatch(navigateToSearchResults);
@@ -172,7 +177,6 @@ export const changeData = (searchValue: string) =>
         } else if (searchValuePresent) {
             dispatch(searchBarActions.SET_CURRENT_VIEW(SearchView.AUTOCOMPLETE));
             dispatch(searchBarActions.SET_SELECTED_ITEM(searchValue));
-            debounceStartSearch(dispatch);
         } else {
             dispatch(searchBarActions.SET_CURRENT_VIEW(SearchView.BASIC));
             dispatch(searchBarActions.SET_SEARCH_RESULTS([]));
@@ -187,30 +191,34 @@ export const submitData = (event: React.FormEvent<HTMLFormElement>) =>
         dispatch<any>(saveRecentQuery(searchValue));
         dispatch<any>(loadRecentQueries());
         dispatch(searchBarActions.CLOSE_SEARCH_VIEW());
-        dispatch(searchBarActions.SET_SEARCH_VALUE(searchValue));
-        dispatch(searchBarActions.SET_SEARCH_RESULTS([]));
-        dispatch(navigateToSearchResults);
+        if (RESOURCE_UUID_REGEX.exec(searchValue) || COLLECTION_PDH_REGEX.exec(searchValue)) {
+            dispatch<any>(navigateTo(searchValue));
+        } else {
+            dispatch(searchBarActions.SET_SEARCH_VALUE(searchValue));
+            dispatch(searchBarActions.SET_SEARCH_RESULTS([]));
+            dispatch(searchResultsPanelActions.CLEAR());
+            dispatch(navigateToSearchResults);
+        }
     };
 
-const debounceStartSearch = debounce((dispatch: Dispatch) => dispatch<any>(startSearch()), DEFAULT_SEARCH_DEBOUNCE);
-
-const startSearch = () =>
-    (dispatch: Dispatch, getState: () => RootState) => {
-        const searchValue = getState().searchBar.searchValue;
-        dispatch<any>(searchData(searchValue));
-    };
 
 const searchGroups = (searchValue: string, limit: number) =>
     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
         const currentView = getState().searchBar.currentView;
 
         if (searchValue || currentView === SearchView.ADVANCED) {
-            const filters = getFilters('name', searchValue);
-            const { items } = await services.groupsService.contents('', {
-                filters,
-                limit,
-                recursive: true
-            });
+            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);
+                return services.groupsService.contents('', {
+                    filters,
+                    limit,
+                    recursive: true
+                }, session);
+            }));
+
+            const items = lists.reduce((items, list) => items.concat(list.items), [] as GroupContentsResource[]);
             dispatch(searchBarActions.SET_SEARCH_RESULTS(items));
         }
     };
@@ -224,7 +232,7 @@ const buildQueryFromKeyMap = (data: any, keyMap: string[][], mode: 'rebuild' | '
         if (data.hasOwnProperty(key)) {
             const pattern = v === false
                 ? `${field.replace(':', '\\:\\s*')}\\s*`
-                : `${field.replace(':', '\\:\\s*')}\\:\\s*[\\w|\\#|\\-|\\/]*\\s*`;
+                : `${field.replace(':', '\\:\\s*')}\\:\\s*"[\\w|\\#|\\-|\\/]*"\\s*`;
             value = value.replace(new RegExp(pattern), '');
         }
 
@@ -259,7 +267,7 @@ export const getQueryFromAdvancedData = (data: SearchBarAdvanceFormData, prevDat
             dateFrom: data.dateFrom,
             dateTo: data.dateTo,
         };
-        (data.properties || []).forEach(p => fo[`prop-${p.key}`] = p.value);
+        (data.properties || []).forEach(p => fo[`prop-"${p.key}"`] = `"${p.value}"`);
         return fo;
     };
 
@@ -267,12 +275,12 @@ export const getQueryFromAdvancedData = (data: SearchBarAdvanceFormData, prevDat
         ['type', 'type'],
         ['cluster', 'cluster'],
         ['project', 'projectUuid'],
-        ['is:trashed', 'inTrash'],
+        [`is:${parser.States.TRASHED}`, 'inTrash'],
         ['from', 'dateFrom'],
         ['to', 'dateTo']
     ];
     _.union(data.properties, prevData ? prevData.properties : [])
-        .forEach(p => keyMap.push([`has:${p.key}`, `prop-${p.key}`]));
+        .forEach(p => keyMap.push([`has:"${p.key}"`, `prop-"${p.key}"`]));
 
     if (prevData) {
         const obj = getModifiedKeysValues(flatData(data), flatData(prevData));
@@ -288,166 +296,59 @@ export const getQueryFromAdvancedData = (data: SearchBarAdvanceFormData, prevDat
     return value;
 };
 
-export interface ParseSearchQuery {
-    hasKeywords: boolean;
-    values: string[];
-    properties: {
-        [key: string]: string
-    };
-}
-
-export const parseSearchQuery: (query: string) => { hasKeywords: boolean; values: string[]; properties: any } = (searchValue: string): ParseSearchQuery => {
-    const keywords = [
-        'type:',
-        'cluster:',
-        'project:',
-        'is:',
-        'from:',
-        'to:',
-        'has:'
-    ];
-
-    const hasKeywords = (search: string) => keywords.reduce((acc, keyword) => acc + search.indexOf(keyword) >= 0 ? 1 : 0, 0);
-    let keywordsCnt = 0;
-
-    const properties = {};
-
-    keywords.forEach(k => {
-        let p = searchValue.indexOf(k);
-        const key = k.substr(0, k.length - 1);
-
-        while (p >= 0) {
-            const l = searchValue.length;
-            keywordsCnt += 1;
-
-            let v = '';
-            let i = p + k.length;
-            while (i < l && searchValue[i] === ' ') {
-                ++i;
-            }
-            const vp = i;
-            while (i < l && searchValue[i] !== ' ') {
-                v += searchValue[i];
-                ++i;
-            }
-
-            if (hasKeywords(v)) {
-                searchValue = searchValue.substr(0, p) + searchValue.substr(vp);
-            } else {
-                if (v !== '') {
-                    if (!properties[key]) {
-                        properties[key] = [];
-                    }
-                    properties[key].push(v);
-                }
-                searchValue = searchValue.substr(0, p) + searchValue.substr(i);
-            }
-            p = searchValue.indexOf(k);
-        }
-    });
-
-    const values = _.uniq(searchValue.split(' ').filter(v => v.length > 0));
-
-    return { hasKeywords: keywordsCnt > 0, values, properties };
-};
-
 export const getAdvancedDataFromQuery = (query: string): SearchBarAdvanceFormData => {
-    const r = parseSearchQuery(query);
-
-    const getFirstProp = (name: string) => r.properties[name] && r.properties[name][0];
-    const getPropValue = (name: string, value: string) => r.properties[name] && r.properties[name].find((v: string) => v === value);
-    const getProperties = () => {
-        if (r.properties.has) {
-            return r.properties.has.map((value: string) => {
-                const v = value.split(':');
-                return {
-                    key: v[0],
-                    value: v[1]
-                };
-            });
-        }
-        return [];
-    };
-
+    const { tokens, searchString } = parser.parseSearchQuery(query);
+    const getValue = parser.getValue(tokens);
     return {
-        searchValue: r.values.join(' '),
-        type: getResourceKind(getFirstProp('type')),
-        cluster: getClusterObjectType(getFirstProp('cluster')),
-        projectUuid: getFirstProp('project'),
-        inTrash: getPropValue('is', 'trashed') !== undefined,
-        dateFrom: getFirstProp('from'),
-        dateTo: getFirstProp('to'),
-        properties: getProperties(),
+        searchValue: searchString,
+        type: getValue(Keywords.TYPE) as ResourceKind,
+        cluster: getValue(Keywords.CLUSTER),
+        projectUuid: getValue(Keywords.PROJECT),
+        inTrash: parser.isTrashed(tokens),
+        dateFrom: getValue(Keywords.FROM) || '',
+        dateTo: getValue(Keywords.TO) || '',
+        properties: parser.getProperties(tokens),
         saveQuery: false,
         queryName: ''
     };
 };
 
-export const getFilters = (filterName: string, searchValue: string): string => {
-    const filter = new FilterBuilder();
-
-    const pq = parseSearchQuery(searchValue);
+export const getSearchSessions = (clusterId: string | undefined, sessions: Session[]): Session[] => {
+    return sessions.filter(s => s.loggedIn && (!clusterId || s.clusterId === clusterId));
+};
 
-    if (!pq.hasKeywords) {
-        filter
-            .addILike(filterName, searchValue, GroupContentsResourcePrefix.COLLECTION)
-            .addILike(filterName, searchValue, GroupContentsResourcePrefix.PROCESS)
-            .addILike(filterName, searchValue, GroupContentsResourcePrefix.PROJECT);
-    } else {
+export const queryToFilters = (query: string) => {
+    const data = getAdvancedDataFromQuery(query);
+    const filter = new FilterBuilder();
+    const resourceKind = data.type;
 
-        if (pq.properties.type) {
-            pq.values.forEach(v => {
-                let prefix = '';
-                switch (ResourceKind[pq.properties.type]) {
-                    case ResourceKind.PROJECT:
-                        prefix = GroupContentsResourcePrefix.PROJECT;
-                        break;
-                    case ResourceKind.COLLECTION:
-                        prefix = GroupContentsResourcePrefix.COLLECTION;
-                        break;
-                    case ResourceKind.PROCESS:
-                        prefix = GroupContentsResourcePrefix.PROCESS;
-                        break;
-                }
-                if (prefix !== '') {
-                    filter.addILike(filterName, v, prefix);
-                }
-            });
-        } else {
-            pq.values.forEach(v => {
-                filter
-                    .addILike(filterName, v, GroupContentsResourcePrefix.COLLECTION)
-                    .addILike(filterName, v, GroupContentsResourcePrefix.PROCESS)
-                    .addILike(filterName, v, GroupContentsResourcePrefix.PROJECT);
-            });
-        }
+    if (data.searchValue) {
+        filter.addFullTextSearch(data.searchValue);
+    }
 
-        if (pq.properties.is && pq.properties.is === 'trashed') {
-        }
+    if (data.projectUuid) {
+        filter.addEqual('ownerUuid', data.projectUuid);
+    }
 
-        if (pq.properties.project) {
-            filter.addEqual('owner_uuid', pq.properties.project, GroupContentsResourcePrefix.PROJECT);
-        }
+    if (data.dateFrom) {
+        filter.addGte('modified_at', buildDateFilter(data.dateFrom));
+    }
 
-        if (pq.properties.from) {
-            filter.addGte('modified_at', buildDateFilter(pq.properties.from));
-        }
+    if (data.dateTo) {
+        filter.addLte('modified_at', buildDateFilter(data.dateTo));
+    }
 
-        if (pq.properties.to) {
-            filter.addLte('modified_at', buildDateFilter(pq.properties.to));
+    data.properties.forEach(p => {
+        if (p.value) {
+            filter
+                .addILike(`properties.${p.key}`, p.value, GroupContentsResourcePrefix.PROJECT)
+                .addILike(`properties.${p.key}`, p.value, GroupContentsResourcePrefix.COLLECTION);
         }
-        // filter
-        //     .addIsA("uuid", buildUuidFilter(resourceKind))
-        //     .addILike(filterName, searchValue, GroupContentsResourcePrefix.COLLECTION)
-        //     .addILike(filterName, searchValue, GroupContentsResourcePrefix.PROCESS)
-        //     .addILike(filterName, searchValue, GroupContentsResourcePrefix.PROJECT)
-        //     .addLte('modified_at', buildDateFilter(dateTo))
-        //     .addGte('modified_at', buildDateFilter(dateFrom))
-        //     .addEqual('groupClass', GroupClass.PROJECT, GroupContentsResourcePrefix.PROJECT)
-    }
+        filter.addExists(p.key);
+    });
 
     return filter
-        .addEqual('groupClass', GroupClass.PROJECT, GroupContentsResourcePrefix.PROJECT)
+        .addIsA("uuid", buildUuidFilter(resourceKind))
         .getFilters();
 };
 
@@ -464,12 +365,12 @@ export const initAdvanceFormProjectsTree = () =>
         dispatch<any>(initUserProject(SEARCH_BAR_ADVANCE_FORM_PICKER_ID));
     };
 
-export const changeAdvanceFormProperty = (property: string, value: PropertyValues[] | string = '') =>
+export const changeAdvanceFormProperty = (property: string, value: PropertyValue[] | string = '') =>
     (dispatch: Dispatch) => {
         dispatch(change(SEARCH_BAR_ADVANCE_FORM_NAME, property, value));
     };
 
-export const updateAdvanceFormProperties = (propertyValues: PropertyValues) =>
+export const updateAdvanceFormProperties = (propertyValues: PropertyValue) =>
     (dispatch: Dispatch) => {
         dispatch(arrayPush(SEARCH_BAR_ADVANCE_FORM_NAME, 'properties', propertyValues));
     };