diff --git a/lib/content-services/src/lib/content-node-selector/content-node-selector-panel.component.html b/lib/content-services/src/lib/content-node-selector/content-node-selector-panel.component.html index 23592e57e1..aee1d180b2 100644 --- a/lib/content-services/src/lib/content-node-selector/content-node-selector-panel.component.html +++ b/lib/content-services/src/lib/content-node-selector/content-node-selector-panel.component.html @@ -21,7 +21,9 @@ + + { +describe('ContentNodeSelectorPanelComponent', () => { const debounceSearch = 200; let component: ContentNodeSelectorPanelComponent; let fixture: ComponentFixture; - let contentNodeSelectorService: ContentNodeSelectorService; - let searchService: SearchService; let nodeService: NodesApiService; let sitesService: SitesService; let searchSpy: jasmine.Spy; let cnSearchSpy: jasmine.Spy; const fakeNodeEntry = new Node({ id: 'fakeId' }); const nodeEntryEvent = new NodeEntryEvent(fakeNodeEntry); - - let _observer: Observer; + let searchQueryBuilderService: SearchQueryBuilderService; + let contentNodeSelectorService: ContentNodeSelectorService; function typeToSearchBox(searchTerm = 'string-to-search') { const searchInput = fixture.debugElement.query(By.css('[data-automation-id="content-node-selector-search-input"]')); @@ -71,8 +72,8 @@ describe('ContentNodeSelectorComponent', () => { fixture.detectChanges(); } - function respondWithSearchResults(result) { - _observer.next(result); + function triggerSearchResults(searchResults: ResultSetPaging) { + component.queryBuilderService.executed.next(searchResults); } setupTestBed({ @@ -90,18 +91,14 @@ describe('ContentNodeSelectorComponent', () => { component = fixture.componentInstance; component.debounceSearch = 0; - searchService = TestBed.inject(SearchService); nodeService = TestBed.inject(NodesApiService); - contentNodeSelectorService = TestBed.inject(ContentNodeSelectorService); sitesService = TestBed.inject(SitesService); + contentNodeSelectorService = TestBed.inject(ContentNodeSelectorService); + searchQueryBuilderService = component.queryBuilderService; spyOn(nodeService, 'getNode').and.returnValue(of({ id: 'fake-node', path: { elements: [{ nodeType: 'st:site', name: 'fake-site'}] } })); - cnSearchSpy = spyOn(contentNodeSelectorService, 'search').and.callThrough(); - searchSpy = spyOn(searchService, 'searchByQueryBody').and.callFake(() => { - return new Observable((observer: Observer) => { - _observer = observer; - }); - }); + cnSearchSpy = spyOn(contentNodeSelectorService, 'createQuery').and.callThrough(); + searchSpy = spyOn(searchQueryBuilderService, 'execute'); const fakeSite = new SiteEntry({ entry: { id: 'fake-site', guid: 'fake-site', title: 'fake-site', visibility: 'visible' } }); spyOn(sitesService, 'getSite').and.returnValue(of(fakeSite)); }); @@ -242,73 +239,40 @@ describe('ContentNodeSelectorComponent', () => { }); }); - it('should not show the breadcrumb if search was performed as last action', fakeAsync(() => { - typeToSearchBox(); - tick(debounceSearch); + it('should not show the breadcrumb if search was performed as last action', async () => { + triggerSearchResults(fakeResultSetPaging); fixture.detectChanges(); - - respondWithSearchResults(ONE_FOLDER_RESULT); - - tick(debounceSearch); - - fixture.detectChanges(); - - tick(debounceSearch); + await fixture.whenStable(); const breadcrumb = fixture.debugElement.query(By.directive(DropdownBreadcrumbComponent)); expect(breadcrumb).toBeNull(); - })); + }); - it('should show the breadcrumb again on folder navigation in the results list', fakeAsync(() => { - typeToSearchBox(); - tick(debounceSearch); - - fixture.detectChanges(); - - respondWithSearchResults(ONE_FOLDER_RESULT); - tick(debounceSearch); - - fixture.detectChanges(); - - tick(debounceSearch); + it('should show the breadcrumb again on folder navigation in the results list', async () => { + triggerSearchResults(fakeResultSetPaging); component.onFolderChange(nodeEntryEvent); fixture.detectChanges(); const breadcrumb = fixture.debugElement.query(By.directive(DropdownBreadcrumbComponent)); expect(breadcrumb).not.toBeNull(); - })); + }); - it('should show the breadcrumb for the selected node when search results are displayed', fakeAsync(() => { - typeToSearchBox(); - - tick(debounceSearch); - - fixture.detectChanges(); - - respondWithSearchResults(ONE_FOLDER_RESULT); - fixture.detectChanges(); - - tick(debounceSearch); + it('should show the breadcrumb for the selected node when search results are displayed', async () => { + triggerSearchResults(fakeResultSetPaging); const chosenNode = new Node({ path: { elements: ['one'] } }); component.onCurrentSelection([ { entry: chosenNode } ]); fixture.detectChanges(); - tick(debounceSearch); - const breadcrumb = fixture.debugElement.query(By.directive(DropdownBreadcrumbComponent)); expect(breadcrumb).not.toBeNull(); expect(breadcrumb.componentInstance.folderNode.path).toBe(chosenNode.path); - })); + }); - it('should NOT show the breadcrumb for the selected node when not on search results list', fakeAsync(() => { - typeToSearchBox(); - - fixture.detectChanges(); - - respondWithSearchResults(ONE_FOLDER_RESULT); + it('should NOT show the breadcrumb for the selected node when not on search results list', async () => { + triggerSearchResults(fakeResultSetPaging); fixture.detectChanges(); component.onFolderChange(nodeEntryEvent); @@ -318,12 +282,10 @@ describe('ContentNodeSelectorComponent', () => { component.onCurrentSelection([ { entry: chosenNode } ]); fixture.detectChanges(); - tick(debounceSearch); - const breadcrumb = fixture.debugElement.query(By.directive(DropdownBreadcrumbComponent)); expect(breadcrumb).not.toBeNull(); expect(breadcrumb.componentInstance.folderNode).toEqual(undefined); - })); + }); it('should keep breadcrumb folderNode unchanged if breadcrumbTransform is NOT defined', (done) => { fixture.detectChanges(); @@ -418,7 +380,7 @@ describe('ContentNodeSelectorComponent', () => { ...parentFiltering ], scope: { - locations: ['nodes'] + locations: 'nodes' } }; @@ -484,11 +446,11 @@ describe('ContentNodeSelectorComponent', () => { expect(component.chosenNode).toBeNull(); })); - it('should update the breadcrumb when changing to a custom site', fakeAsync(() => { + it('should update the breadcrumb when changing to a custom site', async () => { component.siteChanged( { entry: { guid: '-mysites-', title: 'My Sites' } }); expect(component.breadcrumbFolderTitle).toBe('My Sites'); - })); + }); it('should call the search api on changing the site selectBox value', fakeAsync(() => { typeToSearchBox('vegeta'); @@ -503,7 +465,7 @@ describe('ContentNodeSelectorComponent', () => { expect(searchSpy.calls.argsFor(1)).toEqual([defaultSearchOptions('vegeta', 'namek')]); })); - it('should call the content node selector\'s search with the right parameters on changing the site selectbox\'s value', fakeAsync(() => { + it('should create the query with the right parameters on changing the site selectbox\'s value', fakeAsync(() => { typeToSearchBox('vegeta'); tick(debounceSearch); @@ -517,7 +479,7 @@ describe('ContentNodeSelectorComponent', () => { expect(cnSearchSpy).toHaveBeenCalledWith('vegeta', '-sites-', 0, 25, ['123456testId', '09876543testId'], false); })); - it('should call the content node selector\'s search with the right parameters on changing the site selectBox value from a custom dropdown menu', fakeAsync(() => { + it('should create the query with the right parameters on changing the site selectBox value from a custom dropdown menu', fakeAsync(() => { component.dropdownSiteList = { list: { entries: [ { entry: { guid: '-sites-' } }, { entry: { guid: 'namek' } }] } }; fixture.detectChanges(); @@ -638,7 +600,7 @@ describe('ContentNodeSelectorComponent', () => { expect(component.showingSearchResults).toBeFalsy(); }); - it('should restrict the search to the currentFolderId in case is defined', () => { + it('should the query restrict the search to the currentFolderId in case is defined', () => { component.currentFolderId = 'my-root-id'; component.restrictRootToCurrentFolderId = true; component.ngOnInit(); @@ -647,7 +609,7 @@ describe('ContentNodeSelectorComponent', () => { expect(cnSearchSpy).toHaveBeenCalledWith('search', 'my-root-id', 0, 25, [], false); }); - it('should restrict the search to the site and not to the currentFolderId in case is changed', () => { + it('should the query restrict the search to the site and not to the currentFolderId in case is changed', () => { component.currentFolderId = 'my-root-id'; component.restrictRootToCurrentFolderId = true; component.ngOnInit(); @@ -671,7 +633,7 @@ describe('ContentNodeSelectorComponent', () => { expect(component.breadcrumbRootId).toBeUndefined(); }); - it('should clear the search field, nodes and chosenNode when deleting the search input', fakeAsync(() => { + it('should clear the search field, nodes and chosenNode when deleting the search input', fakeAsync (() => { spyOn(component, 'clear').and.callThrough(); typeToSearchBox('a'); @@ -690,26 +652,19 @@ describe('ContentNodeSelectorComponent', () => { expect(component.folderIdToShow).toBe('cat-girl-nuku-nuku', 'back to the folder in which the search was performed'); })); - it('should clear the search field, nodes and chosenNode on folder navigation in the results list', fakeAsync(() => { + it('should clear the search field, nodes and chosenNode on folder navigation in the results list', async () => { spyOn(component, 'clearSearch').and.callThrough(); - typeToSearchBox('a'); + triggerSearchResults(fakeResultSetPaging); - tick(debounceSearch); - fixture.detectChanges(); - - respondWithSearchResults(ONE_FOLDER_RESULT); - - tick(); fixture.detectChanges(); component.onFolderChange(nodeEntryEvent); fixture.detectChanges(); expect(component.clearSearch).toHaveBeenCalled(); + }); - })); - - it('should show nodes from the same folder as selected in the dropdown on clearing the search input', fakeAsync(() => { + it('should show nodes from the same folder as selected in the dropdown on clearing the search input', fakeAsync (() => { typeToSearchBox('piccolo'); tick(debounceSearch); @@ -789,7 +744,7 @@ describe('ContentNodeSelectorComponent', () => { typeToSearchBox(); setTimeout(() => { - respondWithSearchResults(ONE_FOLDER_RESULT); + triggerSearchResults(fakeResultSetPaging); fixture.detectChanges(); const documentList = fixture.debugElement.query(By.css('[data-automation-id="content-node-selector-document-list"]')); @@ -803,7 +758,7 @@ describe('ContentNodeSelectorComponent', () => { typeToSearchBox('My'); setTimeout(() => { - respondWithSearchResults(ONE_FOLDER_RESULT); + triggerSearchResults(fakeResultSetPaging); fixture.detectChanges(); fixture.whenStable().then(() => { @@ -818,7 +773,7 @@ describe('ContentNodeSelectorComponent', () => { typeToSearchBox(); setTimeout(() => { - respondWithSearchResults(ONE_FOLDER_RESULT); + triggerSearchResults(fakeResultSetPaging); fixture.detectChanges(); fixture.whenStable().then(() => { @@ -835,22 +790,21 @@ describe('ContentNodeSelectorComponent', () => { }, 300); }); - it('should reload the original documentList when clearing the search input', fakeAsync(() => { - typeToSearchBox('shenron'); - - tick(debounceSearch); - - respondWithSearchResults(ONE_FOLDER_RESULT); - - typeToSearchBox(''); - - tick(debounceSearch); + it('should reload the original folderId when clearing the search input', async() => { + component.search('mock-type-search'); + triggerSearchResults(fakeResultSetPaging); fixture.detectChanges(); + await fixture.whenStable(); - const documentList = fixture.debugElement.query(By.css('[data-automation-id="content-node-selector-document-list"]')); - expect(documentList.componentInstance.currentFolderId).toBe('cat-girl-nuku-nuku'); - })); + expect(component.folderIdToShow).toBe(null); + + component.clear(); + fixture.detectChanges(); + await fixture.whenStable(); + + expect(component.folderIdToShow).toBe('cat-girl-nuku-nuku'); + }); it('should set the folderIdToShow to the default "currentFolderId" if siteId is undefined', (done) => { component.siteChanged( { entry: { guid: 'Kame-Sennin Muten Roshi' } }); @@ -895,7 +849,7 @@ describe('ContentNodeSelectorComponent', () => { expect(searchSpy).not.toHaveBeenCalled(); }); - it('should set its loading state to true after search was started', fakeAsync(() => { + it('should set its loading state to true after search was started', fakeAsync (() => { component.showingSearchResults = true; typeToSearchBox('shenron'); @@ -911,7 +865,7 @@ describe('ContentNodeSelectorComponent', () => { expect(paginationLoading).not.toBeNull(); })); - it('Should infinite pagination target be null when we use it for search ', fakeAsync(() => { + it('Should infinite pagination target be null when we use it for search ', fakeAsync (() => { component.showingSearchResults = true; typeToSearchBox('shenron'); @@ -925,7 +879,7 @@ describe('ContentNodeSelectorComponent', () => { expect(component.target).toBeNull(); })); - it('Should infinite pagination target be present when search finish', fakeAsync(() => { + it('Should infinite pagination target be present when search finish', fakeAsync (() => { component.showingSearchResults = true; typeToSearchBox('shenron'); @@ -971,7 +925,7 @@ describe('ContentNodeSelectorComponent', () => { component.isSelectionValid = returnHasPermission; }); - it('should NOT be null after selecting node with the necessary permissions', async(() => { + it('should NOT be null after selecting node with the necessary permissions', async () => { hasAllowableOperations = true; component.documentList.folderNode = entry; @@ -982,9 +936,9 @@ describe('ContentNodeSelectorComponent', () => { }); component.documentList.ready.emit(nodePage); - })); + }); - it('should be null after selecting node without the necessary permissions', async(() => { + it('should be null after selecting node without the necessary permissions', async () => { hasAllowableOperations = false; component.documentList.folderNode = entry; @@ -995,9 +949,9 @@ describe('ContentNodeSelectorComponent', () => { }); component.documentList.ready.emit(nodePage); - })); + }); - it('should NOT be null after clicking on a node (with the right permissions) in the list (onNodeSelect)', async(() => { + it('should NOT be null after clicking on a node (with the right permissions) in the list (onNodeSelect)', async() => { hasAllowableOperations = true; component.select.subscribe((nodes) => { @@ -1007,9 +961,9 @@ describe('ContentNodeSelectorComponent', () => { }); component.onCurrentSelection([ { entry } ]); - })); + }); - it('should remain empty when clicking on a node (with the WRONG permissions) in the list (onNodeSelect)', async(() => { + it('should remain empty when clicking on a node (with the WRONG permissions) in the list (onNodeSelect)', async () => { hasAllowableOperations = false; component.select.subscribe((nodes) => { @@ -1019,9 +973,9 @@ describe('ContentNodeSelectorComponent', () => { }); component.onCurrentSelection([ { entry } ]); - })); + }); - it('should become empty when clicking on a node (with the WRONG permissions) after previously selecting a right node', async(() => { + it('should become empty when clicking on a node (with the WRONG permissions) after previously selecting a right node', async () => { component.select.subscribe((nodes) => { if (hasAllowableOperations) { expect(nodes).toBeDefined(); @@ -1039,10 +993,9 @@ describe('ContentNodeSelectorComponent', () => { hasAllowableOperations = false; component.onCurrentSelection([{ entry }]); + }); - })); - - it('should be empty when the chosenNode is reset', async(() => { + it('should be empty when the chosenNode is reset', async () => { hasAllowableOperations = true; component.onCurrentSelection([{ entry: {} }]); @@ -1053,7 +1006,7 @@ describe('ContentNodeSelectorComponent', () => { }); component.resetChosenNode(); - })); + }); }); describe('in the case when isSelectionValid is null', () => { @@ -1062,7 +1015,7 @@ describe('ContentNodeSelectorComponent', () => { component.isSelectionValid = null; }); - it('should NOT be null after selecting node because isSelectionValid would be reset to defaultValidation function', async(() => { + it('should NOT be null after selecting node because isSelectionValid would be reset to defaultValidation function', async () => { component.documentList.folderNode = entry; fixture.detectChanges(); @@ -1075,9 +1028,9 @@ describe('ContentNodeSelectorComponent', () => { component.documentList.ready.emit(nodePage); fixture.detectChanges(); - })); + }); - it('should NOT be null after clicking on a node in the list (onNodeSelect)', async(() => { + it('should NOT be null after clicking on a node in the list (onNodeSelect)', async () => { fixture.detectChanges(); component.select.subscribe((nodes) => { @@ -1089,9 +1042,9 @@ describe('ContentNodeSelectorComponent', () => { }); component.onCurrentSelection([{ entry }]); - })); + }); - it('should be null when the chosenNode is reset', async(() => { + it('should be null when the chosenNode is reset', async () => { fixture.detectChanges(); component.onCurrentSelection([{ entry: {} }]); @@ -1104,7 +1057,7 @@ describe('ContentNodeSelectorComponent', () => { component.resetChosenNode(); fixture.detectChanges(); - })); + }); }); describe('in the case when isSelectionValid is not defined', () => { @@ -1113,7 +1066,7 @@ describe('ContentNodeSelectorComponent', () => { component.isSelectionValid = undefined; }); - it('should NOT be null after selecting node because isSelectionValid would be the defaultValidation function', async(() => { + it('should NOT be null after selecting node because isSelectionValid would be the defaultValidation function', async () => { component.documentList.folderNode = entry; fixture.detectChanges(); @@ -1126,9 +1079,9 @@ describe('ContentNodeSelectorComponent', () => { component.documentList.ready.emit(nodePage); fixture.detectChanges(); - })); + }); - it('should be null when the chosenNode is reset', async(() => { + it('should be null when the chosenNode is reset', async () => { fixture.detectChanges(); component.onCurrentSelection([{ entry: {} }]); @@ -1141,7 +1094,7 @@ describe('ContentNodeSelectorComponent', () => { component.resetChosenNode(); fixture.detectChanges(); - })); + }); }); }); diff --git a/lib/content-services/src/lib/content-node-selector/content-node-selector-panel.component.ts b/lib/content-services/src/lib/content-node-selector/content-node-selector-panel.component.ts index 4719de8554..064138c216 100644 --- a/lib/content-services/src/lib/content-node-selector/content-node-selector-panel.component.ts +++ b/lib/content-services/src/lib/content-node-selector/content-node-selector-panel.component.ts @@ -15,7 +15,17 @@ * limitations under the License. */ -import { Component, EventEmitter, Input, OnInit, Output, ViewChild, ViewEncapsulation, OnDestroy } from '@angular/core'; +import { + Component, + EventEmitter, + Input, + OnInit, + Output, + ViewChild, + ViewEncapsulation, + OnDestroy, + Inject +} from '@angular/core'; import { HighlightDirective, UserPreferencesService, @@ -37,6 +47,8 @@ import { debounceTime, takeUntil, scan } from 'rxjs/operators'; import { CustomResourcesService } from '../document-list/services/custom-resources.service'; import { NodeEntryEvent, ShareDataRow } from '../document-list'; import { Subject } from 'rxjs'; +import { SEARCH_QUERY_SERVICE_TOKEN } from '../search/search-query-service.token'; +import { SearchQueryBuilderService } from '../search/search-query-builder.service'; export type ValidationFunction = (entry: Node) => boolean; @@ -47,7 +59,11 @@ export const defaultValidation = () => true; styleUrls: ['./content-node-selector-panel.component.scss'], templateUrl: './content-node-selector-panel.component.html', encapsulation: ViewEncapsulation.None, - host: { 'class': 'adf-content-node-selector-panel' } + host: { 'class': 'adf-content-node-selector-panel' }, + providers: [{ + provide: SEARCH_QUERY_SERVICE_TOKEN, + useClass: SearchQueryBuilderService + }] }) export class ContentNodeSelectorPanelComponent implements OnInit, OnDestroy { @@ -230,6 +246,7 @@ export class ContentNodeSelectorPanelComponent implements OnInit, OnDestroy { constructor(private contentNodeSelectorService: ContentNodeSelectorService, private customResourcesService: CustomResourcesService, + @Inject(SEARCH_QUERY_SERVICE_TOKEN) public queryBuilderService: SearchQueryBuilderService, private userPreferencesService: UserPreferencesService, private nodesApiService: NodesApiService, private uploadService: UploadService, @@ -253,6 +270,12 @@ export class ContentNodeSelectorPanelComponent implements OnInit, OnDestroy { ) .subscribe(searchValue => this.search(searchValue)); + this.queryBuilderService.executed + .pipe(takeUntil(this.onDestroy$)) + .subscribe( (results: NodePaging) => { + this.showSearchResults(results); + }); + this.userPreferencesService .select(UserPreferenceValues.PaginationSize) .pipe(takeUntil(this.onDestroy$)) @@ -421,15 +444,15 @@ export class ContentNodeSelectorPanelComponent implements OnInit, OnDestroy { if (this.customResourcesService.hasCorrespondingNodeIds(this.siteId)) { this.customResourcesService.getCorrespondingNodeIds(this.siteId) .subscribe((nodeIds) => { - this.contentNodeSelectorService.search(this.searchTerm, this.siteId, this.pagination.skipCount, this.pagination.maxItems, nodeIds, this.showFiles) - .subscribe(this.showSearchResults.bind(this)); - }, + const query = this.contentNodeSelectorService.createQuery(this.searchTerm, this.siteId, this.pagination.skipCount, this.pagination.maxItems, nodeIds, this.showFiles); + this.queryBuilderService.execute(query); + }, () => { this.showSearchResults({ list: { entries: [] } }); }); } else { - this.contentNodeSelectorService.search(this.searchTerm, this.siteId, this.pagination.skipCount, this.pagination.maxItems, [], this.showFiles) - .subscribe(this.showSearchResults.bind(this)); + const query = this.contentNodeSelectorService.createQuery(this.searchTerm, this.siteId, this.pagination.skipCount, this.pagination.maxItems, [], this.showFiles); + this.queryBuilderService.execute(query); } } diff --git a/lib/content-services/src/lib/content-node-selector/content-node-selector-panel.service.ts b/lib/content-services/src/lib/content-node-selector/content-node-selector-panel.service.ts new file mode 100644 index 0000000000..c7e12f1d45 --- /dev/null +++ b/lib/content-services/src/lib/content-node-selector/content-node-selector-panel.service.ts @@ -0,0 +1,58 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Injectable } from '@angular/core'; +import { SearchCategory } from '../search'; + +@Injectable({ + providedIn: 'root' +}) +export class ContentNodeSelectorPanelService { + + customModels: any[]; + + convertCustomModelPropertiesToSearchCategories(): any[] { + const searchConfig = []; + this.customModels?.forEach( (propertyModel) => { + searchConfig.push(this.convertModelPropertyIntoSearchFilter(propertyModel)); + }); + + return searchConfig; + } + + convertModelPropertyIntoSearchFilter(modelProperty: any): SearchCategory { + let filterSearch: SearchCategory; + if (modelProperty.dataType === 'd:text') { + filterSearch = { + id : modelProperty.prefixedName, + name: modelProperty.prefixedName, + expanded: false, + enabled: true, + component: { + selector: 'text', + settings: { + pattern: `${modelProperty.prefixedName}:'(.*?)'`, + field: `${modelProperty.prefixedName}`, + placeholder: `Enter the ${modelProperty.name}` + } + } + }; + } + return filterSearch; + } + +} diff --git a/lib/content-services/src/lib/content-node-selector/content-node-selector.component.spec.ts b/lib/content-services/src/lib/content-node-selector/content-node-selector.component.spec.ts index 92f5570498..e7452ee75f 100644 --- a/lib/content-services/src/lib/content-node-selector/content-node-selector.component.spec.ts +++ b/lib/content-services/src/lib/content-node-selector/content-node-selector.component.spec.ts @@ -30,7 +30,7 @@ import { DocumentListComponent } from '../document-list/components/document-list import { ShareDataRow } from '../document-list'; import { TranslateModule } from '@ngx-translate/core'; -describe('ContentNodeSelectorDialogComponent', () => { +describe('ContentNodeSelectorComponent', () => { let component: ContentNodeSelectorComponent; let fixture: ComponentFixture; diff --git a/lib/content-services/src/lib/content-node-selector/content-node-selector.module.ts b/lib/content-services/src/lib/content-node-selector/content-node-selector.module.ts index 21e6baf3eb..af80442ddf 100644 --- a/lib/content-services/src/lib/content-node-selector/content-node-selector.module.ts +++ b/lib/content-services/src/lib/content-node-selector/content-node-selector.module.ts @@ -24,10 +24,12 @@ import { ContentNodeSelectorPanelComponent } from './content-node-selector-panel import { ContentNodeSelectorComponent } from './content-node-selector.component'; import { SitesDropdownModule } from '../site-dropdown/sites-dropdown.module'; import { BreadcrumbModule } from '../breadcrumb/breadcrumb.module'; +import { SearchModule } from '../search/search.module'; import { CoreModule } from '@alfresco/adf-core'; import { DocumentListModule } from '../document-list/document-list.module'; import { NameLocationCellComponent } from './name-location-cell/name-location-cell.component'; import { UploadModule } from '../upload/upload.module'; +import { SearchQueryBuilderService } from '../search/search-query-builder.service'; @NgModule({ imports: [ @@ -38,6 +40,7 @@ import { UploadModule } from '../upload/upload.module'; MaterialModule, SitesDropdownModule, BreadcrumbModule, + SearchModule, DocumentListModule, UploadModule ], @@ -50,6 +53,7 @@ import { UploadModule } from '../upload/upload.module'; ContentNodeSelectorPanelComponent, NameLocationCellComponent, ContentNodeSelectorComponent - ] + ], + providers: [SearchQueryBuilderService] }) export class ContentNodeSelectorModule {} diff --git a/lib/content-services/src/lib/content-node-selector/content-node-selector.service.spec.ts b/lib/content-services/src/lib/content-node-selector/content-node-selector.service.spec.ts index 86f1272f4c..5b6b858b19 100644 --- a/lib/content-services/src/lib/content-node-selector/content-node-selector.service.spec.ts +++ b/lib/content-services/src/lib/content-node-selector/content-node-selector.service.spec.ts @@ -16,121 +16,105 @@ */ import { TestBed } from '@angular/core/testing'; -import { QueryBody, ResultSetPaging } from '@alfresco/js-api'; -import { SearchService, setupTestBed } from '@alfresco/adf-core'; +import { setupTestBed } from '@alfresco/adf-core'; import { ContentNodeSelectorService } from './content-node-selector.service'; import { ContentTestingModule } from '../testing/content.testing.module'; import { TranslateModule } from '@ngx-translate/core'; -import { Observable, of } from 'rxjs'; - -class SearchServiceMock extends SearchService { - public query: QueryBody; - - searchByQueryBody(queryBody: QueryBody): Observable { - this.query = queryBody; - return of({}); - } -} describe('ContentNodeSelectorService', () => { let service: ContentNodeSelectorService; - let search: SearchServiceMock; setupTestBed({ imports: [ TranslateModule.forRoot(), ContentTestingModule - ], - providers: [ - { provide: SearchService, useClass: SearchServiceMock } ] }); beforeEach(() => { service = TestBed.inject(ContentNodeSelectorService); - search = TestBed.inject(SearchService) as SearchServiceMock; }); it('should have the proper main query for search string', () => { - service.search('nuka cola quantum'); + const queryBody = service.createQuery('nuka cola quantum'); - expect(search.query.query).toEqual({ + expect(queryBody.query).toEqual({ query: 'nuka cola quantum*' }); }); it('should make it including the path and allowableOperations', () => { - service.search('nuka cola quantum'); + const queryBody = service.createQuery('nuka cola quantum'); - expect(search.query.include).toEqual(['path', 'allowableOperations', 'properties']); + expect(queryBody.include).toEqual(['path', 'allowableOperations', 'properties']); }); it('should make the search restricted to nodes only', () => { - service.search('nuka cola quantum'); + const queryBody = service.createQuery('nuka cola quantum'); - expect(search.query.scope.locations).toEqual(['nodes']); + expect(queryBody.scope.locations).toEqual('nodes'); }); it('should set the maxItems and paging properly by parameters', () => { - service.search('nuka cola quantum', null, 10, 100); + const queryBody = service.createQuery('nuka cola quantum', null, 10, 100); - expect(search.query.paging.maxItems).toEqual(100); - expect(search.query.paging.skipCount).toEqual(10); + expect(queryBody.paging.maxItems).toEqual(100); + expect(queryBody.paging.skipCount).toEqual(10); }); it('should set the maxItems and paging properly by default', () => { - service.search('nuka cola quantum'); + const queryBody = service.createQuery('nuka cola quantum'); - expect(search.query.paging.maxItems).toEqual(25); - expect(search.query.paging.skipCount).toEqual(0); + expect(queryBody.paging.maxItems).toEqual(25); + expect(queryBody.paging.skipCount).toEqual(0); }); it('should filter the search for folders', () => { - service.search('nuka cola quantum'); + const queryBody = service.createQuery('nuka cola quantum'); - expect(search.query.filterQueries).toContain({ query: "TYPE:'cm:folder'" }); + expect(queryBody.filterQueries).toContain({ query: "TYPE:'cm:folder'" }); }); it('should filter the search for files', () => { - service.search('nuka cola quantum', null, 0, 25, [], true); + const queryBody = service.createQuery('nuka cola quantum', null, 0, 25, [], true); - expect(search.query.filterQueries).toContain({ query: "TYPE:'cm:folder' OR TYPE:'cm:content'" }); + expect(queryBody.filterQueries).toContain({ query: "TYPE:'cm:folder' OR TYPE:'cm:content'" }); }); it('should filter out the "system-base" entries', () => { - service.search('nuka cola quantum'); + const queryBody = service.createQuery('nuka cola quantum'); - expect(search.query.filterQueries).toContain({ query: 'NOT cm:creator:System' }); + expect(queryBody.filterQueries).toContain({ query: 'NOT cm:creator:System' }); }); it('should filter for the provided ancestor if defined', () => { - service.search('nuka cola quantum', 'diamond-city'); + const queryBody = service.createQuery('nuka cola quantum', 'diamond-city'); - expect(search.query.filterQueries).toContain({ query: 'ANCESTOR:\'workspace://SpacesStore/diamond-city\'' }); + expect(queryBody.filterQueries).toContain({ query: 'ANCESTOR:\'workspace://SpacesStore/diamond-city\'' }); }); it('should NOT filter for the ancestor if NOT defined', () => { - service.search('nuka cola quantum'); + const queryBody = service.createQuery('nuka cola quantum'); - expect(search.query.filterQueries).not.toContain({ query: 'ANCESTOR:\'workspace://SpacesStore/null\'' }); + expect(queryBody.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']); + const queryBody = service.createQuery('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\'' }); + expect(queryBody.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, []); + const queryBody = service.createQuery('nuka cola quantum', 'diamond-city', 0, 25, []); - expect(search.query.filterQueries).toContain({ query: 'ANCESTOR:\'workspace://SpacesStore/diamond-city\'' }); + expect(queryBody.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']); + const queryBody = service.createQuery('nuka cola quantum', 'diamond-city', 0, 25, ['diamond-city']); - expect(search.query.filterQueries).toContain({ query: 'ANCESTOR:\'workspace://SpacesStore/diamond-city\'' }); + expect(queryBody.filterQueries).toContain({ query: 'ANCESTOR:\'workspace://SpacesStore/diamond-city\'' }); }); }); diff --git a/lib/content-services/src/lib/content-node-selector/content-node-selector.service.ts b/lib/content-services/src/lib/content-node-selector/content-node-selector.service.ts index 7fbef32b26..80299f2b36 100644 --- a/lib/content-services/src/lib/content-node-selector/content-node-selector.service.ts +++ b/lib/content-services/src/lib/content-node-selector/content-node-selector.service.ts @@ -15,10 +15,8 @@ * limitations under the License. */ -import { SearchService } from '@alfresco/adf-core'; import { Injectable } from '@angular/core'; -import { ResultSetPaging } from '@alfresco/js-api'; -import { Observable } from 'rxjs'; +import { QueryBody } from '@alfresco/js-api'; /** * Internal service used by ContentNodeSelector component. @@ -28,23 +26,7 @@ import { Observable } from 'rxjs'; }) export class ContentNodeSelectorService { - constructor(private searchService: SearchService) { - } - - /** - * Performs a search for content node selection - * - * @param searchTerm The term to search for - * @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. - * @param [showFiles] shows the files in the dialog search result - */ - public search(searchTerm: string, rootNodeId: string = null, skipCount: number = 0, maxItems: number = 25, extraNodeIds?: string[], showFiles?: boolean): Observable { - + createQuery(searchTerm: string, rootNodeId: string = null, skipCount: number = 0, maxItems: number = 25, extraNodeIds?: string[], showFiles?: boolean): QueryBody { let extraParentFiltering = ''; if (extraNodeIds && extraNodeIds.length) { @@ -57,7 +39,7 @@ export class ContentNodeSelectorService { const parentFiltering = rootNodeId ? [{ query: `ANCESTOR:'workspace://SpacesStore/${rootNodeId}'${extraParentFiltering}` }] : []; - const defaultSearchNode: any = { + return { query: { query: `${searchTerm}*` }, @@ -67,15 +49,14 @@ export class ContentNodeSelectorService { skipCount: skipCount }, filterQueries: [ - { query: `TYPE:'cm:folder'${ showFiles ? " OR TYPE:'cm:content'" : '' }` }, + { query: `TYPE:'cm:folder'${showFiles ? " OR TYPE:'cm:content'" : ''}` }, { query: 'NOT cm:creator:System' }, ...parentFiltering ], scope: { - locations: ['nodes'] + locations: 'nodes' } }; - return this.searchService.searchByQueryBody(defaultSearchNode); } } diff --git a/lib/content-services/src/lib/content-node-selector/public-api.ts b/lib/content-services/src/lib/content-node-selector/public-api.ts index a490150b47..7e02ad4b15 100644 --- a/lib/content-services/src/lib/content-node-selector/public-api.ts +++ b/lib/content-services/src/lib/content-node-selector/public-api.ts @@ -21,5 +21,6 @@ export * from './content-node-selector-panel.component'; export * from './content-node-selector.component'; export * from './content-node-selector.service'; export * from './content-node-dialog.service'; +export * from './content-node-selector-panel.service'; export * from './content-node-selector.module'; diff --git a/lib/content-services/src/lib/search/base-query-builder.service.ts b/lib/content-services/src/lib/search/base-query-builder.service.ts index dcdc57dcc3..f8e77bc7f8 100644 --- a/lib/content-services/src/lib/search/base-query-builder.service.ts +++ b/lib/content-services/src/lib/search/base-query-builder.service.ts @@ -16,7 +16,7 @@ */ import { Injectable } from '@angular/core'; -import { Subject } from 'rxjs'; +import { Subject, Observable, from } from 'rxjs'; import { AlfrescoApiService, AppConfigService } from '@alfresco/adf-core'; import { QueryBody, @@ -196,8 +196,8 @@ export abstract class BaseQueryBuilderService { /** * Builds the current query and triggers the `updated` event. */ - update(): void { - const query = this.buildQuery(); + update(queryBody?: QueryBody): void { + const query = queryBody ? queryBody : this.buildQuery(); this.updated.next(query); } @@ -205,11 +205,11 @@ export abstract class BaseQueryBuilderService { * Builds and executes the current query. * @returns Nothing */ - async execute() { + async execute(queryBody?: QueryBody) { try { - const query = this.buildQuery(); + const query = queryBody ? queryBody : this.buildQuery(); if (query) { - const resultSetPaging: ResultSetPaging = await this.alfrescoApiService.searchApi.search(query); + const resultSetPaging: ResultSetPaging = await this.alfrescoApiService.getInstance().search.searchApi.search(queryBody); this.executed.next(resultSetPaging); } } catch (error) { @@ -226,6 +226,16 @@ export abstract class BaseQueryBuilderService { } } + search(queryBody: QueryBody): Observable { + const promise = this.alfrescoApiService.searchApi.search(queryBody); + + promise.then((resultSetPaging) => { + this.executed.next(resultSetPaging); + }); + + return from(promise); + } + /** * Builds the current query. * @returns The finished query diff --git a/lib/content-services/src/lib/search/components/search-filter/search-filter.component.html b/lib/content-services/src/lib/search/components/search-filter/search-filter.component.html index a3af0a4d6f..bef56b8dc2 100644 --- a/lib/content-services/src/lib/search/components/search-filter/search-filter.component.html +++ b/lib/content-services/src/lib/search/components/search-filter/search-filter.component.html @@ -24,7 +24,7 @@ - + diff --git a/lib/content-services/src/lib/search/components/search-filter/search-filter.component.ts b/lib/content-services/src/lib/search/components/search-filter/search-filter.component.ts index f39a4a649b..f192ad876a 100644 --- a/lib/content-services/src/lib/search/components/search-filter/search-filter.component.ts +++ b/lib/content-services/src/lib/search/components/search-filter/search-filter.component.ts @@ -15,9 +15,9 @@ * limitations under the License. */ -import { Component, ViewEncapsulation, OnInit, OnDestroy, Inject } from '@angular/core'; +import { Component, ViewEncapsulation, OnInit, OnDestroy, Inject, Input } from '@angular/core'; import { MatCheckboxChange } from '@angular/material/checkbox'; -import { SearchService, TranslationService } from '@alfresco/adf-core'; +import { TranslationService, SearchService } from '@alfresco/adf-core'; import { SearchQueryBuilderService } from '../../search-query-builder.service'; import { FacetFieldBucket } from '../../facet-field-bucket.interface'; import { FacetField } from '../../facet-field.interface'; @@ -41,6 +41,9 @@ export interface SelectedBucket { }) export class SearchFilterComponent implements OnInit, OnDestroy { + @Input() + showContextFacets: boolean = true; + private DEFAULT_PAGE_SIZE = 5; /** All facet field items to be displayed in the component. These are updated according to the response. @@ -77,7 +80,7 @@ export class SearchFilterComponent implements OnInit, OnDestroy { this.queryBuilder.updated .pipe(takeUntil(this.onDestroy$)) - .subscribe(() => this.queryBuilder.execute()); + .subscribe((query) => this.queryBuilder.execute(query)); } ngOnInit() { @@ -210,7 +213,7 @@ export class SearchFilterComponent implements OnInit, OnDestroy { const alreadyExistingBuckets = alreadyExistingField.buckets && alreadyExistingField.buckets.items || []; this.updateExistingBuckets(responseField, responseBuckets, alreadyExistingField, alreadyExistingBuckets); - } else if (responseField) { + } else if (responseField && this.showContextFacets) { const bucketList = new SearchFilterList(responseBuckets, field.pageSize); bucketList.filter = this.getBucketFilterFunction(bucketList); @@ -265,7 +268,7 @@ export class SearchFilterComponent implements OnInit, OnDestroy { const alreadyExistingBuckets = alreadyExistingField.buckets && alreadyExistingField.buckets.items || []; this.updateExistingBuckets(responseField, responseBuckets, alreadyExistingField, alreadyExistingBuckets); - } else if (responseField) { + } else if (responseField && this.showContextFacets) { const bucketList = new SearchFilterList(responseBuckets, this.facetQueriesPageSize); bucketList.filter = this.getBucketFilterFunction(bucketList); diff --git a/lib/content-services/src/lib/search/components/search-panel/search-panel.component.html b/lib/content-services/src/lib/search/components/search-panel/search-panel.component.html new file mode 100644 index 0000000000..3a014e0c6d --- /dev/null +++ b/lib/content-services/src/lib/search/components/search-panel/search-panel.component.html @@ -0,0 +1,4 @@ + diff --git a/lib/content-services/src/lib/search/components/search-panel/search-panel.component.spec.ts b/lib/content-services/src/lib/search/components/search-panel/search-panel.component.spec.ts new file mode 100644 index 0000000000..0927170e33 --- /dev/null +++ b/lib/content-services/src/lib/search/components/search-panel/search-panel.component.spec.ts @@ -0,0 +1,215 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { SearchCheckListComponent, SearchListOption } from '../search-check-list/search-check-list.component'; +import { SearchFilterList } from '../search-filter/models/search-filter-list.model'; +import { setupTestBed } from '@alfresco/adf-core'; +import { ContentTestingModule } from '../../../testing/content.testing.module'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { sizeOptions, stepOne, stepThree } from '../../../mock'; +import { By } from '@angular/platform-browser'; +import { TranslateModule } from '@ngx-translate/core'; + +describe('SearchCheckListComponent', () => { + let fixture: ComponentFixture; + let component: SearchCheckListComponent; + + setupTestBed({ + imports: [ + TranslateModule.forRoot(), + ContentTestingModule + ] + }); + + beforeEach(() => { + fixture = TestBed.createComponent(SearchCheckListComponent); + component = fixture.componentInstance; + }); + + it('should setup options from settings', () => { + const options: any = [ + { 'name': 'Folder', 'value': "TYPE:'cm:folder'" }, + { 'name': 'Document', 'value': "TYPE:'cm:content'" } + ]; + component.settings = { options: options }; + component.ngOnInit(); + + expect(component.options.items).toEqual(options); + }); + + it('should handle enter key as click on checkboxes', () => { + component.options = new SearchFilterList([ + { name: 'Folder', value: "TYPE:'cm:folder'", checked: false }, + { name: 'Document', value: "TYPE:'cm:content'", checked: false } + ]); + + component.ngOnInit(); + fixture.detectChanges(); + + const optionElements = fixture.debugElement.queryAll(By.css('mat-checkbox')); + + optionElements[0].triggerEventHandler('keydown.enter', {}); + expect(component.options.items[0].checked).toBeTruthy(); + + optionElements[0].triggerEventHandler('keydown.enter', {}); + expect(component.options.items[0].checked).toBeFalsy(); + }); + + it('should setup operator from the settings', () => { + component.settings = { operator: 'AND' }; + component.ngOnInit(); + expect(component.operator).toBe('AND'); + }); + + it('should use OR operator by default', () => { + component.settings = { operator: null }; + component.ngOnInit(); + expect(component.operator).toBe('OR'); + }); + + it('should update query builder on checkbox change', () => { + component.options = new SearchFilterList([ + { name: 'Folder', value: "TYPE:'cm:folder'", checked: false }, + { name: 'Document', value: "TYPE:'cm:content'", checked: false } + ]); + + component.id = 'checklist'; + component.context = { + queryFragments: {}, + update() {} + }; + + component.ngOnInit(); + + spyOn(component.context, 'update').and.stub(); + + component.changeHandler( + { checked: true }, + component.options.items[0] + ); + + expect(component.context.queryFragments[component.id]).toEqual(`TYPE:'cm:folder'`); + + component.changeHandler( + { checked: true }, + component.options.items[1] + ); + + expect(component.context.queryFragments[component.id]).toEqual( + `TYPE:'cm:folder' OR TYPE:'cm:content'` + ); + }); + + it('should reset selected boxes', () => { + component.options = new SearchFilterList([ + { name: 'Folder', value: "TYPE:'cm:folder'", checked: true }, + { name: 'Document', value: "TYPE:'cm:content'", checked: true } + ]); + + component.reset(); + + expect(component.options.items[0].checked).toBeFalsy(); + expect(component.options.items[1].checked).toBeFalsy(); + }); + + it('should update query builder on reset', () => { + component.id = 'checklist'; + component.context = { + queryFragments: { + 'checklist': 'query' + }, + update() {} + }; + spyOn(component.context, 'update').and.stub(); + + component.ngOnInit(); + component.options = new SearchFilterList([ + { name: 'Folder', value: "TYPE:'cm:folder'", checked: true }, + { name: 'Document', value: "TYPE:'cm:content'", checked: true } + ]); + + component.reset(); + + expect(component.context.update).toHaveBeenCalled(); + expect(component.context.queryFragments[component.id]).toBe(''); + }); + + describe('Pagination', () => { + it('should show 5 items when pageSize not defined', () => { + component.id = 'checklist'; + component.context = { + queryFragments: { + 'checklist': 'query' + }, + update() {} + }; + component.settings = { options: sizeOptions }; + + component.ngOnInit(); + fixture.detectChanges(); + + const optionElements = fixture.debugElement.queryAll(By.css('mat-checkbox')); + expect(optionElements.length).toEqual(5); + const labels = Array.from(optionElements).map(element => element.nativeElement.innerText); + expect(labels).toEqual(stepOne); + }); + + it('should show all items when pageSize is high', () => { + component.id = 'checklist'; + component.context = { + queryFragments: { + 'checklist': 'query' + }, + update() {} + }; + component.settings = { pageSize: 15, options: sizeOptions }; + component.ngOnInit(); + fixture.detectChanges(); + + const optionElements = fixture.debugElement.queryAll(By.css('mat-checkbox')); + expect(optionElements.length).toEqual(13); + const labels = Array.from(optionElements).map(element => element.nativeElement.innerText); + expect(labels).toEqual(stepThree); + }); + }); + + it('should able to check/reset the checkbox', () => { + component.id = 'checklist'; + component.context = { + queryFragments: { + 'checklist': 'query' + }, + update: () => {} + }; + component.settings = { options: sizeOptions }; + spyOn(component, 'submitValues').and.stub(); + component.ngOnInit(); + fixture.detectChanges(); + + const optionElements = fixture.debugElement.query(By.css('mat-checkbox')); + optionElements.triggerEventHandler('change', { checked: true }); + + expect(component.submitValues).toHaveBeenCalled(); + + const clearAllElement = fixture.debugElement.query(By.css('button[title="SEARCH.FILTER.ACTIONS.CLEAR-ALL"]')); + clearAllElement.triggerEventHandler('click', {} ); + fixture.detectChanges(); + + const selectedElements = fixture.debugElement.queryAll(By.css('.mat-checkbox-checked')); + expect(selectedElements.length).toBe(0); + }); +}); diff --git a/lib/content-services/src/lib/search/components/search-panel/search-panel.component.ts b/lib/content-services/src/lib/search/components/search-panel/search-panel.component.ts new file mode 100644 index 0000000000..50b1f8b4eb --- /dev/null +++ b/lib/content-services/src/lib/search/components/search-panel/search-panel.component.ts @@ -0,0 +1,39 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Component, Inject, OnInit, ViewEncapsulation } from '@angular/core'; +import { ContentNodeSelectorPanelService } from '../../../content-node-selector/content-node-selector-panel.service'; +import { SearchQueryBuilderService } from '../../search-query-builder.service'; +import { SEARCH_QUERY_SERVICE_TOKEN } from '../../search-query-service.token'; + +@Component({ + selector: 'adf-search-panel', + templateUrl: './search-panel.component.html', + encapsulation: ViewEncapsulation.None, + host: { class: 'adf-search-panel' } + +}) +export class SearchPanelComponent implements OnInit { + + constructor(public contentNodeSelectorSearchPanelService: ContentNodeSelectorPanelService, + @Inject(SEARCH_QUERY_SERVICE_TOKEN) private queryBuilderService: SearchQueryBuilderService) { + } + + ngOnInit(): void { + this.queryBuilderService.categories = this.contentNodeSelectorSearchPanelService.convertCustomModelPropertiesToSearchCategories(); + } +} diff --git a/lib/content-services/src/lib/search/public-api.ts b/lib/content-services/src/lib/search/public-api.ts index 48584538b8..137b25e69b 100644 --- a/lib/content-services/src/lib/search/public-api.ts +++ b/lib/content-services/src/lib/search/public-api.ts @@ -34,6 +34,7 @@ export * from './components/search-control.component'; export * from './components/empty-search-result.component'; export * from './components/search-control.component'; export * from './components/search.component'; +export * from './components/search-panel/search-panel.component'; export * from './components/search-check-list/search-check-list.component'; export * from './components/search-chip-list/search-chip-list.component'; export * from './components/search-date-range/search-date-range.component'; diff --git a/lib/content-services/src/lib/search/search-query-builder.service.ts b/lib/content-services/src/lib/search/search-query-builder.service.ts index 640cdc8176..c87f45219a 100644 --- a/lib/content-services/src/lib/search/search-query-builder.service.ts +++ b/lib/content-services/src/lib/search/search-query-builder.service.ts @@ -20,9 +20,7 @@ import { AlfrescoApiService, AppConfigService } from '@alfresco/adf-core'; import { SearchConfiguration } from './search-configuration.interface'; import { BaseQueryBuilderService } from './base-query-builder.service'; -@Injectable({ - providedIn: 'root' -}) +@Injectable() export class SearchQueryBuilderService extends BaseQueryBuilderService { public isFilterServiceActive(): boolean { diff --git a/lib/content-services/src/lib/search/search.module.ts b/lib/content-services/src/lib/search/search.module.ts index d035731ef1..6b6c41dfd7 100644 --- a/lib/content-services/src/lib/search/search.module.ts +++ b/lib/content-services/src/lib/search/search.module.ts @@ -32,6 +32,7 @@ import { SearchTextComponent } from './components/search-text/search-text.compon import { SearchRadioComponent } from './components/search-radio/search-radio.component'; import { SearchSliderComponent } from './components/search-slider/search-slider.component'; import { SearchNumberRangeComponent } from './components/search-number-range/search-number-range.component'; +import { SearchPanelComponent } from './components/search-panel/search-panel.component'; import { SearchCheckListComponent } from './components/search-check-list/search-check-list.component'; import { SearchDateRangeComponent } from './components/search-date-range/search-date-range.component'; import { SearchSortingPickerComponent } from './components/search-sorting-picker/search-sorting-picker.component'; @@ -58,6 +59,7 @@ import { SearchFilterContainerComponent } from './components/search-filter-conta SearchRadioComponent, SearchSliderComponent, SearchNumberRangeComponent, + SearchPanelComponent, SearchCheckListComponent, SearchDateRangeComponent, SearchSortingPickerComponent, @@ -74,6 +76,7 @@ import { SearchFilterContainerComponent } from './components/search-filter-conta SearchRadioComponent, SearchSliderComponent, SearchNumberRangeComponent, + SearchPanelComponent, SearchCheckListComponent, SearchDateRangeComponent, SearchSortingPickerComponent, diff --git a/lib/core/services/search.service.ts b/lib/core/services/search.service.ts index bfaa0b49cc..74e557b995 100644 --- a/lib/core/services/search.service.ts +++ b/lib/core/services/search.service.ts @@ -29,8 +29,7 @@ export class SearchService { dataLoaded: Subject = new Subject(); constructor(private apiService: AlfrescoApiService, - private searchConfigurationService: SearchConfigurationService) { - } + private searchConfigurationService: SearchConfigurationService) {} /** * Gets a list of nodes that match the given search criteria. diff --git a/lib/process-services-cloud/src/lib/form/components/widgets/attach-file/attach-file-cloud-widget.component.ts b/lib/process-services-cloud/src/lib/form/components/widgets/attach-file/attach-file-cloud-widget.component.ts index 37ebafbfd1..8abe79283a 100644 --- a/lib/process-services-cloud/src/lib/form/components/widgets/attach-file/attach-file-cloud-widget.component.ts +++ b/lib/process-services-cloud/src/lib/form/components/widgets/attach-file/attach-file-cloud-widget.component.ts @@ -34,6 +34,7 @@ import { ContentCloudNodeSelectorService } from '../../../services/content-cloud import { ProcessCloudContentService } from '../../../services/process-cloud-content.service'; import { UploadCloudWidgetComponent } from './upload-cloud.widget'; import { DestinationFolderPathModel } from '../../../models/form-cloud-representation.model'; +import { ContentNodeSelectorPanelService } from '@alfresco/adf-content-services'; @Component({ selector: 'adf-cloud-attach-file-cloud-widget', @@ -75,7 +76,8 @@ export class AttachFileCloudWidgetComponent extends UploadCloudWidgetComponent i notificationService: NotificationService, private contentNodeSelectorService: ContentCloudNodeSelectorService, private appConfigService: AppConfigService, - private apiService: AlfrescoApiService + private apiService: AlfrescoApiService, + private contentNodeSelectorPanelService: ContentNodeSelectorPanelService ) { super(formService, thumbnails, processCloudContentService, notificationService, logger); } @@ -122,7 +124,6 @@ export class AttachFileCloudWidgetComponent extends UploadCloudWidgetComponent i async openSelectDialog() { const selectedMode = this.field.params.multiple ? 'multiple' : 'single'; - if (this.isAlfrescoAndLocal()) { const destinationFolderPath = this.getAliasAndRelativePathFromDestinationFolderPath(this.field.params.fileSource.destinationFolderPath); destinationFolderPath.path = this.replaceAppNameAliasWithValue(destinationFolderPath.path); @@ -130,6 +131,8 @@ export class AttachFileCloudWidgetComponent extends UploadCloudWidgetComponent i this.rootNodeId = nodeId ? nodeId : destinationFolderPath.alias; } + this.contentNodeSelectorPanelService.customModels = this.field.params.customModels; + this.contentNodeSelectorService .openUploadFileDialog(this.rootNodeId, selectedMode, this.isAlfrescoAndLocal()) .subscribe((selections: Node[]) => {