refs #master Merge branch 'origin/master' into 14280-query-language
authorDaniel Kos <daniel.kos@contractors.roche.com>
Mon, 26 Nov 2018 20:14:06 +0000 (21:14 +0100)
committerDaniel Kos <daniel.kos@contractors.roche.com>
Mon, 26 Nov 2018 20:14:06 +0000 (21:14 +0100)
# Conflicts:
# src/store/collections/collection-create-actions.ts
# src/store/processes/process-move-actions.ts
# src/store/processes/process-update-actions.ts

Arvados-DCO-1.1-Signed-off-by: Daniel Kos <daniel.kos@contractors.roche.com>

14 files changed:
1  2 
src/models/resource.ts
src/store/auth/auth-action.ts
src/store/collection-panel/collection-panel-files/collection-panel-files-actions.ts
src/store/collections/collection-copy-actions.ts
src/store/collections/collection-create-actions.ts
src/store/collections/collection-move-actions.ts
src/store/collections/collection-partial-copy-actions.ts
src/store/processes/process-move-actions.ts
src/store/processes/process-update-actions.ts
src/store/projects/project-move-actions.ts
src/store/repositories/repositories-actions.ts
src/store/search-bar/search-bar-actions.test.ts
src/store/search-bar/search-bar-actions.ts
src/store/search-results-panel/search-results-middleware-service.ts

index f8028c761619745953caf20bfdc43709e045e63b,5fa617974b173741e69e3b017c7778223a606cc9..7852a73e1b8bbc4acf4e20537e3cf232b4230ab0
@@@ -77,22 -82,3 +82,22 @@@ export const extractUuidKind = (uuid: s
              return undefined;
      }
  };
- }
 +
 +export const getResourceKind = (kind?: string) => {
 +    switch (kind) {
 +        case "arvados#collection":
 +            return ResourceKind.COLLECTION;
 +        case "arvados#container":
 +            return ResourceKind.CONTAINER;
 +        case "arvados#containerRequest":
 +            return ResourceKind.CONTAINER_REQUEST;
 +        case "arvados#group":
 +            return ResourceKind.GROUP;
 +        case "arvados#log":
 +            return ResourceKind.LOG;
 +        case "arvados#workflow":
 +            return ResourceKind.WORKFLOW;
 +        default:
 +            return undefined;
 +    }
++};
index ac2e0b7e2f68c6e699e294710d582582701b5fc2,28559b1a7ac126feac5adfb7c15170fa2f28295d..9aadd5b949b2fe325d38d75bf2a03f4bb60e524b
@@@ -4,10 -4,16 +4,16 @@@
  
  import { ofType, unionize, UnionOf } from '~/common/unionize';
  import { Dispatch } from "redux";
- import { User } from "~/models/user";
 -import { reset, stopSubmit, startSubmit } from 'redux-form';
++import { reset, stopSubmit, startSubmit, FormErrors } from 'redux-form';
+ import { AxiosInstance } from "axios";
  import { RootState } from "../store";
+ import { snackbarActions } from '~/store/snackbar/snackbar-actions';
+ import { dialogActions } from '~/store/dialog/dialog-actions';
+ import { setBreadcrumbs } from '~/store/breadcrumbs/breadcrumbs-actions';
  import { ServiceRepository } from "~/services/services";
- import { AxiosInstance } from "axios";
+ import { getAuthorizedKeysServiceError, AuthorizedKeysServiceError } from '~/services/authorized-keys-service/authorized-keys-service';
+ import { KeyType, SshKeyResource } from '~/models/ssh-key';
+ import { User } from "~/models/user";
  
  export const authActions = unionize({
      SAVE_API_TOKEN: ofType<string>(),
@@@ -70,4 -89,77 +89,77 @@@ export const getUserDetails = () => (di
      });
  };
  
 -                dispatch(stopSubmit(SSH_KEY_CREATE_FORM_NAME, { publicKey: 'Public key already exists.' }));
