From e34f80aff73022823298f6d6eea0d5b779231278 Mon Sep 17 00:00:00 2001 From: Suzana Dirla Date: Mon, 18 Feb 2019 20:51:15 +0200 Subject: [PATCH] [ADF-3497] Facet intervals on search filter (#4255) * [ADF-3497] allow facetIntervals on search * [ADF-3497] update schema json * [ADF-3497] update json * [ADF-3497] documentation update * [ADF-3497] specify that sets are not supported * [ADF-3497] no spaces on labels - mention * [ADF-3497] documentation update * [ADF-3497] update examples & document label key * [ADF-3497] tests added * [ADF-3497] testRail id * [ADF-3497] allow config of custom pageSize values * [ADF-3497] support mincount filtering also for intervals * [ADF-3497] support expanded property also for facetIntervals * remove no longer needed info - bcs. of PR #4322 fix --- demo-shell/src/app.config.json | 26 ++++- .../search-filter.component.md | 50 ++++++++++ .../images/search-facet-intervals.png | Bin 0 -> 29743 bytes e2e/pages/adf/searchFiltersPage.ts | 12 +++ e2e/search/search-filters.e2e.ts | 8 ++ .../search-filter.component.spec.ts | 92 +++++++++++++++++- .../search-filter/search-filter.component.ts | 57 +++++++---- .../search/search-configuration.interface.ts | 4 + .../search-query-builder.service.spec.ts | 33 +++++++ .../search/search-query-builder.service.ts | 31 ++++++ lib/core/app-config/schema.json | 76 +++++++++++++++ 11 files changed, 368 insertions(+), 21 deletions(-) create mode 100644 docs/docassets/images/search-facet-intervals.png diff --git a/demo-shell/src/app.config.json b/demo-shell/src/app.config.json index 666c6448c8..600f4e6ebc 100644 --- a/demo-shell/src/app.config.json +++ b/demo-shell/src/app.config.json @@ -122,7 +122,7 @@ "expanded": true, "mincount": 1, "queries": [ - { "query": "created:2018", "label": "SEARCH.FACET_QUERIES.CREATED_THIS_YEAR" }, + { "query": "created:2019", "label": "SEARCH.FACET_QUERIES.CREATED_THIS_YEAR" }, { "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"}, @@ -132,6 +132,30 @@ { "query": "content.size:[134217728 TO MAX]", "label": "SEARCH.FACET_QUERIES.XXTRALARGE", "group":"Size facet queries" } ] }, + "facetIntervals":{ + "expanded": true, + "intervals":[ + { + "label":"TheCreated", + "field":"cm:created", + "sets":[ + { "label":"lastYear", "start":"2018", "end":"2019", "endInclusive":false }, + { "label":"currentYear", "start":"NOW/YEAR", "end":"NOW/YEAR+1YEAR" }, + { "label":"earlier", "start":"*", "end":"2018", "endInclusive":false } + ] + }, + { + "label":"TheModified", + "field":"cm:modified", + "sets":[ + { "label":"2017", "start":"2017", "end":"2018", "endInclusive":false }, + { "label":"2017-2018", "start":"2017", "end":"2018", "endInclusive":true }, + { "label":"currentYear", "start":"NOW/YEAR", "end":"NOW/YEAR+1YEAR" }, + { "label":"earlierThan2017", "start":"*", "end":"2017", "endInclusive":false } + ] + } + ] + }, "categories": [ { "id": "queryName", diff --git a/docs/content-services/search-filter.component.md b/docs/content-services/search-filter.component.md index 2fe05f716e..b4af1f0de0 100644 --- a/docs/content-services/search-filter.component.md +++ b/docs/content-services/search-filter.component.md @@ -352,6 +352,56 @@ The default page size of 5 will be used if you set the value to 0 or omit it ent ![Facet Queries](../docassets/images/search-facet-queries.png) +### Facet Intervals + +These provide custom categories based on admin defined ranges inside `intervals`. What is wanted for every interval can be specified exactly in the config file, and having overlapping ranges could also be possible. + +#### FacetIntervals Properties +| Name | Type | Description | +| ---- | ---- | ----------- | +|intervals|array|Specifies the fields to facet by interval.| +|expanded|boolean|Toggles expanded state of the facet intervals.| +Note: `sets` parameter from Search API (Sets the intervals for all fields) is not yet supported. + + +```json +{ + "search": { + "facetIntervals":{ + "expanded": true, + "intervals":[ + { + "label":"TheCreated", + "field":"cm:created", + "sets":[ + { "label":"lastYear", "start":"2017", "end":"2018", "endInclusive":false }, + { "label":"currentYear", "start":"NOW/YEAR", "end":"NOW/YEAR+1YEAR" }, + { "label":"earlier", "start":"*", "end":"2017", "endInclusive":false } + ] + }, + { + "label":"TheModified", + "field":"cm:modified", + "sets":[ + { "label":"2016", "start":"2017", "end":"2018", "endInclusive":false }, + { "label":"currentYear", "start":"NOW/YEAR", "end":"NOW/YEAR+1YEAR" }, + { "label":"earlierThan2017", "start":"*", "end":"2017", "endInclusive":false } + ] + } + ] + } + } +} +``` + +You can specify a value for the `mincount` property inside each `intervals` item to set the minimum count required for a facet interval to be displayed. By default, only the intervals that have 1 or more response entries are displayed at runtime. +Check the [schema.json](https://github.com/Alfresco/alfresco-ng2-components/blob/master/lib/core/app-config/schema.json) +for more details about what is the structure and the properties of `intervals` that you can set inside the configuration file. + +Each `intervals` item defined is collected into its collapsible category identified uniquely by its `label`. The top code snippet will result in the following display of the facet intervals: + +![Facet Intervals](../docassets/images/search-facet-intervals.png) + ## See also - [Search Query Builder service](search-query-builder.service.md) diff --git a/docs/docassets/images/search-facet-intervals.png b/docs/docassets/images/search-facet-intervals.png new file mode 100644 index 0000000000000000000000000000000000000000..b9eccd6361ab714e4ffd4e6186ea719c44c22bc0 GIT binary patch literal 29743 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 I*Igd}ACDwXu>b%7 literal 0 HcmV?d00001 diff --git a/e2e/pages/adf/searchFiltersPage.ts b/e2e/pages/adf/searchFiltersPage.ts index cc46338836..bb4ac547d7 100644 --- a/e2e/pages/adf/searchFiltersPage.ts +++ b/e2e/pages/adf/searchFiltersPage.ts @@ -37,6 +37,8 @@ export class SearchFiltersPage { '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"]')); + facetIntervalsByCreated = element(by.css('mat-expansion-panel[data-automation-id="expansion-panel-TheCreated"]')); + facetIntervalsByModified = element(by.css('mat-expansion-panel[data-automation-id="expansion-panel-TheModified"]')); checkSearchFiltersIsDisplayed() { Util.waitUntilElementIsVisible(this.searchFilters); @@ -110,6 +112,16 @@ export class SearchFiltersPage { return this; } + checkFacetIntervalsByCreatedIsDisplayed() { + this.searchCategoriesPage.checkFilterIsDisplayed(this.facetIntervalsByCreated); + return this; + } + + checkFacetIntervalsByModifiedIsDisplayed() { + this.searchCategoriesPage.checkFilterIsDisplayed(this.facetIntervalsByModified); + return this; + } + isTypeFacetQueryGroupPresent() { return this.facetQueriesTypeGroup.isPresent(); } diff --git a/e2e/search/search-filters.e2e.ts b/e2e/search/search-filters.e2e.ts index 526317dd92..8756651c49 100644 --- a/e2e/search/search-filters.e2e.ts +++ b/e2e/search/search-filters.e2e.ts @@ -207,4 +207,12 @@ describe('Search Filters', () => { expect(searchFiltersPage.isTypeFacetQueryGroupPresent()).toBe(false); expect(searchFiltersPage.isSizeFacetQueryGroupPresent()).toBe(false); }); + + it('[C297509] Should display search intervals under specified labels from config', () => { + browser.get(TestConfig.adf.url + '/search;q=*'); + + searchFiltersPage.checkFacetIntervalsByCreatedIsDisplayed() + .checkFacetIntervalsByModifiedIsDisplayed(); + }); + }); diff --git a/lib/content-services/search/components/search-filter/search-filter.component.spec.ts b/lib/content-services/search/components/search-filter/search-filter.component.spec.ts index 04a5510416..e33fc432df 100644 --- a/lib/content-services/search/components/search-filter/search-filter.component.spec.ts +++ b/lib/content-services/search/components/search-filter/search-filter.component.spec.ts @@ -202,8 +202,8 @@ describe('SearchFilterComponent', () => { queryBuilder.config = { categories: [], facetFields: { fields: [ - { label: 'f1', field: 'f1' }, - { label: 'f2', field: 'f2' } + { label: 'f1', field: 'f1', mincount: 0 }, + { label: 'f2', field: 'f2', mincount: 0 } ]}, facetQueries: { queries: [] @@ -593,4 +593,92 @@ describe('SearchFilterComponent', () => { expect(entry.checked).toBeFalsy(); } }); + + it('should fetch facet intervals from response payload', () => { + component.responseFacets = null; + queryBuilder.config = { + categories: [], + facetIntervals: { + intervals: [ + { label: 'test_intervals1', field: 'f1', sets: [ + { label: 'interval1', start: 's1', end: 'e1'}, + { label: 'interval2', start: 's2', end: 'e2'} + ]}, + { label: 'test_intervals2', field: 'f2', sets: [ + { label: 'interval3', start: 's3', end: 'e3'}, + { label: 'interval4', start: 's4', end: 'e4'} + ]} + ] + } + }; + + const response1 = [ + { label: 'interval1', filterQuery: 'query1', metrics: [{ value: { count: 1 }}]}, + { label: 'interval2', filterQuery: 'query2', metrics: [{ value: { count: 2 }}]} + ]; + const response2 = [ + { label: 'interval3', filterQuery: 'query3', metrics: [{ value: { count: 3 }}]}, + { label: 'interval4', filterQuery: 'query4', metrics: [{ value: { count: 4 }}]} + ]; + const data = { + list: { + context: { + facets: [ + { type: 'interval', label: 'test_intervals1', buckets: response1 }, + { type: 'interval', label: 'test_intervals2', buckets: response2 } + ] + } + } + }; + + component.onDataLoaded(data); + + expect(component.responseFacets.length).toBe(2); + expect(component.responseFacets[0].buckets.length).toEqual(2); + expect(component.responseFacets[1].buckets.length).toEqual(2); + }); + + it('should filter out the fetched facet intervals that have bucket values less than their set mincount', () => { + component.responseFacets = null; + queryBuilder.config = { + categories: [], + facetIntervals: { + intervals: [ + { label: 'test_intervals1', field: 'f1', mincount: 2, sets: [ + { label: 'interval1', start: 's1', end: 'e1'}, + { label: 'interval2', start: 's2', end: 'e2'} + ]}, + { label: 'test_intervals2', field: 'f2', mincount: 5, sets: [ + { label: 'interval3', start: 's3', end: 'e3'}, + { label: 'interval4', start: 's4', end: 'e4'} + ]} + ] + } + }; + + const response1 = [ + { label: 'interval1', filterQuery: 'query1', metrics: [{ value: { count: 1 }}]}, + { label: 'interval2', filterQuery: 'query2', metrics: [{ value: { count: 2 }}]} + ]; + const response2 = [ + { label: 'interval3', filterQuery: 'query3', metrics: [{ value: { count: 3 }}]}, + { label: 'interval4', filterQuery: 'query4', metrics: [{ value: { count: 4 }}]} + ]; + const data = { + list: { + context: { + facets: [ + { type: 'interval', label: 'test_intervals1', buckets: response1 }, + { type: 'interval', label: 'test_intervals2', buckets: response2 } + ] + } + } + }; + + component.onDataLoaded(data); + + expect(component.responseFacets.length).toBe(2); + expect(component.responseFacets[0].buckets.length).toEqual(1); + expect(component.responseFacets[1].buckets.length).toEqual(0); + }); }); diff --git a/lib/content-services/search/components/search-filter/search-filter.component.ts b/lib/content-services/search/components/search-filter/search-filter.component.ts index 81ecf2cc89..b618447c8b 100644 --- a/lib/content-services/search/components/search-filter/search-filter.component.ts +++ b/lib/content-services/search/components/search-filter/search-filter.component.ts @@ -41,8 +41,9 @@ export class SearchFilterComponent implements OnInit, OnDestroy { private facetQueriesPageSize = this.DEFAULT_PAGE_SIZE; facetQueriesLabel: string = 'Facet Queries'; - facetQueriesExpanded = false; - facetFieldsExpanded = false; + facetExpanded = { + 'default': false + }; selectedBuckets: Array<{ field: FacetField, bucket: FacetFieldBucket }> = []; @@ -52,10 +53,13 @@ export class SearchFilterComponent implements OnInit, OnDestroy { if (queryBuilder.config && queryBuilder.config.facetQueries) { this.facetQueriesLabel = queryBuilder.config.facetQueries.label || 'Facet Queries'; this.facetQueriesPageSize = queryBuilder.config.facetQueries.pageSize || this.DEFAULT_PAGE_SIZE; - this.facetQueriesExpanded = queryBuilder.config.facetQueries.expanded; + this.facetExpanded['query'] = queryBuilder.config.facetQueries.expanded; } if (queryBuilder.config && queryBuilder.config.facetFields) { - this.facetFieldsExpanded = queryBuilder.config.facetFields.expanded; + this.facetExpanded['field'] = queryBuilder.config.facetFields.expanded; + } + if (queryBuilder.config && queryBuilder.config.facetIntervals) { + this.facetExpanded['interval'] = queryBuilder.config.facetIntervals.expanded; } this.queryBuilder.updated.pipe( @@ -146,7 +150,7 @@ export class SearchFilterComponent implements OnInit, OnDestroy { } shouldExpand(field: FacetField): boolean { - return field.type === 'query' ? this.facetQueriesExpanded : this.facetFieldsExpanded; + return this.facetExpanded[field.type] || this.facetExpanded['default']; } onDataLoaded(data: any) { @@ -162,8 +166,9 @@ export class SearchFilterComponent implements OnInit, OnDestroy { private parseFacets(context: ResultSetContext) { if (!this.responseFacets) { const responseFacetFields = this.parseFacetFields(context); + const responseFacetIntervals = this.parseFacetIntervals(context); const responseGroupedFacetQueries = this.parseFacetQueries(context); - this.responseFacets = responseFacetFields.concat(...responseGroupedFacetQueries); + this.responseFacets = responseFacetFields.concat(...responseGroupedFacetQueries, ...responseFacetIntervals); } else { this.responseFacets = this.responseFacets @@ -184,12 +189,11 @@ export class SearchFilterComponent implements OnInit, OnDestroy { } } - private parseFacetFields(context: ResultSetContext): FacetField[] { - const configFacetFields = this.queryBuilder.config.facetFields && this.queryBuilder.config.facetFields.fields || []; - + private parseFacetItems(context: ResultSetContext, configFacetFields, itemType): FacetField[] { return configFacetFields.map((field) => { - const responseField = (context.facets || []).find((response) => response.type === 'field' && response.label === field.label) || {}; - const responseBuckets = this.getResponseBuckets(responseField); + const responseField = (context.facets || []).find((response) => response.type === itemType && response.label === field.label) || {}; + const responseBuckets = this.getResponseBuckets(responseField) + .filter(this.getFilterByMinCount(field.mincount)); const bucketList = new SearchFilterList(responseBuckets, field.pageSize); bucketList.filter = (bucket: FacetFieldBucket): boolean => { @@ -212,6 +216,16 @@ export class SearchFilterComponent implements OnInit, OnDestroy { }); } + private parseFacetFields(context: ResultSetContext): FacetField[] { + const configFacetFields = this.queryBuilder.config.facetFields && this.queryBuilder.config.facetFields.fields || []; + return this.parseFacetItems(context, configFacetFields, 'field'); + } + + private parseFacetIntervals(context: ResultSetContext): FacetField[] { + const configFacetIntervals = this.queryBuilder.config.facetIntervals && this.queryBuilder.config.facetIntervals.intervals || []; + return this.parseFacetItems(context, configFacetIntervals, 'interval'); + } + private parseFacetQueries(context: ResultSetContext): FacetField[] { const configFacetQueries = this.queryBuilder.config.facetQueries && this.queryBuilder.config.facetQueries.queries || []; const configGroups = configFacetQueries.reduce((acc, query) => { @@ -225,10 +239,13 @@ export class SearchFilterComponent implements OnInit, OnDestroy { }, []); const result = []; + const mincount = this.queryBuilder.config.facetQueries && this.queryBuilder.config.facetQueries.mincount; + const mincountFilter = this.getFilterByMinCount(mincount); 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 responseBuckets = this.getResponseQueryBuckets(responseField, configGroups[group]) + .filter(mincountFilter); const bucketList = new SearchFilterList(responseBuckets, this.facetQueriesPageSize); bucketList.filter = (bucket: FacetFieldBucket): boolean => { @@ -278,12 +295,6 @@ export class SearchFilterComponent implements OnInit, OnDestroy { display: respBucket.display, label: respBucket.label }; - }).filter((bucket) => { - let mincount = this.queryBuilder.config.facetQueries.mincount; - if (mincount === undefined) { - mincount = 1; - } - return bucket.count >= mincount; }); } @@ -291,4 +302,14 @@ export class SearchFilterComponent implements OnInit, OnDestroy { return (!!bucket && !!bucket.metrics && bucket.metrics[0] && bucket.metrics[0].value && bucket.metrics[0].value.count) || 0; } + + private getFilterByMinCount(mincountInput: number) { + return (bucket) => { + let mincount = mincountInput; + if (mincount === undefined) { + mincount = 1; + } + return bucket.count >= mincount; + }; + } } diff --git a/lib/content-services/search/search-configuration.interface.ts b/lib/content-services/search/search-configuration.interface.ts index 5853a3d1f4..598883db78 100644 --- a/lib/content-services/search/search-configuration.interface.ts +++ b/lib/content-services/search/search-configuration.interface.ts @@ -38,6 +38,10 @@ export interface SearchConfiguration { expanded?: boolean; fields: Array; }; + facetIntervals?: { + expanded?: boolean; + intervals: Array; + }; sorting?: { options: Array; defaults: Array; diff --git a/lib/content-services/search/search-query-builder.service.spec.ts b/lib/content-services/search/search-query-builder.service.spec.ts index cc4aec4633..79e2df7434 100644 --- a/lib/content-services/search/search-query-builder.service.spec.ts +++ b/lib/content-services/search/search-query-builder.service.spec.ts @@ -371,6 +371,39 @@ describe('SearchQueryBuilder', () => { expect(compiled.facetFields.facets).toEqual(jasmine.objectContaining(config.facetFields.fields)); }); + it('should build query with custom facet intervals', () => { + const config: SearchConfiguration = { + categories: [ + { id: 'cat1', enabled: true } + ], + facetIntervals: { + intervals: [ + { + label: 'test_intervals1', + field: 'f1', + sets: [ + { label: 'interval1', start: 's1', end: 'e1' }, + { label: 'interval2', start: 's2', end: 'e2' } + ] + }, + { + label: 'test_intervals2', + field: 'f2', + sets: [ + { label: 'interval3', start: 's3', end: 'e3' }, + { label: 'interval4', start: 's4', end: 'e4' } + ] + } + ] + } + }; + const builder = new SearchQueryBuilderService(buildConfig(config), null); + builder.queryFragments['cat1'] = 'cm:name:test'; + + const compiled = builder.buildQuery(); + expect(compiled.facetIntervals).toEqual(jasmine.objectContaining(config.facetIntervals)); + }); + it('should build query with sorting', () => { const config: SearchConfiguration = { fields: [], diff --git a/lib/content-services/search/search-query-builder.service.ts b/lib/content-services/search/search-query-builder.service.ts index 0c6294c4e0..c6037242c3 100644 --- a/lib/content-services/search/search-query-builder.service.ts +++ b/lib/content-services/search/search-query-builder.service.ts @@ -224,6 +224,7 @@ export class SearchQueryBuilderService { fields: this.config.fields, filterQueries: this.filterQueries, facetQueries: this.facetQueries, + facetIntervals: this.facetIntervals, facetFields: this.facetFields, sort: this.sort }; @@ -280,6 +281,20 @@ export class SearchQueryBuilderService { return false; } + /** + * Checks if FacetIntervals has been defined + * @returns True if defined, false otherwise + */ + get hasFacetIntervals(): boolean { + if (this.config + && this.config.facetIntervals + && this.config.facetIntervals.intervals + && this.config.facetIntervals.intervals.length > 0) { + return true; + } + return false; + } + protected get sort(): RequestSortDefinitionInner[] { return this.sorting.map((def) => { return new RequestSortDefinitionInner({ @@ -301,6 +316,22 @@ export class SearchQueryBuilderService { return null; } + protected get facetIntervals(): any { + if (this.hasFacetIntervals) { + const configIntervals = this.config.facetIntervals; + + return { + intervals: configIntervals.intervals.map((interval) => { + label: interval.label, + field: interval.field, + sets: interval.sets + }) + }; + } + + return null; + } + protected getFinalQuery(): string { let query = ''; diff --git a/lib/core/app-config/schema.json b/lib/core/app-config/schema.json index 3f101f4931..8eaf04ee1a 100644 --- a/lib/core/app-config/schema.json +++ b/lib/core/app-config/schema.json @@ -980,6 +980,81 @@ } } }, + "facetIntervals": { + "type": "object", + "required": [ + "intervals" + ], + "properties": { + "intervals": { + "description": "List of facet intervals", + "type": "array", + "items": { + "type": "object", + "required": [ + "label", + "field", + "sets" + ], + "properties": { + "label": { + "description": "This specifies the label to use to identify the field facet.", + "type": "string" + }, + "field": { + "description": "This specifies the field to facet on.", + "type": "string" + }, + "sets": { + "type": "array", + "items": { + "type": "object", + "required": [ + "label", + "start", + "end" + ], + "properties": { + "label": { + "description": "This specifies the label to use to identify the set.", + "type": "string" + }, + "start": { + "description": "This specifies the start of the range.", + "type": "string" + }, + "end": { + "description": "This specifies the end of the range.", + "type": "string" + }, + "startInclusive": { + "description": "When true, the set will include values greater or equal to 'start'. The default value is true.", + "type": "boolean" + }, + "endInclusive": { + "description": "When true, the set will include values less than or equal to 'end'. The default value is true.", + "type": "boolean" + } + } + } + }, + "pageSize": { + "type": "number", + "description": "Display page size" + }, + "mincount": { + "type": "number", + "description": "This specifies the minimum count required for a facet interval to be displayed. The default value is 1." + } + } + } + }, + "expanded": { + "description": "Toggles expanded state of the facet intervals", + "type": "boolean" + } + } + }, "facetQueries": { "type": "object", "required": [ @@ -1017,6 +1092,7 @@ "type": "string" }, "label": { + "description": "Unique identifier for the query", "type": "string" }, "group": {