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

Audit log #1324

Merged
merged 123 commits into from
Apr 22, 2019
Merged

Audit log #1324

merged 123 commits into from
Apr 22, 2019

Conversation

ramonski
Copy link
Contributor

@ramonski ramonski commented Apr 9, 2019

Description of the issue/feature this PR addresses

This PR adds a full Audit Log to SENAITE.

Every time a new object is created or an existing object is modified, a snapshot is taken with the help of senaite.core.supermodel. The object is automatically marked with an IAuditable interface and a new tab "Audit Log" is displayed, where the differences of the current version to the previous version can be analysed.

An upgrade step migrates all existing contents to provide a default audit trail which contains one snapshot per review history entry. However, Analyses are skipped in this process to make the upgrade step run faster.

Current behavior before PR

Log tab only shows workflow changes

Desired behavior after PR is merged

Audit log tab shows full audit trail of the object

H2O-0001 — SENAITE 2019-04-09 16-59-40

--
I confirm I have tested this PR thoroughly and coded it according to PEP8
and Plone's Python styleguide standards.

@ramonski
Copy link
Contributor Author

I think after migrating to the new audit log, we can completely remove the old "Log" tab. Actually we could even provide a mechanism to link to a specific version like we do with the calculations at the moment. Something like this "fakes" an object with a given version:

from senaite.core.supermodel import SuperModel

model = SuperModel(self.context)
model.data = { ... }  # set the internal data dictionary with the snapshot data

Copy link
Member

@xispa xispa left a comment

Choose a reason for hiding this comment

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

Awesome and beautiful work!. I've just left some minor comments.

Agree to remove "Log" tab and display this new view instead.

Also, would be nice to remove the "Log" section from the Analysis "info" popup and display the audit log instead (having to go to the analysis audit log through the url is not easy).

Remember to edit CHANGES.rst. Besides the addition of this audit-log functionality, remember to mention that the auto-save in Sample view has been removed!.

("time", {
"title": _("Time"), "sortable": False}),
("modified", {
"title": _("Date Modified"), "sortable": False}),
Copy link
Member

Choose a reason for hiding this comment

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

As far as I understand, time displays the exact time the snapshot took place, while modified displays the value for the attribute with same name from the object/brain. I think both fields will always have same value (maybe with 1 sec. of difference). I would suggest to display one of the two in the listing (I prefer "modified", but with _("Time") as the column title).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

time comes actually from the review history and is there to simply pass in the whole review history when creating the snapshot. Since no modification takes place in this case, the time shows the timestamp of the workflow transition

("modified", {
"title": _("Date Modified"), "sortable": False}),
("actor", {
"title": _("Actor"), "sortable": False}),
Copy link
Member

Choose a reason for hiding this comment

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

We also need to display the full name of the actor when the modification took place.


:returns: List of snapshots
"""
from bika.lims.subscribers.auditlog import get_storage
Copy link
Member

Choose a reason for hiding this comment

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

Why to not move this import at the top of the class?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I named the method before the same as the import. However, I left it there because I don't like to import from subscribers and this reminds me to refactor that block


# Modification Date
m_date = metadata.get("modified")
item["modified"] = self.to_localized_time(m_date)
Copy link
Member

Choose a reason for hiding this comment

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

See my comment re "time" vs "modified" above. I would only display one of the two

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, but unfortunately no modification takes place on wf transitions...

return

# take a new snapshot
take_snapshot(obj)
Copy link
Member

Choose a reason for hiding this comment

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

I would do the following here:

metadata = dict()
if IObjectEditedEvent.providedBy(event):
    metadata["action"] = "Edit"

# Take a new snapshot
take_snapshot(obj, **metadata)

return

# take a new snapshot
take_snapshot(obj)
Copy link
Member

Choose a reason for hiding this comment

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

I would do:

# take a new snapshot
take_snapshot(obj, action="Created")

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good change, will do that 👍

@xispa
Copy link
Member

xispa commented Apr 10, 2019

[...] Actually we could even provide a mechanism to link to a specific version like we do with the calculations at the moment. Something like this "fakes" an object with a given version:

from senaite.core.supermodel import SuperModel

model = SuperModel(self.context)
model.data = { ... }  # set the internal data dictionary with the snapshot data

Exciting idea! Better in a new PR though!

from bika.lims.api.security import get_user
from bika.lims.api.security import get_roles
from DateTime import DateTime
from bika.lims.interfaces import IAnalysis
Copy link
Member

Choose a reason for hiding this comment

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

Can you move this imports at the top or is there any reason why you have to add them here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same as above. I think the has_snapshots, take_snapshot, is_auditable should live somewhere else...

Copy link
Member

@xispa xispa left a comment

Choose a reason for hiding this comment

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

Thanks for this brave work!

@xispa xispa merged commit 1fa2c09 into master Apr 22, 2019
@xispa xispa deleted the audit-log branch April 22, 2019 21:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Development

Successfully merging this pull request may close these issues.

2 participants