mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
Merge pull request #1328 from atmire/w2p-83707_Control-collection-harvest-from-the-UI
Control collection harvest from the UI
This commit is contained in:
@@ -0,0 +1,54 @@
|
|||||||
|
<div *ngVar="(contentSource$ |async) as contentSource">
|
||||||
|
<div class="container-fluid" *ngIf="shouldShow">
|
||||||
|
<h4>{{ 'collection.source.controls.head' | translate }}</h4>
|
||||||
|
<div>
|
||||||
|
<span class="font-weight-bold">{{'collection.source.controls.harvest.status' | translate}}</span>
|
||||||
|
<span>{{contentSource?.harvestStatus}}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span class="font-weight-bold">{{'collection.source.controls.harvest.start' | translate}}</span>
|
||||||
|
<span>{{contentSource?.harvestStartTime ? contentSource?.harvestStartTime : 'collection.source.controls.harvest.no-information'|translate }}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span class="font-weight-bold">{{'collection.source.controls.harvest.last' | translate}}</span>
|
||||||
|
<span>{{contentSource?.message ? contentSource?.message : 'collection.source.controls.harvest.no-information'|translate }}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span class="font-weight-bold">{{'collection.source.controls.harvest.message' | translate}}</span>
|
||||||
|
<span>{{contentSource?.lastHarvested ? contentSource?.lastHarvested : 'collection.source.controls.harvest.no-information'|translate }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button *ngIf="!(testConfigRunning$ |async)" class="btn btn-secondary"
|
||||||
|
[disabled]="!(isEnabled)"
|
||||||
|
(click)="testConfiguration(contentSource)">
|
||||||
|
<span>{{'collection.source.controls.test.submit' | translate}}</span>
|
||||||
|
</button>
|
||||||
|
<button *ngIf="(testConfigRunning$ |async)" class="btn btn-secondary"
|
||||||
|
[disabled]="true">
|
||||||
|
<span class="spinner-border spinner-border-sm spinner-button" role="status" aria-hidden="true"></span>
|
||||||
|
<span>{{'collection.source.controls.test.running' | translate}}</span>
|
||||||
|
</button>
|
||||||
|
<button *ngIf="!(importRunning$ |async)" class="btn btn-primary"
|
||||||
|
[disabled]="!(isEnabled)"
|
||||||
|
(click)="importNow()">
|
||||||
|
<span class="d-none d-sm-inline">{{'collection.source.controls.import.submit' | translate}}</span>
|
||||||
|
</button>
|
||||||
|
<button *ngIf="(importRunning$ |async)" class="btn btn-primary"
|
||||||
|
[disabled]="true">
|
||||||
|
<span class="spinner-border spinner-border-sm spinner-button" role="status" aria-hidden="true"></span>
|
||||||
|
<span class="d-none d-sm-inline">{{'collection.source.controls.import.running' | translate}}</span>
|
||||||
|
</button>
|
||||||
|
<button *ngIf="!(reImportRunning$ |async)" class="btn btn-primary"
|
||||||
|
[disabled]="!(isEnabled)"
|
||||||
|
(click)="resetAndReimport()">
|
||||||
|
<span class="d-none d-sm-inline"> {{'collection.source.controls.reset.submit' | translate}}</span>
|
||||||
|
</button>
|
||||||
|
<button *ngIf="(reImportRunning$ |async)" class="btn btn-primary"
|
||||||
|
[disabled]="true">
|
||||||
|
<span class="spinner-border spinner-border-sm spinner-button" role="status" aria-hidden="true"></span>
|
||||||
|
<span class="d-none d-sm-inline"> {{'collection.source.controls.reset.running' | translate}}</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
@@ -0,0 +1,3 @@
|
|||||||
|
.spinner-button {
|
||||||
|
margin-bottom: calc((var(--bs-line-height-base) * 1rem - var(--bs-font-size-base)) / 2);
|
||||||
|
}
|
@@ -0,0 +1,232 @@
|
|||||||
|
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
|
import { ContentSource } from '../../../../core/shared/content-source.model';
|
||||||
|
import { Collection } from '../../../../core/shared/collection.model';
|
||||||
|
import { createSuccessfulRemoteDataObject$ } from '../../../../shared/remote-data.utils';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
|
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
|
||||||
|
import { CollectionDataService } from '../../../../core/data/collection-data.service';
|
||||||
|
import { RequestService } from '../../../../core/data/request.service';
|
||||||
|
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { ProcessDataService } from '../../../../core/data/processes/process-data.service';
|
||||||
|
import { ScriptDataService } from '../../../../core/data/processes/script-data.service';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { BitstreamDataService } from '../../../../core/data/bitstream-data.service';
|
||||||
|
import { NotificationsServiceStub } from '../../../../shared/testing/notifications-service.stub';
|
||||||
|
import { Process } from '../../../../process-page/processes/process.model';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import { CollectionSourceControlsComponent } from './collection-source-controls.component';
|
||||||
|
import { Bitstream } from '../../../../core/shared/bitstream.model';
|
||||||
|
import { getTestScheduler } from 'jasmine-marbles';
|
||||||
|
import { TestScheduler } from 'rxjs/testing';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
import { VarDirective } from '../../../../shared/utils/var.directive';
|
||||||
|
import { ContentSourceSetSerializer } from '../../../../core/shared/content-source-set-serializer';
|
||||||
|
|
||||||
|
describe('CollectionSourceControlsComponent', () => {
|
||||||
|
let comp: CollectionSourceControlsComponent;
|
||||||
|
let fixture: ComponentFixture<CollectionSourceControlsComponent>;
|
||||||
|
|
||||||
|
const uuid = '29481ed7-ae6b-409a-8c51-34dd347a0ce4';
|
||||||
|
let contentSource: ContentSource;
|
||||||
|
let collection: Collection;
|
||||||
|
let process: Process;
|
||||||
|
let bitstream: Bitstream;
|
||||||
|
|
||||||
|
let scriptDataService: ScriptDataService;
|
||||||
|
let processDataService: ProcessDataService;
|
||||||
|
let requestService: RequestService;
|
||||||
|
let notificationsService;
|
||||||
|
let collectionService: CollectionDataService;
|
||||||
|
let httpClient: HttpClient;
|
||||||
|
let bitstreamService: BitstreamDataService;
|
||||||
|
let scheduler: TestScheduler;
|
||||||
|
|
||||||
|
|
||||||
|
beforeEach(waitForAsync(() => {
|
||||||
|
scheduler = getTestScheduler();
|
||||||
|
contentSource = Object.assign(new ContentSource(), {
|
||||||
|
uuid: uuid,
|
||||||
|
metadataConfigs: [
|
||||||
|
{
|
||||||
|
id: 'dc',
|
||||||
|
label: 'Simple Dublin Core',
|
||||||
|
nameSpace: 'http://www.openarchives.org/OAI/2.0/oai_dc/'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'qdc',
|
||||||
|
label: 'Qualified Dublin Core',
|
||||||
|
nameSpace: 'http://purl.org/dc/terms/'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'dim',
|
||||||
|
label: 'DSpace Intermediate Metadata',
|
||||||
|
nameSpace: 'http://www.dspace.org/xmlns/dspace/dim'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
oaiSource: 'oai-harvest-source',
|
||||||
|
oaiSetId: 'oai-set-id',
|
||||||
|
_links: {self: {href: 'contentsource-selflink'}}
|
||||||
|
});
|
||||||
|
process = Object.assign(new Process(), {
|
||||||
|
processId: 'process-id', processStatus: 'COMPLETED',
|
||||||
|
_links: {output: {href: 'output-href'}}
|
||||||
|
});
|
||||||
|
|
||||||
|
bitstream = Object.assign(new Bitstream(), {_links: {content: {href: 'content-href'}}});
|
||||||
|
|
||||||
|
collection = Object.assign(new Collection(), {
|
||||||
|
uuid: 'fake-collection-id',
|
||||||
|
_links: {self: {href: 'collection-selflink'}}
|
||||||
|
});
|
||||||
|
notificationsService = new NotificationsServiceStub();
|
||||||
|
collectionService = jasmine.createSpyObj('collectionService', {
|
||||||
|
getContentSource: createSuccessfulRemoteDataObject$(contentSource),
|
||||||
|
findByHref: createSuccessfulRemoteDataObject$(collection)
|
||||||
|
});
|
||||||
|
scriptDataService = jasmine.createSpyObj('scriptDataService', {
|
||||||
|
invoke: createSuccessfulRemoteDataObject$(process),
|
||||||
|
});
|
||||||
|
processDataService = jasmine.createSpyObj('processDataService', {
|
||||||
|
findById: createSuccessfulRemoteDataObject$(process),
|
||||||
|
});
|
||||||
|
bitstreamService = jasmine.createSpyObj('bitstreamService', {
|
||||||
|
findByHref: createSuccessfulRemoteDataObject$(bitstream),
|
||||||
|
});
|
||||||
|
httpClient = jasmine.createSpyObj('httpClient', {
|
||||||
|
get: observableOf('Script text'),
|
||||||
|
});
|
||||||
|
requestService = jasmine.createSpyObj('requestService', ['removeByHrefSubstring', 'setStaleByHrefSubstring']);
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [TranslateModule.forRoot(), RouterTestingModule],
|
||||||
|
declarations: [CollectionSourceControlsComponent, VarDirective],
|
||||||
|
providers: [
|
||||||
|
{provide: ScriptDataService, useValue: scriptDataService},
|
||||||
|
{provide: ProcessDataService, useValue: processDataService},
|
||||||
|
{provide: RequestService, useValue: requestService},
|
||||||
|
{provide: NotificationsService, useValue: notificationsService},
|
||||||
|
{provide: CollectionDataService, useValue: collectionService},
|
||||||
|
{provide: HttpClient, useValue: httpClient},
|
||||||
|
{provide: BitstreamDataService, useValue: bitstreamService}
|
||||||
|
],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(CollectionSourceControlsComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
comp.isEnabled = true;
|
||||||
|
comp.collection = collection;
|
||||||
|
comp.shouldShow = true;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
describe('init', () => {
|
||||||
|
it('should', () => {
|
||||||
|
expect(comp).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('testConfiguration', () => {
|
||||||
|
it('should invoke a script and ping the resulting process until completed and show the resulting info', () => {
|
||||||
|
comp.testConfiguration(contentSource);
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect(scriptDataService.invoke).toHaveBeenCalledWith('harvest', [
|
||||||
|
{name: '-g', value: null},
|
||||||
|
{name: '-a', value: contentSource.oaiSource},
|
||||||
|
{name: '-i', value: new ContentSourceSetSerializer().Serialize(contentSource.oaiSetId)},
|
||||||
|
], []);
|
||||||
|
|
||||||
|
expect(processDataService.findById).toHaveBeenCalledWith(process.processId, false);
|
||||||
|
expect(bitstreamService.findByHref).toHaveBeenCalledWith(process._links.output.href);
|
||||||
|
expect(notificationsService.info).toHaveBeenCalledWith(jasmine.anything() as any, 'Script text');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('importNow', () => {
|
||||||
|
it('should invoke a script that will start the harvest', () => {
|
||||||
|
comp.importNow();
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect(scriptDataService.invoke).toHaveBeenCalledWith('harvest', [
|
||||||
|
{name: '-r', value: null},
|
||||||
|
{name: '-c', value: collection.uuid},
|
||||||
|
], []);
|
||||||
|
expect(processDataService.findById).toHaveBeenCalledWith(process.processId, false);
|
||||||
|
expect(notificationsService.success).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('resetAndReimport', () => {
|
||||||
|
it('should invoke a script that will start the harvest', () => {
|
||||||
|
comp.resetAndReimport();
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect(scriptDataService.invoke).toHaveBeenCalledWith('harvest', [
|
||||||
|
{name: '-o', value: null},
|
||||||
|
{name: '-c', value: collection.uuid},
|
||||||
|
], []);
|
||||||
|
expect(processDataService.findById).toHaveBeenCalledWith(process.processId, false);
|
||||||
|
expect(notificationsService.success).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('the controls', () => {
|
||||||
|
it('should be shown when shouldShow is true', () => {
|
||||||
|
comp.shouldShow = true;
|
||||||
|
fixture.detectChanges();
|
||||||
|
const buttons = fixture.debugElement.queryAll(By.css('button'));
|
||||||
|
expect(buttons.length).toEqual(3);
|
||||||
|
});
|
||||||
|
it('should be shown when shouldShow is false', () => {
|
||||||
|
comp.shouldShow = false;
|
||||||
|
fixture.detectChanges();
|
||||||
|
const buttons = fixture.debugElement.queryAll(By.css('button'));
|
||||||
|
expect(buttons.length).toEqual(0);
|
||||||
|
});
|
||||||
|
it('should be disabled when isEnabled is false', () => {
|
||||||
|
comp.shouldShow = true;
|
||||||
|
comp.isEnabled = false;
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const buttons = fixture.debugElement.queryAll(By.css('button'));
|
||||||
|
|
||||||
|
expect(buttons[0].nativeElement.disabled).toBeTrue();
|
||||||
|
expect(buttons[1].nativeElement.disabled).toBeTrue();
|
||||||
|
expect(buttons[2].nativeElement.disabled).toBeTrue();
|
||||||
|
});
|
||||||
|
it('should be enabled when isEnabled is true', () => {
|
||||||
|
comp.shouldShow = true;
|
||||||
|
comp.isEnabled = true;
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const buttons = fixture.debugElement.queryAll(By.css('button'));
|
||||||
|
|
||||||
|
expect(buttons[0].nativeElement.disabled).toBeFalse();
|
||||||
|
expect(buttons[1].nativeElement.disabled).toBeFalse();
|
||||||
|
expect(buttons[2].nativeElement.disabled).toBeFalse();
|
||||||
|
});
|
||||||
|
it('should call the corresponding button when clicked', () => {
|
||||||
|
spyOn(comp, 'testConfiguration');
|
||||||
|
spyOn(comp, 'importNow');
|
||||||
|
spyOn(comp, 'resetAndReimport');
|
||||||
|
|
||||||
|
comp.shouldShow = true;
|
||||||
|
comp.isEnabled = true;
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const buttons = fixture.debugElement.queryAll(By.css('button'));
|
||||||
|
|
||||||
|
buttons[0].triggerEventHandler('click', null);
|
||||||
|
expect(comp.testConfiguration).toHaveBeenCalled();
|
||||||
|
|
||||||
|
buttons[1].triggerEventHandler('click', null);
|
||||||
|
expect(comp.importNow).toHaveBeenCalled();
|
||||||
|
|
||||||
|
buttons[2].triggerEventHandler('click', null);
|
||||||
|
expect(comp.resetAndReimport).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
@@ -0,0 +1,233 @@
|
|||||||
|
import { Component, Input, OnDestroy } from '@angular/core';
|
||||||
|
import { ScriptDataService } from '../../../../core/data/processes/script-data.service';
|
||||||
|
import { ContentSource } from '../../../../core/shared/content-source.model';
|
||||||
|
import { ProcessDataService } from '../../../../core/data/processes/process-data.service';
|
||||||
|
import {
|
||||||
|
getAllCompletedRemoteData,
|
||||||
|
getAllSucceededRemoteDataPayload,
|
||||||
|
getFirstCompletedRemoteData,
|
||||||
|
getFirstSucceededRemoteDataPayload
|
||||||
|
} from '../../../../core/shared/operators';
|
||||||
|
import { filter, map, switchMap, tap } from 'rxjs/operators';
|
||||||
|
import { hasValue, hasValueOperator } from '../../../../shared/empty.util';
|
||||||
|
import { ProcessStatus } from '../../../../process-page/processes/process-status.model';
|
||||||
|
import { Subscription } from 'rxjs/internal/Subscription';
|
||||||
|
import { RequestService } from '../../../../core/data/request.service';
|
||||||
|
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
|
||||||
|
import { Collection } from '../../../../core/shared/collection.model';
|
||||||
|
import { CollectionDataService } from '../../../../core/data/collection-data.service';
|
||||||
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
|
import { Process } from '../../../../process-page/processes/process.model';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { BitstreamDataService } from '../../../../core/data/bitstream-data.service';
|
||||||
|
import { ContentSourceSetSerializer } from '../../../../core/shared/content-source-set-serializer';
|
||||||
|
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component that contains the controls to run, reset and test the harvest
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-collection-source-controls',
|
||||||
|
styleUrls: ['./collection-source-controls.component.scss'],
|
||||||
|
templateUrl: './collection-source-controls.component.html',
|
||||||
|
})
|
||||||
|
export class CollectionSourceControlsComponent implements OnDestroy {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should the controls be enabled.
|
||||||
|
*/
|
||||||
|
@Input() isEnabled: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current collection
|
||||||
|
*/
|
||||||
|
@Input() collection: Collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should the control section be shown
|
||||||
|
*/
|
||||||
|
@Input() shouldShow: boolean;
|
||||||
|
|
||||||
|
contentSource$: Observable<ContentSource>;
|
||||||
|
private subs: Subscription[] = [];
|
||||||
|
|
||||||
|
testConfigRunning$ = new BehaviorSubject(false);
|
||||||
|
importRunning$ = new BehaviorSubject(false);
|
||||||
|
reImportRunning$ = new BehaviorSubject(false);
|
||||||
|
|
||||||
|
constructor(private scriptDataService: ScriptDataService,
|
||||||
|
private processDataService: ProcessDataService,
|
||||||
|
private requestService: RequestService,
|
||||||
|
private notificationsService: NotificationsService,
|
||||||
|
private collectionService: CollectionDataService,
|
||||||
|
private translateService: TranslateService,
|
||||||
|
private httpClient: HttpClient,
|
||||||
|
private bitstreamService: BitstreamDataService
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
// ensure the contentSource gets updated after being set to stale
|
||||||
|
this.contentSource$ = this.collectionService.findByHref(this.collection._links.self.href, false).pipe(
|
||||||
|
getAllSucceededRemoteDataPayload(),
|
||||||
|
switchMap((collection) => this.collectionService.getContentSource(collection.uuid, false)),
|
||||||
|
getAllSucceededRemoteDataPayload()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the provided content source's configuration.
|
||||||
|
* @param contentSource - The content source to be tested
|
||||||
|
*/
|
||||||
|
testConfiguration(contentSource) {
|
||||||
|
this.testConfigRunning$.next(true);
|
||||||
|
this.subs.push(this.scriptDataService.invoke('harvest', [
|
||||||
|
{name: '-g', value: null},
|
||||||
|
{name: '-a', value: contentSource.oaiSource},
|
||||||
|
{name: '-i', value: new ContentSourceSetSerializer().Serialize(contentSource.oaiSetId)},
|
||||||
|
], []).pipe(
|
||||||
|
getFirstCompletedRemoteData(),
|
||||||
|
tap((rd) => {
|
||||||
|
if (rd.hasFailed) {
|
||||||
|
// show a notification when the script invocation fails
|
||||||
|
this.notificationsService.error(this.translateService.get('collection.source.controls.test.submit.error'));
|
||||||
|
this.testConfigRunning$.next(false);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
// filter out responses that aren't successful since the pinging of the process only needs to happen when the invocation was successful.
|
||||||
|
filter((rd) => rd.hasSucceeded && hasValue(rd.payload)),
|
||||||
|
switchMap((rd) => this.processDataService.findById(rd.payload.processId, false)),
|
||||||
|
getAllCompletedRemoteData(),
|
||||||
|
filter((rd) => !rd.isStale && (rd.hasSucceeded || rd.hasFailed)),
|
||||||
|
map((rd) => rd.payload),
|
||||||
|
hasValueOperator(),
|
||||||
|
).subscribe((process: Process) => {
|
||||||
|
if (process.processStatus.toString() !== ProcessStatus[ProcessStatus.COMPLETED].toString() &&
|
||||||
|
process.processStatus.toString() !== ProcessStatus[ProcessStatus.FAILED].toString()) {
|
||||||
|
// Ping the current process state every 5s
|
||||||
|
setTimeout(() => {
|
||||||
|
this.requestService.setStaleByHrefSubstring(process._links.self.href);
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
if (process.processStatus.toString() === ProcessStatus[ProcessStatus.FAILED].toString()) {
|
||||||
|
this.notificationsService.error(this.translateService.get('collection.source.controls.test.failed'));
|
||||||
|
this.testConfigRunning$.next(false);
|
||||||
|
}
|
||||||
|
if (process.processStatus.toString() === ProcessStatus[ProcessStatus.COMPLETED].toString()) {
|
||||||
|
this.bitstreamService.findByHref(process._links.output.href).pipe(getFirstSucceededRemoteDataPayload()).subscribe((bitstream) => {
|
||||||
|
this.httpClient.get(bitstream._links.content.href, {responseType: 'text'}).subscribe((data: any) => {
|
||||||
|
const output = data.replaceAll(new RegExp('.*\\@(.*)', 'g'), '$1')
|
||||||
|
.replaceAll('The script has started', '')
|
||||||
|
.replaceAll('The script has completed', '');
|
||||||
|
this.notificationsService.info(this.translateService.get('collection.source.controls.test.completed'), output);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this.testConfigRunning$.next(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start the harvest for the current collection
|
||||||
|
*/
|
||||||
|
importNow() {
|
||||||
|
this.importRunning$.next(true);
|
||||||
|
this.subs.push(this.scriptDataService.invoke('harvest', [
|
||||||
|
{name: '-r', value: null},
|
||||||
|
{name: '-c', value: this.collection.uuid},
|
||||||
|
], [])
|
||||||
|
.pipe(
|
||||||
|
getFirstCompletedRemoteData(),
|
||||||
|
tap((rd) => {
|
||||||
|
if (rd.hasFailed) {
|
||||||
|
this.notificationsService.error(this.translateService.get('collection.source.controls.import.submit.error'));
|
||||||
|
this.importRunning$.next(false);
|
||||||
|
} else {
|
||||||
|
this.notificationsService.success(this.translateService.get('collection.source.controls.import.submit.success'));
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
filter((rd) => rd.hasSucceeded && hasValue(rd.payload)),
|
||||||
|
switchMap((rd) => this.processDataService.findById(rd.payload.processId, false)),
|
||||||
|
getAllCompletedRemoteData(),
|
||||||
|
filter((rd) => !rd.isStale && (rd.hasSucceeded || rd.hasFailed)),
|
||||||
|
map((rd) => rd.payload),
|
||||||
|
hasValueOperator(),
|
||||||
|
).subscribe((process) => {
|
||||||
|
if (process.processStatus.toString() !== ProcessStatus[ProcessStatus.COMPLETED].toString() &&
|
||||||
|
process.processStatus.toString() !== ProcessStatus[ProcessStatus.FAILED].toString()) {
|
||||||
|
// Ping the current process state every 5s
|
||||||
|
setTimeout(() => {
|
||||||
|
this.requestService.setStaleByHrefSubstring(process._links.self.href);
|
||||||
|
this.requestService.setStaleByHrefSubstring(this.collection._links.self.href);
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
if (process.processStatus.toString() === ProcessStatus[ProcessStatus.FAILED].toString()) {
|
||||||
|
this.notificationsService.error(this.translateService.get('collection.source.controls.import.failed'));
|
||||||
|
this.importRunning$.next(false);
|
||||||
|
}
|
||||||
|
if (process.processStatus.toString() === ProcessStatus[ProcessStatus.COMPLETED].toString()) {
|
||||||
|
this.notificationsService.success(this.translateService.get('collection.source.controls.import.completed'));
|
||||||
|
this.requestService.setStaleByHrefSubstring(this.collection._links.self.href);
|
||||||
|
this.importRunning$.next(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset and reimport the current collection
|
||||||
|
*/
|
||||||
|
resetAndReimport() {
|
||||||
|
this.reImportRunning$.next(true);
|
||||||
|
this.subs.push(this.scriptDataService.invoke('harvest', [
|
||||||
|
{name: '-o', value: null},
|
||||||
|
{name: '-c', value: this.collection.uuid},
|
||||||
|
], [])
|
||||||
|
.pipe(
|
||||||
|
getFirstCompletedRemoteData(),
|
||||||
|
tap((rd) => {
|
||||||
|
if (rd.hasFailed) {
|
||||||
|
this.notificationsService.error(this.translateService.get('collection.source.controls.reset.submit.error'));
|
||||||
|
this.reImportRunning$.next(false);
|
||||||
|
} else {
|
||||||
|
this.notificationsService.success(this.translateService.get('collection.source.controls.reset.submit.success'));
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
filter((rd) => rd.hasSucceeded && hasValue(rd.payload)),
|
||||||
|
switchMap((rd) => this.processDataService.findById(rd.payload.processId, false)),
|
||||||
|
getAllCompletedRemoteData(),
|
||||||
|
filter((rd) => !rd.isStale && (rd.hasSucceeded || rd.hasFailed)),
|
||||||
|
map((rd) => rd.payload),
|
||||||
|
hasValueOperator(),
|
||||||
|
).subscribe((process) => {
|
||||||
|
if (process.processStatus.toString() !== ProcessStatus[ProcessStatus.COMPLETED].toString() &&
|
||||||
|
process.processStatus.toString() !== ProcessStatus[ProcessStatus.FAILED].toString()) {
|
||||||
|
// Ping the current process state every 5s
|
||||||
|
setTimeout(() => {
|
||||||
|
this.requestService.setStaleByHrefSubstring(process._links.self.href);
|
||||||
|
this.requestService.setStaleByHrefSubstring(this.collection._links.self.href);
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
if (process.processStatus.toString() === ProcessStatus[ProcessStatus.FAILED].toString()) {
|
||||||
|
this.notificationsService.error(this.translateService.get('collection.source.controls.reset.failed'));
|
||||||
|
this.reImportRunning$.next(false);
|
||||||
|
}
|
||||||
|
if (process.processStatus.toString() === ProcessStatus[ProcessStatus.COMPLETED].toString()) {
|
||||||
|
this.notificationsService.success(this.translateService.get('collection.source.controls.reset.completed'));
|
||||||
|
this.requestService.setStaleByHrefSubstring(this.collection._links.self.href);
|
||||||
|
this.reImportRunning$.next(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.subs.forEach((sub) => {
|
||||||
|
if (hasValue(sub)) {
|
||||||
|
sub.unsubscribe();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@@ -1,57 +1,74 @@
|
|||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="d-inline-block float-right">
|
<div class="d-inline-block float-right">
|
||||||
<button class=" btn btn-danger" *ngIf="!(isReinstatable() | async)"
|
<button class=" btn btn-danger" *ngIf="!(isReinstatable() | async)"
|
||||||
[disabled]="!(hasChanges() | async)"
|
[disabled]="!(hasChanges() | async)"
|
||||||
(click)="discard()"><i
|
(click)="discard()"><i
|
||||||
class="fas fa-times"></i>
|
class="fas fa-times"></i>
|
||||||
<span class="d-none d-sm-inline"> {{"item.edit.metadata.discard-button" | translate}}</span>
|
<span class="d-none d-sm-inline"> {{"item.edit.metadata.discard-button" | translate}}</span>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-warning" *ngIf="isReinstatable() | async"
|
<button class="btn btn-warning" *ngIf="isReinstatable() | async"
|
||||||
(click)="reinstate()"><i
|
(click)="reinstate()"><i
|
||||||
class="fas fa-undo-alt"></i>
|
class="fas fa-undo-alt"></i>
|
||||||
<span class="d-none d-sm-inline"> {{"item.edit.metadata.reinstate-button" | translate}}</span>
|
<span class="d-none d-sm-inline"> {{"item.edit.metadata.reinstate-button" | translate}}</span>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-primary" [disabled]="!(hasChanges() | async) || !isValid() || (initialHarvestType === harvestTypeNone && contentSource.harvestType === initialHarvestType)"
|
<button class="btn btn-primary"
|
||||||
(click)="onSubmit()"><i
|
[disabled]="!(hasChanges() | async) || !isValid() || (initialHarvestType === harvestTypeNone && contentSource.harvestType === initialHarvestType)"
|
||||||
class="fas fa-save"></i>
|
(click)="onSubmit()"><i
|
||||||
<span class="d-none d-sm-inline"> {{"item.edit.metadata.save-button" | translate}}</span>
|
class="fas fa-save"></i>
|
||||||
</button>
|
<span class="d-none d-sm-inline"> {{"item.edit.metadata.save-button" | translate}}</span>
|
||||||
</div>
|
</button>
|
||||||
<h4>{{ 'collection.edit.tabs.source.head' | translate }}</h4>
|
</div>
|
||||||
<div *ngIf="contentSource" class="form-check mb-4">
|
<h4>{{ 'collection.edit.tabs.source.head' | translate }}</h4>
|
||||||
<input type="checkbox" class="form-check-input" id="externalSourceCheck" [checked]="(contentSource?.harvestType !== harvestTypeNone)" (change)="changeExternalSource()">
|
<div *ngIf="contentSource" class="form-check mb-4">
|
||||||
<label class="form-check-label" for="externalSourceCheck">{{ 'collection.edit.tabs.source.external' | translate }}</label>
|
<input type="checkbox" class="form-check-input" id="externalSourceCheck"
|
||||||
</div>
|
[checked]="(contentSource?.harvestType !== harvestTypeNone)" (change)="changeExternalSource()">
|
||||||
<ds-loading *ngIf="!contentSource" [message]="'loading.content-source' | translate"></ds-loading>
|
<label class="form-check-label"
|
||||||
<h4 *ngIf="contentSource && (contentSource?.harvestType !== harvestTypeNone)">{{ 'collection.edit.tabs.source.form.head' | translate }}</h4>
|
for="externalSourceCheck">{{ 'collection.edit.tabs.source.external' | translate }}</label>
|
||||||
|
</div>
|
||||||
|
<ds-loading *ngIf="!contentSource" [message]="'loading.content-source' | translate"></ds-loading>
|
||||||
|
<h4 *ngIf="contentSource && (contentSource?.harvestType !== harvestTypeNone)">{{ 'collection.edit.tabs.source.form.head' | translate }}</h4>
|
||||||
</div>
|
</div>
|
||||||
<ds-form *ngIf="formGroup && contentSource && (contentSource?.harvestType !== harvestTypeNone)"
|
<div class="row">
|
||||||
[formId]="'collection-source-form-id'"
|
<ds-form *ngIf="formGroup && contentSource && (contentSource?.harvestType !== harvestTypeNone)"
|
||||||
[formGroup]="formGroup"
|
[formId]="'collection-source-form-id'"
|
||||||
[formModel]="formModel"
|
[formGroup]="formGroup"
|
||||||
[formLayout]="formLayout"
|
[formModel]="formModel"
|
||||||
[displaySubmit]="false"
|
[formLayout]="formLayout"
|
||||||
[displayCancel]="false"
|
[displaySubmit]="false"
|
||||||
(dfChange)="onChange($event)"
|
[displayCancel]="false"
|
||||||
(submitForm)="onSubmit()"
|
(dfChange)="onChange($event)"
|
||||||
(cancel)="onCancel()"></ds-form>
|
(submitForm)="onSubmit()"
|
||||||
<div class="container-fluid" *ngIf="(contentSource?.harvestType !== harvestTypeNone)">
|
(cancel)="onCancel()"></ds-form>
|
||||||
<div class="d-inline-block float-right">
|
|
||||||
<button class=" btn btn-danger" *ngIf="!(isReinstatable() | async)"
|
|
||||||
[disabled]="!(hasChanges() | async)"
|
|
||||||
(click)="discard()"><i
|
|
||||||
class="fas fa-times"></i>
|
|
||||||
<span class="d-none d-sm-inline"> {{"item.edit.metadata.discard-button" | translate}}</span>
|
|
||||||
</button>
|
|
||||||
<button class="btn btn-warning" *ngIf="isReinstatable() | async"
|
|
||||||
(click)="reinstate()"><i
|
|
||||||
class="fas fa-undo-alt"></i>
|
|
||||||
<span class="d-none d-sm-inline"> {{"item.edit.metadata.reinstate-button" | translate}}</span>
|
|
||||||
</button>
|
|
||||||
<button class="btn btn-primary" [disabled]="!(hasChanges() | async) || !isValid() || (initialHarvestType === harvestTypeNone && contentSource.harvestType === initialHarvestType)"
|
|
||||||
(click)="onSubmit()"><i
|
|
||||||
class="fas fa-save"></i>
|
|
||||||
<span class="d-none d-sm-inline"> {{"item.edit.metadata.save-button" | translate}}</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="container mt-2" *ngIf="(contentSource?.harvestType !== harvestTypeNone)">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="d-inline-block float-right ml-1">
|
||||||
|
<button class=" btn btn-danger" *ngIf="!(isReinstatable() | async)"
|
||||||
|
[disabled]="!(hasChanges() | async)"
|
||||||
|
(click)="discard()"><i
|
||||||
|
class="fas fa-times"></i>
|
||||||
|
<span class="d-none d-sm-inline"> {{"item.edit.metadata.discard-button" | translate}}</span>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-warning" *ngIf="isReinstatable() | async"
|
||||||
|
(click)="reinstate()"><i
|
||||||
|
class="fas fa-undo-alt"></i>
|
||||||
|
<span class="d-none d-sm-inline"> {{"item.edit.metadata.reinstate-button" | translate}}</span>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-primary"
|
||||||
|
[disabled]="!(hasChanges() | async) || !isValid() || (initialHarvestType === harvestTypeNone && contentSource.harvestType === initialHarvestType)"
|
||||||
|
(click)="onSubmit()"><i
|
||||||
|
class="fas fa-save"></i>
|
||||||
|
<span class="d-none d-sm-inline"> {{"item.edit.metadata.save-button" | translate}}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ds-collection-source-controls
|
||||||
|
[isEnabled]="!(hasChanges()|async)"
|
||||||
|
[shouldShow]="contentSource?.harvestType !== harvestTypeNone"
|
||||||
|
[collection]="(collectionRD$ |async)?.payload"
|
||||||
|
>
|
||||||
|
</ds-collection-source-controls>
|
||||||
|
|
||||||
|
@@ -62,7 +62,8 @@ describe('CollectionSourceComponent', () => {
|
|||||||
label: 'DSpace Intermediate Metadata',
|
label: 'DSpace Intermediate Metadata',
|
||||||
nameSpace: 'http://www.dspace.org/xmlns/dspace/dim'
|
nameSpace: 'http://www.dspace.org/xmlns/dspace/dim'
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
_links: { self: { href: 'contentsource-selflink' } }
|
||||||
});
|
});
|
||||||
fieldUpdate = {
|
fieldUpdate = {
|
||||||
field: contentSource,
|
field: contentSource,
|
||||||
@@ -115,7 +116,7 @@ describe('CollectionSourceComponent', () => {
|
|||||||
updateContentSource: observableOf(contentSource),
|
updateContentSource: observableOf(contentSource),
|
||||||
getHarvesterEndpoint: observableOf('harvester-endpoint')
|
getHarvesterEndpoint: observableOf('harvester-endpoint')
|
||||||
});
|
});
|
||||||
requestService = jasmine.createSpyObj('requestService', ['removeByHrefSubstring']);
|
requestService = jasmine.createSpyObj('requestService', ['removeByHrefSubstring', 'setStaleByHrefSubstring']);
|
||||||
|
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [TranslateModule.forRoot(), RouterTestingModule],
|
imports: [TranslateModule.forRoot(), RouterTestingModule],
|
||||||
|
@@ -380,7 +380,7 @@ export class CollectionSourceComponent extends AbstractTrackableComponent implem
|
|||||||
switchMap((uuid) => this.collectionService.getHarvesterEndpoint(uuid)),
|
switchMap((uuid) => this.collectionService.getHarvesterEndpoint(uuid)),
|
||||||
take(1)
|
take(1)
|
||||||
).subscribe((endpoint) => this.requestService.removeByHrefSubstring(endpoint));
|
).subscribe((endpoint) => this.requestService.removeByHrefSubstring(endpoint));
|
||||||
|
this.requestService.setStaleByHrefSubstring(this.contentSource._links.self.href);
|
||||||
// Update harvester
|
// Update harvester
|
||||||
this.collectionRD$.pipe(
|
this.collectionRD$.pipe(
|
||||||
getFirstSucceededRemoteData(),
|
getFirstSucceededRemoteData(),
|
||||||
|
@@ -9,6 +9,7 @@ import { CollectionCurateComponent } from './collection-curate/collection-curate
|
|||||||
import { CollectionSourceComponent } from './collection-source/collection-source.component';
|
import { CollectionSourceComponent } from './collection-source/collection-source.component';
|
||||||
import { CollectionAuthorizationsComponent } from './collection-authorizations/collection-authorizations.component';
|
import { CollectionAuthorizationsComponent } from './collection-authorizations/collection-authorizations.component';
|
||||||
import { CollectionFormModule } from '../collection-form/collection-form.module';
|
import { CollectionFormModule } from '../collection-form/collection-form.module';
|
||||||
|
import { CollectionSourceControlsComponent } from './collection-source/collection-source-controls/collection-source-controls.component';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Module that contains all components related to the Edit Collection page administrator functionality
|
* Module that contains all components related to the Edit Collection page administrator functionality
|
||||||
@@ -26,6 +27,8 @@ import { CollectionFormModule } from '../collection-form/collection-form.module'
|
|||||||
CollectionRolesComponent,
|
CollectionRolesComponent,
|
||||||
CollectionCurateComponent,
|
CollectionCurateComponent,
|
||||||
CollectionSourceComponent,
|
CollectionSourceComponent,
|
||||||
|
|
||||||
|
CollectionSourceControlsComponent,
|
||||||
CollectionAuthorizationsComponent
|
CollectionAuthorizationsComponent
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
@@ -138,7 +138,7 @@ export class CollectionDataService extends ComColDataService<Collection> {
|
|||||||
* Get the collection's content harvester
|
* Get the collection's content harvester
|
||||||
* @param collectionId
|
* @param collectionId
|
||||||
*/
|
*/
|
||||||
getContentSource(collectionId: string): Observable<RemoteData<ContentSource>> {
|
getContentSource(collectionId: string, useCachedVersionIfAvailable = true): Observable<RemoteData<ContentSource>> {
|
||||||
const href$ = this.getHarvesterEndpoint(collectionId).pipe(
|
const href$ = this.getHarvesterEndpoint(collectionId).pipe(
|
||||||
isNotEmptyOperator(),
|
isNotEmptyOperator(),
|
||||||
take(1)
|
take(1)
|
||||||
@@ -146,7 +146,7 @@ export class CollectionDataService extends ComColDataService<Collection> {
|
|||||||
|
|
||||||
href$.subscribe((href: string) => {
|
href$.subscribe((href: string) => {
|
||||||
const request = new ContentSourceRequest(this.requestService.generateRequestId(), href);
|
const request = new ContentSourceRequest(this.requestService.generateRequestId(), href);
|
||||||
this.requestService.send(request, true);
|
this.requestService.send(request, useCachedVersionIfAvailable);
|
||||||
});
|
});
|
||||||
|
|
||||||
return this.rdbService.buildSingle<ContentSource>(href$);
|
return this.rdbService.buildSingle<ContentSource>(href$);
|
||||||
|
26
src/app/core/shared/content-source-set-serializer.spec.ts
Normal file
26
src/app/core/shared/content-source-set-serializer.spec.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { ContentSourceSetSerializer } from './content-source-set-serializer';
|
||||||
|
|
||||||
|
describe('ContentSourceSetSerializer', () => {
|
||||||
|
let serializer: ContentSourceSetSerializer;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
serializer = new ContentSourceSetSerializer();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Serialize', () => {
|
||||||
|
it('should return all when the value is empty', () => {
|
||||||
|
expect(serializer.Serialize('')).toEqual('all');
|
||||||
|
});
|
||||||
|
it('should return the value when it is not empty', () => {
|
||||||
|
expect(serializer.Serialize('test-value')).toEqual('test-value');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('Deserialize', () => {
|
||||||
|
it('should return an empty value when the value is \'all\'', () => {
|
||||||
|
expect(serializer.Deserialize('all')).toEqual('');
|
||||||
|
});
|
||||||
|
it('should return the value when it is not \'all\'', () => {
|
||||||
|
expect(serializer.Deserialize('test-value')).toEqual('test-value');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
31
src/app/core/shared/content-source-set-serializer.ts
Normal file
31
src/app/core/shared/content-source-set-serializer.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { isEmpty } from '../../shared/empty.util';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serializer to create convert the 'all' value supported by the server to an empty string and vice versa.
|
||||||
|
*/
|
||||||
|
export class ContentSourceSetSerializer {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to serialize a setId
|
||||||
|
* @param {string} setId
|
||||||
|
* @returns {string} the provided set ID, unless when an empty set ID is provided. In that case, 'all' will be returned.
|
||||||
|
*/
|
||||||
|
Serialize(setId: string): any {
|
||||||
|
if (isEmpty(setId)) {
|
||||||
|
return 'all';
|
||||||
|
}
|
||||||
|
return setId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to deserialize a setId
|
||||||
|
* @param {string} setId
|
||||||
|
* @returns {string} the provided set ID. When 'all' is provided, an empty set ID will be returned.
|
||||||
|
*/
|
||||||
|
Deserialize(setId: string): string {
|
||||||
|
if (setId === 'all') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return setId;
|
||||||
|
}
|
||||||
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
import { autoserializeAs, deserializeAs, deserialize } from 'cerialize';
|
import { autoserializeAs, deserialize, deserializeAs, serializeAs } from 'cerialize';
|
||||||
import { HALLink } from './hal-link.model';
|
import { HALLink } from './hal-link.model';
|
||||||
import { MetadataConfig } from './metadata-config.model';
|
import { MetadataConfig } from './metadata-config.model';
|
||||||
import { CacheableObject } from '../cache/object-cache.reducer';
|
import { CacheableObject } from '../cache/object-cache.reducer';
|
||||||
@@ -6,6 +6,7 @@ import { typedObject } from '../cache/builders/build-decorators';
|
|||||||
import { CONTENT_SOURCE } from './content-source.resource-type';
|
import { CONTENT_SOURCE } from './content-source.resource-type';
|
||||||
import { excludeFromEquals } from '../utilities/equals.decorators';
|
import { excludeFromEquals } from '../utilities/equals.decorators';
|
||||||
import { ResourceType } from './resource-type';
|
import { ResourceType } from './resource-type';
|
||||||
|
import { ContentSourceSetSerializer } from './content-source-set-serializer';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type of content harvesting used
|
* The type of content harvesting used
|
||||||
@@ -49,7 +50,8 @@ export class ContentSource extends CacheableObject {
|
|||||||
/**
|
/**
|
||||||
* OAI Specific set ID
|
* OAI Specific set ID
|
||||||
*/
|
*/
|
||||||
@autoserializeAs('oai_set_id')
|
@deserializeAs(new ContentSourceSetSerializer(), 'oai_set_id')
|
||||||
|
@serializeAs(new ContentSourceSetSerializer(), 'oai_set_id')
|
||||||
oaiSetId: string;
|
oaiSetId: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -70,6 +72,30 @@ export class ContentSource extends CacheableObject {
|
|||||||
*/
|
*/
|
||||||
metadataConfigs: MetadataConfig[];
|
metadataConfigs: MetadataConfig[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current harvest status
|
||||||
|
*/
|
||||||
|
@autoserializeAs('harvest_status')
|
||||||
|
harvestStatus: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The last's harvest start time
|
||||||
|
*/
|
||||||
|
@autoserializeAs('harvest_start_time')
|
||||||
|
harvestStartTime: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When the collection was last harvested
|
||||||
|
*/
|
||||||
|
@autoserializeAs('last_harvested')
|
||||||
|
lastHarvested: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current harvest message
|
||||||
|
*/
|
||||||
|
@autoserializeAs('harvest_message')
|
||||||
|
message: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link HALLink}s for this ContentSource
|
* The {@link HALLink}s for this ContentSource
|
||||||
*/
|
*/
|
||||||
|
@@ -895,6 +895,30 @@
|
|||||||
"collection.select.table.title": "Title",
|
"collection.select.table.title": "Title",
|
||||||
|
|
||||||
|
|
||||||
|
"collection.source.controls.head": "Harvest Controls",
|
||||||
|
"collection.source.controls.test.submit.error": "Something went wrong with initiating the testing of the settings",
|
||||||
|
"collection.source.controls.test.failed": "The script to test the settings has failed",
|
||||||
|
"collection.source.controls.test.completed": "The script to test the settings has successfully finished",
|
||||||
|
"collection.source.controls.test.submit": "Test configuration",
|
||||||
|
"collection.source.controls.test.running": "Testing configuration...",
|
||||||
|
"collection.source.controls.import.submit.success": "The import has been successfully initiated",
|
||||||
|
"collection.source.controls.import.submit.error": "Something went wrong with initiating the import",
|
||||||
|
"collection.source.controls.import.submit": "Import now",
|
||||||
|
"collection.source.controls.import.running": "Importing...",
|
||||||
|
"collection.source.controls.import.failed": "An error occurred during the import",
|
||||||
|
"collection.source.controls.import.completed": "The import completed",
|
||||||
|
"collection.source.controls.reset.submit.success": "The reset and reimport has been successfully initiated",
|
||||||
|
"collection.source.controls.reset.submit.error": "Something went wrong with initiating the reset and reimport",
|
||||||
|
"collection.source.controls.reset.failed": "An error occurred during the reset and reimport",
|
||||||
|
"collection.source.controls.reset.completed": "The reset and reimport completed",
|
||||||
|
"collection.source.controls.reset.submit": "Reset and reimport",
|
||||||
|
"collection.source.controls.reset.running": "Resetting and reimporting...",
|
||||||
|
"collection.source.controls.harvest.status": "Harvest status:",
|
||||||
|
"collection.source.controls.harvest.start": "Harvest start time:",
|
||||||
|
"collection.source.controls.harvest.last": "Last time harvested:",
|
||||||
|
"collection.source.controls.harvest.message": "Harvest info:",
|
||||||
|
"collection.source.controls.harvest.no-information": "N/A",
|
||||||
|
|
||||||
|
|
||||||
"collection.source.update.notifications.error.content": "The provided settings have been tested and didn't work.",
|
"collection.source.update.notifications.error.content": "The provided settings have been tested and didn't work.",
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user