[ACA-2219] support more precise searching (#976)

* support more precise searching

* exact term matching

* remove fdescribe

* update docs
This commit is contained in:
Denys Vuika
2019-02-27 04:25:50 +00:00
committed by Cilibiu Bogdan
parent f25eef599d
commit f58b6e5a9f
4 changed files with 151 additions and 10 deletions

View File

@@ -18,3 +18,34 @@ This page consists of the following ADF components:
- [Toolbar with basic actions](/features/document-list-layout#actions-and-the-actions-toolbar) like `Preview`, `Download`, `Favorite`, `Copy`, etc.
And also the Info Drawer, Toolbar and Node Selector dialogs for copy and move operations.
## Alfresco Full Text Search
The following table describes current support of the
[Alfresco Full Text Search](http://docs.alfresco.com/6.0/concepts/rm-searchsyntax-intro.html) (FTS) syntax
in the Content Application when using **Search Input** component.
| Feature | Full | Partial | N/A | Details |
| ---------------------------------------------------------------- | ---- | ------- | --- | ---------------------------------------------------------------------------------- |
| Search for a single term | X | | | [Docs](https://docs.alfresco.com/6.0/concepts/rm-searchsyntax-single.html) |
| Search for a phrase | | X | | [Docs](https://docs.alfresco.com/6.0/concepts/rm-searchsyntax-phrase.html) |
| Search for an exact term | X | | | [Docs](https://docs.alfresco.com/6.0/concepts/rm-searchsyntax-exact.html) |
| Search for term expansion | | | X | [Docs](https://docs.alfresco.com/6.0/concepts/rm-searchsyntax-term.html) |
| Search for conjunctions | X | | | [Docs](https://docs.alfresco.com/6.0/concepts/rm-searchsyntax-conjunct.html) |
| Search for disjunctions | X | | | [Docs](https://docs.alfresco.com/6.0/concepts/rm-searchsyntax-disjunct.html) |
| Search for negation | | | X | [Docs](https://docs.alfresco.com/6.0/concepts/rm-searchsyntax-negate.html) |
| Search for optional, mandatory, and excluded elements of a query | | | X | [Docs](https://docs.alfresco.com/6.0/concepts/rm-searchsyntax-optional.html) |
| Search in fields | | X | | [Docs](https://docs.alfresco.com/6.0/concepts/rm-searchsyntax-fields.html) |
| Search for wildcards | | X | | [Docs](https://docs.alfresco.com/6.0/concepts/rm-searchsyntax-wildcards.html) |
| Search for ranges | | | X | [Docs](https://docs.alfresco.com/6.0/concepts/rm-searchsyntax-ranges.html) |
| Search for fuzzy matching | | | X | [Docs](https://docs.alfresco.com/6.0/concepts/rm-searchsyntax-fuzzy.html) |
| Search for proximity | | | X | [Docs](https://docs.alfresco.com/6.0/concepts/rm-searchsyntax-proximity.html) |
| Search for boosts | | | X | [Docs](https://docs.alfresco.com/6.0/concepts/rm-searchsyntax-boosts.html) |
| Search for grouping | | | X | [Docs](https://docs.alfresco.com/6.0/concepts/rm-searchsyntax-grouping.html) |
| Search for spans and positions | | | X | [Docs](https://docs.alfresco.com/6.0/concepts/rm-searchsyntax-spans.html) |
| Escaping characters | | | X | [Docs](https://docs.alfresco.com/6.0/concepts/rm-searchsyntax-escaping.html) |
| Mixed FTS ID behavior | | | X | [Docs](https://docs.alfresco.com/6.0/concepts/rm-searchsyntax-ftsid.html) |
| Search for operator precedence | | | X | [Docs](https://docs.alfresco.com/6.0/concepts/rm-searchsyntax-precedence.html) |
| Search query templates | | | X | [Docs](https://docs.alfresco.com/6.0/concepts/rm-searchsyntax-querytemplates.html) |
| Search query literals | | | X | [Docs](https://docs.alfresco.com/6.0/concepts/rm-searchsyntax-literals.html) |
| Search using date math | | | X | [Docs](https://docs.alfresco.com/6.0/concepts/rm-searchsyntax-datemaths.html) |

View File

@@ -119,10 +119,6 @@
"cm:name",
"cm:title",
"cm:description",
"ia:whatEvent",
"ia:descriptionEvent",
"lnk:title",
"lnk:description",
"TEXT",
"TAG"
],

View File

@@ -35,11 +35,16 @@ describe('SearchComponent', () => {
expect(component.formatSearchQuery('')).toBeNull();
});
it('should use original user input if content contains colons', () => {
it('should use original user input if text contains colons', () => {
const query = 'TEXT:test OR TYPE:folder';
expect(component.formatSearchQuery(query)).toBe(query);
});
it('should use original user input if text contains quotes', () => {
const query = `"Hello World"`;
expect(component.formatSearchQuery(query)).toBe(query);
});
it('should format user input according to the configuration fields', () => {
config.config = {
search: {
@@ -48,7 +53,7 @@ describe('SearchComponent', () => {
};
const query = component.formatSearchQuery('hello');
expect(query).toBe(`cm:name:"hello*" OR cm:title:"hello*"`);
expect(query).toBe(`(cm:name:"hello*" OR cm:title:"hello*")`);
});
it('should format user input as cm:name if configuration not provided', () => {
@@ -59,7 +64,75 @@ describe('SearchComponent', () => {
};
const query = component.formatSearchQuery('hello');
expect(query).toBe(`cm:name:"hello*"`);
expect(query).toBe(`(cm:name:"hello*")`);
});
it('should use AND operator when conjunction has no operators', () => {
config.config = {
search: {
'aca:fields': ['cm:name']
}
};
const query = component.formatSearchQuery('big yellow banana');
expect(query).toBe(
`(cm:name:"big*") AND (cm:name:"yellow*") AND (cm:name:"banana*")`
);
});
it('should support conjunctions with AND operator', () => {
config.config = {
search: {
'aca:fields': ['cm:name', 'cm:title']
}
};
const query = component.formatSearchQuery('big AND yellow AND banana');
expect(query).toBe(
`(cm:name:"big*" OR cm:title:"big*") AND (cm:name:"yellow*" OR cm:title:"yellow*") AND (cm:name:"banana*" OR cm:title:"banana*")`
);
});
it('should support conjunctions with OR operator', () => {
config.config = {
search: {
'aca:fields': ['cm:name', 'cm:title']
}
};
const query = component.formatSearchQuery('big OR yellow OR banana');
expect(query).toBe(
`(cm:name:"big*" OR cm:title:"big*") OR (cm:name:"yellow*" OR cm:title:"yellow*") OR (cm:name:"banana*" OR cm:title:"banana*")`
);
});
it('should support exact term matching with default fields', () => {
config.config = {
search: {
'aca:fields': ['cm:name', 'cm:title']
}
};
const query = component.formatSearchQuery('=orange');
expect(query).toBe(`(=cm:name:"orange" OR =cm:title:"orange")`);
});
it('should support exact term matching with operators', () => {
config.config = {
search: {
'aca:fields': ['cm:name', 'cm:title']
}
};
const query = component.formatSearchQuery('=test1.pdf or =test2.pdf');
expect(query).toBe(
`(=cm:name:"test1.pdf" OR =cm:title:"test1.pdf") or (=cm:name:"test2.pdf" OR =cm:title:"test2.pdf")`
);
});
it('should navigate to folder on double click', () => {

View File

@@ -114,6 +114,33 @@ export class SearchResultsComponent extends PageComponent implements OnInit {
}
}
private isOperator(input: string): boolean {
if (input) {
input = input.trim().toUpperCase();
const operators = ['AND', 'OR'];
return operators.includes(input);
}
return false;
}
private formatFields(fields: string[], term: string): string {
let prefix = '';
let suffix = '*';
if (term.startsWith('=')) {
prefix = '=';
suffix = '';
term = term.substring(1);
}
return (
'(' +
fields.map(field => `${prefix}${field}:"${term}${suffix}"`).join(' OR ') +
')'
);
}
formatSearchQuery(userInput: string) {
if (!userInput) {
return null;
@@ -121,14 +148,28 @@ export class SearchResultsComponent extends PageComponent implements OnInit {
userInput = userInput.trim();
if (userInput.includes(':')) {
if (userInput.includes(':') || userInput.includes('"')) {
return userInput;
}
const fields = this.config.get<string[]>('search.aca:fields', ['cm:name']);
const query = fields.map(field => `${field}:"${userInput}*"`).join(' OR ');
const words = userInput.split(' ');
return query;
if (words.length > 1) {
const separator = words.some(this.isOperator) ? ' ' : ' AND ';
return words
.map(term => {
if (this.isOperator(term)) {
return term;
}
return this.formatFields(fields, term);
})
.join(separator);
}
return this.formatFields(fields, userInput);
}
onSearchResultLoaded(nodePaging: NodePaging) {