Adds backend controller for DMP Template (profile) External Autocomplete field and refactors DMP edit to show DMP Template (profile). (Issue #134)

This commit is contained in:
gkolokythas 2019-07-24 18:29:29 +03:00
parent 81c5a492c1
commit ee367b3032
11 changed files with 240 additions and 24 deletions

View File

@ -1,10 +1,13 @@
package eu.eudat.controllers;
import eu.eudat.data.dao.criteria.RequestItem;
import eu.eudat.data.entities.DMPProfile;
import eu.eudat.data.query.items.table.dmpprofile.DataManagementPlanProfileTableRequest;
import eu.eudat.logic.managers.DataManagementProfileManager;
import eu.eudat.logic.security.claims.ClaimedAuthorities;
import eu.eudat.logic.services.ApiContext;
import eu.eudat.models.data.helpermodels.Tuple;
import eu.eudat.models.data.helpers.common.AutoCompleteLookupItem;
import eu.eudat.models.data.helpers.common.DataTableData;
import eu.eudat.models.data.helpers.responses.ResponseItem;
import eu.eudat.models.data.listingmodels.DataManagementPlanProfileListingModel;
@ -18,6 +21,7 @@ import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.validation.Valid;
import javax.xml.xpath.XPathExpressionException;
import java.io.IOException;
import java.util.List;
@ -82,4 +86,9 @@ public class DMPProfileController extends BaseController {
.status(ApiMessageCode.SUCCESS_MESSAGE).message(""));
}
@RequestMapping(method = RequestMethod.POST, value = {"/search/autocomplete"})
public ResponseEntity<Object> getExternalAutocomplete(@RequestBody RequestItem<AutoCompleteLookupItem> lookupItem) throws XPathExpressionException {
List<Tuple<String, String>> items = this.dataManagementProfileManager.getExternalAutocomplete(lookupItem);
return ResponseEntity.status(HttpStatus.OK).body(items);
}
}

View File

