mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-24 17:32:15 +00:00
[ACS-4788] Add support for the Categories facet (#8470)
* [ACS-4525] load category paths * [ACS-4525] fix for buckets to update properly * [ACS-4525] getCategory docs * [ACS-4525] unit tests * [ACS-4525] small fixes * [ACS-4525] bug fix * [ACS-4525] code improvements * [ACS-4525] alignment * [ACS-4525] linting
This commit is contained in:
committed by
GitHub
parent
10e6cf1d6f
commit
4fd1e3093f
@@ -13,32 +13,36 @@ Manages categories in Content Services.
|
|||||||
|
|
||||||
### Methods
|
### Methods
|
||||||
|
|
||||||
- **createSubcategories**(parentCategoryId: `string`, payload: `CategoryBody[]`): [`Observable`](http://reactivex.io/documentation/observable.html)`<CategoryPaging|CategoryEntry>`<br/>
|
- **getSubcategories**(parentCategoryId: `string`, skipCount?: `number`, maxItems?: `number`): [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`CategoryPaging`](https://github.com/Alfresco/alfresco-js-api/blob/master/src/api/content-rest-api/docs/CategoryPaging.md)`>`<br/>
|
||||||
Creates subcategories under category with provided categoryId
|
Gets subcategories of a given parent category.
|
||||||
- _parentCategoryId:_ `string` - The identifier of a parent category.
|
- _parentCategoryId:_ `string` - Identifier of a parent category
|
||||||
- _payload:_ `CategoryBody[]` - List of categories to be created.
|
- _skipCount:_ `number` - Number of top categories to skip
|
||||||
- **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<CategoryPaging|CategoryEntry>` - [`Observable`](http://reactivex.io/documentation/observable.html)<CategoryPaging | CategoryEntry>
|
- _maxItems:_ `number` - Maximum number of subcategories returned from Observable
|
||||||
|
- **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`CategoryPaging`](https://github.com/Alfresco/alfresco-js-api/blob/master/src/api/content-rest-api/docs/CategoryPaging.md)`>` - CategoryPaging object (defined in JS-API) with category paging list
|
||||||
|
- **getCategory**(categoryId: `string`): [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`CategoryEntry`](https://github.com/Alfresco/alfresco-js-api/blob/master/src/api/content-rest-api/docs/CategoryEntry.md)`>`<br/>
|
||||||
|
Gets a specific category by categoryId.
|
||||||
|
- _categoryId:_ `string` - The identifier of a category
|
||||||
|
- **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`CategoryEntry`](https://github.com/Alfresco/alfresco-js-api/blob/master/src/api/content-rest-api/docs/CategoryEntry.md)`>` - CategoryEntry object (defined in JS-API) containing information about the category.
|
||||||
|
- **createSubcategories**(parentCategoryId: `string`, payload: [`CategoryBody[]`](https://github.com/Alfresco/alfresco-js-api/blob/master/src/api/content-rest-api/docs/CategoryBody.md)): [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`CategoryPaging`](https://github.com/Alfresco/alfresco-js-api/blob/master/src/api/content-rest-api/docs/CategoryPaging.md) | [`CategoryEntry`](https://github.com/Alfresco/alfresco-js-api/blob/master/src/api/content-rest-api/docs/CategoryEntry.md)`>`<br/>
|
||||||
|
Creates subcategories under category with provided categoryId.
|
||||||
|
- _parentCategoryId:_ `string` - Identifier of a parent category
|
||||||
|
- _payload:_ [`CategoryBody[]`](https://github.com/Alfresco/alfresco-js-api/blob/master/src/api/content-rest-api/docs/CategoryBody.md) - List of categories to be created
|
||||||
|
- **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`CategoryPaging`](https://github.com/Alfresco/alfresco-js-api/blob/master/src/api/content-rest-api/docs/CategoryPaging.md) | [`CategoryEntry`](https://github.com/Alfresco/alfresco-js-api/blob/master/src/api/content-rest-api/docs/CategoryEntry.md)`>` - CategoryEntry object (defined in JS-API) containing the category
|
||||||
|
- **updateCategory**(categoryId: `string`, payload: [`CategoryBody`](https://github.com/Alfresco/alfresco-js-api/blob/master/src/api/content-rest-api/docs/CategoryBody.md)): [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`CategoryEntry`](https://github.com/Alfresco/alfresco-js-api/blob/master/src/api/content-rest-api/docs/CategoryEntry.md)`>`<br/>
|
||||||
|
Updates category.
|
||||||
|
- _categoryId:_ `string` - Identifier of a category
|
||||||
|
- _payload:_ [`CategoryBody`](https://github.com/Alfresco/alfresco-js-api/blob/master/src/api/content-rest-api/docs/CategoryBody.md) - Created category body
|
||||||
|
- **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`CategoryEntry`](https://github.com/Alfresco/alfresco-js-api/blob/master/src/api/content-rest-api/docs/CategoryEntry.md)`>` - CategoryEntry object (defined in JS-API) containing the category
|
||||||
- **deleteCategory**(categoryId: `string`): [`Observable`](http://reactivex.io/documentation/observable.html)`<void>`<br/>
|
- **deleteCategory**(categoryId: `string`): [`Observable`](http://reactivex.io/documentation/observable.html)`<void>`<br/>
|
||||||
Deletes category
|
Deletes category
|
||||||
- _categoryId:_ `string` - The identifier of a category.
|
- _categoryId:_ `string` - The identifier of a category.
|
||||||
- **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<void>` - [`Observable`](http://reactivex.io/documentation/observable.html)<void>
|
- **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<void>` - [`Observable`](http://reactivex.io/documentation/observable.html)<void>
|
||||||
- **getSubcategories**(parentCategoryId: `string`, skipCount?: `number`, maxItems?: `number`): [`Observable`](http://reactivex.io/documentation/observable.html)`<CategoryPaging>`<br/>
|
|
||||||
Get subcategories of a given parent category
|
|
||||||
- _parentCategoryId:_ `string` - The identifier of a parent category.
|
|
||||||
- _skipCount:_ `number` - (Optional) Number of top categories to skip.
|
|
||||||
- _maxItems:_ `number` - (Optional) Maximum number of subcategories returned from [Observable](http://reactivex.io/documentation/observable.html).
|
|
||||||
- **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<CategoryPaging>` - [`Observable`](http://reactivex.io/documentation/observable.html)<CategoryPaging>
|
|
||||||
- **searchCategories**(name: `string`, skipCount: `number` = `0`, 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/>
|
- **searchCategories**(name: `string`, skipCount: `number` = `0`, 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.
|
Searches categories by their name.
|
||||||
- _name:_ `string` - Value for name which should be used during searching categories.
|
- _name:_ `string` - Value for name which should be used during searching categories.
|
||||||
- _skipCount:_ `number` - Specify how many first results should be skipped. Default 0.
|
- _skipCount:_ `number` - Specify how many first results should be skipped. Default 0.
|
||||||
- _maxItems:_ `number` - (Optional) Specify max number of returned categories. Default is specified by [UserPreferencesService](../../core/services/user-preferences.service.md).
|
- _maxItems:_ `number` - (Optional) Specify max number of returned categories. Default is specified by [UserPreferencesService](../../core/services/user-preferences.service.md).
|
||||||
- **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)`>` - [`Observable`](http://reactivex.io/documentation/observable.html)<ResultSetPaging> Found categories which name contains searched name.
|
- **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)`>` - [`Observable`](http://reactivex.io/documentation/observable.html)<ResultSetPaging> Found categories which name contains searched name.
|
||||||
- **updateCategory**(categoryId: `string`, payload: `CategoryBody`): [`Observable`](http://reactivex.io/documentation/observable.html)`<CategoryEntry>`<br/>
|
|
||||||
Updates category
|
|
||||||
- _categoryId:_ `string` - The identifier of a category.
|
|
||||||
- _payload:_ `CategoryBody` - Updated category body
|
|
||||||
- **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<CategoryEntry>` - [`Observable`](http://reactivex.io/documentation/observable.html)<CategoryEntry>
|
|
||||||
|
|
||||||
## Details
|
## Details
|
||||||
|
|
||||||
|
@@ -61,6 +61,13 @@ describe('CategoryService', () => {
|
|||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
it('should fetch the category with the provided categoryId', fakeAsync(() => {
|
||||||
|
const getSpy = spyOn(categoryService.categoriesApi, 'getCategory').and.returnValue(Promise.resolve(fakeCategoryEntry));
|
||||||
|
categoryService.getCategory(fakeParentCategoryId).subscribe(() => {
|
||||||
|
expect(getSpy).toHaveBeenCalledOnceWith(fakeParentCategoryId);
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('should create subcategory', fakeAsync(() => {
|
it('should create subcategory', fakeAsync(() => {
|
||||||
const createSpy = spyOn(categoryService.categoriesApi, 'createSubcategories').and.returnValue(Promise.resolve(fakeCategoryEntry));
|
const createSpy = spyOn(categoryService.categoriesApi, 'createSubcategories').and.returnValue(Promise.resolve(fakeCategoryEntry));
|
||||||
categoryService.createSubcategories(fakeParentCategoryId, [fakeCategoryEntry.entry]).subscribe(() => {
|
categoryService.createSubcategories(fakeParentCategoryId, [fakeCategoryEntry.entry]).subscribe(() => {
|
||||||
|
@@ -57,6 +57,16 @@ export class CategoryService {
|
|||||||
return from(this.categoriesApi.getSubcategories(parentCategoryId ?? '-root-', {skipCount, maxItems}));
|
return from(this.categoriesApi.getSubcategories(parentCategoryId ?? '-root-', {skipCount, maxItems}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a category by ID
|
||||||
|
*
|
||||||
|
* @param categoryId The identifier of a category.
|
||||||
|
* @return Observable<CategoryEntry>
|
||||||
|
*/
|
||||||
|
getCategory(categoryId: string): Observable<CategoryEntry> {
|
||||||
|
return from(this.categoriesApi.getCategory(categoryId));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates subcategories under category with provided categoryId
|
* Creates subcategories under category with provided categoryId
|
||||||
*
|
*
|
||||||
|
@@ -20,16 +20,27 @@ import { TestBed } from '@angular/core/testing';
|
|||||||
import { SearchFacetFiltersService } from './search-facet-filters.service';
|
import { SearchFacetFiltersService } from './search-facet-filters.service';
|
||||||
import { ContentTestingModule } from '../../testing/content.testing.module';
|
import { ContentTestingModule } from '../../testing/content.testing.module';
|
||||||
import { SearchQueryBuilderService } from './search-query-builder.service';
|
import { SearchQueryBuilderService } from './search-query-builder.service';
|
||||||
import { FacetBucketSortBy, FacetBucketSortDirection } from '@alfresco/adf-content-services';
|
import { CategoryService, FacetBucketSortBy, FacetBucketSortDirection } from '@alfresco/adf-content-services';
|
||||||
|
import { EMPTY, of } from 'rxjs';
|
||||||
|
|
||||||
describe('SearchFacetFiltersService', () => {
|
describe('SearchFacetFiltersService', () => {
|
||||||
let searchFacetFiltersService: SearchFacetFiltersService;
|
let searchFacetFiltersService: SearchFacetFiltersService;
|
||||||
let queryBuilder: SearchQueryBuilderService;
|
let queryBuilder: SearchQueryBuilderService;
|
||||||
|
let categoryService: CategoryService;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [ContentTestingModule]
|
imports: [ContentTestingModule],
|
||||||
|
providers: [{
|
||||||
|
provide: CategoryService,
|
||||||
|
useValue: {
|
||||||
|
getCategory: () => EMPTY,
|
||||||
|
searchCategories: () => EMPTY
|
||||||
|
}
|
||||||
|
}]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
categoryService = TestBed.inject(CategoryService);
|
||||||
searchFacetFiltersService = TestBed.inject(SearchFacetFiltersService);
|
searchFacetFiltersService = TestBed.inject(SearchFacetFiltersService);
|
||||||
queryBuilder = TestBed.inject(SearchQueryBuilderService);
|
queryBuilder = TestBed.inject(SearchQueryBuilderService);
|
||||||
});
|
});
|
||||||
@@ -460,6 +471,63 @@ describe('SearchFacetFiltersService', () => {
|
|||||||
expect(searchFacetFiltersService.responseFacets.map(f => f.field)).toEqual(['field4', 'field1', 'Query 1', 'field2', 'field3']);
|
expect(searchFacetFiltersService.responseFacets.map(f => f.field)).toEqual(['field4', 'field1', 'Query 1', 'field2', 'field3']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should load category names for cm:categories facet', () => {
|
||||||
|
const entry = {id: 'test-id-test', name: 'name'};
|
||||||
|
searchFacetFiltersService.responseFacets = null;
|
||||||
|
spyOn(categoryService, 'getCategory').and.returnValue(of({entry}));
|
||||||
|
spyOn(categoryService, 'searchCategories').and.returnValue(of({
|
||||||
|
list: {
|
||||||
|
entries: [{
|
||||||
|
entry: {
|
||||||
|
...entry,
|
||||||
|
nodeType: 'node-type',
|
||||||
|
path: { name: '/categories/General/Test Category/Subcategory'},
|
||||||
|
isFolder: false,
|
||||||
|
isFile: false
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
queryBuilder.config = {
|
||||||
|
categories: [],
|
||||||
|
facetFields: {
|
||||||
|
fields: [
|
||||||
|
{label: 'f1', field: 'f1', mincount: 0},
|
||||||
|
{label: 'categories', field: 'cm:categories', mincount: 0}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
facetQueries: {
|
||||||
|
queries: []
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const fields: any = [
|
||||||
|
{type: 'field', label: 'f1', buckets: [{label: 'a1'}, {label: 'a2'}]},
|
||||||
|
{
|
||||||
|
type: 'field', label: 'categories', buckets: [{
|
||||||
|
label: `workspace://SpacesStore/${entry.id}`,
|
||||||
|
filterQuery: `cm:categories:"workspace://SpacesStore/${entry.id}"`
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
let data = {
|
||||||
|
list: {
|
||||||
|
context: {
|
||||||
|
facets: fields
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
searchFacetFiltersService.onDataLoaded(data);
|
||||||
|
|
||||||
|
expect(categoryService.getCategory).toHaveBeenCalledWith(entry.id);
|
||||||
|
expect(categoryService.searchCategories).toHaveBeenCalledWith(entry.name);
|
||||||
|
expect(searchFacetFiltersService.responseFacets[1].buckets.items[0].display).toBe(`Test Category/Subcategory/${entry.name}`);
|
||||||
|
expect(searchFacetFiltersService.responseFacets[1].buckets.length).toEqual(1);
|
||||||
|
expect(searchFacetFiltersService.responseFacets.length).toEqual(2);
|
||||||
|
});
|
||||||
|
|
||||||
describe('Bucket sorting', () => {
|
describe('Bucket sorting', () => {
|
||||||
let data;
|
let data;
|
||||||
|
|
||||||
|
@@ -17,15 +17,16 @@
|
|||||||
|
|
||||||
import { Inject, Injectable, OnDestroy } from '@angular/core';
|
import { Inject, Injectable, OnDestroy } from '@angular/core';
|
||||||
import { FacetBucketSortBy, FacetBucketSortDirection, FacetField } from '../models/facet-field.interface';
|
import { FacetBucketSortBy, FacetBucketSortDirection, FacetField } from '../models/facet-field.interface';
|
||||||
import { Subject } from 'rxjs';
|
import { Subject, throwError } from 'rxjs';
|
||||||
import { SEARCH_QUERY_SERVICE_TOKEN } from '../search-query-service.token';
|
import { SEARCH_QUERY_SERVICE_TOKEN } from '../search-query-service.token';
|
||||||
import { SearchQueryBuilderService } from './search-query-builder.service';
|
import { SearchQueryBuilderService } from './search-query-builder.service';
|
||||||
import { TranslationService } from '@alfresco/adf-core';
|
import { TranslationService } from '@alfresco/adf-core';
|
||||||
import { SearchService } from '../services/search.service';
|
import { SearchService } from './search.service';
|
||||||
import { takeUntil } from 'rxjs/operators';
|
import { catchError, concatMap, takeUntil } from 'rxjs/operators';
|
||||||
import { GenericBucket, GenericFacetResponse, ResultSetContext, ResultSetPaging } from '@alfresco/js-api';
|
import { GenericBucket, GenericFacetResponse, ResultSetContext, ResultSetPaging } from '@alfresco/js-api';
|
||||||
import { SearchFilterList } from '../models/search-filter-list.model';
|
import { SearchFilterList } from '../models/search-filter-list.model';
|
||||||
import { FacetFieldBucket } from '../models/facet-field-bucket.interface';
|
import { FacetFieldBucket } from '../models/facet-field-bucket.interface';
|
||||||
|
import { CategoryService } from '../../category';
|
||||||
|
|
||||||
export interface SelectedBucket {
|
export interface SelectedBucket {
|
||||||
field: FacetField;
|
field: FacetField;
|
||||||
@@ -53,7 +54,9 @@ export class SearchFacetFiltersService implements OnDestroy {
|
|||||||
|
|
||||||
constructor(@Inject(SEARCH_QUERY_SERVICE_TOKEN) public queryBuilder: SearchQueryBuilderService,
|
constructor(@Inject(SEARCH_QUERY_SERVICE_TOKEN) public queryBuilder: SearchQueryBuilderService,
|
||||||
private searchService: SearchService,
|
private searchService: SearchService,
|
||||||
private translationService: TranslationService) {
|
private translationService: TranslationService,
|
||||||
|
private categoryService: CategoryService
|
||||||
|
) {
|
||||||
if (queryBuilder.config && queryBuilder.config.facetQueries) {
|
if (queryBuilder.config && queryBuilder.config.facetQueries) {
|
||||||
this.facetQueriesPageSize = queryBuilder.config.facetQueries.pageSize || DEFAULT_PAGE_SIZE;
|
this.facetQueriesPageSize = queryBuilder.config.facetQueries.pageSize || DEFAULT_PAGE_SIZE;
|
||||||
}
|
}
|
||||||
@@ -102,6 +105,10 @@ export class SearchFacetFiltersService implements OnDestroy {
|
|||||||
this.sortFacetBuckets(responseBuckets, field.settings?.bucketSortBy, field.settings?.bucketSortDirection ?? FacetBucketSortDirection.ASCENDING);
|
this.sortFacetBuckets(responseBuckets, field.settings?.bucketSortBy, field.settings?.bucketSortDirection ?? FacetBucketSortDirection.ASCENDING);
|
||||||
const alreadyExistingField = this.findResponseFacet(itemType, field.label);
|
const alreadyExistingField = this.findResponseFacet(itemType, field.label);
|
||||||
|
|
||||||
|
if (field.field === 'cm:categories'){
|
||||||
|
this.loadCategoryNames(responseBuckets);
|
||||||
|
}
|
||||||
|
|
||||||
if (alreadyExistingField) {
|
if (alreadyExistingField) {
|
||||||
const alreadyExistingBuckets = alreadyExistingField.buckets && alreadyExistingField.buckets.items || [];
|
const alreadyExistingBuckets = alreadyExistingField.buckets && alreadyExistingField.buckets.items || [];
|
||||||
|
|
||||||
@@ -335,6 +342,27 @@ export class SearchFacetFiltersService implements OnDestroy {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private loadCategoryNames(bucketList: FacetFieldBucket[]) {
|
||||||
|
bucketList.forEach((item) => {
|
||||||
|
const categoryId = item.label.split('/').pop();
|
||||||
|
this.categoryService.getCategory(categoryId)
|
||||||
|
.pipe(
|
||||||
|
concatMap((categoryEntry) => this.categoryService.searchCategories(categoryEntry.entry.name)),
|
||||||
|
catchError(error => throwError(error))
|
||||||
|
)
|
||||||
|
.subscribe(
|
||||||
|
result => {
|
||||||
|
const nextAfterGeneralPathPartIndex = 3;
|
||||||
|
const pathSeparator = '/';
|
||||||
|
const currentCat = result.list.entries.filter(entry => entry.entry.id === categoryId)[0];
|
||||||
|
const path = currentCat.entry.path.name.split(pathSeparator).slice(nextAfterGeneralPathPartIndex).join('/');
|
||||||
|
|
||||||
|
item.display = path ? `${path}/${currentCat.entry.name}` : currentCat.entry.name;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
unselectFacetBucket(field: FacetField, bucket: FacetFieldBucket) {
|
unselectFacetBucket(field: FacetField, bucket: FacetFieldBucket) {
|
||||||
if (bucket) {
|
if (bucket) {
|
||||||
bucket.checked = false;
|
bucket.checked = false;
|
||||||
|
Reference in New Issue
Block a user