Add Authentication support for the remote fetcher and rework dataset template's autocomplete sources

This commit is contained in:
George Kalampokis 2023-10-06 18:07:58 +03:00
parent ecd86f413a
commit ec8f6a6d48
26 changed files with 572 additions and 72 deletions

View File

@ -46,6 +46,10 @@
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId> <artifactId>spring-boot-starter-mail</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/com.googlecode.json-simple/json-simple --> <!-- https://mvnrepository.com/artifact/com.googlecode.json-simple/json-simple -->
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind --> <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->

View File

@ -1,7 +1,5 @@
package eu.eudat.logic.managers; package eu.eudat.logic.managers;
import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;
import eu.eudat.data.dao.criteria.DatasetProfileCriteria; import eu.eudat.data.dao.criteria.DatasetProfileCriteria;
import eu.eudat.data.entities.DescriptionTemplate; import eu.eudat.data.entities.DescriptionTemplate;
import eu.eudat.data.entities.UserDatasetProfile; import eu.eudat.data.entities.UserDatasetProfile;
@ -10,8 +8,10 @@ import eu.eudat.data.query.items.item.datasetprofile.DatasetProfileAutocompleteR
import eu.eudat.data.query.items.table.datasetprofile.DatasetProfileTableRequestItem; import eu.eudat.data.query.items.table.datasetprofile.DatasetProfileTableRequestItem;
import eu.eudat.exceptions.datasetprofile.DatasetProfileNewVersionException; import eu.eudat.exceptions.datasetprofile.DatasetProfileNewVersionException;
import eu.eudat.logic.builders.model.models.DataTableDataBuilder; import eu.eudat.logic.builders.model.models.DataTableDataBuilder;
import eu.eudat.logic.proxy.config.Semantic; import eu.eudat.logic.proxy.config.*;
import eu.eudat.logic.proxy.config.configloaders.ConfigLoader; import eu.eudat.logic.proxy.config.configloaders.ConfigLoader;
import eu.eudat.logic.proxy.config.entities.GeneralUrls;
import eu.eudat.logic.proxy.fetching.RemoteFetcher;
import eu.eudat.logic.services.ApiContext; import eu.eudat.logic.services.ApiContext;
import eu.eudat.logic.services.operations.DatabaseRepository; import eu.eudat.logic.services.operations.DatabaseRepository;
import eu.eudat.logic.utilities.builders.XmlBuilder; import eu.eudat.logic.utilities.builders.XmlBuilder;
@ -36,10 +36,11 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.http.*; import org.springframework.http.HttpHeaders;
import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.w3c.dom.Document; import org.w3c.dom.Document;
import org.w3c.dom.Element; import org.w3c.dom.Element;
@ -56,6 +57,8 @@ import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.*; import javax.xml.xpath.*;
import java.io.*; import java.io.*;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Files; import java.nio.file.Files;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -66,20 +69,20 @@ public class DatasetProfileManager {
private static final Logger logger = LoggerFactory.getLogger(DatasetProfileManager.class); private static final Logger logger = LoggerFactory.getLogger(DatasetProfileManager.class);
private static final List<String> cache = new ArrayList<>(); private static final List<String> cache = new ArrayList<>();
private ApiContext apiContext; private final ApiContext apiContext;
private DatabaseRepository databaseRepository; private final DatabaseRepository databaseRepository;
private Environment environment; private final Environment environment;
private ConfigLoader configLoader; private final ConfigLoader configLoader;
private final MetricsManager metricsManager; private final MetricsManager metricsManager;
private final RemoteFetcher remoteFetcher;
@Autowired @Autowired
public DatasetProfileManager(ApiContext apiContext, Environment environment, ConfigLoader configLoader, MetricsManager metricsManager) { public DatasetProfileManager(ApiContext apiContext, Environment environment, ConfigLoader configLoader, MetricsManager metricsManager, RemoteFetcher remoteFetcher) {
this.apiContext = apiContext; this.apiContext = apiContext;
this.databaseRepository = apiContext.getOperationsContext().getDatabaseRepository(); this.databaseRepository = apiContext.getOperationsContext().getDatabaseRepository();
this.environment = environment; this.environment = environment;
this.configLoader = configLoader; this.configLoader = configLoader;
this.metricsManager = metricsManager; this.metricsManager = metricsManager;
this.remoteFetcher = remoteFetcher;
} }
@Transactional @Transactional
@ -158,10 +161,10 @@ public class DatasetProfileManager {
return field; return field;
} }
public static List<ExternalAutocompleteFieldModel> getAutocomplete(AutoCompleteData data, String like) { public List<ExternalAutocompleteFieldModel> getAutocomplete(AutoCompleteData data, String like) {
List<ExternalAutocompleteFieldModel> result = new LinkedList<>(); /*List<ExternalAutocompleteFieldModel> result = new LinkedList<>();
SimpleClientHttpRequestFactory simpleFactory = new SimpleClientHttpRequestFactory(); SimpleClientHttpRequestFactory simpleFactory = new SimpleClientHttpRequestFactory();
RestTemplate restTemplate = new RestTemplate(simpleFactory); RestTemplate restTemplate = new RestTemplate(simpleFactory);
HttpHeaders headers = new HttpHeaders(); HttpHeaders headers = new HttpHeaders();
DocumentContext jsonContext = null; DocumentContext jsonContext = null;
@ -180,7 +183,7 @@ public class DatasetProfileManager {
if (url.contains("zenodo")) { if (url.contains("zenodo")) {
url = url.replace("?", "/?"); url = url.replace("?", "/?");
} }
url = url.replace("{like}", like.equals("") ? "*" : like); url = url.replace("{like}", like.equals("") ? "*" : like);
url = url.replace("%20", " "); url = url.replace("%20", " ");
url = url.replace("%22", "\""); url = url.replace("%22", "\"");
@ -196,7 +199,7 @@ public class DatasetProfileManager {
headers.setContentType(MediaType.APPLICATION_JSON); headers.setContentType(MediaType.APPLICATION_JSON);
entity = new HttpEntity<>("parameters", headers); entity = new HttpEntity<>("parameters", headers);
response = restTemplate.exchange(url, HttpMethod.GET, entity, Object.class); response = restTemplate.exchange(url, HttpMethod.GET, entity, Object.class);
jsonContext = JsonPath.parse(response.getBody()); jsonContext = JsonPath.parse(response.getBody());
jsonItems = jsonContext.read(singleData.getOptionsRoot() + "['" + singleData.getAutoCompleteOptions().getLabel() + "','" + singleData.getAutoCompleteOptions().getValue() + "','" + singleData.getAutoCompleteOptions().getSource() + "','" + "uri" + "']"); jsonItems = jsonContext.read(singleData.getOptionsRoot() + "['" + singleData.getAutoCompleteOptions().getLabel() + "','" + singleData.getAutoCompleteOptions().getValue() + "','" + singleData.getAutoCompleteOptions().getSource() + "','" + "uri" + "']");
@ -222,8 +225,72 @@ public class DatasetProfileManager {
} }
return result.stream().sorted(Comparator.comparing(ExternalAutocompleteFieldModel::getLabel)).collect(Collectors.toList()); return result.stream().sorted(Comparator.comparing(ExternalAutocompleteFieldModel::getLabel)).collect(Collectors.toList());
*/
//return result; List<ExternalAutocompleteFieldModel> result = new LinkedList<>();
ExternalUrlCriteria urlCriteria = new ExternalUrlCriteria();
GeneralUrls genericUrls = new GeneralUrls();
int ordinal = 1;
List<Map<String, String>> rawResults = new ArrayList<>();
genericUrls.setFetchMode(FetchStrategy.FIRST);
urlCriteria.setLike(like);
for (AutoCompleteData.AutoCompleteSingleData singleData : data.getAutoCompleteSingleDataList()) {
UrlConfiguration urlConfiguration = new UrlConfiguration();
try {
URI uri;
if (singleData.getUrl().contains("?")) {
uri = new URI(singleData.getUrl().substring(0, singleData.getUrl().lastIndexOf("?")));
} else {
uri = new URI(singleData.getUrl());
}
String source = singleData.getAutoCompleteOptions().getSource();
source = source != null && !source.isEmpty() ? source : uri.getHost();
String uriString = singleData.getAutoCompleteOptions().getUri();
uriString = uriString != null && !uriString.isEmpty() ? uriString : "uri";
String parsedUrl = singleData.getUrl();
parsedUrl = parsedUrl.replace("%20", " ");
parsedUrl = parsedUrl.replace("%22", "\"");
while (parsedUrl.contains("&amp;")) {
parsedUrl = parsedUrl.replace("&amp;", "&");
}
urlConfiguration.setUrl(parsedUrl);
urlConfiguration.setOrdinal(ordinal);
urlConfiguration.setType("External");
urlConfiguration.setContentType(MediaType.APPLICATION_JSON_VALUE);
urlConfiguration.setFirstpage("1");
urlConfiguration.setRequestType(singleData.getMethod() != null ? singleData.getMethod() : "GET");
DataUrlConfiguration dataUrlConfiguration = new DataUrlConfiguration();
dataUrlConfiguration.setPath(singleData.getOptionsRoot());
DataFieldsUrlConfiguration fieldsUrlConfiguration = new DataFieldsUrlConfiguration();
fieldsUrlConfiguration.setId(singleData.getAutoCompleteOptions().getValue());
fieldsUrlConfiguration.setName(singleData.getAutoCompleteOptions().getLabel());
fieldsUrlConfiguration.setSource(singleData.getAutoCompleteOptions().getSource().isEmpty()? null : singleData.getAutoCompleteOptions().getSource());
fieldsUrlConfiguration.setUri(uriString);
dataUrlConfiguration.setFieldsUrlConfiguration(fieldsUrlConfiguration);
urlConfiguration.setKey(source);
urlConfiguration.setLabel(source);
urlConfiguration.setData(dataUrlConfiguration);
if (singleData.getHasAuth()) {
AuthenticationConfiguration authenticationConfiguration = new AuthenticationConfiguration();
authenticationConfiguration.setAuthUrl(singleData.getAuth().getUrl());
authenticationConfiguration.setAuthMethod(singleData.getAuth().getMethod());
authenticationConfiguration.setAuthTokenPath(singleData.getAuth().getPath());
authenticationConfiguration.setAuthRequestBody(singleData.getAuth().getBody());
authenticationConfiguration.setType(singleData.getAuth().getType());
urlConfiguration.setAuth(authenticationConfiguration);
}
genericUrls.getUrls().add(urlConfiguration);
List<Map<String, String>> singleResults = this.remoteFetcher.getExternalGeneric(urlCriteria, genericUrls);
if (!singleResults.isEmpty() && !singleResults.get(0).containsKey("source") && !singleData.getAutoCompleteOptions().getSource().isEmpty()) {
singleResults.forEach(singleResult -> singleResult.put("source", singleData.getAutoCompleteOptions().getSource()));
}
rawResults.addAll(singleResults);
genericUrls.getUrls().clear();
} catch (URISyntaxException e) {
logger.error(e.getMessage(), e);
}
}
rawResults.forEach(item -> result.add(new ExternalAutocompleteFieldModel(parseItem(item.get("pid")), parseItem(item.get("name")), parseItem(item.get("source")), parseItem(item.get("uri")))));
return result;
} }
private static String parseItem(Object item) { private static String parseItem(Object item) {

View File

@ -27,11 +27,13 @@ public class PrefillingManager {
private final ObjectMapper objectMapper; private final ObjectMapper objectMapper;
private final DatasetManager datasetManager; private final DatasetManager datasetManager;
private final LicenseManager licenseManager; private final LicenseManager licenseManager;
private final PrefillingMapper prefillingMapper;
@Autowired @Autowired
public PrefillingManager(ApiContext apiContext, ConfigLoader configLoader, DatasetManager datasetManager, LicenseManager licenseManager) { public PrefillingManager(ApiContext apiContext, ConfigLoader configLoader, DatasetManager datasetManager, LicenseManager licenseManager, PrefillingMapper prefillingMapper) {
this.apiContext = apiContext; this.apiContext = apiContext;
this.configLoader = configLoader; this.configLoader = configLoader;
this.prefillingMapper = prefillingMapper;
this.objectMapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); this.objectMapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
this.datasetManager = datasetManager; this.datasetManager = datasetManager;
this.licenseManager = licenseManager; this.licenseManager = licenseManager;
@ -62,14 +64,14 @@ public class PrefillingManager {
PrefillingGet prefillingGet = prefillingConfig.getPrefillingGet(); PrefillingGet prefillingGet = prefillingConfig.getPrefillingGet();
Map<String, Object> prefillingEntity = getSingle(prefillingGet.getUrl(), prefillId); Map<String, Object> prefillingEntity = getSingle(prefillingGet.getUrl(), prefillId);
DescriptionTemplate descriptionTemplate = apiContext.getOperationsContext().getDatabaseRepository().getDatasetProfileDao().find(profileId); DescriptionTemplate descriptionTemplate = apiContext.getOperationsContext().getDatabaseRepository().getDatasetProfileDao().find(profileId);
return PrefillingMapper.mapPrefilledEntityToDatasetWizard(prefillingEntity, prefillingGet, prefillingConfig.getType(), descriptionTemplate, datasetManager, licenseManager); return prefillingMapper.mapPrefilledEntityToDatasetWizard(prefillingEntity, prefillingGet, prefillingConfig.getType(), descriptionTemplate, datasetManager, licenseManager);
} }
public DatasetWizardModel getPrefilledDatasetUsingData(Map<String, Object> data, String configId, UUID profileId) throws Exception { public DatasetWizardModel getPrefilledDatasetUsingData(Map<String, Object> data, String configId, UUID profileId) throws Exception {
PrefillingConfig prefillingConfig = configLoader.getExternalUrls().getPrefillings().get(configId); PrefillingConfig prefillingConfig = configLoader.getExternalUrls().getPrefillings().get(configId);
PrefillingGet prefillingGet = prefillingConfig.getPrefillingGet(); PrefillingGet prefillingGet = prefillingConfig.getPrefillingGet();
DescriptionTemplate descriptionTemplate = apiContext.getOperationsContext().getDatabaseRepository().getDatasetProfileDao().find(profileId); DescriptionTemplate descriptionTemplate = apiContext.getOperationsContext().getDatabaseRepository().getDatasetProfileDao().find(profileId);
return PrefillingMapper.mapPrefilledEntityToDatasetWizard(data, prefillingGet, prefillingConfig.getType(), descriptionTemplate, datasetManager, licenseManager); return prefillingMapper.mapPrefilledEntityToDatasetWizard(data, prefillingGet, prefillingConfig.getType(), descriptionTemplate, datasetManager, licenseManager);
} }
private Map<String, Object> getSingle(String url, String id) { private Map<String, Object> getSingle(String url, String id) {

View File

@ -27,6 +27,8 @@ import eu.eudat.models.data.license.LicenseModel;
import org.json.JSONObject; import org.json.JSONObject;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
@ -35,11 +37,18 @@ import java.time.format.DateTimeFormatter;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Component
public class PrefillingMapper { public class PrefillingMapper {
private static final Logger logger = LoggerFactory.getLogger(PrefillingMapper.class); private static final Logger logger = LoggerFactory.getLogger(PrefillingMapper.class);
private static final ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true); private static final ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
private final DatasetProfileManager datasetProfileManager;
public static DatasetWizardModel mapPrefilledEntityToDatasetWizard(Map<String, Object> prefilledEntity, PrefillingGet prefillingGet, String type, @Autowired
public PrefillingMapper(DatasetProfileManager datasetProfileManager) {
this.datasetProfileManager = datasetProfileManager;
}
public DatasetWizardModel mapPrefilledEntityToDatasetWizard(Map<String, Object> prefilledEntity, PrefillingGet prefillingGet, String type,
DescriptionTemplate profile, DatasetManager datasetManager, LicenseManager licenseManager) throws Exception { DescriptionTemplate profile, DatasetManager datasetManager, LicenseManager licenseManager) throws Exception {
DatasetWizardModel datasetWizardModel = new DatasetWizardModel(); DatasetWizardModel datasetWizardModel = new DatasetWizardModel();
datasetWizardModel.setProfile(new DatasetProfileOverviewModel().fromDataModel(profile)); datasetWizardModel.setProfile(new DatasetProfileOverviewModel().fromDataModel(profile));
@ -77,7 +86,7 @@ public class PrefillingMapper {
return datasetWizardModel; return datasetWizardModel;
} }
private static void setValue(PrefillingMapping prefillingMapping, String value, DatasetWizardModel datasetWizardModel, JsonNode parentNode, Map<String, Object> properties, String type, LicenseManager licenseManager) throws InvocationTargetException, IllegalAccessException, JsonProcessingException { private void setValue(PrefillingMapping prefillingMapping, String value, DatasetWizardModel datasetWizardModel, JsonNode parentNode, Map<String, Object> properties, String type, LicenseManager licenseManager) throws InvocationTargetException, IllegalAccessException, JsonProcessingException {
String trimRegex = prefillingMapping.getTrimRegex() != null ? prefillingMapping.getTrimRegex() : ""; String trimRegex = prefillingMapping.getTrimRegex() != null ? prefillingMapping.getTrimRegex() : "";
if (!value.startsWith("\"") && !value.startsWith("[") && !value.equals("null")) { if (!value.startsWith("\"") && !value.startsWith("[") && !value.equals("null")) {
value = "\"" + value + "\""; value = "\"" + value + "\"";
@ -216,7 +225,7 @@ public class PrefillingMapper {
} }
} }
private static Object parseComboBoxValues(JsonNode node, List<String> parsedValues) throws JsonProcessingException { private Object parseComboBoxValues(JsonNode node, List<String> parsedValues) throws JsonProcessingException {
List<Object> normalizedValues = new ArrayList<>(); List<Object> normalizedValues = new ArrayList<>();
boolean isMultiSelect; boolean isMultiSelect;
String type = node.isArray() ? node.get(0).get("data").get("type").asText() : node.get("data").get("type").asText(); String type = node.isArray() ? node.get(0).get("data").get("type").asText() : node.get("data").get("type").asText();
@ -227,7 +236,7 @@ public class PrefillingMapper {
for (String format : parsedValues) { for (String format : parsedValues) {
List<ExternalAutocompleteFieldModel> result = new ArrayList<>(); List<ExternalAutocompleteFieldModel> result = new ArrayList<>();
try { try {
result = DatasetProfileManager.getAutocomplete(autoCompleteData, format); result = datasetProfileManager.getAutocomplete(autoCompleteData, format);
} }
catch (Exception e) { catch (Exception e) {
logger.error(e.getMessage(), e); logger.error(e.getMessage(), e);

View File

@ -0,0 +1,23 @@
package eu.eudat.logic.proxy.config;
public enum AuthType {
;
private final String name;
AuthType(String name) {
this.name = name;
}
public String getName() {
return name;
}
public static AuthType fromName(String name) {
for (AuthType authType : AuthType.values()) {
if (authType.getName().equals(name)) {
return authType;
}
}
throw new IllegalArgumentException("AuthType [" + name + "] is not supported");
}
}

View File

@ -0,0 +1,57 @@
package eu.eudat.logic.proxy.config;
import javax.xml.bind.annotation.XmlElement;
public class AuthenticationConfiguration {
private String authUrl;
private String authMethod = "GET";
private String authTokenPath;
private String authRequestBody;
private String type;
public String getAuthUrl() {
return authUrl;
}
@XmlElement(name = "authUrl")
public void setAuthUrl(String authUrl) {
this.authUrl = authUrl;
}
public String getAuthMethod() {
return authMethod;
}
@XmlElement(name = "authUrlMethod")
public void setAuthMethod(String authMethod) {
this.authMethod = authMethod;
}
public String getAuthTokenPath() {
return authTokenPath;
}
@XmlElement(name = "authTokenJpath")
public void setAuthTokenPath(String authTokenPath) {
this.authTokenPath = authTokenPath;
}
public String getAuthRequestBody() {
return authRequestBody;
}
@XmlElement(name = "authUrlBody")
public void setAuthRequestBody(String authRequestBody) {
this.authRequestBody = authRequestBody;
}
public String getType() {
return type;
}
@XmlElement(name = "authType")
public void setType(String type) {
this.type = type;
}
}

View File

@ -21,6 +21,7 @@ public class UrlConfiguration {
private String requestType = "GET"; private String requestType = "GET";
private String requestBody = ""; private String requestBody = "";
private String filterType = "remote"; private String filterType = "remote";
private AuthenticationConfiguration auth;
private List<QueryConfig> queries; private List<QueryConfig> queries;
@ -143,4 +144,13 @@ public class UrlConfiguration {
public void setQueries(List<QueryConfig> queries) { public void setQueries(List<QueryConfig> queries) {
this.queries = queries; this.queries = queries;
} }
public AuthenticationConfiguration getAuth() {
return auth;
}
@XmlElement(name="authentication")
public void setAuth(AuthenticationConfiguration auth) {
this.auth = auth;
}
} }

View File

@ -0,0 +1,31 @@
package eu.eudat.logic.proxy.config.entities;
import eu.eudat.logic.proxy.config.FetchStrategy;
import eu.eudat.logic.proxy.config.UrlConfiguration;
import java.util.ArrayList;
import java.util.List;
public class GeneralUrls extends GenericUrls{
List<UrlConfiguration> urls;
FetchStrategy fetchMode;
public GeneralUrls() {
this.urls = new ArrayList<>();
}
@Override
public List<UrlConfiguration> getUrls() {
return urls;
}
@Override
public FetchStrategy getFetchMode() {
return fetchMode;
}
public void setFetchMode(FetchStrategy fetchMode) {
this.fetchMode = fetchMode;
}
}

View File

@ -15,9 +15,14 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Cacheable;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.*; import org.springframework.http.*;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.http.codec.json.Jackson2JsonDecoder;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.netty.http.client.HttpClient;
import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller; import javax.xml.bind.Unmarshaller;
@ -33,10 +38,16 @@ public class RemoteFetcher {
private static final Logger logger = LoggerFactory.getLogger(RemoteFetcher.class); private static final Logger logger = LoggerFactory.getLogger(RemoteFetcher.class);
private ConfigLoader configLoader; private ConfigLoader configLoader;
private final WebClient client;
@Autowired @Autowired
public RemoteFetcher(ConfigLoader configLoader) { public RemoteFetcher(ConfigLoader configLoader) {
this.configLoader = configLoader; this.configLoader = configLoader;
this.client = WebClient.builder().codecs(clientCodecConfigurer -> {
clientCodecConfigurer.defaultCodecs().jackson2JsonDecoder(new Jackson2JsonDecoder(new ObjectMapper(), MediaType.APPLICATION_JSON));
clientCodecConfigurer.defaultCodecs().maxInMemorySize(2 * ((int) Math.pow(1024, 3))); //GK: Why here???
}
).clientConnector(new ReactorClientHttpConnector(HttpClient.create().followRedirect(true))).build();
} }
@Cacheable(value = "repositories", keyGenerator = "externalUrlsKeyGenerator") @Cacheable(value = "repositories", keyGenerator = "externalUrlsKeyGenerator")
@ -198,7 +209,11 @@ public class RemoteFetcher {
ifFunderQueryExist(urlConfiguration, externalUrlCriteria); ifFunderQueryExist(urlConfiguration, externalUrlCriteria);
if (urlConfiguration.getType() == null || urlConfiguration.getType().equals("External")) { if (urlConfiguration.getType() == null || urlConfiguration.getType().equals("External")) {
try { try {
results.addAll(getAllResultsFromUrl(urlConfiguration.getUrl(), fetchStrategy, urlConfiguration.getData(), urlConfiguration.getPaginationPath(), externalUrlCriteria, urlConfiguration.getLabel(), urlConfiguration.getKey(), urlConfiguration.getContentType(), urlConfiguration.getFirstpage(), urlConfiguration.getRequestBody(), urlConfiguration.getRequestType(), urlConfiguration.getFilterType(), urlConfiguration.getQueries())); String auth = null;
if (urlConfiguration.getAuth() != null) {
auth = this.getAuthentication(urlConfiguration.getAuth());
}
results.addAll(getAllResultsFromUrl(urlConfiguration.getUrl(), fetchStrategy, urlConfiguration.getData(), urlConfiguration.getPaginationPath(), externalUrlCriteria, urlConfiguration.getLabel(), urlConfiguration.getKey(), urlConfiguration.getContentType(), urlConfiguration.getFirstpage(), urlConfiguration.getRequestBody(), urlConfiguration.getRequestType(), urlConfiguration.getFilterType(), urlConfiguration.getQueries(), auth));
} catch (Exception e) { } catch (Exception e) {
logger.error(e.getLocalizedMessage(), e); logger.error(e.getLocalizedMessage(), e);
} }
@ -217,6 +232,19 @@ public class RemoteFetcher {
return results; return results;
} }
private String getAuthentication(AuthenticationConfiguration authenticationConfiguration) {
HttpMethod method = HttpMethod.valueOf(authenticationConfiguration.getAuthMethod());
Map<String, Object> reponse = this.client.method(method).uri(authenticationConfiguration.getAuthUrl())
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(this.parseBodyString(authenticationConfiguration.getAuthRequestBody()))
.exchangeToMono(mono -> mono.bodyToMono(new ParameterizedTypeReference<Map<String, Object>>() {
})).block();
return authenticationConfiguration.getType() + " " + reponse.get(authenticationConfiguration.getAuthTokenPath());
}
private List<Map<String, Object>> getAllWithData(List<UrlConfiguration> urlConfigs, ExternalUrlCriteria externalUrlCriteria) { private List<Map<String, Object>> getAllWithData(List<UrlConfiguration> urlConfigs, ExternalUrlCriteria externalUrlCriteria) {
List<Map<String, Object>> results = new LinkedList<>(); List<Map<String, Object>> results = new LinkedList<>();
@ -273,7 +301,7 @@ public class RemoteFetcher {
protected String replaceCriteriaOnUrl(String path, ExternalUrlCriteria externalUrlCriteria, String firstPage, List<QueryConfig> queries) { protected String replaceCriteriaOnUrl(String path, ExternalUrlCriteria externalUrlCriteria, String firstPage, List<QueryConfig> queries) {
String completedPath = path; String completedPath = path;
if (externalUrlCriteria.getLike() != null) { if (externalUrlCriteria.getLike() != null) {
if ((path.contains("openaire") || path.contains("orcid") || path.contains("ror")) && externalUrlCriteria.getLike().equals("")) { if ((path.contains("openaire") || path.contains("orcid") || path.contains("ror") || path.contains("fairsharing")) && externalUrlCriteria.getLike().equals("")) {
completedPath = completedPath.replaceAll("\\{like}", "*"); completedPath = completedPath.replaceAll("\\{like}", "*");
completedPath = completedPath.replaceAll("\\{query}", "*"); completedPath = completedPath.replaceAll("\\{query}", "*");
} else { } else {
@ -329,13 +357,13 @@ public class RemoteFetcher {
return completedPath; return completedPath;
} }
private List<Map<String, String>> getAllResultsFromUrl(String path, FetchStrategy fetchStrategy, final DataUrlConfiguration jsonDataPath, final String jsonPaginationPath, ExternalUrlCriteria externalUrlCriteria, String tag, String key, String contentType, String firstPage, String requestBody, String requestType, String filterType, List<QueryConfig> queries) throws Exception { private List<Map<String, String>> getAllResultsFromUrl(String path, FetchStrategy fetchStrategy, final DataUrlConfiguration jsonDataPath, final String jsonPaginationPath, ExternalUrlCriteria externalUrlCriteria, String tag, String key, String contentType, String firstPage, String requestBody, String requestType, String filterType, List<QueryConfig> queries, String auth) throws Exception {
Set<Integer> pages = new HashSet<>(); Set<Integer> pages = new HashSet<>();
String replacedPath = replaceCriteriaOnUrl(path, externalUrlCriteria, firstPage, queries); String replacedPath = replaceCriteriaOnUrl(path, externalUrlCriteria, firstPage, queries);
String replacedBody = replaceCriteriaOnUrl(requestBody, externalUrlCriteria, firstPage, queries); String replacedBody = replaceCriteriaOnUrl(requestBody, externalUrlCriteria, firstPage, queries);
Results results = getResultsFromUrl(replacedPath, jsonDataPath, jsonPaginationPath, contentType, replacedBody, requestType); Results results = getResultsFromUrl(replacedPath, jsonDataPath, jsonPaginationPath, contentType, replacedBody, requestType, auth);
if(results != null) { if(results != null) {
if (filterType != null && filterType.equals("local") && (externalUrlCriteria.getLike() != null && !externalUrlCriteria.getLike().isEmpty())) { if (filterType != null && filterType.equals("local") && (externalUrlCriteria.getLike() != null && !externalUrlCriteria.getLike().isEmpty())) {
results.setResults(results.getResults().stream() results.setResults(results.getResults().stream()
@ -354,7 +382,7 @@ public class RemoteFetcher {
throw new HugeResultSet("The submitted search query " + externalUrlCriteria.getLike() + " is about to return " + results.getPagination().get("count") + " results... Please submit a more detailed search query"); throw new HugeResultSet("The submitted search query " + externalUrlCriteria.getLike() + " is about to return " + results.getPagination().get("count") + " results... Please submit a more detailed search query");
Optional<Results> optionalResults = pages.parallelStream() Optional<Results> optionalResults = pages.parallelStream()
.map(page -> getResultsFromUrl(path + "&page=" + page, jsonDataPath, jsonPaginationPath, contentType, replacedBody, requestType)) .map(page -> getResultsFromUrl(path + "&page=" + page, jsonDataPath, jsonPaginationPath, contentType, replacedBody, requestType, auth))
.filter(Objects::nonNull) .filter(Objects::nonNull)
.reduce((result1, result2) -> { .reduce((result1, result2) -> {
result1.getResults().addAll(result2.getResults()); result1.getResults().addAll(result2.getResults());
@ -387,7 +415,7 @@ public class RemoteFetcher {
JsonNode jsonBody = new ObjectMapper().readTree(replacedBody); JsonNode jsonBody = new ObjectMapper().readTree(replacedBody);
entity = new HttpEntity<>(jsonBody, headers); entity = new HttpEntity<>(jsonBody, headers);
response = restTemplate.exchange(replacedPath, HttpMethod.resolve(requestType), entity, String.class); response = restTemplate.exchange(replacedPath, HttpMethod.valueOf(requestType), entity, String.class);
if (response.getStatusCode() == HttpStatus.OK) { if (response.getStatusCode() == HttpStatus.OK) {
if (response.getHeaders().get("Content-Type").get(0).contains("json")) { if (response.getHeaders().get("Content-Type").get(0).contains("json")) {
DocumentContext jsonContext = JsonPath.parse(response.getBody()); DocumentContext jsonContext = JsonPath.parse(response.getBody());
@ -403,12 +431,12 @@ public class RemoteFetcher {
} }
protected Results getResultsFromUrl(String urlString, DataUrlConfiguration jsonDataPath, String jsonPaginationPath, String contentType, String requestBody, String requestType) { protected Results getResultsFromUrl(String urlString, DataUrlConfiguration jsonDataPath, String jsonPaginationPath, String contentType, String requestBody, String requestType, String auth) {
try { try {
RestTemplate restTemplate = new RestTemplate(); //RestTemplate restTemplate = new RestTemplate(new SimpleClientHttpRequestFactory());
HttpHeaders headers = new HttpHeaders(); //HttpHeaders headers = new HttpHeaders();
HttpEntity<JsonNode> entity; //HttpEntity<JsonNode> entity;
ResponseEntity<String> response; ResponseEntity<String> response;
/* /*
* URL url = new URL(urlString.replaceAll(" ", "%20")); * URL url = new URL(urlString.replaceAll(" ", "%20"));
@ -416,14 +444,27 @@ public class RemoteFetcher {
* HttpURLConnection con = (HttpURLConnection) url.openConnection(); * HttpURLConnection con = (HttpURLConnection) url.openConnection();
* con.setRequestMethod("GET"); * con.setRequestMethod("GET");
*/ */
if (contentType != null && !contentType.isEmpty()) { /* if (contentType != null && !contentType.isEmpty()) {
headers.setAccept(Collections.singletonList(MediaType.valueOf(contentType))); headers.setAccept(Collections.singletonList(MediaType.valueOf(contentType)));
headers.setContentType(MediaType.valueOf(contentType)); headers.setContentType(MediaType.valueOf(contentType));
} }
if (auth != null) {
headers.set("Authorization", auth);
}*/
JsonNode jsonBody = new ObjectMapper().readTree(requestBody); JsonNode jsonBody = new ObjectMapper().readTree(requestBody);
entity = new HttpEntity<>(jsonBody, headers); // entity = new HttpEntity<>(jsonBody, headers);
response = restTemplate.exchange(urlString, HttpMethod.resolve(requestType), entity, String.class);
response = this.client.method(HttpMethod.valueOf(requestType)).uri(urlString).headers(httpHeaders -> {
if (contentType != null && !contentType.isEmpty()) {
httpHeaders.setAccept(Collections.singletonList(MediaType.valueOf(contentType)));
httpHeaders.setContentType(MediaType.valueOf(contentType));
}
if (auth != null) {
httpHeaders.set("Authorization", auth);
}
}).bodyValue(jsonBody).retrieve().toEntity(String.class).block();
//response = restTemplate.exchange(urlString, HttpMethod.resolve(requestType), entity, String.class);
if (response.getStatusCode() == HttpStatus.OK) { // success if (response.getStatusCode() == HttpStatus.OK) { // success
//do here all the parsing //do here all the parsing
Results results = new Results(); Results results = new Results();
@ -431,7 +472,7 @@ public class RemoteFetcher {
DocumentContext jsonContext = JsonPath.parse(response.getBody()); DocumentContext jsonContext = JsonPath.parse(response.getBody());
if (jsonDataPath.getFieldsUrlConfiguration().getPath() != null) { if (jsonDataPath.getFieldsUrlConfiguration().getPath() != null) {
results = RemoteFetcherUtils.getFromJsonWithRecursiveFetching(jsonContext, jsonDataPath, this, requestBody, requestType); results = RemoteFetcherUtils.getFromJsonWithRecursiveFetching(jsonContext, jsonDataPath, this, requestBody, requestType, auth);
} else if (jsonDataPath.getFieldsUrlConfiguration().getFirstName() != null) { } else if (jsonDataPath.getFieldsUrlConfiguration().getFirstName() != null) {
results = RemoteFetcherUtils.getFromJsonWithFirstAndLastName(jsonContext, jsonDataPath); results = RemoteFetcherUtils.getFromJsonWithFirstAndLastName(jsonContext, jsonDataPath);
} else { } else {
@ -553,8 +594,18 @@ public class RemoteFetcher {
return null; return null;
} }
private String parseBodyString(String bodyString) {
String finalBodyString = bodyString;
if (bodyString.contains("{env:")) {
int index = bodyString.indexOf("{env: ");
while (index >= 0) {
int endIndex = bodyString.indexOf("}", index + 6);
String envName = bodyString.substring(index + 6, endIndex);
finalBodyString = finalBodyString.replace("{env: " + envName + "}", System.getenv(envName));
index = bodyString.indexOf("{env: ", index + 6);
}
}
return finalBodyString;
}
} }

View File

@ -28,7 +28,7 @@ public class RemoteFetcherUtils {
new HashMap<>(1, 1)); new HashMap<>(1, 1));
} }
public static Results getFromJsonWithRecursiveFetching(DocumentContext jsonContext, DataUrlConfiguration jsonDataPath, RemoteFetcher remoteFetcher, String requestBody, String requestType) { public static Results getFromJsonWithRecursiveFetching(DocumentContext jsonContext, DataUrlConfiguration jsonDataPath, RemoteFetcher remoteFetcher, String requestBody, String requestType, String auth) {
Results results = new Results(parseData(jsonContext, jsonDataPath), Results results = new Results(parseData(jsonContext, jsonDataPath),
new HashMap<>(1, 1)); new HashMap<>(1, 1));
@ -37,7 +37,7 @@ public class RemoteFetcherUtils {
externalUrlCriteria.setPath(result.get("path")); externalUrlCriteria.setPath(result.get("path"));
externalUrlCriteria.setHost(result.get("host")); externalUrlCriteria.setHost(result.get("host"));
String replacedPath = remoteFetcher.replaceCriteriaOnUrl(jsonDataPath.getUrlConfiguration().getUrl(), externalUrlCriteria, jsonDataPath.getUrlConfiguration().getFirstpage(), jsonDataPath.getUrlConfiguration().getQueries()); String replacedPath = remoteFetcher.replaceCriteriaOnUrl(jsonDataPath.getUrlConfiguration().getUrl(), externalUrlCriteria, jsonDataPath.getUrlConfiguration().getFirstpage(), jsonDataPath.getUrlConfiguration().getQueries());
return remoteFetcher.getResultsFromUrl(replacedPath, jsonDataPath.getUrlConfiguration().getData(), jsonDataPath.getUrlConfiguration().getData().getPath(), jsonDataPath.getUrlConfiguration().getContentType(), requestBody, requestType); return remoteFetcher.getResultsFromUrl(replacedPath, jsonDataPath.getUrlConfiguration().getData(), jsonDataPath.getUrlConfiguration().getData().getPath(), jsonDataPath.getUrlConfiguration().getContentType(), requestBody, requestType, auth);
}).filter(Objects::nonNull).map(results1 -> results1.getResults().get(0)).collect(Collectors.toList()); }).filter(Objects::nonNull).map(results1 -> results1.getResults().get(0)).collect(Collectors.toList());
return new Results(multiResults, new HashMap<>(1, 1)); return new Results(multiResults, new HashMap<>(1, 1));
} }
@ -98,8 +98,22 @@ public class RemoteFetcherUtils {
} }
} else { } else {
value = value.replace("'", ""); value = value.replace("'", "");
if (stringObjectMap.containsKey(value)) { if (value.contains(".")) {
parsedData.get(parsedData.size() - 1).put(field.getName().equals("types") ? "tags" : value, normalizeValue(stringObjectMap.get(value), (field.getName().equals("types") || field.getName().equals("uri")))); String[] parts = value.split("\\.");
Map<String, Object> tempMap = stringObjectMap;
for (int i = 0; i < parts.length; i++) {
if (tempMap.containsKey(parts[i])) {
if (i + 1 < parts.length) {
tempMap = (Map<String, Object>) tempMap.get(parts[i]);
} else {
parsedData.get(parsedData.size() - 1).put(field.getName().equals("types") ? "tags" : value, normalizeValue(tempMap.get(parts[i]), (field.getName().equals("types") || field.getName().equals("uri"))));
}
}
}
} else {
if (stringObjectMap.containsKey(value)) {
parsedData.get(parsedData.size() - 1).put(field.getName().equals("types") ? "tags" : value, normalizeValue(stringObjectMap.get(value), (field.getName().equals("types") || field.getName().equals("uri"))));
}
} }
} }
} }
@ -135,7 +149,8 @@ public class RemoteFetcherUtils {
} }
} }
} else if (value instanceof Map) { } else if (value instanceof Map) {
return ((Map<String, String>)value).get("content"); String key = ((Map<String, String>)value).containsKey("$") ? "$" : "content";
return ((Map<String, String>)value).get(key);
} }
return value != null ? value.toString() : null; return value != null ? value.toString() : null;
} }

View File

@ -10,11 +10,62 @@ import java.util.List;
import java.util.Map; import java.util.Map;
public class AutoCompleteData extends ComboBoxData<AutoCompleteData> { public class AutoCompleteData extends ComboBoxData<AutoCompleteData> {
public static class AuthAutoCompleteData {
private String url;
private String method;
private String body;
private String path;
private String type;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
public static class AutoCompleteSingleData { public static class AutoCompleteSingleData {
private int autocompleteType; private int autocompleteType;
private String url; private String url;
private ComboBoxData.Option autoCompleteOptions; private ComboBoxData.Option autoCompleteOptions;
private String optionsRoot; private String optionsRoot;
private Boolean hasAuth;
private AuthAutoCompleteData auth;
private String method;
public int getAutocompleteType() { public int getAutocompleteType() {
return autocompleteType; return autocompleteType;
@ -38,12 +89,36 @@ public class AutoCompleteData extends ComboBoxData<AutoCompleteData> {
this.url = url; this.url = url;
} }
public Boolean getHasAuth() {
return hasAuth;
}
public void setHasAuth(Boolean hasAuth) {
this.hasAuth = hasAuth;
}
public AuthAutoCompleteData getAuth() {
return auth;
}
public void setAuth(AuthAutoCompleteData auth) {
this.auth = auth;
}
public ComboBoxData.Option getAutoCompleteOptions() { public ComboBoxData.Option getAutoCompleteOptions() {
return autoCompleteOptions; return autoCompleteOptions;
} }
public void setAutoCompleteOptions(ComboBoxData.Option autoCompleteOptions) { public void setAutoCompleteOptions(ComboBoxData.Option autoCompleteOptions) {
this.autoCompleteOptions = autoCompleteOptions; this.autoCompleteOptions = autoCompleteOptions;
} }
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
} }
private Boolean multiAutoComplete; private Boolean multiAutoComplete;
@ -70,11 +145,22 @@ public class AutoCompleteData extends ComboBoxData<AutoCompleteData> {
parent.setAttribute("url", singleData.url); parent.setAttribute("url", singleData.url);
parent.setAttribute("optionsRoot", singleData.optionsRoot); parent.setAttribute("optionsRoot", singleData.optionsRoot);
parent.setAttribute("autoCompleteType", Integer.toString(singleData.autocompleteType)); parent.setAttribute("autoCompleteType", Integer.toString(singleData.autocompleteType));
parent.setAttribute("hasAuth", Boolean.toString(singleData.hasAuth));
parent.setAttribute("method", singleData.method);
Element element = doc.createElement("option"); Element element = doc.createElement("option");
element.setAttribute("label", singleData.autoCompleteOptions.getLabel()); element.setAttribute("label", singleData.autoCompleteOptions.getLabel());
element.setAttribute("value", singleData.autoCompleteOptions.getValue()); element.setAttribute("value", singleData.autoCompleteOptions.getValue());
element.setAttribute("source", singleData.autoCompleteOptions.getSource()); element.setAttribute("source", singleData.autoCompleteOptions.getSource());
parent.appendChild(element); parent.appendChild(element);
if (singleData.hasAuth) {
Element authElement = doc.createElement("auth");
authElement.setAttribute("url", singleData.auth.url);
authElement.setAttribute("method", singleData.auth.method);
authElement.setAttribute("body", singleData.auth.body);
authElement.setAttribute("path", singleData.auth.path);
authElement.setAttribute("type", singleData.auth.type);
parent.appendChild(authElement);
}
root.appendChild(parent); root.appendChild(parent);
} }
return root; return root;
@ -108,6 +194,8 @@ public class AutoCompleteData extends ComboBoxData<AutoCompleteData> {
} else { } else {
singleData.autocompleteType = AutocompleteType.fromValue(Integer.parseInt(item.getAttribute("autoCompleteType"))).getValue(); singleData.autocompleteType = AutocompleteType.fromValue(Integer.parseInt(item.getAttribute("autoCompleteType"))).getValue();
} }
singleData.hasAuth = Boolean.parseBoolean(item.getAttribute("hasAuth"));
singleData.method = item.hasAttribute("method") ? item.getAttribute("method") : "GET";
Element optionElement = (Element) item.getElementsByTagName("option").item(0); Element optionElement = (Element) item.getElementsByTagName("option").item(0);
if (optionElement != null) { if (optionElement != null) {
singleData.autoCompleteOptions = new Option(); singleData.autoCompleteOptions = new Option();
@ -116,6 +204,17 @@ public class AutoCompleteData extends ComboBoxData<AutoCompleteData> {
singleData.autoCompleteOptions.setSource(optionElement.getAttribute("source")); singleData.autoCompleteOptions.setSource(optionElement.getAttribute("source"));
singleData.autoCompleteOptions.setUri(optionElement.getAttribute("uri")); singleData.autoCompleteOptions.setUri(optionElement.getAttribute("uri"));
} }
if (singleData.hasAuth) {
Element authElement = (Element) item.getElementsByTagName("auth").item(0);
if (authElement != null) {
singleData.auth = new AuthAutoCompleteData();
singleData.auth.setUrl(authElement.getAttribute("url"));
singleData.auth.setMethod(authElement.getAttribute("method"));
singleData.auth.setBody(authElement.getAttribute("body"));
singleData.auth.setPath(authElement.getAttribute("path"));
singleData.auth.setType(authElement.getAttribute("type"));
}
}
} }
@Override @Override
@ -141,6 +240,8 @@ public class AutoCompleteData extends ComboBoxData<AutoCompleteData> {
this.autoCompleteSingleDataList.get(i).autoCompleteOptions = new Option(); this.autoCompleteSingleDataList.get(i).autoCompleteOptions = new Option();
this.autoCompleteSingleDataList.get(i).url = (String) singleData.get("url"); this.autoCompleteSingleDataList.get(i).url = (String) singleData.get("url");
this.autoCompleteSingleDataList.get(i).optionsRoot = (String) singleData.get("optionsRoot"); this.autoCompleteSingleDataList.get(i).optionsRoot = (String) singleData.get("optionsRoot");
this.autoCompleteSingleDataList.get(i).hasAuth = (Boolean) singleData.get("hasAuth");
this.autoCompleteSingleDataList.get(i).method = singleData.containsKey("method") ? (String) singleData.get("method") : "GET";
if (singleData.get("autoCompleteType") == null) { if (singleData.get("autoCompleteType") == null) {
this.autoCompleteSingleDataList.get(i).autocompleteType = AutocompleteType.UNCACHED.getValue(); this.autoCompleteSingleDataList.get(i).autocompleteType = AutocompleteType.UNCACHED.getValue();
@ -154,6 +255,17 @@ public class AutoCompleteData extends ComboBoxData<AutoCompleteData> {
this.autoCompleteSingleDataList.get(i).autoCompleteOptions.setSource(options.get("source")); this.autoCompleteSingleDataList.get(i).autoCompleteOptions.setSource(options.get("source"));
this.autoCompleteSingleDataList.get(i).autoCompleteOptions.setUri(options.get("uri")); this.autoCompleteSingleDataList.get(i).autoCompleteOptions.setUri(options.get("uri"));
} }
if (this.autoCompleteSingleDataList.get(i).hasAuth) {
Map<String, String> auth = (Map<String, String>) singleData.get("auth");
if (auth != null) {
this.autoCompleteSingleDataList.get(i).auth = new AuthAutoCompleteData();
this.autoCompleteSingleDataList.get(i).auth.setUrl(auth.get("url"));
this.autoCompleteSingleDataList.get(i).auth.setType(auth.get("type"));
this.autoCompleteSingleDataList.get(i).auth.setPath(auth.get("path"));
this.autoCompleteSingleDataList.get(i).auth.setBody(auth.get("body"));
this.autoCompleteSingleDataList.get(i).auth.setMethod(auth.get("method"));
}
}
i++; i++;
} }
} }
@ -190,6 +302,8 @@ public class AutoCompleteData extends ComboBoxData<AutoCompleteData> {
node.appendChild(autoCompleteSingles.item(i)); node.appendChild(autoCompleteSingles.item(i));
node.setAttribute("url", item.getAttribute("url")); node.setAttribute("url", item.getAttribute("url"));
node.setAttribute("optionsRoot", item.getAttribute("optionsRoot")); node.setAttribute("optionsRoot", item.getAttribute("optionsRoot"));
node.setAttribute("hasAuth", item.getAttribute("hasAuth"));
node.setAttribute("method", item.hasAttribute("method") ? item.getAttribute("method") : "GET");
autoCompletes.add(singleToMap(node)); autoCompletes.add(singleToMap(node));
} }
} }
@ -214,6 +328,16 @@ public class AutoCompleteData extends ComboBoxData<AutoCompleteData> {
return dataMap; return dataMap;
} }
private Map<String, Object> authToMap(Element item){
HashMap dataMap = new HashMap();
dataMap.put("url", item != null ? item.getAttribute("url") : "");
dataMap.put("method", item != null ? item.getAttribute("method") : "");
dataMap.put("body", item != null ? item.getAttribute("body") : "");
dataMap.put("path", item != null ? item.getAttribute("path") : "");
dataMap.put("type", item != null ? item.getAttribute("type") : "");
return dataMap;
}
private Map<String, Object> singleToMap(Element item) { private Map<String, Object> singleToMap(Element item) {
Map<String, Object> dataMap = new HashMap<>(); Map<String, Object> dataMap = new HashMap<>();
if (!item.getAttribute("autoCompleteType").isEmpty()) { if (!item.getAttribute("autoCompleteType").isEmpty()) {
@ -221,8 +345,12 @@ public class AutoCompleteData extends ComboBoxData<AutoCompleteData> {
} }
dataMap.put("optionsRoot", item != null ? item.getAttribute("optionsRoot") : ""); dataMap.put("optionsRoot", item != null ? item.getAttribute("optionsRoot") : "");
dataMap.put("url", item != null ? item.getAttribute("url") : ""); dataMap.put("url", item != null ? item.getAttribute("url") : "");
dataMap.put("hasAuth", item != null ? item.getAttribute("hasAuth") : "false");
Element optionElement = (Element) item.getElementsByTagName("option").item(0); Element optionElement = (Element) item.getElementsByTagName("option").item(0);
dataMap.put("autoCompleteOptions", item != null ? optionToMap(optionElement) : null); dataMap.put("autoCompleteOptions", item != null ? optionToMap(optionElement) : null);
Element authElement = (Element) item.getElementsByTagName("auth").item(0);
dataMap.put("auth", item != null ? authToMap(authElement) : null);
dataMap.put("method", item != null && item.hasAttribute("method") ? item.getAttribute("method") : "GET");
return dataMap; return dataMap;
} }

View File

@ -12,11 +12,22 @@ export interface AutoCompleteFieldData extends FieldData {
multiAutoComplete: boolean; multiAutoComplete: boolean;
} }
export interface AuthAutoCompleteData extends FieldData {
url: string;
method: string;
body: string;
path: string;
type: string;
}
export interface AutoCompleteSingleData extends FieldData { export interface AutoCompleteSingleData extends FieldData {
url: string; url: string;
optionsRoot: string; optionsRoot: string;
autoCompleteOptions: FieldDataOption; autoCompleteOptions: FieldDataOption;
autocompleteType: number; autocompleteType: number;
hasAuth: boolean;
method: string;
auth: AuthAutoCompleteData;
} }
export interface CheckBoxFieldData extends FieldData { export interface CheckBoxFieldData extends FieldData {

View File

@ -20,10 +20,10 @@ export class PrefillingService {
} }
public getPrefillingDataset(pid: string, profileId: string, configId: string): Observable<DatasetWizardModel> { public getPrefillingDataset(pid: string, profileId: string, configId: string): Observable<DatasetWizardModel> {
return this.http.get<DatasetWizardModel>(this.actionUrl + '/generate/' + encodeURIComponent(pid) + '?configId=' + encodeURIComponent(configId) + '&profileId=' + encodeURIComponent(profileId), { headers: this.headers }); return this.http.get<DatasetWizardModel>(this.actionUrl + 'generate/' + encodeURIComponent(pid) + '?configId=' + encodeURIComponent(configId) + '&profileId=' + encodeURIComponent(profileId), { headers: this.headers });
} }
public getPrefillingDatasetUsingData(data: any, profileId: string, configId: string): Observable<DatasetWizardModel> { public getPrefillingDatasetUsingData(data: any, profileId: string, configId: string): Observable<DatasetWizardModel> {
return this.http.post<DatasetWizardModel>(this.actionUrl + '/generateUsingData' + '?configId=' + encodeURIComponent(configId) + '&profileId=' + encodeURIComponent(profileId), data, { headers: this.headers }); return this.http.post<DatasetWizardModel>(this.actionUrl + 'generateUsingData' + '?configId=' + encodeURIComponent(configId) + '&profileId=' + encodeURIComponent(profileId), data, { headers: this.headers });
} }
} }

View File

@ -3,6 +3,7 @@ import { DatasetProfileComboBoxType } from '@app/core/common/enum/dataset-profil
import { FieldDataOptionEditorModel } from './field-data-option-editor-model'; import { FieldDataOptionEditorModel } from './field-data-option-editor-model';
import { FormGroup, Validators } from '@angular/forms'; import { FormGroup, Validators } from '@angular/forms';
import { AutoCompleteFieldData, AutoCompleteSingleData } from '@app/core/model/dataset-profile-definition/field-data/field-data'; import { AutoCompleteFieldData, AutoCompleteSingleData } from '@app/core/model/dataset-profile-definition/field-data/field-data';
import { AuthFieldEditorModel } from './auto-complete-auth-field-data.model';
export class AutoCompleteSingleDataEditorModel extends FieldDataEditorModel<AutoCompleteSingleDataEditorModel> { export class AutoCompleteSingleDataEditorModel extends FieldDataEditorModel<AutoCompleteSingleDataEditorModel> {
@ -11,6 +12,9 @@ export class AutoCompleteSingleDataEditorModel extends FieldDataEditorModel<Auto
public autoCompleteOptions: FieldDataOptionEditorModel = new FieldDataOptionEditorModel(); public autoCompleteOptions: FieldDataOptionEditorModel = new FieldDataOptionEditorModel();
public autoCompleteType: number; public autoCompleteType: number;
public method: string;
public hasAuth: boolean;
public auth: AuthFieldEditorModel = new AuthFieldEditorModel();
//public multiAutoCompleteOptions: FieldDataOptionEditorModel = new FieldDataOptionEditorModel(); //public multiAutoCompleteOptions: FieldDataOptionEditorModel = new FieldDataOptionEditorModel();
buildForm(disabled: boolean = false, skipDisable: Array<String> = []): FormGroup { buildForm(disabled: boolean = false, skipDisable: Array<String> = []): FormGroup {
@ -18,9 +22,12 @@ export class AutoCompleteSingleDataEditorModel extends FieldDataEditorModel<Auto
label: [{ value: this.label, disabled: (disabled && !skipDisable.includes('AutoCompleteSingleDataEditorModel.label')) }], label: [{ value: this.label, disabled: (disabled && !skipDisable.includes('AutoCompleteSingleDataEditorModel.label')) }],
url: [{ value: this.url, disabled: (disabled && !skipDisable.includes('AutoCompleteSingleDataEditorModel.url')) },[Validators.required]], url: [{ value: this.url, disabled: (disabled && !skipDisable.includes('AutoCompleteSingleDataEditorModel.url')) },[Validators.required]],
optionsRoot: [{ value: this.optionsRoot, disabled: (disabled && !skipDisable.includes('AutoCompleteSingleDataEditorModel.optionsRoot')) }, [Validators.required]], optionsRoot: [{ value: this.optionsRoot, disabled: (disabled && !skipDisable.includes('AutoCompleteSingleDataEditorModel.optionsRoot')) }, [Validators.required]],
autoCompleteType: [{ value: this.autoCompleteType, disabled: (disabled && !skipDisable.includes('AutoCompleteSingleDataEditorModel.autoCompleteType')) }] autoCompleteType: [{ value: this.autoCompleteType, disabled: (disabled && !skipDisable.includes('AutoCompleteSingleDataEditorModel.autoCompleteType')) }],
hasAuth: [{ value: this.hasAuth, disabled: (disabled && !skipDisable.includes('AutoCompleteSingleDataEditorModel.hasAuth')) }],
method: [{ value: this.method, disabled: (disabled && !skipDisable.includes('AutoCompleteSingleDataEditorModel.method')) }]
}); });
formGroup.addControl('autoCompleteOptions', this.autoCompleteOptions.buildForm(disabled, skipDisable)); formGroup.addControl('autoCompleteOptions', this.autoCompleteOptions.buildForm(disabled, skipDisable));
formGroup.addControl('auth', this.auth.buildForm(disabled, skipDisable));
return formGroup; return formGroup;
} }
@ -30,7 +37,10 @@ export class AutoCompleteSingleDataEditorModel extends FieldDataEditorModel<Auto
this.label = item.label; this.label = item.label;
this.optionsRoot = item.optionsRoot; this.optionsRoot = item.optionsRoot;
this.autoCompleteType = item.autocompleteType; this.autoCompleteType = item.autocompleteType;
this.hasAuth = item.hasAuth;
this.method = item.method ? item.method : 'GET';
this.autoCompleteOptions = new FieldDataOptionEditorModel().fromModel(item.autoCompleteOptions); this.autoCompleteOptions = new FieldDataOptionEditorModel().fromModel(item.autoCompleteOptions);
this.auth = new AuthFieldEditorModel().fromModel(item.auth);
return this; return this;
} }
} }

View File

@ -11,41 +11,47 @@
[formControl]="form.get('data').get('label')"> [formControl]="form.get('data').get('label')">
</mat-form-field> </mat-form-field>
<h6 class="col-12" style="font-weight: bold">{{'DATASET-PROFILE-EDITOR.STEPS.FORM.FIELD.FIELDS.FIELD-AUTOCOMPLETE-SOURCE-TITLE' | translate}}</h6> <h6 class="col-12" style="font-weight: bold">{{'DATASET-PROFILE-EDITOR.STEPS.FORM.FIELD.FIELDS.FIELD-AUTOCOMPLETE-SOURCE-TITLE' | translate}}</h6>
<div class="col-12 d-flex align-items-center" style="margin-bottom: 1em;"> <div class="col-12 d-flex align-items-center" style="margin-bottom: 1em;">
<button mat-raised-button <button mat-raised-button
type="button" type="button"
(click)="addSource()" (click)="addSource()"
style="margin-right: 2em;" style="margin-right: 2em;"
> >
<!-- [ngClass]="{'text-danger':form.get('data').errors?.emptyArray && form.touched}" --> <!-- [ngClass]="{'text-danger':form.get('data').errors?.emptyArray && form.touched}" -->
{{'DATASET-PROFILE-EDITOR.STEPS.FORM.FIELD.FIELDS.FIELD-AUTOCOMPLETE-ADD_SOURCE' | translate}} {{'DATASET-PROFILE-EDITOR.STEPS.FORM.FIELD.FIELDS.FIELD-AUTOCOMPLETE-ADD_SOURCE' | translate}}
</button> </button>
<!-- *ngIf="form.get('data').errors?.emptyArray && form.get('data').touched" --> <!-- *ngIf="form.get('data').errors?.emptyArray && form.get('data').touched" -->
<div class="d-flex" *ngIf="form.get('data').errors?.emptyArray && form.get('data').touched"> <div class="d-flex" *ngIf="form.get('data').errors?.emptyArray && form.get('data').touched">
<mat-icon <mat-icon
class="text-danger" class="text-danger"
matTooltip="At least one source must be provided." matTooltip="At least one source must be provided."
>warning_amber</mat-icon> >warning_amber</mat-icon>
<small class="text-danger">{{'DATASET-PROFILE-EDITOR.STEPS.FORM.FIELD.ERROR-MESSAGES.FIELD-OTHER-SOURCES-REQUIRED'| translate}}</small> <small class="text-danger">{{'DATASET-PROFILE-EDITOR.STEPS.FORM.FIELD.ERROR-MESSAGES.FIELD-OTHER-SOURCES-REQUIRED'| translate}}</small>
</div> </div>
</div> </div>
</div> </div>
<div *ngFor="let singleForm of multiForm.controls; let i = index" class="row"> <div *ngFor="let singleForm of multiForm.controls; let i = index" class="row">
<mat-form-field class="col-12"> <!-- <mat-form-field class="col-12">
<mat-label>{{'DATASET-PROFILE-EDITOR.STEPS.FORM.FIELD.FIELDS.FIELD-AUTOCOMPLETE-TYPE' | translate}}</mat-label> <mat-label>{{'DATASET-PROFILE-EDITOR.STEPS.FORM.FIELD.FIELDS.FIELD-AUTOCOMPLETE-TYPE' | translate}}</mat-label>
<mat-select [formControl]="singleForm.get('autoCompleteType')"> <mat-select [formControl]="singleForm.get('autoCompleteType')">
<mat-option [value]="0">{{'DATASET-PROFILE-EDITOR.STEPS.FORM.FIELD.FIELDS.FIELD-AUTOCOMPLETE-TYPE-UNCACHED' | translate}}</mat-option> <mat-option [value]="0">{{'DATASET-PROFILE-EDITOR.STEPS.FORM.FIELD.FIELDS.FIELD-AUTOCOMPLETE-TYPE-UNCACHED' | translate}}</mat-option>
<mat-option [value]="1">{{'DATASET-PROFILE-EDITOR.STEPS.FORM.FIELD.FIELDS.FIELD-AUTOCOMPLETE-TYPE-CACHED' | translate}}</mat-option> <mat-option [value]="1">{{'DATASET-PROFILE-EDITOR.STEPS.FORM.FIELD.FIELDS.FIELD-AUTOCOMPLETE-TYPE-CACHED' | translate}}</mat-option>
</mat-select> </mat-select>
</mat-form-field> -->
<mat-form-field class="col-md-6">
<mat-label>{{'DATASET-PROFILE-EDITOR.STEPS.FORM.FIELD.FIELDS.FIELD-AUTOCOMPLETE-AUTH-METHOD' | translate}}</mat-label>
<mat-select [formControl]="singleForm.get('method')">
<mat-option *ngFor="let method of htmlMethods | keyvalue" [value]="method.value">{{method.value}}</mat-option>
</mat-select>
</mat-form-field> </mat-form-field>
<mat-form-field class="col-md-12"> <mat-form-field class="col-md-6">
<mat-label>{{'DATASET-PROFILE-EDITOR.STEPS.FORM.FIELD.FIELDS.FIELD-AUTOCOMPLETE-URL' | translate}}</mat-label> <mat-label>{{'DATASET-PROFILE-EDITOR.STEPS.FORM.FIELD.FIELDS.FIELD-AUTOCOMPLETE-URL' | translate}}</mat-label>
<input matInput [formControl]="singleForm.get('url')"> <input matInput [formControl]="singleForm.get('url')">
<mat-error *ngIf="singleForm.get('url').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error> <mat-error *ngIf="singleForm.get('url').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
@ -70,6 +76,37 @@
<input matInput [formControl]="singleForm.get('autoCompleteOptions').get('source')"> <input matInput [formControl]="singleForm.get('autoCompleteOptions').get('source')">
<mat-error *ngIf="singleForm.get('autoCompleteOptions').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error> <mat-error *ngIf="singleForm.get('autoCompleteOptions').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field> </mat-form-field>
<mat-checkbox class="col-12" [formControl]="singleForm.get('hasAuth')">{{'DATASET-PROFILE-EDITOR.STEPS.FORM.FIELD.FIELDS.FIELD-AUTOCOMPLETE-HAS-AUTH' | translate}}</mat-checkbox>
<div *ngIf="singleForm.get('hasAuth').value === true" class="col-12">
<mat-form-field class="col-md-6">
<mat-label>{{'DATASET-PROFILE-EDITOR.STEPS.FORM.FIELD.FIELDS.FIELD-AUTOCOMPLETE-AUTH-METHOD' | translate}}</mat-label>
<mat-select [formControl]="singleForm.get('auth').get('method')">
<mat-option *ngFor="let method of htmlMethods | keyvalue" [value]="method.value">{{method.value}}</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field class="col-md-6">
<mat-label>{{'DATASET-PROFILE-EDITOR.STEPS.FORM.FIELD.FIELDS.FIELD-AUTOCOMPLETE-URL' | translate}}</mat-label>
<input matInput [formControl]="singleForm.get('auth').get('url')">
<mat-error *ngIf="singleForm.get('auth').get('url').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field>
<mat-form-field class="col-md-6">
<mat-label>{{'DATASET-PROFILE-EDITOR.STEPS.FORM.FIELD.FIELDS.FIELD-AUTOCOMPLETE-AUTH-TYPE' | translate}}</mat-label>
<mat-select [formControl]="singleForm.get('auth').get('type')">
<mat-option *ngFor="let type of authTypes | keyvalue" [value]="type.value">{{type.value}}</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field class="col-md-6">
<mat-label>{{'DATASET-PROFILE-EDITOR.STEPS.FORM.FIELD.FIELDS.FIELD-AUTOCOMPLETE-OPTIONS-ROOT' | translate}}</mat-label>
<input matInput [formControl]="singleForm.get('auth').get('path')">
<mat-error *ngIf="singleForm.get('auth').get('path').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field>
<mat-form-field class="col-md-12">
<mat-label>{{'DATASET-PROFILE-EDITOR.STEPS.FORM.FIELD.FIELDS.FIELD-AUTOCOMPLETE-AUTH-BODY' | translate}}</mat-label>
<textarea matInput [formControl]="singleForm.get('auth').get('body')"></textarea>
<mat-error *ngIf="singleForm.get('auth').get('body').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field>
</div>
<button mat-button type="button" (click)="removeSource(i)"><mat-icon>delete</mat-icon></button> <button mat-button type="button" (click)="removeSource(i)"><mat-icon>delete</mat-icon></button>
</div> </div>

View File

@ -3,6 +3,8 @@ import { FormGroup, FormArray, AbstractControl } from '@angular/forms';
import { DatasetProfileComboBoxType } from '../../../../../../../core/common/enum/dataset-profile-combo-box-type'; import { DatasetProfileComboBoxType } from '../../../../../../../core/common/enum/dataset-profile-combo-box-type';
import { AutoCompleteFieldDataEditorModel } from '../../../../admin/field-data/auto-complete-field-data-editor-model'; import { AutoCompleteFieldDataEditorModel } from '../../../../admin/field-data/auto-complete-field-data-editor-model';
import { AutoCompleteSingleDataEditorModel } from '@app/ui/admin/dataset-profile/admin/field-data/auto-complete-single-data'; import { AutoCompleteSingleDataEditorModel } from '@app/ui/admin/dataset-profile/admin/field-data/auto-complete-single-data';
import { HtmlMethod } from '@app/core/model/dataset-profile-definition/html-method.enum';
import { AuthType } from '@app/core/model/dataset-profile-definition/auth-type.enum';
@Component({ @Component({
selector: 'app-dataset-profile-editor-auto-complete-field-component', selector: 'app-dataset-profile-editor-auto-complete-field-component',
@ -11,6 +13,9 @@ import { AutoCompleteSingleDataEditorModel } from '@app/ui/admin/dataset-profile
}) })
export class DatasetProfileEditorAutoCompleteFieldComponent implements OnInit { export class DatasetProfileEditorAutoCompleteFieldComponent implements OnInit {
public htmlMethods = HtmlMethod;
public authTypes = AuthType;
@Input() form: FormGroup; @Input() form: FormGroup;
private data: AutoCompleteFieldDataEditorModel = new AutoCompleteFieldDataEditorModel(); private data: AutoCompleteFieldDataEditorModel = new AutoCompleteFieldDataEditorModel();
multiForm: FormArray; multiForm: FormArray;

View File

@ -459,6 +459,10 @@
"FIELD-AUTOCOMPLETE-SOURCE": "Source", "FIELD-AUTOCOMPLETE-SOURCE": "Source",
"FIELD-AUTOCOMPLETE-URL": "Url", "FIELD-AUTOCOMPLETE-URL": "Url",
"FIELD-AUTOCOMPLETE-OPTIONS-ROOT": "Options Root", "FIELD-AUTOCOMPLETE-OPTIONS-ROOT": "Options Root",
"FIELD-AUTOCOMPLETE-HAS-AUTH": "Has Authentication",
"FIELD-AUTOCOMPLETE-AUTH-METHOD": "Method",
"FIELD-AUTOCOMPLETE-AUTH-BODY": "Request Body",
"FIELD-AUTOCOMPLETE-AUTH-TYPE": "Authentication Type",
"FIELD-DATE-PICKER-TITLE": "Date Picker", "FIELD-DATE-PICKER-TITLE": "Date Picker",
"FIELD-DATE-PICKER-PLACEHOLDER": "Input Placeholder Text", "FIELD-DATE-PICKER-PLACEHOLDER": "Input Placeholder Text",
"FIELD-DATE-PICKER-LABEL": "Label", "FIELD-DATE-PICKER-LABEL": "Label",
@ -1755,7 +1759,7 @@
"UNLINK-ACCOUNT-DIALOG": { "UNLINK-ACCOUNT-DIALOG": {
"MESSAGE": "By clicking \"Confirm\", you accept the transfer of your ARGOS activity performed from this account to your main ARGOS account, which is the one that is listed first. By logging in again with the unlinked account, you will be able to start your ARGOS experience from scratch, in an empty dashboard.", "MESSAGE": "By clicking \"Confirm\", you accept the transfer of your ARGOS activity performed from this account to your main ARGOS account, which is the one that is listed first. By logging in again with the unlinked account, you will be able to start your ARGOS experience from scratch, in an empty dashboard.",
"CONFIRM": "Confirm", "CONFIRM": "Confirm",
"CANCEL": "Cancel" "CANCEL": "Cancel"
}, },
"SETTINGS": { "SETTINGS": {
"TITLE": "Einstellungen", "TITLE": "Einstellungen",

View File

@ -459,6 +459,10 @@
"FIELD-AUTOCOMPLETE-SOURCE": "Source", "FIELD-AUTOCOMPLETE-SOURCE": "Source",
"FIELD-AUTOCOMPLETE-URL": "Url", "FIELD-AUTOCOMPLETE-URL": "Url",
"FIELD-AUTOCOMPLETE-OPTIONS-ROOT": "Options Root", "FIELD-AUTOCOMPLETE-OPTIONS-ROOT": "Options Root",
"FIELD-AUTOCOMPLETE-HAS-AUTH": "Has Authentication",
"FIELD-AUTOCOMPLETE-AUTH-METHOD": "Method",
"FIELD-AUTOCOMPLETE-AUTH-BODY": "Request Body",
"FIELD-AUTOCOMPLETE-AUTH-TYPE": "Authentication Type",
"FIELD-DATE-PICKER-TITLE": "Date Picker", "FIELD-DATE-PICKER-TITLE": "Date Picker",
"FIELD-DATE-PICKER-PLACEHOLDER": "Input Placeholder Text", "FIELD-DATE-PICKER-PLACEHOLDER": "Input Placeholder Text",
"FIELD-DATE-PICKER-LABEL": "Label", "FIELD-DATE-PICKER-LABEL": "Label",
@ -1755,7 +1759,7 @@
"UNLINK-ACCOUNT-DIALOG": { "UNLINK-ACCOUNT-DIALOG": {
"MESSAGE": "By clicking \"Confirm\", you accept the transfer of your ARGOS activity performed from this account to your main ARGOS account, which is the one that is listed first. By logging in again with the unlinked account, you will be able to start your ARGOS experience from scratch, in an empty dashboard.", "MESSAGE": "By clicking \"Confirm\", you accept the transfer of your ARGOS activity performed from this account to your main ARGOS account, which is the one that is listed first. By logging in again with the unlinked account, you will be able to start your ARGOS experience from scratch, in an empty dashboard.",
"CONFIRM": "Confirm", "CONFIRM": "Confirm",
"CANCEL": "Cancel" "CANCEL": "Cancel"
}, },
"SETTINGS": { "SETTINGS": {
"TITLE": "Settings", "TITLE": "Settings",

View File

@ -459,6 +459,10 @@
"FIELD-AUTOCOMPLETE-SOURCE": "Fuente", "FIELD-AUTOCOMPLETE-SOURCE": "Fuente",
"FIELD-AUTOCOMPLETE-URL": "Url", "FIELD-AUTOCOMPLETE-URL": "Url",
"FIELD-AUTOCOMPLETE-OPTIONS-ROOT": "Optiones principales", "FIELD-AUTOCOMPLETE-OPTIONS-ROOT": "Optiones principales",
"FIELD-AUTOCOMPLETE-HAS-AUTH": "Has Authentication",
"FIELD-AUTOCOMPLETE-AUTH-METHOD": "Method",
"FIELD-AUTOCOMPLETE-AUTH-BODY": "Request Body",
"FIELD-AUTOCOMPLETE-AUTH-TYPE": "Authentication Type",
"FIELD-DATE-PICKER-TITLE": "Campo de entrada para fecha", "FIELD-DATE-PICKER-TITLE": "Campo de entrada para fecha",
"FIELD-DATE-PICKER-PLACEHOLDER": "Marcador de entrada", "FIELD-DATE-PICKER-PLACEHOLDER": "Marcador de entrada",
"FIELD-DATE-PICKER-LABEL": "Etiqueta", "FIELD-DATE-PICKER-LABEL": "Etiqueta",
@ -1756,7 +1760,7 @@
"UNLINK-ACCOUNT-DIALOG": { "UNLINK-ACCOUNT-DIALOG": {
"MESSAGE": "By clicking \"Confirm\", you accept the transfer of your ARGOS activity performed from this account to your main ARGOS account, which is the one that is listed first. By logging in again with the unlinked account, you will be able to start your ARGOS experience from scratch, in an empty dashboard.", "MESSAGE": "By clicking \"Confirm\", you accept the transfer of your ARGOS activity performed from this account to your main ARGOS account, which is the one that is listed first. By logging in again with the unlinked account, you will be able to start your ARGOS experience from scratch, in an empty dashboard.",
"CONFIRM": "Confirm", "CONFIRM": "Confirm",
"CANCEL": "Cancel" "CANCEL": "Cancel"
}, },
"SETTINGS": { "SETTINGS": {
"TITLE": "Configuración", "TITLE": "Configuración",

View File

@ -459,6 +459,10 @@
"FIELD-AUTOCOMPLETE-SOURCE": "Πηγή", "FIELD-AUTOCOMPLETE-SOURCE": "Πηγή",
"FIELD-AUTOCOMPLETE-URL": "Url", "FIELD-AUTOCOMPLETE-URL": "Url",
"FIELD-AUTOCOMPLETE-OPTIONS-ROOT": "Βάση Επιλογών", "FIELD-AUTOCOMPLETE-OPTIONS-ROOT": "Βάση Επιλογών",
"FIELD-AUTOCOMPLETE-HAS-AUTH": "Has Authentication",
"FIELD-AUTOCOMPLETE-AUTH-METHOD": "Method",
"FIELD-AUTOCOMPLETE-AUTH-BODY": "Request Body",
"FIELD-AUTOCOMPLETE-AUTH-TYPE": "Authentication Type",
"FIELD-DATE-PICKER-TITLE": "Επιλογή Ημερομηνίας", "FIELD-DATE-PICKER-TITLE": "Επιλογή Ημερομηνίας",
"FIELD-DATE-PICKER-PLACEHOLDER": "Τοποθέτηση placeholder", "FIELD-DATE-PICKER-PLACEHOLDER": "Τοποθέτηση placeholder",
"FIELD-DATE-PICKER-LABEL": "Ετικέτα", "FIELD-DATE-PICKER-LABEL": "Ετικέτα",
@ -1756,7 +1760,7 @@
"UNLINK-ACCOUNT-DIALOG": { "UNLINK-ACCOUNT-DIALOG": {
"MESSAGE": "By clicking \"Confirm\", you accept the transfer of your ARGOS activity performed from this account to your main ARGOS account, which is the one that is listed first. By logging in again with the unlinked account, you will be able to start your ARGOS experience from scratch, in an empty dashboard.", "MESSAGE": "By clicking \"Confirm\", you accept the transfer of your ARGOS activity performed from this account to your main ARGOS account, which is the one that is listed first. By logging in again with the unlinked account, you will be able to start your ARGOS experience from scratch, in an empty dashboard.",
"CONFIRM": "Confirm", "CONFIRM": "Confirm",
"CANCEL": "Cancel" "CANCEL": "Cancel"
}, },
"SETTINGS": { "SETTINGS": {
"TITLE": "Ρυθμίσεις", "TITLE": "Ρυθμίσεις",

View File

@ -459,6 +459,10 @@
"FIELD-AUTOCOMPLETE-SOURCE": "Izvor", "FIELD-AUTOCOMPLETE-SOURCE": "Izvor",
"FIELD-AUTOCOMPLETE-URL": "Url", "FIELD-AUTOCOMPLETE-URL": "Url",
"FIELD-AUTOCOMPLETE-OPTIONS-ROOT": "Opcija", "FIELD-AUTOCOMPLETE-OPTIONS-ROOT": "Opcija",
"FIELD-AUTOCOMPLETE-HAS-AUTH": "Has Authentication",
"FIELD-AUTOCOMPLETE-AUTH-METHOD": "Method",
"FIELD-AUTOCOMPLETE-AUTH-BODY": "Request Body",
"FIELD-AUTOCOMPLETE-AUTH-TYPE": "Authentication Type",
"FIELD-DATE-PICKER-TITLE": "Izbor datuma", "FIELD-DATE-PICKER-TITLE": "Izbor datuma",
"FIELD-DATE-PICKER-PLACEHOLDER": "Polje za unos", "FIELD-DATE-PICKER-PLACEHOLDER": "Polje za unos",
"FIELD-DATE-PICKER-LABEL": "Oznaka", "FIELD-DATE-PICKER-LABEL": "Oznaka",
@ -1756,7 +1760,7 @@
"UNLINK-ACCOUNT-DIALOG": { "UNLINK-ACCOUNT-DIALOG": {
"MESSAGE": "By clicking \"Confirm\", you accept the transfer of your ARGOS activity performed from this account to your main ARGOS account, which is the one that is listed first. By logging in again with the unlinked account, you will be able to start your ARGOS experience from scratch, in an empty dashboard.", "MESSAGE": "By clicking \"Confirm\", you accept the transfer of your ARGOS activity performed from this account to your main ARGOS account, which is the one that is listed first. By logging in again with the unlinked account, you will be able to start your ARGOS experience from scratch, in an empty dashboard.",
"CONFIRM": "Confirm", "CONFIRM": "Confirm",
"CANCEL": "Cancel" "CANCEL": "Cancel"
}, },
"SETTINGS": { "SETTINGS": {
"TITLE": "Postavke", "TITLE": "Postavke",

View File

@ -459,6 +459,10 @@
"FIELD-AUTOCOMPLETE-SOURCE": "Źródło", "FIELD-AUTOCOMPLETE-SOURCE": "Źródło",
"FIELD-AUTOCOMPLETE-URL": "Url", "FIELD-AUTOCOMPLETE-URL": "Url",
"FIELD-AUTOCOMPLETE-OPTIONS-ROOT": "$Opcje główne$", "FIELD-AUTOCOMPLETE-OPTIONS-ROOT": "$Opcje główne$",
"FIELD-AUTOCOMPLETE-HAS-AUTH": "Has Authentication",
"FIELD-AUTOCOMPLETE-AUTH-METHOD": "Method",
"FIELD-AUTOCOMPLETE-AUTH-BODY": "Request Body",
"FIELD-AUTOCOMPLETE-AUTH-TYPE": "Authentication Type",
"FIELD-DATE-PICKER-TITLE": "Wybierz datę", "FIELD-DATE-PICKER-TITLE": "Wybierz datę",
"FIELD-DATE-PICKER-PLACEHOLDER": "Wprowadź tekst zastępczy", "FIELD-DATE-PICKER-PLACEHOLDER": "Wprowadź tekst zastępczy",
"FIELD-DATE-PICKER-LABEL": "Etykieta", "FIELD-DATE-PICKER-LABEL": "Etykieta",
@ -1756,7 +1760,7 @@
"UNLINK-ACCOUNT-DIALOG": { "UNLINK-ACCOUNT-DIALOG": {
"MESSAGE": "By clicking \"Confirm\", you accept the transfer of your ARGOS activity performed from this account to your main ARGOS account, which is the one that is listed first. By logging in again with the unlinked account, you will be able to start your ARGOS experience from scratch, in an empty dashboard.", "MESSAGE": "By clicking \"Confirm\", you accept the transfer of your ARGOS activity performed from this account to your main ARGOS account, which is the one that is listed first. By logging in again with the unlinked account, you will be able to start your ARGOS experience from scratch, in an empty dashboard.",
"CONFIRM": "Confirm", "CONFIRM": "Confirm",
"CANCEL": "Cancel" "CANCEL": "Cancel"
}, },
"SETTINGS": { "SETTINGS": {
"TITLE": "Ustawienia", "TITLE": "Ustawienia",

View File

@ -459,6 +459,10 @@
"FIELD-AUTOCOMPLETE-SOURCE": "Fonte", "FIELD-AUTOCOMPLETE-SOURCE": "Fonte",
"FIELD-AUTOCOMPLETE-URL": "URL", "FIELD-AUTOCOMPLETE-URL": "URL",
"FIELD-AUTOCOMPLETE-OPTIONS-ROOT": "Opções de Raiz", "FIELD-AUTOCOMPLETE-OPTIONS-ROOT": "Opções de Raiz",
"FIELD-AUTOCOMPLETE-HAS-AUTH": "Has Authentication",
"FIELD-AUTOCOMPLETE-AUTH-METHOD": "Method",
"FIELD-AUTOCOMPLETE-AUTH-BODY": "Request Body",
"FIELD-AUTOCOMPLETE-AUTH-TYPE": "Authentication Type",
"FIELD-DATE-PICKER-TITLE": "Escolha da Data", "FIELD-DATE-PICKER-TITLE": "Escolha da Data",
"FIELD-DATE-PICKER-PLACEHOLDER": "Sugestão de Preenchimento", "FIELD-DATE-PICKER-PLACEHOLDER": "Sugestão de Preenchimento",
"FIELD-DATE-PICKER-LABEL": "Etiqueta", "FIELD-DATE-PICKER-LABEL": "Etiqueta",
@ -1756,7 +1760,7 @@
"UNLINK-ACCOUNT-DIALOG": { "UNLINK-ACCOUNT-DIALOG": {
"MESSAGE": "By clicking \"Confirm\", you accept the transfer of your ARGOS activity performed from this account to your main ARGOS account, which is the one that is listed first. By logging in again with the unlinked account, you will be able to start your ARGOS experience from scratch, in an empty dashboard.", "MESSAGE": "By clicking \"Confirm\", you accept the transfer of your ARGOS activity performed from this account to your main ARGOS account, which is the one that is listed first. By logging in again with the unlinked account, you will be able to start your ARGOS experience from scratch, in an empty dashboard.",
"CONFIRM": "Confirm", "CONFIRM": "Confirm",
"CANCEL": "Cancel" "CANCEL": "Cancel"
}, },
"SETTINGS": { "SETTINGS": {
"TITLE": "Definições", "TITLE": "Definições",

View File

@ -459,6 +459,10 @@
"FIELD-AUTOCOMPLETE-SOURCE": "Source", "FIELD-AUTOCOMPLETE-SOURCE": "Source",
"FIELD-AUTOCOMPLETE-URL": "Url", "FIELD-AUTOCOMPLETE-URL": "Url",
"FIELD-AUTOCOMPLETE-OPTIONS-ROOT": "Options Root", "FIELD-AUTOCOMPLETE-OPTIONS-ROOT": "Options Root",
"FIELD-AUTOCOMPLETE-HAS-AUTH": "Has Authentication",
"FIELD-AUTOCOMPLETE-AUTH-METHOD": "Method",
"FIELD-AUTOCOMPLETE-AUTH-BODY": "Request Body",
"FIELD-AUTOCOMPLETE-AUTH-TYPE": "Authentication Type",
"FIELD-DATE-PICKER-TITLE": "Date Picker", "FIELD-DATE-PICKER-TITLE": "Date Picker",
"FIELD-DATE-PICKER-PLACEHOLDER": "Input Placeholder", "FIELD-DATE-PICKER-PLACEHOLDER": "Input Placeholder",
"FIELD-DATE-PICKER-LABEL": "Label", "FIELD-DATE-PICKER-LABEL": "Label",
@ -1756,7 +1760,7 @@
"UNLINK-ACCOUNT-DIALOG": { "UNLINK-ACCOUNT-DIALOG": {
"MESSAGE": "By clicking \"Confirm\", you accept the transfer of your ARGOS activity performed from this account to your main ARGOS account, which is the one that is listed first. By logging in again with the unlinked account, you will be able to start your ARGOS experience from scratch, in an empty dashboard.", "MESSAGE": "By clicking \"Confirm\", you accept the transfer of your ARGOS activity performed from this account to your main ARGOS account, which is the one that is listed first. By logging in again with the unlinked account, you will be able to start your ARGOS experience from scratch, in an empty dashboard.",
"CONFIRM": "Confirm", "CONFIRM": "Confirm",
"CANCEL": "Cancel" "CANCEL": "Cancel"
}, },
"SETTINGS": { "SETTINGS": {
"TITLE": "Nastavenia", "TITLE": "Nastavenia",

View File

@ -459,6 +459,10 @@
"FIELD-AUTOCOMPLETE-SOURCE": "Izvor", "FIELD-AUTOCOMPLETE-SOURCE": "Izvor",
"FIELD-AUTOCOMPLETE-URL": "Url", "FIELD-AUTOCOMPLETE-URL": "Url",
"FIELD-AUTOCOMPLETE-OPTIONS-ROOT": "Opcija", "FIELD-AUTOCOMPLETE-OPTIONS-ROOT": "Opcija",
"FIELD-AUTOCOMPLETE-HAS-AUTH": "Has Authentication",
"FIELD-AUTOCOMPLETE-AUTH-METHOD": "Method",
"FIELD-AUTOCOMPLETE-AUTH-BODY": "Request Body",
"FIELD-AUTOCOMPLETE-AUTH-TYPE": "Authentication Type",
"FIELD-DATE-PICKER-TITLE": "Izbor datuma", "FIELD-DATE-PICKER-TITLE": "Izbor datuma",
"FIELD-DATE-PICKER-PLACEHOLDER": "Polje za unos", "FIELD-DATE-PICKER-PLACEHOLDER": "Polje za unos",
"FIELD-DATE-PICKER-LABEL": "Oznaka", "FIELD-DATE-PICKER-LABEL": "Oznaka",
@ -1756,7 +1760,7 @@
"UNLINK-ACCOUNT-DIALOG": { "UNLINK-ACCOUNT-DIALOG": {
"MESSAGE": "By clicking \"Confirm\", you accept the transfer of your ARGOS activity performed from this account to your main ARGOS account, which is the one that is listed first. By logging in again with the unlinked account, you will be able to start your ARGOS experience from scratch, in an empty dashboard.", "MESSAGE": "By clicking \"Confirm\", you accept the transfer of your ARGOS activity performed from this account to your main ARGOS account, which is the one that is listed first. By logging in again with the unlinked account, you will be able to start your ARGOS experience from scratch, in an empty dashboard.",
"CONFIRM": "Confirm", "CONFIRM": "Confirm",
"CANCEL": "Cancel" "CANCEL": "Cancel"
}, },
"SETTINGS": { "SETTINGS": {
"TITLE": "Podešavanja", "TITLE": "Podešavanja",

View File

@ -459,6 +459,10 @@
"FIELD-AUTOCOMPLETE-SOURCE": "Kaynak", "FIELD-AUTOCOMPLETE-SOURCE": "Kaynak",
"FIELD-AUTOCOMPLETE-URL": "Url", "FIELD-AUTOCOMPLETE-URL": "Url",
"FIELD-AUTOCOMPLETE-OPTIONS-ROOT": "Options Root", "FIELD-AUTOCOMPLETE-OPTIONS-ROOT": "Options Root",
"FIELD-AUTOCOMPLETE-HAS-AUTH": "Has Authentication",
"FIELD-AUTOCOMPLETE-AUTH-METHOD": "Method",
"FIELD-AUTOCOMPLETE-AUTH-BODY": "Request Body",
"FIELD-AUTOCOMPLETE-AUTH-TYPE": "Authentication Type",
"FIELD-DATE-PICKER-TITLE": "Tarih Seçici", "FIELD-DATE-PICKER-TITLE": "Tarih Seçici",
"FIELD-DATE-PICKER-PLACEHOLDER": "Input Placeholder Text", "FIELD-DATE-PICKER-PLACEHOLDER": "Input Placeholder Text",
"FIELD-DATE-PICKER-LABEL": "Etiket", "FIELD-DATE-PICKER-LABEL": "Etiket",
@ -1756,7 +1760,7 @@
"UNLINK-ACCOUNT-DIALOG": { "UNLINK-ACCOUNT-DIALOG": {
"MESSAGE": "By clicking \"Confirm\", you accept the transfer of your ARGOS activity performed from this account to your main ARGOS account, which is the one that is listed first. By logging in again with the unlinked account, you will be able to start your ARGOS experience from scratch, in an empty dashboard.", "MESSAGE": "By clicking \"Confirm\", you accept the transfer of your ARGOS activity performed from this account to your main ARGOS account, which is the one that is listed first. By logging in again with the unlinked account, you will be able to start your ARGOS experience from scratch, in an empty dashboard.",
"CONFIRM": "Confirm", "CONFIRM": "Confirm",
"CANCEL": "Cancel" "CANCEL": "Cancel"
}, },
"SETTINGS": { "SETTINGS": {
"TITLE": "Ayarlar", "TITLE": "Ayarlar",