Merge pull request 'national_admin_functions' (#1) from national_admin_functions into master

This commit is contained in:
Michele Artini 2020-10-26 08:32:18 +01:00
commit 95808ba909
48 changed files with 1884 additions and 1148 deletions

View File

@ -32,7 +32,7 @@ public class MainApplication {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(p -> p.startsWith("/api/"))
.paths(p -> p.startsWith("/api/") || p.startsWith("/oa_api"))
.build()
.apiInfo(new ApiInfoBuilder()
.title("D-Net Organizations Service APIs")

View File

@ -23,12 +23,12 @@ public class MyAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(final HttpServletRequest req, final HttpServletResponse res, final AccessDeniedException e)
throws IOException, ServletException {
throws IOException, ServletException {
final Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null) {
logger.warn(String.format("User '%s' attempted to access the protected URL: %s", auth.getName(), req.getRequestURI()));
logger.warn(String.format("User '%s' (%s) attempted to access the protected URL: %s", auth.getName(), req.getRemoteAddr(), req.getRequestURI()));
}
if (UserInfo.isNotAuthorized(auth)) {

View File

@ -33,7 +33,8 @@ public class OpenaireInternalApiController {
log.warn("Call received by blaklisted ip (https proxy): " + req.getRemoteAddr());
throw new RuntimeException("Call received by blaklisted ip (https proxy): " + req.getRemoteAddr());
}
new Thread(databaseUtils::importDedupEvents).run();
new Thread(databaseUtils::importDedupEvents).start();
return Arrays.asList("Importing simrels (request from " + req.getRemoteAddr() + ") ...");
}
}

View File

@ -9,7 +9,6 @@ import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.commons.lang3.StringUtils;
@ -26,7 +25,7 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import eu.dnetlib.organizations.model.OpenaireDuplicate;
import eu.dnetlib.organizations.model.Relationship;
import eu.dnetlib.organizations.model.Organization;
import eu.dnetlib.organizations.model.utils.BrowseEntry;
import eu.dnetlib.organizations.model.utils.OrganizationConflict;
import eu.dnetlib.organizations.model.view.ConflictGroupView;
@ -35,6 +34,7 @@ import eu.dnetlib.organizations.model.view.OpenaireDuplicateView;
import eu.dnetlib.organizations.model.view.OrganizationInfoView;
import eu.dnetlib.organizations.model.view.OrganizationSimpleView;
import eu.dnetlib.organizations.model.view.OrganizationView;
import eu.dnetlib.organizations.repository.OrganizationRepository;
import eu.dnetlib.organizations.repository.UserCountryRepository;
import eu.dnetlib.organizations.repository.readonly.ConflictGroupViewRepository;
import eu.dnetlib.organizations.repository.readonly.DuplicateGroupViewRepository;
@ -49,6 +49,8 @@ import eu.dnetlib.organizations.utils.DatabaseUtils;
@RequestMapping("/api/organizations")
public class OrganizationController {
@Autowired
private OrganizationRepository organizationRepository;
@Autowired
private OrganizationViewRepository organizationViewRepository;
@Autowired
@ -78,7 +80,7 @@ public class OrganizationController {
} else if (StringUtils.isBlank(org.getType())) {
throw new RuntimeException("Missing field: type");
} else if (UserInfo.isSuperAdmin(authentication) || userCountryRepository.verifyAuthorizationForCountry(org.getCountry(), authentication.getName())) {
final String orgId = databaseUtils.insertOrUpdateOrganization(org, authentication.getName());
final String orgId = databaseUtils.insertOrUpdateOrganization(org, authentication.getName(), UserInfo.isSimpleUser(authentication));
return Arrays.asList(orgId);
} else {
throw new RuntimeException("User not authorized");
@ -118,6 +120,30 @@ public class OrganizationController {
}
}
@GetMapping("/delete")
public OrganizationView deleteById(@RequestParam final String id, final Authentication authentication) {
final Organization org = organizationRepository.findById(id).get();
if (UserInfo.isSuperAdmin(authentication) || UserInfo.isNationalAdmin(authentication) &&
userCountryRepository.verifyAuthorizationForCountry(org.getCountry(), authentication.getName())) {
return databaseUtils.markAsDeleted(id, authentication.getName());
} else {
throw new RuntimeException("User not authorized");
}
}
@GetMapping("/discard")
public OrganizationView discardById(@RequestParam final String id, final Authentication authentication) {
final Organization org = organizationRepository.findById(id).get();
if (UserInfo.isSuperAdmin(authentication) || UserInfo.isNationalAdmin(authentication) &&
userCountryRepository.verifyAuthorizationForCountry(org.getCountry(), authentication.getName())) {
return databaseUtils.markAsDiscarded(id, authentication.getName());
} else {
throw new RuntimeException("User not authorized");
}
}
@GetMapping("/conflicts")
public List<OrganizationConflict> conflicts(@RequestParam final String id, final Authentication authentication) {
if (UserInfo.isSuperAdmin(authentication) || userCountryRepository.verifyAuthorizationForId(id, authentication.getName())) {
@ -139,7 +165,7 @@ public class OrganizationController {
@GetMapping("/conflicts/byCountry/{country}")
public Collection<Set<OrganizationConflict>> findConflictsByCountry(@PathVariable final String country, final Authentication authentication) {
databaseUtils.verifyConflictGroups(false);
// databaseUtils.verifyConflictGroups(false);
if (UserInfo.isSuperAdmin(authentication)) {
return groupConflicts(conflictGroupViewRepository.findByCountry1OrCountry2(country, country).stream());
@ -210,26 +236,33 @@ public class OrganizationController {
public Page<OrganizationSimpleView> search(@PathVariable final int page,
@PathVariable final int size,
@RequestParam final String q,
@RequestParam(required = false, defaultValue = "suggested,approved") final String status,
final Authentication authentication) {
return UserInfo.isSuperAdmin(authentication)
? organizationSimpleViewRepository.search(q, PageRequest.of(page, size))
: organizationSimpleViewRepository.searchForUser(q, authentication.getName(), PageRequest.of(page, size));
? organizationSimpleViewRepository.search(q, Arrays.asList(status.split(",")), PageRequest.of(page, size))
: organizationSimpleViewRepository.searchForUser(q, authentication.getName(), Arrays.asList(status.split(",")), PageRequest.of(page, size));
}
@GetMapping("/byCountry/{code}/{page}/{size}")
public Page<OrganizationSimpleView> findByCountry(@PathVariable final String code,
@GetMapping("/byCountry/{status}/{code}/{page}/{size}")
public Page<OrganizationSimpleView> findByCountry(@PathVariable final String status,
@PathVariable final String code,
@PathVariable final int page,
@PathVariable final int size,
final Authentication authentication) {
if (UserInfo.isSuperAdmin(authentication) || userCountryRepository.verifyAuthorizationForCountry(code, authentication.getName())) {
return organizationSimpleViewRepository.findByCountryOrderByName(code, PageRequest.of(page, size));
if (status.equalsIgnoreCase("all")) {
return organizationSimpleViewRepository.findByCountryOrderByName(code, PageRequest.of(page, size));
} else {
return organizationSimpleViewRepository.findByCountryAndStatusOrderByName(code, status, PageRequest.of(page, size));
}
} else {
throw new RuntimeException("User not authorized");
}
}
@GetMapping("/byCountry/{status}/{code}")
public Iterable<OrganizationSimpleView> findPendingOrgsByCountry(@PathVariable final String status,
public Iterable<OrganizationSimpleView> findOrgsByStatusAndCountry(@PathVariable final String status,
@PathVariable final String code,
final Authentication authentication) {
if (UserInfo.isSuperAdmin(authentication) || userCountryRepository.verifyAuthorizationForCountry(code, authentication.getName())) {
@ -243,14 +276,27 @@ public class OrganizationController {
}
}
@GetMapping("/byType/{type}/{page}/{size}")
public Page<OrganizationSimpleView> findByType(@PathVariable final String type,
@GetMapping("/byType/{status}/{type}/{page}/{size}")
public Page<OrganizationSimpleView> findByType(@PathVariable final String status,
@PathVariable final String type,
@PathVariable final int page,
@PathVariable final int size,
final Authentication authentication) {
return UserInfo.isSuperAdmin(authentication)
? organizationSimpleViewRepository.findByTypeOrderByName(type, PageRequest.of(page, size))
: organizationSimpleViewRepository.findByTypeForUser(type, authentication.getName(), PageRequest.of(page, size));
if (UserInfo.isSuperAdmin(authentication)) {
if (status.equalsIgnoreCase("all")) {
return organizationSimpleViewRepository.findByTypeOrderByName(type, PageRequest.of(page, size));
} else {
return organizationSimpleViewRepository.findByTypeAndStatusOrderByName(type, status, PageRequest.of(page, size));
}
} else {
if (status.equalsIgnoreCase("all")) {
return organizationSimpleViewRepository.findByTypeForUser(type, authentication.getName(), PageRequest.of(page, size));
} else {
return organizationSimpleViewRepository.findByTypeAndStatusForUser(type, status, authentication.getName(), PageRequest.of(page, size));
}
}
}
@GetMapping("/browse/countries")
@ -268,18 +314,17 @@ public class OrganizationController {
}
@PostMapping("/conflicts/fix/{masterId}")
public List<Relationship> fixConflicts(final Authentication authentication, @PathVariable final String masterId, @RequestBody final List<String> otherIds) {
public List<String> fixConflicts(final Authentication authentication, @PathVariable final String masterId, @RequestBody final List<String> otherIds) {
if (UserInfo.isSuperAdmin(authentication) || userCountryRepository.verifyAuthorizationForId(masterId, authentication.getName())) {
final List<String> list = new ArrayList<>();
list.add(masterId);
list.addAll(otherIds);
final String newOrgId = databaseUtils.fixConflict(list, authentication.getName());
return otherIds.stream()
.filter(id -> UserInfo.isSuperAdmin(authentication) || userCountryRepository.verifyAuthorizationForId(id, authentication.getName()))
.map(id -> databaseUtils.fixDuplicate(masterId, id))
.flatMap(List::stream)
.collect(Collectors.toList());
return Arrays.asList(newOrgId);
} else {
return new ArrayList<>();
}
}
}

View File

@ -29,6 +29,14 @@ public class OpenaireDuplicate implements Serializable {
@Column(name = "reltype")
private String relType;
public OpenaireDuplicate() {}
public OpenaireDuplicate(final String localId, final String oaOriginalId, final String relType) {
this.localId = localId;
this.oaOriginalId = oaOriginalId;
this.relType = relType;
}
public String getLocalId() {
return localId;
}

View File

@ -1,6 +1,8 @@
package eu.dnetlib.organizations.model.utils;
import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.Map;
public class BrowseEntry implements Serializable {
@ -9,17 +11,16 @@ public class BrowseEntry implements Serializable {
*/
private static final long serialVersionUID = 8854955977257064470L;
private String value;
private String code;
private String name;
private int approved;
private int pending;
private Map<String, Integer> values = new LinkedHashMap<>();
public String getValue() {
return value;
public String getCode() {
return code;
}
public void setValue(final String value) {
this.value = value;
public void setCode(final String code) {
this.code = code;
}
public String getName() {
@ -30,20 +31,12 @@ public class BrowseEntry implements Serializable {
this.name = name;
}
public int getApproved() {
return approved;
public Map<String, Integer> getValues() {
return values;
}
public void setApproved(final int approved) {
this.approved = approved;
}
public int getPending() {
return pending;
}
public void setPending(final int pending) {
this.pending = pending;
public void setValues(final Map<String, Integer> values) {
this.values = values;
}
}

View File

@ -0,0 +1,48 @@
package eu.dnetlib.organizations.model.utils;
import java.io.Serializable;
public class TempBrowseEntry implements Serializable {
/**
*
*/
private static final long serialVersionUID = -2233409680550512318L;
private String code;
private String name;
private String group;
private Integer count;
public String getCode() {
return code;
}
public void setCode(final String code) {
this.code = code;
}
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
public String getGroup() {
return group;
}
public void setGroup(final String group) {
this.group = group;
}
public Integer getCount() {
return count;
}
public void setCount(final Integer count) {
this.count = count;
}
}

View File

@ -46,6 +46,10 @@ public class OrganizationSimpleView implements Serializable, Comparable<Organiza
@Column(name = "acronyms", columnDefinition = "text[]")
private String[] acronyms;
@Type(type = "string-array")
@Column(name = "urls", columnDefinition = "text[]")
private String[] urls;
@Column(name = "status")
private String status;
@ -122,6 +126,14 @@ public class OrganizationSimpleView implements Serializable, Comparable<Organiza
this.status = status;
}
public String[] getUrls() {
return urls;
}
public void setUrls(final String[] urls) {
this.urls = urls;
}
@Override
public int hashCode() {
return Objects.hash(id);

View File

@ -14,6 +14,13 @@ public class OtherName implements Serializable {
private String lang;
public OtherName() {}
public OtherName(final String name, final String lang) {
this.name = name;
this.lang = lang;
}
public String getName() {
return name;
}

View File

@ -22,8 +22,8 @@ public interface OpenaireConflictRepository extends JpaRepository<OpenaireConfli
void resetGroupIds();
@Modifying
@Query(value = "update oa_conflicts set modified_by = ?3, modification_date = ?4 where (id1 = ?1 and id2 = ?2) or (id1 = ?2 and id2 = ?1)", nativeQuery = true)
void updateModificationDate(String id1, String id2, String user, OffsetDateTime now);
@Query(value = "update oa_conflicts set reltype = ?3, modified_by = ?4, modification_date = ?5 where (id1 = ?1 and id2 = ?2) or (id1 = ?2 and id2 = ?1)", nativeQuery = true)
void updateStatus(String id1, String id2, String status, String user, OffsetDateTime now);
long countByGroupNull();

View File

@ -1,5 +1,7 @@
package eu.dnetlib.organizations.repository.readonly;
import java.util.List;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Query;
@ -12,12 +14,15 @@ import eu.dnetlib.organizations.model.view.OrganizationSimpleView;
public interface OrganizationSimpleViewRepository extends ReadOnlyRepository<OrganizationSimpleView, String> {
// SEARCH
@Query(value = "select o.* from organizations_simple_view o left outer join org_index_search idx on (idx.id = o.id) where idx.txt @@ plainto_tsquery(:text) order by o.name", nativeQuery = true)
Page<OrganizationSimpleView> search(@Param("text") String text, Pageable pageable);
@Query(value = "select o.* from organizations_simple_view o left outer join org_index_search idx on (idx.id = o.id) where idx.txt @@ plainto_tsquery(:text) and o.status in :statuses order by o.name", nativeQuery = true)
Page<OrganizationSimpleView> search(@Param("text") String text, @Param("statuses") List<String> statuses, Pageable pageable);
// SEARCH FOR USER
@Query(value = "select o.* from organizations_simple_view o left outer join org_index_search idx on (idx.id = o.id) left outer join user_countries uc on (uc.country = o.country) where idx.txt @@ plainto_tsquery(:text) and uc.email = :email order by o.name", nativeQuery = true)
Page<OrganizationSimpleView> searchForUser(@Param("text") String text, @Param("email") String email, Pageable pageable);
@Query(value = "select o.* from organizations_simple_view o left outer join org_index_search idx on (idx.id = o.id) left outer join user_countries uc on (uc.country = o.country) where idx.txt @@ plainto_tsquery(:text) and uc.email = :email and o.status in :statuses order by o.name", nativeQuery = true)
Page<OrganizationSimpleView> searchForUser(@Param("text") String text,
@Param("email") String email,
@Param("statuses") List<String> statuses,
Pageable pageable);
Page<OrganizationSimpleView> findByCountryOrderByName(String country, Pageable pageable);
@ -25,9 +30,16 @@ public interface OrganizationSimpleViewRepository extends ReadOnlyRepository<Org
Iterable<OrganizationSimpleView> findByCountryAndStatusOrderByName(String code, String status);
Page<OrganizationSimpleView> findByCountryAndStatusOrderByName(String code, String status, Pageable pageable);
Page<OrganizationSimpleView> findByTypeOrderByName(String type, Pageable pageable);
Page<OrganizationSimpleView> findByTypeAndStatusOrderByName(String type, String status, Pageable pageable);
@Query(value = "select o.* from organizations_simple_view o left outer join user_countries uc on (uc.country = o.country) where uc.email = ?2 and o.type = ?1 order by o.name", nativeQuery = true)
Page<OrganizationSimpleView> findByTypeForUser(String type, String name, Pageable pageable);
@Query(value = "select o.* from organizations_simple_view o left outer join user_countries uc on (uc.country = o.country) where o.type = ?1 and o.status = ?2 and uc.email = ?3 order by o.name", nativeQuery = true)
Page<OrganizationSimpleView> findByTypeAndStatusForUser(String type, String status, String name, Pageable pageable);
}

View File

@ -1,19 +1,21 @@
package eu.dnetlib.organizations.utils;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.UUID;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.transaction.Transactional;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@ -38,6 +40,7 @@ import eu.dnetlib.organizations.model.User;
import eu.dnetlib.organizations.model.UserCountry;
import eu.dnetlib.organizations.model.utils.BrowseEntry;
import eu.dnetlib.organizations.model.utils.OrganizationConflict;
import eu.dnetlib.organizations.model.utils.TempBrowseEntry;
import eu.dnetlib.organizations.model.utils.VocabularyTerm;
import eu.dnetlib.organizations.model.view.OrganizationView;
import eu.dnetlib.organizations.model.view.UserView;
@ -51,6 +54,7 @@ import eu.dnetlib.organizations.repository.RelationshipRepository;
import eu.dnetlib.organizations.repository.UrlRepository;
import eu.dnetlib.organizations.repository.UserCountryRepository;
import eu.dnetlib.organizations.repository.UserRepository;
import eu.dnetlib.organizations.repository.readonly.OrganizationViewRepository;
@Component
public class DatabaseUtils {
@ -75,6 +79,9 @@ public class DatabaseUtils {
private OpenaireConflictRepository openaireConflictRepository;
@Autowired
private OpenaireDuplicateRepository openaireDuplicateRepository;
@Autowired
private OrganizationViewRepository organizationViewRepository;
@Autowired
private JdbcTemplate jdbcTemplate;
@ -90,19 +97,29 @@ public class DatabaseUtils {
}
@Transactional
public String insertOrUpdateOrganization(final OrganizationView orgView, final String user) {
final boolean alreadyApproved = StringUtils.equals(orgView.getStatus(), OrganizationStatus.approved.toString());
public String insertOrUpdateOrganization(final OrganizationView orgView, final String user, final boolean isSimpleUser) {
final String oldId = orgView.getId();
final String oldStatus = orgView.getId() != null ? organizationRepository.findById(orgView.getId())
.map(Organization::getStatus)
.orElse(null) : null;
if (StringUtils.isBlank(orgView.getId())) {
orgView.setId(null);
} else if (!alreadyApproved) {
cleanOldRelations(oldId);
organizationRepository.deleteById(oldId);
orgView.setId(null);
final boolean alreadyApproved = StringUtils.equals(oldStatus, OrganizationStatus.approved.toString());
final String newStatus;
if (!isSimpleUser) { // IS ADMIN
newStatus = OrganizationStatus.approved.toString();
} else if (isSimpleUser && oldStatus == null) {
newStatus = OrganizationStatus.suggested.toString();
} else if (isSimpleUser && alreadyApproved) {
newStatus = OrganizationStatus.approved.toString();
} else {
cleanOldRelations(orgView.getId());
throw new RuntimeException("User not authorized");
}
final String oldId = StringUtils.isNotBlank(orgView.getId()) ? new String(orgView.getId()) : null;
if (oldId == null || !oldId.startsWith(OpenOrgsConstants.OPENORGS_PREFIX)) {
orgView.setId(null);
}
final Organization org = new Organization(orgView.getId(),
@ -110,39 +127,67 @@ public class DatabaseUtils {
orgView.getType(),
orgView.getLat(), orgView.getLng(),
orgView.getCity(), orgView.getCountry(),
OrganizationStatus.approved.toString());
newStatus);
final String newId = organizationRepository.save(org).getId();
makeNewRelations(newId, orgView);
final OffsetDateTime now = OffsetDateTime.now();
updateHistoryFields(newId, user, alreadyApproved);
organizationRepository.updateModificationDate(newId, user, now);
if (StringUtils.equals(newId, oldId)) {
makeRelations(newId, orgView, true);
} else {
organizationRepository.updateCreationDate(newId, user, now);
makeRelations(newId, orgView, false);
if (oldId != null) {
final List<OpenaireDuplicate> dups = new ArrayList<>();
dups.add(new OpenaireDuplicate(newId, oldId, SimilarityType.is_similar.toString()));
dups.addAll(openaireDuplicateRepository.findByLocalId(oldId)
.stream()
.map(d -> new OpenaireDuplicate(newId, d.getOaOriginalId(), SimilarityType.suggested.toString()))
.collect(Collectors.toList()));
openaireDuplicateRepository.saveAll(dups);
dups.forEach(d -> openaireDuplicateRepository.updateModificationDate(d.getLocalId(), d.getOaOriginalId(), user, now));
organizationRepository.updateStatus(oldId, OrganizationStatus.duplicate.toString());
organizationRepository.updateModificationDate(oldId, user, now);
}
}
return newId;
}
private void updateHistoryFields(final String id, final String user, final boolean update) {
final OffsetDateTime now = OffsetDateTime.now();
if (update) {
organizationRepository.updateModificationDate(id, user, now);
} else {
organizationRepository.updateCreationDate(id, user, now);
organizationRepository.updateModificationDate(id, user, now);
}
}
@Transactional
public void saveDuplicates(final List<OpenaireDuplicate> simrels, final String email) {
public void saveDuplicates(final List<OpenaireDuplicate> simrels, final String user) {
final OffsetDateTime now = OffsetDateTime.now();
final List<OpenaireDuplicate> list = openaireDuplicateRepository.saveAll(simrels);
list.forEach(d -> openaireDuplicateRepository.updateModificationDate(d.getLocalId(), d.getOaOriginalId(), email, now));
list.forEach(d -> {
openaireDuplicateRepository.updateModificationDate(d.getLocalId(), d.getOaOriginalId(), user, now);
if (d.getRelType().equals(SimilarityType.is_different.toString())) {
updateStatus(d.getOaOriginalId(), OrganizationStatus.suggested, user, now);
} else {
updateStatus(d.getOaOriginalId(), OrganizationStatus.duplicate, user, now);
}
});
}
private void makeNewRelations(final String orgId, final OrganizationView orgView) {
private void makeRelations(final String orgId, final OrganizationView orgView, final boolean update) {
if (update) {
acronymRepository.deleteByOrgId(orgId);
otherNameRepository.deleteByOrgId(orgId);
otherIdentifierRepository.deleteByOrgId(orgId);
urlRepository.deleteByOrgId(orgId);
relationshipRepository.deleteById1(orgId);
relationshipRepository.deleteById2(orgId);
}
orgView.getAcronyms().forEach(s -> acronymRepository.save(new Acronym(orgId, s)));
orgView.getOtherNames().forEach(n -> otherNameRepository.save(new OtherName(orgId, n.getName(), n.getLang())));
orgView.getOtherIdentifiers().forEach(id -> otherIdentifierRepository.save(new OtherIdentifier(orgId, id.getId(), id.getType())));
@ -150,15 +195,6 @@ public class DatabaseUtils {
orgView.getRelations().forEach(r -> makeRelation(orgId, r.getRelatedOrgId(), RelationType.valueOf(r.getType())));
}
private void cleanOldRelations(final String id) {
acronymRepository.deleteByOrgId(id);
otherNameRepository.deleteByOrgId(id);
otherIdentifierRepository.deleteByOrgId(id);
urlRepository.deleteByOrgId(id);
relationshipRepository.deleteById1(id);
relationshipRepository.deleteById2(id);
}
@Cacheable("vocs")
public List<VocabularyTerm> listValuesOfVocabularyTable(final VocabularyTable table) {
final String sql = "select val as value, name as name from " + table;
@ -203,54 +239,30 @@ public class DatabaseUtils {
}
}
@Transactional
public void verifyConflictGroups(final boolean forceUpdate) {
if (forceUpdate || openaireConflictRepository.countByGroupNull() > 0) {
log.info("Recreating conflicts group...");
openaireConflictRepository.resetGroupIds();
final Map<String, Set<String>> groups = new HashMap<>();
for (final OpenaireConflict w : openaireConflictRepository.findAll()) {
final List<String> list = findExistingGroupsForRel(w, groups);
if (list.isEmpty()) {
final String idGroup = generateGroupId();
groups.put(idGroup, new HashSet<>());
addToGroup(groups, idGroup, w);
} else if (list.size() == 1) {
addToGroup(groups, list.get(0), w);
} else {
final String idGroup = generateGroupId();
groups.put(idGroup, new TreeSet<>());
list.forEach(id -> groups.get(idGroup).addAll(groups.get(id)));
list.forEach(id -> groups.remove(id));
addToGroup(groups, idGroup, w);
}
}
for (final Entry<String, Set<String>> e : groups.entrySet()) {
final String gid = e.getKey();
for (final String orgId : e.getValue()) {
for (final OpenaireConflict oc : openaireConflictRepository.findById1AndGroupIsNull(orgId)) {
oc.setGroup(gid);
openaireConflictRepository.save(oc);
}
for (final OpenaireConflict oc : openaireConflictRepository.findById2AndGroupIsNull(orgId)) {
oc.setGroup(gid);
openaireConflictRepository.save(oc);
}
}
}
log.info("...conflicts group recreated");
}
}
private String generateGroupId() {
return "group::" + UUID.randomUUID();
}
/*
* @Transactional public void verifyConflictGroups(final boolean forceUpdate) {
*
* if (forceUpdate || openaireConflictRepository.countByGroupNull() > 0) {
*
* log.info("Recreating conflicts group...");
*
* openaireConflictRepository.resetGroupIds();
*
* final Map<String, Set<String>> groups = new HashMap<>(); for (final OpenaireConflict w : openaireConflictRepository.findAll()) {
* final List<String> list = findExistingGroupsForRel(w, groups); if (list.isEmpty()) { final String idGroup = generateGroupId();
* groups.put(idGroup, new HashSet<>()); addToGroup(groups, idGroup, w); } else if (list.size() == 1) { addToGroup(groups, list.get(0),
* w); } else { final String idGroup = generateGroupId(); groups.put(idGroup, new TreeSet<>()); list.forEach(id ->
* groups.get(idGroup).addAll(groups.get(id))); list.forEach(id -> groups.remove(id)); addToGroup(groups, idGroup, w); } }
*
* for (final Entry<String, Set<String>> e : groups.entrySet()) { final String gid = e.getKey(); for (final String orgId : e.getValue())
* { for (final OpenaireConflict oc : openaireConflictRepository.findById1AndGroupIsNull(orgId)) { oc.setGroup(gid);
* openaireConflictRepository.save(oc); } for (final OpenaireConflict oc : openaireConflictRepository.findById2AndGroupIsNull(orgId)) {
* oc.setGroup(gid); openaireConflictRepository.save(oc); } } }
*
* log.info("...conflict groups recreated"); } }
*
* private String generateGroupId() { return "group::" + UUID.randomUUID(); }
*/
private List<String> findExistingGroupsForRel(final OpenaireConflict w, final Map<String, Set<String>> groups) {
return groups.entrySet()
@ -280,36 +292,51 @@ public class DatabaseUtils {
// BROWSE BY COUNTRY
public List<BrowseEntry> browseCountries() {
final String sql =
"select o.country as value, c.name as name, sum(case when status='approved' then 1 else 0 end) as approved, sum(case when status='pending' then 1 else 0 end) as pending from organizations o left outer join countries c on (o.country = c.val) group by o.country, c.name order by approved desc";
return jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(BrowseEntry.class));
"select o.country as code, c.name as name, o.status as group, count(o.status) as count from organizations o left outer join countries c on (o.country = c.val) group by o.country, c.name, o.status";
return listBrowseEntries(sql);
}
// BROWSE BY COUNTRY FOR USER
public List<BrowseEntry> browseCountriesForUser(final String email) {
final String sql =
"select o.country as value, c.name as name, sum(case when status='approved' then 1 else 0 end) as approved, sum(case when status='pending' then 1 else 0 end) as pending from user_countries uc left outer join organizations o on (uc.country = o.country) left outer join countries c on (o.country = c.val) where uc.email=? group by o.country, c.name order by approved desc";
return jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(BrowseEntry.class), email);
"select o.country as code, c.name as name, o.status as group, count(o.status) as count from user_countries uc left outer join organizations o on (uc.country = o.country) left outer join countries c on (o.country = c.val) where uc.email=? group by o.country, c.name, o.status";
return listBrowseEntries(sql, email);
}
// BROWSE BY ORG TYPE
public List<BrowseEntry> browseTypes() {
final String sql =
"select type as value, type as name, sum(case when status='approved' then 1 else 0 end) as approved, sum(case when status='pending' then 1 else 0 end) as pending from organizations group by type order by approved desc";
return jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(BrowseEntry.class));
"select type as code, type as name, status as group, count(status) as count from organizations group by type, status";
return listBrowseEntries(sql);
}
// BROWSE BY ORG TYPE FOR USER
public List<BrowseEntry> browseTypesForUser(final String email) {
final String sql = "select o.type as value, o.type as name,"
+ "sum(case when status='approved' then 1 else 0 end) as approved, "
+ "sum(case when status='pending' then 1 else 0 end) as pending "
final String sql = "select o.type as code, o.type as name,"
+ "o.status as group, count(o.status) as count "
+ "from organizations o "
+ "left outer join user_countries uc on (uc.country = o.country) "
+ "where uc.email=? "
+ "group by o.type "
+ "order by approved desc;";
return jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(BrowseEntry.class), email);
+ "group by o.type, o.status";
return listBrowseEntries(sql, email);
}
private List<BrowseEntry> listBrowseEntries(final String sql, final Object... params) {
final Map<String, BrowseEntry> map = new HashMap<>();
for (final TempBrowseEntry t : jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(TempBrowseEntry.class), params)) {
if (StringUtils.isNotBlank(t.getCode())) {
if (!map.containsKey(t.getCode())) {
final BrowseEntry e = new BrowseEntry();
e.setCode(t.getCode());
e.setName(t.getName());
map.put(t.getCode(), e);
}
map.get(t.getCode()).getValues().put(t.getGroup(), t.getCount());
}
}
return map.values().stream().sorted((o1, o2) -> StringUtils.compare(o1.getName(), o2.getName())).collect(Collectors.toList());
}
public List<OrganizationConflict> listConflictsForId(final String id) {
@ -321,9 +348,10 @@ public class DatabaseUtils {
@Transactional
public void importDedupEvents() {
try {
// log.info("Importing conflicts and duplicates...");
// jdbcTemplate.update(IOUtils.toString(getClass().getResourceAsStream("/sql/importNewRels.sql")));
// log.info("...done");
log.info("Importing conflicts and duplicates...");
jdbcTemplate.update(IOUtils.toString(getClass().getResourceAsStream("/sql/importDedupEvents.sql")));
log.info("...done");
// verifyConflictGroups(true);
} catch (final Exception e) {
log.error("Error importing conflicts and duplicates", e);
@ -331,13 +359,98 @@ public class DatabaseUtils {
}
@Transactional
public List<Relationship> fixDuplicate(final String masterId, final String otherId) {
public OrganizationView markAsDeleted(final String id, final String user) {
final OffsetDateTime now = OffsetDateTime.now();
updateStatus(id, OrganizationStatus.deleted, user, now);
return organizationViewRepository.findById(id).get();
}
@Transactional
public OrganizationView markAsDiscarded(final String id, final String user) {
final OffsetDateTime now = OffsetDateTime.now();
updateStatus(id, OrganizationStatus.discarded, user, now);
openaireDuplicateRepository.findByLocalId(id).forEach(d -> updateStatus(d.getOaOriginalId(), OrganizationStatus.suggested, user, now));
return organizationViewRepository.findById(id).get();
}
private void updateStatus(final String id, final OrganizationStatus status, final String user, final OffsetDateTime now) {
organizationRepository.updateStatus(id, status.toString());
organizationRepository.updateModificationDate(id, user, now);
}
@Transactional
public String fixConflict(final List<String> ids, final String user) {
final List<OrganizationView> views =
ids.stream().map(organizationViewRepository::findById).filter(Optional::isPresent).map(Optional::get).collect(Collectors.toList());
// I create a new org
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()
.addAll(views.stream()
.map(OrganizationView::getName)
.filter(StringUtils::isNotBlank)
.filter(s -> StringUtils.equalsIgnoreCase(s, newOrg.getName()))
.map(s -> new eu.dnetlib.organizations.model.view.OtherName(s, "UNKNOWN"))
.collect(Collectors.toList()));
final String masterId = insertOrUpdateOrganization(newOrg, user, false);
// I hide the merged organizations
ids.forEach(id -> hideConflictOrgs(masterId, id));
// I reassign the duplicated to the new org
final List<OpenaireDuplicate> newDuplicates = ids.stream()
.map(openaireDuplicateRepository::findByLocalId)
.flatMap(l -> l.stream())
.filter(d -> d.getRelType().equals(SimilarityType.is_similar.toString()))
.map(d -> new OpenaireDuplicate(masterId, d.getOaOriginalId(), d.getRelType()))
.collect(Collectors.toList());
newDuplicates.forEach(d -> d.setLocalId(masterId));
final OffsetDateTime now = OffsetDateTime.now();
for (int i = 0; i < ids.size(); i++) {
for (int j = i + 1; j < ids.size(); j++) {
openaireConflictRepository.updateStatus(ids.get(i), ids.get(j), SimilarityType.is_similar.toString(), user, now);
}
}
return masterId;
}
private String findFirstString(final List<OrganizationView> views, final Function<OrganizationView, String> mapper) {
return views.stream().map(mapper).filter(StringUtils::isNotBlank).findFirst().orElse(null);
}
private Double findFirstNumber(final List<OrganizationView> views, final Function<OrganizationView, Double> mapper) {
return views.stream().map(mapper).filter(Objects::nonNull).filter(n -> n != 0).findFirst().orElse(0.0);
}
private <T> Set<T> findAll(final List<OrganizationView> views, final Function<OrganizationView, Set<T>> mapper) {
return views.stream().map(mapper).flatMap(s -> s.stream()).collect(Collectors.toCollection(LinkedHashSet::new));
}
private List<Relationship> hideConflictOrgs(final String masterId, final String otherId) {
organizationRepository.updateStatus(otherId, OrganizationStatus.hidden.toString());
openaireConflictRepository.findById(new OpenaireConflictPK(masterId, otherId)).ifPresent(openaireConflictRepository::delete);
openaireConflictRepository.findById(new OpenaireConflictPK(otherId, masterId)).ifPresent(openaireConflictRepository::delete);
// TODO Merge the organizations ???
return makeRelation(masterId, otherId, RelationType.Merges);
}

View File

@ -1,10 +1,10 @@
package eu.dnetlib.organizations.utils;
public enum OrganizationStatus {
pending,
approved,
discarded,
hidden,
deleted,
duplicate
suggested, // from user or dedup depends by created_by field
approved, // normal status of valid organizations
discarded, // suggested organization that have been rejected by an administrator
hidden, // hidden organizations after the fix of a conflict
deleted, // organizations that are virtually deleted
duplicate // organizations that duplicate of a valid organization (their id is not an openorgs id)
}

View File

@ -13,9 +13,9 @@ spring.jpa.properties.hibernate.hbm2dll.extra_physical_table_types = MATERIALIZE
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
spring.jpa.open-in-view=true
spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.use_sql_comments=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.show_sql=false
spring.jpa.properties.hibernate.use_sql_comments=false
spring.jpa.properties.hibernate.format_sql=false
# the ICM private network
openaire.api.valid.subnet = 10.19.65.0/24

View File

@ -0,0 +1,43 @@
BEGIN;
DELETE FROM oa_conflicts WHERE created_by = 'dedupWf' and reltype = 'suggested';
DELETE FROM oa_duplicates WHERE created_by = 'dedupWf' and reltype = 'suggested';
DELETE FROM organizations WHERE created_by = 'dedupWf' and modified_by = 'dedupWf';
-- FIX IMPORT DATA
UPDATE tmp_dedup_events SET oa_country = 'UNKNOWN' WHERE oa_country = '' OR oa_country IS NULL;
-- NEW ORGANIZATIONS
INSERT INTO organizations(id, name, country, status, created_by, modified_by)
SELECT oa_original_id, oa_name, oa_country, 'suggested', 'dedupWf', 'dedupWf'
FROM tmp_dedup_events
WHERE oa_original_id NOT LIKE 'openorgs\_\_\_\_::%' AND oa_original_id = local_id
ON CONFLICT DO NOTHING;
INSERT INTO organizations(id, name, country, status, created_by, modified_by)
SELECT oa_original_id, oa_name, oa_country, 'duplicate', 'dedupWf', 'dedupWf'
FROM tmp_dedup_events
WHERE oa_original_id NOT LIKE 'openorgs\_\_\_\_::%' AND oa_original_id != local_id
ON CONFLICT DO NOTHING;
INSERT INTO acronyms(id, acronym) SELECT oa_original_id, oa_acronym FROM tmp_dedup_events WHERE oa_original_id NOT LIKE 'openorgs\_\_\_\_::%' ON CONFLICT DO NOTHING;
INSERT INTO urls(id, url) SELECT oa_original_id, oa_url FROM tmp_dedup_events WHERE oa_original_id NOT LIKE 'openorgs\_\_\_\_::%' ON CONFLICT DO NOTHING;
-- DUPLICATES
INSERT INTO oa_duplicates (local_id, oa_original_id, oa_collectedfrom, created_by, modified_by)
SELECT local_id, oa_original_id, oa_collectedfrom, 'dedupWf', 'dedupWf'
FROM tmp_dedup_events
WHERE local_id IS NOT NULL AND local_id != '' AND oa_original_id NOT LIKE 'openorgs\_\_\_\_::%' AND local_id != oa_original_id
ON CONFLICT DO NOTHING;
-- CONFLICTS
INSERT INTO oa_conflicts (id1, id2, idgroup, created_by, modified_by)
SELECT local_id, oa_original_id, group_id, 'dedupWf', 'dedupWf'
FROM tmp_dedup_events
WHERE local_id LIKE 'openorgs\_\_\_\_::%' AND oa_original_id LIKE 'openorgs\_\_\_\_::%' AND local_id != oa_original_id AND group_id IS NOT NULL AND group_id != ''
ON CONFLICT DO NOTHING;
COMMIT;

View File

@ -1,15 +0,0 @@
DELETE FROM oa_duplicates WHERE reltype = 'suggested';
DELETE FROM oa_conflicts WHERE reltype = 'suggested';
UPDATE oa_conflicts SET idgroup = NULL;
INSERT INTO oa_duplicates (local_id, oa_original_id, oa_name, oa_acronym, oa_country, oa_url, oa_collectedfrom)
SELECT local_id, oa_original_id, oa_name, oa_acronym, oa_country, oa_url, oa_collectedfrom
FROM tmp_simrels
WHERE oa_original_id NOT LIKE 'openorgs____::%'
ON CONFLICT DO NOTHING;
INSERT INTO oa_conflicts (id1, id2)
SELECT local_id, oa_original_id
FROM tmp_simrels
WHERE oa_original_id LIKE 'openorgs____::%'
ON CONFLICT DO NOTHING;

View File

@ -335,13 +335,13 @@ CREATE TABLE organizations (
creation_date timestamp with time zone DEFAULT now(),
modified_by text,
modification_date timestamp with time zone DEFAULT now(),
status text NOT NULL DEFAULT 'pending'
status text NOT NULL DEFAULT 'suggested'
);
CREATE INDEX organizations_type_idx ON organizations(type);
CREATE INDEX organizations_country_idx ON organizations(country);
CREATE TABLE other_ids (
id text REFERENCES organizations(id) ON UPDATE CASCADE,
id text REFERENCES organizations(id) ON UPDATE CASCADE ON DELETE CASCADE,
otherid text,
type text REFERENCES id_types(val),
PRIMARY KEY (id, otherid, type)
@ -349,7 +349,7 @@ CREATE TABLE other_ids (
CREATE INDEX other_ids_id_idx ON other_ids(id);
CREATE TABLE other_names (
id text REFERENCES organizations(id) ON UPDATE CASCADE,
id text REFERENCES organizations(id) ON UPDATE CASCADE ON DELETE CASCADE,
name text,
lang text REFERENCES languages(val),
PRIMARY KEY (id, name, lang)
@ -357,31 +357,31 @@ CREATE TABLE other_names (
CREATE INDEX other_names_id_idx ON other_names(id);
CREATE TABLE acronyms (
id text REFERENCES organizations(id) ON UPDATE CASCADE,
id text REFERENCES organizations(id) ON UPDATE CASCADE ON DELETE CASCADE,
acronym text,
PRIMARY KEY (id, acronym)
);
CREATE INDEX acronyms_id_idx ON acronyms(id);
CREATE TABLE relationships (
id1 text REFERENCES organizations(id) ON UPDATE CASCADE,
id1 text REFERENCES organizations(id) ON UPDATE CASCADE ON DELETE CASCADE,
reltype text,
id2 text REFERENCES organizations(id) ON UPDATE CASCADE,
id2 text REFERENCES organizations(id) ON UPDATE CASCADE ON DELETE CASCADE,
PRIMARY KEY (id1, reltype, id2)
);
CREATE INDEX relationships_id1_idx ON relationships(id1);
CREATE INDEX relationships_id2_idx ON relationships(id2);
CREATE TABLE urls (
id text REFERENCES organizations(id) ON UPDATE CASCADE,
id text REFERENCES organizations(id) ON UPDATE CASCADE ON DELETE CASCADE,
url text,
PRIMARY KEY (id, url)
);
CREATE INDEX urls_id_idx ON urls(id);
CREATE TABLE oa_duplicates (
local_id text REFERENCES organizations(id) ON UPDATE CASCADE,
oa_original_id text REFERENCES organizations(id) ON UPDATE CASCADE,
local_id text REFERENCES organizations(id) ON UPDATE CASCADE ON DELETE CASCADE,
oa_original_id text REFERENCES organizations(id) ON UPDATE CASCADE ON DELETE CASCADE,
oa_name text NOT NULL,
oa_acronym text,
oa_country text,
@ -389,6 +389,7 @@ CREATE TABLE oa_duplicates (
oa_collectedfrom text,
reltype text NOT NULL DEFAULT 'suggested',
creation_date timestamp DEFAULT NOW(),
created_by text,
modification_date timestamp,
modified_by text,
PRIMARY KEY (local_id, oa_original_id)
@ -419,11 +420,12 @@ GROUP BY
d.reltype;
CREATE TABLE oa_conflicts (
id1 text REFERENCES organizations(id) ON UPDATE CASCADE,
id2 text REFERENCES organizations(id) ON UPDATE CASCADE,
id1 text REFERENCES organizations(id) ON UPDATE CASCADE ON DELETE CASCADE,
id2 text REFERENCES organizations(id) ON UPDATE CASCADE ON DELETE CASCADE,
reltype text NOT NULL DEFAULT 'suggested',
idgroup text,
creation_date timestamp DEFAULT NOW(),
created_by text,
modification_date timestamp,
modified_by text,
PRIMARY KEY (id1, id2)
@ -485,10 +487,12 @@ CREATE VIEW organizations_simple_view AS SELECT
org.city,
org.country,
org.status,
array_remove(array_agg(DISTINCT a.acronym), NULL) AS acronyms
array_remove(array_agg(DISTINCT a.acronym), NULL) AS acronyms,
array_remove(array_agg(DISTINCT u.url), NULL) AS urls
FROM
organizations org
LEFT OUTER JOIN acronyms a ON (org.id = a.id)
LEFT OUTER JOIN urls u ON (org.id = u.id)
GROUP BY
org.id,
org.name,
@ -513,9 +517,9 @@ CREATE VIEW suggestions_info_by_country_view AS SELECT c.val AS country,
coalesce(t2.n_conflicts, 0) AS n_conflicts,
coalesce(t3.n_pending_orgs, 0) AS n_pending_orgs
FROM countries c
LEFT OUTER JOIN (SELECT o.country AS country, count(DISTINCT d.*) AS n_duplicates FROM oa_duplicates d LEFT OUTER JOIN organizations o ON (d.local_id = o.id) WHERE d.reltype = 'suggested' GROUP BY o.country) AS t1 ON (t1.country = c.val)
LEFT OUTER JOIN (SELECT o.country AS country, count(DISTINCT c.idgroup) AS n_conflicts FROM oa_conflicts c LEFT OUTER JOIN organizations o ON (c.id1 = o.id) WHERE c.reltype = 'suggested' GROUP BY o.country) AS t2 ON (t2.country = c.val)
LEFT OUTER JOIN (SELECT o.country AS country, count(DISTINCT o.id) AS n_pending_orgs FROM organizations o WHERE o.status = 'pending' GROUP BY o.country) AS t3 ON (t3.country = c.val);
LEFT OUTER JOIN (SELECT o.country AS country, count(DISTINCT d.*) AS n_duplicates FROM oa_duplicates d LEFT OUTER JOIN organizations o ON (d.local_id = o.id) WHERE d.reltype = 'suggested' AND o.status = 'approved' GROUP BY o.country) AS t1 ON (t1.country = c.val)
LEFT OUTER JOIN (SELECT o.country AS country, count(DISTINCT c.idgroup) AS n_conflicts FROM oa_conflicts c LEFT OUTER JOIN organizations o ON (c.id1 = o.id) WHERE c.reltype = 'suggested' AND o.status = 'approved' GROUP BY o.country) AS t2 ON (t2.country = c.val)
LEFT OUTER JOIN (SELECT o.country AS country, count(DISTINCT o.id) AS n_pending_orgs FROM organizations o WHERE o.status = 'suggested' GROUP BY o.country) AS t3 ON (t3.country = c.val);
CREATE VIEW conflict_groups_view AS SELECT
c.idgroup AS idgroup,
@ -534,7 +538,12 @@ FROM
LEFT OUTER JOIN organizations o1 ON (c.id1 = o1.id)
LEFT OUTER JOIN organizations o2 ON (c.id2 = o2.id)
WHERE
o1.id IS NOT NULL AND O2.id IS NOT NULL AND c.idgroup IS NOT NULL;
o1.id IS NOT NULL
AND o2.id IS NOT NULL
AND o1.status = 'approved'
AND o2.status = 'approved'
AND c.idgroup IS NOT NULL
AND c.reltype = 'suggested';
CREATE VIEW duplicate_groups_view AS SELECT
o.id,
@ -546,7 +555,7 @@ FROM
oa_duplicates d
LEFT OUTER JOIN organizations o ON (o.id = d.local_id)
WHERE
d.reltype = 'suggested'
d.reltype = 'suggested' AND o.status = 'approved'
GROUP BY o.id, o.name, o.city, o.country
ORDER BY o.name;

View File

@ -1,16 +0,0 @@
<table class="table table-sm table-hover col-lg-5">
<thead class="thead-light">
<tr>
<th>{{field}}</th>
<th class="text-right text-nowrap"># approved</th>
<th class="text-right text-nowrap"># pending</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="e in entries">
<td><a href="#!{{resultsBasePath}}/0/50/{{e.value}}">{{e.name}}<span ng-if="e.value != e.name"> ({{e.value}})</span></a></td>
<td class="text-right">{{e.approved}}</td>
<td class="text-right">{{e.pending}}</td>
</tr>
</tbody>
</table>

View File

@ -1,18 +0,0 @@
<h4>{{info.name}}</h4>
<div class="alert alert-success" ng-if="message">{{message}}</div>
<p class="text-muted" ng-if="!message">
<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>Modified at</b> {{info.modificationDate | date:'MMMM d, y HH:mm:ss'}} <b>by</b>
{{info.modifiedBy}}
</p>
<div class="card">
<org-tabs-menu org-id="{{orgId}}" info="info" org="org" events="events" selected="currentTab"></org-tabs-menu>
<org-form-metadata org-id="{{orgId}}" org="org" vocabularies="vocabularies" info-method="getInfo()" ng-if="currentTab == 1 && org.status == 'approved'" mode="update"></org-form-metadata>
<org-form-metadata org-id="{{orgId}}" org="org" vocabularies="vocabularies" info-method="getInfo()" ng-if="currentTab == 1 && org.status == 'pending'" mode="approve"></org-form-metadata>
<org-form-metadata org-id="{{orgId}}" org="org" vocabularies="vocabularies" info-method="getInfo()" ng-if="currentTab == 1 && org.status != 'approved' && org.status != 'pending'" mode="readonly"></org-form-metadata>
<org-dedup-events org-id="{{orgId}}" events="events" vocabularies="vocabularies" info-method="getInfo()" ng-if="currentTab == 2 && (org.status == 'approved' || org.status == 'pending')"></org-dedup-events>
</div>

View File

@ -1,42 +0,0 @@
<div>
<div class="text-muted" ng-if="conflicts.length == 0">No suggestions</div>
<div class="input-group input-group-sm mb-3" ng-show="conflicts.length > 0">
<input type="text" class="form-control" ng-model="conflictFilter" placeholder="Filter...">
<div class="input-group-append">
<span class="input-group-text text-outline-primary">Country:</span>
<button class="btn btn-outline-primary dropdown-toggle" data-toggle="dropdown">{{country}}</button>
<div class="dropdown-menu">
<small>
<a class="dropdown-item" href="#!/suggestions/{{c}}/2"
ng-repeat="(c, vals) in info.byCountry"
ng-if="vals.nConflicts > 0">
{{c}} <span class="badge badge-danger float-right">{{vals.nConflicts}}</span>
</a>
</small>
</div>
</div>
</div>
<div class="card text-white mb-3" ng-repeat="w in conflicts | filter:conflictFilter" class="mb-2">
<div class="card-header bg-primary text-white py-1">Group {{$index+1}}</div>
<table class="table table-sm">
<tr ng-repeat="o in w">
<th style="width:40px" class="text-center">#{{$index+1}}</th>
<td><a href="#!/edit/0/{{o.id}}" title="{{o.id}}">{{o.name}}</a></td>
<td style="width:250px"><img ng-src="resources/images/flags/{{o.country}}.gif" /> {{o.city || '-'}}, {{o.country}}</td>
<td style="width:80px" class="text-right">{{o.type}}</td>
</tr>
</table>
<div class="card-footer bg-secondary py-1">
<button type="button"
class="btn btn-sm btn-primary"
data-toggle="modal" data-target="#resolveConflictsModal"
ng-click="prepareConflictsModal(w)">resolve</button>
</div>
</div>
</div>
<resolve-conflicts-modal modal-id="resolveConflictsModal" orgs="orgs" selected-orgs="selectedOrgs"></resolve-conflicts-modal>

View File

@ -1,62 +0,0 @@
<div>
<div class="text-muted" ng-if="duplicates.length == 0">No suggestions</div>
<div class="input-group input-group-sm mb-3" ng-show="duplicates.length > 0">
<input type="text" class="form-control" ng-model="duplicateFilter" placeholder="Filter...">
<div class="input-group-append">
<span class="input-group-text text-outline-primary">Country:</span>
<button class="btn btn-outline-primary dropdown-toggle" data-toggle="dropdown">{{country}}</button>
<div class="dropdown-menu">
<small>
<a class="dropdown-item" href="#!/suggestions/{{c}}/1"
ng-repeat="(c, vals) in info.byCountry"
ng-if="vals.nDuplicates > 0">
{{c}} <span class="badge badge-primary float-right">{{vals.nDuplicates}}</span>
</a>
</small>
</div>
</div>
</div>
<table class="table table-sm table-hover" ng-if="duplicates.length > 0">
<thead class="thead-light">
<tr class="d-flex">
<th class="col-8">Organization</th>
<th class="col-3">Place</th>
<th class="col-1 text-right"># pending duplicates</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="d in duplicates | filter:duplicateFilter" class="d-flex">
<td class="col-8">
<a href="javascript:void(0)" title="{{d.id}}" ng-click="prepareDuplicatesModal(d)" data-toggle="modal" data-target="#duplicatesModal">{{d.name}}</a>
<a href="#!/edit/0/{{d.id}}" title="edit"><i class="fa fa-edit"></i></a>
</td>
<td class="col-3"><img ng-src="resources/images/flags/{{d.country}}.gif" /> {{d.city || '-'}}, {{d.country}}</td>
<td class="col-1 text-right">{{d.numberOfDuplicates}}</td>
</tr>
</tbody>
</table>
</div>
<div class="modal fade" id="duplicatesModal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-xl" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">{{currentOrg.name}}</h5>
<button type="button" class="close" data-dismiss="modal">&times;</button>
</div>
<div class="modal-body">
<div class="text-muted" ng-if="currentDuplicates.length == 0">No duplicates</div>
<org-details org="currentOrgDetails" org-title="Registered organization" show="default"></org-details>
<org-form-duplicates duplicates="currentDuplicates"></org-form-duplicates>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" data-dismiss="modal" ng-click="saveCurrentDuplicates()" ng-if="currentDuplicates.length > 0">Save changes</button>
</div>
</div>
</div>
</div>

View File

@ -1,28 +0,0 @@
<div class="card mb-3">
<div class="card-header bg-primary text-white py-1">Conflicts</div>
<div class="card-body text-muted" ng-if="conflicts.length == 0">No conflicts</div>
<table class="table table-sm mt-2" ng-if="conflicts.length > 0">
<thead>
<tr class="d-flex">
<th class="col-6 pl-3" style="border-top: none;">Related organization</th>
<th class="col-2 text-center" style="border-top: none;">Type</th>
<th class="col-4 text-right" style="border-top: none;">Place</th>
</tr>
</thead>
<tbody>
<tr class="d-flex" ng-repeat="sr in conflicts">
<td class="col-6 pl-3">{{sr.name}}<br /><a href="#!/edit/0/{{sr.id}}" class="small text-monospace">{{sr.id}}</a></td>
<td class="col-2 text-center small">{{sr.type}}</td>
<td class="col-4 text-right small"><img ng-src="resources/images/flags/{{sr.country}}.gif" /> {{sr.city || '-'}}, {{sr.country}}</td>
</tr>
</tbody>
</table>
<div class="card-footer text-right" ng-if="showSaveButton && conflicts.length > 0">
<button class="btn btn-sm btn-primary" data-toggle="modal" data-target="#resolveConflictsModal" ng-click="prepareConflictsModal()">resolve conflicts</button>
</div>
</div>
<resolve-conflicts-modal modal-id="resolveConflictsModal" orgs="candidateConflicts" selected-orgs="selectedConflicts"></resolve-conflicts-modal>

View File

@ -1,297 +0,0 @@
<div class="card-body">
<form name="organizationForm">
<div ng-if="mode == 'approve'" class="alert alert-warning">
This is a pending organization. Please evaluate it before approving.
</div>
<div ng-if="mode == 'readonly'" class="alert alert-secondary">
This organization is managed by the system. You can not edit.
</div>
<fieldset ng-disabled="mode == 'readonly'">
<legend>Official name and type</legend>
<div class="form-group">
<div class="input-group input-group-sm">
<div class="input-group-prepend">
<div class="input-group-text text-white" ng-class="{'bg-primary' : mode != 'approve', 'bg-warning' : mode == 'approve'}">name</div>
</div>
<input name="org_nm" type="text" class="form-control"
placeholder="organization name..." ng-model="org.name"
required="required"
ng-class="{'is-invalid' : organizationForm.org_nm.$error.required}" />
<div class="input-group-append input-group-prepend">
<div class="input-group-text text-white" ng-class="{'bg-primary' : mode != 'approve', 'bg-warning' : mode == 'approve'}">type</div>
</div>
<select name="org_tp" class="custom-select" ng-model="org.type"
required="required"
ng-class="{'is-invalid' : organizationForm.org_tp.$error.required}">
<option disabled="disabled" value='' ng-if="!org.type">type...</option>
<option ng-repeat="t in vocabularies.orgTypes" value="{{t.value}}">{{t.name}}</option>
</select>
</div>
</div>
</fieldset>
<fieldset class="mt-4" ng-disabled="mode == 'readonly'">
<legend>Geographical location</legend>
<div class="form-group">
<div class="input-group input-group-sm">
<div class="input-group-prepend">
<div class="input-group-text text-white" ng-class="{'bg-primary' : mode != 'approve', 'bg-warning' : mode == 'approve'}">city</div>
</div>
<input name="org_ct" type="text" class="form-control" placeholder=""
ng-model="org.city" />
<div class="input-group-append input-group-prepend">
<div class="input-group-text text-white" ng-class="{'bg-primary' : mode != 'approve', 'bg-warning' : mode == 'approve'}">country</div>
</div>
<select name="org_cntr" class="custom-select" ng-model="org.country"
required="required"
ng-class="{'is-invalid' : organizationForm.org_cntr.$error.required}">
<option disabled="disabled" value='' ng-if="!org.country">country...</option>
<option ng-repeat="c in vocabularies.countries"value="{{c.value}}">{{c.name}}</option>
</select>
<div class="input-group-append input-group-prepend">
<div class="input-group-text text-white" ng-class="{'bg-primary' : mode != 'approve', 'bg-warning' : mode == 'approve'}">lat</div>
</div>
<input name="org_lat" type="text" class="form-control"
placeholder="0.0" ng-model="org.lat" />
<div class="input-group-append input-group-prepend">
<div class="input-group-text text-white" ng-class="{'bg-primary' : mode != 'approve', 'bg-warning' : mode == 'approve'}">lng</div>
</div>
<input name="org_lng" type="text" class="form-control"
placeholder="0.0" ng-model="org.lng" />
</div>
</div>
</fieldset>
<fieldset class="mt-4" ng-disabled="mode == 'readonly'">
<legend>Other names and identifiers</legend>
<div class="form-group row">
<div class="col-lg-8 mb-2">
<div class="card">
<div class="card-header text-white text-center py-1" ng-class="{'bg-primary' : mode != 'approve', 'bg-warning' : mode == 'approve'}">Acronyms</div>
<table class="table table-sm">
<tbody>
<tr ng-repeat="a in org.acronyms">
<td>{{a}}</td>
<td class="text-right" style="width: 44px">
<button type="button" class="btn btn-sm btn-outline-danger"
ng-click="org.acronyms.splice($index, 1)">
<i class="fa fa-trash"></i>
</button>
</td>
</tr>
</tbody>
<tfoot>
<tr ng-init="newAcronym=''">
<td><input type="text" class="form-control form-control-sm"
placeholder="new acronym..." ng-model='newAcronym' /></td>
<td class="text-right" style="width: 44px">
<button type="button" class="btn btn-sm btn-outline-success"
ng-click="org.acronyms.push(newAcronym); newAcronym=''"
ng-disabled="!newAcronym || org.acronyms.indexOf(newAcronym) !== -1">
<i class="fa fa-plus"></i>
</button>
</td>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
<div class="form-group row">
<div class="col-lg-8 mb-2">
<div class="card">
<div class="card-header text-white text-center py-1" ng-class="{'bg-primary' : mode != 'approve', 'bg-warning' : mode == 'approve'}">Aliases</div>
<table class="table table-sm">
<thead class="thead-light">
<tr>
<th>name</th>
<th style="width: 156px">language</th>
<th style="width: 44px"></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="n in org.otherNames">
<td>{{n.name}}</td>
<td>{{n.lang}}</td>
<td class="text-right">
<button type="button" class="btn btn-sm btn-outline-danger"
ng-click="org.otherNames.splice($index, 1)">
<i class="fa fa-trash"></i>
</button>
</td>
</tr>
</tbody>
<tfoot>
<tr ng-init="newOtherName=newLang=''">
<td><input type="text" class="form-control form-control-sm"
placeholder="new alias..." ng-model="newOtherName" /></td>
<td><select class="custom-select custom-select-sm"
ng-model="newLang">
<option disabled="disabled" value=''>language...</option>
<option ng-repeat="l in vocabularies.languages" value="{{l.value}}">{{l.name}}</option>
</select></td>
<td class="text-right" style="width: 44px">
<button type="button" class="btn btn-sm btn-outline-success"
ng-click="org.otherNames.push({'name': newOtherName, 'lang': newLang}); newOtherName=newLang=''"
ng-disabled="!newOtherName || !newLang">
<i class="fa fa-plus"></i>
</button>
</td>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
<div class="form-group row">
<div class="col-lg-8 mb-2">
<div class="card">
<div class="card-header text-white text-center py-1" ng-class="{'bg-primary' : mode != 'approve', 'bg-warning' : mode == 'approve'}">Identifiers</div>
<table class="table table-sm">
<thead class="thead-light">
<tr>
<th>id</th>
<th style="width: 156px">type</th>
<th style="width: 44px"></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="id in org.otherIdentifiers">
<td>{{id.id}}</td>
<td>{{id.type}}</td>
<td class="text-right" style="width: 44px">
<button type="button" class="btn btn-sm btn-outline-danger"
ng-click="org.otherIdentifiers.splice($index, 1)">
<i class="fa fa-trash"></i>
</button>
</td>
</tr>
</tbody>
<tfoot>
<tr ng-init="newId=newIdType=''">
<td><input type="text" class="form-control form-control-sm"
placeholder="new id..." ng-model="newId" /></td>
<td><select class="custom-select custom-select-sm"
ng-model="newIdType">
<option disabled="disabled" value=''>type...</option>
<option ng-repeat="t in vocabularies.idTypes" value="{{t.value}}">{{t.name}}</option>
</select></td>
<td class="text-right" style="width: 44px">
<button type="button" class="btn btn-sm btn-outline-success"
ng-click="org.otherIdentifiers.push({'id': newId, 'type': newIdType}); newId=newIdType=''"
ng-disabled="!newId || !newIdType">
<i class="fa fa-plus"></i>
</button>
</td>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
<div class="form-group row">
<div class="col-lg-8 mb-2">
<div class="card">
<div class="card-header text-white text-center py-1" ng-class="{'bg-primary' : mode != 'approve', 'bg-warning' : mode == 'approve'}">Urls</div>
<table class="table table-sm">
<tbody>
<tr ng-repeat="u in org.urls">
<td><a href="{{u}}" target="_blank">{{u}}</a></td>
<td class="text-right" style="width: 44px">
<button type="button" class="btn btn-sm btn-outline-danger"
ng-click="org.urls.splice($index, 1)">
<i class="fa fa-trash"></i>
</button>
</td>
</tr>
</tbody>
<tfoot>
<tr ng-init="newUrl=''">
<td><input type="text" class="form-control form-control-sm"
placeholder="http://..." ng-model="newUrl" /></td>
<td class="text-right" style="width: 44px">
<button type="button" class="btn btn-sm btn-outline-success"
ng-click="org.urls.push(newUrl); newUrl=''"
ng-disabled="!newUrl || org.urls.indexOf(newUrl) !== -1">
<i class="fa fa-plus"></i>
</button>
</td>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
</fieldset>
<fieldset class="mt-4" ng-disabled="mode == 'readonly'">
<legend>Relations</legend>
<div class="form-group row">
<div class="col-lg-8 mb-2">
<div class="card">
<div class="card-header text-white text-center py-1" ng-class="{'bg-primary' : mode != 'approve', 'bg-warning' : mode == 'approve'}">Relations</div>
<table class="table table-sm">
<thead class="thead-light">
<tr>
<th class="pl-3" style="border-top: none;">Related organization</th>
<th style="border-top: none; width: 156px">Type</th>
<th style="border-top: none; width: 44px"></th>
</tr>
</thead>
<tbody>
<tr ng-if="org.relations.length == 0">
<td class="text-center text-muted" colspan="3">No relations</td>
</tr>
<tr ng-repeat="r in org.relations">
<td class="pl-3"><a href="#!/edit/0/{{r.relatedOrgId}}" title="{{r.relatedOrgId}}">{{r.relatedOrgName}}</a></td>
<td>{{r.type}}</td>
<td class="text-right">
<button type="button" class="btn btn-sm btn-outline-danger" ng-click="org.relations.splice($index, 1)"><i class="fa fa-trash"></i></button>
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td>
<input type="text" placeholder="add a related organization..." readonly="readonly"
class="form-control form-control-sm" style="background-color: white; color: #007bff;"
ng-model="newRelation.name" ng-click="resetSelectedRelation()"
data-toggle="modal" data-target="#selectRelatedOrgModal" ng-hide="mode == 'readonly'"/>
<input type="text" placeholder="add a related organization..." disabled="disabled"
class="form-control form-control-sm" ng-model="newRelation.name" ng-show="mode == 'readonly'"/>
</td>
<td>
<select class="custom-select custom-select-sm" ng-model="newRelType">
<option disabled="disabled" value=''>rel type...</option>
<option ng-repeat="t in vocabularies.relTypes" value="{{t.value}}">{{t.name}}</option>
</select>
</td>
<td class="text-right">
<button type="button" class="btn btn-sm btn-outline-success" ng-disabled="!newRelType || !newRelation.id" ng-click="addNewRelation()"><i class="fa fa-plus"></i></button>
</td>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
</fieldset>
<button type="submit" class="btn" ng-class="{'btn-primary' : mode != 'approve', 'btn-warning' : mode == 'approve'}" ng-click="save()" ng-disabled="organizationForm.$invalid" ng-hide="mode == 'readonly'">
<span ng-if="mode != 'approve'">Save</span>
<span ng-if="mode == 'approve'">Register as new Organization</span>
</button>
</form>
</div>
<select-org-modal modal-id="selectRelatedOrgModal" selected-org="newRelation"></select-org-modal>

View File

@ -1,43 +0,0 @@
<div>
<div class="text-muted" ng-if="orgs.length == 0">No suggestions</div>
<div class="input-group input-group-sm mb-3" ng-show="orgs.length > 0">
<input type="text" class="form-control" ng-model="orgFilter" placeholder="Filter..."/>
<div class="input-group-append">
<span class="input-group-text text-outline-primary">Country:</span>
<button class="btn btn-outline-primary dropdown-toggle" data-toggle="dropdown">{{country}}</button>
<div class="dropdown-menu">
<small>
<a class="dropdown-item" href="#!/suggestions/{{c}}/0"
ng-repeat="(c, vals) in info.byCountry"
ng-if="vals.nPendingOrgs > 0">
{{c}} <span class="badge badge-primary float-right">{{vals.nPendingOrgs}}</span>
</a>
</small>
</div>
</div>
</div>
<table class="table table-sm table-hover" ng-if="orgs.length > 0">
<thead class="thead-light">
<tr class="d-flex">
<th class="col-6">Organization name</th>
<th class="col-4">Place</th>
<th class="col-1 text-center">Acronyms</th>
<th class="col-1 text-right">Type</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="o in orgs | filter:orgFilter" class="d-flex">
<td class="col-6">
<a href="#!/edit/0/{{o.id}}" title="{{o.id}}">{{o.name}}</a>
</td>
<td class="col-4"><img ng-src="resources/images/flags/{{o.country}}.gif" /> {{o.city || '-'}}, {{o.country}}</td>
<td class="col-1 text-center">{{o.acronyms.join()}}</td>
<td class="col-1 text-right">{{o.type}}</td>
</tr>
</tbody>
</table>
</div>

View File

@ -1,10 +0,0 @@
<div class="card-header">
<ul class="nav nav-tabs card-header-tabs">
<li class="nav-item">
<a href="javascript:void(0)" class="nav-link" ng-class="{'active': selected == 1}" ng-click="loadOrg()">Metadata Management</a>
</li>
<li class="nav-item">
<a href="javascript:void(0)" class="nav-link" ng-class="{'active': selected == 2}" ng-click="loadDedupEvents()">Dedup Events <span class="badge badge-primary ml-2">{{info.nDuplicates}}</span> <span class="badge badge-danger">{{info.nConflicts}}</span></a>
</li>
</ul>
</div>

View File

@ -1,8 +0,0 @@
<div class="card">
<div class="card-header">
<ul class="nav nav-tabs card-header-tabs">
<li class="nav-item"><a href="javascript:void(0)" class="nav-link active">Metadata</a></li>
</ul>
</div>
<org-form-metadata org="org" vocabularies="vocabularies" mode="insert"></org-form-metadata>
</div>

View File

@ -0,0 +1,44 @@
<h2>Conflicts</h2>
<h4 class="text-muted" ng-if="conflicts.length == 0">No suggestions</h4>
<div class="input-group input-group-sm mb-3" ng-show="conflicts.length > 0">
<input type="text" class="form-control" ng-model="conflictFilter" placeholder="Filter...">
<div class="input-group-append">
<span class="input-group-text text-outline-primary">Country:</span>
<button class="btn btn-outline-primary dropdown-toggle" data-toggle="dropdown">{{country}}</button>
<div class="dropdown-menu">
<small>
<a class="dropdown-item" href="#!/conflicts/{{c}}"
ng-repeat="(c, vals) in info.data.byCountry"
ng-if="vals.nConflicts > 0">
{{c}} <span class="badge badge-danger float-right">{{vals.nConflicts}}</span>
</a>
</small>
</div>
</div>
</div>
<div class="card text-white mb-3" ng-repeat="w in conflicts | filter:conflictFilter" class="mb-2">
<div class="card-header bg-primary text-white py-1">Group {{$index+1}}</div>
<table class="table table-sm">
<tr ng-repeat="o in w">
<th style="width:40px" class="text-center">#{{$index+1}}</th>
<td><a href="#!/edit/0/{{o.id}}" title="{{o.id}}">{{o.name}}</a></td>
<td style="width:250px"><img ng-src="resources/images/flags/{{o.country}}.gif" /> {{o.city || '-'}}, {{o.country}}</td>
<td style="width:80px" class="text-right">{{o.type}}</td>
</tr>
</table>
<div class="card-footer bg-secondary py-1">
<button class="btn btn-sm btn-outline-primary" data-toggle="modal" data-target="#addNewConflictModal" ng-click="prepareAddConflictModal(w)">add</button>
<button type="button"
class="btn btn-sm btn-primary"
data-toggle="modal" data-target="#resolveConflictsModal"
ng-click="prepareConflictsModal(w)">resolve</button>
</div>
</div>
<resolve-conflicts-modal modal-id="resolveConflictsModal" orgs="orgs" selected-orgs="selectedOrgs"></resolve-conflicts-modal>
<select-org-modal modal-id="addNewConflictModal" selected-org="newConflict" filter-status="approved" on-select="addConflict()"></select-org-modal>

View File

@ -0,0 +1,60 @@
<h2>Duplicates</h2>
<h4 class="text-muted" ng-if="duplicates.length == 0">No duplicates</h4>
<div class="input-group input-group-sm mb-3" ng-show="duplicates.length > 0">
<input type="text" class="form-control" ng-model="duplicateFilter" placeholder="Filter...">
<div class="input-group-append">
<span class="input-group-text text-outline-primary">Country:</span>
<button class="btn btn-outline-primary dropdown-toggle" data-toggle="dropdown">{{country}}</button>
<div class="dropdown-menu">
<small>
<a class="dropdown-item" href="#!/duplicates/{{c}}"
ng-repeat="(c, vals) in info.data.byCountry"
ng-if="vals.nDuplicates > 0">
{{c}} <span class="badge badge-primary float-right">{{vals.nDuplicates}}</span>
</a>
</small>
</div>
</div>
</div>
<table class="table table-sm table-hover" ng-if="duplicates.length > 0">
<thead class="thead-light">
<tr class="d-flex">
<th class="col-8">Organization</th>
<th class="col-3">Place</th>
<th class="col-1 text-right"># pending duplicates</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="d in duplicates | filter:duplicateFilter" class="d-flex">
<td class="col-8">
<a href="javascript:void(0)" title="{{d.id}}" ng-click="prepareDuplicatesModal(d)" data-toggle="modal" data-target="#duplicatesModal">{{d.name}}</a>
<a href="#!/edit/0/{{d.id}}" title="edit"><i class="fa fa-edit"></i></a>
</td>
<td class="col-3"><img ng-src="resources/images/flags/{{d.country}}.gif" /> {{d.city || '-'}}, {{d.country}}</td>
<td class="col-1 text-right">{{d.numberOfDuplicates}}</td>
</tr>
</tbody>
</table>
<div class="modal fade" id="duplicatesModal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-xl" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">{{currentOrg.name}}</h5>
<button type="button" class="close" data-dismiss="modal">&times;</button>
</div>
<div class="modal-body">
<div class="text-muted" ng-if="currentDuplicates.length == 0">No duplicates</div>
<org-details org="currentOrgDetails" org-title="Registered organization" show="default"></org-details>
<org-duplicates org-id="currentOrgDetails.id" duplicates="currentDuplicates"></org-form-duplicates>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" data-dismiss="modal" ng-click="saveCurrentDuplicates()" ng-if="currentDuplicates.length > 0">Save changes</button>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,11 @@
<div class="card">
<div class="card-header">
<ul class="nav nav-tabs card-header-tabs">
<li class="nav-item"><a href="javascript:void(0)" class="nav-link active">Metadata</a></li>
</ul>
</div>
<div class="card-body">
<org-form-metadata org="org" vocabularies="vocabularies" mode="insert_full" ng-if="superAdmin"></org-form-metadata>
<org-form-metadata org="org" vocabularies="vocabularies" mode="insert_pending" ng-if="!superAdmin"></org-form-metadata>
</div>
</div>

View File

@ -0,0 +1,42 @@
<h2>Pending Organizations</h2>
<h4 class="text-muted" ng-if="orgs.length == 0">No pending organizations</h4>
<div class="input-group input-group-sm mb-3" ng-show="orgs.length > 0">
<input type="text" class="form-control" ng-model="orgFilter" placeholder="Filter..."/>
<div class="input-group-append">
<span class="input-group-text text-outline-primary">Country:</span>
<button class="btn btn-outline-primary dropdown-toggle" data-toggle="dropdown">{{country}}</button>
<div class="dropdown-menu">
<small>
<a class="dropdown-item" href="#!/pendings/{{c}}"
ng-repeat="(c, vals) in info.data.byCountry"
ng-if="vals.nPendingOrgs > 0">
{{c}} <span class="badge badge-primary float-right">{{vals.nPendingOrgs}}</span>
</a>
</small>
</div>
</div>
</div>
<table class="table table-sm table-hover" ng-if="orgs.length > 0">
<thead class="thead-light">
<tr class="d-flex">
<th class="col-6">Organization name</th>
<th class="col-4">Place</th>
<th class="col-1 text-center">Acronyms</th>
<th class="col-1 text-right">Type</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="o in orgs | filter:orgFilter" class="d-flex">
<td class="col-6">
<a href="#!/edit/0/{{o.id}}" title="{{o.id}}">{{o.name}}</a>
</td>
<td class="col-4"><img ng-src="resources/images/flags/{{o.country}}.gif" /> {{o.city || '-'}}, {{o.country}}</td>
<td class="col-1 text-center">{{o.acronyms.join()}}</td>
<td class="col-1 text-right">{{o.type}}</td>
</tr>
</tbody>
</table>

View File

@ -0,0 +1,45 @@
<h4>{{info.name}}</h4>
<div class="alert alert-success" ng-if="message">{{message}}</div>
<p class="text-muted" ng-if="!message">
<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>Modified at</b> {{info.modificationDate | date:'MMMM d, y HH:mm:ss'}} <b>by</b>
{{info.modifiedBy}}
</p>
<div class="card">
<div class="card-header">
<ul class="nav nav-tabs card-header-tabs">
<li class="nav-item">
<a href="javascript:void(0)" class="nav-link" ng-class="{'active': currentTab == 1}" ng-click="gotoTab(1)">Metadata Management</a>
</li>
<li class="nav-item" ng-show="org.status == 'approved'">
<a href="javascript:void(0)" class="nav-link" ng-class="{'active': currentTab == 2}" ng-click="gotoTab(2)">Duplicates <span class="badge badge-danger ml-2" ng-if="info.nDuplicates > 0" title="{{info.nDuplicates}} duplicate(s) to validate">new</span></a>
</li>
<li class="nav-item" ng-show="org.status == 'approved' && adminMode">
<a href="javascript:void(0)" class="nav-link" ng-class="{'active': currentTab == 3}" ng-click="gotoTab(3)">Conflicts <span class="badge badge-danger ml-2" ng-if="info.nConflicts > 0" title="the current organization seems in conflict with {{info.nConflicts}} other(s)">new</span></a>
</li>
</ul>
</div>
<div class="card-body" ng-if="currentTab == 1 && org.status">
<org-form-metadata org="org" vocabularies="vocabularies" info-method="getInfo()" mode="update_full" ng-if="adminMode && org.status == 'approved'"></org-form-metadata>
<org-form-metadata org="org" vocabularies="vocabularies" info-method="getInfo()" mode="update_simple" ng-if="!adminMode && org.status == 'approved'"></org-form-metadata>
<org-form-metadata org="org" vocabularies="vocabularies" info-method="getInfo()" mode="approve" ng-if="adminMode && org.status == 'suggested'"></org-form-metadata>
<org-form-metadata org="org" vocabularies="vocabularies" info-method="getInfo()" mode="not_authorized" ng-if="!adminMode && org.status == 'suggested'"></org-form-metadata>
<org-form-metadata org="org" vocabularies="vocabularies" info-method="getInfo()" mode="readonly" ng-if="org.status != 'approved' && org.status != 'suggested'"></org-form-metadata>
</div>
<div class="card-body" ng-if="currentTab == 2 && (org.status == 'approved' || org.status == 'suggested')">
<org-details org="org" org-title="Current organization" show="default"></org-details>
<org-duplicates org-id="{{org.id}}" duplicates="duplicates" save-function="saveDuplicates()" show-save-button="1"></org-form-duplicates>
</div>
<div class="card-body" ng-if="adminMode && currentTab == 3 && (org.status == 'approved' || org.status == 'suggested')">
<org-conflicts org="org" conflicts="conflicts" show-save-button="1"></org-form-conflicts>
</div>
</div>

View File

@ -0,0 +1,50 @@
<h2>{{title}}</h2>
<h4 class="text-muted" ng-if="entries.length == 0">No entries</h4>
<p>
<input type="text" class="form-control col-lg-8 col-xs-12" ng-show="entries.length > 0" ng-model="browseFilter" placeholder="Filter...">
</p>
<table class="table table-sm table-bordered table-hover col-lg-8 col-xs-12">
<thead class="thead-light">
<tr>
<th class="col-6">{{field}}</th>
<th class="col-1 text-right text-nowrap" title="valid organizations"># approved</th>
<th class="col-1 text-right text-nowrap" title="to be approved by an administrator"># suggested</th>
<th class="col-1 text-right text-nowrap" title="deleted by an administrator"># deleted</th>
<th class="col-1 text-right text-nowrap" title="duplicates(not openorgs) of a valid organization"># duplicated</th>
<th class="col-1 text-right text-nowrap" title="suggestions rejected by an administrator"># discarded</th>
<th class="col-1 text-right text-nowrap" title="organizations hidden by the system (for example fixing a conflict)"># hidden</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="e in entries | filter: browseFilter">
<th>{{e.name}} <span ng-if="e.code != e.name"> ({{e.code}})</span></th>
<td class="text-right">
<a href="#!{{resultsBasePath}}/0/50/approved/{{e.code}}" ng-if="e.values.approved && e.values.approved > 0">{{e.values.approved}}</a>
<span ng-if="!e.values.approved || e.values.approved == 0">-</span>
</td>
<td class="text-right">
<a href="#!{{resultsBasePath}}/0/50/suggested/{{e.code}}" ng-if="e.values.suggested && e.values.suggested > 0">{{e.values.suggested}}</a>
<span ng-if="!e.values.suggested || e.values.suggested == 0">-</span>
</td>
<td class="text-right">
<a href="#!{{resultsBasePath}}/0/50/deleted/{{e.code}}" ng-if="e.values.deleted && e.values.deleted > 0">{{e.values.deleted}}</a>
<span ng-if="!e.values.deleted || e.values.deleted == 0">-</span>
</td>
<td class="text-right">
<a href="#!{{resultsBasePath}}/0/50/duplicate/{{e.code}}" ng-if="e.values.duplicate && e.values.duplicate > 0">{{e.values.duplicate}}</a>
<span ng-if="!e.values.duplicate || e.values.duplicate == 0">-</span>
</td>
<td class="text-right">
<a href="#!{{resultsBasePath}}/0/50/discarded/{{e.code}}" ng-if="e.values.discarded && e.values.discarded > 0">{{e.values.discarded}}</a>
<span ng-if="!e.values.discarded || e.values.discarded == 0">-</span>
</td>
<td class="text-right">
<a href="#!{{resultsBasePath}}/0/50/hidden/{{e.code}}" ng-if="e.values.hidden && e.values.hidden > 0">{{e.values.hidden}}</a>
<span ng-if="!e.values.hidden || e.values.hidden == 0">-</span>
</td>
</tr>
</tbody>
</table>

View File

@ -0,0 +1,31 @@
<div class="card text-white mb-3">
<div class="card-header bg-primary text-white py-1">Conflicts</div>
<div class="card-body text-muted" ng-if="conflicts.length == 0">No conflicts</div>
<table class="table table-sm" ng-if="conflicts.length > 0">
<tr>
<th style="width:40px" class="text-center">#1</th>
<td><a href="javascript:location.reload()" title="{{org.id}}">{{org.name}}</a> <span class="badge badge-primary">current</span></td>
<td style="width:250px"><img ng-src="resources/images/flags/{{org.country}}.gif" /> {{org.city || '-'}}, {{org.country}}</td>
<td style="width:80px" class="text-right">{{org.type}}</td>
</tr>
<tr ng-repeat="sr in conflicts">
<th style="width:40px" class="text-center">#{{$index+2}}</th>
<td><a href="#!/edit/0/{{sr.id}}" title="{{sr.id}}">{{sr.name}}</a></td>
<td style="width:250px"><img ng-src="resources/images/flags/{{sr.country}}.gif" /> {{sr.city || '-'}}, {{sr.country}}</td>
<td style="width:80px" class="text-right">{{sr.type}}</td>
</tr>
</table>
<div class="card-footer bg-secondary py-1">
<button class="btn btn-sm btn-outline-primary" data-toggle="modal" data-target="#addConflictModal">add</button>
<button class="btn btn-sm btn-primary" data-toggle="modal" data-target="#resolveConflictsModal" ng-click="prepareConflictsModal()" ng-if="showSaveButton && conflicts.length > 0">resolve conflicts</button>
</div>
</div>
<resolve-conflicts-modal modal-id="resolveConflictsModal" orgs="candidateConflicts" selected-orgs="selectedConflicts"></resolve-conflicts-modal>
<select-org-modal modal-id="addConflictModal" selected-org="newConflict" filter-status="approved" on-select="addConflict()"></select-org-modal>

View File

@ -1,11 +0,0 @@
<div class="card-body">
<org-details org="currentOrgDetails" org-title="Current organization" show="default"></org-details>
<org-form-duplicates duplicates="events.duplicates" save-function="saveDuplicates()" show-save-button="1"></org-form-duplicates>
<org-form-conflicts conflicts="events.conflicts" save-function="saveConflicts()" org-id="{{currentOrgDetails.id}}" show-save-button="1"></org-form-conflicts>
</div>

View File

@ -5,7 +5,7 @@
'text-white bg-info' : show == 'info',
'bg-secondary' : show == 'secondary',
}">{{orgTitle}}</div>
<table class="table table-sm table-condensed">
<table class="table table-sm table-condensed" style="table-layout: fixed">
<tr class="d-flex">
<th class="col-4 pl-3">Name</th>
<td class="col-8">{{org.name}}</td>

View File

@ -38,8 +38,11 @@
</tbody>
</table>
<div class="card-footer text-right" ng-if="showSaveButton && duplicates.length > 0">
<button class="btn btn-sm btn-primary" ng-click="saveFunction()">save</button>
<div class="card-footer text-right">
<button class="btn btn-sm btn-outline-primary" data-toggle="modal" data-target="#addDuplicateModal">add</button>
<button class="btn btn-sm btn-primary" ng-click="saveFunction()" ng-if="showSaveButton && duplicates.length > 0">save</button>
</div>
</div>
<select-org-modal modal-id="addDuplicateModal" selected-org="newDuplicate" filter-status="suggested,discarded" on-select="addDuplicate()"></select-org-modal>

View File

@ -0,0 +1,312 @@
<form name="organizationForm">
<div ng-if="mode == 'approve'" class="alert alert-warning">
This is a pending organization. Please evaluate it before approving.
</div>
<div ng-if="mode == 'readonly'" class="alert alert-secondary">
<span class="badge badge-primary">{{org.status}}</span> This organization is managed by the system. You can not edit.
</div>
<div ng-if="mode == 'not_authorized'" class="alert alert-secondary">
<span class="badge badge-primary">{{org.status}}</span> You are not authorized to modify this organization.
</div>
<fieldset ng-disabled="mode == 'readonly' || mode == 'not_authorized'">
<legend>Official name and type</legend>
<div class="form-group">
<div class="input-group input-group-sm">
<div class="input-group-prepend">
<div class="input-group-text bg-primary text-white" ng-class="{'bg-light text-dark' : mode == 'readonly' || mode == 'not_authorized', 'bg-warning' : mode == 'approve'}">name</div>
</div>
<input name="org_nm" type="text" class="form-control"
placeholder="organization name..." ng-model="org.name"
required="required"
ng-class="{'is-invalid' : organizationForm.org_nm.$error.required}" />
<div class="input-group-append input-group-prepend">
<div class="input-group-text bg-primary text-white" ng-class="{'bg-light text-dark' : mode == 'readonly' || mode == 'not_authorized', 'bg-warning' : mode == 'approve'}">type</div>
</div>
<select name="org_tp" class="custom-select" ng-model="org.type"
required="required"
ng-class="{'is-invalid' : organizationForm.org_tp.$error.required}">
<option disabled="disabled" value='' ng-if="!org.type">type...</option>
<option ng-repeat="t in vocabularies.orgTypes" value="{{t.value}}">{{t.name}}</option>
</select>
</div>
</div>
</fieldset>
<fieldset class="mt-4" ng-disabled="mode == 'readonly' || mode == 'not_authorized'">
<legend>Geographical location</legend>
<div class="form-group">
<div class="input-group input-group-sm">
<div class="input-group-prepend">
<div class="input-group-text bg-primary text-white" ng-class="{'bg-light text-dark' : mode == 'readonly' || mode == 'not_authorized', 'bg-warning' : mode == 'approve'}">city</div>
</div>
<input name="org_ct" type="text" class="form-control" placeholder=""
ng-model="org.city" />
<div class="input-group-append input-group-prepend">
<div class="input-group-text bg-primary text-white" ng-class="{'bg-light text-dark' : mode == 'readonly' || mode == 'not_authorized', 'bg-warning' : mode == 'approve'}">country</div>
</div>
<select name="org_cntr" class="custom-select" ng-model="org.country"
required="required"
ng-class="{'is-invalid' : organizationForm.org_cntr.$error.required}">
<option disabled="disabled" value='' ng-if="!org.country">country...</option>
<option ng-repeat="c in vocabularies.countries"value="{{c.value}}">{{c.name}}</option>
</select>
<div class="input-group-append input-group-prepend">
<div class="input-group-text bg-primary text-white" ng-class="{'bg-light text-dark' : mode == 'readonly' || mode == 'not_authorized', 'bg-warning' : mode == 'approve'}">lat</div>
</div>
<input name="org_lat" type="text" class="form-control"
placeholder="0.0" ng-model="org.lat" />
<div class="input-group-append input-group-prepend">
<div class="input-group-text bg-primary text-white" ng-class="{'bg-light text-dark' : mode == 'readonly' || mode == 'not_authorized', 'bg-warning' : mode == 'approve'}">lng</div>
</div>
<input name="org_lng" type="text" class="form-control"
placeholder="0.0" ng-model="org.lng" />
</div>
</div>
</fieldset>
<fieldset class="mt-4" ng-disabled="mode == 'readonly' || mode == 'not_authorized'">
<legend>Other names and identifiers</legend>
<div class="form-group row">
<div class="col-lg-8 mb-2">
<div class="card">
<div class="card-header text-white text-center py-1 bg-primary" ng-class="{'bg-light text-dark' : mode == 'readonly' || mode == 'not_authorized', 'bg-warning' : mode == 'approve'}">Acronyms</div>
<table class="table table-sm">
<tbody>
<tr ng-repeat="a in org.acronyms">
<td>{{a}}</td>
<td class="text-right" style="width: 44px">
<button type="button" class="btn btn-sm btn-outline-danger"
ng-click="org.acronyms.splice($index, 1)">
<i class="fa fa-trash"></i>
</button>
</td>
</tr>
</tbody>
<tfoot>
<tr ng-init="newAcronym=''">
<td><input type="text" class="form-control form-control-sm"
placeholder="new acronym..." ng-model='newAcronym' /></td>
<td class="text-right" style="width: 44px">
<button type="button" class="btn btn-sm btn-outline-success"
ng-click="org.acronyms.push(newAcronym); newAcronym=''"
ng-disabled="!newAcronym || org.acronyms.indexOf(newAcronym) !== -1">
<i class="fa fa-plus"></i>
</button>
</td>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
<div class="form-group row">
<div class="col-lg-8 mb-2">
<div class="card">
<div class="card-header text-white text-center py-1 bg-primary" ng-class="{'bg-light text-dark' : mode == 'readonly' || mode == 'not_authorized', 'bg-warning' : mode == 'approve'}">Aliases</div>
<table class="table table-sm">
<thead class="thead-light">
<tr>
<th>name</th>
<th style="width: 156px">language</th>
<th style="width: 44px"></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="n in org.otherNames">
<td>{{n.name}}</td>
<td>{{n.lang}}</td>
<td class="text-right">
<button type="button" class="btn btn-sm btn-outline-danger"
ng-click="org.otherNames.splice($index, 1)">
<i class="fa fa-trash"></i>
</button>
</td>
</tr>
</tbody>
<tfoot>
<tr ng-init="newOtherName=newLang=''">
<td><input type="text" class="form-control form-control-sm"
placeholder="new alias..." ng-model="newOtherName" /></td>
<td><select class="custom-select custom-select-sm"
ng-model="newLang">
<option disabled="disabled" value=''>language...</option>
<option ng-repeat="l in vocabularies.languages" value="{{l.value}}">{{l.name}}</option>
</select></td>
<td class="text-right" style="width: 44px">
<button type="button" class="btn btn-sm btn-outline-success"
ng-click="org.otherNames.push({'name': newOtherName, 'lang': newLang}); newOtherName=newLang=''"
ng-disabled="!newOtherName || !newLang">
<i class="fa fa-plus"></i>
</button>
</td>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
<div class="form-group row">
<div class="col-lg-8 mb-2">
<div class="card">
<div class="card-header text-white text-center py-1 bg-primary" ng-class="{'bg-light text-dark' : mode == 'readonly' || mode == 'not_authorized', 'bg-warning' : mode == 'approve'}">Identifiers</div>
<table class="table table-sm">
<thead class="thead-light">
<tr>
<th>id</th>
<th style="width: 156px">type</th>
<th style="width: 44px"></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="id in org.otherIdentifiers">
<td>{{id.id}}</td>
<td>{{id.type}}</td>
<td class="text-right" style="width: 44px">
<button type="button" class="btn btn-sm btn-outline-danger"
ng-click="org.otherIdentifiers.splice($index, 1)">
<i class="fa fa-trash"></i>
</button>
</td>
</tr>
</tbody>
<tfoot>
<tr ng-init="newId=newIdType=''">
<td><input type="text" class="form-control form-control-sm"
placeholder="new id..." ng-model="newId" /></td>
<td><select class="custom-select custom-select-sm"
ng-model="newIdType">
<option disabled="disabled" value=''>type...</option>
<option ng-repeat="t in vocabularies.idTypes" value="{{t.value}}">{{t.name}}</option>
</select></td>
<td class="text-right" style="width: 44px">
<button type="button" class="btn btn-sm btn-outline-success"
ng-click="org.otherIdentifiers.push({'id': newId, 'type': newIdType}); newId=newIdType=''"
ng-disabled="!newId || !newIdType">
<i class="fa fa-plus"></i>
</button>
</td>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
<div class="form-group row">
<div class="col-lg-8 mb-2">
<div class="card">
<div class="card-header text-white text-center py-1 bg-primary" ng-class="{'bg-light text-dark' : mode == 'readonly' || mode == 'not_authorized', 'bg-warning' : mode == 'approve'}">Urls</div>
<table class="table table-sm">
<tbody>
<tr ng-repeat="u in org.urls">
<td><a href="{{u}}" target="_blank">{{u}}</a></td>
<td class="text-right" style="width: 44px">
<button type="button" class="btn btn-sm btn-outline-danger"
ng-click="org.urls.splice($index, 1)">
<i class="fa fa-trash"></i>
</button>
</td>
</tr>
</tbody>
<tfoot>
<tr ng-init="newUrl=''">
<td><input type="text" class="form-control form-control-sm"
placeholder="http://..." ng-model="newUrl" /></td>
<td class="text-right" style="width: 44px">
<button type="button" class="btn btn-sm btn-outline-success"
ng-click="org.urls.push(newUrl); newUrl=''"
ng-disabled="!newUrl || org.urls.indexOf(newUrl) !== -1">
<i class="fa fa-plus"></i>
</button>
</td>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
</fieldset>
<fieldset class="mt-4" ng-disabled="mode == 'readonly' || mode == 'not_authorized'">
<legend>Relations</legend>
<div class="form-group row">
<div class="col-lg-8 mb-2">
<div class="card">
<div class="card-header text-white text-center py-1 bg-primary" ng-class="{'bg-light text-dark' : mode == 'readonly' || mode == 'not_authorized', 'bg-warning' : mode == 'approve'}">Relations</div>
<table class="table table-sm">
<thead class="thead-light">
<tr>
<th class="pl-3" style="border-top: none;">Related organization</th>
<th style="border-top: none; width: 156px">Type</th>
<th style="border-top: none; width: 44px"></th>
</tr>
</thead>
<tbody>
<tr ng-if="org.relations.length == 0">
<td class="text-center text-muted" colspan="3">No relations</td>
</tr>
<tr ng-repeat="r in org.relations">
<td class="pl-3"><a href="#!/edit/0/{{r.relatedOrgId}}" title="{{r.relatedOrgId}}">{{r.relatedOrgName}}</a></td>
<td>{{r.type}}</td>
<td class="text-right">
<button type="button" class="btn btn-sm btn-outline-danger" ng-click="org.relations.splice($index, 1)"><i class="fa fa-trash"></i></button>
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td>
<input type="text" placeholder="add a related organization..." readonly="readonly"
class="form-control form-control-sm" style="background-color: white; color: #007bff;"
ng-model="newRelation.name" ng-click="resetSelectedRelation()"
data-toggle="modal" data-target="#selectRelatedOrgModal" ng-hide="mode == 'readonly'"/>
<input type="text" placeholder="add a related organization..." disabled="disabled"
class="form-control form-control-sm" ng-model="newRelation.name" ng-show="mode == 'readonly'"/>
</td>
<td>
<select class="custom-select custom-select-sm" ng-model="newRelType">
<option disabled="disabled" value=''>rel type...</option>
<option ng-repeat="t in vocabularies.relTypes" value="{{t.value}}">{{t.name}}</option>
</select>
</td>
<td class="text-right">
<button type="button" class="btn btn-sm btn-outline-success" ng-disabled="!newRelType || !newRelation.id" ng-click="addNewRelation()"><i class="fa fa-plus"></i></button>
</td>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
</fieldset>
<fieldset ng-if="mode == 'update_full'">
<button type="submit" class="btn btn-primary" ng-click="save()" ng-disabled="organizationForm.$invalid">Save</button>
<button type="button" class="btn btn-danger" ng-click="deleteOrg()">Delete</button>
</fieldset>
<fieldset ng-if="mode == 'update_simple'">
<button type="submit" class="btn btn-primary" ng-click="save()" ng-disabled="organizationForm.$invalid">Save</button>
</fieldset>
<fieldset ng-if="mode == 'approve'">
<button type="submit" class="btn btn-primary" ng-click="save()" ng-disabled="organizationForm.$invalid">Approve as new Organization</button>
<button type="button" class="btn btn-danger" ng-click="saveAsDiscarded()">Discard</button>
</fieldset>
<fieldset ng-if="mode == 'insert_full'">
<button type="submit" class="btn btn-primary" ng-click="save()" ng-disabled="organizationForm.$invalid">Register a new Organization</button>
</fieldset>
<fieldset ng-if="mode == 'insert_pending'">
<button type="submit" class="btn btn-primary" ng-click="save()" ng-disabled="organizationForm.$invalid">Suggest a new Organization</button>
</fieldset>
</form>
<select-org-modal modal-id="selectRelatedOrgModal" selected-org="newRelation" filter-status="approved"></select-org-modal>

View File

@ -16,6 +16,7 @@
<org-results-page orgs="searchOrgs"
prev-function="search(searchValue, searchOrgs.number - 1, searchOrgs.size)"
next-function="search(searchValue, searchOrgs.number + 1, searchOrgs.size)"
on-select="selectOrg()"
selected-org="selectedOrg"
mode="select-modal"></org-results-page>

View File

@ -1,23 +0,0 @@
<div class="card">
<div class="card-header">
<ul class="nav nav-tabs card-header-tabs">
<li class="nav-item text-nowrap">
<a href="#!/suggestions/_/0" class="nav-link" ng-class="{'active': currentTab == 0}">Pending Organizations <span class="badge badge-primary ml-2">{{info.total.nPendingOrgs}}</span></a>
</li>
<li class="nav-item text-nowrap">
<a href="#!/suggestions/_/1" class="nav-link" ng-class="{'active': currentTab == 1}">New Duplicates <span class="badge badge-primary ml-2">{{info.total.nDuplicates}}</span></a>
</li>
<li class="nav-item text-nowrap">
<a href="#!/suggestions/_/2" class="nav-link" ng-class="{'active': currentTab == 2}">New Conflicts <span class="badge badge-danger ml-2">{{info.total.nConflicts}}</span></a>
</li>
</ul>
</div>
<div class="card-body">
<pending-orgs orgs="pendingOrgs" country="{{country}}" info="info" info-method="getInfo()" ng-if="currentTab == 0"></pending-orgs>
<all-duplicates duplicates="duplicates" country="{{country}}" info="info" info-method="getInfo()" ng-if="currentTab == 1"></all-duplicates>
<all-conflicts conflicts="conflicts" country="{{country}}" info="info" info-method="getInfo()" ng-if="currentTab == 2"></all-conflicts>
</div>
</div>

View File

@ -4,12 +4,9 @@ orgsModule.service('vocabulariesService', function($http) {
this.vocs = {};
this.getVocs = function(f) {
if (Object.keys(this.vocs).length === 0) {
$http.get('api/vocabularies').then(function successCallback(res) {
if((typeof res.data) == 'string') { alert("Session expired !"); location.reload(true); }
call_http_get($http, 'api/vocabularies', function(res) {
this.vocs = res.data;
f(angular.copy(this.vocs));
}, function errorCallback(res) {
alert('ERROR: ' + res.data.error + ' (' + res.data.message + ')');
});
} else {
f(this.vocs);
@ -17,29 +14,66 @@ orgsModule.service('vocabulariesService', function($http) {
};
});
orgsModule.directive('selectOrgModal', function($http) {
orgsModule.factory('suggestionInfo', function($http) {
var info = { data : {} };
var getInfo = function() { return info; };
var updateInfo = function(callback) {
call_http_get($http, 'api/organizations/suggestionsInfo', function(res) {
info.data = res.data;
if (callback) { callback(info); }
});
};
return {
getInfo: getInfo,
updateInfo: updateInfo
};
});
orgsModule.controller('menuCtrl', function ($scope, suggestionInfo) {
$scope.info = suggestionInfo.getInfo();
suggestionInfo.updateInfo(null);
});
orgsModule.directive('selectOrgModal', function($http, $timeout) {
return {
restrict: 'E',
scope: {
'modalId' : '@',
'selectedOrg' : '='
'modalId' : '@',
'filterStatus' : '@',
'selectedOrg' : '=',
'onSelect' : '&'
},
templateUrl: 'resources/html/modals/select_org.html',
templateUrl: 'resources/html/parts/select_org.modal.html',
link: function(scope, element, attrs, ctrl) {
scope.searchOrgs = {};
scope.searchValue = '';
scope.searchText = '';
scope.searchValue = '';
scope.search = function(text, page, size) {
scope.searchOrgs = {};
$http.get('api/organizations/search/' + page + '/' + size + '?q=' + text).then(function successCallback(res) {
if((typeof res.data) == 'string') { alert("Session expired !"); location.reload(true); }
call_http_get($http, 'api/organizations/search/' + page + '/' + size + '?status='+ scope.filterStatus + '&q=' + text, function(res) {
scope.searchValue = text;
scope.searchOrgs = res.data;
}, function errorCallback(res) {
alert('ERROR: ' + res.data.error + ' (' + res.data.message + ')');
});
}
scope.selectOrg = function() {
$timeout(function() {
scope.searchText = '';
scope.searchValue = '';
scope.searchOrgs = {};
}, 1000);
if (scope.onSelect) {
scope.onSelect();
}
}
}
}
});
@ -52,7 +86,7 @@ orgsModule.directive('resolveConflictsModal', function($http, $route) {
'orgs' : '=',
'selectedOrgs' : '='
},
templateUrl: 'resources/html/modals/resolve_conflicts.html',
templateUrl: 'resources/html/parts/resolve_conflicts.modal.html',
link: function(scope, element, attrs, ctrl) {
scope.selectOrg = function(org) {
var sel = angular.copy(org);
@ -75,15 +109,10 @@ orgsModule.directive('resolveConflictsModal', function($http, $route) {
else { otherIds.push(o.id); }
});
if (masterId && otherIds.length > 0) {
$http.defaults.headers.post["Content-Type"] = "application/json;charset=UTF-8";
$http.post('api/organizations/conflicts/fix/' + masterId, otherIds).then(function successCallback(res) {
if ((typeof res.data) == 'string') { alert("Session expired !"); location.reload(true); }
call_http_post($http, 'api/organizations/conflicts/fix/' + masterId, otherIds, function(res) {
$('#' + scope.modalId).modal('hide');
$('#' + scope.modalId).on('hidden.bs.modal', function (e) {
$route.reload();
});
}, function errorCallback(res) {
alert('ERROR: ' + res.data.error + ' (' + res.data.message + ')');
$('#' + scope.modalId).on('hidden.bs.modal', function (e) { $route.reload(); });
});
}
}
@ -91,67 +120,17 @@ orgsModule.directive('resolveConflictsModal', function($http, $route) {
}
});
orgsModule.directive('orgTabsMenu', function($http) {
return {
restrict: 'E',
scope: {
'orgId' : '@',
'info' : '=',
'selected' : '=',
'org' : '=',
'events' : '=',
},
templateUrl: 'resources/html/menu/org_tabs_menu.html',
link: function(scope, element, attrs, ctrl) {
scope.loadOrg = function() {
scope.org = {};
$http.get('api/organizations/get?id=' + scope.orgId).then(function successCallback(res) {
if((typeof res.data) == 'string') { alert("Session expired !"); location.reload(true); }
scope.org = res.data;
}, function errorCallback(res) {
alert('ERROR: ' + res.data.error + ' (' + res.data.message + ')');
});
scope.selected = 1;
}
scope.loadDedupEvents = function() {
scope.events = {};
$http.get('api/organizations/conflicts?id=' + scope.orgId).then(function successCallback(res) {
if((typeof res.data) == 'string') { alert("Session expired !"); location.reload(true); }
scope.events.conflicts = res.data;
$http.get('api/organizations/duplicates?id=' + scope.orgId).then(function successCallback(res) {
if((typeof res.data) == 'string') { alert("Session expired !"); location.reload(true); }
scope.events.duplicates = res.data;
}, function errorCallback(res) {
alert('ERROR: ' + res.data.error + ' (' + res.data.message + ')');
});
}, function errorCallback(res) {
alert('ERROR: ' + res.data.error + ' (' + res.data.message + ')');
});
scope.selected = 2;
};
scope.loadOrg();
}
}
});
orgsModule.directive('orgFormMetadata', function($http, $location, $route, $routeParams) {
return {
restrict: 'E',
scope: {
'orgId' : '@',
'org' : '=',
'vocabularies' : '=',
'mode' : '@', // insert, update or approve
'mode' : '@', // insert_full, insert_pending, update_simple, update_full, approve, readonly, not_authorized
'infoMethod' : '&'
},
templateUrl: 'resources/html/forms/org_metadata.html',
templateUrl: 'resources/html/parts/org_metadata.form.html',
link: function(scope, element, attrs, ctrl) {
scope.newRelation = {};
scope.newRelType = '';
@ -170,62 +149,34 @@ orgsModule.directive('orgFormMetadata', function($http, $location, $route, $rout
}
scope.save = function() {
$http.defaults.headers.post["Content-Type"] = "application/json;charset=UTF-8";
$http.post('api/organizations/save', scope.org).then(function successCallback(res) {
if ((typeof res.data) == 'string') { alert("Session expired !"); location.reload(true); }
else if (scope.mode == 'insert') { $location.url('/edit/1/' + res.data[0]); }
call_http_post($http, 'api/organizations/save', scope.org, function(res) {
if (scope.mode == 'insert') { $location.url('/edit/1/' + res.data[0]); }
else if (scope.mode == 'approve') { $location.url('/edit/3/' + res.data[0]); }
else if ($routeParams.msg == 2) { $route.reload(); }
else { $location.url('/edit/2/' + res.data[0]); }
}, function errorCallback(res) {
alert('ERROR: ' + res.data.error + ' (' + res.data.message + ')');
});
}
scope.deleteOrg = function() {
if (confirm("Are you sure?")) {
call_http_get($http, 'api/organizations/delete?id=' + scope.org.id, function(res) {
alert("Organization marked as deleted !!!");
$route.reload();
});
}
};
scope.saveAsDiscarded = function() {
if (confirm("Are you sure?")) {
call_http_get($http, 'api/organizations/discard?id=' + scope.org.id, function(res) {
alert("Organization marked as discarded !!!");
$route.reload();
});
}
};
}
}
});
orgsModule.directive('orgDedupEvents', function($http, $location, $route) {
return {
restrict: 'E',
scope: {
'orgId' : '@',
'events' : '=',
'vocabularies' : '=',
'infoMethod' : '&'
},
templateUrl: 'resources/html/parts/org_dedup_events.html',
link: function(scope, element, attrs, ctrl) {
scope.currentOrgDetails = {};
$http.get('api/organizations/get?id=' + scope.orgId).then(function successCallback(res) {
if((typeof res.data) == 'string') { alert("Session expired !"); location.reload(true); }
scope.currentOrgDetails = res.data;
}, function errorCallback(res) {
alert('ERROR: ' + res.data.error + ' (' + res.data.message + ')');
});
scope.saveDuplicates = function() {
$http.defaults.headers.post["Content-Type"] = "application/json;charset=UTF-8";
$http.post('api/organizations/duplicates', scope.events.duplicates).then(function successCallback(res) {
if((typeof res.data) == 'string') { alert("Session expired !"); location.reload(true); }
if (scope.infoMethod) { scope.infoMethod(); }
alert('Events updated !!!');
scope.events.duplicates = res.data;
}, function errorCallback(res) {
alert('ERROR: ' + res.data.error + ' (' + res.data.message + ')');
});
}
scope.saveConflicts = function() {
alert('todo');
}
}
}
});
orgsModule.directive('orgDetails', function($http, $location, $route) {
return {
restrict: 'E',
@ -247,6 +198,7 @@ orgsModule.directive('orgResultsPage', function($http, $location, $route) {
'orgs' : '=',
'nextFunction' : '&',
'prevFunction' : '&',
'onSelect' : '&',
'selectedOrg' : '=',
'mode' : '@'
},
@ -255,94 +207,90 @@ orgsModule.directive('orgResultsPage', function($http, $location, $route) {
scope.selectOrg = function(o) {
scope.selectedOrg.id = o.id;
scope.selectedOrg.name = o.name;
scope.selectedOrg.type = o.name;
scope.selectedOrg.city = o.name;
scope.selectedOrg.country = o.name;
scope.selectedOrg.acronyms = o.name;
scope.selectedOrg.approved = o.name;
scope.selectedOrg.type = o.type;
scope.selectedOrg.city = o.city;
scope.selectedOrg.country = o.country;
scope.selectedOrg.acronyms = o.acronyms;
scope.selectedOrg.urls = o.urls;
scope.selectedOrg.status = o.status;
if (scope.onSelect) {
scope.onSelect();
}
}
}
}
});
orgsModule.directive('allConflicts', function($http, $location, $route, $q) {
return {
restrict: 'E',
scope: {
'conflicts' : '=',
'country' : '@',
'info' : '=',
'infoMethod' : '&'
},
templateUrl: 'resources/html/forms/all_conflicts.html',
link: function(scope, element, attrs, ctrl) {
scope.orgs = [];
scope.prepareConflictsModal = function(list) {
scope.orgs = [];
scope.selectedOrgs = [];
var gets = list.map((o) => $http.get('api/organizations/get?id=' + o.id));
$q.all(gets).then(function(responses) {
scope.orgs = responses.map((resp) => resp.data);
angular.forEach(scope.orgs, function(org) { org.show = 'secondary'; });
});
}
}
}
});
orgsModule.directive('pendingOrgs', function($http, $location, $route, $q) {
return {
restrict: 'E',
scope: {
'orgs' : '=',
'country' : '@',
'info' : '=',
'infoMethod' : '&'
},
templateUrl: 'resources/html/forms/pending_orgs.html',
link: function(scope, element, attrs, ctrl) {
}
}
});
orgsModule.directive('orgFormDuplicates', function($http, $location, $route) {
orgsModule.directive('orgDuplicates', function($http, $location, $route) {
return {
restrict: 'E',
scope: {
'orgId' : '@',
'duplicates' : '=',
'showSaveButton' : '@',
'saveFunction' : '&'
},
templateUrl: 'resources/html/forms/org_duplicates.html',
link: function(scope, element, attrs, ctrl) {}
templateUrl: 'resources/html/parts/org_duplicates.html',
link: function(scope, element, attrs, ctrl) {
scope.newDuplicate = {};
scope.addDuplicate = function() {
scope.duplicates.push({
'localId' : scope.orgId,
'oaOriginalId' : scope.newDuplicate.id,
'oaName' : scope.newDuplicate.name,
'oaAcronym' : scope.newDuplicate.acronyms.join(),
'oaCountry' : scope.newDuplicate.country,
'oaUrl' : scope.newDuplicate.urls.join(),
'oaCollectedFrom' : 'user',
'relType' : 'is_similar'
});
call_http_get($http, 'api/organizations/duplicates?id=' + scope.newDuplicate.id, function(res) {
angular.forEach(res.data, function(dup) {
dup.localId = scope.orgId;
scope.duplicates.push(dup);
});
});
}
}
}
});
orgsModule.directive('orgFormConflicts', function($http, $location, $route, $q) {
orgsModule.directive('orgConflicts', function($http, $location, $route, $q) {
return {
restrict: 'E',
scope: {
'orgId' : '@',
'org' : '=',
'conflicts' : '=',
'showSaveButton' : '@',
'saveFunction' : '&'
'showSaveButton' : '@'
},
templateUrl: 'resources/html/forms/org_conflicts.html',
templateUrl: 'resources/html/parts/org_conflicts.html',
link: function(scope, element, attrs, ctrl) {
scope.candidateConflicts = [];
scope.selectedConflicts = [];
scope.newConflict = {};
scope.addConflict = function() {
scope.conflicts.push({
'id' : scope.newConflict.id,
'name' : scope.newConflict.name,
'type' : scope.newConflict.type,
'city' : scope.newConflict.city,
'country': scope.newConflict.country
});
}
scope.prepareConflictsModal = function() {
scope.candidateConflicts = [];
scope.selectedConflicts = [];
var gets = [ $http.get('api/organizations/get?id=' + scope.orgId) ];
var gets = [ $http.get('api/organizations/get?id=' + scope.org.id) ];
angular.forEach(scope.conflicts, function(c) { gets.push($http.get('api/organizations/get?id=' + c.id)); });
$q.all(gets).then(function(responses) {
@ -354,83 +302,21 @@ orgsModule.directive('orgFormConflicts', function($http, $location, $route, $q)
}
});
orgsModule.directive('allDuplicates', function($http, $location, $route, $timeout) {
return {
restrict: 'E',
scope: {
'duplicates' : '=',
'country' : '@',
'info' : '=',
'infoMethod' : '&'
},
templateUrl: 'resources/html/forms/all_duplicates.html',
link: function(scope, element, attrs, ctrl) {
scope.currentOrg = {};
scope.currentOrgDetails = {};
scope.currentDuplicates = [];
scope.prepareDuplicatesModal = function(org) {
scope.currentOrg = org;
scope.currentOrgDetails = {};
scope.currentDuplicates = [];
$http.get('api/organizations/get?id=' + org.id).then(function successCallback(res) {
if((typeof res.data) == 'string') { alert("Session expired !"); location.reload(true); }
scope.currentOrgDetails = res.data;
}, function errorCallback(res) {
alert('ERROR: ' + res.data.error + ' (' + res.data.message + ')');
});
$http.get('api/organizations/duplicates?id=' + org.id).then(function successCallback(res) {
if((typeof res.data) == 'string') { alert("Session expired !"); location.reload(true); }
scope.currentDuplicates = res.data;
}, function errorCallback(res) {
alert('ERROR: ' + res.data.error + ' (' + res.data.message + ')');
});
};
scope.saveCurrentDuplicates = function() {
$http.defaults.headers.post["Content-Type"] = "application/json;charset=UTF-8";
$http.post('api/organizations/duplicates', scope.currentDuplicates).then(function successCallback(res) {
if((typeof res.data) == 'string') { alert("Session expired !"); location.reload(true); }
if (scope.infoMethod) { scope.infoMethod(); }
scope.currentOrg.numberOfDuplicates = 0;
for (var i=0; i<res.data.length; i++) {
if (res.data[i].relType == 'suggested') {
scope.currentOrg.numberOfDuplicates++;
}
}
scope.currentDuplicates = [];
$timeout(function() {
if (scope.duplicates.length > 1) {
$route.reload();
} else {
$location.url('/suggestions/_/1');
}
}, 600);
}, function errorCallback(res) {
alert('ERROR: ' + res.data.error + ' (' + res.data.message + ')');
});
};
}
}
});
orgsModule.config(function($routeProvider) {
$routeProvider
.when('/new', { templateUrl: 'resources/html/new.html', controller: 'newOrgCtrl' })
.when('/search', { templateUrl: 'resources/html/search.html', controller: 'searchCtrl' })
.when('/searchResults/:page/:size/:text*', { templateUrl: 'resources/html/searchResults.html', controller: 'searchResultsCtrl' })
.when('/countries', { templateUrl: 'resources/html/browse.html', controller: 'countriesCtrl' })
.when('/byCountry/:page/:size/:code*', { templateUrl: 'resources/html/resultsByCountry.html', controller: 'byCountryCtrl' })
.when('/types', { templateUrl: 'resources/html/browse.html', controller: 'typesCtrl' })
.when('/byType/:page/:size/:type*', { templateUrl: 'resources/html/resultsByType.html', controller: 'byTypeCtrl' })
.when('/edit/:msg/:id', { templateUrl: 'resources/html/edit.html', controller: 'showEditCtrl' })
.when('/suggestions/:country/:tab', { templateUrl: 'resources/html/suggestions.html', controller: 'showSuggestionsCtrl' })
.when('/users', { templateUrl: 'resources/html/users.html', controller: 'usersCtrl' })
.otherwise({ redirectTo: '/suggestions/_/0' });
.when('/search', { templateUrl: 'resources/html/pages/search/search.html', controller: 'searchCtrl' })
.when('/searchResults/:page/:size/:text*', { templateUrl: 'resources/html/pages/search/searchResults.html', controller: 'searchResultsCtrl' })
.when('/countries', { templateUrl: 'resources/html/pages/search/browse.html', controller: 'countriesCtrl' })
.when('/byCountry/:page/:size/:status/:code*', { templateUrl: 'resources/html/pages/search/resultsByCountry.html', controller: 'byCountryCtrl' })
.when('/types', { templateUrl: 'resources/html/pages/search/browse.html', controller: 'typesCtrl' })
.when('/byType/:page/:size/:status/:type*', { templateUrl: 'resources/html/pages/search/resultsByType.html', controller: 'byTypeCtrl' })
.when('/edit/:msg/:id', { templateUrl: 'resources/html/pages/edit/edit.html', controller: 'showEditCtrl' })
.when('/new', { templateUrl: 'resources/html/pages/advanced/new.html', controller: 'newOrgCtrl' })
.when('/pendings/:country', { templateUrl: 'resources/html/pages/advanced/pendingOrgs.html', controller: 'pendingOrgsCtrl' })
.when('/duplicates/:country', { templateUrl: 'resources/html/pages/advanced/duplicates.html', controller: 'duplicatesCtrl' })
.when('/conflicts/:country', { templateUrl: 'resources/html/pages/advanced/conflicts.html', controller: 'conflictsCtrl' })
.when('/users', { templateUrl: 'resources/html/pages/admin/users.html', controller: 'usersCtrl' })
.otherwise({ redirectTo: '/search' });
});
orgsModule.filter('escape', function() {
@ -462,10 +348,9 @@ orgsModule.controller('newOrgCtrl', function ($scope, $http, $routeParams, $loca
"urls": [],
"relations": []
};
$scope.adminMode = adminMode();
$scope.vocabularies = {};
vocabulariesService.getVocs(function(vocs) { $scope.vocabularies = vocs; });
});
orgsModule.controller('searchCtrl', function ($scope, $location) {
@ -486,12 +371,7 @@ orgsModule.controller('searchResultsCtrl', function ($scope, $http, $routeParams
}
$scope.orgs = {};
$http.get('api/organizations/search/' + $routeParams.page + '/' + $routeParams.size + '?q=' + $scope.searchText).then(function successCallback(res) {
if((typeof res.data) == 'string') { alert("Session expired !"); location.reload(true); }
$scope.orgs = res.data;
}, function errorCallback(res) {
alert('ERROR: ' + res.data.error + ' (' + res.data.message + ')');
});
call_http_get($http, 'api/organizations/search/' + $routeParams.page + '/' + $routeParams.size + '?q=' + $scope.searchText, function(res) { $scope.orgs = res.data; });
$scope.prev = function() {
if ($scope.searchText) {
@ -511,16 +391,13 @@ orgsModule.controller('searchResultsCtrl', function ($scope, $http, $routeParams
});
orgsModule.controller('countriesCtrl', function ($scope, $http, $routeParams) {
$scope.title = 'Countries';
$scope.field = 'Country';
$scope.resultsBasePath = '/byCountry'
$scope.entries = [];
$http.get('api/organizations/browse/countries').then(function successCallback(res) {
if((typeof res.data) == 'string') { alert("Session expired !"); location.reload(true); }
$scope.entries = res.data;
}, function errorCallback(res) {
alert('ERROR: ' + res.data.error + ' (' + res.data.message + ')');
});
call_http_get($http, 'api/organizations/browse/countries', function(res) { $scope.entries = res.data; });
});
@ -528,35 +405,25 @@ orgsModule.controller('byCountryCtrl', function ($scope, $http, $routeParams, $l
$scope.fieldValue = decodeURIComponent($routeParams.code);
$scope.orgs = {};
$http.get('api/organizations/byCountry/' + $routeParams.code + '/' + $routeParams.page + '/' + $routeParams.size).then(function successCallback(res) {
if((typeof res.data) == 'string') { alert("Session expired !"); location.reload(true); }
$scope.orgs = res.data;
}, function errorCallback(res) {
alert('ERROR: ' + res.data.error + ' (' + res.data.message + ')');
});
call_http_get($http, 'api/organizations/byCountry/' + $routeParams.status + '/' + $routeParams.code + '/' + $routeParams.page + '/' + $routeParams.size, function(res) { $scope.orgs = res.data; });
$scope.prev = function() {
$location.url('/byCountry/' + ($scope.orgs.number - 1) + '/' + $scope.orgs.size + '/' + encodeURIComponent($scope.fieldValue));
$location.url('/byCountry/' + ($scope.orgs.number - 1) + '/' + $scope.orgs.size + '/' + $routeParams.status + '/' + encodeURIComponent($scope.fieldValue));
}
$scope.next = function() {
$location.url('/byCountry/' + ($scope.orgs.number + 1) + '/' + $scope.orgs.size + '/' + encodeURIComponent($scope.fieldValue));
$location.url('/byCountry/' + ($scope.orgs.number + 1) + '/' + $scope.orgs.size + '/' + $routeParams.status + '/' + encodeURIComponent($scope.fieldValue));
}
});
orgsModule.controller('typesCtrl', function ($scope, $http, $routeParams) {
$scope.title = 'Organization types';
$scope.field = 'Organization type';
$scope.resultsBasePath = '/byType'
$scope.entries = [];
$http.get('api/organizations/browse/types').then(function successCallback(res) {
if((typeof res.data) == 'string') { alert("Session expired !"); location.reload(true); }
$scope.entries = res.data;
}, function errorCallback(res) {
alert('ERROR: ' + res.data.error + ' (' + res.data.message + ')');
});
call_http_get($http, 'api/organizations/browse/types', function(res) { $scope.entries = res.data; });
});
orgsModule.controller('byTypeCtrl', function ($scope, $http, $routeParams, $location) {
@ -565,19 +432,14 @@ orgsModule.controller('byTypeCtrl', function ($scope, $http, $routeParams, $loca
$scope.orgs = {};
$http.get('api/organizations/byType/' + $routeParams.type + '/' + $routeParams.page + '/' + $routeParams.size).then(function successCallback(res) {
if((typeof res.data) == 'string') { alert("Session expired !"); location.reload(true); }
$scope.orgs = res.data;
}, function errorCallback(res) {
alert('ERROR: ' + res.data.error + ' (' + res.data.message + ')');
});
call_http_get($http, 'api/organizations/byType/' + $routeParams.status + '/' + $routeParams.type + '/' + $routeParams.page + '/' + $routeParams.size, function(res) { $scope.orgs = res.data; });
$scope.prev = function() {
$location.url('/byType/' + ($scope.orgs.number - 1) + '/' + $scope.orgs.size + '/' + encodeURIComponent($scope.fieldValue));
$location.url('/byType/' + ($scope.orgs.number - 1) + '/' + $scope.orgs.size + '/' + $routeParams.status + '/' + encodeURIComponent($scope.fieldValue));
}
$scope.next = function() {
$location.url('/byType/' + ($scope.orgs.number + 1) + '/' + $scope.orgs.size + '/' + encodeURIComponent($scope.fieldValue));
$location.url('/byType/' + ($scope.orgs.number + 1) + '/' + $scope.orgs.size + '/' + $routeParams.status + '/' + encodeURIComponent($scope.fieldValue));
}
});
@ -585,89 +447,81 @@ orgsModule.controller('byTypeCtrl', function ($scope, $http, $routeParams, $loca
orgsModule.controller('showEditCtrl', function ($scope, $http, $routeParams, $route, $location, $timeout, $window, vocabulariesService) {
$scope.orgId = $routeParams.id;
$scope.org = {};
$scope.events = {};
$scope.duplicates = [];
$scope.conflicts = [];
$scope.info = {};
$scope.currentTab = 1
$scope.vocabularies = {};
$scope.adminMode = adminMode();
$scope.getInfo = function() {
$http.get('api/organizations/info?id=' + $scope.orgId).then(function successCallback(res) {
if((typeof res.data) == 'string') { alert("Session expired !"); location.reload(true); }
$scope.info = res.data;
}, function errorCallback(res) {
alert('ERROR: ' + res.data.error + ' (' + res.data.message + ')');
});
call_http_get($http, 'api/organizations/info?id=' + $scope.orgId, function(res) { $scope.info = res.data; });
};
$scope.getInfo();
$scope.gotoTab = function(tab) {
$scope.org = {};
call_http_get($http, 'api/organizations/get?id=' + $scope.orgId, function(res) { $scope.org = res.data; });
if (tab == 2) {
$scope.duplicates = [];
call_http_get($http, 'api/organizations/duplicates?id=' + $scope.orgId, function(res) { $scope.duplicates = res.data; });
} else if (tab == 3) {
$scope.conflicts = [];
call_http_get($http, 'api/organizations/conflicts?id=' + $scope.orgId, function(res) { $scope.conflicts = res.data; });
}
$scope.currentTab = tab;
}
$scope.saveDuplicates = function() {
call_http_post($http, 'api/organizations/duplicates', $scope.duplicates, function(res) {
$scope.getInfo();
alert('Events updated !!!');
$scope.duplicates = res.data;
});
}
$scope.vocabularies = {};
vocabulariesService.getVocs(function(vocs) { $scope.vocabularies = vocs; });
$scope.getInfo();
$scope.gotoTab(1);
if ($routeParams.msg == 1) { $scope.message = 'New organization registered !!!'; }
else if ($routeParams.msg == 2) { $scope.message = 'Organization updated !!!'; }
else if ($routeParams.msg == 3) { $scope.message = 'Pending organization registered !!!'; }
else { $scope.message = ''; }
$window.scrollTo(0, 0);
$timeout(function() { $scope.message = ''; }, 3000)
$timeout(function() { $scope.message = ''; }, 3000);
});
orgsModule.controller('showSuggestionsCtrl', function ($scope, $http, $routeParams, $location) {
$scope.info = {};
$scope.pendingOrgs = [];
$scope.duplicates = [];
$scope.conflicts = [];
$scope.currentTab = $routeParams.tab;
orgsModule.controller('pendingOrgsCtrl', function ($scope, $http, $routeParams, $location, suggestionInfo) {
$scope.info = suggestionInfo.getInfo();
$scope.orgs = [];
$scope.country = $routeParams.country;
$scope.getInfo = function() {
$http.get('api/organizations/suggestionsInfo').then(function successCallback(res) {
if((typeof res.data) == 'string') { alert("Session expired !"); location.reload(true); }
$scope.info = res.data;
if ($scope.country == '_') {
var found = '';
angular.forEach($scope.info.byCountry, function(values, c) {
if (!found && (($scope.currentTab == 0 && values.nPendingOrgs > 0) || ($scope.currentTab == 1 && values.nDuplicates > 0) || ($scope.currentTab == 2 && values.nConflicts > 0))) {
found = c;
}
});
if (found) { $location.url('/suggestions/' + found + '/' + $scope.currentTab); }
}
}, function errorCallback(res) {
alert('ERROR: ' + res.data.error + ' (' + res.data.message + ')');
suggestionInfo.updateInfo(function(info) {
if ($scope.country == '_') {
var found = '';
angular.forEach(info.data.byCountry, function(values, c) {
if (!found && values.nPendingOrgs > 0) {
found = c;
}
});
if (found) { $location.url('/pendings/' + found); }
}
});
};
$scope.refresh = function() {
$scope.pendingOrgs = [];
$scope.duplicates = [];
$scope.conflicts = [];
$scope.orgs = [];
if ($scope.country != '_') {
if ($scope.currentTab == 0) {
$http.get('api/organizations/byCountry/pending/' + $scope.country).then(function successCallback(res) {
if((typeof res.data) == 'string') { alert("Session expired !"); location.reload(true); }
$scope.pendingOrgs = res.data;
}, function errorCallback(res) {
alert('ERROR: ' + res.data.error + ' (' + res.data.message + ')');
});
} else if ($scope.currentTab == 1) {
$http.get('api/organizations/duplicates/byCountry/' + $scope.country).then(function successCallback(res) {
if((typeof res.data) == 'string') { alert("Session expired !"); location.reload(true); }
$scope.duplicates = res.data;
}, function errorCallback(res) {
alert('ERROR: ' + res.data.error + ' (' + res.data.message + ')');
});
} else if ($scope.currentTab == 2) {
$http.get('api/organizations/conflicts/byCountry/' + $scope.country).then(function successCallback(res) {
if((typeof res.data) == 'string') { alert("Session expired !"); location.reload(true); }
$scope.conflicts = res.data;
}, function errorCallback(res) {
alert('ERROR: ' + res.data.error + ' (' + res.data.message + ')');
});
} else { }
call_http_get($http, 'api/organizations/byCountry/suggested/' + $scope.country, function(res) { $scope.orgs = res.data; });
}
$scope.getInfo();
}
@ -675,6 +529,135 @@ orgsModule.controller('showSuggestionsCtrl', function ($scope, $http, $routePara
$scope.refresh();
});
orgsModule.controller('duplicatesCtrl', function ($scope, $http, $routeParams, $location, $timeout, $route, suggestionInfo) {
$scope.info = suggestionInfo.getInfo();
$scope.duplicates = [];
$scope.country = $routeParams.country;
$scope.currentOrg = {};
$scope.currentOrgDetails = {};
$scope.prepareDuplicatesModal = function(org) {
$scope.currentOrg = org;
$scope.currentOrgDetails = {};
$scope.currentDuplicates = [];
call_http_get($http, 'api/organizations/get?id=' + org.id, function(res) { $scope.currentOrgDetails = res.data; });
call_http_get($http, 'api/organizations/duplicates?id=' + org.id, function(res) { $scope.currentDuplicates = res.data; });
};
$scope.getInfo = function() {
suggestionInfo.updateInfo(function(info) {
if ($scope.country == '_') {
var found = '';
angular.forEach(info.data.byCountry, function(values, c) {
if (!found && values.nDuplicates > 0) {
found = c;
}
});
if (found) { $location.url('/duplicates/' + found); }
}
});
};
$scope.saveCurrentDuplicates = function() {
call_http_post($http, 'api/organizations/duplicates', $scope.currentDuplicates, function(res) {
$scope.getInfo();
$scope.currentOrg.numberOfDuplicates = 0;
for (var i=0; i<res.data.length; i++) {
if (res.data[i].relType == 'suggested') {
$scope.currentOrg.numberOfDuplicates++;
}
}
$scope.currentDuplicates = [];
$timeout(function() {
if ($scope.duplicates.length > 1) {
$route.reload();
} else {
$location.url('/duplicates/_');
}
}, 600);
});
};
$scope.refresh = function() {
$scope.duplicates = [];
if ($scope.country != '_') {
call_http_get($http, 'api/organizations/duplicates/byCountry/' + $scope.country, function(res) { $scope.duplicates = res.data; });
}
$scope.getInfo();
}
$scope.refresh();
});
orgsModule.controller('conflictsCtrl', function ($scope, $http, $routeParams, $location, $route, $q, suggestionInfo) {
$scope.info = suggestionInfo.getInfo();
$scope.conflicts = [];
$scope.country = $routeParams.country;
$scope.orgs = [];
$scope.newConflict = {};
$scope.currentGroup = [];
$scope.addConflict = function() {
$scope.currentGroup.push({
'id' : $scope.newConflict.id,
'name' : $scope.newConflict.name,
'type' : $scope.newConflict.type,
'city' : $scope.newConflict.city,
'country': $scope.newConflict.country
});
}
$scope.prepareAddConflictModal = function(list) {
$scope.currentGroup = list;
};
$scope.prepareConflictsModal = function(list) {
$scope.orgs = [];
$scope.selectedOrgs = [];
var gets = list.map((o) => $http.get('api/organizations/get?id=' + o.id));
$q.all(gets).then(function(responses) {
$scope.orgs = responses.map((resp) => resp.data);
angular.forEach($scope.orgs, function(org) { org.show = 'secondary'; });
});
}
$scope.getInfo = function() {
suggestionInfo.updateInfo(function(info) {
if ($scope.country == '_') {
var found = '';
angular.forEach(info.data.byCountry, function(values, c) {
if (!found && values.nConflicts > 0) {
found = c;
}
});
if (found) { $location.url('/conflicts/' + found); }
}
});
};
$scope.refresh = function() {
$scope.conflicts = [];
if ($scope.country != '_') {
call_http_get($http, 'api/organizations/conflicts/byCountry/' + $scope.country, function(res) { $scope.conflicts = res.data; });
}
$scope.getInfo();
}
$scope.refresh();
});
orgsModule.controller('usersCtrl', function ($scope, $http, $timeout, $route, vocabulariesService) {
$scope.users = [];
@ -685,12 +668,7 @@ orgsModule.controller('usersCtrl', function ($scope, $http, $timeout, $route, vo
$scope.vocabularies = {};
vocabulariesService.getVocs(function(vocs) { $scope.vocabularies = vocs; });
$http.get('api/users').then(function successCallback(res) {
if((typeof res.data) == 'string') { alert("Session expired !"); location.reload(true); }
$scope.users = res.data;
}, function errorCallback(res) {
alert('ERROR: ' + res.data.error + ' (' + res.data.message + ')');
});
call_http_get($http, 'api/users', function(res) { $scope.users = res.data; });
$scope.setCurrentUser = function(user) {
angular.copy(user, $scope.currentUser);
@ -700,23 +678,12 @@ orgsModule.controller('usersCtrl', function ($scope, $http, $timeout, $route, vo
}
$scope.saveUser = function(user) {
$http.defaults.headers.post["Content-Type"] = "application/json;charset=UTF-8";
$http.post('api/users', user).then(function successCallback(res) {
if((typeof res.data) == 'string') { alert("Session expired !"); location.reload(true); }
$scope.users = res.data;
}, function errorCallback(res) {
alert('ERROR: ' + res.data.error + ' (' + res.data.message + ')');
});
call_http_post($http, 'api/users', user, function(res) { $scope.users = res.data; });
}
$scope.deleteUser = function(email) {
if (confirm("Are you sure ?")) {
$http.delete('api/users?email=' + email).then(function successCallback(res) {
if((typeof res.data) == 'string') { alert("Session expired !"); location.reload(true); }
$scope.users = res.data;
}, function errorCallback(res) {
alert('ERROR: ' + res.data.error + ' (' + res.data.message + ')');
});
call_http_delete($http, 'api/users?email=' + email, function(res) { $scope.users = res.data; });
}
}
});

View File

@ -0,0 +1,348 @@
/**
* Copyright (c) 2011-2014 Felix Gnass
* Licensed under the MIT license
*/
(function(root, factory) {
/* CommonJS */
if (typeof exports == 'object') module.exports = factory()
/* AMD module */
else if (typeof define == 'function' && define.amd) define(factory)
/* Browser global */
else root.Spinner = factory()
}
(this, function() {
"use strict";
var prefixes = ['webkit', 'Moz', 'ms', 'O'] /* Vendor prefixes */
, animations = {} /* Animation rules keyed by their name */
, useCssAnimations /* Whether to use CSS animations or setTimeout */
/**
* Utility function to create elements. If no tag name is given,
* a DIV is created. Optionally properties can be passed.
*/
function createEl(tag, prop) {
var el = document.createElement(tag || 'div')
, n
for(n in prop) el[n] = prop[n]
return el
}
/**
* Appends children and returns the parent.
*/
function ins(parent /* child1, child2, ...*/) {
for (var i=1, n=arguments.length; i<n; i++)
parent.appendChild(arguments[i])
return parent
}
/**
* Insert a new stylesheet to hold the @keyframe or VML rules.
*/
var sheet = (function() {
var el = createEl('style', {type : 'text/css'})
ins(document.getElementsByTagName('head')[0], el)
return el.sheet || el.styleSheet
}())
/**
* Creates an opacity keyframe animation rule and returns its name.
* Since most mobile Webkits have timing issues with animation-delay,
* we create separate rules for each line/segment.
*/
function addAnimation(alpha, trail, i, lines) {
var name = ['opacity', trail, ~~(alpha*100), i, lines].join('-')
, start = 0.01 + i/lines * 100
, z = Math.max(1 - (1-alpha) / trail * (100-start), alpha)
, prefix = useCssAnimations.substring(0, useCssAnimations.indexOf('Animation')).toLowerCase()
, pre = prefix && '-' + prefix + '-' || ''
if (!animations[name]) {
sheet.insertRule(
'@' + pre + 'keyframes ' + name + '{' +
'0%{opacity:' + z + '}' +
start + '%{opacity:' + alpha + '}' +
(start+0.01) + '%{opacity:1}' +
(start+trail) % 100 + '%{opacity:' + alpha + '}' +
'100%{opacity:' + z + '}' +
'}', sheet.cssRules.length)
animations[name] = 1
}
return name
}
/**
* Tries various vendor prefixes and returns the first supported property.
*/
function vendor(el, prop) {
var s = el.style
, pp
, i
prop = prop.charAt(0).toUpperCase() + prop.slice(1)
for(i=0; i<prefixes.length; i++) {
pp = prefixes[i]+prop
if(s[pp] !== undefined) return pp
}
if(s[prop] !== undefined) return prop
}
/**
* Sets multiple style properties at once.
*/
function css(el, prop) {
for (var n in prop)
el.style[vendor(el, n)||n] = prop[n]
return el
}
/**
* Fills in default values.
*/
function merge(obj) {
for (var i=1; i < arguments.length; i++) {
var def = arguments[i]
for (var n in def)
if (obj[n] === undefined) obj[n] = def[n]
}
return obj
}
/**
* Returns the absolute page-offset of the given element.
*/
function pos(el) {
var o = { x:el.offsetLeft, y:el.offsetTop }
while((el = el.offsetParent))
o.x+=el.offsetLeft, o.y+=el.offsetTop
return o
}
/**
* Returns the line color from the given string or array.
*/
function getColor(color, idx) {
return typeof color == 'string' ? color : color[idx % color.length]
}
// Built-in defaults
var defaults = {
lines: 12, // The number of lines to draw
length: 7, // The length of each line
width: 5, // The line thickness
radius: 10, // The radius of the inner circle
rotate: 0, // Rotation offset
corners: 1, // Roundness (0..1)
color: '#000', // #rgb or #rrggbb
direction: 1, // 1: clockwise, -1: counterclockwise
speed: 1, // Rounds per second
trail: 100, // Afterglow percentage
opacity: 1/4, // Opacity of the lines
fps: 20, // Frames per second when using setTimeout()
zIndex: 2e9, // Use a high z-index by default
className: 'spinner', // CSS class to assign to the element
top: '50%', // center vertically
left: '50%', // center horizontally
position: 'absolute' // element position
}
/** The constructor */
function Spinner(o) {
this.opts = merge(o || {}, Spinner.defaults, defaults)
}
// Global defaults that override the built-ins:
Spinner.defaults = {}
merge(Spinner.prototype, {
/**
* Adds the spinner to the given target element. If this instance is already
* spinning, it is automatically removed from its previous target b calling
* stop() internally.
*/
spin: function(target) {
this.stop()
var self = this
, o = self.opts
, el = self.el = css(createEl(0, {className: o.className}), {position: o.position, width: 0, zIndex: o.zIndex})
, mid = o.radius+o.length+o.width
if (target) {
target.insertBefore(el, target.firstChild||null)
css(el, {
left: o.left,
top: o.top
})
}
el.setAttribute('role', 'progressbar')
self.lines(el, self.opts)
if (!useCssAnimations) {
// No CSS animation support, use setTimeout() instead
var i = 0
, start = (o.lines - 1) * (1 - o.direction) / 2
, alpha
, fps = o.fps
, f = fps/o.speed
, ostep = (1-o.opacity) / (f*o.trail / 100)
, astep = f/o.lines
;(function anim() {
i++;
for (var j = 0; j < o.lines; j++) {
alpha = Math.max(1 - (i + (o.lines - j) * astep) % f * ostep, o.opacity)
self.opacity(el, j * o.direction + start, alpha, o)
}
self.timeout = self.el && setTimeout(anim, ~~(1000/fps))
})()
}
return self
},
/**
* Stops and removes the Spinner.
*/
stop: function() {
var el = this.el
if (el) {
clearTimeout(this.timeout)
if (el.parentNode) el.parentNode.removeChild(el)
this.el = undefined
}
return this
},
/**
* Internal method that draws the individual lines. Will be overwritten
* in VML fallback mode below.
*/
lines: function(el, o) {
var i = 0
, start = (o.lines - 1) * (1 - o.direction) / 2
, seg
function fill(color, shadow) {
return css(createEl(), {
position: 'absolute',
width: (o.length+o.width) + 'px',
height: o.width + 'px',
background: color,
boxShadow: shadow,
transformOrigin: 'left',
transform: 'rotate(' + ~~(360/o.lines*i+o.rotate) + 'deg) translate(' + o.radius+'px' +',0)',
borderRadius: (o.corners * o.width>>1) + 'px'
})
}
for (; i < o.lines; i++) {
seg = css(createEl(), {
position: 'absolute',
top: 1+~(o.width/2) + 'px',
transform: o.hwaccel ? 'translate3d(0,0,0)' : '',
opacity: o.opacity,
animation: useCssAnimations && addAnimation(o.opacity, o.trail, start + i * o.direction, o.lines) + ' ' + 1/o.speed + 's linear infinite'
})
if (o.shadow) ins(seg, css(fill('#000', '0 0 4px ' + '#000'), {top: 2+'px'}))
ins(el, ins(seg, fill(getColor(o.color, i), '0 0 1px rgba(0,0,0,.1)')))
}
return el
},
/**
* Internal method that adjusts the opacity of a single line.
* Will be overwritten in VML fallback mode below.
*/
opacity: function(el, i, val) {
if (i < el.childNodes.length) el.childNodes[i].style.opacity = val
}
})
function initVML() {
/* Utility function to create a VML tag */
function vml(tag, attr) {
return createEl('<' + tag + ' xmlns="urn:schemas-microsoft.com:vml" class="spin-vml">', attr)
}
// No CSS transforms but VML support, add a CSS rule for VML elements:
sheet.addRule('.spin-vml', 'behavior:url(#default#VML)')
Spinner.prototype.lines = function(el, o) {
var r = o.length+o.width
, s = 2*r
function grp() {
return css(
vml('group', {
coordsize: s + ' ' + s,
coordorigin: -r + ' ' + -r
}),
{ width: s, height: s }
)
}
var margin = -(o.width+o.length)*2 + 'px'
, g = css(grp(), {position: 'absolute', top: margin, left: margin})
, i
function seg(i, dx, filter) {
ins(g,
ins(css(grp(), {rotation: 360 / o.lines * i + 'deg', left: ~~dx}),
ins(css(vml('roundrect', {arcsize: o.corners}), {
width: r,
height: o.width,
left: o.radius,
top: -o.width>>1,
filter: filter
}),
vml('fill', {color: getColor(o.color, i), opacity: o.opacity}),
vml('stroke', {opacity: 0}) // transparent stroke to fix color bleeding upon opacity change
)
)
)
}
if (o.shadow)
for (i = 1; i <= o.lines; i++)
seg(i, -2, 'progid:DXImageTransform.Microsoft.Blur(pixelradius=2,makeshadow=1,shadowopacity=.3)')
for (i = 1; i <= o.lines; i++) seg(i)
return ins(el, g)
}
Spinner.prototype.opacity = function(el, i, val, o) {
var c = el.firstChild
o = o.shadow && o.lines || 0
if (c && i+o < c.childNodes.length) {
c = c.childNodes[i+o]; c = c && c.firstChild; c = c && c.firstChild
if (c) c.opacity = val
}
}
}
var probe = css(createEl('group'), {behavior: 'url(#default#VML)'})
if (!vendor(probe, 'transform') && probe.adj) initVML()
else useCssAnimations = vendor(probe, 'animation')
return Spinner
}));

View File

@ -15,15 +15,27 @@
<link rel="stylesheet" href="resources/css/fontawesome-all.min.css">
<style type="text/css">
.table > tbody > tr > td {
vertical-align: middle;
}
.card > .table {
margin-bottom: 0 !important;
.table > tbody > tr > td { vertical-align : middle !important; }
.card > .table { margin-bottom : 0 !important; }
fieldset > legend { font-size : 1.2rem !important; }
.overlaydiv {
position: fixed;
width: 100%;
height: 100%;
z-index: 10000;
visibility: hidden;
}
fieldset > legend {
font-size: 1.2rem !important;
.grayRectangle {
position: absolute;
background-color: black;
opacity:0.6;
top: 30%;
left: 40%;
width: 20%;
height: 20%;
z-index: 100;
border-radius: 15px;
}
</style>
@ -33,7 +45,12 @@ fieldset > legend {
</head>
<body ng-app="orgs" sec:authorize="isAuthenticated()">
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div id="spinnerdiv" class="overlaydiv">
<span class="grayRectangle"><!--The spinner is added on loading here--></span>
</div>
<nav class="navbar navbar-expand-lg navbar-dark bg-primary" ng-controller="menuCtrl">
<a class="navbar-brand" href="#"> <img
src="resources/images/openaire_logo_small.png" width="30" height="30" alt="OpenOrgs Database">
OpenOrgs Database
@ -45,7 +62,6 @@ fieldset > legend {
<div class="collapse navbar-collapse w-100 order-1" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
<li class="nav-item"><a class="nav-link" href="#!/suggestions/_/0">Last suggestions</a></li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="javascript:void(0)" data-toggle="dropdown">Search</a>
<div class="dropdown-menu">
@ -55,17 +71,25 @@ fieldset > legend {
<a class="dropdown-item" href="#!/types">browse by type</a>
</div>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="javascript:void(0)" data-toggle="dropdown">Actions</a>
<li class="nav-item dropdown" sec:authorize="hasRole('ROLE_ADMIN') or hasRole('ROLE_NATIONAL_ADMIN')">
<a class="nav-link dropdown-toggle" href="javascript:void(0)" data-toggle="dropdown">Advanced</a>
<div class="dropdown-menu">
<a class="dropdown-item" href="#!/new">register an organization</a>
<a class="dropdown-item d-flex justify-content-between align-items-center" href="#!/pendings/_"><span class="pr-2">pending organizations</span><span class="badge badge-primary badge-pill">{{info.data.total.nPendingOrgs}}</span></a>
<a class="dropdown-item d-flex justify-content-between align-items-center" href="#!/duplicates/_"><span class="pr-2">duplicates</span><span class="badge badge-primary badge-pill">{{info.data.total.nDuplicates}}</span></a>
<a class="dropdown-item d-flex justify-content-between align-items-center" href="#!/conflicts/_"><span class="pr-2">conflicts</span><span class="badge badge-danger badge-pill">{{info.data.total.nConflicts}}</span></a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="#!/new">new from scratch</a>
</div>
</li>
<li class="nav-item dropdown" sec:authorize="hasRole('ROLE_USER')">
<a class="nav-link dropdown-toggle" href="javascript:void(0)" data-toggle="dropdown">Advanced</a>
<div class="dropdown-menu">
<a class="dropdown-item d-flex justify-content-between align-items-center" href="#!/duplicates/_"><span class="pr-2">duplicates</span><span class="badge badge-primary badge-pill">{{info.data.total.nDuplicates}}</span></a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="#!/new">suggest a new organization</a>
</div>
</li>
</ul>
</div>
<div class="navbar-collapse collapse w-100 order-2">
<ul class="navbar-nav ml-auto">
<li class="nav-item dropdown" sec:authorize="hasRole('ROLE_ADMIN') or hasRole('ROLE_NATIONAL_ADMIN')">
<a class="nav-link dropdown-toggle" href="javascript:void(0)" data-toggle="dropdown"><i class="fa fa-cog"></i></a>
@ -84,7 +108,7 @@ fieldset > legend {
<a class="dropdown-item" th:href="@{/logout}">Logout</a>
</div>
</li>
<li class="nav-item"><a class="btn btn-outline-secondary" href="doc">API</a></li>
<li class="nav-item"><a class="btn btn-outline-secondary" href="doc" target="_blank">API</a></li>
</ul>
</div>
@ -98,6 +122,12 @@ fieldset > legend {
<script sec:authorize="!hasRole('ROLE_ADMIN')">
function superAdminMode() { return false; }
</script>
<script sec:authorize="hasRole('ROLE_ADMIN') or hasRole('ROLE_NATIONAL_ADMIN')">
function adminMode() { return true; }
</script>
<script sec:authorize="!hasRole('ROLE_ADMIN') and !hasRole('ROLE_NATIONAL_ADMIN')">
function adminMode() { return false; }
</script>
<script src="resources/js/jquery-3.4.1.min.js"></script>
<script src="resources/js/popper.min.js"></script>
@ -106,6 +136,80 @@ fieldset > legend {
<script src="resources/js/angular-route.min.js"></script>
<script src='resources/js/checklist-model.js'></script>
<script src="resources/js/organizations.js"></script>
<script src="resources/js/spin.js"></script>
<script>
// Spinner show/hide methods ~ Andrea Mannocci
var spinnerOpts = {
lines: 15,
length: 16,
width: 5,
radius: 25,
color: '#eeeeee',
className: 'spinner',
top: '40%'
};
var spinnerTarget = document.getElementById('spinnerdiv');
var spinner;
function showSpinner() {
spinner = new Spinner(spinnerOpts).spin(spinnerTarget);
spinnerTarget.style.visibility = 'visible';
}
function hideSpinner() {
spinnerTarget.style.visibility = 'hidden';
spinner.stop();
}
function call_http_get($http, url, onSuccess) {
showSpinner();
$http.get(url).then(function successCallback(res) {
hideSpinner();
if((typeof res.data) == 'string') {
alert("Session expired !"); location.reload(true);
} else {
onSuccess(res);
}
}, function errorCallback(res) {
hideSpinner();
alert('ERROR: ' + res.data.error + ' (' + res.data.message + ')');
});
}
function call_http_post($http, url, obj, onSuccess) {
showSpinner();
$http.defaults.headers.post["Content-Type"] = "application/json;charset=UTF-8";
$http.post(url, obj).then(function successCallback(res) {
hideSpinner();
if ((typeof res.data) == 'string') {
alert("Session expired !"); location.reload(true);
} else {
onSuccess(res);
}
}, function errorCallback(res) {
hideSpinner();
alert('ERROR: ' + res.data.error + ' (' + res.data.message + ')');
});
}
function call_http_delete($http, url, onSuccess) {
showSpinner();
$http.delete(url).then(function successCallback(res) {
hideSpinner();
if ((typeof res.data) == 'string') {
alert("Session expired !"); location.reload(true);
} else {
onSuccess(res);
}
}, function errorCallback(res) {
hideSpinner();
alert('ERROR: ' + res.data.error + ' (' + res.data.message + ')');
});
}
</script>
</body>
</html>