+ export const openSshKeyCreateDialog = () => dialogActions.OPEN_DIALOG({ id: SSH_KEY_CREATE_FORM_NAME, data: {} });
+ export const openPublicKeyDialog = (name: string, publicKey: string) =>
+     dialogActions.OPEN_DIALOG({ id: SSH_KEY_PUBLIC_KEY_DIALOG, data: { name, publicKey } });
+ export const openSshKeyAttributesDialog = (index: number) =>
+     (dispatch: Dispatch, getState: () => RootState) => {
+         const sshKey = getState().auth.sshKeys[index];
+         dispatch(dialogActions.OPEN_DIALOG({ id: SSH_KEY_ATTRIBUTES_DIALOG, data: { sshKey } }));
+     };
+ export const openSshKeyRemoveDialog = (uuid: string) =>
+     (dispatch: Dispatch, getState: () => RootState) => {
+         dispatch(dialogActions.OPEN_DIALOG({
+             id: SSH_KEY_REMOVE_DIALOG,
+             data: {
+                 title: 'Remove public key',
+                 text: 'Are you sure you want to remove this public key?',
+                 confirmButtonLabel: 'Remove',
+                 uuid
+             }
+         }));
+     };
+ export const removeSshKey = (uuid: string) =>
+     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+         dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Removing ...' }));
+         await services.authorizedKeysService.delete(uuid);
+         dispatch(authActions.REMOVE_SSH_KEY(uuid));
+         dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Public Key has been successfully removed.', hideDuration: 2000 }));
+     };
+ export const createSshKey = (data: SshKeyCreateFormDialogData) =>
+     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+         const userUuid = getState().auth.user!.uuid;
+         const { name, publicKey } = data;
+         dispatch(startSubmit(SSH_KEY_CREATE_FORM_NAME));
+         try {
+             const newSshKey = await services.authorizedKeysService.create({
+                 name,
+                 publicKey,
+                 keyType: KeyType.SSH,
+                 authorizedUserUuid: userUuid
+             });
+             dispatch(authActions.ADD_SSH_KEY(newSshKey));
+             dispatch(dialogActions.CLOSE_DIALOG({ id: SSH_KEY_CREATE_FORM_NAME }));
+             dispatch(reset(SSH_KEY_CREATE_FORM_NAME));
+             dispatch(snackbarActions.OPEN_SNACKBAR({
+                 message: "Public key has been successfully created.",
+                 hideDuration: 2000
+             }));
+         } catch (e) {
+             const error = getAuthorizedKeysServiceError(e);
+             if (error === AuthorizedKeysServiceError.UNIQUE_PUBLIC_KEY) {
 -                dispatch(stopSubmit(SSH_KEY_CREATE_FORM_NAME, { publicKey: 'Public key is invalid' }));
++                dispatch(stopSubmit(SSH_KEY_CREATE_FORM_NAME, { publicKey: 'Public key already exists.' } as FormErrors));
+             } else if (error === AuthorizedKeysServiceError.INVALID_PUBLIC_KEY) {
++                dispatch(stopSubmit(SSH_KEY_CREATE_FORM_NAME, { publicKey: 'Public key is invalid' } as FormErrors));
+             }
+         }
+     };
+ export const loadSshKeysPanel = () =>
+     async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
+         try {
+             dispatch(setBreadcrumbs([{ label: 'SSH Keys'}]));
+             const response = await services.authorizedKeysService.list();
+             dispatch(authActions.SET_SSH_KEYS(response.items));
+         } catch (e) {
+             return;
+         }
+     };
  export type AuthAction = UnionOf<typeof authActions>;
index c61e8da2f3d00fbbca634001a8fd074d0ad5fc91,f4375688141cdc5eb3873321eaac01c22c895024..8d1e9ba5f247be426fc0b2faabb6d4ec60f858fb
@@@ -38,9 -40,10 +40,10 @@@ export const openCollectionCreateDialo
  export const createCollection = (data: CollectionCreateFormDialogData) =>
      async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
          dispatch(startSubmit(COLLECTION_CREATE_FORM_NAME));
 -        let newCollection: CollectionResource | null = null;