@ -1,13 +1,21 @@
package eu.eudat.logic.managers;
import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;
import eu.eudat.data.dao.criteria.RequestItem;
import eu.eudat.data.dao.entities.DMPProfileDao;
import eu.eudat.data.entities.DMPProfile;
import eu.eudat.data.query.items.item.dmpprofile.DataManagementPlanProfileCriteriaRequest;
import eu.eudat.data.query.items.table.dmpprofile.DataManagementPlanProfileTableRequest;
import eu.eudat.logic.services.operations.DatabaseRepository;
import eu.eudat.logic.utilities.builders.XmlBuilder;
import eu.eudat.logic.utilities.documents.helpers.FileEnvelope;
import eu.eudat.logic.utilities.documents.xml.dmpXml.ExportXmlBuilderDmpProfile;
import eu.eudat.logic.utilities.documents.xml.dmpXml.ImportXmlBuilderDmpProfile;
import eu.eudat.models.data.entities.xmlmodels.dmpprofiledefinition.DmpProfileExternalAutoComplete;
import eu.eudat.models.data.entities.xmlmodels.dmpprofiledefinition.Field;
import eu.eudat.models.data.helpermodels.Tuple;
import eu.eudat.models.data.helpers.common.AutoCompleteLookupItem;
import eu.eudat.models.data.helpers.common.DataTableData;
import eu.eudat.models.data.listingmodels.DataManagementPlanProfileListingModel;
import eu.eudat.models.data.security.Principal;
@ -18,14 +26,17 @@ import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import javax.activation.MimetypesFileTypeMap;
import javax.xml.xpath.*;
import java.io.*;
import java.nio.file.Files;
import java.util.List;
import java.util.UUID;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import org.springframework.http.*;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
/**
* Created by ikalyvas on 3/21/2018.
@ -79,11 +90,7 @@ public class DataManagementProfileManager {
apiContext.getOperationsContext().getDatabaseRepository().getDmpProfileDao().createOrUpdate(dmpProfile);
}
public ResponseEntity<byte[]> getDocument(DataManagementPlanProfileListingModel dmpProfile, String label) throws IllegalAccessException, IOException, InstantiationException {
FileEnvelope envelope = getXmlDocument(dmpProfile, label);
InputStream resource = new FileInputStream(envelope.getFile());
System.out.println("Mime Type of " + envelope.getFilename() + " is " +
@ -131,4 +138,37 @@ public class DataManagementProfileManager {
return convFile;
}
public List<Tuple<String, String>> getExternalAutocomplete(RequestItem<AutoCompleteLookupItem> lookupItem) throws XPathExpressionException {
DMPProfile dmpProfile = this.apiContext.getOperationsContext().getDatabaseRepository().getDmpProfileDao().find(UUID.fromString(lookupItem.getCriteria().getProfileID()));
Field field = this.queryForField(dmpProfile.getDefinition(), lookupItem.getCriteria().getFieldID());
DmpProfileExternalAutoComplete data = field.getExternalAutocomplete();
return this.externalAutocompleteRequest(data, lookupItem.getCriteria().getLike());
}
private Field queryForField(String xml, String fieldId) throws XPathExpressionException {
Field field = new Field();
Document document = XmlBuilder.fromXml(xml);
XPathFactory xpathFactory = XPathFactory.newInstance();
XPath xpath = xpathFactory.newXPath();
XPathExpression expr = xpath.compile("//field[@id='" + fieldId + "']");
Element name = (Element) expr.evaluate(document, XPathConstants.NODE);
field.fromXml(name);
return field;
}
private List<Tuple<String, String>> externalAutocompleteRequest(DmpProfileExternalAutoComplete data, String like) {
List<Tuple<String, String>> result = new LinkedList<>();
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.valueOf("application/vnd.api+json; charset=utf-8")));
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> entity = new HttpEntity<>("parameters", headers);
ResponseEntity<Object> response = restTemplate.exchange(data.getUrl() + "?search=" + like, HttpMethod.GET, entity, Object.class);
DocumentContext jsonContext = JsonPath.parse(response.getBody());
List<Map<String, String>> jsonItems = jsonContext.read(data.getOptionsRoot() + "['" + data.getLabel() + "','" + data.getValue() + "']");
jsonItems.forEach(item -> result.add(new Tuple<>(item.get(data.getValue()), item.get(data.getLabel()))));
return result;
}
}

View File

@ -0,0 +1,68 @@
package eu.eudat.models.data.entities.xmlmodels.dmpprofiledefinition;
import eu.eudat.logic.utilities.interfaces.XmlSerializable;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
public class DmpProfileExternalAutoComplete implements XmlSerializable<DmpProfileExternalAutoComplete> {
private String url;
private String optionsRoot;
private Boolean multiAutoComplete;
private String label;
private String value;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getOptionsRoot() {
return optionsRoot;
}
public void setOptionsRoot(String optionsRoot) {
this.optionsRoot = optionsRoot;
}
public Boolean getMultiAutoComplete() { return multiAutoComplete; }
public void setMultiAutoComplete(Boolean multiAutoComplete) { this.multiAutoComplete = multiAutoComplete; }
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
@Override
public Element toXml(Document doc) {
Element root = doc.createElement("externalAutocomplete");
root.setAttribute("url", this.url);
root.setAttribute("optionsRoot", this.optionsRoot);
if (this.multiAutoComplete == null) this.multiAutoComplete = false;
root.setAttribute("multiAutoComplete", this.multiAutoComplete.toString());
root.setAttribute("label", this.label);
root.setAttribute("value", this.value);
return root;
}
@Override
public DmpProfileExternalAutoComplete fromXml(Element item) {
this.url = item.getAttribute("url");
this.optionsRoot = item.getAttribute("optionsRoot");
this.multiAutoComplete = Boolean.valueOf(item.getAttribute("multiAutoComplete"));
this.label = item.getAttribute("label");
this.value = item.getAttribute("value");
return this;
}
}

View File

@ -12,6 +12,7 @@ import { DmpProfileCriteria } from '../../query/dmp/dmp-profile-criteria';
import { DatasetListingModel } from '../../model/dataset/dataset-listing';
import { BaseHttpParams } from '../../../common/http/base-http-params';
import { InterceptorType } from '../../../common/http/interceptors/interceptor-type';
import { DmpProfileExternalAutocompleteCriteria } from '../../query/dmp/dmp-profile-external-autocomplete-criteria';
@Injectable()
export class DmpProfileService {
@ -49,4 +50,8 @@ export class DmpProfileService {
formData.append('file', file[0], labelSent);
return this.http.post(this.actionUrl + "upload", formData, { params: params });
}
externalAutocomplete(lookUpItem: RequestItem<DmpProfileExternalAutocompleteCriteria>): Observable<any> {
return this.httpClient.post(this.actionUrl + 'search/autocomplete', lookUpItem);
}
}

View File

@ -5,10 +5,6 @@
{{'DMP-PROFILE-EDITOR.FIELDS.EXTERNAL-AUTOCOMPLETE.MULTIPLE-AUTOCOMPLETE' | translate}}
</mat-checkbox>
<mat-form-field class="col-12">
<input matInput type="string" placeholder="{{'DMP-PROFILE-EDITOR.FIELDS.EXTERNAL-AUTOCOMPLETE.PLACEHOLDER' | translate}}"
[formControl]="form.get('externalAutocomplete').get('placeholder')">
</mat-form-field>
<mat-form-field class="col-md-12">
<input matInput placeholder="{{'DMP-PROFILE-EDITOR.FIELDS.EXTERNAL-AUTOCOMPLETE.URL' | translate}}" [formControl]="this.form.get('externalAutocomplete').get('url')">
</mat-form-field>

View File

@ -1,4 +1,6 @@
.external-autocomplete {
margin-bottom: 10px;
.centered-row-item {
align-items: center;
display: flex;

View File

@ -3,7 +3,6 @@ import { DmpProfileExternalAutoCompleteField } from "../../../../../core/model/d
export class DmpProfileExternalAutoCompleteFieldDataEditorModel {
public placeholder: string;
public url: string;
public optionsRoot: string;
public multiAutoComplete: boolean = false;
@ -12,7 +11,6 @@ export class DmpProfileExternalAutoCompleteFieldDataEditorModel {
buildForm(disabled: boolean = false, skipDisable: Array<String> = []): FormGroup {
const formGroup = new FormBuilder().group({
placeholder: [{ value: this.placeholder, disabled: (disabled && !skipDisable.includes('DmpProfileExternalAutoCompleteFieldDataEditorModel.placeholder')) }],
url: [{ value: this.url, disabled: (disabled && !skipDisable.includes('DmpProfileExternalAutoCompleteFieldDataEditorModel.url')) }],
optionsRoot: [{ value: this.optionsRoot, disabled: (disabled && !skipDisable.includes('DmpProfileExternalAutoCompleteFieldDataEditorModel.optionsRoot')) }],
multiAutoComplete: [{ value: this.multiAutoComplete, disabled: (disabled && !skipDisable.includes('DmpProfileExternalAutoCompleteFieldDataEditorModel.multiAutoComplete')) }],
@ -24,7 +22,6 @@ export class DmpProfileExternalAutoCompleteFieldDataEditorModel {
}
fromModel(item: DmpProfileExternalAutoCompleteField): DmpProfileExternalAutoCompleteFieldDataEditorModel {
this.placeholder = item.placeholder;
this.url = item.url;
this.optionsRoot = item.optionsRoot;
this.multiAutoComplete = item.multiAutoComplete;

View File

@ -24,6 +24,29 @@
<mat-error *ngIf="formGroup.get('properties').get('fields').get(''+i).get('value')['errors']">{{'GENERAL.VALIDATION.REQUIRED'
| translate}}</mat-error>
</mat-form-field>
<!-- </div> -->
<div class="full-width" *ngIf="field.dataType == dmpProfileFieldDataType.ExternalAutocomplete">
<div *ngIf="field.externalAutocomplete.multiAutoComplete == false">
<mat-form-field class="full-width">
<app-single-auto-complete
[required]="false"
[formControl]="formGroup.get('properties').get('fields').get(''+i).get('value')"
placeholder="{{field.label}}"
[configuration]="singleAutocompleteMap[field.id]">
</app-single-auto-complete>
</mat-form-field>
</div>
<div *ngIf="field.externalAutocomplete.multiAutoComplete == true">
<mat-form-field class="full-width">
<app-multiple-auto-complete
[required]="field.required"
[formControl]="formGroup.get('properties').get('fields').get(''+i).get('value')"
placeholder="{{field.label}}"
[configuration]="multiAutocompleteMap[field.id]">
</app-multiple-auto-complete>
</mat-form-field>
</div>
<!-- <mat-error *ngIf="formGroup.get('properties').get('fields').get(''+i).get('value')['errors']">{{'GENERAL.VALIDATION.REQUIRED'
| translate}}</mat-error> -->
</div>
</div>
</div>

View File

@ -1,8 +1,14 @@
import { Component, Input, OnDestroy, OnInit, SimpleChanges } from '@angular/core';
import { FormArray, FormBuilder, FormGroup } from '@angular/forms';
import { FormArray, FormBuilder, FormGroup, FormControl } from '@angular/forms';
import { DmpProfileFieldDataType } from '../../../../core/common/enum/dmp-profile-field-type';
import { DmpProfileType } from '../../../../core/common/enum/dmp-profile-type';
import { DmpProfileDefinition } from '../../../../core/model/dmp-profile/dmp-profile';
import { SingleAutoCompleteConfiguration } from '../../../../library/auto-complete/single/single-auto-complete-configuration';
import { MultipleAutoCompleteConfiguration } from '../../../../library/auto-complete/multiple/multiple-auto-complete-configuration';
import { DmpProfileService } from '../../../../core/services/dmp/dmp-profile.service';
import { DmpProfileExternalAutocompleteCriteria } from '../../../../core/query/dmp/dmp-profile-external-autocomplete-criteria';
import { RequestItem } from '../../../../core/query/request-item';
import { DmpProfileField } from '../../../../core/model/dmp-profile/dmp-profile-field';
@Component({
selector: 'app-dynamic-dmp-field-resolver',
@ -14,13 +20,47 @@ export class DynamicDmpFieldResolverComponent implements OnInit, OnDestroy {
dmpProfileFieldDataType = DmpProfileFieldDataType;
dmpProfileTypeEnum = DmpProfileType;
singleAutocompleteMap: { [id: string]: SingleAutoCompleteConfiguration; } = {};
multiAutocompleteMap: { [id: string]: MultipleAutoCompleteConfiguration; } = {};
@Input() dmpProfileId: string;
@Input() dmpProfileDefinition: DmpProfileDefinition;
@Input() formGroup: FormGroup;
constructor(
private dmpProfileService: DmpProfileService
) { }
ngOnInit(): void {
this.createControleFields();
if (this.dmpProfileDefinition) {
this.dmpProfileDefinition.fields.forEach(
field => {
if (field.externalAutocomplete) {
if (field.externalAutocomplete.multiAutoComplete) {
const multiConf: MultipleAutoCompleteConfiguration = {
filterFn: this.externalAutocomplete.bind(this, field),
initialItems: (extraData) => this.externalAutocomplete('', field.id),
displayFn: (item) => item['label'],
titleFn: (item) => item['label']
}
this.multiAutocompleteMap[field.id] = multiConf;
} else {
const singleConf: SingleAutoCompleteConfiguration = {
filterFn: this.externalAutocomplete.bind(this, field),
initialItems: (extraData) => this.externalAutocomplete('', field.id),
displayFn: (item) => item['label'],
titleFn: (item) => item['label']
}
this.singleAutocompleteMap[field.id] = singleConf;
}
}
}
);
}
}
ngOnChanges(changes: SimpleChanges) {
if (changes['dmpProfileDefinition'] && !changes['dmpProfileDefinition'].isFirstChange()) {
this.createControleFields();
@ -39,7 +79,7 @@ export class DynamicDmpFieldResolverComponent implements OnInit, OnDestroy {
}));
});
}
if(this.dmpProfileDefinition==null){
if (this.dmpProfileDefinition == null) {
this.formGroup.removeControl('properties');
}
}
@ -47,4 +87,21 @@ export class DynamicDmpFieldResolverComponent implements OnInit, OnDestroy {
ngOnDestroy(): void {
this.formGroup.removeControl('properties');
}
externalAutocomplete(query: any, extFieldID: any) {
const autocompleteRequestItem: RequestItem<DmpProfileExternalAutocompleteCriteria> = new RequestItem();
autocompleteRequestItem.criteria = new DmpProfileExternalAutocompleteCriteria();
if (typeof extFieldID == "string" && typeof query == "string") {
autocompleteRequestItem.criteria.like = query;
autocompleteRequestItem.criteria.profileID = this.dmpProfileId;
autocompleteRequestItem.criteria.fieldID = extFieldID;
} else {
autocompleteRequestItem.criteria.like = extFieldID;
autocompleteRequestItem.criteria.profileID = this.dmpProfileId;
autocompleteRequestItem.criteria.fieldID = query.id;
}
return this.dmpProfileService.externalAutocomplete(autocompleteRequestItem);
}
}

View File

@ -63,8 +63,8 @@
</app-multiple-auto-complete>
<mat-error *ngIf="formGroup.get('researchers').hasError('backendError')">
{{formGroup.get('researchers').getError('backendError').message}}</mat-error>
<mat-error *ngIf="formGroup.get('researchers').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' |
translate}}</mat-error>
<mat-error *ngIf="formGroup.get('researchers').hasError('required')">
{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
<button matSuffix class="input-btn" [disabled]="formGroup.get('researchers').disabled" type="button"
(click)="addResearcher()">
<mat-icon class="icon-btn">add_circle</mat-icon>
@ -74,15 +74,15 @@
</div>
<div class="row pt-2">
<mat-form-field class="col-8">
<app-single-auto-complete [required]='false' [formControl]="formGroup.get('profile')"
<app-single-auto-complete [required]="false" [formControl]="formGroup.get('profile')"
placeholder="{{'DMP-EDITOR.FIELDS.TEMPLATE' | translate}}"
[configuration]="dmpProfileAutoCompleteConfiguration">
</app-single-auto-complete>
</mat-form-field>
</div>
<app-dynamic-dmp-field-resolver *ngIf="selectedDmpProfileDefinition" [formGroup]="formGroup"
[dmpProfileDefinition]="selectedDmpProfileDefinition">
[dmpProfileDefinition]="selectedDmpProfileDefinition"
[dmpProfileId]="selectedDmpProfileId">
</app-dynamic-dmp-field-resolver>
</div>

View File

@ -19,6 +19,7 @@ import { SingleAutoCompleteConfiguration } from '../../../../library/auto-comple
import { LanguageResolverService } from '../../../../services/language-resolver/language-resolver.service';
import { AddResearcherComponent } from '../add-researcher/add-researcher.component';
import { AvailableProfilesComponent } from '../available-profiles/available-profiles.component';
import { startWith } from 'rxjs/internal/operators';
@Component({
selector: 'app-general-tab',
@ -42,6 +43,8 @@ export class GeneralTabComponent extends BaseComponent implements OnInit {
dmpProfileAutoCompleteConfiguration: SingleAutoCompleteConfiguration;
selectedDmpProfileDefinition: DmpProfileDefinition;
selectedDmpProfileId: string;
constructor(
private dmpProfileService: DmpProfileService,
private externalSourcesService: ExternalSourcesService,
@ -53,6 +56,18 @@ export class GeneralTabComponent extends BaseComponent implements OnInit {
}
ngOnInit() {
if (this.formGroup.get('profile'))
this.dmpProfileService.getSingle(this.formGroup.get('profile').value.id)
.pipe(takeUntil(this._destroyed))
.subscribe(result => {
this.selectedDmpProfileDefinition = result.definition;
this.selectedDmpProfileId = result.id;
if (this.formGroup.get('properties')) {
}
});
this.registerFormEventsForDmpProfile();
this.dmpProfileAutoCompleteConfiguration = {
@ -84,9 +99,11 @@ export class GeneralTabComponent extends BaseComponent implements OnInit {
};
}
registerFormEventsForDmpProfile(definitionPropertys?: DmpProfileDefinition): void {
registerFormEventsForDmpProfile(definitionProperties?: DmpProfileDefinition): void {
this.formGroup.get('profile').valueChanges
.pipe(takeUntil(this._destroyed))
.pipe(
startWith(null),
takeUntil(this._destroyed))
.subscribe(Option => {
if (Option instanceof Object) {
this.selectedDmpProfileDefinition = null;
@ -94,11 +111,13 @@ export class GeneralTabComponent extends BaseComponent implements OnInit {
.pipe(takeUntil(this._destroyed))
.subscribe(result => {
this.selectedDmpProfileDefinition = result.definition;
this.selectedDmpProfileId = result.id;
});
} else {
this.selectedDmpProfileDefinition = null;
this.selectedDmpProfileId = null;
}
this.selectedDmpProfileDefinition = definitionPropertys;
this.selectedDmpProfileDefinition = definitionProperties;
})
}