orgs_with_dashboard #9

Merged
michele.artini merged 10 commits from orgs_with_dashboard into master 2022-10-11 14:40:09 +02:00
22 changed files with 417 additions and 122 deletions

View File

@ -22,6 +22,7 @@ import eu.dnetlib.organizations.importer.ImportExecution;
import eu.dnetlib.organizations.importer.ImportExecutor; import eu.dnetlib.organizations.importer.ImportExecutor;
import eu.dnetlib.organizations.model.SystemConfiguration; import eu.dnetlib.organizations.model.SystemConfiguration;
import eu.dnetlib.organizations.model.utils.VocabularyTerm; import eu.dnetlib.organizations.model.utils.VocabularyTerm;
import eu.dnetlib.organizations.model.view.PersistentOrganizationView;
import eu.dnetlib.organizations.model.view.UserView; import eu.dnetlib.organizations.model.view.UserView;
import eu.dnetlib.organizations.repository.SystemConfigurationRepository; import eu.dnetlib.organizations.repository.SystemConfigurationRepository;
import eu.dnetlib.organizations.repository.UserRepository; import eu.dnetlib.organizations.repository.UserRepository;
@ -202,4 +203,22 @@ public class AdminController extends AbstractDnetController {
throw new RuntimeException("User not authorized"); throw new RuntimeException("User not authorized");
} }
} }
@GetMapping("/api/persistentOrgs")
public Iterable<PersistentOrganizationView> listPersistentOrgs() {
return dbUtils.listPersistentOrgs();
}
@PostMapping("/api/persistentOrgs")
public Iterable<PersistentOrganizationView> addPersistentOrgs(@RequestBody final List<String> ids) {
ids.forEach(id -> dbUtils.addPersistentOrgs(id));
return dbUtils.listPersistentOrgs();
}
@DeleteMapping("/api/persistentOrgs")
public Iterable<PersistentOrganizationView> deletePersistentOrgs(@RequestParam final String id) {
dbUtils.deletePersistentOrgs(id);
return dbUtils.listPersistentOrgs();
}
} }

View File

