Skip to content

Commit

Permalink
add keepers registry annotator, and make some adjustments to how form…
Browse files Browse the repository at this point in the history
…s manage widgets to improve the display
  • Loading branch information
richard-jones committed Apr 28, 2023
1 parent fe6e51c commit 6a89bbb
Show file tree
Hide file tree
Showing 10 changed files with 166 additions and 47 deletions.
116 changes: 80 additions & 36 deletions portality/annotation/annotators/keepers_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,46 +4,48 @@
from portality.annotation.resources.issn_org import ISSNOrg
from typing import Callable
from portality.annotation.annotators.issn_active import ISSNAnnotator
from datetime import datetime


class KeepersRegistry(ISSNAnnotator):
__identity__ = "keepers_registry"

NOT_FOUND = "not_found"
FULLY_VALIDATED = "fully_validated"
NOT_VALIDATED = "not_validated"

def _apply_rule(self, field, value, data, fail, url, logger, annotations):
if value is not None:
if data is None:
if not fail:
logger("{y} not registered at {x}".format(y=field, x=url))
annotations.add_annotation(
field=field,
original_value=value,
advice=self.NOT_FOUND,
reference_url=url,
annotator=self.__identity__
)
ID_MAP = {
"CLOCKSS": "http://issn.org/organization/keepers#clockss",
"LOCKSS": "",
"Internet Archive": "",
"PKP PN": "",
"Portico": ""
}

MISSING = "missing"
PRESENT = "present"
OUTDATED = "outdated"

def _get_archive_components(self, eissn_data, pissn_data):
acs = []
if eissn_data is not None:
acs += eissn_data.archive_components
if pissn_data is not None:
acs += pissn_data.archive_components
return acs

def _extract_archive_data(self, acs):
ad = {}
for ac in acs:
id = ac.get("holdingArchive", {}).get("@id")
tc = ac.get("temporalCoverage")
bits = tc.split("/")
if len(bits) != 2:
continue
end_year = int(bits[1].strip())
if id in ad:
if end_year > ad[id]:
ad[id] = end_year
else:
if data.is_registered():
logger("{y} confirmed as fully validated at {x}".format(y=value, x=url))
annotations.add_annotation(
field=field,
original_value=value,
advice=self.FULLY_VALIDATED,
reference_url=url,
annotator=self.__identity__
)
else:
logger("{y} is not fully validated at {x}".format(y=value, x=url))
annotations.add_annotation(
field=field,
original_value=value,
advice=self.NOT_VALIDATED,
reference_url=url,
annotator=self.__identity__
)
ad[id] = end_year

return ad

def annotate(self, form: dict,
jla: JournalLikeObject,
Expand All @@ -53,5 +55,47 @@ def annotate(self, form: dict,

eissn, eissn_url, eissn_data, eissn_fail, pissn, pissn_url, pissn_data, pissn_fail = self.retrieve_from_source(form, resources, annotations, logger)

self._apply_rule("eissn", eissn, eissn_data, eissn_fail, eissn_url, logger, annotations)
self._apply_rule("pissn", pissn, pissn_data, pissn_fail, pissn_url, logger, annotations)
url = eissn_url if eissn_url else pissn_url

acs = self._get_archive_components(eissn_data, pissn_data)
ad = self._extract_archive_data(acs)
services = form.get("preservation_service", [])
logger("There are {x} preservation services on the record: {y}".format(x=len(services), y=",".join(services)))
for service in services:
id = self.ID_MAP.get(service)
if not id:
continue

coverage = ad.get(id)
if not coverage:
# the archive is not mentioned in issn.org
logger("Service {x} is not registered at issn.org for this record".format(x=service))
annotations.add_annotation(
field="preservation_service",
advice=self.MISSING,
reference_url=url,
context={"service": service},
annotator=self.__identity__
)
continue

if coverage < datetime.utcnow().year - 1:
# the temporal coverage is too old
logger("Service {x} is registerd as issn.org for this record, but the archive is not recent enough".format(x=service))
annotations.add_annotation(
field="preservation_service",
advice=self.OUTDATED,
reference_url=url,
context={"service": service},
annotator=self.__identity__
)
else:
# the coverage is within a reasonable period
logger("Service {x} is registerd as issn.org for this record".format(x=service))
annotations.add_annotation(
field="preservation_service",
advice=self.PRESENT,
reference_url=url,
context={"service": service},
annotator=self.__identity__
)
4 changes: 4 additions & 0 deletions portality/annotation/resources/issn_org.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,7 @@ def version(self):

def is_registered(self):
return self.version == "Register"

@property
def archive_components(self):
return [ac for ac in self.data.get("subjectOf", []) if ac.get("@type") == "ArchiveComponent"]
4 changes: 3 additions & 1 deletion portality/bll/services/annotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
from portality import models

from portality.annotation.annotators.issn_active import ISSNActive
from portality.annotation.annotators.keepers_registry import KeepersRegistry

ANNOTATION_PLUGINS = [
# (journal, application, plugin)
(True, True, ISSNActive)
(True, True, ISSNActive),
(True, True, KeepersRegistry)
]

class AnnotationsService(object):
Expand Down
9 changes: 8 additions & 1 deletion portality/forms/application_forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -1196,7 +1196,14 @@ class FieldDefinitions:
},
"validate": [
{"required": {"message": "Select <strong>at least one</strong> option"}}
]
],
"contexts" : {
"admin": {
"widgets": [
"annotation", # ~~^-> Annotation:FormWidget~~
]
}
}
}

