mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 01:54:15 +00:00
CSV export for Filtered Items content report (#4071)
* CSV export for Filtered Items content report * Fixed lint errors * Fixed lint errors * Fixed lint errors * Make variables for CSV export null-proof * Attempt to fix unit tests * Fixed styling errors * Fixed script references in unit tests * Fixed typo in script name * Fixed test parameterization * Parameterization attempt * Parameterization test * Parameterization rollback * Fixed predicate encoding bug * Parameterization test * Fixed styling error * Fixed query predicate parameter * Fixed collection parameterization * Centralized string representation of a predicate * Fixed parameterization * Fixed second export test * Replaced null payload by an empty non-null one * Requested changes * Fixed remaining bugs * Updated Angular control flow syntax * Improved collection parameter handling * Fixed styling error * Updated config.yml to match the central dspace-angular repo * Removed repeated content * Cleaned up a now useless import * Fixed collections loading and added warning message about CSV export * Fixed styling error * Forgot to clean up old code --------- Co-authored-by: Jean-François Morin <jean-francois.morin@bibl.ulaval.ca>
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
@if (shouldShowButton$ | async) {
|
||||
<button class="export-button btn btn-dark btn-sm"
|
||||
[ngbTooltip]="tooltipMsg | translate"
|
||||
(click)="export()"
|
||||
[title]="tooltipMsg | translate" [attr.aria-label]="tooltipMsg | translate">
|
||||
<i class="fas fa-file-export fa-fw"></i>
|
||||
</button>
|
||||
}
|
@@ -0,0 +1,4 @@
|
||||
.export-button {
|
||||
background: var(--ds-admin-sidebar-bg);
|
||||
border-color: var(--ds-admin-sidebar-bg);
|
||||
}
|
@@ -0,0 +1,194 @@
|
||||
import {
|
||||
ComponentFixture,
|
||||
TestBed,
|
||||
waitForAsync,
|
||||
} from '@angular/core/testing';
|
||||
import {
|
||||
FormControl,
|
||||
FormGroup,
|
||||
} from '@angular/forms';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { Router } from '@angular/router';
|
||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
|
||||
import { AuthorizationDataService } from '../../../../core/data/feature-authorization/authorization-data.service';
|
||||
import { ScriptDataService } from '../../../../core/data/processes/script-data.service';
|
||||
import { getProcessDetailRoute } from '../../../../process-page/process-page-routing.paths';
|
||||
import { Process } from '../../../../process-page/processes/process.model';
|
||||
import { Script } from '../../../../process-page/scripts/script.model';
|
||||
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
|
||||
import {
|
||||
createFailedRemoteDataObject$,
|
||||
createSuccessfulRemoteDataObject$,
|
||||
} from '../../../../shared/remote-data.utils';
|
||||
import { NotificationsServiceStub } from '../../../../shared/testing/notifications-service.stub';
|
||||
import { FiltersComponent } from '../../filters-section/filters-section.component';
|
||||
import { OptionVO } from '../option-vo.model';
|
||||
import { QueryPredicate } from '../query-predicate.model';
|
||||
import { FilteredItemsExportCsvComponent } from './filtered-items-export-csv.component';
|
||||
|
||||
describe('FilteredItemsExportCsvComponent', () => {
|
||||
let component: FilteredItemsExportCsvComponent;
|
||||
let fixture: ComponentFixture<FilteredItemsExportCsvComponent>;
|
||||
|
||||
let scriptDataService: ScriptDataService;
|
||||
let authorizationDataService: AuthorizationDataService;
|
||||
let notificationsService;
|
||||
let router;
|
||||
|
||||
const script = Object.assign(new Script(), { id: 'metadata-export-filtered-items-report', name: 'metadata-export-filtered-items-report' });
|
||||
const process = Object.assign(new Process(), { processId: 5, scriptName: 'metadata-export-filtered-items-report' });
|
||||
|
||||
const params = new FormGroup({
|
||||
collections: new FormControl([OptionVO.collection('1', 'coll1')]),
|
||||
queryPredicates: new FormControl([QueryPredicate.of('name', 'equals', 'coll1')]),
|
||||
filters: new FormControl([FiltersComponent.getFilter('is_item')]),
|
||||
});
|
||||
|
||||
const emptyParams = new FormGroup({
|
||||
collections: new FormControl([]),
|
||||
queryPredicates: new FormControl([]),
|
||||
filters: new FormControl([]),
|
||||
});
|
||||
|
||||
function initBeforeEachAsync() {
|
||||
scriptDataService = jasmine.createSpyObj('scriptDataService', {
|
||||
findById: createSuccessfulRemoteDataObject$(script),
|
||||
invoke: createSuccessfulRemoteDataObject$(process),
|
||||
});
|
||||
authorizationDataService = jasmine.createSpyObj('authorizationService', {
|
||||
isAuthorized: observableOf(true),
|
||||
});
|
||||
|
||||
notificationsService = new NotificationsServiceStub();
|
||||
|
||||
router = jasmine.createSpyObj('authorizationService', ['navigateByUrl']);
|
||||
TestBed.configureTestingModule({
|
||||
imports: [TranslateModule.forRoot(), NgbModule, FilteredItemsExportCsvComponent],
|
||||
providers: [
|
||||
{ provide: ScriptDataService, useValue: scriptDataService },
|
||||
{ provide: AuthorizationDataService, useValue: authorizationDataService },
|
||||
{ provide: NotificationsService, useValue: notificationsService },
|
||||
{ provide: Router, useValue: router },
|
||||
],
|
||||
}).compileComponents();
|
||||
}
|
||||
|
||||
function initBeforeEach() {
|
||||
fixture = TestBed.createComponent(FilteredItemsExportCsvComponent);
|
||||
component = fixture.componentInstance;
|
||||
component.reportParams = params;
|
||||
fixture.detectChanges();
|
||||
}
|
||||
|
||||
describe('init', () => {
|
||||
describe('comp', () => {
|
||||
beforeEach(waitForAsync(() => {
|
||||
initBeforeEachAsync();
|
||||
}));
|
||||
beforeEach(() => {
|
||||
initBeforeEach();
|
||||
});
|
||||
it('should init the comp', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
describe('when the user is an admin and the metadata-export-filtered-items-report script is present ', () => {
|
||||
beforeEach(waitForAsync(() => {
|
||||
initBeforeEachAsync();
|
||||
}));
|
||||
beforeEach(() => {
|
||||
initBeforeEach();
|
||||
});
|
||||
it('should add the button', () => {
|
||||
const debugElement = fixture.debugElement.query(By.css('button.export-button'));
|
||||
expect(debugElement).toBeDefined();
|
||||
});
|
||||
});
|
||||
describe('when the user is not an admin', () => {
|
||||
beforeEach(waitForAsync(() => {
|
||||
initBeforeEachAsync();
|
||||
(authorizationDataService.isAuthorized as jasmine.Spy).and.returnValue(observableOf(false));
|
||||
}));
|
||||
beforeEach(() => {
|
||||
initBeforeEach();
|
||||
});
|
||||
it('should not add the button', () => {
|
||||
const debugElement = fixture.debugElement.query(By.css('button.export-button'));
|
||||
expect(debugElement).toBeNull();
|
||||
});
|
||||
});
|
||||
describe('when the metadata-export-filtered-items-report script is not present', () => {
|
||||
beforeEach(waitForAsync(() => {
|
||||
initBeforeEachAsync();
|
||||
(scriptDataService.findById as jasmine.Spy).and.returnValue(createFailedRemoteDataObject$('Not found', 404));
|
||||
}));
|
||||
beforeEach(() => {
|
||||
initBeforeEach();
|
||||
});
|
||||
it('should should not add the button', () => {
|
||||
const debugElement = fixture.debugElement.query(By.css('button.export-button'));
|
||||
expect(debugElement).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('export', () => {
|
||||
beforeEach(waitForAsync(() => {
|
||||
initBeforeEachAsync();
|
||||
}));
|
||||
beforeEach(() => {
|
||||
initBeforeEach();
|
||||
});
|
||||
it('should call the invoke script method with the correct parameters', () => {
|
||||
// Parameterized export
|
||||
component.export();
|
||||
expect(scriptDataService.invoke).toHaveBeenCalledWith('metadata-export-filtered-items-report',
|
||||
[
|
||||
{ name: '-c', value: params.value.collections[0].id },
|
||||
{ name: '-qp', value: QueryPredicate.toString(params.value.queryPredicates[0]) },
|
||||
{ name: '-f', value: FiltersComponent.toQueryString(params.value.filters) },
|
||||
], []);
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
// Non-parameterized export
|
||||
component.reportParams = emptyParams;
|
||||
fixture.detectChanges();
|
||||
component.export();
|
||||
expect(scriptDataService.invoke).toHaveBeenCalledWith('metadata-export-filtered-items-report', [], []);
|
||||
|
||||
});
|
||||
it('should show a success message when the script was invoked successfully and redirect to the corresponding process page', () => {
|
||||
component.export();
|
||||
|
||||
expect(notificationsService.success).toHaveBeenCalled();
|
||||
expect(router.navigateByUrl).toHaveBeenCalledWith(getProcessDetailRoute(process.processId));
|
||||
});
|
||||
it('should show an error message when the script was not invoked successfully and stay on the current page', () => {
|
||||
(scriptDataService.invoke as jasmine.Spy).and.returnValue(createFailedRemoteDataObject$('Error', 500));
|
||||
|
||||
component.export();
|
||||
|
||||
expect(notificationsService.error).toHaveBeenCalled();
|
||||
expect(router.navigateByUrl).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
describe('clicking the button', () => {
|
||||
beforeEach(waitForAsync(() => {
|
||||
initBeforeEachAsync();
|
||||
}));
|
||||
beforeEach(() => {
|
||||
initBeforeEach();
|
||||
});
|
||||
it('should trigger the export function', () => {
|
||||
spyOn(component, 'export');
|
||||
|
||||
const debugElement = fixture.debugElement.query(By.css('button.export-button'));
|
||||
debugElement.triggerEventHandler('click', null);
|
||||
|
||||
expect(component.export).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
@@ -0,0 +1,123 @@
|
||||
import { AsyncPipe } from '@angular/common';
|
||||
import {
|
||||
Component,
|
||||
Input,
|
||||
OnInit,
|
||||
} from '@angular/core';
|
||||
import { FormGroup } from '@angular/forms';
|
||||
import { Router } from '@angular/router';
|
||||
import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
import {
|
||||
TranslateModule,
|
||||
TranslateService,
|
||||
} from '@ngx-translate/core';
|
||||
import {
|
||||
combineLatest as observableCombineLatest,
|
||||
Observable,
|
||||
} from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
import { AuthorizationDataService } from '../../../../core/data/feature-authorization/authorization-data.service';
|
||||
import { FeatureID } from '../../../../core/data/feature-authorization/feature-id';
|
||||
import { ScriptDataService } from '../../../../core/data/processes/script-data.service';
|
||||
import { RemoteData } from '../../../../core/data/remote-data';
|
||||
import { getFirstCompletedRemoteData } from '../../../../core/shared/operators';
|
||||
import { getProcessDetailRoute } from '../../../../process-page/process-page-routing.paths';
|
||||
import { Process } from '../../../../process-page/processes/process.model';
|
||||
import { hasValue } from '../../../../shared/empty.util';
|
||||
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
|
||||
import { FiltersComponent } from '../../filters-section/filters-section.component';
|
||||
import { OptionVO } from '../option-vo.model';
|
||||
import { QueryPredicate } from '../query-predicate.model';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-filtered-items-export-csv',
|
||||
styleUrls: ['./filtered-items-export-csv.component.scss'],
|
||||
templateUrl: './filtered-items-export-csv.component.html',
|
||||
standalone: true,
|
||||
imports: [NgbTooltipModule, AsyncPipe, TranslateModule],
|
||||
})
|
||||
/**
|
||||
* Display a button to export the MetadataQuery (aka Filtered Items) Report results as csv
|
||||
*/
|
||||
export class FilteredItemsExportCsvComponent implements OnInit {
|
||||
|
||||
/**
|
||||
* The current configuration of the search
|
||||
*/
|
||||
@Input() reportParams: FormGroup;
|
||||
|
||||
/**
|
||||
* Observable used to determine whether the button should be shown
|
||||
*/
|
||||
shouldShowButton$: Observable<boolean>;
|
||||
|
||||
/**
|
||||
* The message key used for the tooltip of the button
|
||||
*/
|
||||
tooltipMsg = 'metadata-export-filtered-items.tooltip';
|
||||
|
||||
constructor(private scriptDataService: ScriptDataService,
|
||||
private authorizationDataService: AuthorizationDataService,
|
||||
private notificationsService: NotificationsService,
|
||||
private translateService: TranslateService,
|
||||
private router: Router,
|
||||
) {
|
||||
}
|
||||
|
||||
static csvExportEnabled(scriptDataService: ScriptDataService, authorizationDataService: AuthorizationDataService): Observable<boolean> {
|
||||
const scriptExists$ = scriptDataService.findById('metadata-export-filtered-items-report').pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
map((rd) => rd.isSuccess && hasValue(rd.payload)),
|
||||
);
|
||||
|
||||
const isAuthorized$ = authorizationDataService.isAuthorized(FeatureID.AdministratorOf);
|
||||
|
||||
return observableCombineLatest([scriptExists$, isAuthorized$]).pipe(
|
||||
map(([scriptExists, isAuthorized]: [boolean, boolean]) => scriptExists && isAuthorized),
|
||||
);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.shouldShowButton$ = FilteredItemsExportCsvComponent.csvExportEnabled(this.scriptDataService, this.authorizationDataService);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the export of the items based on the selected parameters
|
||||
*/
|
||||
export() {
|
||||
const parameters = [];
|
||||
const colls = this.reportParams.value.collections || [];
|
||||
for (let i = 0; i < colls.length; i++) {
|
||||
if (colls[i]) {
|
||||
parameters.push({ name: '-c', value: OptionVO.toString(colls[i]) });
|
||||
}
|
||||
}
|
||||
|
||||
const preds = this.reportParams.value.queryPredicates || [];
|
||||
for (let i = 0; i < preds.length; i++) {
|
||||
const field = preds[i].field;
|
||||
const op = preds[i].operator;
|
||||
if (field && op) {
|
||||
parameters.push({ name: '-qp', value: QueryPredicate.toString(preds[i]) });
|
||||
}
|
||||
}
|
||||
|
||||
const filters = FiltersComponent.toQueryString(this.reportParams.value.filters) || [];
|
||||
if (filters.length > 0) {
|
||||
parameters.push({ name: '-f', value: filters });
|
||||
}
|
||||
|
||||
this.scriptDataService.invoke('metadata-export-filtered-items-report', parameters, []).pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
).subscribe((rd: RemoteData<Process>) => {
|
||||
if (rd.hasSucceeded) {
|
||||
this.notificationsService.success(this.translateService.get('metadata-export-filtered-items.submit.success'));
|
||||
this.router.navigateByUrl(getProcessDetailRoute(rd.payload.processId));
|
||||
} else {
|
||||
this.notificationsService.error(this.translateService.get('metadata-export-filtered-items.submit.error'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@@ -11,11 +11,16 @@
|
||||
{{'admin.reports.items.section.collectionSelector' | translate}}
|
||||
</ng-template>
|
||||
<ng-template ngbPanelContent>
|
||||
<select id="collSel" name="collSel" class="form-control" multiple="multiple" size="10" formControlName="collections">
|
||||
@for (item of collections; track item) {
|
||||
<option [value]="item.id" [disabled]="item.disabled">{{item.name$ | async}}</option>
|
||||
}
|
||||
</select>
|
||||
@if (loadingCollections$ | async) {
|
||||
<ds-loading></ds-loading>
|
||||
}
|
||||
@if ((loadingCollections$ | async) !== true) {
|
||||
<select id="collSel" name="collSel" class="form-control" multiple="multiple" size="10" formControlName="collections">
|
||||
@for (item of collections; track item) {
|
||||
<option [value]="item.id" [disabled]="item.disabled">{{item.name$ | async}}</option>
|
||||
}
|
||||
</select>
|
||||
}
|
||||
<div class="row">
|
||||
<span class="col-3"></span>
|
||||
<button class="btn btn-primary mt-1 col-6" (click)="submit()">{{'admin.reports.items.run' | translate}}</button>
|
||||
@@ -132,6 +137,10 @@
|
||||
</select>
|
||||
</div>
|
||||
<div class="row">
|
||||
@if (csvExportEnabled$ | async) {
|
||||
<span class="col-3"></span>
|
||||
<div class="warning">{{ 'metadata-export-filtered-items.columns.warning' | translate }}</div>
|
||||
}
|
||||
<span class="col-3"></span>
|
||||
<button class="btn btn-primary mt-1 col-6" (click)="submit()">{{'admin.reports.items.run' | translate}}</button>
|
||||
</div>
|
||||
@@ -186,9 +195,9 @@
|
||||
<div>
|
||||
<button id="prev" class="btn btn-light" (click)="prevPage()" [dsBtnDisabled]="!canNavigatePrevious()">{{'admin.reports.commons.previous-page' | translate}}</button>
|
||||
<button id="next" class="btn btn-light" (click)="nextPage()" [dsBtnDisabled]="!canNavigateNext()">{{'admin.reports.commons.next-page' | translate}}</button>
|
||||
<!--
|
||||
<button id="export">{{'admin.reports.commons.export' | translate}}</button>
|
||||
-->
|
||||
<div style="float: right; margin-right: 60px;">
|
||||
<ds-filtered-items-export-csv [reportParams]="queryForm"></ds-filtered-items-export-csv>
|
||||
</div>
|
||||
</div>
|
||||
<table id="itemtable" class="sortable"></table>
|
||||
</ng-template>
|
||||
|
@@ -1,3 +1,10 @@
|
||||
.num {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.warning {
|
||||
color: red;
|
||||
font-style: italic;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
@@ -20,13 +20,16 @@ import {
|
||||
TranslateService,
|
||||
} from '@ngx-translate/core';
|
||||
import {
|
||||
BehaviorSubject,
|
||||
map,
|
||||
Observable,
|
||||
} from 'rxjs';
|
||||
import { CollectionDataService } from 'src/app/core/data/collection-data.service';
|
||||
import { CommunityDataService } from 'src/app/core/data/community-data.service';
|
||||
import { AuthorizationDataService } from 'src/app/core/data/feature-authorization/authorization-data.service';
|
||||
import { MetadataFieldDataService } from 'src/app/core/data/metadata-field-data.service';
|
||||
import { MetadataSchemaDataService } from 'src/app/core/data/metadata-schema-data.service';
|
||||
import { ScriptDataService } from 'src/app/core/data/processes/script-data.service';
|
||||
import { RestRequestMethod } from 'src/app/core/data/rest-request-method';
|
||||
import { DspaceRestService } from 'src/app/core/dspace-rest/dspace-rest.service';
|
||||
import { RawRestResponse } from 'src/app/core/dspace-rest/raw-rest-response.model';
|
||||
@@ -36,10 +39,12 @@ import { Collection } from 'src/app/core/shared/collection.model';
|
||||
import { Community } from 'src/app/core/shared/community.model';
|
||||
import { getFirstSucceededRemoteListPayload } from 'src/app/core/shared/operators';
|
||||
import { isEmpty } from 'src/app/shared/empty.util';
|
||||
import { ThemedLoadingComponent } from 'src/app/shared/loading/themed-loading.component';
|
||||
import { environment } from 'src/environments/environment';
|
||||
|
||||
import { BtnDisabledDirective } from '../../../shared/btn-disabled.directive';
|
||||
import { FiltersComponent } from '../filters-section/filters-section.component';
|
||||
import { FilteredItemsExportCsvComponent } from './filtered-items-export-csv/filtered-items-export-csv.component';
|
||||
import {
|
||||
FilteredItem,
|
||||
FilteredItems,
|
||||
@@ -62,12 +67,19 @@ import { QueryPredicate } from './query-predicate.model';
|
||||
AsyncPipe,
|
||||
FiltersComponent,
|
||||
BtnDisabledDirective,
|
||||
FilteredItemsExportCsvComponent,
|
||||
ThemedLoadingComponent,
|
||||
],
|
||||
standalone: true,
|
||||
})
|
||||
export class FilteredItemsComponent implements OnInit {
|
||||
|
||||
collections: OptionVO[];
|
||||
/**
|
||||
* A Boolean representing if loading the list of collections is pending
|
||||
*/
|
||||
loadingCollections$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
||||
|
||||
presetQueries: PresetQuery[];
|
||||
metadataFields: OptionVO[];
|
||||
metadataFieldsWithAny: OptionVO[];
|
||||
@@ -79,6 +91,10 @@ export class FilteredItemsComponent implements OnInit {
|
||||
results: FilteredItems = new FilteredItems();
|
||||
results$: Observable<FilteredItem[]>;
|
||||
@ViewChild('acc') accordionComponent: NgbAccordion;
|
||||
/**
|
||||
* Observable used to determine whether CSV export is enabled
|
||||
*/
|
||||
csvExportEnabled$: Observable<boolean>;
|
||||
|
||||
constructor(
|
||||
private communityService: CommunityDataService,
|
||||
@@ -86,6 +102,8 @@ export class FilteredItemsComponent implements OnInit {
|
||||
private metadataSchemaService: MetadataSchemaDataService,
|
||||
private metadataFieldService: MetadataFieldDataService,
|
||||
private translateService: TranslateService,
|
||||
private scriptDataService: ScriptDataService,
|
||||
private authorizationDataService: AuthorizationDataService,
|
||||
private formBuilder: FormBuilder,
|
||||
private restService: DspaceRestService) {}
|
||||
|
||||
@@ -100,6 +118,8 @@ export class FilteredItemsComponent implements OnInit {
|
||||
new QueryPredicate().toFormGroup(this.formBuilder),
|
||||
];
|
||||
|
||||
this.csvExportEnabled$ = FilteredItemsExportCsvComponent.csvExportEnabled(this.scriptDataService, this.authorizationDataService);
|
||||
|
||||
this.queryForm = this.formBuilder.group({
|
||||
collections: this.formBuilder.control([''], []),
|
||||
presetQuery: this.formBuilder.control('new', []),
|
||||
@@ -111,6 +131,7 @@ export class FilteredItemsComponent implements OnInit {
|
||||
}
|
||||
|
||||
loadCollections(): void {
|
||||
this.loadingCollections$.next(true);
|
||||
this.collections = [];
|
||||
const wholeRepo$ = this.translateService.stream('admin.reports.items.wholeRepo');
|
||||
this.collections.push(OptionVO.collectionLoc('', wholeRepo$));
|
||||
@@ -132,6 +153,7 @@ export class FilteredItemsComponent implements OnInit {
|
||||
const collVO = OptionVO.collection(collection.uuid, '–' + collection.name);
|
||||
this.collections.push(collVO);
|
||||
});
|
||||
this.loadingCollections$.next(false);
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -167,10 +189,10 @@ export class FilteredItemsComponent implements OnInit {
|
||||
QueryPredicate.of('dc.description.provenance', QueryPredicate.DOES_NOT_MATCH, '^.*No\. of bitstreams(.|\r|\n|\r\n)*\.(PDF|pdf|DOC|doc|PPT|ppt|DOCX|docx|PPTX|pptx).*$'),
|
||||
]),
|
||||
PresetQuery.of('q9', 'admin.reports.items.preset.hasEmptyMetadata', [
|
||||
QueryPredicate.of('*', QueryPredicate.MATCHES, '^\s*$'),
|
||||
QueryPredicate.of('*', QueryPredicate.MATCHES, '^\\s*$'),
|
||||
]),
|
||||
PresetQuery.of('q10', 'admin.reports.items.preset.hasUnbreakingDataInDescription', [
|
||||
QueryPredicate.of('dc.description.*', QueryPredicate.MATCHES, '^.*[^\s]{50,}.*$'),
|
||||
QueryPredicate.of('dc.description.*', QueryPredicate.MATCHES, '^.*(\\S){50,}.*$'),
|
||||
]),
|
||||
PresetQuery.of('q12', 'admin.reports.items.preset.hasXmlEntityInMetadata', [
|
||||
QueryPredicate.of('*', QueryPredicate.MATCHES, '^.*&#.*$'),
|
||||
@@ -344,13 +366,8 @@ export class FilteredItemsComponent implements OnInit {
|
||||
|
||||
const preds = this.queryForm.value.queryPredicates;
|
||||
for (let i = 0; i < preds.length; i++) {
|
||||
const field = preds[i].field;
|
||||
const op = preds[i].operator;
|
||||
const value = preds[i].value;
|
||||
params += `&queryPredicates=${field}:${op}`;
|
||||
if (value) {
|
||||
params += `:${value}`;
|
||||
}
|
||||
const pred = encodeURIComponent(QueryPredicate.toString(preds[i]));
|
||||
params += `&queryPredicates=${pred}`;
|
||||
}
|
||||
|
||||
const filters = FiltersComponent.toQueryString(this.queryForm.value.filters);
|
||||
|
@@ -46,6 +46,16 @@ export class OptionVO {
|
||||
subscriber.next(value);
|
||||
subscriber.complete();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
static toString(obj: any): string {
|
||||
if (obj) {
|
||||
if (obj instanceof OptionVO && obj.id) {
|
||||
return obj.id;
|
||||
}
|
||||
return obj as string;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -29,6 +29,13 @@ export class QueryPredicate {
|
||||
return pred;
|
||||
}
|
||||
|
||||
static toString(pred: QueryPredicate): string {
|
||||
if (pred.value) {
|
||||
return `${pred.field}:${pred.operator}:${pred.value}`;
|
||||
}
|
||||
return `${pred.field}:${pred.operator}`;
|
||||
}
|
||||
|
||||
toFormGroup(formBuilder: FormBuilder): FormGroup {
|
||||
return formBuilder.group({
|
||||
field: new FormControl(this.field),
|
||||
|
@@ -6861,4 +6861,12 @@
|
||||
"search.filters.access_status.metadata.only": "Metadata only",
|
||||
|
||||
"search.filters.access_status.unknown": "Unknown",
|
||||
|
||||
"metadata-export-filtered-items.tooltip": "Export report output as CSV",
|
||||
|
||||
"metadata-export-filtered-items.submit.success": "CSV export succeeded.",
|
||||
|
||||
"metadata-export-filtered-items.submit.error": "CSV export failed.",
|
||||
|
||||
"metadata-export-filtered-items.columns.warning": "CSV export automatically includes all relevant fields, so selections in this list are not taken into account.",
|
||||
}
|
||||
|
@@ -8584,4 +8584,15 @@
|
||||
//"search.filters.access_status.unknown": "Unknown",
|
||||
"search.filters.access_status.unknown": "Inconnu",
|
||||
|
||||
//"metadata-export-filtered-items.tooltip": "Export report output as CSV",
|
||||
"metadata-export-filtered-items.tooltip": "Exporter le rapport en CSV",
|
||||
|
||||
//"metadata-export-filtered-items.submit.success": "CSV export succeeded.",
|
||||
"metadata-export-filtered-items.submit.success": "Exportation CSV complétée.",
|
||||
|
||||
//"metadata-export-filtered-items.submit.error": "CSV export failed.",
|
||||
"metadata-export-filtered-items.submit.error": "L'exportation CSV n'a pas fonctionné.",
|
||||
|
||||
//"metadata-export-filtered-items.columns.warning": "CSV export automatically includes all relevant fields, so selections in this list are not taken into account.",
|
||||
"metadata-export-filtered-items.columns.warning": "L'exportation CSV inclut automatiquement tous les champs pertinents, sans égard au contenu sélectionné de cette liste.",
|
||||
}
|
||||
|
Reference in New Issue
Block a user