@ -11,6 +11,7 @@ import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
@ -30,6 +31,9 @@ public class HomeController extends AbstractDnetController {
@Autowired @Autowired
private SystemConfigurationRepository systemConfigurationRepository; private SystemConfigurationRepository systemConfigurationRepository;
@Value("${openaire.explore.organization.baseurl}")
private String openaireBaseUrl;
@Value("${openorgs.support.pages}") @Value("${openorgs.support.pages}")
private String supportPagesJson; private String supportPagesJson;
@ -40,6 +44,11 @@ public class HomeController extends AbstractDnetController {
return env.acceptsProfiles(Profiles.of("dev")) ? "redirect:main" : "home"; return env.acceptsProfiles(Profiles.of("dev")) ? "redirect:main" : "home";
} }
@GetMapping("/redirect/oa/{orgId}")
public String openaireUrl(@PathVariable final String orgId) {
return "redirect:" + String.format(openaireBaseUrl, orgId);
}
@GetMapping("/main") @GetMapping("/main")
public String main() { public String main() {
return "main"; return "main";

View File

@ -17,7 +17,6 @@ import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.PageRequest;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
@ -83,8 +82,6 @@ public class OrganizationController extends AbstractDnetController {
private JournalEntryRepository journalEntryRepository; private JournalEntryRepository journalEntryRepository;
@Autowired @Autowired
private DatabaseUtils databaseUtils; private DatabaseUtils databaseUtils;
@Value("${openaire.explore.organization.baseurl}")
private String oaBaseUrl;
@PostMapping("/save") @PostMapping("/save")
public List<String> save(@RequestBody final OrganizationView org, final Authentication authentication) { public List<String> save(@RequestBody final OrganizationView org, final Authentication authentication) {
@ -107,9 +104,7 @@ public class OrganizationController extends AbstractDnetController {
@GetMapping("/info") @GetMapping("/info")
public OrganizationInfoView infoById(@RequestParam final String id, final Authentication authentication) { public OrganizationInfoView infoById(@RequestParam final String id, final Authentication authentication) {
final OrganizationInfoView info = organizationInfoViewRepository.findById(id).get(); return organizationInfoViewRepository.findById(id).get();
info.fillGraphNodeInfo(info.getId(), oaBaseUrl);
return info;
} }
@GetMapping("/suggestionsInfo") @GetMapping("/suggestionsInfo")
@ -160,9 +155,7 @@ public class OrganizationController extends AbstractDnetController {
} }
private List<OpenaireDuplicateView> listDuplicates(final String id) { private List<OpenaireDuplicateView> listDuplicates(final String id) {
final List<OpenaireDuplicateView> list = openaireDuplicateViewRepository.findByLocalId(id); return openaireDuplicateViewRepository.findByLocalId(id);
list.forEach(d -> d.fillGraphNodeInfo(d.getOaOriginalId(), oaBaseUrl));
return list;
} }
@GetMapping("/conflicts/byCountry/{country}") @GetMapping("/conflicts/byCountry/{country}")

View File

@ -0,0 +1,34 @@
package eu.dnetlib.organizations.model;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "persistent_orgs")
public class PersistentOrganization implements Serializable {
private static final long serialVersionUID = 7684478315366015099L;
@Id
@Column(name = "id")
private String id;
public PersistentOrganization() {}
public PersistentOrganization(final String id) {
this.id = id;
}
public String getId() {
return id;
}
public void setId(final String id) {
this.id = id;
}
}

View File

@ -1,23 +1,31 @@
package eu.dnetlib.organizations.model.utils; package eu.dnetlib.organizations.model.utils;
import org.apache.commons.codec.digest.DigestUtils; import javax.persistence.Column;
import org.apache.commons.lang3.StringUtils; import javax.persistence.MappedSuperclass;
public interface OpenaireGraphNode { @MappedSuperclass
public abstract class OpenaireGraphNode {
String getOaGraphId(); @Column(name = "openaire_id")
private String openaireId;
void setOaGraphId(String oaGraphId); @Column(name = "openaire_persistent")
private Boolean persistent = false;
String getOaGraphUrl(); public String getOpenaireId() {
return openaireId;
}
void setOaGraphUrl(String oaGraphUrl); public void setOpenaireId(final String openaireId) {
this.openaireId = openaireId;
}
default void fillGraphNodeInfo(final String origId, final String baseUrl) { public Boolean getPersistent() {
final String oaGraphId = StringUtils.substringBefore(origId, "::") + "::" return persistent;
+ DigestUtils.md5Hex(StringUtils.substringAfter(origId, "::")); }
setOaGraphId(oaGraphId);
setOaGraphUrl(baseUrl + oaGraphId); public void setPersistent(final Boolean persistent) {
this.persistent = persistent;
} }
} }

View File

@ -8,7 +8,6 @@ import javax.persistence.Entity;
import javax.persistence.Id; import javax.persistence.Id;
import javax.persistence.IdClass; import javax.persistence.IdClass;
import javax.persistence.Table; import javax.persistence.Table;
import javax.persistence.Transient;
import org.hibernate.annotations.Type; import org.hibernate.annotations.Type;
@ -18,7 +17,7 @@ import eu.dnetlib.organizations.model.utils.OpenaireGraphNode;
@Entity @Entity
@Table(name = "oa_duplicates_view") @Table(name = "oa_duplicates_view")
@IdClass(OpenaireDuplicatePK.class) @IdClass(OpenaireDuplicatePK.class)
public class OpenaireDuplicateView implements Serializable, OpenaireGraphNode { public class OpenaireDuplicateView extends OpenaireGraphNode implements Serializable {
/** /**
* *
@ -88,12 +87,6 @@ public class OpenaireDuplicateView implements Serializable, OpenaireGraphNode {
@Column(name = "ec_nutscode") @Column(name = "ec_nutscode")
private Boolean ecNutscode; private Boolean ecNutscode;
@Transient
private String oaGraphId;
@Transient
private String oaGraphUrl;
public String getLocalId() { public String getLocalId() {
return localId; return localId;
} }
@ -254,24 +247,4 @@ public class OpenaireDuplicateView implements Serializable, OpenaireGraphNode {
this.ecNutscode = ecNutscode; this.ecNutscode = ecNutscode;
} }
@Override
public String getOaGraphId() {
return oaGraphId;
}
@Override
public void setOaGraphId(final String oaGraphId) {
this.oaGraphId = oaGraphId;
}
@Override
public String getOaGraphUrl() {
return oaGraphUrl;
}
@Override
public void setOaGraphUrl(final String oaGraphUrl) {
this.oaGraphUrl = oaGraphUrl;
}
} }

View File

@ -7,13 +7,12 @@ import javax.persistence.Column;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.Id; import javax.persistence.Id;
import javax.persistence.Table; import javax.persistence.Table;
import javax.persistence.Transient;
import eu.dnetlib.organizations.model.utils.OpenaireGraphNode; import eu.dnetlib.organizations.model.utils.OpenaireGraphNode;
@Entity @Entity
@Table(name = "organizations_info_view") @Table(name = "organizations_info_view")
public class OrganizationInfoView implements Serializable, OpenaireGraphNode { public class OrganizationInfoView extends OpenaireGraphNode implements Serializable {
/** /**
* *
@ -48,12 +47,6 @@ public class OrganizationInfoView implements Serializable, OpenaireGraphNode {
@Column(name = "note") @Column(name = "note")
private boolean note; private boolean note;
@Transient
private String oaGraphId;
@Transient
private String oaGraphUrl;
public String getId() { public String getId() {
return id; return id;
} }
@ -126,24 +119,4 @@ public class OrganizationInfoView implements Serializable, OpenaireGraphNode {
this.note = note; this.note = note;
} }
@Override
public String getOaGraphId() {
return oaGraphId;
}
@Override
public void setOaGraphId(final String oaGraphId) {
this.oaGraphId = oaGraphId;
}
@Override
public String getOaGraphUrl() {
return oaGraphUrl;
}
@Override
public void setOaGraphUrl(final String oaGraphUrl) {
this.oaGraphUrl = oaGraphUrl;
}
} }

View File

@ -15,13 +15,15 @@ import org.hibernate.annotations.TypeDefs;
import com.vladmihalcea.hibernate.type.json.JsonBinaryType; import com.vladmihalcea.hibernate.type.json.JsonBinaryType;
import com.vladmihalcea.hibernate.type.json.JsonStringType; import com.vladmihalcea.hibernate.type.json.JsonStringType;
import eu.dnetlib.organizations.model.utils.OpenaireGraphNode;
@Entity @Entity
@Table(name = "organizations_view") @Table(name = "organizations_view")
@TypeDefs({ @TypeDefs({
@TypeDef(name = "json", typeClass = JsonStringType.class), @TypeDef(name = "json", typeClass = JsonStringType.class),
@TypeDef(name = "jsonb", typeClass = JsonBinaryType.class) @TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)
}) })
public class OrganizationView implements Serializable { public class OrganizationView extends OpenaireGraphNode implements Serializable {
/** /**
* *

View File

@ -0,0 +1,63 @@
package eu.dnetlib.organizations.model.view;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import eu.dnetlib.organizations.model.utils.OpenaireGraphNode;
@Entity
@Table(name = "persistent_orgs_view")
public class PersistentOrganizationView extends OpenaireGraphNode implements Serializable {
private static final long serialVersionUID = -8906936709574708538L;
@Id
@Column(name = "id")
private String id;
@Column(name = "name")
private String name;
@Column(name = "city")
private String city;
@Column(name = "country")
private String country;
public String getId() {
return id;
}
public void setId(final String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
public String getCity() {
return city;
}
public void setCity(final String city) {
this.city = city;
}
public String getCountry() {
return country;
}
public void setCountry(final String country) {
this.country = country;
}
}

View File

@ -22,9 +22,10 @@ public interface OrganizationRepository extends JpaRepository<Organization, Stri
@Query("update Organization set status = ?2 where id = ?1") @Query("update Organization set status = ?2 where id = ?1")
void updateStatus(String id, String status); void updateStatus(String id, String status);
// to override the generation strategy of the ID
@Modifying @Modifying
@Query(value = "insert into organizations(id) values (?1)", nativeQuery = true) @Query(value = "insert into organizations(id) values (?1)", nativeQuery = true)
void preparePendingOrg(String id); void prepareOrgWithId(String id);
double countByStatus(String string); double countByStatus(String string);

View File

@ -0,0 +1,11 @@
package eu.dnetlib.organizations.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import eu.dnetlib.organizations.model.PersistentOrganization;
@Repository
public interface PersistentOrganizationRepository extends JpaRepository<PersistentOrganization, String> {
}

View File

@ -0,0 +1,10 @@
package eu.dnetlib.organizations.repository.readonly;
import org.springframework.stereotype.Repository;
import eu.dnetlib.organizations.model.view.PersistentOrganizationView;
@Repository
public interface PersistentOrganizationViewRepository extends ReadOnlyRepository<PersistentOrganizationView, String> {
}

View File

@ -12,10 +12,12 @@ import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.transaction.Transactional; import javax.transaction.Transactional;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
@ -37,6 +39,7 @@ import eu.dnetlib.organizations.model.OpenaireDuplicate;
import eu.dnetlib.organizations.model.Organization; import eu.dnetlib.organizations.model.Organization;
import eu.dnetlib.organizations.model.OtherIdentifier; import eu.dnetlib.organizations.model.OtherIdentifier;
import eu.dnetlib.organizations.model.OtherName; import eu.dnetlib.organizations.model.OtherName;
import eu.dnetlib.organizations.model.PersistentOrganization;
import eu.dnetlib.organizations.model.Relationship; import eu.dnetlib.organizations.model.Relationship;
import eu.dnetlib.organizations.model.Url; import eu.dnetlib.organizations.model.Url;
import eu.dnetlib.organizations.model.User; import eu.dnetlib.organizations.model.User;
@ -46,6 +49,7 @@ import eu.dnetlib.organizations.model.utils.OrganizationConflict;
import eu.dnetlib.organizations.model.utils.TempBrowseEntry; import eu.dnetlib.organizations.model.utils.TempBrowseEntry;
import eu.dnetlib.organizations.model.utils.VocabularyTerm; import eu.dnetlib.organizations.model.utils.VocabularyTerm;
import eu.dnetlib.organizations.model.view.OrganizationView; import eu.dnetlib.organizations.model.view.OrganizationView;
import eu.dnetlib.organizations.model.view.PersistentOrganizationView;
import eu.dnetlib.organizations.model.view.UserView; import eu.dnetlib.organizations.model.view.UserView;
import eu.dnetlib.organizations.repository.AcronymRepository; import eu.dnetlib.organizations.repository.AcronymRepository;
import eu.dnetlib.organizations.repository.JournalEntryRepository; import eu.dnetlib.organizations.repository.JournalEntryRepository;
@ -54,11 +58,13 @@ import eu.dnetlib.organizations.repository.OpenaireDuplicateRepository;
import eu.dnetlib.organizations.repository.OrganizationRepository; import eu.dnetlib.organizations.repository.OrganizationRepository;
import eu.dnetlib.organizations.repository.OtherIdentifierRepository; import eu.dnetlib.organizations.repository.OtherIdentifierRepository;
import eu.dnetlib.organizations.repository.OtherNameRepository; import eu.dnetlib.organizations.repository.OtherNameRepository;
import eu.dnetlib.organizations.repository.PersistentOrganizationRepository;
import eu.dnetlib.organizations.repository.RelationshipRepository; import eu.dnetlib.organizations.repository.RelationshipRepository;
import eu.dnetlib.organizations.repository.UrlRepository; import eu.dnetlib.organizations.repository.UrlRepository;
import eu.dnetlib.organizations.repository.UserCountryRepository; import eu.dnetlib.organizations.repository.UserCountryRepository;
import eu.dnetlib.organizations.repository.UserRepository; import eu.dnetlib.organizations.repository.UserRepository;
import eu.dnetlib.organizations.repository.readonly.OrganizationViewRepository; import eu.dnetlib.organizations.repository.readonly.OrganizationViewRepository;
import eu.dnetlib.organizations.repository.readonly.PersistentOrganizationViewRepository;
@Component @Component
public class DatabaseUtils { public class DatabaseUtils {
@ -87,6 +93,10 @@ public class DatabaseUtils {
private OrganizationViewRepository organizationViewRepository; private OrganizationViewRepository organizationViewRepository;
@Autowired @Autowired
private JournalEntryRepository journalEntryRepository; private JournalEntryRepository journalEntryRepository;
@Autowired
private PersistentOrganizationRepository persistentOrganizationRepository;
@Autowired
private PersistentOrganizationViewRepository persistentOrganizationViewRepository;
@Autowired @Autowired
private JdbcTemplate jdbcTemplate; private JdbcTemplate jdbcTemplate;
@ -129,7 +139,7 @@ public class DatabaseUtils {
final String pendingId = OpenOrgsConstants.OPENORGS_PENDING_PREFIX + UUID.randomUUID(); final String pendingId = OpenOrgsConstants.OPENORGS_PENDING_PREFIX + UUID.randomUUID();
orgView.setId(pendingId); orgView.setId(pendingId);
// to override the generation strategy of the ID // to override the generation strategy of the ID
organizationRepository.preparePendingOrg(pendingId); organizationRepository.prepareOrgWithId(pendingId);
} else { } else {
orgView.setId(null); // The ID is generated by the DB orgView.setId(null); // The ID is generated by the DB
} }
@ -468,45 +478,97 @@ public class DatabaseUtils {
@Transactional @Transactional
public String fixConflictSimilars(final List<String> similarIds, final String user) { public String fixConflictSimilars(final List<String> similarIds, final String user) {
final OffsetDateTime now = OffsetDateTime.now();
final List<OrganizationView> views = final List<OrganizationView> views =
similarIds.stream().map(organizationViewRepository::findById).filter(Optional::isPresent).map(Optional::get).collect(Collectors.toList()); similarIds.stream().map(organizationViewRepository::findById).filter(Optional::isPresent).map(Optional::get).collect(Collectors.toList());
// I create a new org final List<OrganizationView> persistents = views.stream().filter(v -> v.getPersistent()).collect(Collectors.toList());
final OrganizationView newOrg = new OrganizationView();
newOrg.setId(null);
newOrg.setStatus(null);
newOrg.setName(findFirstString(views, OrganizationView::getName));
newOrg.setType(findFirstString(views, OrganizationView::getType));
newOrg.setLat(findFirstNumber(views, OrganizationView::getLat));
newOrg.setLng(findFirstNumber(views, OrganizationView::getLng));
newOrg.setCity(findFirstString(views, OrganizationView::getCity));
newOrg.setCountry(findFirstString(views, OrganizationView::getCountry));
newOrg.setOtherIdentifiers(findAll(views, OrganizationView::getOtherIdentifiers));
newOrg.setOtherNames(findAll(views, OrganizationView::getOtherNames));
newOrg.setAcronyms(findAll(views, OrganizationView::getAcronyms));
newOrg.setUrls(findAll(views, OrganizationView::getUrls));
newOrg.setRelations(findAll(views, OrganizationView::getRelations));
newOrg.getOtherNames() final OrganizationView masterOrg = new OrganizationView();
.addAll(views.stream()
if (persistents.size() > 1) {
throw new RuntimeException("Too many persintent organizations");
} else if (persistents.size() == 1) {
backupOrg(persistents.get(0), user);
masterOrg.setId(persistents.get(0).getId());
masterOrg.setStatus(OrganizationStatus.approved.toString());
} else {
masterOrg.setId(null);
masterOrg.setStatus(null);
}
return fixConflicts(masterOrg, views, user);
}
private String backupOrg(final OrganizationView org, final String user) {
final String origId = org.getId();
final String backupId = origId + "::" + OffsetDateTime.now().toEpochSecond();
organizationRepository.prepareOrgWithId(backupId);
try {
final OrganizationView backupOrg = (OrganizationView) BeanUtils.cloneBean(org);
backupOrg.setId(backupId);
insertOrUpdateOrganization(backupOrg, user, false);
organizationRepository.updateStatus(backupId, OrganizationStatus.hidden.toString());
journalEntryRepository
.save(new JournalEntry(origId, JournalOperations.BACKUP_ORG, "Saved a backup copy: " + backupId, user));
journalEntryRepository
.save(new JournalEntry(backupId, JournalOperations.BACKUP_ORG, "Saved a backup copy of " + origId, user));
return backupId;
} catch (final Exception e) {
log.error("Error performing the backup of " + origId, e);
throw new RuntimeException("Error performing the backup of " + origId, e);
}
}
private String fixConflicts(final OrganizationView masterOrg, final List<OrganizationView> orgs, final String user) {
final OffsetDateTime now = OffsetDateTime.now();
final String finalMessage = (masterOrg.getId() == null ? "New org created merging: " : "Merging in persistent org: ") +
orgs.stream()
.map(OrganizationView::getId)
.collect(Collectors.joining(", "));
masterOrg.setName(findFirstString(orgs, OrganizationView::getName));
masterOrg.setType(findFirstString(orgs, OrganizationView::getType));
masterOrg.setLat(findFirstNumber(orgs, OrganizationView::getLat));
masterOrg.setLng(findFirstNumber(orgs, OrganizationView::getLng));
masterOrg.setCity(findFirstString(orgs, OrganizationView::getCity));
masterOrg.setCountry(findFirstString(orgs, OrganizationView::getCountry));
masterOrg.setOtherIdentifiers(findAll(orgs, OrganizationView::getOtherIdentifiers));
masterOrg.setOtherNames(findAll(orgs, OrganizationView::getOtherNames));
masterOrg.setAcronyms(findAll(orgs, OrganizationView::getAcronyms));
masterOrg.setUrls(findAll(orgs, OrganizationView::getUrls));
masterOrg.setRelations(findAll(orgs, OrganizationView::getRelations, r -> !r.getType().equals(RelationType.Merged_In.toString())
&& !r.getType().equals(RelationType.Merged_In.toString())));
masterOrg.getOtherNames()
.addAll(orgs.stream()
.map(OrganizationView::getName) .map(OrganizationView::getName)
.filter(StringUtils::isNotBlank) .filter(StringUtils::isNotBlank)
.filter(s -> StringUtils.equalsIgnoreCase(s, newOrg.getName())) .filter(s -> StringUtils.equalsIgnoreCase(s, masterOrg.getName()))
.map(s -> new eu.dnetlib.organizations.model.view.OtherName(s, "UNKNOWN")) .map(s -> new eu.dnetlib.organizations.model.view.OtherName(s, "UNKNOWN"))
.collect(Collectors.toList())); .collect(Collectors.toList()));
final String masterId = insertOrUpdateOrganization(newOrg, user, false); final String masterId = insertOrUpdateOrganization(masterOrg, user, false);
// I hide the merged organizations // I hide the merged organizations
similarIds.forEach(id -> { orgs.stream()
hideConflictOrgs(masterId, id); .map(OrganizationView::getId)
journalEntryRepository.save(new JournalEntry(masterId, JournalOperations.FIX_CONFLICT, "The org has been hidded and merged in " + masterId, user)); .filter(id -> !id.equals(masterId))
}); .forEach(id -> {
hideConflictOrgs(masterId, id);
journalEntryRepository
.save(new JournalEntry(id, JournalOperations.FIX_CONFLICT, "The org has been hidded and merged in " + masterId, user));
});
// I reassign the duplicates to the new org // I reassign the duplicates to the new org
final List<OpenaireDuplicate> newDuplicates = similarIds.stream() final List<OpenaireDuplicate> newDuplicates = orgs.stream()
.map(OrganizationView::getId)
.map(openaireDuplicateRepository::findByLocalId) .map(openaireDuplicateRepository::findByLocalId)
.flatMap(l -> l.stream()) .flatMap(l -> l.stream())
.map(d -> new OpenaireDuplicate(masterId, d.getOaOriginalId(), d.getRelType(), d.getOaCollectedFrom())) .map(d -> new OpenaireDuplicate(masterId, d.getOaOriginalId(), d.getRelType(), d.getOaCollectedFrom()))
@ -518,18 +580,20 @@ public class DatabaseUtils {
openaireDuplicateRepository.updateModificationDate(d.getLocalId(), d.getOaOriginalId(), user, now); openaireDuplicateRepository.updateModificationDate(d.getLocalId(), d.getOaOriginalId(), user, now);
}); });
for (final String similarId : similarIds) { orgs.forEach(org -> {
final String similarId = org.getId();
openaireConflictRepository.updateMultipleStatusAndResetGroup(similarId, SimilarityType.is_different.toString(), user, now); openaireConflictRepository.updateMultipleStatusAndResetGroup(similarId, SimilarityType.is_different.toString(), user, now);
} });
for (int i = 0; i < similarIds.size(); i++) { for (int i = 0; i < orgs.size(); i++) {
for (int j = i + 1; j < similarIds.size(); j++) { for (int j = i + 1; j < orgs.size(); j++) {
openaireConflictRepository.updateStatusAndResetGroup(similarIds.get(i), similarIds.get(j), SimilarityType.is_similar.toString(), user, now); openaireConflictRepository
.updateStatusAndResetGroup(orgs.get(i).getId(), orgs.get(j).getId(), SimilarityType.is_similar.toString(), user, now);
} }
} }
journalEntryRepository journalEntryRepository
.save(new JournalEntry(masterId, JournalOperations.FIX_CONFLICT, "New org created merging: " + StringUtils.join(similarIds, ", "), user)); .save(new JournalEntry(masterId, JournalOperations.FIX_CONFLICT, finalMessage, user));
return masterId; return masterId;
} }
@ -572,6 +636,10 @@ public class DatabaseUtils {
return views.stream().map(mapper).flatMap(s -> s.stream()).collect(Collectors.toCollection(LinkedHashSet::new)); return views.stream().map(mapper).flatMap(s -> s.stream()).collect(Collectors.toCollection(LinkedHashSet::new));
} }
private <T> Set<T> findAll(final List<OrganizationView> views, final Function<OrganizationView, Set<T>> mapper, final Predicate<T> filter) {
return views.stream().map(mapper).flatMap(s -> s.stream()).filter(filter).collect(Collectors.toCollection(LinkedHashSet::new));
}
private List<Relationship> hideConflictOrgs(final String masterId, final String otherId) { private List<Relationship> hideConflictOrgs(final String masterId, final String otherId) {
organizationRepository.updateStatus(otherId, OrganizationStatus.hidden.toString()); organizationRepository.updateStatus(otherId, OrganizationStatus.hidden.toString());
openaireConflictRepository.findById(new OpenaireConflictPK(masterId, otherId)).ifPresent(openaireConflictRepository::delete); openaireConflictRepository.findById(new OpenaireConflictPK(masterId, otherId)).ifPresent(openaireConflictRepository::delete);
@ -585,4 +653,24 @@ public class DatabaseUtils {
return jdbcTemplate.queryForList(sql, String.class); return jdbcTemplate.queryForList(sql, String.class);
} }
public Iterable<PersistentOrganizationView> listPersistentOrgs() {
return persistentOrganizationViewRepository.findAll();
}
public void addPersistentOrgs(final String id) {
final boolean valid = organizationRepository.findById(id)
.map(Organization::getStatus)
.filter(s -> s.equals(OrganizationStatus.approved.toString()))
.isPresent();
if (valid) {
persistentOrganizationRepository.save(new PersistentOrganization(id));
} else {
throw new RuntimeException("The ID does not refer to an approved Organization");
}
}
public void deletePersistentOrgs(final String id) {
persistentOrganizationRepository.deleteById(id);
}
} }

View File

@ -9,5 +9,6 @@ public enum JournalOperations {
DUPLICATES, DUPLICATES,
FIX_CONFLICT, FIX_CONFLICT,
NO_CONFLICT, NO_CONFLICT,
BACKUP_ORG,
UNKNOWN UNKNOWN
} }

View File

@ -51,4 +51,4 @@ openorgs.support.pages = { "Ask a question": "https://www.openaire.eu/support/he
openaire.override.logout.url = openaire.override.logout.url =
openaire.explore.organization.baseurl = https://explore.openaire.eu/search/organization?organizationId= openaire.explore.organization.baseurl = https://explore.openaire.eu/search/organization?organizationId=%s

View File

@ -5,6 +5,7 @@ DROP VIEW IF EXISTS users_view;
DROP VIEW IF EXISTS conflict_groups_view; DROP VIEW IF EXISTS conflict_groups_view;
DROP VIEW IF EXISTS suggestions_info_by_country_view; DROP VIEW IF EXISTS suggestions_info_by_country_view;
DROP VIEW IF EXISTS duplicate_groups_view; DROP VIEW IF EXISTS duplicate_groups_view;
DROP VIEW IF EXISTS persistent_orgs_view;
DROP TABLE IF EXISTS sysconf; DROP TABLE IF EXISTS sysconf;
DROP TABLE IF EXISTS other_ids; DROP TABLE IF EXISTS other_ids;
@ -14,6 +15,8 @@ DROP TABLE IF EXISTS relationships;
DROP TABLE IF EXISTS urls; DROP TABLE IF EXISTS urls;
DROP TABLE IF EXISTS oa_duplicates; DROP TABLE IF EXISTS oa_duplicates;
DROP TABLE IF EXISTS oa_conflicts; DROP TABLE IF EXISTS oa_conflicts;
DROP TABLE IF EXISTS persistent_orgs;
DROP TABLE IF EXISTS organizations; DROP TABLE IF EXISTS organizations;
DROP TABLE IF EXISTS org_types; DROP TABLE IF EXISTS org_types;
@ -374,6 +377,10 @@ CREATE TABLE organizations (
CREATE INDEX organizations_type_idx ON organizations(type); CREATE INDEX organizations_type_idx ON organizations(type);
CREATE INDEX organizations_country_idx ON organizations(country); CREATE INDEX organizations_country_idx ON organizations(country);
CREATE TABLE persistent_orgs (
id text PRIMARY KEY REFERENCES organizations(id) ON UPDATE CASCADE ON DELETE CASCADE
);
CREATE TABLE other_ids ( CREATE TABLE other_ids (
id text REFERENCES organizations(id) ON UPDATE CASCADE ON DELETE CASCADE, id text REFERENCES organizations(id) ON UPDATE CASCADE ON DELETE CASCADE,
otherid text, otherid text,
@ -464,7 +471,9 @@ CREATE VIEW oa_duplicates_view AS
o.ec_internationalorganization, o.ec_internationalorganization,
o.ec_enterprise, o.ec_enterprise,
o.ec_smevalidated, o.ec_smevalidated,
o.ec_nutscode o.ec_nutscode,
substr(d.oa_original_id, 1, 14)||md5(substr(d.oa_original_id, 15)) as openaire_id,
false as openaire_persistent
FROM FROM
oa_duplicates d oa_duplicates d
LEFT OUTER JOIN organizations o ON (o.id = d.oa_original_id) LEFT OUTER JOIN organizations o ON (o.id = d.oa_original_id)
@ -524,6 +533,8 @@ CREATE VIEW organizations_view AS SELECT
org.ec_enterprise, org.ec_enterprise,
org.ec_smevalidated, org.ec_smevalidated,
org.ec_nutscode, org.ec_nutscode,
substr(org.id, 1, 14)||md5(substr(org.id, 15)) as openaire_id,
count(po.id) > 0 as openaire_persistent,
COALESCE(jsonb_agg(DISTINCT jsonb_build_object('id', oid.otherid, 'type', oid.type)) FILTER (WHERE oid.otherid IS NOT NULL), '[]') AS other_ids, COALESCE(jsonb_agg(DISTINCT jsonb_build_object('id', oid.otherid, 'type', oid.type)) FILTER (WHERE oid.otherid IS NOT NULL), '[]') AS other_ids,
COALESCE(jsonb_agg(DISTINCT jsonb_build_object('name', n.name, 'lang', n.lang)) FILTER (WHERE n.name IS NOT NULL), '[]') AS other_names, COALESCE(jsonb_agg(DISTINCT jsonb_build_object('name', n.name, 'lang', n.lang)) FILTER (WHERE n.name IS NOT NULL), '[]') AS other_names,
COALESCE(jsonb_agg(DISTINCT a.acronym) FILTER (WHERE a.acronym IS NOT NULL), '[]') AS acronyms, COALESCE(jsonb_agg(DISTINCT a.acronym) FILTER (WHERE a.acronym IS NOT NULL), '[]') AS acronyms,
@ -537,6 +548,7 @@ FROM
LEFT OUTER JOIN urls u ON (org.id = u.id) LEFT OUTER JOIN urls u ON (org.id = u.id)
LEFT OUTER JOIN relationships r ON (org.id = r.id1) LEFT OUTER JOIN relationships r ON (org.id = r.id1)
LEFT OUTER JOIN organizations relorg ON (relorg.id = r.id2) LEFT OUTER JOIN organizations relorg ON (relorg.id = r.id2)
LEFT OUTER JOIN persistent_orgs po ON (org.id = po.id)
GROUP BY GROUP BY
org.id, org.id,
org.name, org.name,
@ -564,13 +576,16 @@ CREATE VIEW organizations_info_view AS SELECT
org.creation_date, org.creation_date,
org.modified_by, org.modified_by,
org.modification_date, org.modification_date,
substr(org.id, 1, 14)||md5(substr(org.id, 15)) as openaire_id,
count(po.id) > 0 as openaire_persistent,
count(DISTINCT d.oa_original_id) as n_duplicates, count(DISTINCT d.oa_original_id) as n_duplicates,
count(DISTINCT c.id2) as n_conflicts, count(DISTINCT c.id2) as n_conflicts,
count(DISTINCT n.note) > 0 as note count(DISTINCT n.note) > 0 as note
FROM organizations org FROM organizations org
LEFT OUTER JOIN oa_duplicates d ON (org.id = d.local_id AND d.reltype = 'suggested') LEFT OUTER JOIN oa_duplicates d ON (org.id = d.local_id AND d.reltype = 'suggested')
LEFT OUTER JOIN oa_conflicts c ON (org.id = c.id1 AND c.reltype = 'suggested') LEFT OUTER JOIN oa_conflicts c ON (org.id = c.id1 AND c.reltype = 'suggested')
LEFT OUTER JOIN notes n ON (org.id = n.id) LEFT OUTER JOIN notes n ON (org.id = n.id)
LEFT OUTER JOIN persistent_orgs po ON (org.id = po.id)
GROUP BY org.id; GROUP BY org.id;
CREATE VIEW organizations_simple_view AS SELECT CREATE VIEW organizations_simple_view AS SELECT
@ -674,6 +689,18 @@ WHERE
GROUP BY o.id, o.name, o.city, o.country GROUP BY o.id, o.name, o.city, o.country
ORDER BY o.name; ORDER BY o.name;
CREATE VIEW persistent_orgs_view AS SELECT
po.id,
substr(po.id, 1, 14)||md5(substr(po.id,15)) as openaire_id,
true as openaire_persistent,
o.name,
o.city,
o.country
FROM
persistent_orgs po
JOIN organizations o ON (po.id = o.id)
ORDER BY o.name;
CREATE TABLE org_index_search(id text PRIMARY KEY, txt tsvector); CREATE TABLE org_index_search(id text PRIMARY KEY, txt tsvector);
CREATE INDEX org_index_search_txt_gin_idx ON org_index_search USING gin(txt); CREATE INDEX org_index_search_txt_gin_idx ON org_index_search USING gin(txt);

View File

@ -0,0 +1,47 @@
<h4>Persistent Organizations</h4>
<p>
<small>It is necessary to persist the identifiers of the organizations associated to an Institutional Dashboard</small>
</p>
<table class="table table-sm small">
<thead class="thead-light">
<tr>
<th style="width: 200px">ID</th>
<th style="width: 300px">OA Graph Node ID</th>
<th>Name</th>
<th style="width: 300px">Place</th>
<th style="width: 40px"></th>
</tr>
</thead>
<tbody ng-show="orgs.length > 0">
<tr ng-repeat="o in orgs">
<td><a href="#!/edit/0/{{o.id}}">{{o.id}}</a></td>
<td>{{o.openaireId}}</td>
<td>{{o.name}}</td>
<td><img ng-src="resources/images/flags/{{o.country}}.gif" /> {{o.city || '-'}}, {{o.country}}</td>
<td class="text-right">
<button type="button" class="btn btn-sm btn-outline-danger" ng-click="deletePersistentOrg(o.id)">
<i class="fa fa-trash"></i>
</button>
</td>
</tr>
</tbody>
<tbody ng-hide="orgs.length > 0">
<tr>
<td colspan="5" class="text-muted">No persistent organizazions</td>
</tr>
</tbody>
<tfoot>
<tr ng-init="newId=''">
<td colspan="4">
<input type="text" class="form-control form-control-sm" placeholder="new persistent organization ID..." ng-model="newId" />
</td>
<td class="text-right">
<button type="button" class="btn btn-sm btn-outline-success" ng-click="addPersistentOrg(newId); newId=''" ng-disabled="!newId">
<i class="fa fa-plus"></i>
</button>
</td>
</tr>
</tfoot>
</table>

View File

@ -6,7 +6,9 @@
<b>ID: </b>{{info.id}}<br /> <b>ID: </b>{{info.id}}<br />
<b>Created at</b> {{info.creationDate | date:'MMMM d, y HH:mm:ss'}} <b>by</b> {{info.createdBy}}<br /> <b>Created at</b> {{info.creationDate | date:'MMMM d, y HH:mm:ss'}} <b>by</b> {{info.createdBy}}<br />
<b>Modified at</b> {{info.modificationDate | date:'MMMM d, y HH:mm:ss'}} <b>by</b> {{info.modifiedBy}}<br /> <b>Modified at</b> {{info.modificationDate | date:'MMMM d, y HH:mm:ss'}} <b>by</b> {{info.modifiedBy}}<br />
<b>OA Graph Node ID: </b> {{info.oaGraphId}} <a href="{{info.oaGraphUrl}}" target="_blank">[try on OA Explore]</a><br/> <b>OA Graph Node ID: </b> {{info.openaireId}} <a href="/redirect/oa/{{info.openaireId}}" target="_blank">[try on OA Explore]</a>
<span class="badge badge-info" title="It is probably related to an Institutional Dashboard" ng-show="info.persistent">persistent</span>
<br/>
</p> </p>
<div class="card"> <div class="card">

View File

@ -4,7 +4,7 @@
'text-white bg-success' : show == 'success', 'text-white bg-success' : show == 'success',
'text-white bg-info' : show == 'info', 'text-white bg-info' : show == 'info',
'bg-secondary' : show == 'secondary', 'bg-secondary' : show == 'secondary',
}">{{orgTitle}}</div> }">{{orgTitle}} <span class="badge badge-warning" title="It is probably related to an Institutional Dashboard" ng-show="org.persistent">persistent ID</span></div>
<table class="table table-sm table-condensed" style="table-layout: fixed"> <table class="table table-sm table-condensed" style="table-layout: fixed">
<tr class="d-flex"> <tr class="d-flex">
<th class="col-4 pl-3">Name</th> <th class="col-4 pl-3">Name</th>

View File

@ -36,7 +36,7 @@
<td class="col-2 text-center small"><img ng-src="resources/images/flags/{{sr.oaCountry}}.gif" /> {{sr.oaCountry}}</td> <td class="col-2 text-center small"><img ng-src="resources/images/flags/{{sr.oaCountry}}.gif" /> {{sr.oaCountry}}</td>
<td class="col-3 small"> <td class="col-3 small">
<b>Original Id:</b> <span class="text-monospace">{{sr.oaOriginalId}}</span><br /> <b>Original Id:</b> <span class="text-monospace">{{sr.oaOriginalId}}</span><br />
<b>OA Graph Node ID: </b> <span class="text-monospace">{{sr.oaGraphId}}</span> <a href="{{sr.oaGraphUrl}}" target="_blank">[try]</a> <b>OA Graph Node ID: </b> <span class="text-monospace">{{sr.openaireId}}</span> <a href="/redirect/oa/{{sr.openaireId}}" target="_blank">[try]</a>
<span ng-if="sr.oaCollectedFrom"><br /><b>Provenance:</b> {{sr.oaCollectedFrom}}</span> <span ng-if="sr.oaCollectedFrom"><br /><b>Provenance:</b> {{sr.oaCollectedFrom}}</span>
<span ng-if="sr.createdBy && sr.createdBy != 'dedupWf'"><br /><b>Added by:</b> {{sr.createdBy}}</span> <span ng-if="sr.createdBy && sr.createdBy != 'dedupWf'"><br /><b>Added by:</b> {{sr.createdBy}}</span>
</td> </td>

View File

@ -119,7 +119,12 @@ orgsModule.directive('resolveConflictsModal', function($http, $route, $window) {
call_http_post($http, 'api/organizations/conflicts/fix/similar', ids, function(res) { call_http_post($http, 'api/organizations/conflicts/fix/similar', ids, function(res) {
$('#' + scope.modalId).modal('hide'); $('#' + scope.modalId).modal('hide');
if (scope.openNewOrg == '1') { if (scope.openNewOrg == '1') {
$('#' + scope.modalId).on('hidden.bs.modal', function (e) { $window.location.assign('#!/edit/0/' + res.data[0]); }); $('#' + scope.modalId).on('hidden.bs.modal', function (e) {
var oldLoc = $window.location;
$window.location.assign('#!/edit/0/' + res.data[0]);
var newLoc = $window.location;
if (oldLoc == newLoc) $route.reload();
});
} else { } else {
$('#' + scope.modalId).on('hidden.bs.modal', function (e) { $route.reload(); }); $('#' + scope.modalId).on('hidden.bs.modal', function (e) { $route.reload(); });
} }
@ -323,7 +328,13 @@ orgsModule.directive('orgConflicts', function($http, $window, $location, $route,
angular.forEach(scope.conflicts, function(o, pos) { ids.push(o.id); }); angular.forEach(scope.conflicts, function(o, pos) { ids.push(o.id); });
if (merge) { if (merge) {
call_http_post($http, "api/organizations/conflicts/fix/similar", ids, function(res) { $window.location.assign('#!/edit/0/' + res.data[0]); }); call_http_post($http, "api/organizations/conflicts/fix/similar", ids, function(res) {
if (res.data[0] == scope.org.id) {
$route.reload();
} else {
$window.location.assign('#!/edit/0/' + res.data[0]);
}
});
} else { } else {
call_http_post($http, "api/organizations/conflicts/fix/different", ids, function(res) { $route.reload(); }); call_http_post($http, "api/organizations/conflicts/fix/different", ids, function(res) { $route.reload(); });
} }
@ -379,6 +390,7 @@ orgsModule.config(function($routeProvider) {
.when('/sysconf', { templateUrl: 'resources/html/pages/admin/sysConf.html', controller: 'sysConfCtrl' }) .when('/sysconf', { templateUrl: 'resources/html/pages/admin/sysConf.html', controller: 'sysConfCtrl' })
.when('/utils', { templateUrl: 'resources/html/pages/admin/utils.html', controller: 'utilsCtrl' }) .when('/utils', { templateUrl: 'resources/html/pages/admin/utils.html', controller: 'utilsCtrl' })
.when('/lastImport', { templateUrl: 'resources/html/pages/admin/lastImport.html', controller: 'lastImportCtrl' }) .when('/lastImport', { templateUrl: 'resources/html/pages/admin/lastImport.html', controller: 'lastImportCtrl' })
.when('/persistentOrgs', { templateUrl: 'resources/html/pages/admin/persistentOrgs.html', controller: 'persistentOrgsCtrl' })
.otherwise({ redirectTo: '/search' }); .otherwise({ redirectTo: '/search' });
}); });
@ -806,6 +818,26 @@ orgsModule.controller('lastImportCtrl', function ($scope, $http) {
$scope.refresh(); $scope.refresh();
}); });
orgsModule.controller('persistentOrgsCtrl', function ($scope, $http) {
$scope.orgs = {};
$scope.refresh = function() {
call_http_get($http, 'api/persistentOrgs', function(res) { $scope.orgs = res.data; });
}
$scope.addPersistentOrg = function(id) {
call_http_post($http, 'api/persistentOrgs', [ id ], function(res) { $scope.orgs = res.data; });
}
$scope.deletePersistentOrg = function(id) {
if (confirm("Are you sure ?")) {
call_http_delete($http, 'api/persistentOrgs?id=' + id, function(res) { $scope.orgs = res.data; });
}
}
$scope.refresh();
});
orgsModule.controller('utilsCtrl', function ($scope, $http) { orgsModule.controller('utilsCtrl', function ($scope, $http) {
$scope.fulltextIndexMessage = ''; $scope.fulltextIndexMessage = '';

View File

@ -117,6 +117,8 @@ fieldset > legend { font-size : 1.2rem !important; }
<a class="dropdown-item" href="#!/utils" sec:authorize="hasRole('ROLE_OPENORGS_ADMIN')">System utilities</a> <a class="dropdown-item" href="#!/utils" sec:authorize="hasRole('ROLE_OPENORGS_ADMIN')">System utilities</a>
<a class="dropdown-item" href="#!/lastImport" sec:authorize="hasRole('ROLE_OPENORGS_ADMIN')">Last import of suggestions</a> <a class="dropdown-item" href="#!/lastImport" sec:authorize="hasRole('ROLE_OPENORGS_ADMIN')">Last import of suggestions</a>
<div class="dropdown-divider" sec:authorize="hasRole('ROLE_OPENORGS_ADMIN')"></div> <div class="dropdown-divider" sec:authorize="hasRole('ROLE_OPENORGS_ADMIN')"></div>
<a class="dropdown-item" href="#!/persistentOrgs">Configure persistent organizations</a>
<div class="dropdown-divider" sec:authorize="hasRole('ROLE_OPENORGS_ADMIN')"></div>
<a class="dropdown-item" href="#!/users">Manage users</a> <a class="dropdown-item" href="#!/users">Manage users</a>
</div> </div>
</li> </li>