++        let newCollection: CollectionResource;
          try {
              dispatch(progressIndicatorActions.START_WORKING(COLLECTION_CREATE_FORM_NAME));
-             const newCollection = await services.collectionService.create(data);
+             newCollection = await services.collectionService.create(data);
              await dispatch<any>(uploadCollectionFiles(newCollection.uuid));
              dispatch(dialogActions.CLOSE_DIALOG({ id: COLLECTION_CREATE_FORM_NAME }));
              dispatch(reset(COLLECTION_CREATE_FORM_NAME));
          } catch (e) {
              const error = getCommonResourceServiceError(e);
              if (error === CommonResourceServiceError.UNIQUE_VIOLATION) {
 -                dispatch(stopSubmit(COLLECTION_CREATE_FORM_NAME, { name: 'Collection with the same name already exists.' }));
 +                dispatch(stopSubmit(COLLECTION_CREATE_FORM_NAME, { name: 'Collection with the same name already exists.' } as FormErrors));
+             } else if (error === CommonResourceServiceError.NONE) {
+                 dispatch(stopSubmit(COLLECTION_CREATE_FORM_NAME));
+                 dispatch(dialogActions.CLOSE_DIALOG({ id: COLLECTION_CREATE_FORM_NAME }));
+                 dispatch(snackbarActions.OPEN_SNACKBAR({
+                     message: 'Collection has not been created.',
+                     hideDuration: 2000,
+                     kind: SnackbarKind.ERROR
+                 }));
+                 await services.collectionService.delete(newCollection!.uuid);
              }
              dispatch(progressIndicatorActions.STOP_WORKING(COLLECTION_CREATE_FORM_NAME));
              return;
index 89dc0efeb096de2c723634f8dfe504c65fe72e6b,7e65bcca0bd221c8eea7bd293b77f42895ee26f5..dcf97185c3a1ebca03bfaccfdbfbde16db80d83f
@@@ -45,9 -42,7 +42,7 @@@ export const moveProcess = (resource: M
          } catch (e) {
              const error = getCommonResourceServiceError(e);
              if (error === CommonResourceServiceError.UNIQUE_VIOLATION) {
 -                dispatch(stopSubmit(PROCESS_MOVE_FORM_NAME, { ownerUuid: 'A process with the same name already exists in the target project.' }));
 +                dispatch(stopSubmit(PROCESS_MOVE_FORM_NAME, { ownerUuid: 'A process with the same name already exists in the target project.' } as FormErrors));
-             } else if (error === CommonResourceServiceError.MODIFYING_CONTAINER_REQUEST_FINAL_STATE) {
-                 dispatch(stopSubmit(PROCESS_MOVE_FORM_NAME, { ownerUuid: 'You can move only draft processes.' } as FormErrors));
              } else {
                  dispatch(dialogActions.CLOSE_DIALOG({ id: PROCESS_MOVE_FORM_NAME }));
                  dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Could not move the process.', hideDuration: 2000 }));
index dfd548b0e95e747f7a978be1a2293ae4a2ddf194,2063f11362ced7bb2b04e0327dc3743432bc9b48..372e18829d222745fe487adc395d979fb83557ce
@@@ -42,9 -41,7 +41,7 @@@ export const updateProcess = (resource
          } catch (e) {
              const error = getCommonResourceServiceError(e);
              if (error === CommonResourceServiceError.UNIQUE_VIOLATION) {
 -                dispatch(stopSubmit(PROCESS_UPDATE_FORM_NAME, { name: 'Process with the same name already exists.' }));
 +                dispatch(stopSubmit(PROCESS_UPDATE_FORM_NAME, { name: 'Process with the same name already exists.' } as FormErrors));
-             } else if (error === CommonResourceServiceError.MODIFYING_CONTAINER_REQUEST_FINAL_STATE) {
-                 dispatch(stopSubmit(PROCESS_UPDATE_FORM_NAME, { name: 'You cannot modified in "Final" state.' } as FormErrors));
              } else {
                  dispatch(dialogActions.CLOSE_DIALOG({ id: PROCESS_UPDATE_FORM_NAME }));
                  dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Could not update the process.', hideDuration: 2000 }));
index 0000000000000000000000000000000000000000,a672738fd2edf18c36237f3e20c490a1100fdf73..dd80f8d79fd117851fa4f86eec41319ff43d9187
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,107 +1,107 @@@
 -import { startSubmit, reset, stopSubmit } from "redux-form";
+ // Copyright (C) The Arvados Authors. All rights reserved.
+ //
+ // SPDX-License-Identifier: AGPL-3.0
+ import { Dispatch } from "redux";
+ import { bindDataExplorerActions } from '~/store/data-explorer/data-explorer-action';
+ import { RootState } from '~/store/store';
+ import { ServiceRepository } from "~/services/services";
+ import { navigateToRepositories } from "~/store/navigation/navigation-action";
+ import { unionize, ofType, UnionOf } from "~/common/unionize";
+ import { dialogActions } from '~/store/dialog/dialog-actions';
+ import { RepositoryResource } from "~/models/repositories";
 -            dispatch(snackbarActions.OPEN_SNACKBAR({ message: "Repository has been successfully created.", hideDuration: 2000, kind: SnackbarKind.SUCCESS })); 
 -            dispatch<any>(loadRepositoriesData());     
++import { startSubmit, reset, stopSubmit, FormErrors } from "redux-form";
+ import { getCommonResourceServiceError, CommonResourceServiceError } from "~/services/common-service/common-resource-service";
+ import { snackbarActions, SnackbarKind } from '~/store/snackbar/snackbar-actions';
+ export const repositoriesActions = unionize({
+     SET_REPOSITORIES: ofType<any>(),
+ });
+ export type RepositoriesActions = UnionOf<typeof repositoriesActions>;
+ export const REPOSITORIES_PANEL = 'repositoriesPanel';
+ export const REPOSITORIES_SAMPLE_GIT_DIALOG = 'repositoriesSampleGitDialog';
+ export const REPOSITORY_ATTRIBUTES_DIALOG = 'repositoryAttributesDialog';
+ export const REPOSITORY_CREATE_FORM_NAME = 'repositoryCreateFormName';
+ export const REPOSITORY_REMOVE_DIALOG = 'repositoryRemoveDialog';
+ export const openRepositoriesSampleGitDialog = () =>
+     (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+         const uuidPrefix = getState().properties.uuidPrefix;
+         dispatch(dialogActions.OPEN_DIALOG({ id: REPOSITORIES_SAMPLE_GIT_DIALOG, data: { uuidPrefix } }));
+     };
+ export const openRepositoryAttributes = (index: number) =>
+     (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+         const repositoryData = getState().repositories.items[index];
+         dispatch(dialogActions.OPEN_DIALOG({ id: REPOSITORY_ATTRIBUTES_DIALOG, data: { repositoryData } }));
+     };
+ export const openRepositoryCreateDialog = () =>
+     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+         const userUuid = await services.authService.getUuid();
+         const user = await services.userService.get(userUuid!);
+         dispatch(reset(REPOSITORY_CREATE_FORM_NAME));
+         dispatch(dialogActions.OPEN_DIALOG({ id: REPOSITORY_CREATE_FORM_NAME, data: { user } }));
+     };
+ export const createRepository = (repository: RepositoryResource) =>
+     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+         const userUuid = await services.authService.getUuid();
+         const user = await services.userService.get(userUuid!);
+         dispatch(startSubmit(REPOSITORY_CREATE_FORM_NAME));
+         try {
+             const newRepository = await services.repositoriesService.create({ name: `${user.username}/${repository.name}` });
+             dispatch(dialogActions.CLOSE_DIALOG({ id: REPOSITORY_CREATE_FORM_NAME }));
+             dispatch(reset(REPOSITORY_CREATE_FORM_NAME));
 -                dispatch(stopSubmit(REPOSITORY_CREATE_FORM_NAME, { name: 'Repository with the same name already exists.' }));
++            dispatch(snackbarActions.OPEN_SNACKBAR({ message: "Repository has been successfully created.", hideDuration: 2000, kind: SnackbarKind.SUCCESS }));
++            dispatch<any>(loadRepositoriesData());
+             return newRepository;
+         } catch (e) {
+             const error = getCommonResourceServiceError(e);
+             if (error === CommonResourceServiceError.NAME_HAS_ALREADY_BEEN_TAKEN) {
 -    };
++                dispatch(stopSubmit(REPOSITORY_CREATE_FORM_NAME, { name: 'Repository with the same name already exists.' } as FormErrors));
+             }
+             return undefined;
+         }
+     };
+ export const openRemoveRepositoryDialog = (uuid: string) =>
+     (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+         dispatch(dialogActions.OPEN_DIALOG({
+             id: REPOSITORY_REMOVE_DIALOG,
+             data: {
+                 title: 'Remove repository',
+                 text: 'Are you sure you want to remove this repository?',
+                 confirmButtonLabel: 'Remove',
+                 uuid
+             }
+         }));
+     };
+ export const removeRepository = (uuid: string) =>
+     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+         dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Removing ...' }));
+         await services.repositoriesService.delete(uuid);
+         dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Removed.', hideDuration: 2000 }));
+         dispatch<any>(loadRepositoriesData());
+     };
+ const repositoriesBindedActions = bindDataExplorerActions(REPOSITORIES_PANEL);
+ export const openRepositoriesPanel = () =>
+     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+         dispatch<any>(navigateToRepositories);
+     };
+ export const loadRepositoriesData = () =>
+     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+         const repositories = await services.repositoriesService.list();
+         dispatch(repositoriesActions.SET_REPOSITORIES(repositories.items));
+     };
+ export const loadRepositoriesPanel = () =>
+     (dispatch: Dispatch) => {
+         dispatch(repositoriesBindedActions.REQUEST_ITEMS());
++    };
index 48dfeecfe5b07805eb12877f04b8ff4298e98755,0000000000000000000000000000000000000000..d936caf528c7f66b4e7dbc4143a49396f45db9f1
mode 100644,000000..100644
--- /dev/null
@@@ -1,137 -1,0 +1,137 @@@
-             })
 +// Copyright (C) The Arvados Authors. All rights reserved.
 +//
 +// SPDX-License-Identifier: AGPL-3.0
 +
 +import { getAdvancedDataFromQuery, getQueryFromAdvancedData, parseQuery } from "~/store/search-bar/search-bar-actions";
 +import { ResourceKind } from "~/models/resource";
 +import { ClusterObjectType } from "~/models/search-bar";
 +
 +describe('search-bar-actions', () => {
 +    describe('parseQuery', () => {
 +        it('should correctly parse query #1', () => {
 +            const q = 'val0 is:trashed val1';
 +            const r = parseQuery(q);
 +            expect(r.hasKeywords).toBeTruthy();
 +            expect(r.values).toEqual(['val0', 'val1']);
 +            expect(r.properties).toEqual({
 +                is: ['trashed']
 +            });
 +        });
 +
 +        it('should correctly parse query #2 (value with keyword should be ignored)', () => {
 +            const q = 'val0 is:from:trashed val1';
 +            const r = parseQuery(q);
 +            expect(r.hasKeywords).toBeTruthy();
 +            expect(r.values).toEqual(['val0', 'val1']);
 +            expect(r.properties).toEqual({
 +                from: ['trashed']
 +            });
 +        });
 +
 +        it('should correctly parse query #3 (many keywords)', () => {
 +            const q = 'val0 is:trashed val2 from:2017-04-01 val1';
 +            const r = parseQuery(q);
 +            expect(r.hasKeywords).toBeTruthy();
 +            expect(r.values).toEqual(['val0', 'val2', 'val1']);
 +            expect(r.properties).toEqual({
 +                is: ['trashed'],
 +                from: ['2017-04-01']
 +            });
 +        });
 +
 +        it('should correctly parse query #4 (no duplicated values)', () => {
 +            const q = 'val0 is:trashed val2 val2 val0';
 +            const r = parseQuery(q);
 +            expect(r.hasKeywords).toBeTruthy();
 +            expect(r.values).toEqual(['val0', 'val2']);
 +            expect(r.properties).toEqual({
 +                is: ['trashed']
 +            });
 +        });
 +
 +        it('should correctly parse query #5 (properties)', () => {
 +            const q = 'val0 has:filesize:100mb val2 val2 val0';
 +            const r = parseQuery(q);
 +            expect(r.hasKeywords).toBeTruthy();
 +            expect(r.values).toEqual(['val0', 'val2']);
 +            expect(r.properties).toEqual({
 +                'has': ['filesize:100mb']
 +            });
 +        });
 +
 +        it('should correctly parse query #6 (multiple properties, multiple is)', () => {
 +            const q = 'val0 has:filesize:100mb val2 has:user:daniel is:starred val2 val0 is:trashed';
 +            const r = parseQuery(q);
 +            expect(r.hasKeywords).toBeTruthy();
 +            expect(r.values).toEqual(['val0', 'val2']);
 +            expect(r.properties).toEqual({
 +                'has': ['filesize:100mb', 'user:daniel'],
 +                'is': ['starred', 'trashed']
 +            });
 +        });
 +    });
 +
 +    describe('getAdvancedDataFromQuery', () => {
 +        it('should correctly build advanced data record from query #1', () => {
 +            const r = getAdvancedDataFromQuery('val0 has:filesize:100mb val2 has:user:daniel is:starred val2 val0 is:trashed');
 +            expect(r).toEqual({
 +                searchValue: 'val0 val2',
 +                type: undefined,
 +                cluster: undefined,
 +                projectUuid: undefined,
 +                inTrash: true,
 +                dateFrom: undefined,
 +                dateTo: undefined,
 +                properties: [{
 +                    key: 'filesize',
 +                    value: '100mb'
 +                }, {
 +                    key: 'user',
 +                    value: 'daniel'
 +                }],
 +                saveQuery: false,
 +                queryName: ''
-             })
++            });
 +        });
 +
 +        it('should correctly build advanced data record from query #2', () => {
 +            const r = getAdvancedDataFromQuery('document from:2017-08-01 pdf has:filesize:101mb is:trashed type:arvados#collection cluster:indianapolis');
 +            expect(r).toEqual({
 +                searchValue: 'document pdf',
 +                type: ResourceKind.COLLECTION,
 +                cluster: ClusterObjectType.INDIANAPOLIS,
 +                projectUuid: undefined,
 +                inTrash: true,
 +                dateFrom: '2017-08-01',
 +                dateTo: undefined,
 +                properties: [{
 +                    key: 'filesize',
 +                    value: '101mb'
 +                }],
 +                saveQuery: false,
 +                queryName: ''
-         })
++            });
 +        });
 +    });
 +
 +    describe('getQueryFromAdvancedData', () => {
 +        it('should build query from advanced data', () => {
 +            const q = getQueryFromAdvancedData({
 +                searchValue: 'document pdf',
 +                type: ResourceKind.COLLECTION,
 +                cluster: ClusterObjectType.INDIANAPOLIS,
 +                projectUuid: undefined,
 +                inTrash: true,
 +                dateFrom: '2017-08-01',
 +                dateTo: '',
 +                properties: [{
 +                    key: 'filesize',
 +                    value: '101mb'
 +                }],
 +                saveQuery: false,
 +                queryName: ''
 +            });
 +            expect(q).toBe('document pdf type:arvados#collection cluster:indianapolis is:trashed from:2017-08-01 has:filesize:101mb');
++        });
 +    });
 +});
