From 7e86c5f480a0df15b0d518cdf8ddf616c50f6125 Mon Sep 17 00:00:00 2001 From: Bertrand Zuchuat Date: Fri, 28 Jun 2019 10:26:18 +0200 Subject: [PATCH] facets: expand facet items by link MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Adds a link to see more or less results on the facet. * Closes #87 Co-Authored-by: Bertrand Zuchuat Co-Authored-by: Johnny Mariéthoz --- rero_ils/config.py | 70 ++++++++++++++++--- rero_ils/utils.py | 22 ++++++ ui/src/app/records/records.module.ts | 4 +- .../aggregation/aggregation.component.html | 34 +++++++++ .../aggregation/aggregation.component.scss | 0 .../aggregation/aggregation.component.spec.ts | 25 +++++++ .../aggregation/aggregation.component.ts | 61 ++++++++++++++++ .../app/records/search/search.component.html | 23 ++---- ui/src/app/records/search/search.component.ts | 46 +++++------- ui/src/assets/i18n/de.json | 6 +- ui/src/assets/i18n/en.json | 24 +++---- ui/src/assets/i18n/en_US.json | 32 +++++---- ui/src/assets/i18n/fr.json | 6 +- ui/src/assets/i18n/it.json | 6 +- 14 files changed, 269 insertions(+), 90 deletions(-) create mode 100644 ui/src/app/records/search/aggregation/aggregation.component.html create mode 100644 ui/src/app/records/search/aggregation/aggregation.component.scss create mode 100644 ui/src/app/records/search/aggregation/aggregation.component.spec.ts create mode 100644 ui/src/app/records/search/aggregation/aggregation.component.ts diff --git a/rero_ils/config.py b/rero_ils/config.py index c87099e2e9..2f2b2db7d9 100644 --- a/rero_ils/config.py +++ b/rero_ils/config.py @@ -52,6 +52,7 @@ from rero_ils.modules.api import IlsRecordIndexer from rero_ils.modules.loans.api import Loan +from rero_ils.utils import get_agg_config from .modules.circ_policies.api import CircPolicy from .modules.documents.api import Document @@ -703,33 +704,80 @@ def _(x): 'status', ], 'expand': ['document_type'], + 'initialBucketSize': 5 }, 'ptrn': { 'order': [ 'roles' ], - 'expand': ['roles'] + 'expand': ['roles'], + 'initialBucketSize': 5 }, 'pers': { 'order': [ 'sources' ], - 'expand': ['sources'] + 'expand': ['sources'], + 'initialBucketSize': 5 }, } +# Default number of results in facet +RERO_ILS_DEFAULT_AGGREGATION_SIZE = 50 + +# Number of aggregation by index name +RERO_ILS_AGGREGATION_SIZE = { + 'documents': 50 +} + RECORDS_REST_FACETS = { 'documents': dict( aggs=dict( - document_type=dict(terms=dict(field='type')), - library=dict(terms=dict(field='items.library_pid')), - author__en=dict(terms=dict(field='facet_authors_en')), - author__fr=dict(terms=dict(field='facet_authors_fr')), - author__de=dict(terms=dict(field='facet_authors_de')), - author__it=dict(terms=dict(field='facet_authors_it')), - language=dict(terms=dict(field='languages.language')), - subject=dict(terms=dict(field='facet_subjects')), - status=dict(terms=dict(field='items.status')) + document_type=partial( + get_agg_config, + index_name='documents', + field='type' + ), + library=partial( + get_agg_config, + index_name='documents', + field='items.library_pid' + ), + author__en=partial( + get_agg_config, + index_name='documents', + field='facet_authors_en' + ), + author__fr=partial( + get_agg_config, + index_name='documents', + field='facet_authors_fr' + ), + author__de=partial( + get_agg_config, + index_name='documents', + field='facet_authors_de' + ), + author__it=partial( + get_agg_config, + index_name='documents', + field='facet_authors_it' + ), + language=partial( + get_agg_config, + index_name='documents', + field='languages.language' + ), + subject=partial( + get_agg_config, + index_name='documents', + field='facet_subjects' + ), + status=partial( + get_agg_config, + index_name='items.status', + field='items.status' + ) ), filters={ _('document_type'): terms_filter('type'), diff --git a/rero_ils/utils.py b/rero_ils/utils.py index b5ff294b5d..f206767df5 100644 --- a/rero_ils/utils.py +++ b/rero_ils/utils.py @@ -65,3 +65,25 @@ def send_mail(subject, recipients, template, language, **context): def unique_list(data): """Unicity of list.""" return list(dict.fromkeys(data)) + + +def get_agg_config(index_name, field): + """Get Elasticsearch aggregation term configuration. + + This function allows to configure aggregation size per index name using + environnement variables. + + :param index_name: name of Elasticsearch index + :param field: field name for the aggregation + :return: dict of Elasticsearch DSL aggregation configuration + """ + from flask import current_app + return dict(terms=dict( + field=field, + size=current_app.config.get( + 'RERO_ILS_AGGREGATION_SIZE', {} + ).get( + index_name, + current_app.config.get('RERO_ILS_DEFAULT_AGGREGATION_SIZE') + ) + )) diff --git a/ui/src/app/records/records.module.ts b/ui/src/app/records/records.module.ts index 5b5fb42ddf..9e782f2911 100644 --- a/ui/src/app/records/records.module.ts +++ b/ui/src/app/records/records.module.ts @@ -48,6 +48,7 @@ import { PersonsSearchComponent } from './search/public-search/persons-search.co import { RefAuthorityComponent } from './editor/ref-authority/ref-authority.component'; import { TypeaheadModule } from 'ngx-bootstrap/typeahead'; import { RolesCheckboxesComponent } from './editor/roles-checkboxes/roles-checkboxes.component'; +import { AggregationComponent } from './search/aggregation/aggregation.component'; @NgModule({ declarations: [ @@ -79,7 +80,8 @@ import { RolesCheckboxesComponent } from './editor/roles-checkboxes/roles-checkb DocumentsSearchComponent, PersonsSearchComponent, RefAuthorityComponent, - RolesCheckboxesComponent + RolesCheckboxesComponent, + AggregationComponent ], imports: [ CommonModule, diff --git a/ui/src/app/records/search/aggregation/aggregation.component.html b/ui/src/app/records/search/aggregation/aggregation.component.html new file mode 100644 index 0000000000..6ebe0c42ff --- /dev/null +++ b/ui/src/app/records/search/aggregation/aggregation.component.html @@ -0,0 +1,34 @@ +
+ +
+
    +
  • + + +
  • +
