[AAE-3199] - Add support for searchable content model properties (new search filters panel in the attach file widget) (#6134)

* Create new panel component and search refactoring

* Replace

* First refactoring - Fix global search and service injection issues

* More refactoring

* Fix service injection - create one new instance for content-node-selector component

* Avoid having more services extending BaseQueryBuilderService

* more refactoring, remove injecting the SearchQueryBuilderService from the content node selector service

* Fix build errors to launch unit tests

* Fix existing unit tests

* Fix wrong import and constructor injection comments

* Fix wrong import

* Fix comments

* Fix api compatibility

* Revert demo shell causing e2e fail

Co-authored-by: adomi <ardit.domi@alfresco.com>
This commit is contained in:
Maurizio Vitale
2020-10-01 09:11:23 +01:00
committed by GitHub
parent f0af0c4113
commit 7c83f2eb18
20 changed files with 515 additions and 234 deletions

View File

@@ -21,7 +21,9 @@
</mat-icon>
</mat-form-field>
<adf-search-panel>
</adf-search-panel>
<adf-sites-dropdown
*ngIf="showDropdownSiteList"
class="full-width"

View File

@@ -16,14 +16,13 @@
*/
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { async, fakeAsync, tick, ComponentFixture, TestBed } from '@angular/core/testing';
import { tick, ComponentFixture, TestBed, fakeAsync } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { NodeEntry, Node, SiteEntry, SitePaging, NodePaging } from '@alfresco/js-api';
import { SearchService, SitesService, setupTestBed, NodesApiService } from '@alfresco/adf-core';
import { Observable, Observer, of, throwError } from 'rxjs';
import { NodeEntry, Node, SiteEntry, SitePaging, NodePaging, ResultSetPaging } from '@alfresco/js-api';
import { SitesService, setupTestBed, NodesApiService } from '@alfresco/adf-core';
import { of, throwError } from 'rxjs';
import { DropdownBreadcrumbComponent } from '../breadcrumb';
import { ContentNodeSelectorPanelComponent } from './content-node-selector-panel.component';
import { ContentNodeSelectorService } from './content-node-selector.service';
import { ContentTestingModule } from '../testing/content.testing.module';
import { DocumentListService } from '../document-list/services/document-list.service';
import { DocumentListComponent } from '../document-list/components/document-list.component';
@@ -31,38 +30,40 @@ import { DropdownSitesComponent } from '../site-dropdown/sites-dropdown.componen
import { CustomResourcesService } from '../document-list/services/custom-resources.service';
import { NodeEntryEvent, ShareDataRow } from '../document-list';
import { TranslateModule } from '@ngx-translate/core';
import { SearchQueryBuilderService } from '../search';
import { ContentNodeSelectorService } from './content-node-selector.service';
const ONE_FOLDER_RESULT = {
const fakeResultSetPaging: ResultSetPaging = {
list: {
pagination: {
totalItems: 1
},
entries: [
{
entry: {
id: '123', name: 'MyFolder', isFile: false, isFolder: true,
createdByUser: { displayName: 'John Doe' },
modifiedByUser: { displayName: 'John Doe' }
id: '123',
name: 'MyFolder',
isFile: false,
isFolder: true,
nodeType: 'mock'
}
}
],
pagination: {
hasMoreItems: true
}
]
}
};
describe('ContentNodeSelectorComponent', () => {
describe('ContentNodeSelectorPanelComponent', () => {
const debounceSearch = 200;
let component: ContentNodeSelectorPanelComponent;
let fixture: ComponentFixture<ContentNodeSelectorPanelComponent>;
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<NodePaging>;
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<NodePaging>) => {
_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(<SiteEntry> { 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 = <SitePaging> { list: { entries: [<SiteEntry> { entry: { guid: '-sites-' } }, <SiteEntry> { 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(<SiteEntry> { 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: <Node> {} }]);
@@ -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: <Node> {} }]);
@@ -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: <Node> {} }]);
@@ -1141,7 +1094,7 @@ describe('ContentNodeSelectorComponent', () => {
component.resetChosenNode();
fixture.detectChanges();
}));
});
});
});

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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<ContentNodeSelectorComponent>;

View File

@@ -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 {}

View File

@@ -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<ResultSetPaging> {
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\'' });
});
});

View File

@@ -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<ResultSetPaging> {
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);
}
}

View File

@@ -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';

View File

@@ -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<ResultSetPaging> {
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

View File

@@ -24,7 +24,7 @@
</adf-search-widget-container>
</mat-expansion-panel>
<ng-container *ngIf="responseFacets">
<ng-container *ngIf="responseFacets && showContextFacets">
<mat-expansion-panel [attr.data-automation-id]="'expansion-panel-'+field.label" *ngFor="let field of responseFacets"
[expanded]="shouldExpand(field)">
<mat-expansion-panel-header>

View File

@@ -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<FacetFieldBucket>(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<FacetFieldBucket>(responseBuckets, this.facetQueriesPageSize);
bucketList.filter = this.getBucketFilterFunction(bucketList);

View File

@@ -0,0 +1,4 @@
<adf-search-filter *ngIf="contentNodeSelectorSearchPanelService?.customModels?.length > 0"
class="app-search-settings" #searchFilter
[showContextFacets]="false"
></adf-search-filter>

View File

@@ -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<SearchCheckListComponent>;
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 = <any> { options: options };
component.ngOnInit();
expect(component.options.items).toEqual(options);
});
it('should handle enter key as click on checkboxes', () => {
component.options = new SearchFilterList<SearchListOption>([
{ 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 = <any> { operator: 'AND' };
component.ngOnInit();
expect(component.operator).toBe('AND');
});
it('should use OR operator by default', () => {
component.settings = <any> { operator: null };
component.ngOnInit();
expect(component.operator).toBe('OR');
});
it('should update query builder on checkbox change', () => {
component.options = new SearchFilterList<SearchListOption>([
{ name: 'Folder', value: "TYPE:'cm:folder'", checked: false },
{ name: 'Document', value: "TYPE:'cm:content'", checked: false }
]);
component.id = 'checklist';
component.context = <any> {
queryFragments: {},
update() {}
};
component.ngOnInit();
spyOn(component.context, 'update').and.stub();
component.changeHandler(
<any> { checked: true },
component.options.items[0]
);
expect(component.context.queryFragments[component.id]).toEqual(`TYPE:'cm:folder'`);
component.changeHandler(
<any> { 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<SearchListOption>([
{ 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 = <any> {
queryFragments: {
'checklist': 'query'
},
update() {}
};
spyOn(component.context, 'update').and.stub();
component.ngOnInit();
component.options = new SearchFilterList<SearchListOption>([
{ 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 = <any> {
queryFragments: {
'checklist': 'query'
},
update() {}
};
component.settings = <any> { 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 = <any> {
queryFragments: {
'checklist': 'query'
},
update() {}
};
component.settings = <any> { 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 = <any> {
queryFragments: {
'checklist': 'query'
},
update: () => {}
};
component.settings = <any> { 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);
});
});

View File

@@ -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();
}
}

View File

@@ -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';

View File

@@ -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 {

View File

@@ -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,

View File

@@ -29,8 +29,7 @@ export class SearchService {
dataLoaded: Subject<ResultSetPaging> = new Subject();
constructor(private apiService: AlfrescoApiService,
private searchConfigurationService: SearchConfigurationService) {
}
private searchConfigurationService: SearchConfigurationService) {}
/**
* Gets a list of nodes that match the given search criteria.

View File

@@ -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[]) => {