index 1746c953388ddc31e20a854f98f1d3a9f08fcdb0,165392c6c20ef0dc9fef9bdbfe559608529a623a..2ad9b2838a578b909491cf09f4580c3926980f06
@@@ -10,15 -10,14 +10,15 @@@ import { RootState } from '~/store/stor
  import { initUserProject } from '~/store/tree-picker/tree-picker-actions';
  import { ServiceRepository } from '~/services/services';
  import { FilterBuilder } from "~/services/api/filter-builder";
 -import { ResourceKind } from '~/models/resource';
 +import { getResourceKind, ResourceKind } from '~/models/resource';
  import { GroupClass } from '~/models/group';
  import { SearchView } from '~/store/search-bar/search-bar-reducer';
 -import { navigateToSearchResults, navigateTo } from '~/store/navigation/navigation-action';
 +import { navigateTo, navigateToSearchResults } from '~/store/navigation/navigation-action';
  import { snackbarActions, SnackbarKind } from '~/store/snackbar/snackbar-actions';
- import { ClusterObjectType, getClusterObjectType, PropertyValues, SearchBarAdvanceFormData } from '~/models/search-bar';
 -import { initialize } from 'redux-form';
 -import { SearchBarAdvanceFormData, PropertyValues } from '~/models/search-bar';
