71806: Use server config for curation tasks

This commit is contained in:
Yana De Pauw
2020-07-30 14:31:59 +02:00
parent 3e8ff05d2d
commit 76ad72f536
13 changed files with 260 additions and 51 deletions

View File

@@ -160,6 +160,8 @@ import { SubmissionCcLicenseDataService } from './submission/submission-cc-licen
import { SubmissionCcLicence } from './submission/models/submission-cc-license.model';
import { SubmissionCcLicenceUrl } from './submission/models/submission-cc-license-url.model';
import { SubmissionCcLicenseUrlDataService } from './submission/submission-cc-license-url-data.service';
import { ConfigurationDataService } from './data/configuration-data.service';
import { ConfigurationProperty } from './shared/configuration-property.model';
/**
* When not in production, endpoint responses can be mocked for testing purposes
@@ -245,6 +247,7 @@ const PROVIDERS = [
UploaderService,
FileService,
DSpaceObjectDataService,
ConfigurationDataService,
DSOChangeAnalyzer,
DefaultChangeAnalyzer,
ArrayMoveChangeAnalyzer,
@@ -350,7 +353,8 @@ export const models =
TemplateItem,
Feature,
Authorization,
Registration
Registration,
ConfigurationProperty
];
@NgModule({

View File

@@ -0,0 +1,87 @@
import { cold, getTestScheduler } from 'jasmine-marbles';
import { TestScheduler } from 'rxjs/testing';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { HALEndpointService } from '../shared/hal-endpoint.service';
import { FindByIDRequest } from './request.models';
import { RequestService } from './request.service';
import { ObjectCacheService } from '../cache/object-cache.service';
import { NotificationsService } from '../../shared/notifications/notifications.service';
import { HttpClient } from '@angular/common/http';
import { ConfigurationDataService } from './configuration-data.service';
import { ConfigurationProperty } from '../shared/configuration-property.model';
describe('ConfigurationDataService', () => {
let scheduler: TestScheduler;
let service: ConfigurationDataService;
let halService: HALEndpointService;
let requestService: RequestService;
let rdbService: RemoteDataBuildService;
let objectCache: ObjectCacheService;
const testObject = {
uuid: 'test-property',
name: 'test-property',
values: ['value-1', 'value-2']
} as ConfigurationProperty;
const configLink = 'https://rest.api/rest/api/config/properties';
const requestURL = `https://rest.api/rest/api/config/properties/${testObject.name}`;
const requestUUID = 'test-property';
beforeEach(() => {
scheduler = getTestScheduler();
halService = jasmine.createSpyObj('halService', {
getEndpoint: cold('a', {a: configLink})
});
requestService = jasmine.createSpyObj('requestService', {
generateRequestId: requestUUID,
configure: true
});
rdbService = jasmine.createSpyObj('rdbService', {
buildSingle: cold('a', {
a: {
payload: testObject
}
})
});
objectCache = {} as ObjectCacheService;
const notificationsService = {} as NotificationsService;
const http = {} as HttpClient;
const comparator = {} as any;
service = new ConfigurationDataService(
requestService,
rdbService,
objectCache,
halService,
notificationsService,
http,
comparator
);
});
describe('findById', () => {
it('should call HALEndpointService with the path to the properties endpoint', () => {
scheduler.schedule(() => service.findByPropertyName(testObject.name));
scheduler.flush();
expect(halService.getEndpoint).toHaveBeenCalledWith('properties');
});
it('should configure the proper FindByIDRequest', () => {
scheduler.schedule(() => service.findByPropertyName(testObject.name));
scheduler.flush();
expect(requestService.configure).toHaveBeenCalledWith(new FindByIDRequest(requestUUID, requestURL, testObject.name));
});
it('should return a RemoteData<ConfigurationProperty> for the object with the given name', () => {
const result = service.findByPropertyName(testObject.name);
const expected = cold('a', {
a: {
payload: testObject
}
});
expect(result).toBeObservable(expected);
});
});
});

View File

@@ -0,0 +1,66 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs';
import { NotificationsService } from '../../shared/notifications/notifications.service';
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
import { dataService } from '../cache/builders/build-decorators';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { ObjectCacheService } from '../cache/object-cache.service';
import { CoreState } from '../core.reducers';
import { DSpaceObject } from '../shared/dspace-object.model';
import { DSPACE_OBJECT } from '../shared/dspace-object.resource-type';
import { HALEndpointService } from '../shared/hal-endpoint.service';
import { DataService } from './data.service';
import { DSOChangeAnalyzer } from './dso-change-analyzer.service';
import { RemoteData } from './remote-data';
import { RequestService } from './request.service';
import { ConfigurationProperty } from '../shared/configuration-property.model';
import { DefaultChangeAnalyzer } from './default-change-analyzer.service';
import { CONFIG_PROPERTY } from '../shared/config-property.resource-type';
/* tslint:disable:max-classes-per-file */
class DataServiceImpl extends DataService<ConfigurationProperty> {
protected linkPath = 'properties';
constructor(
protected requestService: RequestService,
protected rdbService: RemoteDataBuildService,
protected store: Store<CoreState>,
protected objectCache: ObjectCacheService,
protected halService: HALEndpointService,
protected notificationsService: NotificationsService,
protected http: HttpClient,
protected comparator: DefaultChangeAnalyzer<ConfigurationProperty>) {
super();
}
}
@Injectable()
@dataService(CONFIG_PROPERTY)
/**
* Data Service responsible for retrieving Configuration properties
*/
export class ConfigurationDataService {
protected linkPath = 'properties';
private dataService: DataServiceImpl;
constructor(
protected requestService: RequestService,
protected rdbService: RemoteDataBuildService,
protected objectCache: ObjectCacheService,
protected halService: HALEndpointService,
protected notificationsService: NotificationsService,
protected http: HttpClient,
protected comparator: DefaultChangeAnalyzer<ConfigurationProperty>) {
this.dataService = new DataServiceImpl(requestService, rdbService, null, objectCache, halService, notificationsService, http, comparator);
}
/**
* Finds a configuration property by name
* @param name
*/
findByPropertyName(name: string): Observable<RemoteData<ConfigurationProperty>> {
return this.dataService.findById(name);
}
}

