Skip to content

Ensure action is always set when records are saved#12058

Closed
jimchamp wants to merge 4 commits into
internetarchive:masterfrom
jimchamp:actions
Closed

Ensure action is always set when records are saved#12058
jimchamp wants to merge 4 commits into
internetarchive:masterfrom
jimchamp:actions

Conversation

@jimchamp

@jimchamp jimchamp commented Mar 9, 2026

Copy link
Copy Markdown
Collaborator

Blocker: Let's please settle on a naming convention for actions before merging. Will require discussion.

Addresses #11955

Updates all save, save_many, and Thing._save calls to include a valid and accurate action.

We may also want to update the default save and save_many values in Infogami. This PR does that. When merged, we can check the /recentchanges views for the default types to determine if any transaction needs an action.

Technical

Testing

Screenshot

Stakeholders

Copilot AI review requested due to automatic review settings March 9, 2026 19:00

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR standardizes write operations across the codebase by ensuring action is supplied to save, save_many, and Thing._save calls, improving changeset classification/auditing.

Changes:

  • Added explicit action= values to many save/save_many/_save call sites (scripts + plugins).
  • Updated User.set_data to require an action and forward it to _save.
  • Refined list and import-related actions to be more specific (though some mappings/consumers may need updating).

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
scripts/update_stale_ocaid_references.py Adds an explicit bulk redaction action to save_many.
scripts/copydocs.py Adds an action to batch transfers (but currently breaks non-OL dest implementations).
openlibrary/plugins/upstream/covers.py Adds actions for updating covers/photos via _save.
openlibrary/plugins/upstream/code.py Adds an explicit action for revert saves.
openlibrary/plugins/upstream/addtag.py Adds explicit tag delete/update actions.
openlibrary/plugins/upstream/addbook.py Adds actions to several author-related saves; introduces a placeholder delete action needing finalization.
openlibrary/plugins/openlibrary/lists.py Replaces generic lists action with more specific list/series actions.
openlibrary/plugins/openlibrary/code.py Adds action for sampleload, add-author, and YAML updates (one formatting issue present).
openlibrary/plugins/openlibrary/bulk_tag.py Adds an explicit bulk tagging action.
openlibrary/plugins/openlibrary/api.py Adds actions for bulk delete and edition updates.
openlibrary/plugins/admin/code.py Adds actions for spam reverts / permission updates; leaves one _save() without action in stats POST.
openlibrary/olbase/events.py Adds a clarifying comment around subevent triggering.
openlibrary/core/models.py Requires action for User.set_data and adds actions for usergroup member changes + tag creation.
openlibrary/catalog/add_book/init.py Changes import save_many action selection (currently acknowledged as inaccurate and may affect changeset classification).
openlibrary/accounts/model.py Updates anonymization to pass an action into set_data.
Comments suppressed due to low confidence (1)

openlibrary/plugins/openlibrary/lists.py:742

  • action="update-list-items" is a new changeset kind for list edits; currently ListChangeset is only registered for kinds lists and series. If you keep this more granular action naming, you’ll likely want to register update-list-items (and delete-list, etc.) to ListChangeset so list change rendering/logic continues to work as before.
        return lst._save(comment="Updated list.", action="update-list-items", data=changeset_data)


class list_seed_yaml(list_seeds):
    encoding = "yml"

Comment thread scripts/copydocs.py Outdated
for group in web.group(docs, 50):
try:
print(dest.save_many(group, comment=comment))
print(dest.save_many(group, comment=comment, action="copydocs-transfer"))

Copilot AI Mar 9, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

copy() supports writing to a Disk destination (and tests use FakeServer), but those save_many implementations only accept (docs, comment=...). Passing action= here will raise TypeError. Consider either updating Disk.save_many/test doubles to accept an optional action (or **kwargs) and ignore it, or only passing action when dest is an OpenLibrary instance.

Copilot uses AI. Check for mistakes.
Comment thread openlibrary/plugins/openlibrary/code.py
Comment on lines 712 to 715
doc = web.ctx.site.new(key, {"key": key, "type": {"key": "/type/delete"}})
doc._save(comment=comment)
# TODO : pull type from key, then compose `delete-{type}` action string
doc._save(comment=comment, action="delete-?")

Copilot AI Mar 9, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

action="delete-?" looks like a placeholder and will leak into production changesets/recentchanges. Please replace with a real, stable action value (e.g. based on the doc type/key) before merging.

Copilot uses AI. Check for mistakes.
Comment thread openlibrary/catalog/add_book/__init__.py
Comment on lines +399 to 404
action = "add-series" if list_type_plural == "series" else "add-list"
web.ctx.site.save_many(
records_to_save,
action="lists",
action=action,
comment=web.input(_comment="")._comment or None,
)

Copilot AI Mar 9, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These new list/series action values (add-list/add-series) won’t match the currently registered changeset kinds for lists (lists and series in openlibrary/core/lists/model.py). That likely means list-related changesets will no longer be parsed/rendered as ListChangeset. Either keep the existing action values, or update the changeset registration to include the new action strings.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's unclear to me if this will affect anything, aside from the /recentchanges/lists page(which will not load for me).

When this change is applied, one will need to visit /recentchanges/add-list to see newly added lists.

Comment on lines 515 to 522
def POST(self, today):
"""Update stats for today."""
doc = self.get_stats(today)
# This seems like it would save a Thing
# ...but, I don't know if this endpoint is in use today.
# I suspect not....
doc._save()
raise web.seeother(web.ctx.path)

Copilot AI Mar 9, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This endpoint still calls doc._save() without an action, which conflicts with the PR goal of ensuring saves always provide an action for audit/recentchanges classification. Please pass an explicit action here (even if the endpoint is rarely used) or remove the save if it’s truly obsolete.