++import { getClusterObjectType, PropertyValues, SearchBarAdvanceFormData } from '~/models/search-bar';
  import { debounce } from 'debounce';
 +import * as _ from "lodash";
 +import { getModifiedKeysValues } from "~/common/objects";
  
  export const searchBarActions = unionize({
      SET_CURRENT_VIEW: ofType<string>(),
@@@ -202,228 -191,15 +202,228 @@@ const searchGroups = (searchValue: stri
          }
      };
  
 -export const getFilters = (filterName: string, searchValue: string, props: any): string => {
 -    const { resourceKind, dateTo, dateFrom } = props;
 -    return new FilterBuilder()
 -        .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))
 +const buildQueryFromKeyMap = (data: any, keyMap: string[][], mode: 'rebuild' | 'reuse') => {
 +    let value = data.searchValue;
 +
 +    const addRem = (field: string, key: string) => {
 +        const v = data[key];
 +        if (v) {
 +            const nv = v === true
 +                ? `${field}`
 +                : `${field}:${v}`;
 +
 +            if (mode === 'rebuild') {
 +                value = value + ' ' + nv;
 +            } else {
 +                value = nv + ' ' + value;
 +            }
 +        } else if (data.hasOwnProperty(key) && (v === undefined || v === false)) {
 +            const pattern = v === false
 +                ? `${field.replace(':', '\\:\\s*')}\\s*`
 +                : `${field.replace(':', '\\:\\s*')}\\:\\s*[\\w|\\#|\\-|\\/]*`;
 +            value = value.replace(new RegExp(pattern), '');
 +        }
 +    };
 +
 +    keyMap.forEach(km => addRem(km[0], km[1]));
 +
 +    return value;
 +};
 +
 +export const getQueryFromAdvancedData = (data: SearchBarAdvanceFormData, prevData?: SearchBarAdvanceFormData) => {
 +    let value = '';
 +
 +    const flatData = (data: SearchBarAdvanceFormData) => {
 +        const fo = {
 +            searchValue: data.searchValue,
 +            type: data.type,
 +            cluster: data.cluster,
 +            projectUuid: data.projectUuid,
 +            inTrash: data.inTrash,
 +            dateFrom: data.dateFrom,
 +            dateTo: data.dateTo,
 +        };
 +        (data.properties || []).forEach(p => fo[`prop-${p.key}`] = p.value);
 +        return fo;
 +    };
 +
 +    const keyMap = [
 +        ['type', 'type'],
 +        ['cluster', 'cluster'],
 +        ['project', 'projectUuid'],
 +        ['is:trashed', 'inTrash'],
 +        ['from', 'dateFrom'],
 +        ['to', 'dateTo']
 +    ];
 +    _.union(data.properties, prevData ? prevData.properties : [])
 +        .forEach(p => keyMap.push([`has:${p.key}`, `prop-${p.key}`]));
 +
 +    if (prevData) {
 +        const obj = getModifiedKeysValues(flatData(data), flatData(prevData));
 +        console.log(obj);
 +        value = buildQueryFromKeyMap({
 +            searchValue: data.searchValue,
 +            ...obj
 +        } as SearchBarAdvanceFormData, keyMap, "reuse");
 +    } else {
 +        value = buildQueryFromKeyMap(flatData(data), keyMap, "rebuild");
 +    }
 +
 +    value = value.trim();
 +    return value;
 +};
 +
 +export const parseQuery: (query: string) => { hasKeywords: boolean; values: string[]; properties: any } = (searchValue: string) => {
 +    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 = parseQuery(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) => {
++        if (r.properties.has) {
++            return r.properties.has.map((value: string) => {
 +                const v = value.split(':');
 +                return {
 +                    key: v[0],
 +                    value: v[1]
-                 }
-             })
++                };
++            });
 +        }
 +        return [];
 +    };
 +
 +    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(),
 +        saveQuery: false,
