From 100166023a582b5626d728073e540d6f0ea23001 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Thu, 23 Sep 2021 15:47:43 +0200 Subject: [PATCH 1/4] 83707: Control collection harvest from the UI --- .../collection-source-controls.component.html | 35 ++++ .../collection-source-controls.component.ts | 150 ++++++++++++++++++ .../collection-source.component.html | 125 ++++++++------- .../collection-source.component.ts | 2 +- .../edit-collection-page.module.ts | 3 + src/app/core/data/collection-data.service.ts | 4 +- src/app/core/shared/content-source.model.ts | 9 ++ src/assets/i18n/en.json5 | 16 ++ 8 files changed, 287 insertions(+), 57 deletions(-) create mode 100644 src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.html create mode 100644 src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.ts diff --git a/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.html b/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.html new file mode 100644 index 0000000000..425d773e2d --- /dev/null +++ b/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.html @@ -0,0 +1,35 @@ +
+
+

{{ 'collection.source.controls.head' | translate }}

+
+ {{'collection.source.controls.harvest.status' | translate}} + {{contentSource?.harvestStatus}} +
+
+ {{'collection.source.controls.harvest.start' | translate}} + {{contentSource?.harvestStartTime ? contentSource?.harvestStartTime : 'collection.source.controls.harvest.no-information'|translate }} +
+
+ {{'collection.source.controls.harvest.last' | translate}} + {{contentSource?.lastHarvested ? contentSource?.lastHarvested : 'collection.source.controls.harvest.no-information'|translate }} +
+ + + + + + +
+
\ No newline at end of file diff --git a/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.ts b/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.ts new file mode 100644 index 0000000000..417627cf46 --- /dev/null +++ b/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.ts @@ -0,0 +1,150 @@ +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'; + +@Component({ + selector: 'ds-collection-source-controls', + templateUrl: './collection-source-controls.component.html', +}) +export class CollectionSourceControlsComponent implements OnDestroy { + + @Input() isEnabled: boolean; + @Input() collection: Collection; + @Input() shouldShow: boolean; + + contentSource$: Observable; + private subs: Subscription[] = []; + + 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() { + this.contentSource$ = this.collectionService.findByHref(this.collection._links.self.href, false).pipe( + getAllSucceededRemoteDataPayload(), + switchMap((collection) => this.collectionService.getContentSource(collection.uuid, false)), + getAllSucceededRemoteDataPayload() + ); + } + + testConfiguration(contentSource) { + this.subs.push(this.scriptDataService.invoke('harvest', [ + {name: '-g', value: null}, + {name: '-a', value: contentSource.oaiSource}, + {name: '-i', value: contentSource.oaiSetId}, + ], []).pipe( + getFirstCompletedRemoteData(), + tap((rd) => { + if (rd.hasFailed) { + this.notificationsService.error(this.translateService.get('collection.source.controls.test.submit.error')); + } + }), + 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()) { + 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')); + } + if (process.processStatus.toString() === ProcessStatus[ProcessStatus.COMPLETED].toString()) { + this.bitstreamService.findByHref(process._links.output.href, false).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.success(this.translateService.get('collection.source.controls.test.completed'), output); + }); + }); + } + } + )); + } + + importNow() { + this.subs.push(this.scriptDataService.invoke('harvest', [ + {name: '-r', value: null}, + {name: '-e', value: 'dspacedemo+admin@gmail.com'}, + {name: '-c', value: this.collection.uuid}, + ], []) + .pipe( + getFirstCompletedRemoteData(), + tap((rd) => { + if (rd.hasFailed) { + this.notificationsService.error(this.translateService.get('collection.source.controls.test.submit.error')); + } + }), + 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()) { + 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')); + } + if (process.processStatus.toString() === ProcessStatus[ProcessStatus.COMPLETED].toString()) { + this.notificationsService.success(this.translateService.get('collection.source.controls.import.completed')); + setTimeout(() => { + this.requestService.setStaleByHrefSubstring(this.collection._links.self.href); + }, 5000); + } + } + )); + } + + resetAndReimport() { + // TODO implement when a single option is present + } + + ngOnDestroy(): void { + this.subs.forEach((sub) => { + if (hasValue(sub)) { + sub.unsubscribe(); + } + }); + } +} diff --git a/src/app/collection-page/edit-collection-page/collection-source/collection-source.component.html b/src/app/collection-page/edit-collection-page/collection-source/collection-source.component.html index de7f0b4708..b67ee9a1bd 100644 --- a/src/app/collection-page/edit-collection-page/collection-source/collection-source.component.html +++ b/src/app/collection-page/edit-collection-page/collection-source/collection-source.component.html @@ -1,57 +1,74 @@
-
- - - -
-

{{ 'collection.edit.tabs.source.head' | translate }}

-
- - -
- -

{{ 'collection.edit.tabs.source.form.head' | translate }}

+
+ + + +
+

{{ 'collection.edit.tabs.source.head' | translate }}

+
+ + +
+ +

{{ 'collection.edit.tabs.source.form.head' | translate }}

- -
-
- - - -
+
+
+
+
+
+
+ + + +
+
+
+
+ + + diff --git a/src/app/collection-page/edit-collection-page/collection-source/collection-source.component.ts b/src/app/collection-page/edit-collection-page/collection-source/collection-source.component.ts index c4b42d028d..ae48b9309e 100644 --- a/src/app/collection-page/edit-collection-page/collection-source/collection-source.component.ts +++ b/src/app/collection-page/edit-collection-page/collection-source/collection-source.component.ts @@ -380,7 +380,7 @@ export class CollectionSourceComponent extends AbstractTrackableComponent implem switchMap((uuid) => this.collectionService.getHarvesterEndpoint(uuid)), take(1) ).subscribe((endpoint) => this.requestService.removeByHrefSubstring(endpoint)); - + this.requestService.setStaleByHrefSubstring(this.contentSource._links.self.href); // Update harvester this.collectionRD$.pipe( getFirstSucceededRemoteData(), diff --git a/src/app/collection-page/edit-collection-page/edit-collection-page.module.ts b/src/app/collection-page/edit-collection-page/edit-collection-page.module.ts index b743032c8c..0b09542fa0 100644 --- a/src/app/collection-page/edit-collection-page/edit-collection-page.module.ts +++ b/src/app/collection-page/edit-collection-page/edit-collection-page.module.ts @@ -9,6 +9,7 @@ import { CollectionCurateComponent } from './collection-curate/collection-curate import { CollectionSourceComponent } from './collection-source/collection-source.component'; import { CollectionAuthorizationsComponent } from './collection-authorizations/collection-authorizations.component'; 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 @@ -26,6 +27,8 @@ import { CollectionFormModule } from '../collection-form/collection-form.module' CollectionRolesComponent, CollectionCurateComponent, CollectionSourceComponent, + + CollectionSourceControlsComponent, CollectionAuthorizationsComponent ] }) diff --git a/src/app/core/data/collection-data.service.ts b/src/app/core/data/collection-data.service.ts index f58f36450f..334e56e371 100644 --- a/src/app/core/data/collection-data.service.ts +++ b/src/app/core/data/collection-data.service.ts @@ -138,7 +138,7 @@ export class CollectionDataService extends ComColDataService { * Get the collection's content harvester * @param collectionId */ - getContentSource(collectionId: string): Observable> { + getContentSource(collectionId: string, useCachedVersionIfAvailable = true): Observable> { const href$ = this.getHarvesterEndpoint(collectionId).pipe( isNotEmptyOperator(), take(1) @@ -146,7 +146,7 @@ export class CollectionDataService extends ComColDataService { href$.subscribe((href: string) => { const request = new ContentSourceRequest(this.requestService.generateRequestId(), href); - this.requestService.send(request, true); + this.requestService.send(request, useCachedVersionIfAvailable); }); return this.rdbService.buildSingle(href$); diff --git a/src/app/core/shared/content-source.model.ts b/src/app/core/shared/content-source.model.ts index 326407822f..c98901219d 100644 --- a/src/app/core/shared/content-source.model.ts +++ b/src/app/core/shared/content-source.model.ts @@ -70,6 +70,15 @@ export class ContentSource extends CacheableObject { */ metadataConfigs: MetadataConfig[]; + @autoserializeAs('harvest_status') + harvestStatus: string; + + @autoserializeAs('harvest_start_time') + harvestStartTime: string; + + @autoserializeAs('last_harvested') + lastHarvested: string; + /** * The {@link HALLink}s for this ContentSource */ diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 8908ca42f6..8d89561d68 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -881,6 +881,22 @@ "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.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.failed": "An error occurred during the import", + "collection.source.controls.import.completed": "The import completed", + "collection.source.controls.reset.submit": "Reset and reimport", + "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.no-information": "N/A", + "collection.source.update.notifications.error.content": "The provided settings have been tested and didn't work.", From 4e9bd86012b006ab930b3430854ee7c52f97c94c Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Mon, 27 Sep 2021 11:11:54 +0200 Subject: [PATCH 2/4] 83707: Implement tests --- ...llection-source-controls.component.spec.ts | 219 ++++++++++++++++++ .../collection-source-controls.component.ts | 35 ++- .../collection-source.component.spec.ts | 5 +- 3 files changed, 253 insertions(+), 6 deletions(-) create mode 100644 src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.spec.ts diff --git a/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.spec.ts b/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.spec.ts new file mode 100644 index 0000000000..3307b8fc79 --- /dev/null +++ b/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.spec.ts @@ -0,0 +1,219 @@ +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'; + +describe('CollectionSourceControlsComponent', () => { + let comp: CollectionSourceControlsComponent; + let fixture: ComponentFixture; + + 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: 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: '-e', value: 'dspacedemo+admin@gmail.com'}, + {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(); + }); + }); + + +}); diff --git a/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.ts b/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.ts index 417627cf46..4562772391 100644 --- a/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.ts +++ b/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.ts @@ -22,14 +22,28 @@ import { TranslateService } from '@ngx-translate/core'; import { HttpClient } from '@angular/common/http'; import { BitstreamDataService } from '../../../../core/data/bitstream-data.service'; +/** + * Component that contains the controls to run, reset and test the harvest + */ @Component({ selector: 'ds-collection-source-controls', 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; @@ -47,6 +61,7 @@ export class CollectionSourceControlsComponent implements OnDestroy { } 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)), @@ -54,6 +69,10 @@ export class CollectionSourceControlsComponent implements OnDestroy { ); } + /** + * Tests the provided content source's configuration. + * @param contentSource - The content source to be tested + */ testConfiguration(contentSource) { this.subs.push(this.scriptDataService.invoke('harvest', [ {name: '-g', value: null}, @@ -63,9 +82,11 @@ export class CollectionSourceControlsComponent implements OnDestroy { 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')); } }), + // 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(), @@ -75,6 +96,7 @@ export class CollectionSourceControlsComponent implements OnDestroy { ).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); @@ -83,12 +105,12 @@ export class CollectionSourceControlsComponent implements OnDestroy { this.notificationsService.error(this.translateService.get('collection.source.controls.test.failed')); } if (process.processStatus.toString() === ProcessStatus[ProcessStatus.COMPLETED].toString()) { - this.bitstreamService.findByHref(process._links.output.href, false).pipe(getFirstSucceededRemoteDataPayload()).subscribe((bitstream) => { + 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.success(this.translateService.get('collection.source.controls.test.completed'), output); + this.notificationsService.info(this.translateService.get('collection.source.controls.test.completed'), output); }); }); } @@ -96,6 +118,9 @@ export class CollectionSourceControlsComponent implements OnDestroy { )); } + /** + * Start the harvest for the current collection + */ importNow() { this.subs.push(this.scriptDataService.invoke('harvest', [ {name: '-r', value: null}, @@ -118,6 +143,7 @@ export class CollectionSourceControlsComponent implements OnDestroy { ).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); @@ -128,14 +154,15 @@ export class CollectionSourceControlsComponent implements OnDestroy { } if (process.processStatus.toString() === ProcessStatus[ProcessStatus.COMPLETED].toString()) { this.notificationsService.success(this.translateService.get('collection.source.controls.import.completed')); - setTimeout(() => { this.requestService.setStaleByHrefSubstring(this.collection._links.self.href); - }, 5000); } } )); } + /** + * Reset and reimport the current collection + */ resetAndReimport() { // TODO implement when a single option is present } diff --git a/src/app/collection-page/edit-collection-page/collection-source/collection-source.component.spec.ts b/src/app/collection-page/edit-collection-page/collection-source/collection-source.component.spec.ts index 869238b956..3fb1a50bf1 100644 --- a/src/app/collection-page/edit-collection-page/collection-source/collection-source.component.spec.ts +++ b/src/app/collection-page/edit-collection-page/collection-source/collection-source.component.spec.ts @@ -62,7 +62,8 @@ describe('CollectionSourceComponent', () => { label: 'DSpace Intermediate Metadata', nameSpace: 'http://www.dspace.org/xmlns/dspace/dim' } - ] + ], + _links: { self: { href: 'contentsource-selflink' } } }); fieldUpdate = { field: contentSource, @@ -115,7 +116,7 @@ describe('CollectionSourceComponent', () => { updateContentSource: observableOf(contentSource), getHarvesterEndpoint: observableOf('harvester-endpoint') }); - requestService = jasmine.createSpyObj('requestService', ['removeByHrefSubstring']); + requestService = jasmine.createSpyObj('requestService', ['removeByHrefSubstring', 'setStaleByHrefSubstring']); TestBed.configureTestingModule({ imports: [TranslateModule.forRoot(), RouterTestingModule], From e434bb895246e8ce050b172dcb8fad9e2a4e13f3 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Thu, 30 Sep 2021 10:22:48 +0200 Subject: [PATCH 3/4] 83707: Implement feedback --- .../collection-source-controls.component.html | 4 ++ ...llection-source-controls.component.spec.ts | 17 ++++++- .../collection-source-controls.component.ts | 46 +++++++++++++++++-- .../content-source-set-serializer.spec.ts | 26 +++++++++++ .../shared/content-source-set-serializer.ts | 31 +++++++++++++ src/app/core/shared/content-source.model.ts | 22 ++++++++- src/assets/i18n/en.json5 | 5 ++ 7 files changed, 143 insertions(+), 8 deletions(-) create mode 100644 src/app/core/shared/content-source-set-serializer.spec.ts create mode 100644 src/app/core/shared/content-source-set-serializer.ts diff --git a/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.html b/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.html index 425d773e2d..7a0f07adda 100644 --- a/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.html +++ b/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.html @@ -11,6 +11,10 @@
{{'collection.source.controls.harvest.last' | translate}} + {{contentSource?.message ? contentSource?.message : 'collection.source.controls.harvest.no-information'|translate }} +
+
+ {{'collection.source.controls.harvest.message' | translate}} {{contentSource?.lastHarvested ? contentSource?.lastHarvested : 'collection.source.controls.harvest.no-information'|translate }}
diff --git a/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.spec.ts b/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.spec.ts index 3307b8fc79..3eb83ebe8a 100644 --- a/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.spec.ts +++ b/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.spec.ts @@ -21,6 +21,7 @@ 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; @@ -133,7 +134,7 @@ describe('CollectionSourceControlsComponent', () => { expect(scriptDataService.invoke).toHaveBeenCalledWith('harvest', [ {name: '-g', value: null}, {name: '-a', value: contentSource.oaiSource}, - {name: '-i', value: contentSource.oaiSetId}, + {name: '-i', value: new ContentSourceSetSerializer().Serialize(contentSource.oaiSetId)}, ], []); expect(processDataService.findById).toHaveBeenCalledWith(process.processId, false); @@ -148,7 +149,19 @@ describe('CollectionSourceControlsComponent', () => { expect(scriptDataService.invoke).toHaveBeenCalledWith('harvest', [ {name: '-r', value: null}, - {name: '-e', value: 'dspacedemo+admin@gmail.com'}, + {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); diff --git a/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.ts b/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.ts index 4562772391..ca6e1e9cd8 100644 --- a/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.ts +++ b/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.ts @@ -21,6 +21,7 @@ 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'; /** * Component that contains the controls to run, reset and test the harvest @@ -77,7 +78,7 @@ export class CollectionSourceControlsComponent implements OnDestroy { this.subs.push(this.scriptDataService.invoke('harvest', [ {name: '-g', value: null}, {name: '-a', value: contentSource.oaiSource}, - {name: '-i', value: contentSource.oaiSetId}, + {name: '-i', value: new ContentSourceSetSerializer().Serialize(contentSource.oaiSetId)}, ], []).pipe( getFirstCompletedRemoteData(), tap((rd) => { @@ -124,14 +125,15 @@ export class CollectionSourceControlsComponent implements OnDestroy { importNow() { this.subs.push(this.scriptDataService.invoke('harvest', [ {name: '-r', value: null}, - {name: '-e', value: 'dspacedemo+admin@gmail.com'}, {name: '-c', value: this.collection.uuid}, ], []) .pipe( getFirstCompletedRemoteData(), tap((rd) => { if (rd.hasFailed) { - this.notificationsService.error(this.translateService.get('collection.source.controls.test.submit.error')); + this.notificationsService.error(this.translateService.get('collection.source.controls.import.submit.error')); + } else { + this.notificationsService.success(this.translateService.get('collection.source.controls.import.submit.success')); } }), filter((rd) => rd.hasSucceeded && hasValue(rd.payload)), @@ -164,7 +166,43 @@ export class CollectionSourceControlsComponent implements OnDestroy { * Reset and reimport the current collection */ resetAndReimport() { - // TODO implement when a single option is present + 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')); + } 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')); + } + 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); + } + } + )); } ngOnDestroy(): void { diff --git a/src/app/core/shared/content-source-set-serializer.spec.ts b/src/app/core/shared/content-source-set-serializer.spec.ts new file mode 100644 index 0000000000..2203481250 --- /dev/null +++ b/src/app/core/shared/content-source-set-serializer.spec.ts @@ -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'); + }); + }); +}); diff --git a/src/app/core/shared/content-source-set-serializer.ts b/src/app/core/shared/content-source-set-serializer.ts new file mode 100644 index 0000000000..ec0baec5a6 --- /dev/null +++ b/src/app/core/shared/content-source-set-serializer.ts @@ -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; + } +} diff --git a/src/app/core/shared/content-source.model.ts b/src/app/core/shared/content-source.model.ts index c98901219d..cb78c85a7c 100644 --- a/src/app/core/shared/content-source.model.ts +++ b/src/app/core/shared/content-source.model.ts @@ -1,4 +1,4 @@ -import { autoserializeAs, deserializeAs, deserialize } from 'cerialize'; +import { autoserializeAs, deserializeAs, deserialize, serializeAs } from 'cerialize'; import { HALLink } from './hal-link.model'; import { MetadataConfig } from './metadata-config.model'; import { CacheableObject } from '../cache/object-cache.reducer'; @@ -6,6 +6,8 @@ import { typedObject } from '../cache/builders/build-decorators'; import { CONTENT_SOURCE } from './content-source.resource-type'; import { excludeFromEquals } from '../utilities/equals.decorators'; import { ResourceType } from './resource-type'; +import { IDToUUIDSerializer } from '../cache/id-to-uuid-serializer'; +import { ContentSourceSetSerializer } from './content-source-set-serializer'; /** * The type of content harvesting used @@ -49,7 +51,8 @@ export class ContentSource extends CacheableObject { /** * OAI Specific set ID */ - @autoserializeAs('oai_set_id') + @deserializeAs(new ContentSourceSetSerializer(), 'oai_set_id') + @serializeAs(new ContentSourceSetSerializer(), 'oai_set_id') oaiSetId: string; /** @@ -70,15 +73,30 @@ export class ContentSource extends CacheableObject { */ 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 */ diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 8d89561d68..052f0ecc2b 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -891,10 +891,15 @@ "collection.source.controls.import.submit": "Import now", "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.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", From 72d1235b2c6b935601ef7a9287ed227734d94edf Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Tue, 12 Oct 2021 09:50:56 +0200 Subject: [PATCH 4/4] 83707: Add spinners to in progress buttons --- .../collection-source-controls.component.html | 23 +++++++++++++++---- .../collection-source-controls.component.scss | 3 +++ .../collection-source-controls.component.ts | 20 +++++++++++++++- src/app/core/shared/content-source.model.ts | 3 +-- src/assets/i18n/en.json5 | 3 +++ 5 files changed, 45 insertions(+), 7 deletions(-) create mode 100644 src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.scss diff --git a/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.html b/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.html index 7a0f07adda..7dc93e8adf 100644 --- a/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.html +++ b/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.html @@ -18,21 +18,36 @@ {{contentSource?.lastHarvested ? contentSource?.lastHarvested : 'collection.source.controls.harvest.no-information'|translate }} - - + - + + diff --git a/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.scss b/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.scss new file mode 100644 index 0000000000..98f634e66b --- /dev/null +++ b/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.scss @@ -0,0 +1,3 @@ +.spinner-button { + margin-bottom: calc((var(--bs-line-height-base) * 1rem - var(--bs-font-size-base)) / 2); +} \ No newline at end of file diff --git a/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.ts b/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.ts index ca6e1e9cd8..abc5fe3083 100644 --- a/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.ts +++ b/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.ts @@ -22,12 +22,14 @@ 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 { @@ -50,6 +52,10 @@ export class CollectionSourceControlsComponent implements OnDestroy { contentSource$: Observable; 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, @@ -75,6 +81,7 @@ export class CollectionSourceControlsComponent implements OnDestroy { * @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}, @@ -85,6 +92,7 @@ export class CollectionSourceControlsComponent implements OnDestroy { 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. @@ -104,6 +112,7 @@ export class CollectionSourceControlsComponent implements OnDestroy { } 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) => { @@ -114,6 +123,7 @@ export class CollectionSourceControlsComponent implements OnDestroy { this.notificationsService.info(this.translateService.get('collection.source.controls.test.completed'), output); }); }); + this.testConfigRunning$.next(false); } } )); @@ -123,6 +133,7 @@ export class CollectionSourceControlsComponent implements OnDestroy { * 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}, @@ -132,6 +143,7 @@ export class CollectionSourceControlsComponent implements OnDestroy { 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')); } @@ -153,10 +165,12 @@ export class CollectionSourceControlsComponent implements OnDestroy { } 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.requestService.setStaleByHrefSubstring(this.collection._links.self.href); + this.importRunning$.next(false); } } )); @@ -166,6 +180,7 @@ export class CollectionSourceControlsComponent implements OnDestroy { * 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}, @@ -175,6 +190,7 @@ export class CollectionSourceControlsComponent implements OnDestroy { 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')); } @@ -196,10 +212,12 @@ export class CollectionSourceControlsComponent implements OnDestroy { } 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); } } )); diff --git a/src/app/core/shared/content-source.model.ts b/src/app/core/shared/content-source.model.ts index cb78c85a7c..40cf43ad0c 100644 --- a/src/app/core/shared/content-source.model.ts +++ b/src/app/core/shared/content-source.model.ts @@ -1,4 +1,4 @@ -import { autoserializeAs, deserializeAs, deserialize, serializeAs } from 'cerialize'; +import { autoserializeAs, deserialize, deserializeAs, serializeAs } from 'cerialize'; import { HALLink } from './hal-link.model'; import { MetadataConfig } from './metadata-config.model'; import { CacheableObject } from '../cache/object-cache.reducer'; @@ -6,7 +6,6 @@ import { typedObject } from '../cache/builders/build-decorators'; import { CONTENT_SOURCE } from './content-source.resource-type'; import { excludeFromEquals } from '../utilities/equals.decorators'; import { ResourceType } from './resource-type'; -import { IDToUUIDSerializer } from '../cache/id-to-uuid-serializer'; import { ContentSourceSetSerializer } from './content-source-set-serializer'; /** diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 052f0ecc2b..ebf849f1a7 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -886,9 +886,11 @@ "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", @@ -896,6 +898,7 @@ "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:",