Skip to content

Commit ff05094

Browse files
authored
Merge pull request DSpace#2163 from jeffmorin/main
Content reports ported from DSpace 6.x
2 parents 0d63a85 + 410a278 commit ff05094

51 files changed

Lines changed: 11184 additions & 2 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { FilteredCollectionsComponent } from './filtered-collections/filtered-collections.component';
2+
import { FilteredItemsComponent } from './filtered-items/filtered-items.component';
3+
import { RouterModule } from '@angular/router';
4+
import { NgModule } from '@angular/core';
5+
import { I18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.resolver';
6+
7+
@NgModule({
8+
imports: [
9+
RouterModule.forChild([
10+
{
11+
path: 'collections',
12+
resolve: { breadcrumb: I18nBreadcrumbResolver },
13+
data: {title: 'admin.reports.collections.title', breadcrumbKey: 'admin.reports.collections'},
14+
children: [
15+
{
16+
path: '',
17+
component: FilteredCollectionsComponent
18+
}
19+
]
20+
},
21+
{
22+
path: 'queries',
23+
resolve: { breadcrumb: I18nBreadcrumbResolver },
24+
data: {title: 'admin.reports.items.title', breadcrumbKey: 'admin.reports.items'},
25+
children: [
26+
{
27+
path: '',
28+
component: FilteredItemsComponent
29+
}
30+
]
31+
}
32+
])
33+
]
34+
})
35+
export class AdminReportsRoutingModule {
36+
37+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { NgModule } from '@angular/core';
2+
import { CommonModule } from '@angular/common';
3+
import { FilteredCollectionsComponent } from './filtered-collections/filtered-collections.component';
4+
import { RouterModule } from '@angular/router';
5+
import { SharedModule } from '../../shared/shared.module';
6+
import { FormModule } from '../../shared/form/form.module';
7+
import { FilteredItemsComponent } from './filtered-items/filtered-items.component';
8+
import { AdminReportsRoutingModule } from './admin-reports-routing.module';
9+
import { NgbAccordionModule } from '@ng-bootstrap/ng-bootstrap';
10+
import { FiltersComponent } from './filters-section/filters-section.component';
11+
12+
@NgModule({
13+
imports: [
14+
CommonModule,
15+
SharedModule,
16+
RouterModule,
17+
FormModule,
18+
AdminReportsRoutingModule,
19+
NgbAccordionModule
20+
],
21+
declarations: [
22+
FilteredCollectionsComponent,
23+
FilteredItemsComponent,
24+
FiltersComponent
25+
]
26+
})
27+
export class AdminReportsModule {
28+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
export class FilteredCollection {
2+
3+
public label: string;
4+
public handle: string;
5+
public communityLabel: string;
6+
public communityHandle: string;
7+
public nbTotalItems: number;
8+
public values = {};
9+
public allFiltersValue: number;
10+
11+
public clear() {
12+
this.label = '';
13+
this.handle = '';
14+
this.communityLabel = '';
15+
this.communityHandle = '';
16+
this.nbTotalItems = 0;
17+
this.values = {};
18+
this.allFiltersValue = 0;
19+
}
20+
21+
public deserialize(object: any) {
22+
this.clear();
23+
this.label = object.label;
24+
this.handle = object.handle;
25+
this.communityLabel = object.community_label;
26+
this.communityHandle = object.community_handle;
27+
this.nbTotalItems = object.nb_total_items;
28+
let valuesPerFilter = object.values;
29+
for (let filter in valuesPerFilter) {
30+
if (valuesPerFilter.hasOwnProperty(filter)) {
31+
this.values[filter] = valuesPerFilter[filter];
32+
}
33+
}
34+
this.allFiltersValue = object.all_filters_value;
35+
}
36+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<div class="container">
2+
<div class="metadata-registry row">
3+
<div class="col-12">
4+
5+
<h1 id="header" class="border-bottom pb-2">{{ "admin.reports.collections.head" | translate }}</h1>
6+
7+
<div id="metadatadiv">
8+
<ngb-accordion [closeOthers]="true" activeIds="filters" #acc="ngbAccordion">
9+
<ngb-panel id="filters">
10+
<ng-template ngbPanelTitle>
11+
{{ "admin.reports.commons.filters" | translate }}
12+
</ng-template>
13+
<ng-template ngbPanelContent>
14+
<div class="container">
15+
<div class="row">
16+
<span class="col-3"></span>
17+
<button class="btn btn-primary mt-1 col-6" (click)="submit()">{{ "admin.reports.button.show-collections" | translate }}</button>
18+
</div>
19+
<ds-filters [filtersForm]="filtersFormGroup()"></ds-filters>
20+
<div class="row">
21+
<span class="col-3"></span>
22+
<button class="btn btn-primary mt-1 col-6" (click)="submit()">{{ "admin.reports.button.show-collections" | translate }}</button>
23+
</div>
24+
</div>
25+
</ng-template>
26+
</ngb-panel>
27+
<ngb-panel id="collections">
28+
<ng-template ngbPanelTitle>
29+
{{ "admin.reports.collections.collections-report" | translate }}
30+
</ng-template>
31+
<ng-template ngbPanelContent>
32+
<table id="table" class="table table-striped">
33+
<thead>
34+
<tr class="header">
35+
<th rowspan="2">{{ "admin.reports.collections.community" | translate }}</th>
36+
<th rowspan="2">{{ "admin.reports.collections.collection" | translate }}</th>
37+
<th>{{ "admin.reports.collections.nb_items" | translate }}</th>
38+
<th>{{ "admin.reports.collections.match_all_selected_filters" | translate }}</th>
39+
<th *ngFor="let filter of results.summary.values | keyvalue">{{ ("admin.reports.commons.filters." + getGroup(filter.key) + "." + filter.key) | translate }}</th>
40+
</tr>
41+
<tr class="header">
42+
<th class="num">{{ results.summary.nbTotalItems }}</th>
43+
<th class="num">{{ results.summary.allFiltersValue }}</th>
44+
<th class="num" *ngFor="let filter of results.summary.values | keyvalue">{{ filter.value }}</th>
45+
</tr>
46+
</thead>
47+
<tbody>
48+
<tr *ngFor="let coll of results.collections">
49+
<td><a href="/handle/{{ coll.communityHandle }}" rel="noopener noreferrer" target="_blank">{{ coll.communityLabel }}</a></td>
50+
<td><a href="/handle/{{ coll.handle }}" rel="noopener noreferrer" target="_blank">{{ coll.label }}</a></td>
51+
<td class="num">{{ coll.nbTotalItems }}</td>
52+
<td class="num">{{ coll.allFiltersValue }}</td>
53+
<td class="num" *ngFor="let filter of results.summary.values | keyvalue">{{ coll.values[filter.key] || 0 }}</td>
54+
</tr>
55+
</tbody>
56+
</table>
57+
</ng-template>
58+
</ngb-panel>
59+
</ngb-accordion>
60+
</div>
61+
62+
</div>
63+
</div>
64+
</div>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.num {
2+
text-align: center;
3+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { waitForAsync, ComponentFixture, TestBed} from '@angular/core/testing';
2+
import { NO_ERRORS_SCHEMA } from '@angular/core';
3+
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
4+
import { TranslateLoaderMock } from 'src/app/shared/mocks/translate-loader.mock';
5+
import { FormBuilder } from '@angular/forms';
6+
import { FilteredCollectionsComponent } from './filtered-collections.component';
7+
import { DspaceRestService } from 'src/app/core/dspace-rest/dspace-rest.service';
8+
import { NgbAccordion, NgbAccordionModule } from '@ng-bootstrap/ng-bootstrap';
9+
import { HttpClientTestingModule } from '@angular/common/http/testing';
10+
import { of as observableOf } from 'rxjs';
11+
import { RawRestResponse } from 'src/app/core/dspace-rest/raw-rest-response.model';
12+
13+
describe('FiltersComponent', () => {
14+
let component: FilteredCollectionsComponent;
15+
let fixture: ComponentFixture<FilteredCollectionsComponent>;
16+
let formBuilder: FormBuilder;
17+
18+
const expected = {
19+
payload: {
20+
collections: [],
21+
summary: {
22+
label: 'Test'
23+
}
24+
},
25+
statusCode: 200,
26+
statusText: 'OK'
27+
} as RawRestResponse;
28+
29+
beforeEach(waitForAsync(() => {
30+
TestBed.configureTestingModule({
31+
declarations: [FilteredCollectionsComponent],
32+
imports: [
33+
NgbAccordionModule,
34+
TranslateModule.forRoot({
35+
loader: {
36+
provide: TranslateLoader,
37+
useClass: TranslateLoaderMock
38+
}
39+
}),
40+
HttpClientTestingModule
41+
],
42+
providers: [
43+
FormBuilder,
44+
DspaceRestService
45+
],
46+
schemas: [NO_ERRORS_SCHEMA]
47+
});
48+
}));
49+
50+
beforeEach(waitForAsync(() => {
51+
formBuilder = TestBed.inject(FormBuilder);
52+
53+
fixture = TestBed.createComponent(FilteredCollectionsComponent);
54+
component = fixture.componentInstance;
55+
fixture.detectChanges();
56+
}));
57+
58+
it('should create the component', () => {
59+
expect(component).toBeTruthy();
60+
});
61+
62+
it('should be displaying the filters panel initially', () => {
63+
let accordion: NgbAccordion = component.accordionComponent;
64+
expect(accordion.isExpanded('filters')).toBeTrue();
65+
});
66+
67+
describe('toggle', () => {
68+
beforeEach(() => {
69+
spyOn(component, 'getFilteredCollections').and.returnValue(observableOf(expected));
70+
spyOn(component.results, 'deserialize');
71+
spyOn(component.accordionComponent, 'expand').and.callThrough();
72+
component.submit();
73+
fixture.detectChanges();
74+
});
75+
76+
it('should be displaying the collections panel after submitting', waitForAsync(() => {
77+
fixture.whenStable().then(() => {
78+
expect(component.accordionComponent.expand).toHaveBeenCalledWith('collections');
79+
expect(component.accordionComponent.isExpanded('collections')).toBeTrue();
80+
});
81+
}));
82+
});
83+
});
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { Component, ViewChild } from '@angular/core';
2+
import { FormBuilder, FormGroup } from '@angular/forms';
3+
import { NgbAccordion } from '@ng-bootstrap/ng-bootstrap';
4+
import { Observable } from 'rxjs';
5+
import { RestRequestMethod } from 'src/app/core/data/rest-request-method';
6+
import { DspaceRestService } from 'src/app/core/dspace-rest/dspace-rest.service';
7+
import { RawRestResponse } from 'src/app/core/dspace-rest/raw-rest-response.model';
8+
import { environment } from 'src/environments/environment';
9+
import { FiltersComponent } from '../filters-section/filters-section.component';
10+
import { FilteredCollections } from './filtered-collections.model';
11+
12+
/**
13+
* Component representing the Filtered Collections content report
14+
*/
15+
@Component({
16+
selector: 'ds-report-filtered-collections',
17+
templateUrl: './filtered-collections.component.html',
18+
styleUrls: ['./filtered-collections.component.scss']
19+
})
20+
export class FilteredCollectionsComponent {
21+
22+
queryForm: FormGroup;
23+
results: FilteredCollections = new FilteredCollections();
24+
@ViewChild('acc') accordionComponent: NgbAccordion;
25+
26+
constructor(
27+
private formBuilder: FormBuilder,
28+
private restService: DspaceRestService) {}
29+
30+
ngOnInit() {
31+
this.queryForm = this.formBuilder.group({
32+
filters: FiltersComponent.formGroup(this.formBuilder)
33+
});
34+
}
35+
36+
filtersFormGroup(): FormGroup {
37+
return this.queryForm.get('filters') as FormGroup;
38+
}
39+
40+
getGroup(filterId: string): string {
41+
return FiltersComponent.getGroup(filterId).id;
42+
}
43+
44+
submit() {
45+
this
46+
.getFilteredCollections()
47+
.subscribe(
48+
response => {
49+
this.results.deserialize(response.payload);
50+
this.accordionComponent.expand('collections');
51+
}
52+
);
53+
}
54+
55+
getFilteredCollections(): Observable<RawRestResponse> {
56+
let params = this.toQueryString();
57+
if (params.length > 0) {
58+
params = `?${params}`;
59+
}
60+
let scheme = environment.rest.ssl ? 'https' : 'http';
61+
let urlRestApp = `${scheme}://${environment.rest.host}:${environment.rest.port}${environment.rest.nameSpace}`;
62+
return this.restService.request(RestRequestMethod.GET, `${urlRestApp}/api/contentreport/filteredcollections${params}`);
63+
}
64+
65+
private toQueryString(): string {
66+
let params = FiltersComponent.toQueryString(this.queryForm.value.filters);
67+
return params;
68+
}
69+
70+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { FilteredCollection } from './filtered-collection.model';
2+
3+
export class FilteredCollections {
4+
5+
public collections: Array<FilteredCollection> = [];
6+
public summary: FilteredCollection = new FilteredCollection();
7+
8+
public clear() {
9+
this.collections.splice(0, this.collections.length);
10+
this.summary.clear();
11+
}
12+
13+
public deserialize(object: any) {
14+
this.clear();
15+
let summary = object.summary;
16+
this.summary.deserialize(summary);
17+
let collections = object.collections;
18+
for (let i = 0; i < collections.length; i++) {
19+
let collection = collections[i];
20+
let coll = new FilteredCollection();
21+
coll.deserialize(collection);
22+
this.collections.push(coll);
23+
}
24+
}
25+
26+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { Item } from 'src/app/core/shared/item.model';
2+
3+
export class FilteredItems {
4+
5+
public items: Item[] = [];
6+
public itemCount: number;
7+
8+
public clear() {
9+
this.items.splice(0, this.items.length);
10+
}
11+
12+
public deserialize(object: any, offset: number = 0) {
13+
this.clear();
14+
this.itemCount = object.itemCount;
15+
let items = object.items;
16+
for (let i = 0; i < items.length; i++) {
17+
let item = items[i];
18+
item.index = this.items.length + offset + 1;
19+
this.items.push(item);
20+
}
21+
}
22+
23+
}

0 commit comments

Comments
 (0)