Merge remote-tracking branch 'origin/evaluator-plugin' into dmp-refactoring

# Conflicts:
#	frontend/src/app/ui/description/overview/description-overview.component.ts
#	frontend/src/app/ui/plan/overview/plan-overview.component.ts
This commit is contained in:
Diamantis Tziotzios 2024-10-07 10:22:41 +03:00
commit e1bcc362b7
75 changed files with 2298 additions and 342 deletions

View File

@ -95,6 +95,11 @@
<artifactId>oidc-authn</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>org.opencdmp</groupId>
<artifactId>evaluator-base</artifactId>
<version>0.0.2</version>
</dependency>
<dependency>
<groupId>gr.cite</groupId>

View File

@ -155,6 +155,8 @@ public class AuditableAction {
public static final EventId FileTransformer_GetAvailableConfigurations = new EventId(20000, "FileTransformer_GetAvailableConfigurations");
public static final EventId Evaluator_GetAvailableConfigurations = new EventId(20001, "Evaluator_GetAvailableConfigurations");
public static final EventId ContactSupport_Sent = new EventId(210000, "ContactSupport_Sent");
public static final EventId ContactSupport_PublicSent = new EventId(210001, "ContactSupport_PublicSent");

View File

@ -89,6 +89,7 @@ public final class Permission {
public static String AssignPlanUsers = "AssignPlanUsers";
public static String InvitePlanUsers = "InvitePlanUsers";
public static String AnnotatePlan = "AnnotatePlan";
public static String EvaluatePlan = "EvaluatePlan";
//PlanStatus
public static String BrowsePlanStatus = "BrowsePlanStatus";
@ -137,6 +138,7 @@ public final class Permission {
public static String DeleteDescription = "DeleteDescription";
public static String CloneDescription = "CloneDescription";
public static String ExportDescription = "ExportDescription";
public static String EvaluateDescription = "EvaluateDescription";
//DescriptionTag
public static String BrowseDescriptionTag = "BrowseDescriptionTag";

View File

@ -11,7 +11,8 @@ public enum TenantConfigurationType implements DatabaseEnum<Short> {
FileTransformerPlugins((short) 1),
DefaultUserLocale((short) 2),
Logo((short) 3),
CssColors((short) 4);
CssColors((short) 4),
EvaluatorPlugins((short) 5);
private final Short value;

View File

@ -0,0 +1,68 @@
package org.opencdmp.commons.types.evaluator;
public class EvaluatorSourceEntity {
private String url;
private String evaluatorId;
private String issuerUrl;
private String clientId;
private String clientSecret;
private String scope;
private int maxInMemorySizeInBytes;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getEvaluatorId() {
return evaluatorId;
}
public void setEvaluatorId(String evaluatorId) {
this.evaluatorId = evaluatorId;
}
public String getIssuerUrl() {
return issuerUrl;
}
public void setIssuerUrl(String issuerUrl) {
this.issuerUrl = issuerUrl;
}
public String getClientId() {
return clientId;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public String getClientSecret() {
return clientSecret;
}
public void setClientSecret(String clientSecret) {
this.clientSecret = clientSecret;
}
public String getScope() {
return scope;
}
public void setScope(String scope) {
this.scope = scope;
}
public int getMaxInMemorySizeInBytes() {
return maxInMemorySizeInBytes;
}
public void setMaxInMemorySizeInBytes(int maxInMemorySizeInBytes) {
this.maxInMemorySizeInBytes = maxInMemorySizeInBytes;
}
}

View File

@ -0,0 +1,26 @@
package org.opencdmp.commons.types.tenantconfiguration;
import org.opencdmp.commons.types.evaluator.EvaluatorSourceEntity;
import java.util.List;
public class EvaluatorTenantConfigurationEntity {
private List<EvaluatorSourceEntity> sources;
private boolean disableSystemSources;
public List<EvaluatorSourceEntity> getSources() {
return sources;
}
public void setSources(List<EvaluatorSourceEntity> sources) {
this.sources = sources;
}
public boolean getDisableSystemSources(){
return disableSystemSources;
}
public void setDisableSystemSources(boolean disableSystemSources) {
this.disableSystemSources = disableSystemSources;
}
}

View File

@ -61,6 +61,7 @@ public class PlanCommonModelBuilder extends BaseCommonModelBuilder<PlanModel, Pl
private FileEnvelopeModel pdfFile;
private FileEnvelopeModel rdaJsonFile;
private String repositoryId;
private String evaluatorId;
private boolean disableDescriptions;
private EnumSet<AuthorizationFlags> authorize = EnumSet.of(AuthorizationFlags.None);
@ -96,6 +97,11 @@ public class PlanCommonModelBuilder extends BaseCommonModelBuilder<PlanModel, Pl
return this;
}
public PlanCommonModelBuilder setEvaluatorId(String evaluatorId){
this.evaluatorId = evaluatorId;
return this;
}
public PlanCommonModelBuilder setDisableDescriptions(boolean disableDescriptions) {
this.disableDescriptions = disableDescriptions;
return this;

View File

@ -0,0 +1,34 @@
package org.opencdmp.model.evaluator;
import java.util.UUID;
public class EvaluateRequestModel {
private UUID id;
private String evaluatorId;
private String format;
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public String getEvaluatorId() {
return evaluatorId;
}
public void setEvaluatorId(String evaluatorId) {
this.evaluatorId = evaluatorId;
}
public String getFormat() {
return format;
}
public void setFormat(String format) {
this.format = format;
}
}

View File

@ -0,0 +1,54 @@
package org.opencdmp.model.evaluator;
import org.opencdmp.evaluatorbase.enums.EvaluatorEntityType;
import org.opencdmp.evaluatorbase.models.misc.RankConfig;
import java.util.List;
public class EvaluatorConfiguration {
private String evaluatorId;
private RankConfig rankConfig;
private List<EvaluatorEntityType> evaluatorEntityTypes;
private boolean useSharedStorage;
private boolean hasLogo;
public String getEvaluatorId() {
return evaluatorId;
}
public void setEvaluatorId(String evaluatorId) {
this.evaluatorId = evaluatorId;
}
public RankConfig getRankConfig() {
return rankConfig;
}
public void setRankConfig(RankConfig rankConfig) {
this.rankConfig = rankConfig;
}
public List<EvaluatorEntityType> getEvaluatorEntityTypes() {
return evaluatorEntityTypes;
}
public void setEvaluatorEntityTypes(List<EvaluatorEntityType> evaluatorEntityTypes) {
this.evaluatorEntityTypes = evaluatorEntityTypes;
}
public boolean isUseSharedStorage() {
return useSharedStorage;
}
public void setUseSharedStorage(boolean useSharedStorage) {
this.useSharedStorage = useSharedStorage;
}
public boolean isHasLogo() {
return hasLogo;
}
public void setHasLogo(boolean hasLogo) {
this.hasLogo = hasLogo;
}
}

View File

@ -0,0 +1,76 @@
package org.opencdmp.model.evaluator;
public class EvaluatorSource {
private String url;
public static final String _url = "url";
private String evaluatorId;
public static final String _evaluatorId = "evaluatorId";
private String issuerUrl;
public static final String _issuerUrl = "issuerUrl";
private String clientId;
public static final String _clientId = "clientId";
private String clientSecret;
public static final String _clientSecret = "clientSecret";
private String scope;
public static final String _scope = "scope";
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getEvaluatorId() {
return evaluatorId;
}
public void setEvaluatorId(String evaluatorId) {
this.evaluatorId = evaluatorId;
}
public String getIssuerUrl() {
return issuerUrl;
}
public void setIssuerUrl(String issuerUrl) {
this.issuerUrl = issuerUrl;
}
public String getClientId() {
return clientId;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public String getClientSecret() {
return clientSecret;
}
public void setClientSecret(String clientSecret) {
this.clientSecret = clientSecret;
}
public String getScope() {
return scope;
}
public void setScope(String scope) {
this.scope = scope;
}
}

View File

@ -2,6 +2,7 @@ package org.opencdmp.model.tenantconfiguration;
import org.opencdmp.commons.enums.IsActive;
import org.opencdmp.commons.enums.TenantConfigurationType;
import org.opencdmp.commons.types.tenantconfiguration.EvaluatorTenantConfigurationEntity;
import java.time.Instant;
import java.util.UUID;
@ -36,6 +37,10 @@ public class TenantConfiguration {
public static final String _fileTransformerPlugins = "fileTransformerPlugins";
private EvaluatorTenantConfigurationEntity evaluatorPlugins;
public static final String _evaluatorPlugins = "evaluatorPlugins";
private LogoTenantConfiguration logo;
public static final String _logo = "logo";
@ -150,4 +155,12 @@ public class TenantConfiguration {
public void setType(TenantConfigurationType type) {
this.type = type;
}
public EvaluatorTenantConfigurationEntity getEvaluatorPlugins() {
return evaluatorPlugins;
}
public void setEvaluatorPlugins(EvaluatorTenantConfigurationEntity evaluatorPlugins) {
this.evaluatorPlugins = evaluatorPlugins;
}
}

View File

@ -0,0 +1,58 @@
package org.opencdmp.service.evaluator;
import com.sun.jdi.InvalidTypeException;
import gr.cite.tools.logging.LoggerService;
import gr.cite.tools.logging.MapLogEntry;
import org.opencdmp.commonmodels.models.description.DescriptionModel;
import org.opencdmp.commonmodels.models.plan.PlanModel;
import org.opencdmp.evaluatorbase.interfaces.EvaluatorClient;
import org.opencdmp.evaluatorbase.interfaces.EvaluatorConfiguration;
import org.opencdmp.evaluatorbase.models.misc.RankModel;
import org.opencdmp.service.filetransformer.FileTransformerRepository;
import org.slf4j.LoggerFactory;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import javax.management.InvalidApplicationException;
import java.io.IOException;
public class EvaluatorClientImpl implements EvaluatorClient {
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(FileTransformerRepository.class));
private final WebClient transformerClient;
public EvaluatorClientImpl(WebClient transformerClient) {
this.transformerClient = transformerClient;
}
@Override
public RankModel rankPlan(PlanModel planModel) throws InvalidApplicationException, IOException, InvalidTypeException {
logger.debug(new MapLogEntry("rankPlan").And("planModel", planModel));
return this.transformerClient.post().uri("/rank/plan").bodyValue(planModel) // Send planModel in the body
.exchangeToMono(mono -> mono.statusCode().isError() ? mono.createException().flatMap(Mono::error) : mono.bodyToMono(RankModel.class)).block();
}
@Override
public RankModel rankDescription(DescriptionModel descriptionModel) throws InvalidApplicationException, IOException {
logger.debug(new MapLogEntry("rankDescription").And("descriptionModel", descriptionModel));
return this.transformerClient.post().uri("/rank/description").bodyValue(descriptionModel) // Send descriptionModel in the body
.exchangeToMono(mono -> mono.statusCode().isError() ? mono.createException().flatMap(Mono::error) : mono.bodyToMono(RankModel.class)).block();
}
@Override
public EvaluatorConfiguration getConfiguration() {
logger.debug(new MapLogEntry("getConfiguration"));
return this.transformerClient.get().uri("/config")
.exchangeToMono(mono -> mono.statusCode().isError() ? mono.createException().flatMap(Mono::error) : mono.bodyToMono(new ParameterizedTypeReference<EvaluatorConfiguration>() {})).block();
}
@Override
public String getLogo() {
logger.debug(new MapLogEntry("getLogo"));
return this.transformerClient.get().uri("/logo").exchangeToMono(mono -> mono.statusCode().isError() ? mono.createException().flatMap(Mono::error) : mono.bodyToMono(String.class)).block();
}
}

View File

@ -0,0 +1,9 @@
package org.opencdmp.service.evaluator;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableConfigurationProperties({EvaluatorProperties.class})
public class EvaluatorConfiguration {
}

View File

@ -0,0 +1,10 @@
package org.opencdmp.service.evaluator;
import gr.cite.tools.cache.CacheOptions;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties(prefix = "cache.evaluator-config-by-id")
public class EvaluatorConfigurationCacheOptions extends CacheOptions {
}

View File

@ -0,0 +1,76 @@
package org.opencdmp.service.evaluator;
import gr.cite.tools.cache.CacheService;
import org.opencdmp.evaluatorbase.interfaces.EvaluatorConfiguration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.HashMap;
@Service
public class EvaluatorConfigurationCacheService extends CacheService<EvaluatorConfigurationCacheService.EvaluatorConfigurationCacheValue> {
public static class EvaluatorConfigurationCacheValue {
public EvaluatorConfigurationCacheValue() {
}
public EvaluatorConfigurationCacheValue(String repositoryId, String tenantCode, EvaluatorConfiguration configuration) {
this.evaluatorId = repositoryId;
this.configuration = configuration;
this.tenantCode = tenantCode == null ? "" : tenantCode;
}
private String evaluatorId;
private String tenantCode;
public String getEvaluatorId() {
return evaluatorId;
}
public void setEvaluatorId(String evaluatorId) {
this.evaluatorId = evaluatorId;
}
private EvaluatorConfiguration configuration;
public EvaluatorConfiguration getConfiguration() {
return configuration;
}
public void setConfiguration(EvaluatorConfiguration configuration) {
this.configuration = configuration;
}
public String getTenantCode() {
return tenantCode;
}
public void setTenantCode(String tenantCode) {
this.tenantCode = tenantCode;
}
}
@Autowired
public EvaluatorConfigurationCacheService(EvaluatorConfigurationCacheOptions options) {
super(options);
}
@Override
protected Class<EvaluatorConfigurationCacheService.EvaluatorConfigurationCacheValue> valueClass() {
return EvaluatorConfigurationCacheService.EvaluatorConfigurationCacheValue.class;
}
@Override
public String keyOf(EvaluatorConfigurationCacheService.EvaluatorConfigurationCacheValue value) {
return this.buildKey(value.getEvaluatorId(), value.getTenantCode());
}
public String buildKey(String evaluatorId, String tenantCod) {
HashMap<String, String> keyParts = new HashMap<>();
keyParts.put("$evaluatorId$", evaluatorId);
keyParts.put("$tenantCode$", tenantCod);
return this.generateKey(keyParts);
}
}

View File

@ -0,0 +1,21 @@
package org.opencdmp.service.evaluator;
import org.opencdmp.commons.types.evaluator.EvaluatorSourceEntity;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.List;
@ConfigurationProperties(prefix = "evaluator")
public class EvaluatorProperties {
private List<EvaluatorSourceEntity> sources;
public List<EvaluatorSourceEntity> getSources() {
return sources;
}
public void setSources(List<EvaluatorSourceEntity> sources) {
this.sources = sources;
}
}

View File

@ -0,0 +1,26 @@
package org.opencdmp.service.evaluator;
import com.sun.jdi.InvalidTypeException;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.management.InvalidApplicationException;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.UUID;
public interface EvaluatorService {
org.opencdmp.evaluatorbase.models.misc.RankModel rankPlan(UUID planId, String repositoryId, String format, boolean isPublic) throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, InvalidApplicationException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException, IOException, InvalidTypeException;
org.opencdmp.evaluatorbase.models.misc.RankModel rankDescription(UUID descriptionId, String repositoryId, String format, boolean isPublic) throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, InvalidApplicationException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException, IOException;
List<org.opencdmp.evaluatorbase.interfaces.EvaluatorConfiguration> getAvailableEvaluators() throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, InvalidApplicationException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException;
String getLogo(String evaluatorId) throws InvalidApplicationException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException;
}

View File

@ -0,0 +1,326 @@
package org.opencdmp.service.evaluator;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.sun.jdi.InvalidTypeException;
import gr.cite.commons.web.authz.service.AuthorizationService;
import gr.cite.commons.web.oidc.filter.webflux.TokenExchangeCacheService;
import gr.cite.commons.web.oidc.filter.webflux.TokenExchangeFilterFunction;
import gr.cite.commons.web.oidc.filter.webflux.TokenExchangeModel;
import gr.cite.tools.data.builder.BuilderFactory;
import gr.cite.tools.data.query.QueryFactory;
import gr.cite.tools.exception.MyNotFoundException;
import gr.cite.tools.fieldset.BaseFieldSet;
import gr.cite.tools.logging.LoggerService;
import gr.cite.tools.logging.MapLogEntry;
import org.opencdmp.authorization.AuthorizationFlags;
import org.opencdmp.authorization.Permission;
import org.opencdmp.commonmodels.models.description.DescriptionModel;
import org.opencdmp.commons.JsonHandlingService;
import org.opencdmp.commons.enums.*;
import org.opencdmp.commons.scope.tenant.TenantScope;
import org.opencdmp.commons.types.evaluator.EvaluatorSourceEntity;
import org.opencdmp.depositbase.repository.DepositClient;
import org.opencdmp.evaluatorbase.interfaces.EvaluatorClient;
import org.opencdmp.evaluatorbase.interfaces.EvaluatorConfiguration;
import org.opencdmp.commons.types.tenantconfiguration.EvaluatorTenantConfigurationEntity;
import org.opencdmp.convention.ConventionService;
import org.opencdmp.data.DescriptionEntity;
import org.opencdmp.data.PlanEntity;
import org.opencdmp.data.TenantConfigurationEntity;
import org.opencdmp.data.TenantEntityManager;
import org.opencdmp.evaluatorbase.models.misc.RankModel;
import org.opencdmp.event.TenantConfigurationTouchedEvent;
import org.opencdmp.model.builder.commonmodels.DepositConfigurationBuilder;
import org.opencdmp.model.builder.commonmodels.description.DescriptionCommonModelBuilder;
import org.opencdmp.model.builder.commonmodels.plan.PlanCommonModelBuilder;
import org.opencdmp.model.description.Description;
import org.opencdmp.model.plan.Plan;
import org.opencdmp.commonmodels.models.plan.PlanModel;
import org.opencdmp.model.tenantconfiguration.TenantConfiguration;
import org.opencdmp.query.DescriptionQuery;
import org.opencdmp.query.PlanQuery;
import org.opencdmp.query.TenantConfigurationQuery;
import org.opencdmp.service.accounting.AccountingService;
import org.opencdmp.service.encryption.EncryptionService;
import org.opencdmp.service.tenant.TenantProperties;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.event.EventListener;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.http.MediaType;
import org.springframework.http.codec.json.Jackson2JsonDecoder;
import org.springframework.http.codec.json.Jackson2JsonEncoder;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.management.InvalidApplicationException;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import static org.opencdmp.authorization.AuthorizationFlags.Public;
@Service
public class EvaluatorServiceImpl implements EvaluatorService {
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(EvaluatorServiceImpl.class));
private final EvaluatorProperties evaluatorProperties;
private final Map<String, EvaluatorClientImpl> clients;
private final TokenExchangeCacheService tokenExchangeCacheService;
private final EvaluatorConfigurationCacheService evaluatorConfigurationCacheService;
private final AuthorizationService authorizationService;
private final QueryFactory queryFactory;
private final BuilderFactory builderFactory;
private final MessageSource messageSource;
private final ConventionService conventionService;
private final TenantScope tenantScope;
private final EncryptionService encryptionService;
private final TenantProperties tenantProperties;
private final JsonHandlingService jsonHandlingService;
private final EvaluatorSourcesCacheService evaluatorSourcesCacheService;
private final AccountingService accountingService;
private final TenantEntityManager entityManager;
@Autowired
public EvaluatorServiceImpl(EvaluatorProperties evaluatorProperties, Map<String, EvaluatorClientImpl> clients, TokenExchangeCacheService tokenExchangeCacheService, EvaluatorConfigurationCacheService evaluatorConfigurationCacheService, AuthorizationService authorizationService, QueryFactory queryFactory, BuilderFactory builderFactory, MessageSource messageSource, ConventionService conventionService, TenantScope tenantScope, EncryptionService encryptionService, TenantProperties tenantProperties, JsonHandlingService jsonHandlingService, EvaluatorSourcesCacheService evaluatorSourcesCacheService, AccountingService accountingService, TenantEntityManager entityManager) {
this.evaluatorProperties = evaluatorProperties;
this.clients = clients;
this.tokenExchangeCacheService = tokenExchangeCacheService;
this.evaluatorConfigurationCacheService = evaluatorConfigurationCacheService;
this.authorizationService = authorizationService;
this.queryFactory = queryFactory;
this.builderFactory = builderFactory;
this.messageSource = messageSource;
this.conventionService = conventionService;
this.tenantScope = tenantScope;
this.encryptionService = encryptionService;
this.tenantProperties = tenantProperties;
this.jsonHandlingService = jsonHandlingService;
this.evaluatorSourcesCacheService = evaluatorSourcesCacheService;
this.accountingService = accountingService;
this.entityManager = entityManager;
}
private EvaluatorClientImpl getEvaluatorClient(String repoId) throws InvalidApplicationException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
String repositoryIdByTenant = this.getRepositoryIdByTenant(repoId);
if (this.clients.containsKey(repositoryIdByTenant)) {
return this.clients.get(repositoryIdByTenant);
}
EvaluatorSourceEntity source = this.getEvaluatorSources().stream()
.filter(evaluatorSourceEntity -> evaluatorSourceEntity.getEvaluatorId().equals(repoId))
.findFirst().orElse(null);
try {
TokenExchangeModel tokenExchangeModel = new TokenExchangeModel(
"evaluator:" + repositoryIdByTenant,
source.getIssuerUrl(),
source.getClientId(),
source.getClientSecret(),
source.getScope()
);
TokenExchangeFilterFunction tokenExchangeFilterFunction = new TokenExchangeFilterFunction(
this.tokenExchangeCacheService,
tokenExchangeModel
);
EvaluatorClientImpl repository = new EvaluatorClientImpl(
WebClient.builder().baseUrl(source.getUrl() + "/api/evaluator")
.filters(exchangeFilterFunctions -> {
exchangeFilterFunctions.add(tokenExchangeFilterFunction);
exchangeFilterFunctions.add(logRequest());
exchangeFilterFunctions.add(logResponse());
})
.codecs(codecs -> {
codecs.defaultCodecs().maxInMemorySize(source.getMaxInMemorySizeInBytes());
codecs.defaultCodecs().jackson2JsonDecoder(new Jackson2JsonDecoder(new ObjectMapper().registerModule(new JavaTimeModule()), MediaType.APPLICATION_JSON));
codecs.defaultCodecs().jackson2JsonEncoder(new Jackson2JsonEncoder(new ObjectMapper().registerModule(new JavaTimeModule()), MediaType.APPLICATION_JSON));
})
.build()
);
this.clients.put(repositoryIdByTenant, repository);
return repository;
} catch (Exception e) {
logger.error("Exception occurred while creating EvaluatorClientImpl", e);
return null;
}
}
private List<EvaluatorSourceEntity> getEvaluatorSources() throws InvalidApplicationException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
String tenantCode = this.tenantScope.isSet() && this.tenantScope.isMultitenant() ? this.tenantScope.getTenantCode() : "";
EvaluatorSourcesCacheService.EvaluatorSourceCacheValue cacheValue = this.evaluatorSourcesCacheService.lookup(this.evaluatorSourcesCacheService.buildKey(tenantCode));
if (cacheValue == null) {
List<EvaluatorSourceEntity> evaluatorSourceEntities = new ArrayList<>(this.evaluatorProperties.getSources());
if (this.tenantScope.isSet() && this.tenantScope.isMultitenant()) {
TenantConfigurationQuery tenantConfigurationQuery = this.queryFactory.query(TenantConfigurationQuery.class).disableTracking().isActive(IsActive.Active).types(TenantConfigurationType.EvaluatorPlugins);
if (this.tenantScope.isDefaultTenant()) tenantConfigurationQuery.tenantIsSet(false);
else tenantConfigurationQuery.tenantIsSet(true).tenantIds(this.tenantScope.getTenant());
TenantConfigurationEntity tenantConfiguration = tenantConfigurationQuery.firstAs(new BaseFieldSet().ensure(TenantConfiguration._evaluatorPlugins));
if (tenantConfiguration != null && !this.conventionService.isNullOrEmpty(tenantConfiguration.getValue())) {
EvaluatorTenantConfigurationEntity evaluatorTenantConfigurationEntity = this.jsonHandlingService.fromJsonSafe(EvaluatorTenantConfigurationEntity.class, tenantConfiguration.getValue());
if (evaluatorTenantConfigurationEntity != null) {
if (evaluatorTenantConfigurationEntity.getDisableSystemSources()) evaluatorSourceEntities = new ArrayList<>();
evaluatorSourceEntities.addAll(this.buildEvaluatorSourceItems(evaluatorTenantConfigurationEntity.getSources()));
}
}
}
cacheValue = new EvaluatorSourcesCacheService.EvaluatorSourceCacheValue(tenantCode, evaluatorSourceEntities);
this.evaluatorSourcesCacheService.put(cacheValue);
}
return cacheValue.getSources();
}
@EventListener
public void handleTenantConfigurationTouchedEvent(TenantConfigurationTouchedEvent event) {
if (!event.getType().equals(TenantConfigurationType.FileTransformerPlugins)) return;
EvaluatorSourcesCacheService.EvaluatorSourceCacheValue evaluatorSourceCacheValue = this.evaluatorSourcesCacheService.lookup(this.evaluatorSourcesCacheService.buildKey(event.getTenantCode()));
if (evaluatorSourceCacheValue != null && evaluatorSourceCacheValue.getSources() != null){
for (EvaluatorSourceEntity source : evaluatorSourceCacheValue.getSources()){
String repositoryIdByTenant = source.getEvaluatorId() + "_" + event.getTenantCode();
this.clients.remove(repositoryIdByTenant);
this.evaluatorConfigurationCacheService.evict(this.evaluatorConfigurationCacheService.buildKey(source.getEvaluatorId(), event.getTenantCode()));
}
}
this.evaluatorConfigurationCacheService.evict(this.evaluatorSourcesCacheService.buildKey(event.getTenantCode()));
}
private List<EvaluatorSourceEntity> buildEvaluatorSourceItems(List<EvaluatorSourceEntity> sources) throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
List<EvaluatorSourceEntity> items = new ArrayList<>();
if (this.conventionService.isListNullOrEmpty(sources)) return items;
for (EvaluatorSourceEntity source : sources){
EvaluatorSourceEntity item = new EvaluatorSourceEntity();
item.setEvaluatorId(source.getEvaluatorId());
item.setUrl(source.getUrl());
item.setIssuerUrl(source.getIssuerUrl());
item.setClientId(source.getClientId());
if (!this.conventionService.isNullOrEmpty(source.getClientSecret())) item.setClientSecret(this.encryptionService.decryptAES(source.getClientSecret(), this.tenantProperties.getConfigEncryptionAesKey(), this.tenantProperties.getConfigEncryptionAesIv()));
item.setScope(source.getScope());
items.add(item);
}
return items;
}
private String getRepositoryIdByTenant(String repositoryId) throws InvalidApplicationException {
if (this.tenantScope.isSet() && this.tenantScope.isMultitenant()) {
return repositoryId + "_" + this.tenantScope.getTenantCode();
} else {
return repositoryId;
}
}
@Override
public List<org.opencdmp.evaluatorbase.interfaces.EvaluatorConfiguration> getAvailableEvaluators() throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, InvalidApplicationException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
this.authorizationService.authorizeForce(Permission.BrowsePlan, Permission.DeferredAffiliation);
List<org.opencdmp.evaluatorbase.interfaces.EvaluatorConfiguration> configurations = new ArrayList<>();
for(EvaluatorSourceEntity evaluatorSource : this.getEvaluatorSources()){
String tenantCode = this.tenantScope.isSet() && this.tenantScope.isMultitenant() ? this.tenantScope.getTenantCode() : "";
EvaluatorConfigurationCacheService.EvaluatorConfigurationCacheValue cacheValue = this.evaluatorConfigurationCacheService.lookup(this.evaluatorConfigurationCacheService.buildKey(evaluatorSource.getEvaluatorId(), tenantCode));
if(cacheValue == null){
try{
EvaluatorClientImpl evaluatorClient = this.getEvaluatorClient(evaluatorSource.getEvaluatorId());
if(evaluatorClient == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{evaluatorSource.getEvaluatorId(), EvaluatorClientImpl.class.getSimpleName()}, LocaleContextHolder.getLocale()));
EvaluatorConfiguration configuration = evaluatorClient.getConfiguration();
cacheValue = new EvaluatorConfigurationCacheService.EvaluatorConfigurationCacheValue(evaluatorSource.getEvaluatorId(), tenantCode, configuration);
this.evaluatorConfigurationCacheService.put(cacheValue);
}catch (Exception e){
logger.error(e.getMessage(), e);
}
}
if(cacheValue != null){
configurations.add(cacheValue.getConfiguration());
}
}
return configurations;
}
@Override
public RankModel rankPlan(UUID planId, String evaluatorId, String format, boolean isPublic) throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, InvalidApplicationException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException, IOException, InvalidTypeException {
this.authorizationService.authorizeForce(Permission.EvaluatePlan);
EvaluatorClientImpl repository = this.getEvaluatorClient(evaluatorId);
if(repository == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{format, EvaluatorClientImpl.class.getSimpleName()}, LocaleContextHolder.getLocale()));
PlanEntity planEntity = this.queryFactory.query(PlanQuery.class).disableTracking().ids(planId).first();
if (planEntity == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{planId, PlanEntity.class.getSimpleName()}, LocaleContextHolder.getLocale()));
PlanModel evaluatorModel = this.builderFactory.builder(PlanCommonModelBuilder.class).useSharedStorage(repository.getConfiguration().isUseSharedStorage()).setEvaluatorId(repository.getConfiguration().getEvaluatorId()).isPublic(isPublic).authorize(AuthorizationFlags.All).build(planEntity);
if(evaluatorModel == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{planId, Plan.class.getSimpleName()}, LocaleContextHolder.getLocale()));
this.accountingService.increase(UsageLimitTargetMetric.FILE_TRANSFORMER_EXPORT_PLAN_EXECUTION_COUNT.getValue());
this.increaseTargetMetricWithRepositoryId(UsageLimitTargetMetric.FILE_TRANSFORMER_EXPORT_PLAN_EXECUTION_COUNT_FOR, evaluatorId);
return repository.rankPlan(evaluatorModel);
}
@Override
public RankModel rankDescription(UUID descriptionId, String repositoryId, String format, boolean isPublic) throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, InvalidApplicationException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException, IOException {
this.authorizationService.authorizeForce(Permission.EvaluateDescription);
EvaluatorClientImpl repository = this.getEvaluatorClient(repositoryId);
if(repository == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{format, EvaluatorClientImpl.class.getSimpleName()}, LocaleContextHolder.getLocale()));
DescriptionEntity descriptionEntity = this.queryFactory.query(DescriptionQuery.class).disableTracking().ids(descriptionId).first();
if (descriptionEntity == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{descriptionId, DescriptionEntity.class.getSimpleName()}, LocaleContextHolder.getLocale()));
DescriptionModel descriptionEvaluatorModel = this.builderFactory.builder(DescriptionCommonModelBuilder.class).setRepositoryId(repository.getConfiguration().getEvaluatorId()).useSharedStorage(repository.getConfiguration().isUseSharedStorage()).isPublic(isPublic).authorize(AuthorizationFlags.All).build(descriptionEntity);
if (descriptionEvaluatorModel == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{descriptionId, Description.class.getSimpleName()}, LocaleContextHolder.getLocale()));
this.accountingService.increase(UsageLimitTargetMetric.FILE_TRANSFORMER_EXPORT_DESCRIPTIONS_EXECUTION_COUNT.getValue());
this.increaseTargetMetricWithRepositoryId(UsageLimitTargetMetric.FILE_TRANSFORMER_EXPORT_DESCRIPTIONS_EXECUTION_COUNT_FOR, repositoryId);
return repository.rankDescription(descriptionEvaluatorModel);
}
@Override
public String getLogo(String evaluatorId) throws InvalidApplicationException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
this.authorizationService.authorizeForce(Permission.BrowseDeposit, Permission.DeferredAffiliation);
EvaluatorClient evaluatorClient = this.getEvaluatorClient(evaluatorId);
if(evaluatorClient == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{evaluatorId, EvaluatorClient.class.getSimpleName()}, LocaleContextHolder.getLocale()));
return evaluatorClient.getLogo();
}
private static ExchangeFilterFunction logRequest() {
return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {
logger.debug(new MapLogEntry("Request").And("method", clientRequest.method().toString()).And("url", clientRequest.url()));
return Mono.just(clientRequest);
});
}
private static ExchangeFilterFunction logResponse() {
return ExchangeFilterFunction.ofResponseProcessor(response -> {
if (response.statusCode().isError()) {
return response.mutate().build().bodyToMono(String.class)
.flatMap(body -> {
logger.error(new MapLogEntry("Response").And("method", response.request().getMethod().toString()).And("url", response.request().getURI()).And("status", response.statusCode().toString()).And("body", body));
return Mono.just(response);
});
}
return Mono.just(response);
});
}
private void increaseTargetMetricWithRepositoryId(UsageLimitTargetMetric metric, String repositoryId) throws InvalidApplicationException {
this.accountingService.increase(metric.getValue() + repositoryId);
}
}