-         queryName: '',
-     }
++        queryName: ''
++    };
 +};
 +
 +export const getFilters = (filterName: string, searchValue: string): string => {
 +    const filter = new FilterBuilder();
 +
 +    const pq = parseQuery(searchValue);
 +
 +    if (!pq.hasKeywords) {
 +        filter
 +            .addILike(filterName, searchValue, GroupContentsResourcePrefix.COLLECTION)
 +            .addILike(filterName, searchValue, GroupContentsResourcePrefix.PROCESS)
 +            .addILike(filterName, searchValue, GroupContentsResourcePrefix.PROJECT);
 +    } else {
 +
 +        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 (pq.properties.is && pq.properties.is === 'trashed') {
 +        }
 +
 +        if (pq.properties.project) {
 +            filter.addEqual('owner_uuid', pq.properties.project, GroupContentsResourcePrefix.PROJECT);
 +        }
 +
 +        if (pq.properties.from) {
 +            filter.addGte('modified_at', buildDateFilter(pq.properties.from));
 +        }
 +
 +        if (pq.properties.to) {
 +            filter.addLte('modified_at', buildDateFilter(pq.properties.to));
 +        }
 +        // 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)
 +    }
 +
 +    return filter
          .addEqual('groupClass', GroupClass.PROJECT, GroupContentsResourcePrefix.PROJECT)
          .getFilters();
  };