mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-06-30 18:15:11 +00:00
[ACS-4565] add search for categories tree in admin cc (#8279)
* ACS-4565 Added possibility to search categories * ACS-4565 Fixed unit test * ACS-4565 Fixed lint issue * ACS-4565 Removed extra empty line * ACS-4565 Replaced tags label with categories label in unit tests * ACS-4565 Replaced tags label with categories label in doc
This commit is contained in:
parent
477d49eaee
commit
02dcd4fb48
@ -13,11 +13,12 @@ Datasource service for category tree.
|
||||
|
||||
### Methods
|
||||
|
||||
- **getSubNodes**(parentNodeId: `string`, skipCount?: `number`, maxItems?: `number`): [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`TreeResponse<CategoryNode>`](../../../lib/content-services/src/lib/tree/models/tree-response.interface.ts)`>`<br/>
|
||||
- **getSubNodes**(parentNodeId: `string`, skipCount?: `number`, maxItems?: `number`, name?: `string`): [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`TreeResponse<CategoryNode>`](../../../lib/content-services/src/lib/tree/models/tree-response.interface.ts)`>`<br/>
|
||||
Gets categories as nodes for category tree.
|
||||
- _parentNodeId:_ `string` - Identifier of a parent category
|
||||
- _skipCount:_ `number` - Number of top categories to skip
|
||||
- _maxItems:_ `number` - Maximum number of subcategories returned from Observable
|
||||
- _name:_ `string` - Optional parameter which specifies if categories should be filtered out by name or not. If not specified then returns categories without filtering.
|
||||
- **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`TreeResponse<CategoryNode>`](../../../lib/content-services/src/lib/tree/models/tree-response.interface.ts)`>` - TreeResponse object containing pagination object and list on nodes
|
||||
|
||||
## Details
|
||||
|
@ -33,6 +33,12 @@ Manages categories in Content Services.
|
||||
Deletes category.
|
||||
- _categoryId:_ `string` - Identifier of a category
|
||||
- **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<void>` - Null object when the operation completes
|
||||
- **searchCategories**(name: `string`, skipCount: `number`, maxItems?: `number`): [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`ResultSetPaging`](https://github.com/Alfresco/alfresco-js-api/blob/develop/src/api/search-rest-api/docs/ResultSetPaging.md)`>`<br/>
|
||||
Searches categories by their name.
|
||||
- _name:_ `string` - Value for name which should be used during searching categories.
|
||||
- _skipCount:_ `number` - Specify how many first results should be skipped. Default 0.
|
||||
- _maxItems:_ `number` - Specify max number of returned categories. Default is specified by UserPreferencesService.
|
||||
- **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`ResultSetPaging`](https://github.com/Alfresco/alfresco-js-api/blob/develop/src/api/search-rest-api/docs/ResultSetPaging.md)`>` - Found categories which name contains searched name.
|
||||
|
||||
## Details
|
||||
|
||||
|
@ -18,7 +18,8 @@ Accesses the Content Services Search API.
|
||||
- _term:_ `string` - Term to search for
|
||||
- _options:_ [`SearchOptions`](lib/content-services/src/lib/search/services/search.service.ts) - (Optional) Options for delivery of the search results
|
||||
- **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`NodePaging`](https://github.com/Alfresco/alfresco-js-api/blob/develop/src/api/content-rest-api/docs/NodePaging.md)`>` - List of nodes resulting from the search
|
||||
- **search**(searchTerm: `string`, maxResults: `number`, skipCount: `number`): [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`ResultSetPaging`](https://github.com/Alfresco/alfresco-js-api/blob/develop/src/api/search-rest-api/docs/ResultSetPaging.md)`>`<br/>
|
||||
- **search**(searchTerm: `string`, maxResults: `number`, skipCount: `number`): [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`ResultSetPaging`]
|
||||
- `>`<br/>
|
||||
Performs a search.
|
||||
- _searchTerm:_ `string` - Term to search for
|
||||
- _maxResults:_ `number` - Maximum number of items in the list of results
|
||||
|
@ -16,7 +16,13 @@
|
||||
*/
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CategoryEntry, CategoryPaging } from '@alfresco/js-api';
|
||||
import {
|
||||
CategoryEntry,
|
||||
CategoryPaging, Pagination, PathInfo, ResultNode,
|
||||
ResultSetPaging,
|
||||
ResultSetPagingList,
|
||||
ResultSetRowEntry
|
||||
} from '@alfresco/js-api';
|
||||
import { Observable, of } from 'rxjs';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
@ -26,6 +32,29 @@ export class CategoryServiceMock {
|
||||
return parentNodeId ? of(this.getChildrenLevelResponse(skipCount, maxItems)) : of(this.getRootLevelResponse(skipCount, maxItems));
|
||||
}
|
||||
|
||||
public searchCategories(): Observable<ResultSetPaging> {
|
||||
const result = new ResultSetPaging();
|
||||
result.list = new ResultSetPagingList();
|
||||
const category1 = new ResultSetRowEntry();
|
||||
category1.entry = new ResultNode();
|
||||
category1.entry.name = 'some name';
|
||||
category1.entry.id = 'some id 1';
|
||||
category1.entry.parentId = 'parent id 1';
|
||||
category1.entry.path = new PathInfo();
|
||||
category1.entry.path.name = '/categories/General';
|
||||
const category2 = new ResultSetRowEntry();
|
||||
category2.entry = new ResultNode();
|
||||
category2.entry.name = 'some other name';
|
||||
category2.entry.id = 'some id 2';
|
||||
category2.entry.parentId = 'parent id 2';
|
||||
category2.entry.path = new PathInfo();
|
||||
category2.entry.path.name = '/categories/General/Language';
|
||||
result.list.entries = [category1, category2];
|
||||
result.list.pagination = new Pagination();
|
||||
result.list.pagination.count = 2;
|
||||
return of(result);
|
||||
}
|
||||
|
||||
private getRootLevelResponse(skipCount?: number, maxItems?: number): CategoryPaging {
|
||||
const rootCategoryEntry: CategoryEntry = {entry: {id: 'testId', name: 'testNode', parentId: '-root-', hasChildren: true}};
|
||||
return {list: {pagination: {skipCount, maxItems, hasMoreItems: false}, entries: [rootCategoryEntry]}};
|
||||
|
@ -18,13 +18,15 @@
|
||||
import { CoreTestingModule } from '@alfresco/adf-core';
|
||||
import { fakeAsync, TestBed } from '@angular/core/testing';
|
||||
import { CategoryService } from '../services/category.service';
|
||||
import { CategoryTreeDatasourceService } from './category-tree-datasource.service';
|
||||
import { CategoryNode, CategoryTreeDatasourceService } from '@alfresco/adf-content-services';
|
||||
import { CategoryServiceMock } from '../mock/category-mock.service';
|
||||
import { TreeNodeType, TreeResponse } from '../../tree';
|
||||
import { CategoryNode } from '../models/category-node.interface';
|
||||
import { EMPTY } from 'rxjs';
|
||||
import { Pagination } from '@alfresco/js-api';
|
||||
|
||||
describe('CategoryTreeDatasourceService', () => {
|
||||
let categoryTreeDatasourceService: CategoryTreeDatasourceService;
|
||||
let categoryService: CategoryService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
@ -37,6 +39,7 @@ describe('CategoryTreeDatasourceService', () => {
|
||||
});
|
||||
|
||||
categoryTreeDatasourceService = TestBed.inject(CategoryTreeDatasourceService);
|
||||
categoryService = TestBed.inject(CategoryService);
|
||||
});
|
||||
|
||||
it('should get root level categories', fakeAsync(() => {
|
||||
@ -69,4 +72,43 @@ describe('CategoryTreeDatasourceService', () => {
|
||||
expect(treeResponse.entries[1].nodeType).toBe(TreeNodeType.LoadMoreNode);
|
||||
});
|
||||
}));
|
||||
|
||||
it('should call searchCategories on CategoryService if value of name parameter is defined', () => {
|
||||
spyOn(categoryService, 'searchCategories').and.returnValue(EMPTY);
|
||||
const skipCount = 10;
|
||||
const maxItems = 100;
|
||||
const name = 'name';
|
||||
|
||||
categoryTreeDatasourceService.getSubNodes('id', skipCount, maxItems, name);
|
||||
expect(categoryService.searchCategories).toHaveBeenCalledWith(name, skipCount, maxItems);
|
||||
});
|
||||
|
||||
it('should return observable which emits correct categories', (done) => {
|
||||
categoryTreeDatasourceService.getSubNodes('id', undefined, undefined, 'name')
|
||||
.subscribe((response) => {
|
||||
const pagination = new Pagination();
|
||||
pagination.count = 2;
|
||||
expect(response).toEqual({
|
||||
pagination,
|
||||
entries: [{
|
||||
id: 'some id 1',
|
||||
nodeName: 'some name',
|
||||
parentId: 'parent id 1',
|
||||
level: 0,
|
||||
nodeType: TreeNodeType.RegularNode,
|
||||
hasChildren: false,
|
||||
isLoading: false
|
||||
}, {
|
||||
id: 'some id 2',
|
||||
nodeName: 'Language/some other name',
|
||||
parentId: 'parent id 2',
|
||||
level: 0,
|
||||
nodeType: TreeNodeType.RegularNode,
|
||||
hasChildren: false,
|
||||
isLoading: false
|
||||
}]
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -16,9 +16,7 @@
|
||||
*/
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { TreeNodeType } from '../../tree/models/tree-node.interface';
|
||||
import { TreeResponse } from '../../tree/models/tree-response.interface';
|
||||
import { TreeService } from '../../tree/services/tree.service';
|
||||
import { TreeNodeType, TreeResponse, TreeService } from '../../tree';
|
||||
import { CategoryNode } from '../models/category-node.interface';
|
||||
import { CategoryService } from './category.service';
|
||||
import { CategoryEntry, CategoryPaging } from '@alfresco/js-api';
|
||||
@ -32,8 +30,8 @@ export class CategoryTreeDatasourceService extends TreeService<CategoryNode> {
|
||||
super();
|
||||
}
|
||||
|
||||
public getSubNodes(parentNodeId: string, skipCount?: number, maxItems?: number): Observable<TreeResponse<CategoryNode>> {
|
||||
return this.categoryService.getSubcategories(parentNodeId, skipCount, maxItems).pipe(map((response: CategoryPaging) => {
|
||||
public getSubNodes(parentNodeId: string, skipCount?: number, maxItems?: number, name?: string): Observable<TreeResponse<CategoryNode>> {
|
||||
return !name ? this.categoryService.getSubcategories(parentNodeId, skipCount, maxItems).pipe(map((response: CategoryPaging) => {
|
||||
const parentNode: CategoryNode = this.getParentNode(parentNodeId);
|
||||
const nodesList: CategoryNode[] = response.list.entries.map((entry: CategoryEntry) => {
|
||||
return {
|
||||
@ -60,6 +58,25 @@ export class CategoryTreeDatasourceService extends TreeService<CategoryNode> {
|
||||
}
|
||||
const treeResponse: TreeResponse<CategoryNode> = {entries: nodesList, pagination: response.list.pagination};
|
||||
return treeResponse;
|
||||
})) : this.categoryService.searchCategories(name, skipCount, maxItems).pipe(map((pagingResult) => {
|
||||
const nextAfterGeneralPathPartIndex = 3;
|
||||
const pathSeparator = '/';
|
||||
return {
|
||||
entries: pagingResult.list.entries.map((category) => {
|
||||
const path = category.entry.path.name.split(pathSeparator).slice(nextAfterGeneralPathPartIndex)
|
||||
.join(pathSeparator);
|
||||
return {
|
||||
id: category.entry.id,
|
||||
nodeName: path ? `${path}/${category.entry.name}` : category.entry.name,
|
||||
parentId: category.entry.parentId,
|
||||
level: 0,
|
||||
nodeType: TreeNodeType.RegularNode,
|
||||
hasChildren: false,
|
||||
isLoading: false
|
||||
};
|
||||
}),
|
||||
pagination: pagingResult.list.pagination
|
||||
};
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
@ -15,13 +15,22 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { CoreTestingModule } from '@alfresco/adf-core';
|
||||
import { CategoryBody, CategoryEntry, CategoryPaging } from '@alfresco/js-api';
|
||||
import { CoreTestingModule, UserPreferencesService } from '@alfresco/adf-core';
|
||||
import {
|
||||
CategoryBody,
|
||||
CategoryEntry,
|
||||
CategoryPaging, PathInfo,
|
||||
RequestQuery, ResultNode,
|
||||
ResultSetPaging,
|
||||
ResultSetPagingList, ResultSetRowEntry
|
||||
} from '@alfresco/js-api';
|
||||
import { fakeAsync, TestBed } from '@angular/core/testing';
|
||||
import { CategoryService } from './category.service';
|
||||
|
||||
describe('CategoryService', () => {
|
||||
let categoryService: CategoryService;
|
||||
let userPreferencesService: UserPreferencesService;
|
||||
|
||||
const fakeParentCategoryId = 'testParentId';
|
||||
const fakeCategoriesResponse: CategoryPaging = { list: { pagination: {}, entries: [] }};
|
||||
const fakeCategoryEntry: CategoryEntry = { entry: { id: 'testId', name: 'testName' }};
|
||||
@ -35,6 +44,7 @@ describe('CategoryService', () => {
|
||||
});
|
||||
|
||||
categoryService = TestBed.inject(CategoryService);
|
||||
userPreferencesService = TestBed.inject(UserPreferencesService);
|
||||
});
|
||||
|
||||
it('should fetch categories with provided parentId', fakeAsync(() => {
|
||||
@ -71,4 +81,66 @@ describe('CategoryService', () => {
|
||||
expect(deleteSpy).toHaveBeenCalledOnceWith(fakeParentCategoryId);
|
||||
});
|
||||
}));
|
||||
|
||||
describe('searchCategories', () => {
|
||||
const defaultMaxItems = 25;
|
||||
|
||||
let result: ResultSetPaging;
|
||||
|
||||
beforeEach(() => {
|
||||
spyOnProperty(userPreferencesService, 'paginationSize').and.returnValue(defaultMaxItems);
|
||||
result = new ResultSetPaging();
|
||||
result.list = new ResultSetPagingList();
|
||||
const category = new ResultSetRowEntry();
|
||||
category.entry = new ResultNode();
|
||||
category.entry.name = 'some name';
|
||||
category.entry.path = new PathInfo();
|
||||
category.entry.path.name = '/categories/General';
|
||||
result.list.entries = [category];
|
||||
spyOn(categoryService.searchApi, 'search').and.returnValue(Promise.resolve(result));
|
||||
});
|
||||
|
||||
it('should call search on searchApi with correct parameters when specified all parameters', () => {
|
||||
const name = 'name';
|
||||
const skipCount = 10;
|
||||
const maxItems = 100;
|
||||
|
||||
categoryService.searchCategories(name, skipCount, maxItems);
|
||||
expect(categoryService.searchApi.search).toHaveBeenCalledWith({
|
||||
query: {
|
||||
language: RequestQuery.LanguageEnum.Afts,
|
||||
query: `cm:name:"*${name}*" AND TYPE:'cm:category' AND PATH:"/cm:categoryRoot/cm:generalclassifiable//*"`
|
||||
},
|
||||
paging: {
|
||||
skipCount,
|
||||
maxItems
|
||||
},
|
||||
include: ['path']
|
||||
});
|
||||
});
|
||||
|
||||
it('should call search on searchApi with default parameters when skipped optional parameters', () => {
|
||||
const name = 'name';
|
||||
|
||||
categoryService.searchCategories(name);
|
||||
expect(categoryService.searchApi.search).toHaveBeenCalledWith({
|
||||
query: {
|
||||
language: RequestQuery.LanguageEnum.Afts,
|
||||
query: `cm:name:"*${name}*" AND TYPE:'cm:category' AND PATH:"/cm:categoryRoot/cm:generalclassifiable//*"`
|
||||
},
|
||||
paging: {
|
||||
skipCount: 0,
|
||||
maxItems: defaultMaxItems
|
||||
},
|
||||
include: ['path']
|
||||
});
|
||||
});
|
||||
|
||||
it('should return observable which emits paging object for categories', (done) => {
|
||||
categoryService.searchCategories('name').subscribe((paging) => {
|
||||
expect(paging).toBe(result);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -16,20 +16,34 @@
|
||||
*/
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { AlfrescoApiService } from '@alfresco/adf-core';
|
||||
import { CategoriesApi, CategoryBody, CategoryEntry, CategoryPaging } from '@alfresco/js-api';
|
||||
import { AlfrescoApiService, UserPreferencesService } from '@alfresco/adf-core';
|
||||
import {
|
||||
CategoriesApi,
|
||||
CategoryBody,
|
||||
CategoryEntry,
|
||||
CategoryPaging,
|
||||
RequestQuery,
|
||||
ResultSetPaging,
|
||||
SearchApi
|
||||
} from '@alfresco/js-api';
|
||||
import { from, Observable } from 'rxjs';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class CategoryService {
|
||||
private _categoriesApi: CategoriesApi;
|
||||
private _searchApi: SearchApi;
|
||||
|
||||
get categoriesApi(): CategoriesApi {
|
||||
this._categoriesApi = this._categoriesApi ?? new CategoriesApi(this.apiService.getInstance());
|
||||
return this._categoriesApi;
|
||||
}
|
||||
|
||||
constructor(private apiService: AlfrescoApiService) {}
|
||||
get searchApi(): SearchApi {
|
||||
this._searchApi = this._searchApi ?? new SearchApi(this.apiService.getInstance());
|
||||
return this._searchApi;
|
||||
}
|
||||
|
||||
constructor(private apiService: AlfrescoApiService, private userPreferencesService: UserPreferencesService) {}
|
||||
|
||||
/**
|
||||
* Get subcategories of a given parent category
|
||||
@ -74,4 +88,27 @@ export class CategoryService {
|
||||
deleteCategory(categoryId: string): Observable<void> {
|
||||
return from(this.categoriesApi.deleteCategory(categoryId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches categories by their name.
|
||||
*
|
||||
* @param name Value for name which should be used during searching categories.
|
||||
* @param skipCount Specify how many first results should be skipped. Default 0.
|
||||
* @param maxItems Specify max number of returned categories. Default is specified by UserPreferencesService.
|
||||
* @return Observable<ResultSetPaging> Found categories which name contains searched name.
|
||||
*/
|
||||
searchCategories(name: string, skipCount = 0, maxItems?: number): Observable<ResultSetPaging> {
|
||||
maxItems = maxItems || this.userPreferencesService.paginationSize;
|
||||
return from(this.searchApi.search({
|
||||
query: {
|
||||
language: RequestQuery.LanguageEnum.Afts,
|
||||
query: `cm:name:"*${name}*" AND TYPE:'cm:category' AND PATH:"/cm:categoryRoot/cm:generalclassifiable//*"`
|
||||
},
|
||||
paging: {
|
||||
skipCount,
|
||||
maxItems
|
||||
},
|
||||
include: ['path']
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
@ -156,9 +156,9 @@ describe('TreeComponent', () => {
|
||||
it('should clear the selection and load root nodes on refresh', () => {
|
||||
const selectionSpy = spyOn(component.treeNodesSelection, 'clear');
|
||||
const getNodesSpy = spyOn(component.treeService, 'getSubNodes').and.callThrough();
|
||||
component.refreshTree(0, 25);
|
||||
component.refreshTree(0, 25, 'some term');
|
||||
expect(selectionSpy).toHaveBeenCalled();
|
||||
expect(getNodesSpy).toHaveBeenCalledWith('-root-', 0, 25);
|
||||
expect(getNodesSpy).toHaveBeenCalledWith('-root-', 0, 25, 'some term');
|
||||
});
|
||||
|
||||
it('should call correct server method on collapsing node', () => {
|
||||
|
@ -122,11 +122,12 @@ export class TreeComponent<T extends TreeNode> implements OnInit {
|
||||
*
|
||||
* @param skipCount Number of root nodes to skip.
|
||||
* @param maxItems Maximum number of nodes returned from Observable.
|
||||
* @param searchTerm Specifies if categories should be filtered out by name or not. If not specified then returns categories without filtering.
|
||||
*/
|
||||
public refreshTree(skipCount?: number, maxItems?: number): void {
|
||||
public refreshTree(skipCount?: number, maxItems?: number, searchTerm?: string): void {
|
||||
this.loadingRootSource.next(true);
|
||||
this.treeNodesSelection.clear();
|
||||
this.treeService.getSubNodes('-root-', skipCount, maxItems).subscribe((response: TreeResponse<T>) => {
|
||||
this.treeService.getSubNodes('-root-', skipCount, maxItems, searchTerm).subscribe((response: TreeResponse<T>) => {
|
||||
this.treeService.treeNodes = response.entries;
|
||||
this.treeNodesSelection.deselect(...response.entries);
|
||||
this.paginationChanged.emit(response.pagination);
|
||||
|
@ -42,7 +42,7 @@ export abstract class TreeService<T extends TreeNode> extends DataSource<T> {
|
||||
this.treeNodes = [];
|
||||
}
|
||||
|
||||
public abstract getSubNodes(parentNodeId: string, skipCount?: number, maxItems?: number): Observable<TreeResponse<T>>;
|
||||
public abstract getSubNodes(parentNodeId: string, skipCount?: number, maxItems?: number, searchTerm?: string): Observable<TreeResponse<T>>;
|
||||
|
||||
/**
|
||||
* Expands node applying subnodes to it.
|
||||
|
Loading…
x
Reference in New Issue
Block a user