mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-24 17:32:15 +00:00
[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:
@@ -21,7 +21,9 @@
|
||||
</mat-icon>
|
||||
|
||||
</mat-form-field>
|
||||
<adf-search-panel>
|
||||
|
||||
</adf-search-panel>
|
||||
<adf-sites-dropdown
|
||||
*ngIf="showDropdownSiteList"
|
||||
class="full-width"
|
||||
|
@@ -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();
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
}
|
@@ -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>;
|
||||
|
@@ -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 {}
|
||||
|
@@ -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\'' });
|
||||
});
|
||||
});
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -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';
|
||||
|
@@ -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
|
||||
|
@@ -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>
|
||||
|
@@ -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);
|
||||
|
@@ -0,0 +1,4 @@
|
||||
<adf-search-filter *ngIf="contentNodeSelectorSearchPanelService?.customModels?.length > 0"
|
||||
class="app-search-settings" #searchFilter
|
||||
[showContextFacets]="false"
|
||||
></adf-search-filter>
|
@@ -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);
|
||||
});
|
||||
});
|
@@ -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();
|
||||
}
|
||||
}
|
@@ -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';
|
||||
|
@@ -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 {
|
||||
|
@@ -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,
|
||||
|
@@ -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.
|
||||
|
@@ -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[]) => {
|
||||
|
Reference in New Issue
Block a user