Skip to content

Commit

Permalink
wip doi transitions
Browse files Browse the repository at this point in the history
  • Loading branch information
zzacharo committed Dec 12, 2024
1 parent 4420700 commit e77fb48
Show file tree
Hide file tree
Showing 5 changed files with 179 additions and 57 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,12 @@ export class RDMDepositApiClient extends DepositApiClient {
);
return new DepositApiClientResponse(data, errors);
} catch (error) {
const errorData = error.response.data;
let errorData = error.response.data;
const errors = this.recordSerializer.deserializeErrors(
error.response.data.errors || []
);
// this is to serialize raised error from the backend on publish
if (errors) errorData = errors;
throw new DepositApiClientResponse({}, errorData);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ class ManagedUnmanagedSwitch extends Component {
handleChange = (e, { value }) => {
const { onManagedUnmanagedChange } = this.props;
const isManagedSelected = value === "managed";
const isNoNeedSelected = value === "notNeeded";
const isNoNeedSelected = value === "notneeded";
onManagedUnmanagedChange(isManagedSelected, isNoNeedSelected);
};

Expand Down Expand Up @@ -156,7 +156,7 @@ class ManagedUnmanagedSwitch extends Component {
label={i18next.t("No, I don't need one")}
aria-label={i18next.t("No, I don't need one")}
name="radioGroup"
value="notNeeded"
value="notneeded"
disabled={disabled}
checked={isNoNeedSelected}
onChange={this.handleChange}
Expand Down Expand Up @@ -386,8 +386,7 @@ class CustomPIDField extends Component {
isDraft === true && value?.identifier && value?.provider !== PROVIDER_EXTERNAL
? true
: undefined,
isNoNeedSelected:
isDraft === true && value?.identifier === undefined ? true : undefined,
isNoNeedSelected: undefined,
};
}

Expand Down Expand Up @@ -426,9 +425,10 @@ class CustomPIDField extends Component {
pidType,
field,
record,
doiDefaultSelection,
} = this.props;

let { doiDefaultSelection } = this.props;

const value = field.value || {};
const currentIdentifier = value.identifier || "";
const currentProvider = value.provider || "";
Expand All @@ -443,6 +443,16 @@ class CustomPIDField extends Component {

const hasManagedIdentifier = managedIdentifier !== "";
const hasUnmanagedIdentifier = unmanagedIdentifier !== "";
const doi = record?.pids?.doi?.identifier || "";
const parentDoi = record.parent?.pids?.doi?.identifier || "";

const hasDoi = doi !== "";
const hasParentDoi = parentDoi !== "";
const isDoiCreated = currentIdentifier !== "";

// check if record has parent DOI already minted and if yes, then set the doiDefaultSelection
// to yes
doiDefaultSelection = hasParentDoi ? "yes" : doiDefaultSelection;

const isDraft = record.is_draft;
const _isUnmanagedSelected =
Expand All @@ -454,7 +464,7 @@ class CustomPIDField extends Component {
const _isManagedSelected =
isManagedSelected === undefined
? hasManagedIdentifier ||
(currentIdentifier === "" && doiDefaultSelection === "no")
(currentIdentifier === "" && doiDefaultSelection === "no") // i.e pids: {}
: isManagedSelected;

const _isNoNeedSelected =
Expand All @@ -465,13 +475,10 @@ class CustomPIDField extends Component {
doiDefaultSelection === "not_needed")
: isNoNeedSelected;

const doi = record?.pids?.doi?.identifier || "";
const hasDoi = doi !== "";
const isDoiCreated = currentIdentifier !== "";
const fieldError = getFieldErrors(form, fieldPath);
return (
<>
<Form.Field required error={fieldError}>
<Form.Field required={required || hasParentDoi} error={fieldError}>
<FieldLabel htmlFor={fieldPath} icon={pidIcon} label={fieldLabel} />
</Form.Field>

Expand Down
16 changes: 16 additions & 0 deletions invenio_rdm_records/records/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,22 @@ def get_latest_published_by_parent(cls, parent):
return None
return latest_record

@classmethod
def get_previous_published_by_parent(cls, parent):
"""Get the latest published record for the specified parent record.
It might return None if there is no latest published version i.e not
published yet or all versions are deleted.
"""
with db.session.no_autoflush:
records = cls.get_records_by_parent(parent)
for record in records:
latest_version_index = record.versions.latest_index
if latest_version_index > 1:
if record.versions.index == latest_version_index - 1:
return record
return None


RDMFileRecord.record_cls = RDMRecord

Expand Down
74 changes: 64 additions & 10 deletions invenio_rdm_records/services/components/pids.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
from flask import current_app
from invenio_drafts_resources.services.records.components import ServiceComponent
from invenio_drafts_resources.services.records.uow import ParentRecordCommitOp
from invenio_i18n import lazy_gettext as _
from invenio_records_resources.services.uow import TaskOp

from ..errors import ValidationErrorWithMessageAsList
from ..pids.tasks import register_or_update_pid


Expand Down Expand Up @@ -63,6 +65,57 @@ def delete_draft(self, identity, draft=None, record=None, force=False):

def publish(self, identity, draft=None, record=None):
"""Publish handler."""

def _validate_doi_transition(new_provider, previous_published_provider):
"""."""
ALLOWED_DOI_TRANSITIONS = {
"datacite": {
"allowed_providers": ["datacite"],
"message": _(
"A previous version was published with a locally managed DOI. This version must also use a locally managed DOI."
),
},
"external": {
"allowed_providers": ["external", "not_needed"],
"message": _(
"A previous version was published with an external DOI or without a DOI. You cannot use a locally managed DOI for this version."
),
},
"not_needed": {
"allowed_providers": ["external", "not_needed"],
"message": _(
"A previous version was published with an external DOI or without a DOI. You cannot use a locally managed DOI for this version."
),
},
}

valid_transitions = ALLOWED_DOI_TRANSITIONS.get(
previous_published_provider, {}
)
if new_provider not in valid_transitions.get("allowed_providers", []):
raise ValidationErrorWithMessageAsList(
message=[
{
"field": "pids.doi",
"messages": [valid_transitions.get("message")],
}
]
)

def _validate_optional_doi():
# Validate the draft PIDs
previous_published = record.get_previous_published_by_parent(record.parent)
# Check only for previously published records
if previous_published:
# check the that pid
previous_published_pids = previous_published.get("pids", {})
doi_pid = [pid for pid in draft_pids.values() if "doi" in draft_pids]
previous_published_provider = previous_published_pids.get(
"doi", {}
).get("provider", "not_needed")
new_provider = "not_needed" if not doi_pid else doi_pid[0]["provider"]
_validate_doi_transition(new_provider, previous_published_provider)

# ATTENTION: A draft can be for both an unpublished or published
# record. For an unpublished record, we usually simply need to create
# and reserve all PIDs. For a published record, some PIDs may allow
Expand All @@ -75,12 +128,16 @@ def publish(self, identity, draft=None, record=None):
record_schemes = set(record_pids.keys())
required_schemes = set(self.service.config.pids_required)

# if a doi was ever minted for the parent record then we always require one
# for any version of the record that will be published
if "doi" in draft.parent.pids:
required_schemes.add("doi")
# if DOI is not required in an instance check validate allowed providers
# for each record version
if "doi" not in required_schemes:
# if a doi was ever minted for the parent record then we always require one
# for any version of the record that will be published
if draft.parent.get("pids", {}).get("doi"):
required_schemes.add("doi")

_validate_optional_doi()

# Validate the draft PIDs
self.service.pids.pid_manager.validate(draft_pids, draft, raise_errors=True)

# Detect which PIDs on a published record that has been changed.
Expand Down Expand Up @@ -136,10 +193,7 @@ def new_version(self, identity, draft=None, record=None):
"""A new draft should not have any pids from the previous record."""
# This makes the draft use the same identifier as the previous
# version
if record.pids.get("doi", {}).get("provider") == "external":
draft.pids = {"doi": {"provider": "external", "identifier": ""}}
else:
draft.pids = {}
draft.pids = {}

def edit(self, identity, draft=None, record=None):
"""Add current pids from the record to the draft.
Expand Down Expand Up @@ -178,7 +232,7 @@ def publish(self, identity, draft=None, record=None):
required_schemes = set(self.service.config.parent_pids_required)

# if parent DOI is not required in the config, but record DOI is created, we need to create parent DOI as well
if "doi" in draft.pids:
if draft.get("pids", {}).get("doi"):
required_schemes.add("doi")

conditional_schemes = self.service.config.parent_pids_conditional
Expand Down
Loading

0 comments on commit e77fb48

Please sign in to comment.