View File

@ -0,0 +1,10 @@
package org.opencdmp.service.evaluator;
import gr.cite.tools.cache.CacheOptions;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties(prefix = "cache.evaluator-sources-by-tenant")
public class EvaluatorSourcesCacheOptions extends CacheOptions {
}

View File

@ -0,0 +1,66 @@
package org.opencdmp.service.evaluator;
import gr.cite.tools.cache.CacheService;
import org.opencdmp.commons.types.evaluator.EvaluatorSourceEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.List;
@Service
public class EvaluatorSourcesCacheService extends CacheService<EvaluatorSourcesCacheService.EvaluatorSourceCacheValue> {
public static class EvaluatorSourceCacheValue {
public EvaluatorSourceCacheValue() {
}
public EvaluatorSourceCacheValue(String tenantCode, List<EvaluatorSourceEntity> sources) {
this.tenantCode = tenantCode;
this.sources = sources;
}
private String tenantCode;
private List<EvaluatorSourceEntity> sources;
public String getTenantCode() {
return tenantCode;
}
public void setTenantCode(String tenantCode) {
this.tenantCode = tenantCode;
}
public List<EvaluatorSourceEntity> getSources() {
return sources;
}
public void setSources(List<EvaluatorSourceEntity> sources) {
this.sources = sources;
}
}
@Autowired
public EvaluatorSourcesCacheService(EvaluatorSourcesCacheOptions options) {
super(options);
}
@Override
protected Class<EvaluatorSourceCacheValue> valueClass() {
return EvaluatorSourceCacheValue.class;
}
@Override
public String keyOf(EvaluatorSourceCacheValue value) {
return this.buildKey(value.getTenantCode());
}
public String buildKey(String tenantCod) {
HashMap<String, String> keyParts = new HashMap<>();
keyParts.put("$tenantCode$", tenantCod);
return this.generateKey(keyParts);
}
}