Copilot uses AI. Check for mistakes.
Comment thread scripts/copydocs.py Outdated
Comment on lines +185 to +186
# Must check `book.key` for record type, if needed
book._save('Update covers', action="update-cover")

@jimchamp jimchamp Mar 9, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# Must check `book.key` for record type, if needed
book._save('Update covers', action="update-cover")
book._save('Update covers', action="add-cover")

This one is tricky, as (I believe) covers can be added and removed by this handler.

How would these render in /recentchanges/add-cover if a cover were removed? Would it break the page?

raise web.redirect(web.ctx.path)
edition.set_identifiers(data)
saveutil.save(edition)
# `action` : edit-book-isbn?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# `action` : edit-book-isbn?

saveutil.save(edition)

comment = utils.get_message("comment_add_book")
# XXX : creates both work and edition

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# XXX : creates both work and edition

Using book as the record type is probably fine for cases where either a work or edition record is being updated.

Comment on lines +140 to +141
# `action` is always being passed to `commit` today, but maybe not tomorrow?
# Should `action` be a positional argument for this method?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# `action` is always being passed to `commit` today, but maybe not tomorrow?
# Should `action` be a positional argument for this method?

Reviewers: Should action be a required positional parameter for this commit method?

self.docs = []

def save(self, doc) -> None:
def save(self, doc) -> None: # total misnomer -- nothing is saved in the method

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def save(self, doc) -> None: # total misnomer -- nothing is saved in the method
def save(self, doc) -> None:

Out-of-scope for this PR.


web.ctx.site.save_many(docs_to_update, comment="Bulk tagging works")
web.ctx.site.save_many(
docs_to_update, comment="Bulk tagging works", action="bulk-update-work-tags"

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe bulk-update-tags is better here?

if not data["source_records"]:
del data["source_records"]
web.ctx.site.save(data, "Remove OCAID: Item no longer available to borrow.")
web.ctx.site.save(data, "Remove OCAID: Item no longer available to borrow.", action="update-edition")

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
web.ctx.site.save(data, "Remove OCAID: Item no longer available to borrow.", action="update-edition")
web.ctx.site.save(data, "Remove OCAID: Item no longer available to borrow.", action="update-book")

Generalize Edition and Work records as book.


web.ctx.site.save_many(delete_payload, comment)
# this deletes editions and works
web.ctx.site.save_many(delete_payload, comment, action="bulk-delete-book-records")

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
web.ctx.site.save_many(delete_payload, comment, action="bulk-delete-book-records")
web.ctx.site.save_many(delete_payload, comment, action="bulk-delete-books")

#
# I suspect that we'd want to filter these out from the
# `/recentchanges` page.
page._save("updated blocked IPs", action="block-ips")

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe update-blocked-ips is better here? Either way, this probably shouldn't be publicly accessible via /recentchanges, which will require other changes.

Comment on lines +749 to +751
# This is still wrong -- the `edits` list can contain works, authors,
# and editions. Some records may be new, and some may be existing.
# XXX : bulk-*

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# This is still wrong -- the `edits` list can contain works, authors,
# and editions. Some records may be new, and some may be existing.
# XXX : bulk-*

Reviewers: what should be done for this case?

# Clear patron's profile page:
data = {'key': patron.key, 'type': '/type/delete'}
patron.set_data(data)
patron.set_data(data, "delete-profile")

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll likely want to filter these from /recentchanges.

web.ctx.ip = web.ctx.ip or '127.0.0.1'
web.ctx.site.save_many(updated_eds, comment="Redacting ocaids")
web.ctx.site.save_many(
updated_eds, comment="Redacting ocaids", action="bulk-redact-ocaid"

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
updated_eds, comment="Redacting ocaids", action="bulk-redact-ocaid"
updated_eds, comment="Redacting ocaids", action="bulk-edit-books"

I don't know if a bulk-redact-ocaid action is helpful at all. If it is, we'll probably want to exclude these from /recentchanges views.

else:
prev._save("revert to revision %d" % prev.revision)
prev._save(
"revert to revision %d" % prev.revision, action="revert-version"

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to exclude these changes from /recentchanges views?

edition.set_identifiers(data)
saveutil.save(edition)
# `action` : edit-book-isbn?
saveutil.commit(comment="Added an %s identifier." % typ, action="edit-book")

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe update-book is more memorable?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...we shouldn't change this, though...

Should we conform to an edit-{record} pattern when naming actions (e.g. edit-tag or edit-author)? I've been prefixing update- to other such actions, but edit- has precedence.

Comment on lines +713 to +714
# TODO : pull type from key, then compose `delete-{type}` action string
doc._save(comment=comment, action="delete-?")

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewers: What should be done for this case? Do we want the actual type that's being deleted, or would delete-record suffice?

@mekarpeles mekarpeles added the Priority: 1 Do this week, receiving emails, time sensitive, . [managed] label Mar 23, 2026
@mekarpeles mekarpeles self-assigned this Mar 23, 2026
@jimchamp jimchamp added the State: Blocked Work has stopped, waiting for something (Info, Dependent fix, etc. See comments). [managed] label Mar 23, 2026
"Edit by %s, changeset_id=%s, changes=%s", author, changeset["id"], keys
)

# XXX : is this triggering a save or save_many call?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Either document answer or remove comment as necessary (otherwise others will get confused)

@jimchamp jimchamp marked this pull request as draft March 25, 2026 22:33
@jimchamp jimchamp closed this Mar 31, 2026
@jimchamp jimchamp deleted the actions branch April 23, 2026 17:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Priority: 1 Do this week, receiving emails, time sensitive, . [managed] State: Blocked Work has stopped, waiting for something (Info, Dependent fix, etc. See comments). [managed]

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants