64503: Content Source fetched from API

This commit is contained in:
Kristof De Langhe
2019-08-20 16:25:40 +02:00
parent 2d3dfde6bb
commit 764f532b99
10 changed files with 245 additions and 145 deletions

View File

@@ -101,17 +101,17 @@
"collection.edit.tabs.roles.title": "Collection Edit - Roles", "collection.edit.tabs.roles.title": "Collection Edit - Roles",
"collection.edit.tabs.source.external": "This collection harvests its content from an external source", "collection.edit.tabs.source.external": "This collection harvests its content from an external source",
"collection.edit.tabs.source.form.errors.provider.required": "You must provide a set id of the target collection.", "collection.edit.tabs.source.form.errors.provider.required": "You must provide a set id of the target collection.",
"collection.edit.tabs.source.form.format": "Metadata Format", "collection.edit.tabs.source.form.harvestType": "Content being harvested",
"collection.edit.tabs.source.form.harvest": "Content being harvested",
"collection.edit.tabs.source.form.head": "Configure an external source", "collection.edit.tabs.source.form.head": "Configure an external source",
"collection.edit.tabs.source.form.options.format.dc": "Simple Dublin Core", "collection.edit.tabs.source.form.metadataConfigId": "Metadata Format",
"collection.edit.tabs.source.form.options.format.dim": "DSpace Intermediate Metadata", "collection.edit.tabs.source.form.oaiSetId": "OAI specific set id",
"collection.edit.tabs.source.form.options.format.qdc": "Qualified Dublin Core", "collection.edit.tabs.source.form.oaiSource": "OAI Provider",
"collection.edit.tabs.source.form.options.harvest.1": "Harvest metadata only", "collection.edit.tabs.source.form.options.harvestType.METADATA_AND_BITSTREAMS": "Harvest metadata and bitstreams (requires ORE support)",
"collection.edit.tabs.source.form.options.harvest.2": "Harvest metadata and references to bitstreams (requires ORE support)", "collection.edit.tabs.source.form.options.harvestType.METADATA_AND_REF": "Harvest metadata and references to bitstreams (requires ORE support)",
"collection.edit.tabs.source.form.options.harvest.3": "Harvest metadata and bitstreams (requires ORE support)", "collection.edit.tabs.source.form.options.harvestType.METADATA_ONLY": "Harvest metadata only",
"collection.edit.tabs.source.form.provider": "OAI Provider", "collection.edit.tabs.source.form.options.metadataConfigId.dc": "Simple Dublin Core",
"collection.edit.tabs.source.form.set": "OAI specific set id", "collection.edit.tabs.source.form.options.metadataConfigId.dim": "DSpace Intermediate Metadata",
"collection.edit.tabs.source.form.options.metadataConfigId.qdc": "Qualified Dublin Core",
"collection.edit.tabs.source.head": "Content Source", "collection.edit.tabs.source.head": "Content Source",
"collection.edit.tabs.source.notifications.discarded.content": "Your changes were discarded. To reinstate your changes click the 'Undo' button", "collection.edit.tabs.source.notifications.discarded.content": "Your changes were discarded. To reinstate your changes click the 'Undo' button",
"collection.edit.tabs.source.notifications.discarded.title": "Changed discarded", "collection.edit.tabs.source.notifications.discarded.title": "Changed discarded",

View File

@@ -1,55 +1,57 @@
<div class="container-fluid"> <ng-container *ngIf="contentSource">
<div class="d-inline-block float-right"> <div class="container-fluid">
<button class=" btn btn-danger" *ngIf="!(isReinstatable() | async)" <div class="d-inline-block float-right">
[disabled]="!(hasChanges() | async)" <button class=" btn btn-danger" *ngIf="!(isReinstatable() | async)"
(click)="discard()"><i [disabled]="!(hasChanges() | async)"
class="fas fa-times"></i> (click)="discard()"><i
<span class="d-none d-sm-inline">&nbsp;{{"item.edit.metadata.discard-button" | translate}}</span> class="fas fa-times"></i>
</button> <span class="d-none d-sm-inline">&nbsp;{{"item.edit.metadata.discard-button" | translate}}</span>
<button class="btn btn-warning" *ngIf="isReinstatable() | async" </button>
(click)="reinstate()"><i <button class="btn btn-warning" *ngIf="isReinstatable() | async"
class="fas fa-undo-alt"></i> (click)="reinstate()"><i
<span class="d-none d-sm-inline">&nbsp;{{"item.edit.metadata.reinstate-button" | translate}}</span> class="fas fa-undo-alt"></i>
</button> <span class="d-none d-sm-inline">&nbsp;{{"item.edit.metadata.reinstate-button" | translate}}</span>
<button class="btn btn-primary" [disabled]="!(hasChanges() | async) || !isValid()" </button>
(click)="onSubmit()"><i <button class="btn btn-primary" [disabled]="!(hasChanges() | async) || !isValid()"
class="fas fa-save"></i> (click)="onSubmit()"><i
<span class="d-none d-sm-inline">&nbsp;{{"item.edit.metadata.save-button" | translate}}</span> class="fas fa-save"></i>
</button> <span class="d-none d-sm-inline">&nbsp;{{"item.edit.metadata.save-button" | translate}}</span>
</button>
</div>
<h4>{{ 'collection.edit.tabs.source.head' | translate }}</h4>
<div class="form-check mb-4">
<input type="checkbox" class="form-check-input" id="externalSourceCheck" [checked]="(contentSource.harvestType !== harvestTypeNone)" (change)="changeExternalSource()">
<label class="form-check-label" for="externalSourceCheck">{{ 'collection.edit.tabs.source.external' | translate }}</label>
</div>
<h4 *ngIf="(contentSource.harvestType !== harvestTypeNone)">{{ 'collection.edit.tabs.source.form.head' | translate }}</h4>
</div> </div>
<h4>{{ 'collection.edit.tabs.source.head' | translate }}</h4> <ds-form *ngIf="formGroup && (contentSource.harvestType !== harvestTypeNone)"
<div class="form-check mb-4"> [formId]="'collection-source-form-id'"
<input type="checkbox" class="form-check-input" id="externalSourceCheck" [checked]="contentSource.enabled" (change)="changeExternalSource()"> [formGroup]="formGroup"
<label class="form-check-label" for="externalSourceCheck">{{ 'collection.edit.tabs.source.external' | translate }}</label> [formModel]="formModel"
[formLayout]="formLayout"
[displaySubmit]="false"
(dfChange)="onChange($event)"
(submitForm)="onSubmit()"
(cancel)="onCancel()"></ds-form>
<div class="container-fluid" *ngIf="(contentSource.harvestType !== harvestTypeNone)">
<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">&nbsp;{{"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">&nbsp;{{"item.edit.metadata.reinstate-button" | translate}}</span>
</button>
<button class="btn btn-primary" [disabled]="!(hasChanges() | async) || !isValid()"
(click)="onSubmit()"><i
class="fas fa-save"></i>
<span class="d-none d-sm-inline">&nbsp;{{"item.edit.metadata.save-button" | translate}}</span>
</button>
</div>
</div> </div>
<h4 *ngIf="contentSource.enabled">{{ 'collection.edit.tabs.source.form.head' | translate }}</h4> </ng-container>
</div>
<ds-form *ngIf="formGroup && contentSource.enabled"
[formId]="'collection-source-form-id'"
[formGroup]="formGroup"
[formModel]="formModel"
[formLayout]="formLayout"
[displaySubmit]="false"
(dfChange)="onChange($event)"
(submitForm)="onSubmit()"
(cancel)="onCancel()"></ds-form>
<div class="container-fluid" *ngIf="contentSource.enabled">
<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">&nbsp;{{"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">&nbsp;{{"item.edit.metadata.reinstate-button" | translate}}</span>
</button>
<button class="btn btn-primary" [disabled]="!(hasChanges() | async) || !isValid()"
(click)="onSubmit()"><i
class="fas fa-save"></i>
<span class="d-none d-sm-inline">&nbsp;{{"item.edit.metadata.save-button" | translate}}</span>
</button>
</div>
</div>