+ +
+
diff --git a/ui/src/app/records/search/aggregation/aggregation.component.scss b/ui/src/app/records/search/aggregation/aggregation.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/ui/src/app/records/search/aggregation/aggregation.component.spec.ts b/ui/src/app/records/search/aggregation/aggregation.component.spec.ts new file mode 100644 index 0000000000..6e21f7d425 --- /dev/null +++ b/ui/src/app/records/search/aggregation/aggregation.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AggregationComponent } from './aggregation.component'; + +describe('AggregationComponent', () => { + let component: AggregationComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ AggregationComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(AggregationComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/ui/src/app/records/search/aggregation/aggregation.component.ts b/ui/src/app/records/search/aggregation/aggregation.component.ts new file mode 100644 index 0000000000..d0ddfa45dc --- /dev/null +++ b/ui/src/app/records/search/aggregation/aggregation.component.ts @@ -0,0 +1,61 @@ +import { Component, Input, Output, EventEmitter } from '@angular/core'; + +@Component({ + selector: 'app-aggregation', + templateUrl: './aggregation.component.html', + styleUrls: ['./aggregation.component.scss'] +}) +export class AggregationComponent { + + @Input() aggFilters = null; + @Input() aggsSettings = null; + @Input() aggregation = null; + + @Output() addAggFilter = new EventEmitter<{term: string, value: string}>(); + @Output() removeAggFilter = new EventEmitter<{term: string, value: string}>(); + + private moreMode = true; + + isFiltered(term: any, value?: any) { + if (value) { + const filterValue = `${term}=${value}`; + return this.aggFilters.some((val: any) => filterValue === val); + } else { + return this.aggFilters.some((val: any) => term === val.split('=')[0]); + } + } + + aggFilter(term: string, value: string) { + if (this.isFiltered(term, value)) { + this.removeAggFilter.emit({term: term, value: value}); + } else { + this.addAggFilter.emit({term: term, value: value}); + } + } + + isOpen(title: string) { + if (this.isFiltered(title)) { + return true; + } + if (this.aggsSettings.expand.some((value: any) => value === title)) { + return true; + } + return false; + } + + sizeOfBucket() { + if (this.moreMode) { + return this.aggsSettings.initialBucketSize; + } else { + return this.aggregation.buckets.length; + } + } + + displayMoreAndLessLink() { + return this.aggregation.buckets.length > this.aggsSettings.initialBucketSize; + } + + setMoreMode(state: boolean) { + this.moreMode = state; + } +} diff --git a/ui/src/app/records/search/search.component.html b/ui/src/app/records/search/search.component.html index 0fbb0bdde4..b37efe5a92 100644 --- a/ui/src/app/records/search/search.component.html +++ b/ui/src/app/records/search/search.component.html @@ -28,22 +28,13 @@
diff --git a/ui/src/app/records/search/search.component.ts b/ui/src/app/records/search/search.component.ts index cf6a960506..4bd3daa5f9 100644 --- a/ui/src/app/records/search/search.component.ts +++ b/ui/src/app/records/search/search.component.ts @@ -125,6 +125,7 @@ export class SearchComponent implements OnInit { public language = null; public permissions = null; onInitDone = false; + constructor( protected recordsService: RecordsService, protected route: ActivatedRoute, @@ -251,35 +252,6 @@ export class SearchComponent implements OnInit { }); } - aggFilter(term, value) { - const filterValue = `${term}=${value}`; - if (this.isFiltered(term, value)) { - this.aggFilters = this.aggFilters.filter(val => val !== filterValue); - } else { - this.aggFilters.push(filterValue); - } - this.updateRoute(); - } - - isFiltered(term, value?) { - if (value) { - const filterValue = `${term}=${value}`; - return this.aggFilters.some(val => filterValue === val); - } else { - return this.aggFilters.some(val => term === val.split('=')[0]); - } - } - - startOpen(title: string) { - if (this.isFiltered(title)) { - return true; - } - if (this.aggsSettings.expand.some(value => value === title)) { - return true; - } - return false; - } - hasPermissionToCreate() { if (this.permissions && this.permissions.cannot_create @@ -288,4 +260,20 @@ export class SearchComponent implements OnInit { } return true; } + + removeAggFilter(event: any) { + this.aggFilters = this.aggFilters.filter( + val => val !== this.formatFilterValue(event) + ); + this.updateRoute(); + } + + addAggFilter(event: any) { + this.aggFilters.push(this.formatFilterValue(event)); + this.updateRoute(); + } + + formatFilterValue(object: {term: string, value: string}) { + return `${object.term}=${object.value}`; + } } diff --git a/ui/src/assets/i18n/de.json b/ui/src/assets/i18n/de.json index 7bf5c95c17..13120a560c 100644 --- a/ui/src/assets/i18n/de.json +++ b/ui/src/assets/i18n/de.json @@ -243,5 +243,7 @@ "Documents": "Documents", "Persons": "Persons", "results": "Resultate", - "No result found.": "Keine Resultate gefunden" -} \ No newline at end of file + "No result found.": "Keine Resultate gefunden", + "more…": "Weiteres…", + "less…": "Weniger…" +} diff --git a/ui/src/assets/i18n/en.json b/ui/src/assets/i18n/en.json index 97d1820b45..002583ec14 100644 --- a/ui/src/assets/i18n/en.json +++ b/ui/src/assets/i18n/en.json @@ -46,18 +46,18 @@ "readingRoom": "readingRoom", "readingroom": "readingroom", "publicaccess": "publicaccess", - "fre": "fre", - "eng": "eng", - "ger": "ger", - "ita": "ita", - "lat": "lat", - "spa": "spa", - "ara": "ara", - "por": "por", - "rus": "rus", - "heb": "heb", - "jpn": "jpn", - "chi": "chi", + "fre": "French", + "eng": "English", + "ger": "German", + "ita": "Italian", + "lat": "Latin", + "spa": "Spanish", + "ara": "Arabic", + "por": "Portuguese", + "rus": "Russian", + "heb": "Hebrew", + "jpn": "Japanese", + "chi": "Chinese", "und": "Undefined", "patron": "patron", "librarian": "librarian", diff --git a/ui/src/assets/i18n/en_US.json b/ui/src/assets/i18n/en_US.json index 681660ff9f..6096ba8ba9 100644 --- a/ui/src/assets/i18n/en_US.json +++ b/ui/src/assets/i18n/en_US.json @@ -46,19 +46,19 @@ "readingRoom": "readingRoom", "readingroom": "readingroom", "publicaccess": "publicaccess", - "fre": "fre", - "eng": "eng", - "ger": "ger", - "ita": "ita", - "lat": "lat", - "spa": "spa", - "ara": "ara", - "por": "por", - "rus": "rus", - "heb": "heb", - "jpn": "jpn", - "chi": "chi", - "und": "und", + "fre": "French", + "eng": "English", + "ger": "German", + "ita": "Italian", + "lat": "Latin", + "spa": "Spanish", + "ara": "Arabic", + "por": "Portuguese", + "rus": "Russian", + "heb": "Hebrew", + "jpn": "Japanese", + "chi": "Chinese", + "und": "Undefined", "patron": "patron", "librarian": "librarian", "system_librarian": "system librarian", @@ -243,5 +243,7 @@ "Documents": "Documents", "Persons": "Persons", "results": "results", - "No result found.": "No result found." -} \ No newline at end of file + "No result found.": "No result found.", + "more…": "more…", + "less…": "less…" +} diff --git a/ui/src/assets/i18n/fr.json b/ui/src/assets/i18n/fr.json index 63f371e6c2..978f86d708 100644 --- a/ui/src/assets/i18n/fr.json +++ b/ui/src/assets/i18n/fr.json @@ -243,5 +243,7 @@ "Documents": "Documents", "Persons": "Personnes", "results": "résultats", - "No result found.": "Aucun résultat n'a été trouvé." -} \ No newline at end of file + "No result found.": "Aucun résultat n'a été trouvé.", + "more…": "plus…", + "less…": "moins…" +} diff --git a/ui/src/assets/i18n/it.json b/ui/src/assets/i18n/it.json index e01f1d4d2b..9962312c53 100644 --- a/ui/src/assets/i18n/it.json +++ b/ui/src/assets/i18n/it.json @@ -243,5 +243,7 @@ "Documents": "Documenti", "Persons": "Persons", "results": "risultati", - "No result found.": "Nessun risultato trovato." -} \ No newline at end of file + "No result found.": "Nessun risultato trovato.", + "more…": "di più…", + "less…": "di meno…" +}