[ADF-3496] Grouped facet queries (#4209)

* [ADF-3496] search query format v2 response parser

- replacer for the parseFacetFields

* [ADF-3496] format v2 search facetQueries parser

* [ADF-3496] cleanup

* [ADF-3496] Grouped facet queries

- selection working
- needs code cleanup

* [ADF-3496] code cleanup

* [ADF-3496] code refactoring - renaming

* [ADF-3496] update tests

* [ADF-3496] update tests part2

* [ADF-3496] preserve order

* [ADF-3496] fix facet queries expand

* [ADF-3496] code cleanup & fixes

* [ADF-3496] reorder methods

* [ADF-3496] update test

* fix unrelated failing test

* [ADF-3496] fix config snippet

* [ADF-3496] facet queries mincount

* [ADF-3496] documentation updated

* [ADF-3496] small fix

* [ADF-3496] e2e testing

* [ADF-3496] added TestRail ids

* [ADF-3496] import from right api
This commit is contained in:
Suzana Dirla 2019-02-04 23:38:21 +02:00 committed by Eugenio Romano
parent c91c3f5a81
commit bf718d905f
20 changed files with 377 additions and 379 deletions

View File

@ -228,7 +228,7 @@
"FACET_QUERIES": {
"MY_FACET_QUERIES": "My facet queries",
"CREATED_THIS_YEAR": "1.Created This Year",
"MIMETYPE": "2.Type",
"MIMETYPE": "2.Type: HTML",
"XTRASMALL": "3.Size: xtra small",
"SMALL": "4.Size: small",
"MEDIUM": "5.Size: medium",

View File

@ -119,15 +119,17 @@
"facetQueries": {
"label": "SEARCH.FACET_QUERIES.MY_FACET_QUERIES",
"pageSize": 5,
"expanded": true,
"mincount": 1,
"queries": [
{ "query": "created:2018", "label": "SEARCH.FACET_QUERIES.CREATED_THIS_YEAR" },
{ "query": "content.mimetype", "label": "SEARCH.FACET_QUERIES.MIMETYPE" },
{ "query": "content.size:[0 TO 10240]", "label": "SEARCH.FACET_QUERIES.XTRASMALL"},
{ "query": "content.size:[10240 TO 102400]", "label": "SEARCH.FACET_QUERIES.SMALL"},
{ "query": "content.size:[102400 TO 1048576]", "label": "SEARCH.FACET_QUERIES.MEDIUM" },
{ "query": "content.size:[1048576 TO 16777216]", "label": "SEARCH.FACET_QUERIES.LARGE" },
{ "query": "content.size:[16777216 TO 134217728]", "label": "SEARCH.FACET_QUERIES.XTRALARGE" },
{ "query": "content.size:[134217728 TO MAX]", "label": "SEARCH.FACET_QUERIES.XXTRALARGE" }
{ "query": "content.mimetype:text/html", "label": "SEARCH.FACET_QUERIES.MIMETYPE", "group":"Type facet queries" },
{ "query": "content.size:[0 TO 10240]", "label": "SEARCH.FACET_QUERIES.XTRASMALL", "group":"Size facet queries"},
{ "query": "content.size:[10240 TO 102400]", "label": "SEARCH.FACET_QUERIES.SMALL", "group":"Size facet queries"},
{ "query": "content.size:[102400 TO 1048576]", "label": "SEARCH.FACET_QUERIES.MEDIUM", "group":"Size facet queries" },
{ "query": "content.size:[1048576 TO 16777216]", "label": "SEARCH.FACET_QUERIES.LARGE", "group":"Size facet queries" },
{ "query": "content.size:[16777216 TO 134217728]", "label": "SEARCH.FACET_QUERIES.XTRALARGE", "group":"Size facet queries" },
{ "query": "content.size:[134217728 TO MAX]", "label": "SEARCH.FACET_QUERIES.XXTRALARGE", "group":"Size facet queries" }
]
},
"categories": [

View File

@ -76,7 +76,7 @@ A typical configuration is shown below:
"pageSize": 4,
"queries": [
{ "query": "created:2018", "label": "Created This Year" },
{ "query": "content.mimetype", "label": "Type" },
{ "query": "content.mimetype:text/html", "label": "Type: HTML" },
{ "query": "content.size:[0 TO 10240]", "label": "Size: xtra small"},
{ "query": "content.size:[10240 TO 102400]", "label": "Size: small"},
{ "query": "content.size:[102400 TO 1048576]", "label": "Size: medium" },
@ -298,7 +298,7 @@ These provide custom categories based on admin-defined facet queries.
"expanded": true,
"queries": [
{ "query": "created:2018", "label": "Created This Year" },
{ "query": "content.mimetype", "label": "Type" },
{ "query": "content.mimetype:text/html", "label": "Type: HTML" },
{ "query": "content.size:[0 TO 10240]", "label": "Size: xtra small"},
{ "query": "content.size:[10240 TO 102400]", "label": "Size: small"},
{ "query": "content.size:[102400 TO 1048576]", "label": "Size: medium" },
@ -311,12 +311,40 @@ These provide custom categories based on admin-defined facet queries.
}
```
The queries declared in the `facetQueries` are collected into a single collapsible category.
Only the queries that have 1 or more response entries are displayed at runtime.
By default, the queries declared in the `facetQueries` are collected into a single collapsible category.
The `mincount` property allows setting the minimum count required for a facet field to be displayed. By default, only the queries that have 1 or more response entries are displayed at runtime.
The component provides a `Show more` button to display more items if the number of items
exceeds the `pageSize` value.
You can also provide a custom `label` (or i18n resource key) for the resulting collapsible category.
You can also provide a custom `label` (or i18n resource key) for the default resulting collapsible category.
If you need to display more resulting collapsible categories, you can group different facet queries under custom labels by using the `group` property on those facet queries:
```json
{
"search": {
"facetQueries": {
"label": "Facet queries",
"pageSize": 5,
"expanded": true,
"mincount": 0,
"queries": [
{ "query": "created:2018", "label": "Created This Year" },
{ "query": "modifier:admin", "label": "Admin modifier" },
{ "query": "content.mimetype:text/html", "label": "Type: HTML", "group":"Type facet queries" },
{ "query": "content.mimetype:image/png", "label": "Type: PNG", "group":"Type facet queries" },
{ "query": "content.size:[0 TO 10240]", "label": "Size: xtra small", "group":"Size facet queries"},
{ "query": "content.size:[10240 TO 102400]", "label": "Size: small", "group":"Size facet queries"},
{ "query": "content.size:[102400 TO 1048576]", "label": "Size: medium", "group":"Size facet queries" },
{ "query": "content.size:[1048576 TO 16777216]", "label": "Size: large", "group":"Size facet queries" },
{ "query": "content.size:[16777216 TO 134217728]", "label": "Size: xtra large", "group":"Size facet queries" },
{ "query": "content.size:[134217728 TO MAX]", "label": "Size: XX large", "group":"Size facet queries" }
]
}
}
}
```
This will result in the following display of the grouped facet queries:
![Grouped Facet Queries](../docassets/images/search-facet-queries-groups.png)
The `pageSize` property allows you to define the number of results to display.
Users will see `Show more` or `Show less` buttons as appropriate for the result set.

View File

@ -20,9 +20,6 @@ Stores information from all the custom search and faceted search widgets, compil
Adds a facet bucket to a field.
- _field:_ [`FacetField`](../../content-services/search/facet-field.interface.ts) - The target field
- _bucket:_ [`FacetFieldBucket`](../../content-services/search/facet-field-bucket.interface.ts) - Bucket to add
- **addUserFacetQuery**(query: [`FacetQuery`](../../content-services/search/facet-query.interface.ts))<br/>
Adds a facet query.
- _query:_ [`FacetQuery`](../../content-services/search/facet-query.interface.ts) - Query to add
- **buildQuery**(): `QueryBody`<br/>
Builds the current query.
- **Returns** `QueryBody` - The finished query
@ -53,9 +50,6 @@ Stores information from all the custom search and faceted search widgets, compil
Removes an existing bucket from a field.
- _field:_ [`FacetField`](../../content-services/search/facet-field.interface.ts) - The target field
- _bucket:_ [`FacetFieldBucket`](../../content-services/search/facet-field-bucket.interface.ts) - Bucket to remove
- **removeUserFacetQuery**(query: [`FacetQuery`](../../content-services/search/facet-query.interface.ts))<br/>
Removes an existing facet query.
- _query:_ [`FacetQuery`](../../content-services/search/facet-query.interface.ts) - Query to remove
- **resetToDefaults**()<br/>
Resets the query to the defaults specified in the app config.
- **update**()<br/>
@ -92,6 +86,7 @@ constructor(queryBuilder: SearchQueryBuilderService) {
}
```
> **Note:** Since ADF 3.0.0, the query contains the `"facetFormat": "V2"` parameter so that all the responses have the same structure even if coming from search queries containing facetFields, facetQueries, grouped facetQueries or facetIntervals.
## See also

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 38 KiB

View File

@ -33,6 +33,10 @@ export class SearchFiltersPage {
typeFilter = element(by.css('mat-expansion-panel[data-automation-id="expansion-panel-Type"]'));
sizeRangeFilter = element(by.css('mat-expansion-panel[data-automation-id="expansion-panel-Content Size (range)"]'));
sizeSliderFilter = element(by.css('mat-expansion-panel[data-automation-id="expansion-panel-Content Size"]'));
facetQueriesDefaultGroup = element(by.css('mat-expansion-panel[data-automation-id="expansion-panel-SEARCH.FACET_QUERIES.MY_FACET_QUERIES"],' +
'mat-expansion-panel[data-automation-id="expansion-panel-My facet queries"]'));
facetQueriesTypeGroup = element(by.css('mat-expansion-panel[data-automation-id="expansion-panel-Type facet queries"]'));
facetQueriesSizeGroup = element(by.css('mat-expansion-panel[data-automation-id="expansion-panel-Size facet queries"]'));
checkSearchFiltersIsDisplayed() {
Util.waitUntilElementIsVisible(this.searchFilters);
@ -91,6 +95,29 @@ export class SearchFiltersPage {
return this;
}
checkDefaultFacetQueryGroupIsDisplayed() {
this.searchCategoriesPage.checkFilterIsDisplayed(this.facetQueriesDefaultGroup);
return this;
}
checkTypeFacetQueryGroupIsDisplayed() {
this.searchCategoriesPage.checkFilterIsDisplayed(this.facetQueriesTypeGroup);
return this;
}
checkSizeFacetQueryGroupIsDisplayed() {
this.searchCategoriesPage.checkFilterIsDisplayed(this.facetQueriesSizeGroup);
return this;
}
isTypeFacetQueryGroupPresent() {
return this.facetQueriesTypeGroup.isPresent();
}
isSizeFacetQueryGroupPresent() {
return this.facetQueriesSizeGroup.isPresent();
}
clickCheckListFilter() {
this.searchCategoriesPage.clickFilter(this.checkListFilter);
return this;

View File

@ -65,6 +65,8 @@ describe('Search Filters', () => {
let filter = { type: 'TYPE-PNG Image' };
let jsonFile;
beforeAll(async (done) => {
this.alfrescoJsApi = new AlfrescoApi({
@ -88,9 +90,9 @@ describe('Search Filters', () => {
searchDialog.checkSearchIconIsVisible();
searchDialog.clickOnSearchIcon();
searchDialog.enterTextAndPressEnter(fileUploaded.entry.name);
searchFiltersPage.checkSearchFiltersIsDisplayed();
let searchConfiguration = new SearchConfiguration();
jsonFile = searchConfiguration.getConfiguration();
done();
});
@ -105,6 +107,10 @@ describe('Search Filters', () => {
});
it('[C286298] Should be able to cancel a filter using "x" button from the toolbar', () => {
searchDialog.enterTextAndPressEnter(fileUploaded.entry.name);
searchFiltersPage.checkSearchFiltersIsDisplayed();
let userOption = `${acsUser.firstName} ${acsUser.lastName}`;
searchFiltersPage.creatorCheckListFiltersPage().filterBy(userOption)
.checkChipIsDisplayed(userOption)
@ -157,8 +163,6 @@ describe('Search Filters', () => {
});
it('[C291802] Should be able to filter facet fields with "Contains"', () => {
let searchConfiguration = new SearchConfiguration();
let jsonFile = searchConfiguration.getConfiguration();
navigationBar.clickConfigEditorButton();
configEditor.clickSearchConfiguration();
configEditor.clickClearButton();
@ -176,4 +180,31 @@ describe('Search Filters', () => {
.checkCheckListOptionIsDisplayed('Administrator');
});
it('[C291980] Should group search facets under specified labels', () => {
browser.get(TestConfig.adf.url + '/search;q=*');
searchFiltersPage.checkDefaultFacetQueryGroupIsDisplayed()
.checkTypeFacetQueryGroupIsDisplayed()
.checkSizeFacetQueryGroupIsDisplayed();
});
it('[C291981] Should group search facets under the default label, by default', () => {
browser.refresh();
navigationBar.clickConfigEditorButton();
configEditor.clickSearchConfiguration();
configEditor.clickClearButton();
jsonFile['filterWithContains'] = true;
configEditor.enterBigConfigurationText(JSON.stringify(jsonFile));
configEditor.clickSaveButton();
searchDialog.clickOnSearchIcon()
.enterTextAndPressEnter('*');
searchResults.tableIsLoaded();
searchFiltersPage.checkDefaultFacetQueryGroupIsDisplayed();
expect(searchFiltersPage.isTypeFacetQueryGroupPresent()).toBe(false);
expect(searchFiltersPage.isSizeFacetQueryGroupPresent()).toBe(false);
});
});

View File

@ -72,7 +72,7 @@ export class SearchConfiguration {
'pageSize': 5,
'queries': [
{'query': 'created:2018', 'label': '1.Created This Year'},
{'query': 'content.mimetype', 'label': '2.Type'},
{'query': 'content.mimetype:text/html', 'label': '2.Type: HTML'},
{'query': 'content.size:[0 TO 10240]', 'label': '3.Size: xtra small'},
{'query': 'content.size:[10240 TO 102400]', 'label': '4.Size: small'},
{'query': 'content.size:[102400 TO 1048576]', 'label': '5.Size: medium'},

View File

@ -1,13 +1,4 @@
<mat-chip-list>
<ng-container *ngIf="searchFilter && searchFilter.selectedFacetQueries.length">
<mat-chip
*ngFor="let query of searchFilter.selectedFacetQueries"
[removable]="true"
(removed)="searchFilter.unselectFacetQuery(query)">
{{ query.label | translate }}
<mat-icon matChipRemove>cancel</mat-icon>
</mat-chip>
</ng-container>
<ng-container *ngIf="searchFilter && searchFilter.selectedBuckets.length">
<mat-chip
*ngFor="let selection of searchFilter.selectedBuckets"

View File

@ -29,9 +29,7 @@ import { SearchModule } from '../../search.module';
class TestComponent {
searchFilter = {
selectedFacetQueries : [],
selectedBuckets: [],
unselectFacetQuery() {},
unselectFacetBucket() {}
};
}
@ -48,13 +46,15 @@ describe('SearchChipListComponent', () => {
]
});
xit('should remove items from the search filter', () => {
it('should remove items from the search filter', () => {
const fixture = TestBed.createComponent(TestComponent);
const component: TestComponent = fixture.componentInstance;
spyOn(component.searchFilter, 'unselectFacetQuery').and.stub();
spyOn(component.searchFilter, 'unselectFacetBucket').and.stub();
component.searchFilter.selectedFacetQueries = [{ id: 1 }, { id: 2 }];
const selectedBucket1 = {field: { id: 1 }, bucket: {label: 'bucket1'}};
const selectedBucket2 = {field: { id: 2 }, bucket: {label: 'bucket2'}};
component.searchFilter.selectedBuckets = [selectedBucket1, selectedBucket2];
fixture.detectChanges();
@ -64,7 +64,7 @@ describe('SearchChipListComponent', () => {
closeButtons[0].click();
fixture.detectChanges();
expect(component.searchFilter.unselectFacetQuery).toHaveBeenCalledWith({ id: 1 });
expect(component.searchFilter.unselectFacetBucket).toHaveBeenCalledWith(selectedBucket1.field, selectedBucket1.bucket);
});
});

View File

@ -16,66 +16,9 @@
</adf-search-widget-container>
</mat-expansion-panel>
<ng-container *ngIf="responseFacetQueries">
<mat-expansion-panel [expanded]="facetQueriesExpanded" [attr.data-automation-id]="'expansion-panel-'+facetQueriesLabel">
<mat-expansion-panel-header>
<mat-panel-title>{{ facetQueriesLabel | translate }}</mat-panel-title>
</mat-expansion-panel-header>
<div class="adf-facet-result-filter">
<mat-form-field>
<input
matInput
placeholder="{{ 'SEARCH.FILTER.ACTIONS.FILTER-CATEGORY' | translate }}"
[attr.data-automation-id]="'facet-result-filter-'+facetQueriesLabel"
[(ngModel)]="responseFacetQueries.filterText">
<button *ngIf="responseFacetQueries.filterText"
mat-button matSuffix mat-icon-button
(click)="responseFacetQueries.filterText = ''">
<mat-icon>close</mat-icon>
</button>
</mat-form-field>
</div>
<div class="adf-checklist">
<ng-container *ngFor="let query of responseFacetQueries">
<mat-checkbox
[checked]="query.checked"
[attr.data-automation-id]="'checkbox-'+facetQueriesLabel+'-'+query.label"
(change)="onToggleFacetQuery($event, query)">
<div
matTooltip="{{ query.label | translate }} ({{ query.count }})"
matTooltipPosition="right"
class="adf-facet-label">
{{ query.label | translate }} ({{ query.count }})
</div>
</mat-checkbox>
</ng-container>
</div>
<div class="adf-facet-buttons">
<button mat-icon-button
*ngIf="canResetSelectedQueries"
title="{{ 'SEARCH.FILTER.ACTIONS.CLEAR-ALL' | translate }}"
(click)="resetSelectedQueries()">
<mat-icon>clear</mat-icon>
</button>
<button mat-icon-button
*ngIf="responseFacetQueries.canShowLessItems"
title="{{ 'SEARCH.FILTER.ACTIONS.SHOW-LESS' | translate }}"
(click)="responseFacetQueries.showLessItems()">
<mat-icon>keyboard_arrow_up</mat-icon>
</button>
<button mat-icon-button
*ngIf="responseFacetQueries.canShowMoreItems"
title="{{ 'SEARCH.FILTER.ACTIONS.SHOW-MORE' | translate }}"
(click)="responseFacetQueries.showMoreItems()">
<mat-icon>keyboard_arrow_down</mat-icon>
</button>
</div>
</mat-expansion-panel>
</ng-container>
<ng-container *ngIf="responseFacetFields">
<mat-expansion-panel [attr.data-automation-id]="'expansion-panel-'+field.label" *ngFor="let field of responseFacetFields"
[expanded]="facetFieldsExpanded">
<ng-container *ngIf="responseFacets">
<mat-expansion-panel [attr.data-automation-id]="'expansion-panel-'+field.label" *ngFor="let field of responseFacets"
[expanded]="shouldExpand(field)">
<mat-expansion-panel-header>
<mat-panel-title>{{ field.label | translate }}</mat-panel-title>
</mat-expansion-panel-header>

View File

@ -20,10 +20,8 @@ import { SearchQueryBuilderService } from '../../search-query-builder.service';
import { AppConfigService, TranslationMock } from '@alfresco/adf-core';
import { Subject } from 'rxjs';
import { FacetFieldBucket } from '../../facet-field-bucket.interface';
import { FacetQuery } from '../../facet-query.interface';
import { FacetField } from '../../facet-field.interface';
import { SearchFilterList } from './models/search-filter-list.model';
import { ResponseFacetQueryList } from './models/response-facet-query-list.model';
describe('SearchFilterComponent', () => {
@ -84,24 +82,26 @@ describe('SearchFilterComponent', () => {
it('should unselect facet query and update builder', () => {
spyOn(queryBuilder, 'update').and.stub();
spyOn(queryBuilder, 'removeUserFacetQuery').and.callThrough();
spyOn(queryBuilder, 'removeUserFacetBucket').and.callThrough();
const event: any = { checked: false };
const query: FacetQuery = { checked: true, label: 'q1', query: 'query1' };
const query = { checked: true, label: 'q1', filterQuery: 'query1' };
const field = { type: 'query', label: 'label1', buckets: [ query ] };
component.onToggleFacetQuery(event, query);
component.onToggleBucket(event, <any> field, <any> query);
expect(query.checked).toBeFalsy();
expect(queryBuilder.removeUserFacetQuery).toHaveBeenCalledWith(query);
expect(queryBuilder.removeUserFacetBucket).toHaveBeenCalledWith(field, query);
expect(queryBuilder.update).toHaveBeenCalled();
});
it('should fetch facet queries from response payload', () => {
component.responseFacetQueries = null;
component.responseFacets = null;
queryBuilder.config = {
categories: [],
facetQueries: {
label: 'label1',
queries: [
{ label: 'q1', query: 'query1' },
{ label: 'q2', query: 'query2' }
@ -110,29 +110,34 @@ describe('SearchFilterComponent', () => {
};
const queries = [
{ label: 'q1', query: 'query1', count: 1 },
{ label: 'q2', query: 'query2', count: 1 }
{ label: 'q1', filterQuery: 'query1', metrics: [{value: {count: 1}}] },
{ label: 'q2', filterQuery: 'query2', metrics: [{value: {count: 1}}] }
];
const data = {
list: {
context: {
facetQueries: queries
facets: [{
type: 'query',
label: 'label1',
buckets: queries
}]
}
}
};
component.onDataLoaded(data);
expect(component.responseFacetQueries.length).toBe(2);
expect(component.responseFacetQueries.items).toEqual(queries);
expect(component.responseFacets.length).toBe(1);
expect(component.responseFacets[0].buckets.length).toEqual(2);
});
it('should preserve order after response processing', () => {
component.responseFacetQueries = null;
component.responseFacets = null;
queryBuilder.config = {
categories: [],
facetQueries: {
label: 'label1',
queries: [
{ label: 'q1', query: 'query1' },
{ label: 'q2', query: 'query2' },
@ -142,29 +147,34 @@ describe('SearchFilterComponent', () => {
};
const queries = [
{ label: 'q2', query: 'query2', count: 1 },
{ label: 'q1', query: 'query1', count: 1 },
{ label: 'q3', query: 'query3', count: 1 }
{ label: 'q2', filterQuery: 'query2', metrics: [{value: {count: 1}}] },
{ label: 'q1', filterQuery: 'query1', metrics: [{value: {count: 1}}] },
{ label: 'q3', filterQuery: 'query3', metrics: [{value: {count: 1}}] }
];
const data = {
list: {
context: {
facetQueries: queries
facets: [{
type: 'query',
label: 'label1',
buckets: queries
}]
}
}
};
component.onDataLoaded(data);
expect(component.responseFacetQueries.length).toBe(3);
expect(component.responseFacetQueries.items[0].label).toBe('q1');
expect(component.responseFacetQueries.items[1].label).toBe('q2');
expect(component.responseFacetQueries.items[2].label).toBe('q3');
expect(component.responseFacets.length).toBe(1);
expect(component.responseFacets[0].buckets.length).toBe(3);
expect(component.responseFacets[0].buckets.items[0].label).toBe('q1');
expect(component.responseFacets[0].buckets.items[1].label).toBe('q2');
expect(component.responseFacets[0].buckets.items[2].label).toBe('q3');
});
it('should not fetch facet queries from response payload', () => {
component.responseFacetQueries = null;
component.responseFacets = null;
queryBuilder.config = {
categories: [],
@ -176,18 +186,18 @@ describe('SearchFilterComponent', () => {
const data = {
list: {
context: {
facetQueries: null
facets: null
}
}
};
component.onDataLoaded(data);
expect(component.responseFacetQueries).toBeNull();
expect(component.responseFacets.length).toBe(0);
});
it('should fetch facet fields from response payload', () => {
component.responseFacetFields = null;
component.responseFacets = null;
queryBuilder.config = {
categories: [],
@ -201,20 +211,22 @@ describe('SearchFilterComponent', () => {
};
const fields: any = [
{ label: 'f1', buckets: [] },
{ label: 'f2', buckets: [] }
{ type: 'field', label: 'f1', buckets: [{ label: 'a1' }, { label: 'a2' }] },
{ type: 'field', label: 'f2', buckets: [{ label: 'b1' }, { label: 'b2' }] }
];
const data = {
list: {
context: {
facetsFields: fields
facets: fields
}
}
};
component.onDataLoaded(data);
expect(component.responseFacetFields.length).toEqual(2);
expect(component.responseFacets.length).toEqual(2);
expect(component.responseFacets[0].buckets.length).toEqual(2);
expect(component.responseFacets[1].buckets.length).toEqual(2);
});
it('should filter response facet fields based on search filter config method', () => {
@ -230,10 +242,10 @@ describe('SearchFilterComponent', () => {
};
const initialFields: any = [
{ label: 'f1', buckets: [
{ label: 'firstLabel', display: 'firstLabel', count: 5 },
{ label: 'secondLabel', display: 'secondLabel', count: 5 },
{ label: 'thirdLabel', display: 'thirdLabel', count: 5 }
{ type: 'field', label: 'f1', buckets: [
{ label: 'firstLabel', display: 'firstLabel', metrics: [{value: {count: 5}}] },
{ label: 'secondLabel', display: 'secondLabel', metrics: [{value: {count: 5}}] },
{ label: 'thirdLabel', display: 'thirdLabel', metrics: [{value: {count: 5}}] }
]
}
];
@ -241,31 +253,32 @@ describe('SearchFilterComponent', () => {
const data = {
list: {
context: {
facetsFields: initialFields
facets: initialFields
}
}
};
component.onDataLoaded(data);
expect(component.responseFacetFields[0].buckets.visibleItems.length).toBe(3);
expect(component.responseFacets.length).toBe(1);
expect(component.responseFacets[0].buckets.visibleItems.length).toBe(3);
component.responseFacetFields[0].buckets.filterText = 'f';
expect(component.responseFacetFields[0].buckets.visibleItems.length).toBe(1);
expect(component.responseFacetFields[0].buckets.visibleItems[0].label).toEqual('firstLabel');
component.responseFacets[0].buckets.filterText = 'f';
expect(component.responseFacets[0].buckets.visibleItems.length).toBe(1);
expect(component.responseFacets[0].buckets.visibleItems[0].label).toEqual('firstLabel');
component.responseFacetFields[0].buckets.filterText = 'label';
expect(component.responseFacetFields[0].buckets.visibleItems.length).toBe(0);
component.responseFacets[0].buckets.filterText = 'label';
expect(component.responseFacets[0].buckets.visibleItems.length).toBe(0);
// Set filter method to use contains and test again
queryBuilder.config.filterWithContains = true;
component.responseFacetFields[0].buckets.filterText = 'f';
expect(component.responseFacetFields[0].buckets.visibleItems.length).toBe(1);
component.responseFacetFields[0].buckets.filterText = 'label';
expect(component.responseFacetFields[0].buckets.visibleItems.length).toBe(3);
component.responseFacets[0].buckets.filterText = 'f';
expect(component.responseFacets[0].buckets.visibleItems.length).toBe(1);
component.responseFacets[0].buckets.filterText = 'label';
expect(component.responseFacets[0].buckets.visibleItems.length).toBe(3);
});
it('should fetch facet fields from response payload and show the bucket values', () => {
component.responseFacetFields = null;
component.responseFacets = null;
queryBuilder.config = {
categories: [],
@ -280,26 +293,27 @@ describe('SearchFilterComponent', () => {
const serverResponseFields: any = [
{
type: 'field',
label: 'f1',
buckets: [
{ label: 'b1', count: 10 },
{ label: 'b2', count: 1 }
{ label: 'b1', metrics: [{value: {count: 10}}] },
{ label: 'b2', metrics: [{value: {count: 1}}] }
]
},
{ label: 'f2', buckets: [] }
{ type: 'field', label: 'f2', buckets: [] }
];
const data = {
list: {
context: {
facetsFields: serverResponseFields
facets: serverResponseFields
}
}
};
component.onDataLoaded(data);
expect(component.responseFacetFields[0].buckets.items[0].count).toEqual(10);
expect(component.responseFacetFields[0].buckets.items[1].count).toEqual(1);
expect(component.responseFacets.length).toEqual(2);
expect(component.responseFacets[0].buckets.items[0].count).toEqual(10);
expect(component.responseFacets[0].buckets.items[1].count).toEqual(1);
});
it('should fetch facet fields from response payload and update the existing bucket values', () => {
@ -315,34 +329,34 @@ describe('SearchFilterComponent', () => {
};
const initialFields: any = [
{ label: 'f1', buckets: { items: [{ label: 'b1', count: 10, filterQuery: 'filter' }, { label: 'b2', count: 1 }]} },
{ label: 'f2', buckets: [] }
{ type: 'field', label: 'f1', buckets: { items: [{ label: 'b1', count: 10, filterQuery: 'filter' }, { label: 'b2', count: 1 }]} },
{ type: 'field', label: 'f2', buckets: [] }
];
component.responseFacetFields = initialFields;
expect(component.responseFacetFields[0].buckets.items[0].count).toEqual(10);
expect(component.responseFacetFields[0].buckets.items[1].count).toEqual(1);
component.responseFacets = initialFields;
expect(component.responseFacets[0].buckets.items[0].count).toEqual(10);
expect(component.responseFacets[0].buckets.items[1].count).toEqual(1);
const serverResponseFields: any = [
{ label: 'f1', buckets: [{ label: 'b1', count: 6, filterQuery: 'filter' }, { label: 'b2', count: 0 }] },
{ label: 'f2', buckets: [] }
{ type: 'field', label: 'f1', buckets:
[{ label: 'b1', metrics: [{value: {count: 6}}], filterQuery: 'filter' },
{ label: 'b2', metrics: [{value: {count: 0}}] }] },
{ type: 'field', label: 'f2', buckets: [] }
];
const data = {
list: {
context: {
facetsFields: serverResponseFields
facets: serverResponseFields
}
}
};
component.onDataLoaded(data);
expect(component.responseFacetFields[0].buckets.items[0].count).toEqual(6);
expect(component.responseFacetFields[0].buckets.items[1].count).toEqual(0);
expect(component.responseFacets[0].buckets.items[0].count).toEqual(6);
expect(component.responseFacets[0].buckets.items[1].count).toEqual(0);
});
it('should update correctly the existing facetFields bucket values', () => {
component.responseFacetFields = null;
component.responseFacets = null;
queryBuilder.config = {
categories: [],
@ -351,20 +365,22 @@ describe('SearchFilterComponent', () => {
};
const firstCallFields: any = [{
type: 'field',
label: 'f1',
buckets: [{ label: 'b1', count: 10 }]
buckets: [{ label: 'b1', metrics: [{value: {count: 10}}] }]
}];
const firstCallData = { list: { context: { facetsFields: firstCallFields }}};
const firstCallData = { list: { context: { facets: firstCallFields }}};
component.onDataLoaded(firstCallData);
expect(component.responseFacetFields[0].buckets.items[0].count).toEqual(10);
expect(component.responseFacets[0].buckets.items[0].count).toEqual(10);
const secondCallFields: any = [{
type: 'field',
label: 'f1',
buckets: [{ label: 'b1', count: 6 }]
buckets: [{ label: 'b1', metrics: [{value: {count: 6}}] }]
}];
const secondCallData = { list: { context: { facetsFields: secondCallFields}}};
const secondCallData = { list: { context: { facets: secondCallFields}}};
component.onDataLoaded(secondCallData);
expect(component.responseFacetFields[0].buckets.items[0].count).toEqual(6);
expect(component.responseFacets[0].buckets.items[0].count).toEqual(6);
});
it('should fetch facet fields from response payload and show the already checked items', () => {
@ -380,31 +396,31 @@ describe('SearchFilterComponent', () => {
}
};
component.responseFacetFields = <any> [
component.responseFacets = <any> [
{ label: 'f1', field: 'f1', buckets: {items: [
{ label: 'b1', count: 10, filterQuery: 'filter', checked: true },
{ label: 'b2', count: 1, filterQuery: 'filter2' }] }},
{ label: 'f2', field: 'f2', buckets: {items: [] }}
];
component.queryBuilder.addUserFacetBucket({ label: 'f1', field: 'f1' }, component.responseFacetFields[0].buckets.items[0]);
component.queryBuilder.addUserFacetBucket({ label: 'f1', field: 'f1' }, component.responseFacets[0].buckets.items[0]);
const serverResponseFields: any = [
{ label: 'f1', field: 'f1', buckets: [
{ label: 'b1', count: 6, filterQuery: 'filter' },
{ label: 'b2', count: 1, filterQuery: 'filter2' }] },
{ label: 'f2', field: 'f2', buckets: [] }
{ type: 'field', label: 'f1', field: 'f1', buckets: [
{ label: 'b1', metrics: [{value: {count: 6}}], filterQuery: 'filter' },
{ label: 'b2', metrics: [{value: {count: 1}}], filterQuery: 'filter2' }] },
{ type: 'field', label: 'f2', field: 'f2', buckets: [] }
];
const data = {
list: {
context: {
facetsFields: serverResponseFields
facets: serverResponseFields
}
}
};
component.selectFacetBucket({ field: 'f1', label: 'f1' }, component.responseFacetFields[0].buckets.items[1]);
component.selectFacetBucket({ field: 'f1', label: 'f1' }, component.responseFacets[0].buckets.items[1]);
component.onDataLoaded(data);
expect(component.responseFacetFields.length).toEqual(2);
expect(component.responseFacetFields[0].buckets.items[0].checked).toEqual(true, 'should show the already checked item');
expect(component.responseFacets.length).toEqual(2);
expect(component.responseFacets[0].buckets.items[0].checked).toEqual(true, 'should show the already checked item');
});
it('should fetch facet fields from response payload and show the newly checked items', () => {
@ -420,31 +436,31 @@ describe('SearchFilterComponent', () => {
}
};
component.responseFacetFields = <any> [
component.responseFacets = <any> [
{ label: 'f1', field: 'f1', buckets: {items: [
{ label: 'b1', count: 10, filterQuery: 'filter', checked: true },
{ label: 'b2', count: 1, filterQuery: 'filter2' }] }},
{ label: 'f2', field: 'f2', buckets: {items: [] }}
];
component.queryBuilder.addUserFacetBucket({ label: 'f1', field: 'f1' }, component.responseFacetFields[0].buckets.items[0]);
component.queryBuilder.addUserFacetBucket({ label: 'f1', field: 'f1' }, component.responseFacets[0].buckets.items[0]);
const serverResponseFields: any = [
{ label: 'f1', field: 'f1', buckets: [
{ label: 'b1', count: 6, filterQuery: 'filter' },
{ label: 'b2', count: 1, filterQuery: 'filter2' }] },
{ label: 'f2', field: 'f2', buckets: [] }
{ type: 'field', label: 'f1', field: 'f1', buckets: [
{ label: 'b1', metrics: [{value: {count: 6}}], filterQuery: 'filter' },
{ label: 'b2', metrics: [{value: {count: 1}}], filterQuery: 'filter2' }] },
{ type: 'field', label: 'f2', field: 'f2', buckets: [] }
];
const data = {
list: {
context: {
facetsFields: serverResponseFields
facets: serverResponseFields
}
}
};
component.selectFacetBucket({ label: 'f1', field: 'f1' }, component.responseFacetFields[0].buckets.items[1]);
component.selectFacetBucket({ label: 'f1', field: 'f1' }, component.responseFacets[0].buckets.items[1]);
component.onDataLoaded(data);
expect(component.responseFacetFields.length).toEqual(2);
expect(component.responseFacetFields[0].buckets.items[1].checked).toEqual(true, 'should show the newly checked item');
expect(component.responseFacets.length).toEqual(2);
expect(component.responseFacets[0].buckets.items[1].checked).toEqual(true, 'should show the newly checked item');
});
it('should show buckets with 0 values when there are no facet fields on the response payload', () => {
@ -460,23 +476,23 @@ describe('SearchFilterComponent', () => {
}
};
component.responseFacetFields = <any> [
component.responseFacets = <any> [
{ label: 'f1', field: 'f1', buckets: {items: [
{ label: 'b1', count: 10, filterQuery: 'filter', checked: true },
{ label: 'b2', count: 1, filterQuery: 'filter2' }] }},
{ label: 'f2', field: 'f2', buckets: {items: [] }}
];
component.queryBuilder.addUserFacetBucket({ label: 'f1', field: 'f1' }, component.responseFacetFields[0].buckets.items[0]);
component.queryBuilder.addUserFacetBucket({ label: 'f1', field: 'f1' }, component.responseFacets[0].buckets.items[0]);
const data = {
list: {
context: {}
}
};
component.selectFacetBucket({ label: 'f1', field: 'f1' }, component.responseFacetFields[0].buckets.items[1]);
component.selectFacetBucket({ label: 'f1', field: 'f1' }, component.responseFacets[0].buckets.items[1]);
component.onDataLoaded(data);
expect(component.responseFacetFields[0].buckets.items[0].count).toEqual(0);
expect(component.responseFacetFields[0].buckets.items[1].count).toEqual(0);
expect(component.responseFacets[0].buckets.items[0].count).toEqual(0);
expect(component.responseFacets[0].buckets.items[1].count).toEqual(0);
});
it('should update query builder only when has bucket to unselect', () => {
@ -557,20 +573,23 @@ describe('SearchFilterComponent', () => {
it('should update query builder upon resetting selected queries', () => {
spyOn(queryBuilder, 'update').and.stub();
spyOn(queryBuilder, 'removeUserFacetQuery').and.callThrough();
spyOn(queryBuilder, 'removeUserFacetBucket').and.callThrough();
component.canResetSelectedQueries = true;
component.responseFacetQueries = new ResponseFacetQueryList([
{ label: 'q1', query: 'q1', checked: true, count: 1 },
{ label: 'q2', query: 'q2', checked: false, count: 1 },
{ label: 'q3', query: 'q3', checked: true, count: 1 }
], translationMock);
component.resetSelectedQueries();
const queryResponse = <any> {
label: 'query response',
buckets: <any> {
items: [
{ label: 'q1', query: 'q1', checked: true, metrics: [{value: {count: 1}}] },
{ label: 'q2', query: 'q2', checked: false, metrics: [{value: {count: 1}}] },
{ label: 'q3', query: 'q3', checked: true, metrics: [{value: {count: 1}}] }]
}};
component.responseFacets = [queryResponse];
component.resetSelectedBuckets(queryResponse);
expect(queryBuilder.removeUserFacetQuery).toHaveBeenCalledTimes(3);
expect(queryBuilder.removeUserFacetBucket).toHaveBeenCalledTimes(3);
expect(queryBuilder.update).toHaveBeenCalled();
for (let entry of component.responseFacetQueries.items) {
for (let entry of component.responseFacets[0].buckets.items) {
expect(entry.checked).toBeFalsy();
}
});

View File

@ -20,12 +20,10 @@ import { MatCheckboxChange } from '@angular/material';
import { SearchService, TranslationService } from '@alfresco/adf-core';
import { SearchQueryBuilderService } from '../../search-query-builder.service';
import { FacetFieldBucket } from '../../facet-field-bucket.interface';
import { ResponseFacetQueryList } from './models/response-facet-query-list.model';
import { FacetQuery } from '../../facet-query.interface';
import { FacetField } from '../../facet-field.interface';
import { SearchFilterList } from './models/search-filter-list.model';
import { takeWhile } from 'rxjs/operators';
import { ResultSetPaging } from '@alfresco/js-api';
import { ResultSetPaging, GenericBucket, GenericFacetResponse, ResultSetContext } from '@alfresco/js-api';
@Component({
selector: 'adf-search-filter',
@ -39,16 +37,13 @@ export class SearchFilterComponent implements OnInit, OnDestroy {
private DEFAULT_PAGE_SIZE = 5;
isAlive = true;
responseFacetQueries: ResponseFacetQueryList = null;
responseFacetFields: FacetField[] = null;
responseFacets: FacetField[] = null;
private facetQueriesPageSize = this.DEFAULT_PAGE_SIZE;
facetQueriesLabel: string = 'Facet Queries';
facetQueriesExpanded = false;
facetFieldsExpanded = false;
canResetSelectedQueries = false;
selectedFacetQueries: Array<FacetQuery> = [];
selectedBuckets: Array<{ field: FacetField, bucket: FacetFieldBucket }> = [];
constructor(public queryBuilder: SearchQueryBuilderService,
@ -85,38 +80,10 @@ export class SearchFilterComponent implements OnInit, OnDestroy {
this.isAlive = false;
}
onToggleFacetQuery(event: MatCheckboxChange, facetQuery: FacetQuery) {
if (event && facetQuery) {
if (event.checked) {
this.selectFacetQuery(facetQuery);
} else {
this.unselectFacetQuery(facetQuery);
}
}
}
selectFacetQuery(query: FacetQuery) {
if (query) {
query.checked = true;
this.queryBuilder.addUserFacetQuery(query);
this.updateSelectedFields();
this.queryBuilder.update();
}
}
unselectFacetQuery(query: FacetQuery) {
if (query) {
query.checked = false;
this.queryBuilder.removeUserFacetQuery(query);
this.updateSelectedFields();
this.queryBuilder.update();
}
}
private updateSelectedBuckets() {
if (this.responseFacetFields) {
if (this.responseFacets) {
this.selectedBuckets = [];
for (let field of this.responseFacetFields) {
for (let field of this.responseFacets) {
if (field.buckets) {
this.selectedBuckets.push(
...this.queryBuilder.getUserFacetBuckets(field.field)
@ -132,16 +99,6 @@ export class SearchFilterComponent implements OnInit, OnDestroy {
}
}
private updateSelectedFields() {
if (this.responseFacetQueries) {
this.selectedFacetQueries = this.responseFacetQueries.items.filter((item) => item.checked);
this.canResetSelectedQueries = this.selectedFacetQueries.length > 0;
} else {
this.selectedFacetQueries = [];
this.canResetSelectedQueries = false;
}
}
onToggleBucket(event: MatCheckboxChange, field: FacetField, bucket: FacetFieldBucket) {
if (event && bucket) {
if (event.checked) {
@ -170,18 +127,6 @@ export class SearchFilterComponent implements OnInit, OnDestroy {
}
}
resetSelectedQueries() {
if (this.canResetSelectedQueries) {
for (let query of this.responseFacetQueries.items) {
query.checked = false;
this.queryBuilder.removeUserFacetQuery(query);
}
this.selectedFacetQueries = [];
this.canResetSelectedQueries = false;
this.queryBuilder.update();
}
}
canResetSelectedBuckets(field: FacetField): boolean {
if (field && field.buckets) {
return field.buckets.items.some((bucket) => bucket.checked);
@ -200,65 +145,37 @@ export class SearchFilterComponent implements OnInit, OnDestroy {
}
}
shouldExpand(field: FacetField): boolean {
return field.type === 'query' ? this.facetQueriesExpanded : this.facetFieldsExpanded;
}
onDataLoaded(data: any) {
const context = data.list.context;
if (context) {
this.parseFacetFields(context);
this.parseFacetQueries(context);
this.parseFacets(context);
} else {
this.responseFacetQueries = null;
this.responseFacetFields = null;
this.responseFacets = null;
}
}
private parseFacetFields(context: any) {
if (!this.responseFacetFields) {
const configFacetFields = this.queryBuilder.config.facetFields && this.queryBuilder.config.facetFields.fields || [];
this.responseFacetFields = configFacetFields.map((field) => {
const responseField = (context.facetsFields || []).find((response) => response.label === field.label);
const buckets: FacetFieldBucket[] = ((responseField && responseField.buckets) || []).map((bucket) => {
const selectedBucket = this.selectedBuckets.find((facetBucket) =>
facetBucket.bucket.label === bucket.label && facetBucket.field.field === field.field);
return <FacetFieldBucket> {
...bucket,
checked: !!selectedBucket,
display: bucket.display,
label: bucket.label
};
});
const bucketList = new SearchFilterList<FacetFieldBucket>(buckets, field.pageSize);
bucketList.filter = (bucket: FacetFieldBucket): boolean => {
if (bucket && bucketList.filterText) {
const pattern = (bucketList.filterText || '').toLowerCase();
const label = (this.translationService.instant(bucket.display) || this.translationService.instant(bucket.label)).toLowerCase();
return this.queryBuilder.config.filterWithContains ? label.indexOf(pattern) !== -1 : label.startsWith(pattern);
}
return true;
};
return {
...field,
label: field.label,
pageSize: field.pageSize | this.DEFAULT_PAGE_SIZE,
currentPageSize: field.pageSize | this.DEFAULT_PAGE_SIZE,
buckets: bucketList
};
});
private parseFacets(context: ResultSetContext) {
if (!this.responseFacets) {
const responseFacetFields = this.parseFacetFields(context);
const responseGroupedFacetQueries = this.parseFacetQueries(context);
this.responseFacets = responseFacetFields.concat(...responseGroupedFacetQueries);
} else {
this.responseFacetFields = this.responseFacetFields
this.responseFacets = this.responseFacets
.map((field) => {
let responseField = (context.facetsFields || []).find((response) => response.label === field.label);
let responseField = (context.facets || []).find((response) => response.label === field.label && response.type === field.type);
(field && field.buckets && field.buckets.items || [])
.map((bucket) => {
const responseBucket = ((responseField && responseField.buckets) || []).find((respBucket) => respBucket.label === bucket.label);
bucket.count = responseBucket ? responseBucket.count : 0;
bucket.count = responseBucket ? this.getCountValue(responseBucket) : 0;
return bucket;
});
@ -267,48 +184,111 @@ export class SearchFilterComponent implements OnInit, OnDestroy {
}
}
private parseFacetQueries(context: any) {
const responseQueries = this.getFacetQueryMap(context);
if (this.queryBuilder.config.facetQueries) {
const bkpResponseFacetQueries = Object.assign({}, this.responseFacetQueries);
const facetQueries = (this.queryBuilder.config.facetQueries.queries || [])
.map((query) => {
private parseFacetFields(context: ResultSetContext): FacetField[] {
const configFacetFields = this.queryBuilder.config.facetFields && this.queryBuilder.config.facetFields.fields || [];
const queryResult = responseQueries[query.label];
const bkpQuery = (bkpResponseFacetQueries.items || []).find((item) => item.label === query.label);
return configFacetFields.map((field) => {
const responseField = (context.facets || []).find((response) => response.type === 'field' && response.label === field.label) || {};
const responseBuckets = this.getResponseBuckets(responseField);
if (bkpQuery) {
bkpQuery.count = queryResult.count;
return bkpQuery;
}
return <FacetQuery> {
...query,
label: query.label,
count: queryResult.count
};
});
if (facetQueries.length > 0) {
if (this.responseFacetQueries) {
this.responseFacetQueries.items = facetQueries;
} else {
this.responseFacetQueries = new ResponseFacetQueryList(facetQueries, this.translationService, this.facetQueriesPageSize);
const bucketList = new SearchFilterList<FacetFieldBucket>(responseBuckets, field.pageSize);
bucketList.filter = (bucket: FacetFieldBucket): boolean => {
if (bucket && bucketList.filterText) {
const pattern = (bucketList.filterText || '').toLowerCase();
const label = (this.translationService.instant(bucket.display) || this.translationService.instant(bucket.label)).toLowerCase();
return this.queryBuilder.config.filterWithContains ? label.indexOf(pattern) !== -1 : label.startsWith(pattern);
}
return true;
};
} else {
this.responseFacetQueries = null;
}
}
return <FacetField> {
...field,
type: responseField.type,
label: field.label,
pageSize: field.pageSize | this.DEFAULT_PAGE_SIZE,
currentPageSize: field.pageSize | this.DEFAULT_PAGE_SIZE,
buckets: bucketList
};
});
}
private getFacetQueryMap(context: any): { [key: string]: any } {
const result = {};
private parseFacetQueries(context: ResultSetContext): FacetField[] {
const configFacetQueries = this.queryBuilder.config.facetQueries && this.queryBuilder.config.facetQueries.queries || [];
const configGroups = configFacetQueries.reduce((acc, query) => {
const group = this.queryBuilder.getQueryGroup(query);
if (acc[group]) {
acc[group].push(query);
} else {
acc[group] = [query];
}
return acc;
}, []);
(context.facetQueries || []).forEach((query) => {
result[query.label] = query;
const result = [];
Object.keys(configGroups).forEach((group) => {
const responseField = (context.facets || []).find((response) => response.type === 'query' && response.label === group) || {};
const responseBuckets = this.getResponseQueryBuckets(responseField, configGroups[group]);
const bucketList = new SearchFilterList<FacetFieldBucket>(responseBuckets, this.facetQueriesPageSize);
bucketList.filter = (bucket: FacetFieldBucket): boolean => {
if (bucket && bucketList.filterText) {
const pattern = (bucketList.filterText || '').toLowerCase();
const label = (this.translationService.instant(bucket.display) || this.translationService.instant(bucket.label)).toLowerCase();
return this.queryBuilder.config.filterWithContains ? label.indexOf(pattern) !== -1 : label.startsWith(pattern);
}
return true;
};
result.push(<FacetField> {
field: group,
type: responseField.type,
label: group,
pageSize: this.DEFAULT_PAGE_SIZE,
currentPageSize: this.DEFAULT_PAGE_SIZE,
buckets: bucketList
});
});
return result;
}
private getResponseBuckets(responseField: GenericFacetResponse): FacetFieldBucket[] {
return ((responseField && responseField.buckets) || []).map((respBucket) => {
respBucket['count'] = this.getCountValue(respBucket);
return <FacetFieldBucket> {
...respBucket,
checked: false,
display: respBucket.display,
label: respBucket.label
};
});
}
private getResponseQueryBuckets(responseField: GenericFacetResponse, configGroup: any): FacetFieldBucket[] {
return (configGroup || []).map((query) => {
const respBucket = ((responseField && responseField.buckets) || [])
.find((bucket) => bucket.label === query.label);
respBucket['count'] = this.getCountValue(respBucket);
return <FacetFieldBucket> {
...respBucket,
checked: false,
display: respBucket.display,
label: respBucket.label
};
}).filter((bucket) => {
let mincount = this.queryBuilder.config.facetQueries.mincount;
if (mincount === undefined) {
mincount = 1;
}
return bucket.count >= mincount;
});
}
private getCountValue(bucket: GenericBucket): number {
return (!!bucket && !!bucket.metrics && bucket.metrics[0] && bucket.metrics[0].value && bucket.metrics[0].value.count)
|| 0;
}
}

View File

@ -30,4 +30,5 @@ export interface FacetField {
pageSize?: number;
currentPageSize?: number;
checked?: boolean;
type?: string;
}

View File

@ -21,4 +21,5 @@ export interface FacetQuery {
checked?: boolean;
count?: number;
group?: string;
}

View File

@ -31,6 +31,7 @@ export interface SearchConfiguration {
label?: string;
pageSize?: number;
expanded?: boolean;
mincount?: number;
queries: Array<FacetQuery>;
};
facetFields?: {

View File

@ -343,7 +343,7 @@ describe('SearchQueryBuilder', () => {
],
facetQueries: {
queries: [
{ query: 'q1', label: 'q2' }
{ query: 'q1', label: 'q2', group: 'group-name' }
]
}
};

View File

@ -50,7 +50,6 @@ export class SearchQueryBuilderService {
paging: { maxItems?: number; skipCount?: number } = null;
sorting: Array<SearchSortingDefinition> = [];
protected userFacetQueries: FacetQuery[] = [];
protected userFacetBuckets: { [key: string]: Array<FacetFieldBucket> } = {};
get userQuery(): string {
@ -81,39 +80,12 @@ export class SearchQueryBuilderService {
this.categories = (this.config.categories || []).filter((category) => category.enabled);
this.filterQueries = this.config.filterQueries || [];
this.userFacetBuckets = {};
this.userFacetQueries = [];
if (this.config.sorting) {
this.sorting = this.config.sorting.defaults || [];
}
}
}
/**
* Adds a facet query.
* @param query Query to add
*/
addUserFacetQuery(query: FacetQuery) {
if (query) {
const existing = this.userFacetQueries.find((facetQuery) => facetQuery.label === query.label);
if (existing) {
existing.query = query.query;
} else {
this.userFacetQueries.push({ ...query });
}
}
}
/**
* Removes an existing facet query.
* @param query Query to remove
*/
removeUserFacetQuery(query: FacetQuery) {
if (query) {
this.userFacetQueries = this.userFacetQueries
.filter((facetQuery) => facetQuery.label !== query.label);
}
}
/**
* Adds a facet bucket to a field.
* @param field The target field
@ -240,7 +212,7 @@ export class SearchQueryBuilderService {
}
if (query) {
const result: QueryBody = {
const result: QueryBody = <QueryBody> {
query: {
query: query,
language: 'afts'
@ -254,6 +226,7 @@ export class SearchQueryBuilderService {
sort: this.sort
};
result['facetFormat'] = 'V2';
return result;
}
@ -282,6 +255,10 @@ export class SearchQueryBuilderService {
return [];
}
getQueryGroup(query) {
return query.group || this.config.facetQueries.label || 'Facet Queries';
}
/**
* Checks if FacetQueries has been defined
* @returns True if defined, false otherwise
@ -309,6 +286,7 @@ export class SearchQueryBuilderService {
protected get facetQueries(): FacetQuery[] {
if (this.hasFacetQueries) {
return this.config.facetQueries.queries.map((query) => {
query.group = this.getQueryGroup(query);
return <FacetQuery> { ...query };
});
}
@ -333,13 +311,6 @@ export class SearchQueryBuilderService {
.filter((entry) => entry)
.join(' AND ');
if (this.userFacetQueries && this.userFacetQueries.length > 0) {
const combined = this.userFacetQueries
.map((userQuery) => userQuery.query)
.join(' OR ');
result += ` AND (${combined})`;
}
if (this.userFacetBuckets) {
Object.keys(this.userFacetBuckets).forEach((key) => {
const subQuery = (this.userFacetBuckets[key] || [])

View File

@ -988,17 +988,21 @@
],
"properties": {
"label": {
"description": "Category label text",
"description": "Label text for the default facet queries group",
"type": "string"
},
"pageSize": {
"description": "Default page size of the category",
"description": "Default page size for the facet queries groups",
"type": "number"
},
"expanded": {
"description": "Toggles expanded state of the category",
"description": "Toggles expanded state of the facet queries groups",
"type": "boolean"
},
"mincount": {
"description": "This specifies the minimum count required for a facet query to be displayed. The default value is 1.",
"type": "number"
},
"queries": {
"description": "List of custom facet queries",
"type": "array",
@ -1014,6 +1018,10 @@
},
"label": {
"type": "string"
},
"group": {
"description": "The group that the facet query belongs to. If no group is defined, the facet query will appear under the default facet queries group label",
"type": "string"
}
}
}