View File

@@ -1,8 +1,13 @@
import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; import { Component, Inject, OnDestroy, OnInit } from '@angular/core';
import { AbstractTrackableComponent } from '../../../shared/trackable/abstract-trackable.component'; import { AbstractTrackableComponent } from '../../../shared/trackable/abstract-trackable.component';
import { import {
DynamicFormControlModel, DynamicFormGroupModel, DynamicFormLayout, DynamicFormService, DynamicFormControlModel,
DynamicInputModel, DynamicOptionControlModel, DynamicRadioGroupModel, DynamicFormGroupModel,
DynamicFormLayout,
DynamicFormService,
DynamicInputModel,
DynamicOptionControlModel,
DynamicRadioGroupModel,
DynamicSelectModel, DynamicSelectModel,
DynamicTextAreaModel DynamicTextAreaModel
} from '@ng-dynamic-forms/core'; } from '@ng-dynamic-forms/core';
@@ -12,16 +17,18 @@ import { ObjectUpdatesService } from '../../../core/data/object-updates/object-u
import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { NotificationsService } from '../../../shared/notifications/notifications.service';
import { FormGroup } from '@angular/forms'; import { FormGroup } from '@angular/forms';
import { hasValue, isNotEmpty } from '../../../shared/empty.util'; import { hasValue, isNotEmpty } from '../../../shared/empty.util';
import { ContentSource } from '../../../core/shared/content-source.model'; import { ContentSource, ContentSourceHarvestType } from '../../../core/shared/content-source.model';
import { Observable } from 'rxjs/internal/Observable'; import { Observable } from 'rxjs/internal/Observable';
import { RemoteData } from '../../../core/data/remote-data'; import { RemoteData } from '../../../core/data/remote-data';
import { Collection } from '../../../core/shared/collection.model'; import { Collection } from '../../../core/shared/collection.model';
import { first, map } from 'rxjs/operators'; import { first, map, switchMap, take } from 'rxjs/operators';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { FieldUpdate, FieldUpdates } from '../../../core/data/object-updates/object-updates.reducer'; import { FieldUpdate, FieldUpdates } from '../../../core/data/object-updates/object-updates.reducer';
import { Subscription } from 'rxjs/internal/Subscription'; import { Subscription } from 'rxjs/internal/Subscription';
import { cloneDeep } from 'lodash'; import { cloneDeep } from 'lodash';
import { GLOBAL_CONFIG, GlobalConfig } from '../../../../config'; import { GLOBAL_CONFIG, GlobalConfig } from '../../../../config';
import { CollectionDataService } from '../../../core/data/collection-data.service';
import { getSucceededRemoteData } from '../../../core/shared/operators';
/** /**
* Component for managing the content source of the collection * Component for managing the content source of the collection
@@ -64,9 +71,9 @@ export class CollectionSourceComponent extends AbstractTrackableComponent implem
/** /**
* The Dynamic Input Model for the OAI Provider * The Dynamic Input Model for the OAI Provider
*/ */
providerModel = new DynamicInputModel({ oaiSourceModel = new DynamicInputModel({
id: 'provider', id: 'oaiSource',
name: 'provider', name: 'oaiSource',
required: true, required: true,
validators: { validators: {
required: null required: null
@@ -79,17 +86,17 @@ export class CollectionSourceComponent extends AbstractTrackableComponent implem
/** /**
* The Dynamic Input Model for the OAI Set * The Dynamic Input Model for the OAI Set
*/ */
setModel = new DynamicInputModel({ oaiSetIdModel = new DynamicInputModel({
id: 'set', id: 'oaiSetId',
name: 'set' name: 'oaiSetId'
}); });
/** /**
* The Dynamic Input Model for the Metadata Format used * The Dynamic Input Model for the Metadata Format used
*/ */
formatModel = new DynamicSelectModel({ metadataConfigIdModel = new DynamicSelectModel({
id: 'format', id: 'metadataConfigId',
name: 'format', name: 'metadataConfigId',
options: [ options: [
{ {
value: 'dc' value: 'dc'
@@ -100,24 +107,25 @@ export class CollectionSourceComponent extends AbstractTrackableComponent implem
{ {
value: 'dim' value: 'dim'
} }
] ],
value: 'dc'
}); });
/** /**
* The Dynamic Input Model for the type of harvesting * The Dynamic Input Model for the type of harvesting
*/ */
harvestModel = new DynamicRadioGroupModel<number>({ harvestTypeModel = new DynamicRadioGroupModel<string>({
id: 'harvest', id: 'harvestType',
name: 'harvest', name: 'harvestType',
options: [ options: [
{ {
value: 1 value: ContentSourceHarvestType.Metadata
}, },
{ {
value: 2 value: ContentSourceHarvestType.MetadataAndRef
}, },
{ {
value: 3 value: ContentSourceHarvestType.MetadataAndBitstreams
} }
] ]
}); });
@@ -125,7 +133,7 @@ export class CollectionSourceComponent extends AbstractTrackableComponent implem
/** /**
* All input models in a simple array for easier iterations * All input models in a simple array for easier iterations
*/ */
inputModels = [this.providerModel, this.setModel, this.formatModel, this.harvestModel]; inputModels = [this.oaiSourceModel, this.oaiSetIdModel, this.metadataConfigIdModel, this.harvestTypeModel];
/** /**
* The dynamic form fields used for editing the content source of a collection * The dynamic form fields used for editing the content source of a collection
@@ -133,22 +141,22 @@ export class CollectionSourceComponent extends AbstractTrackableComponent implem
*/ */
formModel: DynamicFormControlModel[] = [ formModel: DynamicFormControlModel[] = [
new DynamicFormGroupModel({ new DynamicFormGroupModel({
id: 'providerContainer', id: 'oaiSourceContainer',
group: [ group: [
this.providerModel this.oaiSourceModel
] ]
}), }),
new DynamicFormGroupModel({ new DynamicFormGroupModel({
id: 'setContainer', id: 'oaiSetContainer',
group: [ group: [
this.setModel, this.oaiSetIdModel,
this.formatModel this.metadataConfigIdModel
] ]
}), }),
new DynamicFormGroupModel({ new DynamicFormGroupModel({
id: 'harvestContainer', id: 'harvestTypeContainer',
group: [ group: [
this.harvestModel this.harvestTypeModel
] ]
}) })
]; ];
@@ -157,38 +165,38 @@ export class CollectionSourceComponent extends AbstractTrackableComponent implem
* Layout used for structuring the form inputs * Layout used for structuring the form inputs
*/ */
formLayout: DynamicFormLayout = { formLayout: DynamicFormLayout = {
provider: { oaiSource: {
grid: { grid: {
host: 'col-12 d-inline-block' host: 'col-12 d-inline-block'
} }
}, },
set: { oaiSetId: {
grid: { grid: {
host: 'col col-sm-6 d-inline-block' host: 'col col-sm-6 d-inline-block'
} }
}, },
format: { metadataConfigId: {
grid: { grid: {
host: 'col col-sm-6 d-inline-block' host: 'col col-sm-6 d-inline-block'
} }
}, },
harvest: { harvestType: {
grid: { grid: {
host: 'col-12', host: 'col-12',
option: 'btn-outline-secondary' option: 'btn-outline-secondary'
} }
}, },
setContainer: { oaiSetContainer: {
grid: { grid: {
host: 'row' host: 'row'
} }
}, },
providerContainer: { oaiSourceContainer: {
grid: { grid: {
host: 'row' host: 'row'
} }
}, },
harvestContainer: { harvestTypeContainer: {
grid: { grid: {
host: 'row' host: 'row'
} }
@@ -205,6 +213,8 @@ export class CollectionSourceComponent extends AbstractTrackableComponent implem
*/ */
updateSub: Subscription; updateSub: Subscription;
harvestTypeNone = ContentSourceHarvestType.None;
public constructor(public objectUpdatesService: ObjectUpdatesService, public constructor(public objectUpdatesService: ObjectUpdatesService,
public notificationsService: NotificationsService, public notificationsService: NotificationsService,
protected location: Location, protected location: Location,
@@ -212,7 +222,8 @@ export class CollectionSourceComponent extends AbstractTrackableComponent implem
protected translate: TranslateService, protected translate: TranslateService,
protected route: ActivatedRoute, protected route: ActivatedRoute,
protected router: Router, protected router: Router,
@Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig,) { @Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig,
protected collectionService: CollectionDataService) {
super(objectUpdatesService, notificationsService, translate); super(objectUpdatesService, notificationsService, translate);
} }
@@ -229,16 +240,21 @@ export class CollectionSourceComponent extends AbstractTrackableComponent implem
this.formGroup = this.formService.createFormGroup(this.formModel); this.formGroup = this.formService.createFormGroup(this.formModel);
this.collectionRD$ = this.route.parent.data.pipe(first(), map((data) => data.dso)); this.collectionRD$ = this.route.parent.data.pipe(first(), map((data) => data.dso));
/* Create a new ContentSource object - TODO: Update to be fetched from the collection */ this.collectionRD$.pipe(
this.contentSource = new ContentSource(); getSucceededRemoteData(),
map((col) => col.payload.uuid),
switchMap((uuid) => this.collectionService.getContentSource(uuid)),
take(1)
).subscribe((contentSource: ContentSource) => {
this.contentSource = contentSource;
this.initializeOriginalContentSource();
});
this.updateFieldTranslations(); this.updateFieldTranslations();
this.translate.onLangChange this.translate.onLangChange
.subscribe(() => { .subscribe(() => {
this.updateFieldTranslations(); this.updateFieldTranslations();
}); });
this.initializeOriginalContentSource();
} }
/** /**
@@ -254,15 +270,15 @@ export class CollectionSourceComponent extends AbstractTrackableComponent implem
if (update) { if (update) {
const field = update.field as ContentSource; const field = update.field as ContentSource;
this.formGroup.patchValue({ this.formGroup.patchValue({
providerContainer: { oaiSourceContainer: {
provider: field.provider oaiSource: field.oaiSource
}, },
setContainer: { oaiSetContainer: {
set: field.set, oaiSetId: field.oaiSetId,
format: field.format metadataConfigId: field.metadataConfigId
}, },
harvestContainer: { harvestTypeContainer: {
harvest: field.harvest harvestType: field.harvestType
} }
}); });
this.contentSource = cloneDeep(field); this.contentSource = cloneDeep(field);
@@ -307,7 +323,7 @@ export class CollectionSourceComponent extends AbstractTrackableComponent implem
* @param event * @param event
*/ */
onChange(event) { onChange(event) {
this.updateContentSourceField(event.model); this.updateContentSourceField(event.model, true);
this.saveFieldUpdate(); this.saveFieldUpdate();
} }
@@ -331,24 +347,28 @@ export class CollectionSourceComponent extends AbstractTrackableComponent implem
* Is the current form valid to be submitted ? * Is the current form valid to be submitted ?
*/ */
isValid(): boolean { isValid(): boolean {
return !this.contentSource.enabled || this.formGroup.valid; return (this.contentSource.harvestType === ContentSourceHarvestType.None) || this.formGroup.valid;
} }
/** /**
* Switch the external source on or off and fire a field update * Switch the external source on or off and fire a field update
*/ */
changeExternalSource() { changeExternalSource() {
this.contentSource.enabled = !this.contentSource.enabled; if (this.contentSource.harvestType === ContentSourceHarvestType.None) {
this.updateContentSource(); this.contentSource.harvestType = ContentSourceHarvestType.Metadata;
} else {
this.contentSource.harvestType = ContentSourceHarvestType.None;
}
this.updateContentSource(false);
} }
/** /**
* Loop over all inputs and update the Content Source with their value * Loop over all inputs and update the Content Source with their value
*/ */
updateContentSource() { updateContentSource(updateHarvestType: boolean) {
this.inputModels.forEach( this.inputModels.forEach(
(fieldModel: DynamicInputModel) => { (fieldModel: DynamicInputModel) => {
this.updateContentSourceField(fieldModel) this.updateContentSourceField(fieldModel, updateHarvestType)
} }
); );
this.saveFieldUpdate(); this.saveFieldUpdate();
@@ -358,8 +378,8 @@ export class CollectionSourceComponent extends AbstractTrackableComponent implem
* Update the Content Source with the value from a DynamicInputModel * Update the Content Source with the value from a DynamicInputModel
* @param fieldModel * @param fieldModel
*/ */
updateContentSourceField(fieldModel: DynamicInputModel) { updateContentSourceField(fieldModel: DynamicInputModel, updateHarvestType: boolean) {
if (hasValue(fieldModel.value)) { if (hasValue(fieldModel.value) && !(fieldModel.id === this.harvestTypeModel.id && !updateHarvestType)) {
this.contentSource[fieldModel.id] = fieldModel.value; this.contentSource[fieldModel.id] = fieldModel.value;
} }
} }

View File

@@ -14,6 +14,7 @@ import { DSpaceObject } from '../shared/dspace-object.model';
import { NormalizedAuthStatus } from '../auth/models/normalized-auth-status.model'; import { NormalizedAuthStatus } from '../auth/models/normalized-auth-status.model';
import { MetadataSchema } from '../metadata/metadata-schema.model'; import { MetadataSchema } from '../metadata/metadata-schema.model';
import { MetadataField } from '../metadata/metadata-field.model'; import { MetadataField } from '../metadata/metadata-field.model';
import { ContentSource } from '../shared/content-source.model';
/* tslint:disable:max-classes-per-file */ /* tslint:disable:max-classes-per-file */
export class RestResponse { export class RestResponse {
@@ -288,4 +289,17 @@ export class FilteredDiscoveryQueryResponse extends RestResponse {
super(true, statusCode, statusText); super(true, statusCode, statusText);
} }
} }
/**
* A successful response containing exactly one MetadataSchema
*/
export class ContentSourceSuccessResponse extends RestResponse {
constructor(
public contentsource: ContentSource,
public statusCode: number,
public statusText: string,
) {
super(true, statusCode, statusText);
}
}
/* tslint:enable:max-classes-per-file */ /* tslint:enable:max-classes-per-file */

View File

@@ -117,6 +117,7 @@ import { MetadatafieldParsingService } from './data/metadatafield-parsing.servic
import { NormalizedSubmissionUploadsModel } from './config/models/normalized-config-submission-uploads.model'; import { NormalizedSubmissionUploadsModel } from './config/models/normalized-config-submission-uploads.model';
import { NormalizedBrowseEntry } from './shared/normalized-browse-entry.model'; import { NormalizedBrowseEntry } from './shared/normalized-browse-entry.model';
import { BrowseDefinition } from './shared/browse-definition.model'; import { BrowseDefinition } from './shared/browse-definition.model';
import { ContentSourceResponseParsingService } from './data/content-source-response-parsing.service';
const IMPORTS = [ const IMPORTS = [
CommonModule, CommonModule,
@@ -203,6 +204,7 @@ const PROVIDERS = [
TaskResponseParsingService, TaskResponseParsingService,
ClaimedTaskDataService, ClaimedTaskDataService,
PoolTaskDataService, PoolTaskDataService,
ContentSourceResponseParsingService,
// register AuthInterceptor as HttpInterceptor // register AuthInterceptor as HttpInterceptor
{ {
provide: HTTP_INTERCEPTORS, provide: HTTP_INTERCEPTORS,

View File

@@ -1,6 +1,6 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { filter, map, take } from 'rxjs/operators'; import { filter, map, take, tap } from 'rxjs/operators';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
@@ -16,9 +16,12 @@ import { HttpClient } from '@angular/common/http';
import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service'; import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
import { DSOChangeAnalyzer } from './dso-change-analyzer.service'; import { DSOChangeAnalyzer } from './dso-change-analyzer.service';
import { Observable } from 'rxjs/internal/Observable'; import { Observable } from 'rxjs/internal/Observable';
import { FindAllOptions } from './request.models'; import { ContentSourceRequest, FindAllOptions, RestRequest } from './request.models';
import { RemoteData } from './remote-data'; import { RemoteData } from './remote-data';
import { PaginatedList } from './paginated-list'; import { PaginatedList } from './paginated-list';
import { ContentSource } from '../shared/content-source.model';
import { configureRequest, filterSuccessfulResponses, getRequestFromRequestHref } from '../shared/operators';
import { ContentSourceSuccessResponse } from '../cache/response.models';
@Injectable() @Injectable()
export class CollectionDataService extends ComColDataService<Collection> { export class CollectionDataService extends ComColDataService<Collection> {
@@ -57,4 +60,21 @@ export class CollectionDataService extends ComColDataService<Collection> {
map((collections: RemoteData<PaginatedList<Collection>>) => collections.payload.totalElements > 0) map((collections: RemoteData<PaginatedList<Collection>>) => collections.payload.totalElements > 0)
); );
} }
getHarvesterEndpoint(collectionId: string): Observable<string> {
return this.halService.getEndpoint(this.linkPath).pipe(
map((href: string) => `${href}/${collectionId}/harvester`)
);
}
getContentSource(collectionId: string): Observable<ContentSource> {
return this.getHarvesterEndpoint(collectionId).pipe(
map((href: string) => new ContentSourceRequest(this.requestService.generateRequestId(), href)),
configureRequest(this.requestService),
map((request: RestRequest) => request.href),
getRequestFromRequestHref(this.requestService),
filterSuccessfulResponses(),
map((response: ContentSourceSuccessResponse) => response.contentsource)
);
}
} }

View File

@@ -0,0 +1,19 @@
import { Injectable } from '@angular/core';
import { ResponseParsingService } from './parsing.service';
import { RestRequest } from './request.models';
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
import { ContentSourceSuccessResponse, RestResponse } from '../cache/response.models';
import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer';
import { ContentSource } from '../shared/content-source.model';
@Injectable()
export class ContentSourceResponseParsingService implements ResponseParsingService {
parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse {
const payload = data.payload;
const deserialized = new DSpaceRESTv2Serializer(ContentSource).deserialize(payload);
return new ContentSourceSuccessResponse(deserialized, data.statusCode, data.statusText);
}
}

View File

@@ -93,14 +93,16 @@ export class ObjectUpdatesService {
const objectUpdates = this.getObjectEntry(url); const objectUpdates = this.getObjectEntry(url);
return objectUpdates.pipe(map((objectEntry) => { return objectUpdates.pipe(map((objectEntry) => {
const fieldUpdates: FieldUpdates = {}; const fieldUpdates: FieldUpdates = {};
Object.keys(objectEntry.fieldStates).forEach((uuid) => { if (hasValue(objectEntry)) {
let fieldUpdate = objectEntry.fieldUpdates[uuid]; Object.keys(objectEntry.fieldStates).forEach((uuid) => {
if (isEmpty(fieldUpdate)) { let fieldUpdate = objectEntry.fieldUpdates[uuid];
const identifiable = initialFields.find((object: Identifiable) => object.uuid === uuid); if (isEmpty(fieldUpdate)) {
fieldUpdate = { field: identifiable, changeType: undefined }; const identifiable = initialFields.find((object: Identifiable) => object.uuid === uuid);
} fieldUpdate = {field: identifiable, changeType: undefined};
fieldUpdates[uuid] = fieldUpdate; }
}); fieldUpdates[uuid] = fieldUpdate;
});
}
return fieldUpdates; return fieldUpdates;
})) }))
} }

View File

@@ -18,6 +18,7 @@ import { MetadataschemaParsingService } from './metadataschema-parsing.service';
import { MetadatafieldParsingService } from './metadatafield-parsing.service'; import { MetadatafieldParsingService } from './metadatafield-parsing.service';
import { URLCombiner } from '../url-combiner/url-combiner'; import { URLCombiner } from '../url-combiner/url-combiner';
import { TaskResponseParsingService } from '../tasks/task-response-parsing.service'; import { TaskResponseParsingService } from '../tasks/task-response-parsing.service';
import { ContentSourceResponseParsingService } from './content-source-response-parsing.service';
/* tslint:disable:max-classes-per-file */ /* tslint:disable:max-classes-per-file */
@@ -358,6 +359,16 @@ export class CreateRequest extends PostRequest {
} }
} }
export class ContentSourceRequest extends GetRequest {
constructor(uuid: string, href: string) {
super(uuid, href);
}
getResponseParser(): GenericConstructor<ResponseParsingService> {
return ContentSourceResponseParsingService;
}
}
/** /**
* Request to delete an object based on its identifier * Request to delete an object based on its identifier
*/ */

View File

@@ -1,41 +1,51 @@
import { v4 as uuid } from 'uuid'; import { autoserialize, autoserializeAs } from 'cerialize';
export enum ContentSourceHarvestType {
None = 'NONE',
Metadata = 'METADATA_ONLY',
MetadataAndRef = 'METADATA_AND_REF',
MetadataAndBitstreams = 'METADATA_AND_BITSTREAMS'
}
/** /**
* A model class that holds information about the Content Source of a Collection * A model class that holds information about the Content Source of a Collection
*/ */
export class ContentSource { export class ContentSource {
/** /**
* Unique identifier * Unique identifier, this is necessary to store the ContentSource in FieldUpdates
* Because the ContentSource coming from the REST API doesn't have a UUID, we're using the selflink
*/ */
@autoserializeAs('self')
uuid: string; uuid: string;
/** /**
* Does this collection harvest its content from an external source ? * OAI Provider / Source
*/ */
enabled = false; @autoserializeAs('oai_source')
oaiSource: string;
/**
* OAI Provider
*/
provider: string;
/** /**
* OAI Specific set ID * OAI Specific set ID
*/ */
set: string; @autoserializeAs('oai_set_id')
oaiSetId: string;
/** /**
* Metadata Format * The ID of the metadata format used
*/ */
format = 'dc'; @autoserializeAs('metadata_config_id')
metadataConfigId = 'dc';
/** /**
* Type of content being harvested * Type of content being harvested
* Defaults to 'NONE', meaning the collection doesn't harvest its content from an external source
*/ */
harvest = 3; @autoserializeAs('harvest_type')
harvestType = ContentSourceHarvestType.None;
constructor() { /**
// TODO: Remove this once the Content Source is fetched from the REST API and a custom generated UUID is not necessary anymore * The REST link to itself
this.uuid = uuid(); */
} @autoserialize
self: string;
} }