diff --git a/lib/content-services/content-node-selector/content-node-selector-panel.component.spec.ts b/lib/content-services/content-node-selector/content-node-selector-panel.component.spec.ts index ea412d8c9b..86cb20dd58 100644 --- a/lib/content-services/content-node-selector/content-node-selector-panel.component.spec.ts +++ b/lib/content-services/content-node-selector/content-node-selector-panel.component.spec.ts @@ -18,7 +18,7 @@ import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; -import { MinimalNodeEntryEntity, SiteEntry } from 'alfresco-js-api'; +import { MinimalNodeEntryEntity, SiteEntry, SitePaging } from 'alfresco-js-api'; import { AlfrescoApiService, ContentService, @@ -59,7 +59,9 @@ describe('ContentNodeSelectorComponent', () => { let component: ContentNodeSelectorPanelComponent; let fixture: ComponentFixture; let searchService: SearchService; + let contentNodeSelectorService: ContentNodeSelectorService; let searchSpy: jasmine.Spy; + let cnSearchSpy: jasmine.Spy; let _observer: Observer; @@ -124,6 +126,8 @@ describe('ContentNodeSelectorComponent', () => { component.debounceSearch = 0; searchService = TestBed.get(SearchService); + contentNodeSelectorService = TestBed.get(ContentNodeSelectorService); + cnSearchSpy = spyOn(contentNodeSelectorService, 'search').and.callThrough(); searchSpy = spyOn(searchService, 'searchByQueryBody').and.callFake(() => { return Observable.create((observer: Observer) => { _observer = observer; @@ -259,6 +263,7 @@ describe('ContentNodeSelectorComponent', () => { }); describe('Search functionality', () => { + let getCorrespondingNodeIdsSpy; function defaultSearchOptions(searchTerm, rootNodeId = undefined, skipCount = 0) { @@ -292,6 +297,13 @@ describe('ContentNodeSelectorComponent', () => { spyOn(documentListService, 'getFolderNode').and.returnValue(Promise.resolve(expectedDefaultFolderNode)); spyOn(component.documentList, 'loadFolderNodesByFolderNodeId').and.returnValue(Promise.resolve()); + getCorrespondingNodeIdsSpy = spyOn(component.documentList, 'getCorrespondingNodeIds').and + .callFake(id => { + if (id === '-sites-') { + return new Promise((resolve) => resolve(['123456testId', '09876543testId'])); + } + return new Promise((resolve) => resolve([id])); + }); component.currentFolderId = 'cat-girl-nuku-nuku'; fixture.detectChanges(); @@ -324,9 +336,82 @@ describe('ContentNodeSelectorComponent', () => { component.siteChanged( { entry: { guid: 'namek' } }); - expect(searchSpy.calls.count()).toBe(2, 'Search count should be two after the site change'); - expect(searchSpy.calls.argsFor(1)).toEqual([defaultSearchOptions('vegeta', 'namek')] ); - done(); + fixture.whenStable().then(() => { + expect(searchSpy.calls.count()).toBe(2, 'Search count should be two after the site change'); + expect(searchSpy.calls.argsFor(1)).toEqual([defaultSearchOptions('vegeta', 'namek')] ); + done(); + }); + }, 300); + }); + + it('should call the content node selector\'s search with the right parameters on changing the site selectbox\'s value', (done) => { + typeToSearchBox('vegeta'); + + setTimeout(() => { + expect(cnSearchSpy.calls.count()).toBe(1); + + component.siteChanged( { entry: { guid: '-sites-' } }); + + fixture.whenStable().then(() => { + expect(cnSearchSpy).toHaveBeenCalled(); + expect(cnSearchSpy.calls.count()).toBe(2); + expect(cnSearchSpy).toHaveBeenCalledWith('vegeta', '-sites-', 0, 25); + done(); + }); + }, 300); + }); + + it('should call the content node selector\'s search with the right parameters on changing the site selectbox\'s value from a custom dropdown menu', (done) => { + component.dropdownSiteList = {list: {entries: [ { entry: { guid: '-sites-' } }, { entry: { guid: 'namek' } }]}}; + fixture.detectChanges(); + + typeToSearchBox('vegeta'); + + setTimeout(() => { + expect(cnSearchSpy.calls.count()).toBe(1); + + component.siteChanged( { entry: { guid: '-sites-' } }); + + fixture.whenStable().then(() => { + expect(cnSearchSpy).toHaveBeenCalled(); + expect(cnSearchSpy.calls.count()).toBe(2); + expect(cnSearchSpy).toHaveBeenCalledWith('vegeta', '-sites-', 0, 25, ['123456testId', '09876543testId']); + done(); + }); + }, 300); + }); + + it('should get the corresponding node ids before the search call on changing the site selectbox\'s value from a custom dropdown menu', (done) => { + component.dropdownSiteList = {list: {entries: [ { entry: { guid: '-sites-' } }, { entry: { guid: 'namek' } }]}}; + fixture.detectChanges(); + + typeToSearchBox('vegeta'); + + setTimeout(() => { + expect(getCorrespondingNodeIdsSpy.calls.count()).toBe(1, 'getCorrespondingNodeIdsSpy calls count should be one after only one search'); + + component.siteChanged( { entry: { guid: 'namek' } }); + + fixture.whenStable().then(() => { + expect(getCorrespondingNodeIdsSpy.calls.count()).toBe(2, 'getCorrespondingNodeIdsSpy calls count should be two after the site change'); + expect(getCorrespondingNodeIdsSpy.calls.allArgs()).toEqual([[undefined], ['namek']]); + done(); + }); + }, 300); + }); + + it('should NOT get the corresponding node ids before the search call on changing the site selectbox\'s value from default dropdown menu', (done) => { + typeToSearchBox('vegeta'); + + setTimeout(() => { + expect(getCorrespondingNodeIdsSpy.calls.count()).toBe(0, 'getCorrespondingNodeIdsSpy should not be called'); + + component.siteChanged( { entry: { guid: 'namek' } }); + + fixture.whenStable().then(() => { + expect(getCorrespondingNodeIdsSpy).not.toHaveBeenCalled(); + done(); + }); }, 300); }); @@ -523,7 +608,9 @@ describe('ContentNodeSelectorComponent', () => { component.getNextPageOfSearch({ skipCount }); - expect(searchSpy).toHaveBeenCalledWith(defaultSearchOptions('kakarot', undefined, skipCount)); + fixture.whenStable().then(() => { + expect(searchSpy).toHaveBeenCalledWith(defaultSearchOptions('kakarot', undefined, skipCount)); + }); }); it('should be shown when pagination\'s hasMoreItems is true', () => { diff --git a/lib/content-services/content-node-selector/content-node-selector-panel.component.ts b/lib/content-services/content-node-selector/content-node-selector-panel.component.ts index 77122bc457..0e49b6fdb6 100644 --- a/lib/content-services/content-node-selector/content-node-selector-panel.component.ts +++ b/lib/content-services/content-node-selector/content-node-selector-panel.component.ts @@ -220,8 +220,19 @@ export class ContentNodeSelectorPanelComponent implements OnInit { private querySearch(): void { this.loadingSearchResults = true; - this.contentNodeSelectorService.search(this.searchTerm, this.siteId, this.skipCount, this.pageSize) - .subscribe(this.showSearchResults.bind(this)); + if (this.dropdownSiteList) { + this.documentList.getCorrespondingNodeIds(this.siteId) + .then(nodeIds => { + this.contentNodeSelectorService.search(this.searchTerm, this.siteId, this.skipCount, this.pageSize, nodeIds) + .subscribe(this.showSearchResults.bind(this)); + }) + .catch(() => { + this.showSearchResults({list: {entries: []}}); + }); + } else { + this.contentNodeSelectorService.search(this.searchTerm, this.siteId, this.skipCount, this.pageSize) + .subscribe(this.showSearchResults.bind(this)); + } } /** diff --git a/lib/content-services/content-node-selector/content-node-selector.service.spec.ts b/lib/content-services/content-node-selector/content-node-selector.service.spec.ts index 9669449206..466a30fda0 100644 --- a/lib/content-services/content-node-selector/content-node-selector.service.spec.ts +++ b/lib/content-services/content-node-selector/content-node-selector.service.spec.ts @@ -103,4 +103,22 @@ describe('ContentNodeSelectorService', () => { expect(search.query.filterQueries).not.toContain({ query: 'ANCESTOR:\'workspace://SpacesStore/null\'' }); }); + + it('should filter for the extra provided ancestors if defined', () => { + service.search('nuka cola quantum', 'diamond-city', 0, 25, ['extra-diamond-city']); + + expect(search.query.filterQueries).toContain({ query: 'ANCESTOR:\'workspace://SpacesStore/diamond-city\' OR ANCESTOR:\'workspace://SpacesStore/extra-diamond-city\'' }); + }); + + it('should NOT filter for extra ancestors if an empty list of ids is provided', () => { + service.search('nuka cola quantum', 'diamond-city', 0, 25, []); + + expect(search.query.filterQueries).toContain({ query: 'ANCESTOR:\'workspace://SpacesStore/diamond-city\'' }); + }); + + it('should NOT filter for the extra provided ancestor if it\'s the same as the rootNodeId', () => { + service.search('nuka cola quantum', 'diamond-city', 0, 25, ['diamond-city']); + + expect(search.query.filterQueries).toContain({ query: 'ANCESTOR:\'workspace://SpacesStore/diamond-city\'' }); + }); }); diff --git a/lib/content-services/content-node-selector/content-node-selector.service.ts b/lib/content-services/content-node-selector/content-node-selector.service.ts index d743fbc5f7..65d4856bb1 100644 --- a/lib/content-services/content-node-selector/content-node-selector.service.ts +++ b/lib/content-services/content-node-selector/content-node-selector.service.ts @@ -32,13 +32,26 @@ export class ContentNodeSelectorService { * Performs a search for content node selection * * @param searchTerm The term to search for - * @param skipCount From where to start the loading * @param rootNodeId The root is to start the search from + * @param skipCount From where to start the loading * @param maxItems How many items to load + * @param [extraNodeIds] List of extra node ids to search from. This last parameter is necessary when + * the rootNodeId is one of the supported aliases (e.g. '-my-', '-root-', '-mysites-', etc.) + * and search is not supported for that alias, but can be performed on its corresponding nodes. */ - public search(searchTerm: string, rootNodeId: string = null, skipCount: number = 0, maxItems: number = 25): Observable { + public search(searchTerm: string, rootNodeId: string = null, skipCount: number = 0, maxItems: number = 25, extraNodeIds?: string[]): Observable { - const parentFiltering = rootNodeId ? [ { query: `ANCESTOR:'workspace://SpacesStore/${rootNodeId}'` } ] : []; + let extraParentFiltering = ''; + + if (extraNodeIds && extraNodeIds.length) { + extraNodeIds + .filter(id => id !== rootNodeId) + .forEach(extraId => { + extraParentFiltering += ` OR ANCESTOR:'workspace://SpacesStore/${extraId}'`; + }); + } + + const parentFiltering = rootNodeId ? [ { query: `ANCESTOR:'workspace://SpacesStore/${rootNodeId}'${extraParentFiltering}` } ] : []; let defaultSearchNode: any = { query: { diff --git a/lib/content-services/document-list/components/document-list.component.ts b/lib/content-services/document-list/components/document-list.component.ts index 4c6b380f77..541a9f0e50 100644 --- a/lib/content-services/document-list/components/document-list.component.ts +++ b/lib/content-services/document-list/components/document-list.component.ts @@ -671,33 +671,7 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte } private loadRecent(merge: boolean = false): void { - this.apiService.peopleApi.getPerson('-me-') - .then((person: PersonEntry) => { - const username = person.entry.id; - const query = { - query: { - query: '*', - language: 'afts' - }, - filterQueries: [ - { query: `cm:modified:[NOW/DAY-30DAYS TO NOW/DAY+1DAY]` }, - { query: `cm:modifier:${username} OR cm:creator:${username}` }, - { query: `TYPE:"content" AND -TYPE:"app:filelink" AND -TYPE:"fm:post"` } - ], - include: ['path', 'properties', 'allowableOperations'], - sort: [{ - type: 'FIELD', - field: 'cm:modified', - ascending: false - }], - paging: { - maxItems: this.maxItems, - skipCount: this.skipCount - } - }; - - return this.apiService.searchApi.search(query); - }) + this.getRecentFiles('-me-') .then((page: NodePaging) => this.onPageLoaded(page, merge)) .catch(error => this.error.emit(error)); } @@ -930,4 +904,68 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte this.contextActionHandlerSubscription = null; } } + + getCorrespondingNodeIds(nodeId: string): Promise { + if (nodeId === '-trashcan-') { + return this.apiService.nodesApi.getDeletedNodes() + .then(result => result.list.entries.map(node => node.entry.id)); + + } else if (nodeId === '-sharedlinks-') { + return this.apiService.sharedLinksApi.findSharedLinks() + .then(result => result.list.entries.map(node => node.entry.nodeId)); + + } else if (nodeId === '-sites-') { + return this.apiService.sitesApi.getSites() + .then(result => result.list.entries.map(node => node.entry.guid)); + + } else if (nodeId === '-mysites-') { + return this.apiService.peopleApi.getSiteMembership('-me-') + .then(result => result.list.entries.map(node => node.entry.guid)); + + } else if (nodeId === '-favorites-') { + return this.apiService.favoritesApi.getFavorites('-me-') + .then(result => result.list.entries.map(node => node.entry.targetGuid)); + + } else if (nodeId === '-recent-') { + return this.getRecentFiles('-me-') + .then(result => result.list.entries.map(node => node.entry.id)); + + } else if (nodeId) { + return this.documentListService.getFolderNode(nodeId) + .then(node => [ node.id ]); + } + + return new Promise((resolve) => { + resolve([]); + }); + } + + getRecentFiles(personId: string): Promise { + return this.apiService.peopleApi.getPerson(personId) + .then((person: PersonEntry) => { + const username = person.entry.id; + const query = { + query: { + query: '*', + language: 'afts' + }, + filterQueries: [ + { query: `cm:modified:[NOW/DAY-30DAYS TO NOW/DAY+1DAY]` }, + { query: `cm:modifier:${username} OR cm:creator:${username}` }, + { query: `TYPE:"content" AND -TYPE:"app:filelink" AND -TYPE:"fm:post"` } + ], + include: ['path', 'properties', 'allowableOperations'], + sort: [{ + type: 'FIELD', + field: 'cm:modified', + ascending: false + }], + paging: { + maxItems: this.maxItems, + skipCount: this.skipCount + } + }; + return this.apiService.searchApi.search(query); + }); + } }