View File

@ -151,8 +151,6 @@ public class FileTransformerServiceImpl implements FileTransformerService {
return null;
}
private List<FileTransformerSourceEntity> getFileTransformerSources() throws InvalidApplicationException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
String tenantCode = this.tenantScope.isSet() && this.tenantScope.isMultitenant() ? this.tenantScope.getTenantCode() : "";
FileTransformerSourcesCacheService.FileTransformerSourceCacheValue cacheValue = this.fileTransformerSourcesCacheService.lookup(this.fileTransformerSourcesCacheService.buildKey(tenantCode));

View File

@ -869,8 +869,6 @@ public class PlanServiceImpl implements PlanService {
}
private void updateVersionStatusAndSave(PlanEntity data, PlanStatus previousStatus, PlanStatus newStatus) throws InvalidApplicationException {
if (previousStatus == null && newStatus == null)
return;

View File

@ -0,0 +1,114 @@
package org.opencdmp.controllers;
import gr.cite.tools.auditing.AuditService;
import gr.cite.tools.logging.LoggerService;
import gr.cite.tools.logging.MapLogEntry;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import org.opencdmp.audit.AuditableAction;
import org.opencdmp.controllers.swagger.SwaggerHelpers;
import org.opencdmp.controllers.swagger.annotation.OperationWithTenantHeader;
import org.opencdmp.controllers.swagger.annotation.Swagger404;
import org.opencdmp.controllers.swagger.annotation.SwaggerCommonErrorResponses;
import org.opencdmp.evaluatorbase.interfaces.EvaluatorConfiguration;
import org.opencdmp.evaluatorbase.models.misc.RankModel;
import org.opencdmp.model.evaluator.EvaluateRequestModel;
import org.opencdmp.model.file.ExportRequestModel;
import org.opencdmp.service.evaluator.EvaluatorService;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.management.InvalidApplicationException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.AbstractMap;
import java.util.List;
import java.util.Map;
@RestController
@CrossOrigin
@RequestMapping(value = "/api/evaluator")
@SwaggerCommonErrorResponses
public class EvaluatorController {
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(EvaluatorController.class));
private final EvaluatorService evaluatorService;
private final AuditService auditService;
@Autowired
public EvaluatorController(EvaluatorService evaluatorService, AuditService auditService){
this.evaluatorService = evaluatorService;
this.auditService = auditService;
}
@GetMapping("/available")
@OperationWithTenantHeader(summary = "Fetch all evaluators", description = SwaggerHelpers.Evaluator.endpoint_get_available_evaluators,
responses = @ApiResponse(description = "OK", responseCode = "200", content = @Content(
array = @ArraySchema(
schema = @Schema(
implementation = EvaluatorConfiguration.class
)))
))
public List<EvaluatorConfiguration> getAvailableConfigurations() throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, InvalidApplicationException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
logger.debug(new MapLogEntry("getAvailableConfigurations"));
List<EvaluatorConfiguration> model = this.evaluatorService.getAvailableEvaluators();
this.auditService.track(AuditableAction.Evaluator_GetAvailableConfigurations);
return model;
}
@PostMapping("/rank-plan")
@OperationWithTenantHeader(summary = "Rank a plan", description = SwaggerHelpers.Evaluator.endpoint_rank_plans,
responses = @ApiResponse(description = "OK", responseCode = "200"))
public ResponseEntity<RankModel> rankPlan(@RequestBody EvaluateRequestModel requestModel) throws Exception {
logger.debug(new MapLogEntry("ranking plan"));
RankModel rankModel = this.evaluatorService.rankPlan(requestModel.getId(), requestModel.getEvaluatorId(), requestModel.getFormat(), true);
return new ResponseEntity<>(rankModel, HttpStatus.OK);
}
@PostMapping("/rank-description")
@OperationWithTenantHeader(summary = "Rank a description", description = SwaggerHelpers.Evaluator.endpoint_rank_descriptions,
responses = @ApiResponse(description = "OK", responseCode = "200"))
public ResponseEntity<RankModel> rankDescription(@RequestBody EvaluateRequestModel requestModel) throws Exception {
logger.debug(new MapLogEntry("ranking description"));
RankModel rankModel = this.evaluatorService.rankDescription(requestModel.getId(), requestModel.getEvaluatorId(), requestModel.getFormat(), true);
return new ResponseEntity<>(rankModel, HttpStatus.OK);
}
@GetMapping("/{evaluatorId}/logo")
@OperationWithTenantHeader(summary = "Fetch a specific evaluator logo by id", description = SwaggerHelpers.Deposit.endpoint_get_logo,
responses = @ApiResponse(description = "OK", responseCode = "200", content = @Content(
schema = @Schema(
implementation = String.class
))
))
@Swagger404
public String getLogo(
@Parameter(name = "evaluatorId", description = "The id of an evaluator of which to fetch the logo", example = "zenodo", required = true) @PathVariable("evaluatorId") String evaluatorId
) throws InvalidApplicationException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
logger.debug(new MapLogEntry("get logo" + EvaluatorConfiguration.class.getSimpleName()).And("evaluatorId", evaluatorId));
String logo = this.evaluatorService.getLogo(evaluatorId);
this.auditService.track(AuditableAction.Deposit_GetLogo, Map.ofEntries(
new AbstractMap.SimpleEntry<String, Object>("evaluatorId", evaluatorId)
));
return logo;
}
}

View File