View File

@@ -0,0 +1,9 @@
import { ResourceType } from './resource-type';
/**
* The resource type for ConfigurationProperty
*
* Needs to be in a separate file to prevent circular
* dependencies in webpack.
*/
export const CONFIG_PROPERTY = new ResourceType('property');

View File

@@ -0,0 +1,48 @@
import { autoserialize, autoserializeAs, deserialize } from 'cerialize';
import { typedObject } from '../cache/builders/build-decorators';
import { CacheableObject } from '../cache/object-cache.reducer';
import { excludeFromEquals } from '../utilities/equals.decorators';
import { HALLink } from './hal-link.model';
import { ResourceType } from './resource-type';
import { CONFIG_PROPERTY } from './config-property.resource-type';
/**
* Model class for a Configuration Property
*/
@typedObject
export class ConfigurationProperty implements CacheableObject {
static type = CONFIG_PROPERTY;
/**
* The object type
*/
@excludeFromEquals
@autoserialize
type: ResourceType;
/**
* The uuid of the configuration property
* The name is used as id for configuration properties
*/
@autoserializeAs(String, 'name')
uuid: string;
/**
* The name of the configuration property
*/
@autoserialize
name: string;
/**
* The values of the configuration property
*/
@autoserialize
values: string[];
/**
* The links of the configuration property
*/
@deserialize
_links: { self: HALLink };
}

View File

@@ -4,8 +4,8 @@
<div class="col-12 col-sm-6">
<label class="font-weight-bold" for="task">{{'curation.form.task-select.label' |translate }}</label>
<select id="task" formControlName="task" class="form-control">
<option *ngFor="let task of tasks" [ngValue]="task">
{{ task.label | translate }}
<option *ngFor="let task of tasks" [value]="task">
{{ 'curation-task.task.' + task + '.label' | translate }}
</option>
</select>
</div>

View File

