Skip to content

Commit

Permalink
search: aggregations improvments
Browse files Browse the repository at this point in the history
* Refactors RecordSearchComponent.
* Allows recursives buckets in aggregations.
* Creates a BucketsComponent for displaying aggregations values.
* Creates a RecordSearchService for handling aggregations filters.

Co-Authored-by: Alicia Zangger <alicia.zangger@rero.ch>
Co-Authored-by: Johnny Mariéthoz <Johnny.Mariethoz@rero.ch>
Co-Authored-by: Sébastien Délèze <sebastien.deleze@rero.ch>
  • Loading branch information
3 people committed Mar 31, 2020
1 parent 825cbf5 commit 9b036e3
Show file tree
Hide file tree
Showing 19 changed files with 711 additions and 476 deletions.
2 changes: 2 additions & 0 deletions projects/ng-core-tester/src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,8 @@ const routes: Routes = [
canDelete,
canRead,
aggregations,
aggregationsBucketSize: 2,
aggregationsExpand: ['language'],
formFieldMap,
listHeaders: {
'Content-Type': 'application/rero+json'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ export class EditorComponent implements OnInit, OnDestroy {
* Component initialisation
*/
ngOnInit() {
combineLatest(this.route.params, this.route.queryParams)
combineLatest([this.route.params, this.route.queryParams])
.pipe(map(results => ({ params: results[0], query: results[1] })))
.subscribe(results => {
const params = results.params;
Expand Down
4 changes: 3 additions & 1 deletion projects/rero/ng-core/src/lib/record/record.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import { MultiSchemaTypeComponent } from './editor/multischema/multischema.compo
import { DatepickerTypeComponent } from './editor/type/datepicker-type.component';
import {ToggleWrapperComponent} from './editor/toggle-wrapper/toggle-wrappers.component';
import { hooksFormlyExtension } from './editor/extensions';
import { BucketsComponent } from './search/aggregation/buckets/buckets.component';


@NgModule({
Expand All @@ -75,7 +76,8 @@ import { hooksFormlyExtension } from './editor/extensions';
SwitchComponent,
MultiSchemaTypeComponent,
DatepickerTypeComponent,
ToggleWrapperComponent
ToggleWrapperComponent,
BucketsComponent
],
imports: [
CoreModule,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
<!--
Invenio angular core
Copyright (C) 2019 RERO

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, version 3 of the License.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Invenio angular core
Copyright (C) 2019 RERO
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, version 3 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<div class="card mb-2" *ngIf="aggregation.value.buckets && aggregation.value.buckets.length">
<div class="card-header p-0">
Expand All @@ -24,21 +24,11 @@ <h5 class="mb-0">
</div>
<div class="collapse" [class.show]="showAggregation()">
<div class="card-body">
<ul class="list-unstyled m-0">
<li class="form-check" *ngFor="let bucket of aggregation.value.buckets|slice:0:bucketSize">
<input class="form-check-input" type="checkbox" [checked]="isSelected(bucket.key)"
(click)="updateFilter(bucket.key) ">
<label class="form-check-label">
<span *ngIf="bucket.name">{{ bucket.name }}</span>
<span *ngIf="!bucket.name && aggregation.key != 'language'">{{ bucket.key | translate }}</span>
<span *ngIf="!bucket.name && aggregation.key == 'language'">{{ bucket.key | translateLanguage:language }}</span> ({{ bucket.doc_count }})
</label>
</li>
</ul>
<div *ngIf="displayMoreAndLessLink()">
<button class="btn btn-link ml-2" *ngIf="moreMode" (click)="setMoreMode(false)" translate>more…</button>
<button class="btn btn-link ml-2" *ngIf="!moreMode" (click)="setMoreMode(true)" translate>less…</button>
</div>
<ng-core-record-search-aggregation-buckets
[buckets]="aggregation.value.buckets"
[aggregationKey]="aggregation.key"
[size]="aggregation.bucketSize"
></ng-core-record-search-aggregation-buckets>
</div>
</div>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { TranslateModule, TranslateLoader, TranslateFakeLoader } from '@ngx-translate/core';

import { RecordSearchAggregationComponent } from './aggregation.component';
import { TranslateFakeLoader, TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { UpperCaseFirstPipe } from '../../../pipe/ucfirst.pipe';
import { TranslateLanguagePipe } from '../../../translate/translate-language.pipe';
import { RecordSearchService } from '../record-search.service';
import { RecordSearchAggregationComponent } from './aggregation.component';
import { BucketsComponent } from './buckets/buckets.component';

describe('RecordSearchAggregationComponent', () => {
let component: RecordSearchAggregationComponent;
Expand All @@ -30,13 +31,15 @@ describe('RecordSearchAggregationComponent', () => {
declarations: [
RecordSearchAggregationComponent,
UpperCaseFirstPipe,
TranslateLanguagePipe
TranslateLanguagePipe,
BucketsComponent
],
imports: [
TranslateModule.forRoot({
loader: { provide: TranslateLoader, useClass: TranslateFakeLoader }
})
]
],
providers: [RecordSearchService]
})
.compileComponents();
}));
Expand All @@ -60,52 +63,17 @@ describe('RecordSearchAggregationComponent', () => {
]
}
};
component.selectedValues = ['Filippini, Massimo'];
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});

it('should return true if value is selected', () => {
expect(component.isSelected('Filippini, Massimo')).toBe(true);
});

it('should show aggregation filter', () => {
expect(component.showAggregation()).toBe(true);

component.expand = false;
expect(component.showAggregation()).toBe(true);
});

it('should add value to selected filters', () => {
component.updateFilter('Botturi, Luca');
expect(component.selectedValues.includes('Botturi, Luca')).toBe(true);
});

it('should remove value from selected filters', () => {
component.updateFilter('Filippini, Massimo');
expect(component.selectedValues.includes('Filippini, Massimo')).toBe(false);
});

it('should return a correct bucket size', () => {
component.aggregation.bucketSize = null;
expect(component.bucketSize).toBe(2);

component.aggregation.bucketSize = 1;
expect(component.bucketSize).toBe(1);
// it('should show aggregation filter', () => {
// expect(component.showAggregation()).toBe(true);

component.moreMode = false;
component.aggregation.bucketSize = 1;
expect(component.bucketSize).toBe(2);
});

it('should display link', () => {
component.aggregation.bucketSize = 1;
expect(component.displayMoreAndLessLink).toBeTruthy();

component.aggregation.bucketSize = 5;
expect(component.displayMoreAndLessLink).toBeTruthy();
});
// component.expand = false;
// expect(component.showAggregation()).toBe(true);
// });
});
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Component, Input, Output, EventEmitter } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { Component, Input } from '@angular/core';

@Component({
selector: 'ng-core-record-search-aggregation',
Expand All @@ -26,107 +25,25 @@ export class RecordSearchAggregationComponent {
* Aggregation data
*/
@Input()
public aggregation: { key: string, bucketSize: any, value: { buckets: {}[] } };
aggregation: { key: string, bucketSize: any, value: { buckets: Array<{}> } };

/**
* Selected value for filter
* Current selected values for the aggregation
*/
@Input()
public selectedValues: string[] = [];
aggregationFilters = [];

/**
* Show or hide filter items
* If true, by default buckets are displayed.
*/
@Input()
public expand = true;
expand = true;

/**
* Emit event to parent when a value is clicked
* Display buckets for the aggregation or not.
* @return Boolean
*/
@Output()
public updateAggregationFilter = new EventEmitter<{ term: string, values: string[] }>();

/**
* More and less on aggregation content (facet)
*/
moreMode = true;

/**
* Constructor
* @param translate TranslateService
*/
constructor(private translate: TranslateService) {}

/**
* Interface language
*/
get language() {
return this.translate.currentLang;
}

/**
* Check if a value is already registered in filters.
* @param value - string, filter value
*/
isSelected(value: string) {
return this.selectedValues.includes(value);
}

/**
* Update selected values with given value and emit event to parent
* @param value - string, filter value
*/
updateFilter(value: string) {
if (this.isSelected(value)) {
this.selectedValues = this.selectedValues.filter(selectedValue => selectedValue !== value);
} else {
this.selectedValues.push(value);
}

this.updateAggregationFilter.emit({ term: this.aggregation.key, values: this.selectedValues });
}

/**
* Return bucket size
*/
get bucketSize() {
const aggregationBucketSize = this.aggregation.value.buckets.length;
if (this.aggregation.bucketSize === null) {
return aggregationBucketSize;
} else {
if (this.moreMode) {
return this.aggregation.bucketSize;
} else {
return aggregationBucketSize;
}
}
}

/**
* Show filter values
* @return boolean
*/
showAggregation() {
return this.expand || this.selectedValues.length > 0;
}

/**
* Display more or less link
* @return boolean
*/
displayMoreAndLessLink(): boolean {
if (this.aggregation.bucketSize === null) {
return false;
}
return this.aggregation.value.buckets.length > this.aggregation.bucketSize;
}

/**
* Set More mode
* @param state - boolean
* @return void
*/
setMoreMode(state: boolean) {
this.moreMode = state;
showAggregation(): boolean {
return this.expand || this.aggregationFilters.length > 0;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<!--
Invenio angular core
Copyright (C) 2019 RERO
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, version 3 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<ul *ngIf="buckets" class="list-unstyled m-0">
<li class="form-check" *ngFor="let bucket of buckets|slice:0:bucketSize">
<input class="form-check-input" type="checkbox" [checked]="isSelected(bucket.key)"
(click)="updateFilter(bucket.key)">
<label class="form-check-label">
<span *ngIf="bucket.name">{{ bucket.name }}</span>
<span *ngIf="!bucket.name && aggregationKey != 'language'">{{ bucket.key | translate }}</span>
<span *ngIf="!bucket.name && aggregationKey == 'language'">{{ bucket.key | translateLanguage:language }}</span> ({{ bucket.doc_count }})
</label>
<ng-core-record-search-aggregation-buckets
*ngFor="let aggregation of bucketChildren(bucket)"
[buckets]="aggregation.buckets"
[aggregationKey]="aggregation.key"
[size]="size"
[parents]="getParentsWithValue(bucket.key)"
></ng-core-record-search-aggregation-buckets>
</li>
</ul>
<div *ngIf="displayMoreAndLessLink()">
<button class="btn btn-link ml-2" *ngIf="moreMode" (click)="moreMode = false" translate>more…</button>
<button class="btn btn-link ml-2" *ngIf="!moreMode" (click)="moreMode = true" translate>less…</button>
</div>
Loading

0 comments on commit 9b036e3

Please sign in to comment.