add(load): EP approval record migration stream#541
Conversation
4619216 to
dc83813
Compare
cf8176b to
6de53c6
Compare
| "mint_legacy_recid": True, | ||
| "save_original_dump": True, | ||
| "clc_sync": True, | ||
| "record_state": True, |
There was a problem hiding this comment.
what does that mean? to generate or not the record state?
There was a problem hiding this comment.
it means we'll not add the restricted record to record_state_logger, i can change the variable name if it's confusing
There was a problem hiding this comment.
why would we have to skip a restricted one to the state logger?
There was a problem hiding this comment.
this is due to the EP migration. Legacy CDS with EP approval is one public record which in the new system will become 2: one restricted internal + one public with the EP number. The latter will be used for redirection of the legacy one.
There was a problem hiding this comment.
I think we discussed that it will change in the end, right?
| "Can also be set per-collection in streams.yaml under transform.workers." | ||
| ), | ||
| ) | ||
| @click.option( |
There was a problem hiding this comment.
I explained in the PR description
#541 (comment)
invenio migration run --collection <collection-name> --ep-approval --dry-run
There was a problem hiding this comment.
please remember to create a recipe following the other migrations we have done or doing https://gitlab.cern.ch/cds-team/cds-rdm-openshift/-/work_items?show=eyJpaWQiOiIxNSIsImZ1bGxfcGF0aCI6ImNkcy10ZWFtL2Nkcy1yZG0tb3BlbnNoaWZ0IiwiaWQiOjM3NTE2MX0%3D and take it into account there
| # Dump the computed record state. This is useful to migrate then the record stats | ||
| if record_state_context: | ||
| self.record_state_logger.add_record_state(record_state_context) | ||
| if self._should_log_record_state(): |
There was a problem hiding this comment.
are there cases when we shouldn't log record state? what cases are that?
There was a problem hiding this comment.
We'll have 2 cds-rdm records and we want to use the public one for stats migration and redirections, that's why i'm setting it False if it's the restricted/draft record
There was a problem hiding this comment.
an alternative would be to log every record and introduce a special attribute for the internal EP that then is not taken into account on stats and redirections. The only advantage with this is that record_state depicts the full picture of the migration
There was a problem hiding this comment.
I added `internal record ` to the log, stats and redirections doesnt effected. But comments migration needs to change because it uses legacy_recid to get the migrated record. @kpsherva
dedbdb7 to
7b9b117
Compare
7b9b117 to
2bcb422
Compare
palkerecsenyi
left a comment
There was a problem hiding this comment.
I don't yet have a huge amount of understanding of the migrator kit in general, but it looks good to me! Just two questions, but I can't see any major problems
| self._resolve_user_by_email(approved.get("submitted_by"), "approver") | ||
|
|
||
| # Check if record approved after the deadline | ||
| waiting_deadline = self._parse_legacy_datetime(waiting.get("deadline")) |
There was a problem hiding this comment.
What is this deadline? Is this the date after which if EP has not responded it gets auto-approved?
There was a problem hiding this comment.
It's not auto-approved, it gives permission to the user requested the ep approval to approve. so it goes back to requester. For migration i'm just adding the deadline to the request. we'll not need this feature
| data={ | ||
| "title": f'EP approval for "{publication_title}"', | ||
| # Use the default | ||
| "payload": {}, |
There was a problem hiding this comment.
There was a problem hiding this comment.
I checked the legacy code, these info only used in emails in legacy so we dont have this information in DB or record marc
| field="validation", | ||
| stage="load", | ||
| recid=recid, | ||
| priority="warning", |
There was a problem hiding this comment.
I think if there is an unknown error then it should be critical not only warning
| "record_state": True, | ||
| } | ||
|
|
||
| def _restricted_load_flags(self): |
There was a problem hiding this comment.
it doesn't need to be a method, it is used only in one place - I would prefer if you place it in load split record, it will be better for readability of the code. Same for the method above - it only defines the dictionary that is used in one place
|
|
||
| self._ep_approval_parsed = (waiting_entry, approved_entry, report_number) | ||
|
|
||
| def _create_ep_approval(self, restricted_record_state, legacy_recid): |
There was a problem hiding this comment.
this method should be moved to ApprovalRequest class
| self._finalise_on_load = finalise | ||
| return super()._load(entry) | ||
|
|
||
| def _validate_ep_approval(self, ep_approval, legacy_recid): |
There was a problem hiding this comment.
this method should be moved to ApprovalRequest class
| ) | ||
| return {"user": str(user.id)} | ||
|
|
||
| def _existing_ep_approval_request(self, legacy_recid): |
There was a problem hiding this comment.
this method should be moved to ApprovalRequest class
| continue | ||
| return None | ||
|
|
||
| def _get_ep_referee_group(self, restricted_parent): |
There was a problem hiding this comment.
this method should be moved to ApprovalRequest class
| EP_APPROVAL_REPORT_NUMBER_RE = re.compile(r"^CERN-EP-\d{4}-\d{3}$") | ||
|
|
||
|
|
||
| class CDSEPApprovalRecordServiceLoad(CDSRecordServiceLoad): |
There was a problem hiding this comment.
| class CDSEPApprovalRecordServiceLoad(CDSRecordServiceLoad): | |
| class CDSEPApprovalRecordServiceLoad(Load): |
e4d2f5f to
52c508e
Compare
| self.approval_request = ApprovalRequest( | ||
| ep_approval=ep_approval, | ||
| legacy_recid=recid, | ||
| title=metadata.get("title"), | ||
| resource_type=metadata.get("resource_type"), | ||
| dry_run=self.dry_run, | ||
| ) | ||
| self.approval_request.validate() |
There was a problem hiding this comment.
I would move these lines just before where you call .create for readability
There was a problem hiding this comment.
When I split the entries i need the validated approval request
| """Return waiting/approved history entries and the report number.""" | ||
| if len(self.ep_approval) != 2: | ||
| raise UnexpectedValue( | ||
| message="EP approval history has more/less than 2 entries", |
There was a problem hiding this comment.
what does that mean in practice if it has 2 entries?
There was a problem hiding this comment.
we should have waiting and accepted entries. if we are missing it should raise, now it doesnt support only waiting state
| return ep_config["referee_group"] | ||
|
|
||
| @staticmethod | ||
| def _resolve_user_by_email(email, role): |
There was a problem hiding this comment.
do we always have emails of referees?
| expires_at = self.parse_legacy_datetime(self.waiting_entry.get("deadline")) | ||
| referee_group = self._get_referee_group(restricted_parent) | ||
|
|
||
| with UnitOfWork() as uow: |
There was a problem hiding this comment.
I change the request after create, and i think uow is useful so create doesn’t commit early, so all those changes can be registered and committed together. you think it's not needed?
| self.migration_logger.add_log(exc, record=entry) | ||
|
|
||
| def _link_ep_approval_records( | ||
| self, restricted_record_state, public_record_state, legacy_recid |
There was a problem hiding this comment.
this method is mixing two actions - adding the request to parent of restricted record and public record and linking them together. I would rename this method as "add_request_to_parents" and move the related_identifier creation to have them right after the records are created.
| ] = communities | ||
|
|
||
| def _should_remove_ep_report_number(self, identifier, public_split): | ||
| """Return whether an EP report number should be stripped from metadata.""" |
There was a problem hiding this comment.
I am a bit confused why this check is needed - you will remove the EP report number only from the restricted_entry when you are preparing the metadata, right? so you know already that you should remove it because you are preparing the restricted entry
There was a problem hiding this comment.
it should be apprn not cdsrn, so i'm removing from both records. Usually there's a draft report number like CERN-EP-DRAFT-FASER-2024-001 and we need to keep that one in the restricted record
| public_entry = self._split_entry(entry, include_epphapp=False) | ||
| restricted_entry = self._split_entry(entry, include_epphapp=True) |
There was a problem hiding this comment.
ok sorry for digging deeper on the pattern but I see the opportunity here to encapsulate the responsibilities a bit futher
| public_entry = self._split_entry(entry, include_epphapp=False) | |
| restricted_entry = self._split_entry(entry, include_epphapp=True) | |
| public_entry = PublicEntry(entry, include_epphapp=False).build() | |
| restricted_entry = RestrictedEntry(entry, include_epphapp=True).build() |
where
class MetadataEntry()
def identifiers(entry):
raise NotImplementedError
def build():
return {
"metadata": {
"identifiers": self.idenfifiers(entry)
... and any other key of the entry that you need to modify based on if this is restricted record or not
}
}
class PublicEntry(MetadataEntry):
# all the dict manipulation for the public record go here, like adding cern research community
def __init__():
def identifiers():
# you probably keep the identifiers unchanged here
return identifiers
class RestrictedEntry(MetadataEntry):
# all the dict manipulations needed for the restricted record go here
def identifiers():
# you remove the ep report number here
return identifersplease let me know if we need to discuss the details in person
52c508e to
488a3ad
Compare
1877efc to
f46917b
Compare
| class PublicEntry(MetadataEntry): | ||
| """Build the public EP approval split entry.""" | ||
|
|
||
| def _build_versions(self, split): |
There was a problem hiding this comment.
are there any similarities or duplications we could make general for both public and restricted entry ?
ad8d63a to
2bffd96
Compare

needs https://github.com/CERNDocumentServer/cds-rdm/tree/feature/ep-approval
This PR covers the migration of EP approved records.
We'll have a new loader (
CDSEPApprovalRecordServiceLoad) for records that went through EP approval. It splits one legacy record into two RDM records, creates the EP approval request with the original history (submitter, approver, dates, report number), and links everything together with related identifiers and an APPRN.Metadata split
CERN-EP-YYYY-*): removed from both splits before load. After the request is created, the APPRN is minted on the restricted record and added to the public record metadata.CERN-EP-*): removed from public record before load.Files split
EPPHAPP_FILE(restricted file for EP) files only if record has the restricted file, otherwise use all.Loader
The loader creates 2 records and links them via:
isversionof/isvariantformof)How to run
Records with
ep_approvalcannot use the standard record stream it'll raise a migration error. They must be migrated with--ep-approval.Result
For now this only handles records with a approved EP approval history (exactly one
waitingand oneapprovedentry).Questions
waitingorrejectedstate, we would need a different split/load mechanism. Is this a case?We'll migrate the accepted state records.
EPPHAPP_FILEhttps://cds.cern.ch/record/2864686/files/