@@ -17,6 +17,8 @@ import { NotificationsService } from '../shared/notifications/notifications.serv
import { Router } from '@angular/router';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { By } from '@angular/platform-browser';
import { ConfigurationDataService } from '../core/data/configuration-data.service';
import { ConfigurationProperty } from '../core/shared/configuration-property.model';
describe('CurationFormComponent', () => {
let comp: CurationFormComponent;
@@ -24,6 +26,7 @@ describe('CurationFormComponent', () => {
let scriptDataService: ScriptDataService;
let processDataService: ProcessDataService;
let configurationDataService: ConfigurationDataService;
let authService: AuthService;
let notificationsService;
let router;
@@ -49,6 +52,17 @@ describe('CurationFormComponent', () => {
getAuthenticatedUserFromStore: observableOf(Object.assign(new EPerson(), {email: 'test@mail'}))
});
configurationDataService = jasmine.createSpyObj('configurationDataService', {
findByPropertyName: createSuccessfulRemoteDataObject$(Object.assign(new ConfigurationProperty(), {
name: 'plugin.named.org.dspace.curate.CurationTask',
values: [
'org.dspace.ctask.general.ProfileFormats = profileformats',
'org.dspace.ctask.general.RequiredMetadata = requiredmetadata',
'org.dspace.ctask.general.MetadataValueLinkChecker = checklinks'
]
}))
});
notificationsService = new NotificationsServiceStub();
router = new RouterStub();
@@ -61,6 +75,7 @@ describe('CurationFormComponent', () => {
{provide: AuthService, useValue: authService},
{provide: NotificationsService, useValue: notificationsService},
{provide: Router, useValue: router},
{provide: ConfigurationDataService, useValue: configurationDataService},
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
}).compileComponents();

View File

@@ -1,12 +1,10 @@
import { Component, Input, OnInit } from '@angular/core';
import { ScriptDataService } from '../core/data/processes/script-data.service';
import { environment } from '../../environments/environment';
import { CurationTask } from '../../config/curation-task.interface';
import { FormControl, FormGroup } from '@angular/forms';
import { getResponseFromEntry } from '../core/shared/operators';
import { DSOSuccessResponse } from '../core/cache/response.models';
import { AuthService } from '../core/auth/auth.service';
import { filter, switchMap, take } from 'rxjs/operators';
import { filter, map, switchMap, take } from 'rxjs/operators';
import { EPerson } from '../core/eperson/models/eperson.model';
import { NotificationsService } from '../shared/notifications/notifications.service';
import { TranslateService } from '@ngx-translate/core';
@@ -15,6 +13,12 @@ import { RemoteData } from '../core/data/remote-data';
import { Router } from '@angular/router';
import { ProcessDataService } from '../core/data/processes/process-data.service';
import { Process } from '../process-page/processes/process.model';
import { ConfigurationDataService } from '../core/data/configuration-data.service';
import { ConfigurationProperty } from '../core/shared/configuration-property.model';
import { Observable } from 'rxjs';
import { find } from 'rxjs/internal/operators/find';
export const CURATION_CFG = 'plugin.named.org.dspace.curate.CurationTask';
/**
* Component responsible for rendering the Curation Task form
@@ -25,7 +29,8 @@ import { Process } from '../process-page/processes/process.model';
})
export class CurationFormComponent implements OnInit {
tasks: CurationTask[];
config: Observable<RemoteData<ConfigurationProperty>>;
tasks: string[];
form: FormGroup;
@Input()
@@ -33,6 +38,7 @@ export class CurationFormComponent implements OnInit {
constructor(
private scriptDataService: ScriptDataService,
private configurationDataService: ConfigurationDataService,
private processDataService: ProcessDataService,
private authService: AuthService,
private notificationsService: NotificationsService,
@@ -42,12 +48,19 @@ export class CurationFormComponent implements OnInit {
}
ngOnInit(): void {
this.tasks = environment.curationTasks;
this.form = new FormGroup({
task: new FormControl(this.tasks[0]),
task: new FormControl(''),
handle: new FormControl('')
});
this.config = this.configurationDataService.findByPropertyName(CURATION_CFG);
this.config.pipe(
find((rd: RemoteData<ConfigurationProperty>) => rd.hasSucceeded),
map((rd: RemoteData<ConfigurationProperty>) => rd.payload)
).subscribe((configProperties) => {
this.tasks = configProperties.values.map((value) => value.split('=')[1].trim());
this.form.get('task').patchValue(this.tasks[0]);
});
}
/**
@@ -65,7 +78,7 @@ export class CurationFormComponent implements OnInit {
* Navigate to the process page on success
*/
submit() {
const taskName = (this.form.get('task').value as CurationTask).name;
const taskName = this.form.get('task').value;
let handle;
if (this.hasHandleValue()) {
handle = this.dsoHandle;

View File

@@ -917,11 +917,17 @@
"curation-task.task.checklinks.label": "Check Links in Metadata",
"curation-task.task.noop.label": "NOOP",
"curation-task.task.profileformats.label": "Profile Bitstream Formats",
"curation-task.task.requiredmetadata.label": "Check for Required Metadata",
"curation-task.task.checklinks.label": "Check Links in Metadata",
"curation-task.task.translate.label": "Microsoft Translator",
"curation-task.task.vscan.label": "Virus Scan",

View File

@@ -1,9 +0,0 @@
import { Config } from './config.interface';
/**
* An interface to represent a curation task in the configuration. A CurationTask has a name and a label.
*/
export interface CurationTask extends Config {
name: string;
label: string;
}

View File

@@ -11,7 +11,6 @@ import { ItemPageConfig } from './item-page-config.interface';
import { CollectionPageConfig } from './collection-page-config.interface';
import { Theme } from './theme.inferface';
import {AuthConfig} from './auth-config.interfaces';
import { CurationTask } from './curation-task.interface';
export interface GlobalConfig extends Config {
ui: ServerConfig;
@@ -32,5 +31,4 @@ export interface GlobalConfig extends Config {
item: ItemPageConfig;
collection: CollectionPageConfig;
theme: Theme;
curationTasks: CurationTask[];
}

View File

@@ -213,18 +213,4 @@ export const environment: GlobalConfig = {
theme: {
name: 'default',
},
curationTasks: [
{
name: 'profileformats',
label: 'curation-task.task.profileformats.label'
},
{
name: 'requiredmetadata',
label: 'curation-task.task.requiredmetadata.label'
},
{
name: 'checklinks',
label: 'curation-task.task.checklinks.label'
}
]
};

View File

@@ -196,18 +196,4 @@ export const environment: Partial<GlobalConfig> = {
theme: {
name: 'default',
},
curationTasks: [
{
name: 'profileformats',
label: 'curation-task.task.profileformats.label'
},
{
name: 'requiredmetadata',
label: 'curation-task.task.requiredmetadata.label'
},
{
name: 'checklinks',
label: 'curation-task.task.checklinks.label'
}
]
};