diff --git a/demo-shell-ng2/app/components/search/search-bar.component.html b/demo-shell-ng2/app/components/search/search-bar.component.html index b890c54210..2533df52c2 100644 --- a/demo-shell-ng2/app/components/search/search-bar.component.html +++ b/demo-shell-ng2/app/components/search/search-bar.component.html @@ -1,5 +1,12 @@ - + +

Search results

- + diff --git a/ng2-components/ng2-alfresco-search/README.md b/ng2-components/ng2-alfresco-search/README.md index 7715253afc..ac68aeb226 100644 --- a/ng2-components/ng2-alfresco-search/README.md +++ b/ng2-components/ng2-alfresco-search/README.md @@ -180,9 +180,13 @@ bootstrap(SearchDemo, [ **searchTerm**: {string} (optional) default "". Search term to pre-populate the field with
**inputType**: {string} (optional) default "text". Type of the input field to render, e.g. "search" or "text" (default)
-**expandable** {boolean} (optional) default true. Whether to use an expanding search control, if false then a regular input is used. -**autocomplete** {boolean} (optional) default true. Whether the browser should offer field auto-completion for the input field to the user. -**autocompleteEnabled** {boolean} (optional) default true. Whether find-as-you-type suggestions should be offered for matching content items. Set to false to disable. +**expandable** {boolean} (optional) default true. Whether to use an expanding search control, if false then a regular input is used.
+**autocomplete** {boolean} (optional) default true. Whether the browser should offer field auto-completion for the input field to the user.
+**liveSearchEnabled** {boolean} (optional) default true. Whether find-as-you-type suggestions should be offered for matching content items. Set to false to disable.
+**liveSearchRoot** {boolean} (optional) default "-root-". NodeRef or node name where the search should start.
+**liveSearchResultType** {boolean} (optional) default (none). Node type to filter live search results by, e.g. 'cm:content'.
+**liveSearchMaxResults** {boolean} (optional) default 5. Maximum number of results to show in the live search.
+**liveSearchResultSort** {boolean} (optional) default (none). Criteria to sort live search results by, must be one of "name" , "modifiedAt" or "createdAt" ### Search results @@ -337,6 +341,10 @@ None **searchTerm**: {string} (optional) default "". Search term to use when executing the search. Updating this value will run a new search and update the results.
+**rootNodeId** {boolean} (optional) default "-root-". NodeRef or node name where the search should start.
+**resultType** {boolean} (optional) default (none). Node type to filter search results by, e.g. 'cm:content'.
+**maxResults** {boolean} (optional) default 20. Maximum number of results to show in the search.
+**resultSort** {boolean} (optional) default (none). Criteria to sort search results by, must be one of "name" , "modifiedAt" or "createdAt" ## Build from sources diff --git a/ng2-components/ng2-alfresco-search/src/assets/alfresco-search.service.mock.ts b/ng2-components/ng2-alfresco-search/src/assets/alfresco-search.service.mock.ts index 0f15f11a81..22e61c1090 100644 --- a/ng2-components/ng2-alfresco-search/src/assets/alfresco-search.service.mock.ts +++ b/ng2-components/ng2-alfresco-search/src/assets/alfresco-search.service.mock.ts @@ -56,8 +56,8 @@ export var fakeError = { export var fakeApi = { core: { - searchApi: { - liveSearchNodes: (term, opts) => Promise.resolve(fakeSearch) + queriesApi: { + findNodes: (term, opts) => Promise.resolve(fakeSearch) } } }; diff --git a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.spec.ts b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.spec.ts index 43f9e89abb..86344e5d0c 100644 --- a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.spec.ts +++ b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.spec.ts @@ -80,7 +80,7 @@ describe('AlfrescoSearchAutocompleteComponent', () => { it('should clear results straight away when a new search term is entered', async(() => { - spyOn(searchService, 'getSearchNodesPromise') + spyOn(searchService, 'getQueryNodesPromise') .and.returnValue(Promise.resolve(result)); component.searchTerm = 'searchTerm'; @@ -97,7 +97,7 @@ describe('AlfrescoSearchAutocompleteComponent', () => { it('should display the returned search results', (done) => { - spyOn(searchService, 'getSearchNodesPromise') + spyOn(searchService, 'getQueryNodesPromise') .and.returnValue(Promise.resolve(result)); component.resultsLoad.subscribe(() => { @@ -112,7 +112,7 @@ describe('AlfrescoSearchAutocompleteComponent', () => { it('should limit the number of returned search results to the configured maximum', (done) => { - spyOn(searchService, 'getSearchNodesPromise') + spyOn(searchService, 'getQueryNodesPromise') .and.returnValue(Promise.resolve(results)); component.resultsLoad.subscribe(() => { @@ -127,7 +127,7 @@ describe('AlfrescoSearchAutocompleteComponent', () => { it('should display the correct thumbnail for result items', (done) => { - spyOn(searchService, 'getSearchNodesPromise') + spyOn(searchService, 'getQueryNodesPromise') .and.returnValue(Promise.resolve(result)); component.baseComponentPath = 'http://localhost'; @@ -150,7 +150,7 @@ describe('AlfrescoSearchAutocompleteComponent', () => { it('should display no result if no result are returned', (done) => { - spyOn(searchService, 'getSearchNodesPromise') + spyOn(searchService, 'getQueryNodesPromise') .and.returnValue(Promise.resolve(noResult)); component.resultsLoad.subscribe(() => { @@ -170,7 +170,7 @@ describe('AlfrescoSearchAutocompleteComponent', () => { beforeEach(() => { searchService = fixture.debugElement.injector.get(AlfrescoSearchService); - spyOn(searchService, 'getSearchNodesPromise') + spyOn(searchService, 'getQueryNodesPromise') .and.returnValue(Promise.reject(errorJson)); }); @@ -215,7 +215,7 @@ describe('AlfrescoSearchAutocompleteComponent', () => { it('should emit file select when file item clicked', (done) => { - spyOn(searchService, 'getSearchNodesPromise') + spyOn(searchService, 'getQueryNodesPromise') .and.returnValue(Promise.resolve(result)); component.resultsLoad.subscribe(() => { @@ -232,7 +232,7 @@ describe('AlfrescoSearchAutocompleteComponent', () => { it('should not emit preview if a non-file item is clicked', (done) => { - spyOn(searchService, 'getSearchNodesPromise') + spyOn(searchService, 'getQueryNodesPromise') .and.returnValue(Promise.resolve(folderResult)); spyOn(component.fileSelect, 'emit'); @@ -254,7 +254,7 @@ describe('AlfrescoSearchAutocompleteComponent', () => { beforeEach(() => { searchService = fixture.debugElement.injector.get(AlfrescoSearchService); - spyOn(searchService, 'getSearchNodesPromise') + spyOn(searchService, 'getQueryNodesPromise') .and.returnValue(Promise.resolve(results)); }); @@ -366,7 +366,7 @@ describe('AlfrescoSearchAutocompleteComponent', () => { beforeEach(() => { searchService = fixture.debugElement.injector.get(AlfrescoSearchService); - spyOn(searchService, 'getSearchNodesPromise') + spyOn(searchService, 'getQueryNodesPromise') .and.returnValue(Promise.resolve(result)); }); diff --git a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.ts b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.ts index 903883f98a..1b94466ebd 100644 --- a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.ts +++ b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-autocomplete.component.ts @@ -16,7 +16,7 @@ */ import { Component, ElementRef, EventEmitter, Input, OnInit, OnChanges, Output, ViewChild } from '@angular/core'; -import { AlfrescoSearchService } from './../services/alfresco-search.service'; +import { AlfrescoSearchService, SearchOptions } from './../services/alfresco-search.service'; import { AlfrescoThumbnailService } from './../services/alfresco-thumbnail.service'; import { AlfrescoTranslationService } from 'ng2-alfresco-core'; @@ -43,6 +43,15 @@ export class AlfrescoSearchAutocompleteComponent implements OnInit, OnChanges { @Input() maxResults: number = 5; + @Input() + resultSort: string = null; + + @Input() + rootNodeId: string = '-root'; + + @Input() + resultType: string = null; + @Output() fileSelect: EventEmitter = new EventEmitter(); @@ -84,9 +93,16 @@ export class AlfrescoSearchAutocompleteComponent implements OnInit, OnChanges { * @param searchTerm Search query entered by user */ private displaySearchResults(searchTerm) { + let searchOpts: SearchOptions = { + include: ['path'], + rootNodeId: this.rootNodeId, + nodeType: this.resultType, + maxItems: this.maxResults, + orderBy: this.resultSort + }; if (searchTerm !== null && searchTerm !== '') { this.alfrescoSearchService - .getLiveSearchResults(searchTerm) + .getNodeQueryResults(searchTerm, searchOpts) .subscribe( results => { this.results = results.list.entries.slice(0, this.maxResults); diff --git a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-control.component.html b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-control.component.html index 2ea9201e75..ed950027f0 100644 --- a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-control.component.html +++ b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-control.component.html @@ -22,8 +22,13 @@ - { it('should keep find-as-you-type control visible when user tabs into results', (done) => { let searchService = fixture.debugElement.injector.get(AlfrescoSearchService); - spyOn(searchService, 'getSearchNodesPromise') + spyOn(searchService, 'getQueryNodesPromise') .and.returnValue(Promise.resolve(result)); fixture.detectChanges(); @@ -219,7 +219,7 @@ describe('AlfrescoSearchControlComponent', () => { it('should select the first result in find-as-you-type when down arrow is pressed and FAYT is visible', (done) => { fixture.detectChanges(); - spyOn(component.autocompleteComponent, 'focusResult'); + spyOn(component.liveSearchComponent, 'focusResult'); fixture.detectChanges(); inputEl.dispatchEvent(new Event('focus')); window.setTimeout(() => { // wait for debounce() to complete @@ -228,7 +228,7 @@ describe('AlfrescoSearchControlComponent', () => { key: 'ArrowDown' })); fixture.detectChanges(); - expect(component.autocompleteComponent.focusResult).toHaveBeenCalled(); + expect(component.liveSearchComponent.focusResult).toHaveBeenCalled(); done(); }, 100); }); @@ -254,7 +254,7 @@ describe('AlfrescoSearchControlComponent', () => { }); it('should NOT display a find-as-you-type control when configured not to', () => { - fixture.componentInstance.autocompleteEnabled = false; + fixture.componentInstance.liveSearchEnabled = false; fixture.detectChanges(); let autocomplete: Element = element.querySelector('alfresco-search-autocomplete'); expect(autocomplete).toBeNull(); diff --git a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-control.component.ts b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-control.component.ts index 0657cf121f..ef0825a858 100644 --- a/ng2-components/ng2-alfresco-search/src/components/alfresco-search-control.component.ts +++ b/ng2-components/ng2-alfresco-search/src/components/alfresco-search-control.component.ts @@ -56,16 +56,29 @@ export class AlfrescoSearchControlComponent implements OnInit, OnDestroy { searchControl: FormControl; - @ViewChild('searchInput', {}) searchInput: ElementRef; + @ViewChild('searchInput', {}) + searchInput: ElementRef; - @ViewChild('autocomplete') - autocompleteComponent: AlfrescoSearchAutocompleteComponent; + @ViewChild(AlfrescoSearchAutocompleteComponent) + liveSearchComponent: AlfrescoSearchAutocompleteComponent; @Input() - autocompleteEnabled = true; + liveSearchEnabled: boolean = true; @Input() - autocompleteSearchTerm = ''; + liveSearchTerm: string = ''; + + @Input() + liveSearchRoot: string = '-root-'; + + @Input() + liveSearchResultType: string = 'cm:content'; + + @Input() + liveSearchResultSort: string = null; + + @Input() + liveSearchMaxResults: number = 5; searchActive = false; @@ -99,7 +112,7 @@ export class AlfrescoSearchControlComponent implements OnInit, OnDestroy { private onSearchTermChange(value: string): void { this.setAutoCompleteDisplayed(true); - this.autocompleteSearchTerm = value; + this.liveSearchTerm = value; this.searchControl.setValue(value, true); this.searchValid = this.searchControl.valid; this.searchChange.emit({ @@ -195,7 +208,7 @@ export class AlfrescoSearchControlComponent implements OnInit, OnDestroy { onArrowDown(): void { if (this.isAutoCompleteDisplayed()) { - this.autocompleteComponent.focusResult(); + this.liveSearchComponent.focusResult(); } else { this.setAutoCompleteDisplayed(true); } diff --git a/ng2-components/ng2-alfresco-search/src/components/alfresco-search.component.spec.ts b/ng2-components/ng2-alfresco-search/src/components/alfresco-search.component.spec.ts index 8489511c7d..412caff412 100644 --- a/ng2-components/ng2-alfresco-search/src/components/alfresco-search.component.spec.ts +++ b/ng2-components/ng2-alfresco-search/src/components/alfresco-search.component.spec.ts @@ -154,17 +154,40 @@ describe('AlfrescoSearchComponent', () => { expect(translationService.addTranslationFolder).toHaveBeenCalledWith('node_modules/ng2-alfresco-search/dist/src'); }); - describe('Rendering search results', () => { + describe('Search results', () => { + + it('should call search service with the correct parameters', (done) => { + let searchTerm = 'searchTerm63688', options = { + include: ['path'], + rootNodeId: '-my-', + nodeType: 'my:type', + maxItems: 20, + orderBy: null + }; + component.searchTerm = searchTerm; + component.rootNodeId = '-my-'; + component.resultType = 'my:type'; + let searchService = fixture.debugElement.injector.get(AlfrescoSearchService); + spyOn(searchService, 'getQueryNodesPromise') + .and.returnValue(Promise.resolve(result)); + fixture.detectChanges(); + + component.resultsLoad.subscribe(() => { + fixture.detectChanges(); + expect(searchService.getQueryNodesPromise).toHaveBeenCalledWith(searchTerm, options); + done(); + }); + }); it('should display search results when a search term is provided', (done) => { let searchService = fixture.debugElement.injector.get(AlfrescoSearchService); - spyOn(searchService, 'getSearchNodesPromise') + spyOn(searchService, 'getQueryNodesPromise') .and.returnValue(Promise.resolve(result)); component.resultsLoad.subscribe(() => { fixture.detectChanges(); - expect(searchService.getSearchNodesPromise).toHaveBeenCalled(); + expect(searchService.getQueryNodesPromise).toHaveBeenCalled(); expect(element.querySelector('#result_user_0')).not.toBeNull(); expect(element.querySelector('#result_user_0').innerHTML).toBe('John Doe'); expect(element.querySelector('#result_name_0').innerHTML).toBe('MyDoc'); @@ -178,7 +201,7 @@ describe('AlfrescoSearchComponent', () => { it('should display no result if no result are returned', (done) => { let searchService = fixture.debugElement.injector.get(AlfrescoSearchService); - spyOn(searchService, 'getSearchNodesPromise') + spyOn(searchService, 'getQueryNodesPromise') .and.returnValue(Promise.resolve(noResult)); component.resultsLoad.subscribe(() => { @@ -194,7 +217,7 @@ describe('AlfrescoSearchComponent', () => { it('should display an error if an error is encountered running the search', (done) => { let searchService = fixture.debugElement.injector.get(AlfrescoSearchService); - spyOn(searchService, 'getSearchNodesPromise') + spyOn(searchService, 'getQueryNodesPromise') .and.returnValue(Promise.reject(errorJson)); component.resultsLoad.subscribe(() => {}, () => { @@ -214,12 +237,12 @@ describe('AlfrescoSearchComponent', () => { it('should update search results when the search term input is changed', (done) => { let searchService = fixture.debugElement.injector.get(AlfrescoSearchService); - spyOn(searchService, 'getSearchNodesPromise') + spyOn(searchService, 'getQueryNodesPromise') .and.returnValue(Promise.resolve(result)); component.resultsLoad.subscribe(() => { fixture.detectChanges(); - expect(searchService.getSearchNodesPromise).toHaveBeenCalledWith('searchTerm2'); + expect(searchService.getQueryNodesPromise.calls.mostRecent().args[0]).toBe('searchTerm2'); expect(element.querySelector('#result_user_0')).not.toBeNull(); expect(element.querySelector('#result_user_0').innerHTML).toBe('John Doe'); expect(element.querySelector('#result_name_0').innerHTML).toBe('MyDoc'); @@ -235,7 +258,7 @@ describe('AlfrescoSearchComponent', () => { it('should emit preview when file item clicked', (done) => { let searchService = fixture.debugElement.injector.get(AlfrescoSearchService); - spyOn(searchService, 'getSearchNodesPromise') + spyOn(searchService, 'getQueryNodesPromise') .and.returnValue(Promise.resolve(result)); component.resultsLoad.subscribe(() => { @@ -254,7 +277,7 @@ describe('AlfrescoSearchComponent', () => { it('should not emit preview when non-file item is clicked', (done) => { let searchService = fixture.debugElement.injector.get(AlfrescoSearchService); - spyOn(searchService, 'getSearchNodesPromise') + spyOn(searchService, 'getQueryNodesPromise') .and.returnValue(Promise.resolve(folderResult)); spyOn(component.preview, 'emit'); diff --git a/ng2-components/ng2-alfresco-search/src/components/alfresco-search.component.ts b/ng2-components/ng2-alfresco-search/src/components/alfresco-search.component.ts index cd71b4617a..e02a5c7c40 100644 --- a/ng2-components/ng2-alfresco-search/src/components/alfresco-search.component.ts +++ b/ng2-components/ng2-alfresco-search/src/components/alfresco-search.component.ts @@ -17,7 +17,7 @@ import { Component, EventEmitter, Input, Output, Optional, OnChanges, SimpleChanges, OnInit } from '@angular/core'; import { ActivatedRoute, Params } from '@angular/router'; -import { AlfrescoSearchService } from './../services/alfresco-search.service'; +import { AlfrescoSearchService, SearchOptions } from './../services/alfresco-search.service'; import { AlfrescoThumbnailService } from './../services/alfresco-thumbnail.service'; import { AlfrescoTranslationService } from 'ng2-alfresco-core'; @@ -34,6 +34,18 @@ export class AlfrescoSearchComponent implements OnChanges, OnInit { @Input() searchTerm: string = ''; + @Input() + maxResults: number = 20; + + @Input() + resultSort: string = null; + + @Input() + rootNodeId: string = '-root-'; + + @Input() + resultType: string = null; + @Output() preview: EventEmitter = new EventEmitter(); @@ -102,10 +114,17 @@ export class AlfrescoSearchComponent implements OnChanges, OnInit { * Loads and displays search results * @param searchTerm Search query entered by user */ - public displaySearchResults(searchTerm): void { - if (searchTerm !== null && this.alfrescoSearchService !== null) { + private displaySearchResults(searchTerm): void { + if (searchTerm && this.alfrescoSearchService) { + let searchOpts: SearchOptions = { + include: ['path'], + rootNodeId: this.rootNodeId, + nodeType: this.resultType, + maxItems: this.maxResults, + orderBy: this.resultSort + }; this.alfrescoSearchService - .getLiveSearchResults(searchTerm) + .getNodeQueryResults(searchTerm, searchOpts) .subscribe( results => { this.results = results.list.entries; diff --git a/ng2-components/ng2-alfresco-search/src/services/alfresco-search.service.spec.ts b/ng2-components/ng2-alfresco-search/src/services/alfresco-search.service.spec.ts index 814737874d..eb68e3d80c 100644 --- a/ng2-components/ng2-alfresco-search/src/services/alfresco-search.service.spec.ts +++ b/ng2-components/ng2-alfresco-search/src/services/alfresco-search.service.spec.ts @@ -40,23 +40,34 @@ describe('AlfrescoSearchService', () => { spyOn(authenticationService, 'getAlfrescoApi').and.returnValue(fakeApi); }); - it('should call search API with the correct parameters', (done) => { + it('should call search API with no additional options', (done) => { + let searchTerm = 'searchTerm63688'; + spyOn(fakeApi.core.queriesApi, 'findNodes').and.returnValue(Promise.resolve(fakeSearch)); + service.getNodeQueryResults(searchTerm).subscribe( + () => { + expect(fakeApi.core.queriesApi.findNodes).toHaveBeenCalledWith(searchTerm, undefined); + done(); + } + ); + }); + + it('should call search API with additional options', (done) => { let searchTerm = 'searchTerm63688', options = { include: [ 'path' ], rootNodeId: '-root-', nodeType: 'cm:content' }; - spyOn(fakeApi.core.searchApi, 'liveSearchNodes').and.returnValue(Promise.resolve(fakeSearch)); - service.getLiveSearchResults(searchTerm).subscribe( + spyOn(fakeApi.core.queriesApi, 'findNodes').and.returnValue(Promise.resolve(fakeSearch)); + service.getNodeQueryResults(searchTerm, options).subscribe( () => { - expect(fakeApi.core.searchApi.liveSearchNodes).toHaveBeenCalledWith(searchTerm, options); + expect(fakeApi.core.queriesApi.findNodes).toHaveBeenCalledWith(searchTerm, options); done(); } ); }); it('should return search results returned from the API', (done) => { - service.getLiveSearchResults('').subscribe( + service.getNodeQueryResults('').subscribe( (res: any) => { expect(res).toBeDefined(); expect(res).toEqual(fakeSearch); @@ -66,8 +77,8 @@ describe('AlfrescoSearchService', () => { }); it('should notify errors returned from the API', (done) => { - spyOn(fakeApi.core.searchApi, 'liveSearchNodes').and.returnValue(Promise.reject(fakeError)); - service.getLiveSearchResults('').subscribe( + spyOn(fakeApi.core.queriesApi, 'findNodes').and.returnValue(Promise.reject(fakeError)); + service.getNodeQueryResults('').subscribe( () => {}, (res: any) => { expect(res).toBeDefined(); @@ -78,8 +89,8 @@ describe('AlfrescoSearchService', () => { }); it('should notify a general error if the API does not return a specific error', (done) => { - spyOn(fakeApi.core.searchApi, 'liveSearchNodes').and.returnValue(Promise.reject(null)); - service.getLiveSearchResults('').subscribe( + spyOn(fakeApi.core.queriesApi, 'findNodes').and.returnValue(Promise.reject(null)); + service.getNodeQueryResults('').subscribe( () => {}, (res: any) => { expect(res).toBeDefined(); diff --git a/ng2-components/ng2-alfresco-search/src/services/alfresco-search.service.ts b/ng2-components/ng2-alfresco-search/src/services/alfresco-search.service.ts index caf5308d34..203d3b48a9 100644 --- a/ng2-components/ng2-alfresco-search/src/services/alfresco-search.service.ts +++ b/ng2-components/ng2-alfresco-search/src/services/alfresco-search.service.ts @@ -32,25 +32,30 @@ export class AlfrescoSearchService { * Execute a search against the repository * * @param term Search term + * @param options Additional options passed to the search * @returns {Observable} Search results */ - public getLiveSearchResults(term: string): Observable { - return Observable.fromPromise(this.getSearchNodesPromise(term)) + public getNodeQueryResults(term: string, options?: SearchOptions): Observable { + return Observable.fromPromise(this.getQueryNodesPromise(term, options)) .map(res => res) .catch(this.handleError); } - private getSearchNodesPromise(term: string) { - let nodeId = '-root-'; - let opts = { - include: ['path'], - rootNodeId: nodeId, - nodeType: 'cm:content' - }; - return this.authService.getAlfrescoApi().core.searchApi.liveSearchNodes(term, opts); + private getQueryNodesPromise(term: string, opts: SearchOptions) { + return this.authService.getAlfrescoApi().core.queriesApi.findNodes(term, opts); } private handleError(error: any): Observable { return Observable.throw(error || 'Server error'); } } + +export interface SearchOptions { + skipCount?: number; + maxItems?: number; + rootNodeId?: string; + nodeType?: string; + include?: string[]; + orderBy?: string; + fields?: string[]; +}