@ -2589,6 +2589,24 @@ public final class SwaggerHelpers {
}
public static final class Evaluator {
public static final String endpoint_get_available_evaluators =
"""
This endpoint is used to fetch all the available evaluators.</br>
""";
public static final String endpoint_rank_plans =
"""
This endpoint is used to rank a plan using a specific evaluator.</br>
""";
public static final String endpoint_rank_descriptions =
"""
This endpoint is used to rank a description using a specific evaluator.</br>
""";
}
public static final class EntityDoi {
public static final String endpoint_query =

View File

@ -29,6 +29,7 @@ spring:
optional:classpath:config/public-api.yml[.yml], optional:classpath:config/public-api-${spring.profiles.active}.yml[.yml], optional:file:../config/public-api-${spring.profiles.active}.yml[.yml],
optional:classpath:config/dashboard.yml[.yml], optional:classpath:config/dashboard-${spring.profiles.active}.yml[.yml], optional:file:../config/dashboard-${spring.profiles.active}.yml[.yml],
optional:classpath:config/file-transformer.yml[.yml], optional:classpath:config/file-transformer-${spring.profiles.active}.yml[.yml], optional:file:../config/file-transformer-${spring.profiles.active}.yml[.yml],
optional:classpath:config/evaluator.yml[.yml], optional:classpath:config/evaluator-${spring.profiles.active}.yml[.yml], optional:file:../config/evaluator-${spring.profiles.active}.yml[.yml],
optional:classpath:config/authorization.yml[.yml], optional:classpath:config/authorization-${spring.profiles.active}.yml[.yml], optional:file:../config/authorization-${spring.profiles.active}.yml[.yml],
optional:classpath:config/metrics.yml[.yml], optional:classpath:config/metrics-${spring.profiles.active}.yml[.yml], optional:file:../config/metrics-${spring.profiles.active}.yml[.yml],
optional:classpath:config/field-set-expander.yml[.yml], optional:classpath:config/field-set-expander-${spring.profiles.active}.yml[.yml], optional:file:../config/field-set-expander-${spring.profiles.active}.yml[.yml],

View File

@ -50,12 +50,24 @@ cache:
maximumSize: 500
enableRecordStats: false
expireAfterWriteSeconds: 600
- names: [ "evaluatorConfigById" ]
allowNullValues: true
initialCapacity: 100
maximumSize: 500
enableRecordStats: false
expireAfterWriteSeconds: 600
- names: [ "fileTransformerSourcesByTenant" ]
allowNullValues: true
initialCapacity: 100
maximumSize: 500
enableRecordStats: false
expireAfterWriteSeconds: 600
- names: [ "evaluatorSourcesByTenant" ]
allowNullValues: true
initialCapacity: 100
maximumSize: 500
enableRecordStats: false
expireAfterWriteSeconds: 600
- names: [ "tokenExchangeKey" ]
allowNullValues: true
initialCapacity: 100
@ -126,9 +138,15 @@ cache:
fileTransformerConfigById:
name: fileTransformerConfigById
keyPattern: file_transformer_config_by_id_$transformerId$_$tenantCode$:v0
evaluatorConfigById:
name: evaluatorConfigById
keyPattern: evaluator_config_by_id_$evaluatorId$_$tenantCode$:v0
fileTransformerSourcesByTenant:
name: fileTransformerSourcesByTenant
keyPattern: ile_transformer_sources_by_tenant_$tenantCode$:v0
evaluatorSourcesByTenant:
name: evaluatorSourcesByTenant
keyPattern: evaluator_sources_by_tenant_$tenantCode$:v0
token-exchange-key:
name: tokenExchangeKey
keyPattern: resolve_$keyhash$:v0

View File

@ -0,0 +1,9 @@
evaluator:
sources:
- url: http://localhost:8084
evaluatorId: fair
issuer-url: ${IDP_ISSUER_URI_TOKEN}
client-id: ${IDP_APIKEY_CLIENT_ID}
client-secret: ${IDP_APIKEY_CLIENT_SECRET}
scope: ${IDP_APIKEY_SCOPE}
maxInMemorySizeInBytes: 6554000

View File

@ -0,0 +1,2 @@
evaluator:
sources: []

View File

@ -371,6 +371,17 @@ permissions:
clients: [ ]
allowAnonymous: false
allowAuthenticated: false
EvaluateDescription:
roles:
- Admin
- TenantAdmin
plan:
roles:
- Owner
claims: [ ]
clients: [ ]
allowAnonymous: false
allowAuthenticated: false
CloneDescription:
roles:
- Admin
@ -658,6 +669,17 @@ permissions:
clients: [ ]
allowAnonymous: false
allowAuthenticated: false
EvaluatePlan:
roles:
- Admin
- TenantAdmin
plan:
roles:
- Owner
claims: [ ]
clients: [ ]
allowAnonymous: false
allowAuthenticated: false
ClonePlan:
roles:
- Admin

View File

@ -28,8 +28,8 @@
"cookieconsent": "^3.1.1",
"dragula": "^3.7.3",
"file-saver": "^2.0.5",
"keycloak-angular": "^15.2.1",
"keycloak-js": "^24.0.5",
"keycloak-angular": "^16.0.1",
"keycloak-js": "^25.0.0",
"moment": "^2.30.1",
"moment-timezone": "^0.5.45",
"ng-dialog-animation": "^9.0.4",
@ -8856,23 +8856,23 @@
}
},
"node_modules/keycloak-angular": {
"version": "15.2.1",
"resolved": "https://registry.npmjs.org/keycloak-angular/-/keycloak-angular-15.2.1.tgz",
"integrity": "sha512-7w8bkJQ9OBtBJt5eNfqnRG2IL9btvp8Stf2fpVipSE1C/qtd5UQ31skx735PMPgMTUFsdz/0VA32Gmsng54+Xg==",
"version": "16.0.1",
"resolved": "https://registry.npmjs.org/keycloak-angular/-/keycloak-angular-16.0.1.tgz",
"integrity": "sha512-ytkL32R/tfHEyZ3txQtgH1y0WofW/D36zTbo2agDCYUtZETq0wAQ3E/4bVDUAr6ZKwotgAnIyOORfErnvDkXng==",
"dependencies": {
"tslib": "^2.3.1"
},
"peerDependencies": {
"@angular/common": "^17",
"@angular/core": "^17",
"@angular/router": "^17",
"keycloak-js": "^18 || ^19 || ^20 || ^21 || ^22 || ^23 || ^24"
"@angular/common": "^18",
"@angular/core": "^18",
"@angular/router": "^18",
"keycloak-js": "^18 || ^19 || ^20 || ^21 || ^22 || ^23 || ^24 || ^25"
}
},
"node_modules/keycloak-js": {
"version": "24.0.5",
"resolved": "https://registry.npmjs.org/keycloak-js/-/keycloak-js-24.0.5.tgz",
"integrity": "sha512-VQOSn3j13DPB6OuavKAq+sRjDERhIKrXgBzekoHRstifPuyULILguugX6yxRUYFSpn3OMYUXmSX++tkdCupOjA==",
"version": "25.0.6",
"resolved": "https://registry.npmjs.org/keycloak-js/-/keycloak-js-25.0.6.tgz",
"integrity": "sha512-Km+dc+XfNvY6a4az5jcxTK0zPk52ns9mAxLrHj7lF3V+riVYvQujfHmhayltJDjEpSOJ4C8a57LFNNKnNnRP2g==",
"dependencies": {
"js-sha256": "^0.11.0",
"jwt-decode": "^4.0.0"

View File

@ -0,0 +1,4 @@
export enum EvaluatorEntityType {
Plan = 0,
Description = 1
}

View File

@ -84,6 +84,7 @@ export enum AppPermission {
AssignPlanUsers = "AssignPlanUsers",
InvitePlanUsers = "InvitePlanUsers",
AnnotatePlan = "AnnotatePlan",
EvaluatePlan = "EvaluatePlan",
//PlanStatus
BrowsePlanStatus = "BrowsePlanStatus",
@ -121,6 +122,7 @@ export enum AppPermission {
DeleteDescription = "DeleteDescription",
CloneDescription = "CloneDescription",
ExportDescription = "ExportDescription",
EvaluateDescription = "EvaluateDescription",
//DescriptionTag
BrowseDescriptionTag = "BrowseDescriptionTag",

View File

@ -52,6 +52,7 @@ import { PlanStatusService } from './services/plan/plan-status.service';
import { DescriptionStatusService } from './services/description-status/description-status.service';
import { PlanWorkflowService } from './services/plan/plan-workflow.service';
import { DescriptionWorkflowService } from './services/description-workflow/description-workflow.service';
import { EvaluatorHttpService } from './services/evaluator/evaluator.http.service';
//
//
// This is shared module that provides all the services. Its imported only once on the AppModule.
@ -112,6 +113,7 @@ export class CoreServiceModule {
CanDeactivateGuard,
FileTransformerService,
FileTransformerHttpService,
EvaluatorHttpService,
SemanticsService,
PrefillingSourceService,
VisibilityRulesService,

View File

@ -0,0 +1,11 @@
import { RankType } from "./rank-type";
import { EvaluatorEntityType } from "@app/core/common/enum/evaluator-entity-type";
import { RankConfig } from "./rank-config";
export class EvaluatorConfiguration{
evaluatorId: string;
rankType: RankType[];
evaluatorEntityTypes: EvaluatorEntityType[];
rankConfig: RankConfig[];
hasLogo: boolean;
}

View File

@ -0,0 +1,12 @@
import { EvaluatorEntityType } from "@app/core/common/enum/evaluator-entity-type";
import { RankType } from "./rank-type";
import { SelectionConfiguration } from "./evaluator-selection";
import { ValueRangeConfiguration } from "./evaluator-value-range";
export interface EvaluatorFormat {
rankType: RankType[];
selectionConfiguration: SelectionConfiguration;
valueRangeConfiguration: ValueRangeConfiguration;
evaluatorId: string;
entityTypes: EvaluatorEntityType[];
}

View File

@ -0,0 +1,4 @@
export enum NumberType {
Decimal = 0,
Integer = 1
}

View File

@ -0,0 +1,5 @@
export class RankModel {
rank: number;
details: string;
messages: { [key: string]: string };
}

View File

@ -0,0 +1,9 @@
import { ValueSet } from "./evaluator-value-set";
export class SelectionConfiguration {
valueSetList: ValueSet[];
constructor(valueSetList: ValueSet[]) {
this.valueSetList = valueSetList;
}
}

View File

@ -0,0 +1,4 @@
export enum SuccessStatus {
Fail = 0,
Pass = 1
}

View File

@ -0,0 +1,15 @@
import { NumberType } from "./evaluator-number-type.model";
export class ValueRangeConfiguration {
numberType: NumberType;
min: number;
max: number;
minPassValue: number;
constructor(numberType: NumberType, min: number, max: number, minPassValue: number) {
this.numberType = numberType;
this.min = min;
this.max = max;
this.minPassValue = minPassValue;
}
}

View File

@ -0,0 +1,11 @@
import { SuccessStatus } from "./evaluator-success-status.model";
export class ValueSet {
key: number;
successStatus: SuccessStatus;
constructor(key: number, successStatus: SuccessStatus) {
this.key = key;
this.successStatus = successStatus;
}
}

View File

@ -0,0 +1,9 @@
import { RankType } from "./rank-type";
import { ValueRangeConfiguration } from "./evaluator-value-range";
import { SelectionConfiguration } from "./evaluator-selection";
export class RankConfig{
rankType: RankType;
valueRangeConfiguration?: ValueRangeConfiguration;
selectionConfiguration?: SelectionConfiguration;
}

View File

@ -0,0 +1,4 @@
export enum RankType {
ValueRange = 0,
Selection = 1
}

View File

@ -0,0 +1,44 @@
import { HttpHeaders } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { BaseService } from '@common/base/base.service';
import { Guid } from '@common/types/guid';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { ConfigurationService } from '../configuration/configuration.service';
import { BaseHttpV2Service } from '../http/base-http-v2.service';
import { EvaluatorFormat } from '@app/core/model/evaluator/evaluator-format.model';
import { RankModel } from '@app/core/model/evaluator/evaluator-plan-model.model';
@Injectable()
export class EvaluatorHttpService extends BaseService {
private headers = new HttpHeaders();
constructor(
private http: BaseHttpV2Service,
private configurationService: ConfigurationService
) { super(); }
private get apiBase(): string { return `${this.configurationService.server}evaluator`; }
getAvailableConfigurations(): Observable<EvaluatorFormat[]> {
const url = `${this.apiBase}/available`;
return this.http.get<EvaluatorFormat[]>(url).pipe(catchError((error: any) => throwError(error)));
}
rankPlan(id: Guid, evaluatorId: string, format: string): Observable<RankModel> {
const url = `${this.apiBase}/rank-plan`;
return this.http.post<RankModel>(url, {id: id, evaluatorId: evaluatorId, format: format}, {responseType: 'json', observe: 'response'}).pipe(catchError((error: any) => throwError(error)));
}
rankDescription(id: Guid, evaluatorId: string, format: string): Observable<RankModel> {
const url = `${this.apiBase}/rank-description`;
return this.http.post<RankModel>(url, {id: id, evaluatorId: evaluatorId, format: format}, {responseType: 'json', observe: 'response'}).pipe(catchError((error: any) => throwError(error)));
}
getLogo(evaluatorId: string): Observable<string>{
const url = `${this.apiBase}/${evaluatorId}/logo`;
return this.http.get<string>(url).pipe(catchError((error: any) => throwError(error)));
}
}

View File

@ -0,0 +1,136 @@
import { Component, EventEmitter, Input, OnInit, Output, Injectable } from '@angular/core';
import { BaseService } from '@common/base/base.service';
import { catchError, takeUntil } from 'rxjs/operators';
import { EvaluatorHttpService } from './evaluator.http.service';
import { HttpErrorHandlingService } from '@common/modules/errors/error-handling/http-error-handling.service';
import { AuthService } from '../auth/auth.service';
import { EvaluatorEntityType } from '@app/core/common/enum/evaluator-entity-type';
import { EvaluatorConfiguration } from '@app/core/model/evaluator/evaluator-configuration';
import { TranslateService } from '@ngx-translate/core';
import { Guid } from '@common/types/guid';
import {
SnackBarNotificationLevel,
UiNotificationService
} from '@app/core/services/notification/ui-notification-service';
import { Observable, throwError } from 'rxjs';
import { tap, share } from 'rxjs/operators';
import { RankModel } from '@app/core/model/evaluator/evaluator-plan-model.model';
@Injectable({
providedIn: 'root'
})
export class EvaluatorService extends BaseService {
constructor(
private evaluatorHttpService: EvaluatorHttpService,
private authentication: AuthService,
private httpErrorHandlingService: HttpErrorHandlingService,
private language: TranslateService,
private uiNotificationService: UiNotificationService,
) { super(); }
private _initialized: boolean = false;
private _loading: boolean = false;
private _availableEvaluators: EvaluatorConfiguration[] = [];
get availableEvaluators(): EvaluatorConfiguration[] {
if (!this.authentication.currentAccountIsAuthenticated()) {
return [];
}
if (!this._initialized && !this._loading) this.init(); // if not initialized and loading calls init to initialize the evaluators.
return this._availableEvaluators;
}
public availableEvaluatorsFor(entityType: EvaluatorEntityType) {
// Filter evaluators by entity type
// The fetch logo config should be here.
if (this.availableEvaluators) {
const filteredEvaluators = this.availableEvaluators.filter(x => {
return x.evaluatorEntityTypes && x.evaluatorEntityTypes.includes(entityType);
});
return filteredEvaluators;
}
return [];
}
init() {
this._loading = true;
this.evaluatorHttpService.getAvailableConfigurations()
.pipe(takeUntil(this._destroyed), catchError((error) => {
this._loading = false;
this._initialized = true;
this.httpErrorHandlingService.handleBackedRequestError(error);
return [];
}))
.subscribe(items => {
this._availableEvaluators = items;
this._loading = false;
this._initialized = true;
});
}
rankPlan(id: Guid, evaluatorId: string, format: string, isPublic: boolean = false): Observable<RankModel> {
this._loading = true;
return this.evaluatorHttpService.rankPlan(id, evaluatorId, format).pipe(
tap({
next: (doi) => {
this.onCallbackSuccess();
},
error: (error) => {
this.onCallbackError(error);
// Ensure loading state is turned off in case of error
this._loading = false;
},
complete: () => {
this._loading = false;
}
}),
catchError((error) => {
// Ensure loading state is turned off in case of error
this._loading = false;
return throwError(error);
}),
share()
);
}
rankDescription(id: Guid, evaluatorId: string, format: string, isPublic: boolean = false): Observable<RankModel> {
this._loading = true;
return this.evaluatorHttpService.rankDescription(id, evaluatorId, format)
.pipe(
takeUntil(this._destroyed),
tap(response => {
this._loading = false;
this.onCallbackSuccess();
}),
catchError(error => {
this._loading = false;
this.onCallbackError(error);
return throwError(error);
})
);
}
getLogo(evaluatorId: string): Observable<string> {
return this.evaluatorHttpService.getLogo(evaluatorId).pipe(
catchError((error) => {
this.httpErrorHandlingService.handleBackedRequestError(error);
return throwError(error);
})
);
}
onCallbackSuccess(): void {
this.uiNotificationService.snackBarNotification(this.language.instant('PLAN-EDITOR.SNACK-BAR.SUCCESSFUL-EVALUATION'), SnackBarNotificationLevel.Success);
}
onCallbackError(error) {
this.uiNotificationService.snackBarNotification(error.error.message ? error.error.message : this.language.instant('PLAN-EDITOR.SNACK-BAR.UNSUCCESSFUL-EVALUATION'), SnackBarNotificationLevel.Error);
}
}

View File

@ -3,6 +3,7 @@ import { FormattingModule } from '@app/core/formatting.module';
import { DescriptionRoutingModule, PublicDescriptionRoutingModule } from '@app/ui/description/description.routing';
import { CommonFormsModule } from '@common/forms/common-forms.module';
import { CommonUiModule } from '@common/ui/common-ui.module';
import { EvaluateDescriptionDialogModule } from './evaluate-description-dialog/evaluate-description-dialog.module';
@NgModule({
imports: [
@ -24,6 +25,7 @@ export class DescriptionModule { }
CommonFormsModule,
FormattingModule,
PublicDescriptionRoutingModule,
EvaluateDescriptionDialogModule,
],
declarations: [
],

View File

@ -0,0 +1,35 @@
<h1 mat-dialog-title>{{ 'DESCRIPTION-EVALUATE-DIALOG.HEADER' | translate }}</h1>
<div mat-dialog-content>
<mat-card>
<mat-card-header>
<mat-card-title>{{'DESCRIPTION-EVALUATE-DIALOG.DETAILS-SUB-HEADER' | translate}}</mat-card-title>
</mat-card-header>
<mat-card-content>
<div *ngIf="data.rankData" class="dialog-content">
<mat-form-field appearance="fill">
<mat-label>{{'DESCRIPTION-EVALUATE-DIALOG.RANK-BODY' | translate}}</mat-label>
<input matInput [value]="data.rankData.body.rank" disabled>
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>{{'DESCRIPTION-EVALUATE-DIALOG.DETAILS-BODY' | translate}}</mat-label>
<textarea matInput [value]="data.rankData.body.details" rows="4" disabled></textarea>
</mat-form-field>
<div *ngIf="data.rankData.body.messages">
<mat-card-header>
<mat-card-title>{{'DESCRIPTION-EVALUATE-DIALOG.MESSAGES-BODY' | translate}}</mat-card-title>
</mat-card-header>
<mat-card>
<mat-card-content>
<ul>
<li *ngFor="let entry of data.rankData.body.messages | keyvalue">
<strong>{{ entry.key }}:</strong> {{ entry.value }}
</li>
</ul>
</mat-card-content>
</mat-card>
</div>
</div>
</mat-card-content>
</mat-card>
</div>

View File

@ -0,0 +1,8 @@
.dialog-content {
display: flex;
flex-direction: column; /* Stack form fields vertically */
}
mat-form-field {
margin-bottom: 16px; /* Space between fields */
}

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { EvaluateDescriptionDialogComponent } from './evaluate-description-dialog.component';
describe('EvaluateDescriptionDialogComponent', () => {
let component: EvaluateDescriptionDialogComponent;
let fixture: ComponentFixture<EvaluateDescriptionDialogComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [EvaluateDescriptionDialogComponent]
})
.compileComponents();
fixture = TestBed.createComponent(EvaluateDescriptionDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,16 @@
import { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { RankModel } from '@app/core/model/evaluator/evaluator-plan-model.model';
@Component({
selector: 'app-evaluate-description-dialog',
templateUrl: './evaluate-description-dialog.component.html',
styleUrl: './evaluate-description-dialog.component.scss'
})
export class EvaluateDescriptionDialogComponent {
constructor(
public dialogRef: MatDialogRef<EvaluateDescriptionDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: { rankData: RankModel }
) { }
}

View File

@ -0,0 +1,24 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatCardModule } from '@angular/material/card';
import { MatButtonModule } from '@angular/material/button';
import { MatInputModule } from '@angular/material/input';
import { MatFormFieldModule } from '@angular/material/form-field';
import { NgIf } from '@angular/common';
import { EvaluateDescriptionDialogComponent } from './evaluate-description-dialog.component';
import { CommonUiModule } from '@common/ui/common-ui.module';
@NgModule({
imports: [
CommonModule,
MatCardModule,
MatButtonModule,
MatInputModule,
MatFormFieldModule,
NgIf,
CommonUiModule
],
declarations: [EvaluateDescriptionDialogComponent],
exports: [EvaluateDescriptionDialogComponent]
})
export class EvaluateDescriptionDialogModule { }

View File

@ -168,6 +168,28 @@
</div>
</div>
</ng-container>
<ng-container *ngIf="evaluatorService.availableEvaluatorsFor(evaluatorEntityTypeEnum.Description).length > 0">
<div class="row mb-3 align-items-center">
<div class="col-auto pr-0">
<button mat-mini-fab class="frame-btn" [matMenuTriggerFor]="rankMenu">
<mat-icon class="mat-mini-fab-icon">open_in_new</mat-icon>
</button>
</div>
<div class="col-auto pl-0">
<p class="mb-0 mr-0 pl-2 frame-txt" [matMenuTriggerFor]="rankMenu">{{ 'DESCRIPTION-OVERVIEW.ACTIONS.EVALUATE' | translate }}</p>
</div>
</div>
</ng-container>
<mat-menu #rankMenu="matMenu" xPosition="before">
<button mat-menu-item *ngFor='let evaluator of evaluatorService.availableEvaluatorsFor(evaluatorEntityTypeEnum.Description)'
(click)="onEvaluateDescription(description.id, evaluator.evaluatorId, evaluator.format, isPublicView)">
<span class="evaluator-id pr-2">{{ (evaluator.evaluatorId?.toUpperCase()) | translate }}</span>
<img *ngIf="evaluator.hasLogo" class="logo" [src]="logos.get(evaluator.evaluatorId)">
<img *ngIf="!evaluator.hasLogo" class="logo" src="assets/images/repository-placeholder.png">
</button>
</mat-menu>
<mat-menu #exportMenu="matMenu" xPosition="before">
<button mat-menu-item *ngFor='let fileTransformer of fileTransformerService.availableFormatsFor(fileTransformerEntityTypeEnum.Description)' (click)="fileTransformerService.exportDescription(description.id, fileTransformer.repositoryId, fileTransformer.format, isPublicView)">
<i class="fa pr-2" [ngClass]="fileTransformer.icon ? fileTransformer.icon : 'fa-file-o'"></i>

View File

@ -274,3 +274,8 @@
.deleted-item {
color: #cf1407;
}
.logo {
margin-right: 16px;
max-width: 24px;
max-height: 24px;
}

View File

@ -2,17 +2,24 @@ import { Location } from '@angular/common';
import { Component, OnInit } from '@angular/core';
import { UntypedFormBuilder, Validators } from '@angular/forms';
import { MatDialog } from '@angular/material/dialog';
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
import { ActivatedRoute, Params, Router } from '@angular/router';
import { DescriptionStatusEnum } from '@app/core/common/enum/description-status';
import { PlanAccessType } from '@app/core/common/enum/plan-access-type';
import { PlanStatusEnum } from '@app/core/common/enum/plan-status';
import { PlanUserRole } from '@app/core/common/enum/plan-user-role';
import { DescriptionStatusAvailableActionType } from '@app/core/common/enum/description-status-available-action-type';
import { DescriptionStatusPermission } from '@app/core/common/enum/description-status-permission.enum';
import { EvaluatorEntityType } from '@app/core/common/enum/evaluator-entity-type';
import { FileTransformerEntityType } from '@app/core/common/enum/file-transformer-entity-type';
import { IsActive } from '@app/core/common/enum/is-active.enum';
import { AppPermission } from '@app/core/common/enum/permission.enum';
import { PlanAccessType } from '@app/core/common/enum/plan-access-type';
import { PlanStatusEnum } from '@app/core/common/enum/plan-status';
import { PlanUserRole } from '@app/core/common/enum/plan-user-role';
import { DescriptionStatus, DescriptionStatusDefinition } from '@app/core/model/description-status/description-status';
import { DescriptionTemplate } from '@app/core/model/description-template/description-template';
import { BaseDescription, Description, DescriptionStatusPersist, PublicDescription } from '@app/core/model/description/description';
import { RankModel } from '@app/core/model/evaluator/evaluator-plan-model.model';
import { PlanBlueprint, PlanBlueprintDefinition, PlanBlueprintDefinitionSection } from '@app/core/model/plan-blueprint/plan-blueprint';
import { PlanStatus } from '@app/core/model/plan-status/plan-status';
import { Plan, PlanDescriptionTemplate, PlanUser, PlanUserRemovePersist } from '@app/core/model/plan/plan';
import { PlanReference } from '@app/core/model/plan/plan-reference';
import { ReferenceType } from '@app/core/model/reference-type/reference-type';
@ -21,20 +28,23 @@ import { User } from '@app/core/model/user/user';
import { AuthService } from '@app/core/services/auth/auth.service';
import { ConfigurationService } from '@app/core/services/configuration/configuration.service';
import { DescriptionService } from '@app/core/services/description/description.service';
import { PlanService } from '@app/core/services/plan/plan.service';
import { EvaluatorService } from '@app/core/services/evaluator/evaluator.service';
import { FileTransformerService } from '@app/core/services/file-transformer/file-transformer.service';
import { LockService } from '@app/core/services/lock/lock.service';
import { LoggingService } from '@app/core/services/logging/logging-service';
import { AnalyticsService } from '@app/core/services/matomo/analytics-service';
import { SnackBarNotificationLevel, UiNotificationService } from '@app/core/services/notification/ui-notification-service';
import { PlanService } from '@app/core/services/plan/plan.service';
import { ReferenceTypeService } from '@app/core/services/reference-type/reference-type.service';
import { ReferenceService } from '@app/core/services/reference/reference.service';
import { RouterUtilsService } from '@app/core/services/router/router-utils.service';
import { UserService } from '@app/core/services/user/user.service';
import { EnumUtils } from '@app/core/services/utilities/enum-utils.service';
import { FileUtils } from '@app/core/services/utilities/file-utils.service';
import { PopupNotificationDialogComponent } from '@app/library/notification/popup/popup-notification.component';
import { DescriptionValidationOutput } from '@app/ui/plan/plan-finalize-dialog/plan-finalize-dialog.component';
import { PlanInvitationDialogComponent } from '@app/ui/plan/invitation/dialog/plan-invitation-dialog.component';
import { BreadcrumbService } from '@app/ui/misc/breadcrumb/breadcrumb.service';
import { PlanInvitationDialogComponent } from '@app/ui/plan/invitation/dialog/plan-invitation-dialog.component';
import { DescriptionValidationOutput } from '@app/ui/plan/plan-finalize-dialog/plan-finalize-dialog.component';
import { BaseComponent } from '@common/base/base.component';
import { ConfirmationDialogComponent } from '@common/modules/confirmation-dialog/confirmation-dialog.component';
import { HttpErrorHandlingService } from '@common/modules/errors/error-handling/http-error-handling.service';
@ -43,11 +53,7 @@ import { TranslateService } from '@ngx-translate/core';
import { map, takeUntil } from 'rxjs/operators';
import { nameof } from 'ts-simple-nameof';
import { DescriptionCopyDialogComponent } from '../description-copy-dialog/description-copy-dialog.component';
import { RouterUtilsService } from '@app/core/services/router/router-utils.service';
import { DescriptionStatus, DescriptionStatusDefinition } from '@app/core/model/description-status/description-status';
import { PlanStatus } from '@app/core/model/plan-status/plan-status';
import { DescriptionStatusAvailableActionType } from '@app/core/common/enum/description-status-available-action-type';
import { DescriptionStatusPermission } from '@app/core/common/enum/description-status-permission.enum';
import { EvaluateDescriptionDialogComponent } from './../evaluate-description-dialog/evaluate-description-dialog.component';
@Component({
@ -70,6 +76,8 @@ export class DescriptionOverviewComponent extends BaseComponent implements OnIni
planStatusEnum = PlanStatusEnum;
planUserRoleEnum = PlanUserRole;
fileTransformerEntityTypeEnum = FileTransformerEntityType;
evaluatorEntityTypeEnum = EvaluatorEntityType;
logos: Map<string, SafeResourceUrl> = new Map<string, SafeResourceUrl>();
canEdit = false;
canCopy = false;
@ -77,6 +85,8 @@ export class DescriptionOverviewComponent extends BaseComponent implements OnIni
canFinalize = false;
canAnnotate = false;
canInvitePlanUsers = false;
canEvaluate = false;
get canAssignPlanUsers(): boolean {
const authorizationFlags = !this.isPublicView ? (this.description?.plan as Plan)?.authorizationFlags : [];
return (authorizationFlags?.some(x => x === AppPermission.InvitePlanUsers) || this.authentication.hasPermission(AppPermission.InvitePlanUsers)) &&
@ -110,6 +120,9 @@ export class DescriptionOverviewComponent extends BaseComponent implements OnIni
private breadcrumbService: BreadcrumbService,
private httpErrorHandlingService: HttpErrorHandlingService,
private userService: UserService,
private evaluatorService: EvaluatorService,
private logger: LoggingService,
private sanitizer: DomSanitizer,
) {
super();
}
@ -122,6 +135,7 @@ export class DescriptionOverviewComponent extends BaseComponent implements OnIni
this.canCopy = false;
this.canFinalize = false;
this.canInvitePlanUsers = false;
this.canEvaluate = false;
// Gets description data using parameter id
this.route.params
.pipe(takeUntil(this._destroyed))
@ -159,6 +173,9 @@ export class DescriptionOverviewComponent extends BaseComponent implements OnIni
this.canInvitePlanUsers = this.isActive && (this.authService.hasPermission(AppPermission.InvitePlanUsers) ||
this.description.authorizationFlags?.some(x => x === AppPermission.InvitePlanUsers)) && this.description.belongsToCurrentTenant != false;
this.canEvaluate = this.isActive && (this.authService.hasPermission(AppPermission.EvaluateDescription) ||
this.description.authorizationFlags?.some(x => x === AppPermission.EvaluateDescription)) && this.description.belongsToCurrentTenant != false;
},
error: (error: any) => {
this.httpErrorHandlingService.handleBackedRequestError(error);
@ -233,6 +250,35 @@ export class DescriptionOverviewComponent extends BaseComponent implements OnIni
return (this.description as Description).availableStatuses?.find(x => x.internalStatus === DescriptionStatusEnum.Finalized) != null;
}
onEvaluateDescription(planId: Guid, evaluatorId: string, format: string, isPublicView: boolean) {
this.evaluatorService.rankDescription(planId, evaluatorId, format).subscribe(
(response: RankModel) => {
this.evaluatorService.getLogo(evaluatorId).subscribe(
(logo: string) => {
this.logos.set(evaluatorId, this.sanitizer.bypassSecurityTrustResourceUrl('data:image/png;base64, ' + logo));
const dialogRef = this.dialog.open(EvaluateDescriptionDialogComponent, {
data: {
rankData: response,
}
});
dialogRef.afterClosed().subscribe(result => {
this.logger.debug("Dialog closed with result:", result);
});
},
error => {
this.logger.error("Error fetching evaluator logo:", error);
}
);
},
error => {
this.logger.error("Error ranking description:", error);
}
);
}
checkLockStatus(id: Guid) {
this.lockService.checkLockStatus(id).pipe(takeUntil(this._destroyed))
.subscribe({
@ -569,6 +615,7 @@ export class DescriptionOverviewComponent extends BaseComponent implements OnIni
[nameof<Description>(x => x.authorizationFlags), AppPermission.FinalizeDescription].join('.'),
[nameof<Description>(x => x.authorizationFlags), AppPermission.InvitePlanUsers].join('.'),
[nameof<Description>(x => x.authorizationFlags), AppPermission.AnnotateDescription].join('.'),
[nameof<Description>(x => x.authorizationFlags), AppPermission.EvaluateDescription].join('.'),
[nameof<Description>(x => x.statusAuthorizationFlags), DescriptionStatusPermission.Edit].join('.'),

View File

@ -228,6 +228,29 @@
</div>
</div>
</ng-container>
<ng-container *ngIf="canEvaluatePlan() && evaluatorService.availableEvaluatorsFor(evaluatorEntityTypeEnum.Plan).length > 0">
<div class="row mb-3 align-items-center">
<div class="col-auto pr-0">
<button mat-mini-fab class="frame-btn" [matMenuTriggerFor]="rankMenu">
<mat-icon class="mat-mini-fab-icon">open_in_new</mat-icon>
</button>
</div>
<div class="col-auto pl-0">
<p class="mb-0 pl-2 frame-txt" [matMenuTriggerFor]="rankMenu">{{ 'PLAN-OVERVIEW.ACTIONS.EVALUATE' | translate }}</p>
</div>
</div>
</ng-container>
<mat-menu #rankMenu="matMenu" xPosition="before">
<button mat-menu-item *ngFor='let evaluator of evaluatorService.availableEvaluatorsFor(evaluatorEntityTypeEnum.Plan)'
(click)="onEvaluatePlan(plan.id, evaluator.evaluatorId, evaluator.format, isPublicView)">
<span class="evaluator-id pr-2">{{ (evaluator.evaluatorId?.toUpperCase()) | translate }}</span>
<img *ngIf="evaluator.hasLogo" class="logo" [src]="logos.get(evaluator.evaluatorId)">
<img *ngIf="!evaluator.hasLogo" class="logo" src="assets/images/repository-placeholder.png">
</button>
</mat-menu>
<mat-menu #exportMenu="matMenu" xPosition="before">
<button mat-menu-item *ngFor='let fileTransformer of fileTransformerService.availableFormatsFor(fileTransformerEntityTypeEnum.Plan)' (click)="fileTransformerService.exportPlan(plan.id, fileTransformer.repositoryId, fileTransformer.format, isPublicView)">
<i class="fa pr-2" [ngClass]="fileTransformer.icon ? fileTransformer.icon : 'fa-file-o'"></i>

View File

@ -300,3 +300,9 @@
.deleted-item {
color: #cf1407;
}
.logo {
margin-right: 16px;
max-width: 24px;
max-height: 24px;
}

View File

@ -1,36 +1,46 @@
import { Location } from '@angular/common';
import { Component, ElementRef, OnInit, ViewChild } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
import { ActivatedRoute, Params, Router } from '@angular/router';
import { DescriptionStatusEnum } from '@app/core/common/enum/description-status';
import { PlanAccessType } from '@app/core/common/enum/plan-access-type';
import { PlanStatusEnum } from '@app/core/common/enum/plan-status';
import { PlanUserRole } from '@app/core/common/enum/plan-user-role';
import { PlanVersionStatus } from '@app/core/common/enum/plan-version-status';
import { EvaluatorEntityType } from '@app/core/common/enum/evaluator-entity-type';
import { FileTransformerEntityType } from '@app/core/common/enum/file-transformer-entity-type';
import { IsActive } from '@app/core/common/enum/is-active.enum';
import { AppPermission } from '@app/core/common/enum/permission.enum';
import { PlanAccessType } from '@app/core/common/enum/plan-access-type';
import { PlanStatusEnum } from '@app/core/common/enum/plan-status';
import { PlanStatusAvailableActionType } from '@app/core/common/enum/plan-status-available-action-type';
import { PlanStatusPermission } from '@app/core/common/enum/plan-status-permission.enum';
import { PlanUserRole } from '@app/core/common/enum/plan-user-role';
import { PlanVersionStatus } from '@app/core/common/enum/plan-version-status';
import { DepositConfiguration } from '@app/core/model/deposit/deposit-configuration';
import { DescriptionStatus } from '@app/core/model/description-status/description-status';
import { DescriptionTemplate } from '@app/core/model/description-template/description-template';
import { Description } from '@app/core/model/description/description';
import { EntityDoi } from '@app/core/model/entity-doi/entity-doi';
import { RankModel } from '@app/core/model/evaluator/evaluator-plan-model.model';
import { DescriptionTemplatesInSection, PlanBlueprint, PlanBlueprintDefinition, PlanBlueprintDefinitionSection } from '@app/core/model/plan-blueprint/plan-blueprint';
import { PlanStatus, PlanStatusDefinition } from '@app/core/model/plan-status/plan-status';
import { BasePlan, Plan, PlanDescriptionTemplate, PlanUser, PlanUserRemovePersist, PublicPlan } from '@app/core/model/plan/plan';
import { PlanReference } from '@app/core/model/plan/plan-reference';
import { EntityDoi } from '@app/core/model/entity-doi/entity-doi';
import { ReferenceType } from '@app/core/model/reference-type/reference-type';
import { Reference } from '@app/core/model/reference/reference';
import { User } from '@app/core/model/user/user';
import { AuthService } from '@app/core/services/auth/auth.service';
import { ConfigurationService } from '@app/core/services/configuration/configuration.service';
import { DepositService } from '@app/core/services/deposit/deposit.service';
import { PlanBlueprintService } from '@app/core/services/plan/plan-blueprint.service';
import { PlanService } from '@app/core/services/plan/plan.service';
import { EvaluatorService } from '@app/core/services/evaluator/evaluator.service';
import { FileTransformerService } from '@app/core/services/file-transformer/file-transformer.service';
import { LockService } from '@app/core/services/lock/lock.service';
import { LoggingService } from '@app/core/services/logging/logging-service';
import { AnalyticsService } from '@app/core/services/matomo/analytics-service';
import { SnackBarNotificationLevel, UiNotificationService } from '@app/core/services/notification/ui-notification-service';
import { PlanBlueprintService } from '@app/core/services/plan/plan-blueprint.service';
import { PlanService } from '@app/core/services/plan/plan.service';
import { ReferenceTypeService } from '@app/core/services/reference-type/reference-type.service';
import { ReferenceService } from '@app/core/services/reference/reference.service';
import { RouterUtilsService } from '@app/core/services/router/router-utils.service';
import { UserService } from '@app/core/services/user/user.service';
import { EnumUtils } from '@app/core/services/utilities/enum-utils.service';
import { FileUtils } from '@app/core/services/utilities/file-utils.service';
@ -44,16 +54,11 @@ import { TranslateService } from '@ngx-translate/core';
import { map, takeUntil } from 'rxjs/operators';
import { nameof } from 'ts-simple-nameof';
import { ClonePlanDialogComponent } from '../clone-dialog/plan-clone-dialog.component';
import { PlanDeleteDialogComponent } from '../plan-delete-dialog/plan-delete-dialog.component';
import { PlanEditorEntityResolver } from '../plan-editor-blueprint/resolvers/plan-editor-enitity.resolver';
import { PlanFinalizeDialogComponent, PlanFinalizeDialogOutput } from '../plan-finalize-dialog/plan-finalize-dialog.component';
import { PlanInvitationDialogComponent } from '../invitation/dialog/plan-invitation-dialog.component';
import { NewVersionPlanDialogComponent } from '../new-version-dialog/plan-new-version-dialog.component';
import { RouterUtilsService } from '@app/core/services/router/router-utils.service';
import { DescriptionStatus } from '@app/core/model/description-status/description-status';
import { PlanStatus, PlanStatusDefinition } from '@app/core/model/plan-status/plan-status';
import { PlanStatusAvailableActionType } from '@app/core/common/enum/plan-status-available-action-type';
import { PlanStatusPermission } from '@app/core/common/enum/plan-status-permission.enum';
import { PlanDeleteDialogComponent } from '../plan-delete-dialog/plan-delete-dialog.component';
import { PlanEvaluateDialogComponent } from '../plan-evaluate-dialog/plan-evaluate-dialog.component';
import { PlanFinalizeDialogComponent, PlanFinalizeDialogOutput } from '../plan-finalize-dialog/plan-finalize-dialog.component';
@Component({
selector: 'app-plan-overview',
@ -73,6 +78,8 @@ export class PlanOverviewComponent extends BaseComponent implements OnInit {
textMessage: any;
selectedModel: EntityDoi;
fileTransformerEntityTypeEnum = FileTransformerEntityType;
evaluatorEntityTypeEnum = EvaluatorEntityType
logos: Map<string, SafeResourceUrl> = new Map<string, SafeResourceUrl>();
@ViewChild('doi')
doi: ElementRef;
@ -112,6 +119,9 @@ export class PlanOverviewComponent extends BaseComponent implements OnInit {
private breadcrumbService: BreadcrumbService,
private httpErrorHandlingService: HttpErrorHandlingService,
private userService: UserService,
private evaluatorService: EvaluatorService,
private logger: LoggingService,
private sanitizer: DomSanitizer,
) {
super();
}
@ -309,6 +319,60 @@ export class PlanOverviewComponent extends BaseComponent implements OnInit {
}
canEvaluatePlan(): boolean {
const authorizationFlags = !this.isPublicView ? (this.plan as Plan).authorizationFlags : [];
return (authorizationFlags?.some(x => x === AppPermission.EvaluatePlan) || this.authentication.hasPermission(AppPermission.EvaluatePlan));
}
// onEvaluatePlan(planId: Guid, evaluatorId: string, format: string, isPublicView: boolean) {
// this.evaluatorService.rankPlan(planId, evaluatorId, format).subscribe(
// (response: RankModel) => {
// const dialogRef = this.dialog.open(PlanEvaluateDialogComponent, {
// data: { rankData: response }
// });
// dialogRef.afterClosed().subscribe(result => {
// this.logger.debug("Dialog closed with result:", result);
// });
// },
// error => {
// this.logger.error("Error ranking plan:", error);
// }
// );
// }
onEvaluatePlan(planId: Guid, evaluatorId: string, format: string, isPublicView: boolean) {
this.evaluatorService.rankPlan(planId, evaluatorId, format).subscribe(
(response: RankModel) => {
this.evaluatorService.getLogo(evaluatorId).subscribe(
(logo: string) => {
this.logos.set(evaluatorId, this.sanitizer.bypassSecurityTrustResourceUrl('data:image/png;base64, ' + logo));
const dialogRef = this.dialog.open(PlanEvaluateDialogComponent, {
data: {
rankData: response,
}
});
dialogRef.afterClosed().subscribe(result => {
this.logger.debug("Dialog closed with result:", result);
});
},
error => {
this.logger.error("Error fetching evaluator logo:", error);
}
);
},
error => {
this.logger.error("Error ranking plan:", error);
}
);
}
canInvitePlanUsers(): boolean {
const authorizationFlags = !this.isPublicView ? (this.plan as Plan).authorizationFlags : [];
return (
@ -679,6 +743,7 @@ export class PlanOverviewComponent extends BaseComponent implements OnInit {
[nameof<Plan>(x => x.authorizationFlags), AppPermission.ClonePlan].join('.'),
[nameof<Plan>(x => x.authorizationFlags), AppPermission.FinalizePlan].join('.'),
[nameof<Plan>(x => x.authorizationFlags), AppPermission.ExportPlan].join('.'),
[nameof<Plan>(x => x.authorizationFlags), AppPermission.EvaluatePlan].join('.'),
[nameof<Plan>(x => x.authorizationFlags), AppPermission.InvitePlanUsers].join('.'),
[nameof<Plan>(x => x.authorizationFlags), AppPermission.AssignPlanUsers].join('.'),
[nameof<Plan>(x => x.authorizationFlags), AppPermission.EditPlan].join('.'),

View File

@ -0,0 +1,35 @@
<h1 mat-dialog-title>{{ 'PLAN-EVALUATE-DIALOG.HEADER' | translate }}</h1>
<div mat-dialog-content>
<mat-card>
<mat-card-header>
<mat-card-title>{{'PLAN-EVALUATE-DIALOG.DETAILS-SUB-HEADER' | translate}}</mat-card-title>
</mat-card-header>
<mat-card-content>
<div *ngIf="data.rankData" class="dialog-content">
<mat-form-field appearance="fill">
<mat-label>{{'PLAN-EVALUATE-DIALOG.RANK-BODY' | translate}}</mat-label>
<input matInput [value]="data.rankData.body.rank" disabled>
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>{{'PLAN-EVALUATE-DIALOG.DETAILS-BODY' | translate}}</mat-label>
<textarea matInput [value]="data.rankData.body.details" rows="4" disabled></textarea>
</mat-form-field>
<div *ngIf="data.rankData.body.messages">
<mat-card-header>
<mat-card-title>{{'PLAN-EVALUATE-DIALOG.MESSAGES-BODY' | translate}}</mat-card-title>
</mat-card-header>
<mat-card>
<mat-card-content>
<ul>
<li *ngFor="let entry of data.rankData.body.messages | keyvalue">
<strong>{{ entry.key }}:</strong> {{ entry.value }}
</li>
</ul>
</mat-card-content>
</mat-card>
</div>
</div>
</mat-card-content>
</mat-card>
</div>

View File

@ -0,0 +1,8 @@
.dialog-content {
display: flex;
flex-direction: column; /* Stack form fields vertically */
}
mat-form-field {
margin-bottom: 16px; /* Space between fields */
}

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { PlanEvaluateDialogComponent } from './plan-evaluate-dialog.component';
describe('PlanEvaluateDialogComponent', () => {
let component: PlanEvaluateDialogComponent;
let fixture: ComponentFixture<PlanEvaluateDialogComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [PlanEvaluateDialogComponent]
})
.compileComponents();
fixture = TestBed.createComponent(PlanEvaluateDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,16 @@
import { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { RankModel } from '@app/core/model/evaluator/evaluator-plan-model.model';
@Component({
selector: 'app-plan-evaluate-dialog',
templateUrl: './plan-evaluate-dialog.component.html',
styleUrl: './plan-evaluate-dialog.component.scss'
})
export class PlanEvaluateDialogComponent {
// Injecting the dialog data into the component
constructor(
public dialogRef: MatDialogRef<PlanEvaluateDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: { rankData: RankModel },
) { }
}

View File

@ -0,0 +1,24 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatCardModule } from '@angular/material/card';
import { MatButtonModule } from '@angular/material/button';
import { NgIf } from '@angular/common';
import { PlanEvaluateDialogComponent } from './plan-evaluate-dialog.component';
import { CommonUiModule } from '@common/ui/common-ui.module';
@NgModule({
imports: [
CommonModule,
MatCardModule,
MatButtonModule,
MatInputModule,
MatFormFieldModule,
NgIf,
CommonUiModule,
],
declarations: [PlanEvaluateDialogComponent],
exports: [PlanEvaluateDialogComponent]
})
export class EvaluatePlanDialogModule { }

View File

@ -4,6 +4,7 @@ import { PlanRoutingModule, PublicPlanRoutingModule } from '@app/ui/plan/plan.ro
import { CommonFormsModule } from '@common/forms/common-forms.module';
import { CommonUiModule } from '@common/ui/common-ui.module';
import { InvitationAcceptedComponent } from './invitation/accepted/plan-invitation-accepted.component';
import { EvaluatePlanDialogModule } from './plan-evaluate-dialog/plan-evaluate-dialog.module';
@NgModule({
imports: [
@ -11,6 +12,7 @@ import { InvitationAcceptedComponent } from './invitation/accepted/plan-invitati
CommonFormsModule,
FormattingModule,
PlanRoutingModule,
EvaluatePlanDialogModule,
],
declarations: [
InvitationAcceptedComponent

View File

@ -190,6 +190,9 @@
"JSON": "RDA JSON",
"DOCX": "Document"
},
"EVALUATOR": {
"FAIR": "FAIR Evaluator"
},
"LANGUAGES": {
"en": "English",
"gr": "Greek",
@ -856,7 +859,8 @@
"NEW-VERSION": "Start new version",
"INVITE-SHORT": "Invite",
"REMOVE-AUTHOR": "Remove",
"PREVIEW": "Preview"
"PREVIEW": "Preview",
"EVALUATE": "Evaluate"
},
"UNDO-FINALIZATION-DIALOG": {
"TITLE": "Undo Finalization?",
@ -1002,7 +1006,8 @@
"EXPORT": "Export",
"INVITE-SHORT": "Invite",
"REMOVE-AUTHOR": "Remove",
"PREVIEW": "Preview"
"PREVIEW": "Preview",
"EVALUATE": "Evaluate"
},
"COPY-DIALOG": {
"COPY": "Copy",
@ -1941,7 +1946,9 @@
"SNACK-BAR": {
"UNSUCCESSFUL-DOI": "DOIa ez da behar bezala sortu",
"SUCCESSFUL-DOI": "DOIa ondo sortu da",
"SUCCESSFUL-PLAN-CONTACT": "User added"
"SUCCESSFUL-PLAN-CONTACT": "User added",
"SUCCESSFUL-EVALUATION": "Successful evaluation",
"UNSUCCESSFUL-EVALUATION":"Unsuccessful evaluation"
},
"DESCRIPTION-TEMPLATE-LIST": {
"TITLE": "Available Description Templates",
@ -1959,6 +1966,20 @@
"MESSAGE": "Somebody else is modifying the Plan at this moment. You may view the Plan but you cannot make any changes."
}
},
"PLAN-EVALUATE-DIALOG":{
"HEADER": "Ranking Information",
"DETAILS-SUB-HEADER": "Details",
"RANK-BODY": "Rank",
"DETAILS-BODY": "Details",
"MESSAGES-BODY": "Messages"
},
"DESCRIPTION-EVALUATE-DIALOG":{
"HEADER": "Ranking Information",
"DETAILS-SUB-HEADER": "Details",
"RANK-BODY": "Rank",
"DETAILS-BODY": "Details",
"MESSAGES-BODY": "Messages"
},
"REFERENCE-FIELD": {
"COULD-NOT-FIND-MESSAGE": "Couldn't find it?",
"ACTIONS": {

View File

@ -193,6 +193,9 @@
"JSON": "RDA JSON",
"DOCX": "Document"
},
"EVALUATOR": {
"FAIR": "FAIR Evaluator"
},
"LANGUAGES": {
"en": "English",
"gr": "Greek",
@ -859,7 +862,8 @@
"NEW-VERSION": "Start new version",
"INVITE-SHORT": "Invite",
"REMOVE-AUTHOR": "Remove",
"PREVIEW": "Preview"
"PREVIEW": "Preview",
"EVALUATE": "Evaluate"
},
"UNDO-FINALIZATION-DIALOG": {
"TITLE": "Undo Finalization?",
@ -1005,7 +1009,8 @@
"EXPORT": "Export",
"INVITE-SHORT": "Invite",
"REMOVE-AUTHOR": "Remove",
"PREVIEW": "Preview"
"PREVIEW": "Preview",
"EVALUATE": "Evaluate"
},
"COPY-DIALOG": {
"COPY": "Copy",
@ -1944,7 +1949,9 @@
"SNACK-BAR": {
"UNSUCCESSFUL-DOI": "DOI Erstellung fehlgeschlagen",
"SUCCESSFUL-DOI": "DOI Erstellung erfolgreich",
"SUCCESSFUL-PLAN-CONTACT": "User added"
"SUCCESSFUL-PLAN-CONTACT": "User added",
"SUCCESSFUL-EVALUATION": "Successful evaluation",
"UNSUCCESSFUL-EVALUATION":"Unsuccessful evaluation"
},
"DESCRIPTION-TEMPLATE-LIST": {
"TITLE": "Available Description Templates",
@ -1962,6 +1969,20 @@
"MESSAGE": "Somebody else is modifying the Plan at this moment. You may view the Plan but you cannot make any changes."
}
},
"PLAN-EVALUATE-DIALOG":{
"HEADER": "Ranking Information",
"DETAILS-SUB-HEADER": "Details",
"RANK-BODY": "Rank",
"DETAILS-BODY": "Details",
"MESSAGES-BODY": "Messages"
},
"DESCRIPTION-EVALUATE-DIALOG":{
"HEADER": "Ranking Information",
"DETAILS-SUB-HEADER": "Details",
"RANK-BODY": "Rank",
"DETAILS-BODY": "Details",
"MESSAGES-BODY": "Messages"
},
"REFERENCE-FIELD": {
"COULD-NOT-FIND-MESSAGE": "Couldn't find it?",
"ACTIONS": {

View File

@ -193,6 +193,9 @@
"JSON": "RDA JSON",
"DOCX": "Document"
},
"EVALUATOR": {
"FAIR": "FAIR Evaluator"
},
"LANGUAGES": {
"en": "English",
"gr": "Greek",
@ -857,7 +860,8 @@
"NEW-VERSION": "Start new version",
"INVITE-SHORT": "Invite",
"REMOVE-AUTHOR": "Remove",
"PREVIEW": "Preview"
"PREVIEW": "Preview",
"EVALUATE": "Evaluate"
},
"UNDO-FINALIZATION-DIALOG": {
"TITLE": "Undo Finalization?",
@ -1003,7 +1007,8 @@
"EXPORT": "Export",
"INVITE-SHORT": "Invite",
"REMOVE-AUTHOR": "Remove",
"PREVIEW": "Preview"
"PREVIEW": "Preview",
"EVALUATE": "Evaluate"
},
"COPY-DIALOG": {
"COPY": "Copy",
@ -1944,7 +1949,9 @@
"SNACK-BAR": {
"UNSUCCESSFUL-DOI": "Unsuccessful DOI creation",
"SUCCESSFUL-DOI": "Successful DOI creation",
"SUCCESSFUL-PLAN-CONTACT": "User added"
"SUCCESSFUL-PLAN-CONTACT": "User added",
"SUCCESSFUL-EVALUATION": "Successful evaluation",
"UNSUCCESSFUL-EVALUATION": "Unsuccessful evaluation"
},
"DESCRIPTION-TEMPLATE-LIST": {
"TITLE": "Available Description Templates",
@ -1962,6 +1969,20 @@
"MESSAGE": "Somebody else is modifying the Plan at this moment. You may view the Plan but you cannot make any changes."
}
},
"PLAN-EVALUATE-DIALOG":{
"HEADER": "Ranking Information",
"DETAILS-SUB-HEADER": "Details",
"RANK-BODY": "Rank",
"DETAILS-BODY": "Details",
"MESSAGES-BODY": "Messages"
},
"DESCRIPTION-EVALUATE-DIALOG":{
"HEADER": "Ranking Information",
"DETAILS-SUB-HEADER": "Details",
"RANK-BODY": "Rank",
"DETAILS-BODY": "Details",
"MESSAGES-BODY": "Messages"
},
"REFERENCE-FIELD": {
"COULD-NOT-FIND-MESSAGE": "Couldn't find it?",
"ACTIONS": {

View File

@ -193,6 +193,9 @@
"JSON": "RDA JSON",
"DOCX": "Document"
},
"EVALUATOR": {
"FAIR": "FAIR Evaluator"
},
"LANGUAGES": {
"en": "English",
"gr": "Greek",
@ -859,7 +862,8 @@
"NEW-VERSION": "Start new version",
"INVITE-SHORT": "Invite",
"REMOVE-AUTHOR": "Remove",
"PREVIEW": "Preview"
"PREVIEW": "Preview",
"EVALUATE": "Evaluate"
},
"UNDO-FINALIZATION-DIALOG": {
"TITLE": "Undo Finalization?",
@ -1005,7 +1009,8 @@
"EXPORT": "Export",
"INVITE-SHORT": "Invite",
"REMOVE-AUTHOR": "Remove",
"PREVIEW": "Preview"
"PREVIEW": "Preview",
"EVALUATE": "Evaluate"
},
"COPY-DIALOG": {
"COPY": "Copy",
@ -1944,7 +1949,9 @@
"SNACK-BAR": {
"UNSUCCESSFUL-DOI": "Fallo en la creación del DOI",
"SUCCESSFUL-DOI": "Creación del DOI correcta",
"SUCCESSFUL-PLAN-CONTACT": "User added"
"SUCCESSFUL-PLAN-CONTACT": "User added",
"SUCCESSFUL-EVALUATION": "Successful evaluation",
"UNSUCCESSFUL-EVALUATION":"Unsuccessful evaluation"
},
"DESCRIPTION-TEMPLATE-LIST": {
"TITLE": "Available Description Templates",
@ -1962,6 +1969,20 @@
"MESSAGE": "Somebody else is modifying the Plan at this moment. You may view the Plan but you cannot make any changes."
}
},
"PLAN-EVALUATE-DIALOG": {
"HEADER": "Ranking Information",
"DETAILS-SUB-HEADER": "Details",
"RANK-BODY": "Rank",
"DETAILS-BODY": "Details",
"MESSAGES-BODY": "Messages"
},
"DESCRIPTION-EVALUATE-DIALOG": {
"HEADER": "Ranking Information",
"DETAILS-SUB-HEADER": "Details",
"RANK-BODY": "Rank",
"DETAILS-BODY": "Details",
"MESSAGES-BODY": "Messages"
},
"REFERENCE-FIELD": {
"COULD-NOT-FIND-MESSAGE": "Couldn't find it?",
"ACTIONS": {

View File

@ -193,6 +193,9 @@
"JSON": "RDA JSON",
"DOCX": "Document"
},
"EVALUATOR": {
"FAIR": "FAIR Evaluator"
},
"LANGUAGES": {
"en": "English",
"gr": "Greek",
@ -859,7 +862,8 @@
"NEW-VERSION": "Start new version",
"INVITE-SHORT": "Invite",
"REMOVE-AUTHOR": "Remove",
"PREVIEW": "Preview"
"PREVIEW": "Preview",
"EVALUATE": "Evaluate"
},
"UNDO-FINALIZATION-DIALOG": {
"TITLE": "Undo Finalization?",
@ -1005,7 +1009,8 @@
"EXPORT": "Export",
"INVITE-SHORT": "Invite",
"REMOVE-AUTHOR": "Remove",
"PREVIEW": "Preview"
"PREVIEW": "Preview",
"EVALUATE": "Evaluate"
},
"COPY-DIALOG": {
"COPY": "Copy",
@ -1944,7 +1949,9 @@
"SNACK-BAR": {
"UNSUCCESSFUL-DOI": "Αποτυχία δημιουργίας Μονοσήμαντων Αναγνωριστικών Ψηφιακών Αντικειμένων (DOI)",
"SUCCESSFUL-DOI": "Επιτυχία δημιουργίας Μονοσήμαντων Αναγνωριστικών Ψηφιακών Αντικειμένων (DOI)",
"SUCCESSFUL-PLAN-CONTACT": "User added"
"SUCCESSFUL-PLAN-CONTACT": "User added",
"SUCCESSFUL-EVALUATION": "Successful evaluation",
"UNSUCCESSFUL-EVALUATION":"Unsuccessful evaluation"
},
"DESCRIPTION-TEMPLATE-LIST": {
"TITLE": "Available Description Templates",
@ -1962,6 +1969,20 @@
"MESSAGE": "Somebody else is modifying the Plan at this moment. You may view the Plan but you cannot make any changes."
}
},
"PLAN-EVALUATE-DIALOG": {
"HEADER": "Ranking Information",
"DETAILS-SUB-HEADER": "Details",
"RANK-BODY": "Rank",
"DETAILS-BODY": "Details",
"MESSAGES-BODY": "Messages"
},
"DESCRIPTION-EVALUATE-DIALOG": {
"HEADER": "Ranking Information",
"DETAILS-SUB-HEADER": "Details",
"RANK-BODY": "Rank",
"DETAILS-BODY": "Details",
"MESSAGES-BODY": "Messages"
},
"REFERENCE-FIELD": {
"COULD-NOT-FIND-MESSAGE": "Couldn't find it?",
"ACTIONS": {

View File

@ -193,6 +193,9 @@
"JSON": "RDA JSON",
"DOCX": "Document"
},
"EVALUATOR": {
"FAIR": "FAIR Evaluator"
},
"LANGUAGES": {
"en": "English",
"gr": "Greek",
@ -859,7 +862,8 @@
"NEW-VERSION": "Start new version",
"INVITE-SHORT": "Invite",
"REMOVE-AUTHOR": "Remove",
"PREVIEW": "Preview"
"PREVIEW": "Preview",
"EVALUATE": "Evaluate"
},
"UNDO-FINALIZATION-DIALOG": {
"TITLE": "Undo Finalization?",
@ -1005,7 +1009,8 @@
"EXPORT": "Export",
"INVITE-SHORT": "Invite",
"REMOVE-AUTHOR": "Remove",
"PREVIEW": "Preview"
"PREVIEW": "Preview",
"EVALUATE": "Evaluate"
},
"COPY-DIALOG": {
"COPY": "Copy",
@ -1944,7 +1949,9 @@
"SNACK-BAR": {
"UNSUCCESSFUL-DOI": "Neuspješno generiran DOI",
"SUCCESSFUL-DOI": "Uspješno generiran DOI",
"SUCCESSFUL-PLAN-CONTACT": "User added"
"SUCCESSFUL-PLAN-CONTACT": "User added",
"SUCCESSFUL-EVALUATION": "Successful evaluation",
"UNSUCCESSFUL-EVALUATION":"Unsuccessful evaluation"
},
"DESCRIPTION-TEMPLATE-LIST": {
"TITLE": "Available Description Templates",
@ -1962,6 +1969,20 @@
"MESSAGE": "Somebody else is modifying the Plan at this moment. You may view the Plan but you cannot make any changes."
}
},
"PLAN-EVALUATE-DIALOG": {
"HEADER": "Ranking Information",
"DETAILS-SUB-HEADER": "Details",
"RANK-BODY": "Rank",
"DETAILS-BODY": "Details",
"MESSAGES-BODY": "Messages"
},
"DESCRIPTION-EVALUATE-DIALOG": {
"HEADER": "Ranking Information",
"DETAILS-SUB-HEADER": "Details",
"RANK-BODY": "Rank",
"DETAILS-BODY": "Details",
"MESSAGES-BODY": "Messages"
},
"REFERENCE-FIELD": {
"COULD-NOT-FIND-MESSAGE": "Couldn't find it?",
"ACTIONS": {

View File

@ -193,6 +193,9 @@
"JSON": "RDA JSON",
"DOCX": "Document"
},
"EVALUATOR": {
"FAIR": "FAIR Evaluator"
},
"LANGUAGES": {
"en": "English",
"gr": "Greek",
@ -859,7 +862,8 @@
"NEW-VERSION": "Start new version",
"INVITE-SHORT": "Invite",
"REMOVE-AUTHOR": "Remove",
"PREVIEW": "Preview"
"PREVIEW": "Preview",
"EVALUATE": "Evaluate"
},
"UNDO-FINALIZATION-DIALOG": {
"TITLE": "Undo Finalization?",
@ -1005,7 +1009,8 @@
"EXPORT": "Export",
"INVITE-SHORT": "Invite",
"REMOVE-AUTHOR": "Remove",
"PREVIEW": "Preview"
"PREVIEW": "Preview",
"EVALUATE": "Evaluate"
},
"COPY-DIALOG": {
"COPY": "Copy",
@ -1944,7 +1949,9 @@
"SNACK-BAR": {
"UNSUCCESSFUL-DOI": "Nie udało się utworzyć DOI",
"SUCCESSFUL-DOI": "Utworzono DOI",
"SUCCESSFUL-PLAN-CONTACT": "User added"
"SUCCESSFUL-PLAN-CONTACT": "User added",
"SUCCESSFUL-EVALUATION": "Successful evaluation",
"UNSUCCESSFUL-EVALUATION":"Unsuccessful evaluation"
},
"DESCRIPTION-TEMPLATE-LIST": {
"TITLE": "Available Description Templates",
@ -1962,6 +1969,20 @@
"MESSAGE": "Somebody else is modifying the Plan at this moment. You may view the Plan but you cannot make any changes."
}
},
"PLAN-EVALUATE-DIALOG": {
"HEADER": "Ranking Information",
"DETAILS-SUB-HEADER": "Details",
"RANK-BODY": "Rank",
"DETAILS-BODY": "Details",
"MESSAGES-BODY": "Messages"
},
"DESCRIPTION-EVALUATE-DIALOG": {
"HEADER": "Ranking Information",
"DETAILS-SUB-HEADER": "Details",
"RANK-BODY": "Rank",
"DETAILS-BODY": "Details",
"MESSAGES-BODY": "Messages"
},
"REFERENCE-FIELD": {
"COULD-NOT-FIND-MESSAGE": "Couldn't find it?",
"ACTIONS": {

View File

@ -193,6 +193,9 @@
"JSON": "RDA JSON",
"DOCX": "Document"
},
"EVALUATOR": {
"FAIR": "FAIR Evaluator"
},
"LANGUAGES": {
"en": "English",
"gr": "Greek",
@ -859,7 +862,8 @@
"NEW-VERSION": "Start new version",
"INVITE-SHORT": "Invite",
"REMOVE-AUTHOR": "Remove",
"PREVIEW": "Preview"
"PREVIEW": "Preview",
"EVALUATE": "Evaluate"
},
"UNDO-FINALIZATION-DIALOG": {
"TITLE": "Undo Finalization?",
@ -1005,7 +1009,8 @@
"EXPORT": "Export",
"INVITE-SHORT": "Invite",
"REMOVE-AUTHOR": "Remove",
"PREVIEW": "Preview"
"PREVIEW": "Preview",
"EVALUATE": "Evaluate"
},
"COPY-DIALOG": {
"COPY": "Copy",
@ -1944,7 +1949,9 @@
"SNACK-BAR": {
"UNSUCCESSFUL-DOI": "Criação de DOI sem sucesso",
"SUCCESSFUL-DOI": "Criação de DOI com sucesso",
"SUCCESSFUL-PLAN-CONTACT": "User added"
"SUCCESSFUL-PLAN-CONTACT": "User added",
"SUCCESSFUL-EVALUATION": "Successful evaluation",
"UNSUCCESSFUL-EVALUATION":"Unsuccessful evaluation"
},
"DESCRIPTION-TEMPLATE-LIST": {
"TITLE": "Available Description Templates",
@ -1962,6 +1969,20 @@
"MESSAGE": "Somebody else is modifying the Plan at this moment. You may view the Plan but you cannot make any changes."
}
},
"PLAN-EVALUATE-DIALOG": {
"HEADER": "Ranking Information",
"DETAILS-SUB-HEADER": "Details",
"RANK-BODY": "Rank",
"DETAILS-BODY": "Details",
"MESSAGES-BODY": "Messages"
},
"DESCRIPTION-EVALUATE-DIALOG": {
"HEADER": "Ranking Information",
"DETAILS-SUB-HEADER": "Details",
"RANK-BODY": "Rank",
"DETAILS-BODY": "Details",
"MESSAGES-BODY": "Messages"
},
"REFERENCE-FIELD": {
"COULD-NOT-FIND-MESSAGE": "Couldn't find it?",
"ACTIONS": {

View File

@ -193,6 +193,9 @@
"JSON": "RDA JSON",
"DOCX": "Document"
},
"EVALUATOR": {
"FAIR": "FAIR Evaluator"
},
"LANGUAGES": {
"en": "English",
"gr": "Greek",
@ -859,7 +862,8 @@
"NEW-VERSION": "Start new version",
"INVITE-SHORT": "Invite",
"REMOVE-AUTHOR": "Remove",
"PREVIEW": "Preview"
"PREVIEW": "Preview",
"EVALUATE": "Evaluate"
},
"UNDO-FINALIZATION-DIALOG": {
"TITLE": "Undo Finalization?",
@ -1005,7 +1009,8 @@
"EXPORT": "Export",
"INVITE-SHORT": "Invite",
"REMOVE-AUTHOR": "Remove",
"PREVIEW": "Preview"
"PREVIEW": "Preview",
"EVALUATE": "Evaluate"
},
"COPY-DIALOG": {
"COPY": "Copy",
@ -1944,7 +1949,9 @@
"SNACK-BAR": {
"UNSUCCESSFUL-DOI": "Neúspešné vytvorenie DOI",
"SUCCESSFUL-DOI": "Úspešné vytvorenie DOI",
"SUCCESSFUL-PLAN-CONTACT": "User added"
"SUCCESSFUL-PLAN-CONTACT": "User added",
"SUCCESSFUL-EVALUATION": "Successful evaluation",
"UNSUCCESSFUL-EVALUATION":"Unsuccessful evaluation"
},
"DESCRIPTION-TEMPLATE-LIST": {
"TITLE": "Available Description Templates",
@ -1962,6 +1969,20 @@
"MESSAGE": "Somebody else is modifying the Plan at this moment. You may view the Plan but you cannot make any changes."
}
},
"PLAN-EVALUATE-DIALOG": {
"HEADER": "Ranking Information",
"DETAILS-SUB-HEADER": "Details",
"RANK-BODY": "Rank",
"DETAILS-BODY": "Details",
"MESSAGES-BODY": "Messages"
},
"DESCRIPTION-EVALUATE-DIALOG": {
"HEADER": "Ranking Information",
"DETAILS-SUB-HEADER": "Details",
"RANK-BODY": "Rank",
"DETAILS-BODY": "Details",
"MESSAGES-BODY": "Messages"
},
"REFERENCE-FIELD": {
"COULD-NOT-FIND-MESSAGE": "Couldn't find it?",
"ACTIONS": {

View File

@ -193,6 +193,9 @@
"JSON": "RDA JSON",
"DOCX": "Document"
},
"EVALUATOR": {
"FAIR": "FAIR Evaluator"
},
"LANGUAGES": {
"en": "English",
"gr": "Greek",
@ -859,7 +862,8 @@
"NEW-VERSION": "Start new version",
"INVITE-SHORT": "Invite",
"REMOVE-AUTHOR": "Remove",
"PREVIEW": "Preview"
"PREVIEW": "Preview",
"EVALUATE": "Evaluate"
},
"UNDO-FINALIZATION-DIALOG": {
"TITLE": "Undo Finalization?",
@ -1005,7 +1009,8 @@
"EXPORT": "Export",
"INVITE-SHORT": "Invite",
"REMOVE-AUTHOR": "Remove",
"PREVIEW": "Preview"
"PREVIEW": "Preview",
"EVALUATE": "Evaluate"
},
"COPY-DIALOG": {
"COPY": "Copy",
@ -1944,7 +1949,9 @@
"SNACK-BAR": {
"UNSUCCESSFUL-DOI": "Neuspešno registrovan DOI",
"SUCCESSFUL-DOI": "Uspešno registrovan DOI",
"SUCCESSFUL-PLAN-CONTACT": "User added"
"SUCCESSFUL-PLAN-CONTACT": "User added",
"SUCCESSFUL-EVALUATION": "Successful evaluation",
"UNSUCCESSFUL-EVALUATION":"Unsuccessful evaluation"
},
"DESCRIPTION-TEMPLATE-LIST": {
"TITLE": "Available Description Templates",
@ -1962,6 +1969,20 @@
"MESSAGE": "Somebody else is modifying the Plan at this moment. You may view the Plan but you cannot make any changes."
}
},
"PLAN-EVALUATE-DIALOG": {
"HEADER": "Ranking Information",
"DETAILS-SUB-HEADER": "Details",
"RANK-BODY": "Rank",
"DETAILS-BODY": "Details",
"MESSAGES-BODY": "Messages"
},
"DESCRIPTION-EVALUATE-DIALOG": {
"HEADER": "Ranking Information",
"DETAILS-SUB-HEADER": "Details",
"RANK-BODY": "Rank",
"DETAILS-BODY": "Details",
"MESSAGES-BODY": "Messages"
},
"REFERENCE-FIELD": {
"COULD-NOT-FIND-MESSAGE": "Couldn't find it?",
"ACTIONS": {

View File

@ -193,6 +193,9 @@
"JSON": "RDA JSON",
"DOCX": "Document"
},
"EVALUATOR": {
"FAIR": "FAIR Evaluator"
},
"LANGUAGES": {
"en": "English",
"gr": "Greek",
@ -859,7 +862,8 @@
"NEW-VERSION": "Start new version",
"INVITE-SHORT": "Invite",
"REMOVE-AUTHOR": "Remove",
"PREVIEW": "Preview"
"PREVIEW": "Preview",
"EVALUATE": "Evaluate"
},
"UNDO-FINALIZATION-DIALOG": {
"TITLE": "Undo Finalization?",
@ -1005,7 +1009,8 @@
"EXPORT": "Export",
"INVITE-SHORT": "Invite",
"REMOVE-AUTHOR": "Remove",
"PREVIEW": "Preview"
"PREVIEW": "Preview",
"EVALUATE": "Evaluate"
},
"COPY-DIALOG": {
"COPY": "Copy",
@ -1944,7 +1949,9 @@
"SNACK-BAR": {
"UNSUCCESSFUL-DOI": "Başarısız DOI oluşturma",
"SUCCESSFUL-DOI": "Başarılı DOI oluşumu",
"SUCCESSFUL-PLAN-CONTACT": "User added"
"SUCCESSFUL-PLAN-CONTACT": "User added",
"SUCCESSFUL-EVALUATION": "Successful evaluation",
"UNSUCCESSFUL-EVALUATION":"Unsuccessful evaluation"
},
"DESCRIPTION-TEMPLATE-LIST": {
"TITLE": "Available Description Templates",
@ -1962,6 +1969,20 @@
"MESSAGE": "Somebody else is modifying the Plan at this moment. You may view the Plan but you cannot make any changes."
}
},
"PLAN-EVALUATE-DIALOG": {
"HEADER": "Ranking Information",
"DETAILS-SUB-HEADER": "Details",
"RANK-BODY": "Rank",
"DETAILS-BODY": "Details",
"MESSAGES-BODY": "Messages"
},
"DESCRIPTION-EVALUATE-DIALOG": {
"HEADER": "Ranking Information",
"DETAILS-SUB-HEADER": "Details",
"RANK-BODY": "Rank",
"DETAILS-BODY": "Details",
"MESSAGES-BODY": "Messages"
},
"REFERENCE-FIELD": {
"COULD-NOT-FIND-MESSAGE": "Couldn't find it?",
"ACTIONS": {