Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve contact parsing in the EmlImportService and added tests #260

Draft
wants to merge 17 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ dependencies {
testImplementation "org.grails:grails-gorm-testing-support"
testImplementation "org.mockito:mockito-core"
testImplementation "org.grails:grails-web-testing-support"
testImplementation "org.grails:grails-testing-support"
testImplementation "org.spockframework:spock-core"
// testImplementation "org.grails.plugins:geb"
// testImplementation "org.seleniumhq.selenium:selenium-remote-driver:$seleniumVersion"
// testImplementation "org.seleniumhq.selenium:selenium-api:$seleniumVersion"
Expand Down Expand Up @@ -175,6 +177,10 @@ assets {
}

test {
testLogging.showStandardStreams = true
useJUnitPlatform()
include '**/*Spec.*'
testLogging {
events "PASSED", "FAILED", "SKIPPED"
showStandardStreams = true
}
}

14 changes: 13 additions & 1 deletion grails-app/domain/au/org/ala/collectory/Contact.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ class Contact implements Serializable {
String mobile
String email
String fax
String organizationName
String positionName
String userId
String notes
boolean publish = true // controls whether the contact is listed on web site

Expand All @@ -31,6 +34,9 @@ class Contact implements Serializable {
mobile(nullable: true, maxSize:45)
email(nullable: true, maxSize:128, email: true)
fax(nullable: true, maxSize:45)
organizationName(nullable: true, maxSize: 255)
positionName(nullable: true, maxSize: 255)
userId(nullable: true, maxSize: 255)
notes(nullable: true, maxSize: 1024)
publish()
userLastModified(maxSize:256)
Expand Down Expand Up @@ -92,8 +98,14 @@ class Contact implements Serializable {
String buildName() {
if (lastName)
return [(title ?: ''), (firstName ?: ''), lastName].join(" ").trim()
else if (organizationName)
return organizationName
else if (positionName)
return positionName
else if (email)
return email
else if (userId)
return userId
else if (phone)
return phone
else if (mobile)
Expand All @@ -114,6 +126,6 @@ class Contact implements Serializable {
* Note stupid name because Grails seems to object to isEmpty thinking there should be an empty property.
*/
boolean hasContent() {
lastName || phone || mobile || email || fax
lastName || phone || mobile || email || fax || organizationName || positionName
}
}
33 changes: 33 additions & 0 deletions grails-app/migrations/changelog.xml
Original file line number Diff line number Diff line change
Expand Up @@ -460,4 +460,37 @@ liquibase.command.referencePassword: XXXXX
</addColumn>
</changeSet>

<changeSet author="vjrj" id="20250127-01">
<preConditions onFail="MARK_RAN">
<not>
<columnExists tableName="contact" columnName="organization_name" />
</not>
</preConditions>
<addColumn tableName="contact">
<column name="organization_name" type="VARCHAR(255)"/>
</addColumn>
</changeSet>

<changeSet author="vjrj" id="20250127-02">
<preConditions onFail="MARK_RAN">
<not>
<columnExists tableName="contact" columnName="position_name" />
</not>
</preConditions>
<addColumn tableName="contact">
<column name="position_name" type="VARCHAR(255)"/>
</addColumn>
</changeSet>

<changeSet author="vjrj" id="20250127-03">
<preConditions onFail="MARK_RAN">
<not>
<columnExists tableName="contact" columnName="user_id" />
</not>
</preConditions>
<addColumn tableName="contact">
<column name="user_id" type="VARCHAR(255)"/>
</addColumn>
</changeSet>

</databaseChangeLog>
163 changes: 103 additions & 60 deletions grails-app/services/au/org/ala/collectory/EmlImportService.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -112,10 +112,10 @@ class EmlImportService {
* @param connParams
* @return
*/
def extractContactsFromEml(eml, dataResource){

def extractContactsFromEml(eml, dataResource) {
def contacts = []
def primaryContacts = []
def processedContacts = []

emlFields.each { name, accessor ->
def val = accessor(eml)
Expand All @@ -124,91 +124,134 @@ class EmlImportService {
}
}

//add contacts...
if (eml.dataset.creator){
eml.dataset.creator.each {
def contact = addOrUpdateContact(it)
if (contact){
def addUniqueContact = { provider ->
def providerName = "${provider.individualName?.givenName?.text()?.trim()}_${provider.individualName?.surName?.text()?.trim()}"
def providerOrg = provider.organizationName?.text()?.trim()
def providerPosition = provider.positionName?.text()?.trim()

def uniqueKey = providerName ?: providerOrg ?: providerPosition
if (uniqueKey && !processedContacts.contains(uniqueKey)) {
def contact = addOrUpdateContact(provider)
if (contact) {
contacts << contact
processedContacts << uniqueKey
return contact
}
}
}

if (eml.dataset.metadataProvider
&& eml.dataset.metadataProvider.electronicMailAddress != eml.dataset.creator.electronicMailAddress){
// EML Schema
// https://eml.ecoinformatics.org/images/eml-party.png

eml.dataset.metadataProvider.each {
def contact = addOrUpdateContact(it)
if (contact){
contacts << contact
}
}
if (eml.dataset.creator) {
eml.dataset.creator.each { addUniqueContact(it) }
}

// Add additional contacts
if (eml.dataset.contact){
if (eml.dataset.contact) {
eml.dataset.contact.each {
def contact = addOrUpdateContact(it)
if (contact){
contacts << contact
def contact = addUniqueContact(it)
if (contact) {
primaryContacts << contact
}
}
}

if (eml.dataset.associatedParty){
eml.dataset.associatedParty.each {
def contact = addOrUpdateContact(it)
if (contact){
contacts << contact
}
}
if (eml.dataset.metadataProvider) {
eml.dataset.metadataProvider.each { addUniqueContact(it) }
}

if (eml.dataset.associatedParty) {
eml.dataset.associatedParty.each { addUniqueContact(it) }
}

[contacts: contacts, primaryContacts: primaryContacts]
}

private def addOrUpdateContact(emlElement) {
def contact = null
if (emlElement.electronicMailAddress && !emlElement.electronicMailAddress.isEmpty()) {
String email = emlElement.electronicMailAddress.text().trim()
contact = Contact.findByEmail(email)
} else if (emlElement.individualName.givenName && emlElement.individualName.surName) {
contact = Contact.findByFirstNameAndLastName(emlElement.individualName.givenName, emlElement.individualName.surName)
} else if (emlElement.individualName.surName) {
// surName is mandatory
contact = Contact.findByLastName(emlElement.individualName.surName)
if (emlElement.individualName?.givenName && emlElement.individualName?.surName) {
contact = Contact.findByFirstNameAndLastName(
emlElement.individualName.givenName.text()?.trim(),
emlElement.individualName.surName.text()?.trim()
)
} else if (emlElement.individualName?.surName) {
contact = Contact.findByLastName(emlElement.individualName.surName.text()?.trim())
} else if (emlElement.organizationName) {
contact = Contact.findByOrganizationName(emlElement.organizationName.text()?.trim())
} else if (emlElement.positionName) {
contact = Contact.findByPositionName(emlElement.positionName.text()?.trim())
}

// Create the contact if it doesn't exist and it's a individualName with email or surName
// to prevent empty contacts (e.g. with emlElement.organizationName only)
boolean hasEmail = emlElement?.electronicMailAddress?.text()?.trim()?.isEmpty() == false
boolean hasName = emlElement?.individualName?.surName?.text()?.trim()?.isEmpty() == false
boolean hasSurName = emlElement?.individualName?.surName?.text()?.trim()?.isEmpty() == false
boolean hasOrg = emlElement?.organizationName?.text()?.trim()?.isEmpty() == false
boolean hasPosition = emlElement?.positionName?.text()?.trim()?.isEmpty() == false
String userId = emlElement.userId?.text()?.trim()
String userIdDirectory = emlElement.userId?.@directory?.text()?.trim()
String userIdUrl = userIdDirectory && userId ? "${userIdDirectory}${userId}" : null

if (!contact && (hasEmail || hasName)) {
if (!contact && (hasSurName || hasOrg || hasPosition)) {
contact = new Contact()
} else {
return null
}

// Update the contact details
contact.firstName = emlElement.individualName.givenName
contact.lastName = emlElement.individualName.surName
// some email has leading/trailing spaces causing the email constrain regexp to fail, lets trim
contact.email = emlElement.electronicMailAddress.text().trim()
contact.setUserLastModified(collectoryAuthService.username())
Contact.withTransaction {
if (contact.validate()) {
contact.save(flush: true, failOnError: true)
return contact
} else {
contact.errors.each {
log.error("Problem creating contact: " + it)
contact.firstName = emlElement.individualName?.givenName?.text()?.trim()
contact.lastName = emlElement.individualName?.surName?.text()?.trim()
contact.organizationName = emlElement.organizationName?.text()?.trim()
contact.positionName = emlElement.positionName?.text()?.trim()
contact.userId = userIdUrl
contact.email = emlElement.electronicMailAddress?.text()?.trim()
contact.phone = emlElement.phone?.text()?.trim()
contact.setUserLastModified(collectoryAuthService.username())

Contact.withTransaction {
if (contact.validate()) {
contact.save(flush: true, failOnError: true)
} else {
log.error("Validation errors creating contact: ${contact.errors}")
return null
}
}
} else if (contact) {
// Update existing contact fields if they differ
boolean updated = false
if (emlElement.phone?.text()?.trim() && emlElement.phone.text().trim() != contact.phone) {
contact.phone = emlElement.phone.text().trim()
updated = true
}
if (emlElement.electronicMailAddress?.text()?.trim() && emlElement.electronicMailAddress.text().trim() != contact.email) {
contact.email = emlElement.electronicMailAddress.text().trim()
updated = true
}
if (emlElement.individualName?.givenName?.text()?.trim() && emlElement.individualName.givenName.text().trim() != contact.firstName) {
contact.firstName = emlElement.individualName.givenName.text().trim()
updated = true
}
if (emlElement.individualName?.surName?.text()?.trim() && emlElement.individualName.surName.text().trim() != contact.lastName) {
contact.lastName = emlElement.individualName.surName.text().trim()
updated = true
}
if (emlElement.organizationName?.text()?.trim() && emlElement.organizationName.text().trim() != contact.organizationName) {
contact.organizationName = emlElement.organizationName.text().trim()
updated = true
}
if (emlElement.positionName?.text()?.trim() && emlElement.positionName.text().trim() != contact.positionName) {
contact.positionName = emlElement.positionName.text().trim()
updated = true
}
if (userIdUrl != contact.userId) {
contact.userId = userIdUrl
updated = true
}
if (updated) {
contact.setUserLastModified(collectoryAuthService.username())
Contact.withTransaction {
if (contact.validate()) {
contact.save(flush: true, failOnError: true)
} else {
log.error("Validation errors updating contact: ${contact.errors}")
return null
}
}
return null
}
}

contact
return contact
}

}
Loading