# ~~->$ PreservationServiceLibrary:FormField~~
Expand Down
14 changes: 11 additions & 3 deletions portality/forms/application_processors.py
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ def finalise(self, account, save_target=True, email_alert=True, id=None):
# FIXME: imports are delayed because of a circular import problem buried in portality.decorators
from portality.tasks.application_annotations import ApplicationAnnotations
from portality.tasks.helpers import background_helper
background_helper.execute_by_bg_task_type(ApplicationAnnotations,
background_helper.submit_by_bg_task_type(ApplicationAnnotations,
application=self.target.id,
status_on_complete=constants.APPLICATION_STATUS_PENDING)

Expand Down Expand Up @@ -699,8 +699,8 @@ def finalise(self, save_target=True, email_alert=True):
# if we are allowed to finalise, kick this up to the superclass
super(PublisherUpdateRequest, self).finalise()

# set the status to update_request (if not already)
self.target.set_application_status(constants.APPLICATION_STATUS_UPDATE_REQUEST)
# set the status to post submission review (will be updated again later after the review job runs)
self.target.set_application_status(constants.APPLICATION_STATUS_POST_SUBMISSION_REVIEW)

# Save the target
self.target.set_last_manual_update()
Expand All @@ -725,6 +725,14 @@ def finalise(self, save_target=True, email_alert=True):
else:
self.target.remove_current_journal()

# Kick off the post-submission review
# FIXME: imports are delayed because of a circular import problem buried in portality.decorators
from portality.tasks.application_annotations import ApplicationAnnotations
from portality.tasks.helpers import background_helper
background_helper.submit_by_bg_task_type(ApplicationAnnotations,
application=self.target.id,
status_on_complete=constants.APPLICATION_STATUS_UPDATE_REQUEST)

# email the publisher to tell them we received their update request
if email_alert:
try:
Expand Down
22 changes: 18 additions & 4 deletions portality/models/annotation.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
from portality.lib import es_data_mapping
from portality.core import app

import json


ANNOTATION_STRUCT = {
"fields": {
"id": {"coerce": "unicode"},
Expand All @@ -27,7 +30,8 @@
"advice": {"coerce": "unicode"},
"reference_url": {"coerce": "unicode"},
"annotator": {"coerce": "unicode"},
"dismissed": {"coerce": "bool"}
"dismissed": {"coerce": "bool"},
"context": {"coerce": "unicode"}
},
"lists": {
"suggested_value": {"contains": "field", "coerce": "unicode"}
Expand Down Expand Up @@ -99,7 +103,7 @@ def journal(self):
def journal(self, val):
self.__seamless__.set_with_struct("journal", val)

def add_annotation(self, field=None, original_value=None, suggested_value=None, advice=None, reference_url=None, annotator=None):
def add_annotation(self, field=None, original_value=None, suggested_value=None, advice=None, reference_url=None, context=None, annotator=None):
obj = {}
if field is not None:
obj["field"] = field
Expand All @@ -113,6 +117,8 @@ def add_annotation(self, field=None, original_value=None, suggested_value=None,
obj["reference_url"] = reference_url
if annotator is not None:
obj["annotator"] = annotator
if context is not None:
obj["context"] = json.dumps(context)

# ensure we add the annotation only once
exists = self.__seamless__.exists_in_list("annotations", val=obj)
Expand All @@ -124,17 +130,25 @@ def add_annotation(self, field=None, original_value=None, suggested_value=None,

@property
def annotations(self):
annos = self.__seamless__.get_list("annotations")
for anno in annos:
if "context" in anno:
anno["context"] = json.loads(anno["context"])
return annos

@property
def annotations_raw(self):
return self.__seamless__.get_list("annotations")

def dismiss(self, annotation_id):
annos = self.annotations
annos = self.annotations_raw
for anno in annos:
if anno.get("id") == annotation_id:
anno["dismissed"] = True
break

def undismiss(self, annotation_id):
annos = self.annotations
annos = self.annotations_raw
for anno in annos:
if anno.get("id") == annotation_id:
del anno["dismissed"]
Expand Down
30 changes: 29 additions & 1 deletion portality/static/js/annotators.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,36 @@ doaj.annotators.ISSNActive = class {
}
}

doaj.annotators.KeepersRegistry = class {
MESSAGES = {
"missing": "{service} is missing from the Keepers Registry record at ISSN.org",
"present": "{service} is present and current in the Keepers Registry record at ISSN.org",
"outdated": "{service} is not currently being used according to the Keepers Registry record as ISSN.org"
}

ICONS = {
"missing": "x-circle",
"present": "check-circle",
"outdated": "x-circle"
}

draw(annotation) {
let icon = this.ICONS[annotation.advice];
let message = this.MESSAGES[annotation.advice];

let context = JSON.parse(annotation.context);
message = message.replace("{service}", context.service);

let frag = `<span data-feather="${icon}" aria-hidden="true"></span>
${message} (see
<a href="${annotation.reference_url}">${annotation.reference_url})</a>`;
return frag;
}
}

doaj.annotators.registry = {
"issn_active": doaj.annotators.ISSNActive
"issn_active": doaj.annotators.ISSNActive,
"keepers_registry": doaj.annotators.KeepersRegistry
}

doaj.annotators.DismissedAnnotations = class {
Expand Down
7 changes: 6 additions & 1 deletion portality/static/js/formulaic.js
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,11 @@ var formulaic = {
var selector = "." + this.containerClassTemplate.replace("{name}", name);
return $(selector, context);
};

this.widgetsContainer = function(params) {
var context = this.get_context(params);
return $("#" + params.name + "_widgets-container", context)
}
},

edges : {
Expand Down Expand Up @@ -1088,7 +1093,7 @@ var formulaic = {
this.init = function() {
// first, remove any existing annotations, leaving the container intact
let listSelector = edges.css_class_selector(this.namespace, "annotations");
let elements = this.form.controlSelect.input({name: this.fieldDef.name});
let elements = this.form.controlSelect.widgetsContainer({name: this.fieldDef.name});
let existing = false;
for (let i = 0; i < elements.length; i++) {
let el = $(elements[i]);
Expand Down
4 changes: 4 additions & 0 deletions portality/templates/application_form/_field.html
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@
</div>
{% endif %}

{% if f.widgets %}
<div id="{{ f.name }}_widgets-container" class="widgets-container"></div>
{% endif %}

</div>

{% include "application_form/modal.html" %}
3 changes: 3 additions & 0 deletions portality/templates/application_form/maned_application.html
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,7 @@ <h3 id="modalLabel-quick_reject" class="modal__title">
<script type="text/javascript" src="/static/js/annotators.js?v={{config.get('DOAJ_VERSION')}}"></script>
{% endif %}
{% include "application_form/js/_form_js.html" %}
<script type="application/javascript">
doaj.annotators.dismissed = new doaj.annotators.DismissedAnnotations({})
</script>
{% endblock %}

0 comments on commit 6a89bbb

Please sign in to comment.