Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

facets: expand facet items by link #364

Merged
merged 1 commit into from
Jul 11, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 59 additions & 11 deletions rero_ils/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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'),
Expand Down
22 changes: 22 additions & 0 deletions rero_ils/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
)
))
4 changes: 3 additions & 1 deletion ui/src/app/records/records.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
Expand Down Expand Up @@ -79,7 +80,8 @@ import { RolesCheckboxesComponent } from './editor/roles-checkboxes/roles-checkb
DocumentsSearchComponent,
PersonsSearchComponent,
RefAuthorityComponent,
RolesCheckboxesComponent
RolesCheckboxesComponent,
AggregationComponent
],
imports: [
CommonModule,
Expand Down
34 changes: 34 additions & 0 deletions ui/src/app/records/search/aggregation/aggregation.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<section class="mb-2" *ngIf="aggregation.buckets.length">
<a class="text-muted" [ngClass]="{'collapsed': !isOpen(aggregation.title)}"
data-toggle="collapse" href="#{{'agg_'+aggregation.title }}"
aria-expanded="false" aria-controls="libraryData">
<h6 class="mb-0 d-inline border-bottom pb-1 font-weight-bold"><i class="fa fa-caret-down" aria-hidden="true"></i> {{ aggregation.name | translate }}</h6>
</a>
<div class="collapse" [ngClass]="{'show': isOpen(aggregation.title)}" id="{{'agg_'+aggregation.title }}">
<ul class="list-unstyled mb-0">
<li class="form-check" *ngFor="let bucket of aggregation.buckets|slice:0:sizeOfBucket()">
<input class="form-check-input" type="checkbox" [checked]="isFiltered(aggregation.title, bucket.key)" (click)="aggFilter(aggregation.title, bucket.key) ">
<label class="form-check-label">
<span *ngIf="bucket.name">{{ bucket.name | translate }}</span>
<span *ngIf="!bucket.name">{{ bucket.key | translate }}</span> ({{ bucket.doc_count }})
</label>
</li>
</ul>
<div *ngIf="displayMoreAndLessLink()">
<a
*ngIf="moreMode"
href="#"
data-toggle="collapse"
(click)="setMoreMode(false)"
translate
>more…</a>
<a
*ngIf="!moreMode"
href="#"
data-toggle="collapse"
(click)="setMoreMode(true)"
translate
>less…</a>
</div>
</div>
</section>
Empty file.
Original file line number Diff line number Diff line change
@@ -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<AggregationComponent>;

beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ AggregationComponent ]
})
.compileComponents();
}));

beforeEach(() => {
fixture = TestBed.createComponent(AggregationComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
61 changes: 61 additions & 0 deletions ui/src/app/records/search/aggregation/aggregation.component.ts
Original file line number Diff line number Diff line change
@@ -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;
}
}
23 changes: 7 additions & 16 deletions ui/src/app/records/search/search.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -28,22 +28,13 @@ <h6>

<aside *ngIf="aggregations && aggregations.length" class="col-md-4 col-lg-3 order-12 order-md-0">
<div *ngFor="let item of aggregations">
<section class="mb-2" *ngIf="item.buckets.length" >
<a class="text-muted" [ngClass]="{'collapsed': !startOpen(item.title)}"
data-toggle="collapse" href="#{{'agg_'+item.title }}"
aria-expanded="false" aria-controls="libraryData">
<h6 class="mb-0 d-inline border-bottom pb-1 font-weight-bold"><i class="fa fa-caret-down" aria-hidden="true"></i> {{ item.name | translate }}</h6>
</a>
<ul class="list-unstyled collapse" [ngClass]="{'show': startOpen(item.title)}" id="{{'agg_'+item.title }}">
<li class="form-check" *ngFor="let bucket of item.buckets">
<input class="form-check-input" type="checkbox" [checked]="isFiltered(item.title, bucket.key)" (click)="aggFilter(item.title, bucket.key) ">
<label class="form-check-label">
<span *ngIf="bucket.name">{{ bucket.name | translate }}</span>
<span *ngIf="!bucket.name">{{ bucket.key | translate }}</span> ({{ bucket.doc_count }})
</label>
</li>
</ul>
</section>
<app-aggregation
[aggFilters]="aggFilters"
[aggsSettings]="aggsSettings"
[aggregation]="item"
(addAggFilter)="addAggFilter($event)"
(removeAggFilter)="removeAggFilter($event)"
></app-aggregation>
</div>
</aside>
<section [ngClass]="aggregations && aggregations.length ? 'col-md-8 col-lg-9' : 'col-12'">
Expand Down
46 changes: 17 additions & 29 deletions ui/src/app/records/search/search.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ export class SearchComponent implements OnInit {
public language = null;
public permissions = null;
onInitDone = false;

constructor(
protected recordsService: RecordsService,
protected route: ActivatedRoute,
Expand Down Expand Up @@ -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
Expand All @@ -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}`;
}
}
6 changes: 4 additions & 2 deletions ui/src/assets/i18n/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -243,5 +243,7 @@
"Documents": "Documents",
"Persons": "Persons",
"results": "Resultate",
"No result found.": "Keine Resultate gefunden"
}
"No result found.": "Keine Resultate gefunden",
"more…": "Weiteres…",
"less…": "Weniger…"
}
Loading