//
// SPDX-License-Identifier: AGPL-3.0
-import { getAdvancedDataFromQuery, getQueryFromAdvancedData, parseSearchQuery } from "~/store/search-bar/search-bar-actions";
+import { getAdvancedDataFromQuery, getQueryFromAdvancedData } from "~/store/search-bar/search-bar-actions";
import { ResourceKind } from "~/models/resource";
describe('search-bar-actions', () => {
- describe('parseSearchQuery', () => {
- it('should correctly parse query #1', () => {
- const q = 'val0 is:trashed val1';
- const r = parseSearchQuery(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 = parseSearchQuery(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 = parseSearchQuery(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 = parseSearchQuery(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 = parseSearchQuery(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 = parseSearchQuery(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');
+ const r = getAdvancedDataFromQuery('val0 has:"file size":"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,
+ dateFrom: '',
+ dateTo: '',
properties: [{
- key: 'filesize',
+ key: 'file size',
value: '100mb'
}, {
key: 'user',
});
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:c97qx');
+ const r = getAdvancedDataFromQuery('document from:2017-08-01 pdf has:"filesize":"101mb" is:trashed type:arvados#collection cluster:c97qx');
expect(r).toEqual({
searchValue: 'document pdf',
type: ResourceKind.COLLECTION,
projectUuid: undefined,
inTrash: true,
dateFrom: '2017-08-01',
- dateTo: undefined,
+ dateTo: '',
properties: [{
key: 'filesize',
value: '101mb'
dateFrom: '2017-08-01',
dateTo: '',
properties: [{
- key: 'filesize',
+ key: 'file size',
value: '101mb'
}],
saveQuery: false,
queryName: ''
});
- expect(q).toBe('document pdf type:arvados#collection cluster:c97qx is:trashed from:2017-08-01 has:filesize:101mb');
+ expect(q).toBe('document pdf type:arvados#collection cluster:c97qx is:trashed from:2017-08-01 has:"file size":"101mb"');
});
});
});
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>(),
const currentView = getState().searchBar.currentView;
if (searchValue || currentView === SearchView.ADVANCED) {
- const sq = parseSearchQuery(searchValue);
- const clusterId = getSearchQueryFirstProp(sq, 'cluster');
+ const { cluster: clusterId } = getAdvancedDataFromQuery(searchValue);
const sessions = getSearchSessions(clusterId, getState().auth.sessions);
const lists: ListResults<GroupContentsResource>[] = await Promise.all(sessions.map(session => {
- const filters = searchQueryToFilters(sq);
+ const filters = queryToFilters(searchValue);
return services.groupsService.contents('', {
filters,
limit,
['type', 'type'],
['cluster', 'cluster'],
['project', 'projectUuid'],
- ['is:trashed', 'inTrash'],
+ [`is:${parser.States.TRASHED}`, 'inTrash'],
['from', 'dateFrom'],
['to', 'dateTo']
];
return value;
};
-export class ParseSearchQuery {
- hasKeywords: boolean;
- values: string[];
- properties: {
- [key: string]: string[]
- };
-}
-
-export const parseSearchQuery: (query: string) => ParseSearchQuery = (searchValue: string) => {
- const keywords = [
- 'type:',
- 'cluster:',
- 'project:',
- 'is:',
- 'from:',
- 'to:',
- 'has:'
- ];
-
- const hasKeywords = (search: string) => keywords.reduce((acc, keyword) => acc + (search.includes(keyword) ? 1 : 0), 0);
- let keywordsCnt = 0;
-
- const properties = {};
-
- keywords.forEach(k => {
- if (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 getSearchQueryFirstProp = (sq: ParseSearchQuery, name: string) => sq.properties[name] && sq.properties[name][0];
-export const getSearchQueryPropValue = (sq: ParseSearchQuery, name: string, value: string) => sq.properties[name] && sq.properties[name].find((v: string) => v === value);
-export const getSearchQueryProperties = (sq: ParseSearchQuery): PropertyValue[] => {
- if (sq.properties.has) {
- return sq.properties.has.map((value: string) => {
- const v = value.split(':');
- return {
- key: v[0],
- value: v[1]
- };
- });
- }
- return [];
-};
-
export const getAdvancedDataFromQuery = (query: string): SearchBarAdvanceFormData => {
- const sq = parseSearchQuery(query);
-
+ const { tokens, searchString } = parser.parseSearchQuery(query);
+ const getValue = parser.getValue(tokens);
return {
- searchValue: sq.values.join(' '),
- type: getSearchQueryFirstProp(sq, 'type') as ResourceKind,
- cluster: getSearchQueryFirstProp(sq, 'cluster'),
- projectUuid: getSearchQueryFirstProp(sq, 'project'),
- inTrash: getSearchQueryPropValue(sq, 'is', 'trashed') !== undefined,
- dateFrom: getSearchQueryFirstProp(sq, 'from'),
- dateTo: getSearchQueryFirstProp(sq, 'to'),
- properties: getSearchQueryProperties(sq),
+ 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: ''
};
return sessions.filter(s => s.loggedIn && (!clusterId || s.clusterId === clusterId));
};
-export const searchQueryToFilters = (sq: ParseSearchQuery): string => {
+export const queryToFilters = (query: string) => {
+ const data = getAdvancedDataFromQuery(query);
const filter = new FilterBuilder();
- const resourceKind = getSearchQueryFirstProp(sq, 'type') as ResourceKind;
+ const resourceKind = data.type;
- const projectUuid = getSearchQueryFirstProp(sq, 'project');
- if (projectUuid) {
- filter.addEqual('ownerUuid', projectUuid);
+ if (data.projectUuid) {
+ filter.addEqual('ownerUuid', data.projectUuid);
}
- const dateFrom = getSearchQueryFirstProp(sq, 'from');
- if (dateFrom) {
- filter.addGte('modified_at', buildDateFilter(dateFrom));
+ if (data.dateFrom) {
+ filter.addGte('modified_at', buildDateFilter(data.dateFrom));
}
- const dateTo = getSearchQueryFirstProp(sq, 'to');
- if (dateTo) {
- filter.addLte('modified_at', buildDateFilter(dateTo));
+ if (data.dateTo) {
+ filter.addLte('modified_at', buildDateFilter(data.dateTo));
}
- const props = getSearchQueryProperties(sq);
- props.forEach(p => {
+ data.properties.forEach(p => {
if (p.value) {
filter
.addILike(`properties.${p.key}`, p.value, GroupContentsResourcePrefix.PROJECT)
return filter
.addIsA("uuid", buildUuidFilter(resourceKind))
- .addFullTextSearch(sq.values.join(' '))
+ .addFullTextSearch(data.searchValue)
.getFilters();
};
import { ListResults } from '~/services/common-service/common-service';
import { searchResultsPanelActions } from '~/store/search-results-panel/search-results-panel-actions';
import {
- getSearchQueryFirstProp,
- getSearchSessions, ParseSearchQuery,
- parseSearchQuery,
- searchQueryToFilters,
- getSearchQueryPropValue
+ getSearchSessions,
+ queryToFilters,
+ getAdvancedDataFromQuery
} from '~/store/search-bar/search-bar-actions';
import { getSortColumn } from "~/store/data-explorer/data-explorer-reducer";
import { joinFilters } from '~/services/api/filter-builder';
const state = api.getState();
const dataExplorer = getDataExplorer(state.dataExplorer, this.getId());
const searchValue = state.searchBar.searchValue;
- const sq = parseSearchQuery(searchValue);
- const clusterId = getSearchQueryFirstProp(sq, 'cluster');
+ const { cluster: clusterId } = getAdvancedDataFromQuery(searchValue);
const sessions = getSearchSessions(clusterId, state.auth.sessions);
if (searchValue.trim() === '') {
}
try {
- const params = getParams(dataExplorer, sq);
+ const params = getParams(dataExplorer, searchValue);
const responses = await Promise.all(sessions.map(session =>
this.services.groupsService.contents('', params, session)
const typeFilters = (columns: DataColumns<string>) => serializeResourceTypeFilters(getDataExplorerColumnFilters(columns, ProjectPanelColumnNames.TYPE));
-export const getParams = (dataExplorer: DataExplorer, sq: ParseSearchQuery) => ({
+export const getParams = (dataExplorer: DataExplorer, query: string) => ({
...dataExplorerToListParams(dataExplorer),
filters: joinFilters(
- searchQueryToFilters(sq),
+ queryToFilters(query),
typeFilters(dataExplorer.columns)
),
order: getOrder(dataExplorer),
- includeTrash: (!!getSearchQueryPropValue(sq, 'is', 'trashed')) || false
+ includeTrash: getAdvancedDataFromQuery(query).inTrash
});
const getOrder = (dataExplorer: DataExplorer) => {