Skip to content

Show audit changes in words: one line per list element, named - #999

Merged
KrzysztofPajak merged 6 commits into
developfrom
feature/audit-readable-changes
Oct 30, 2026
Merged

KrzysztofPajak merged 6 commits into
developfrom
feature/audit-readable-changes

Conversation

@KrzysztofPajak

@KrzysztofPajak KrzysztofPajak commented Oct 30, 2026 •

Copy link
Copy Markdown
Member

Type: feature

Issue

Follow-up to #998. The change history still read like a database dump. Adding one specification attribute to a product showed six lines:

ProductSpecificationAttributes[id=6ae42d82f6567f2f29c48cf7].AttributeTypeId: — → Option (0) · ProductSpecificationAttributes[id=6ae42d82f6567f2f29c48cf7].SpecificationAttributeId: — → Color family (6ae399b7577fba63f26014ac) · ProductSpecificationAttributes[id=6ae42d82f6567f2f29c48cf7].SpecificationAttributeOptionId: — → Black (6ae399b7577fba63f26014ad) · ProductSpecificationAttributes[id=6ae42d82f6567f2f29c48cf7].AllowFiltering: — → false · …
  • every field of a list element is its own line, and an added or removed element reads as N field changes;
  • the element is shown by its id, not by what it is;
  • property names, true/false and the ids stored next to names.

This applies to every list of the domain (37 element types; 12 lists on a product alone, plus order items, addresses, shipment items, store domains, …). A large change also made the history popup cell very long.

Solution

  • One line per list element, named by the element. For an added element: Specification attributes › Color family: added (Attribute type: Option, Specification attribute option: Black, Allow filtering: No, Show on product page: Yes, Display order: 0). A changed element lists its fields (… › Color family: Display order: 0 → 2), and a removed one is one line, Categories › Lamps: removed.
  • The element name is taken when the change is recorded (AuditChange.ElementNames), because a change of only DisplayOrder does not carry the element's name. It comes from the element's [AuditName], else its Name; on an [AuditReference] property it is the referenced record's name. [AuditName] is added to 19 element types (specification, category, collection, attribute mapping, warehouse, bundle/related/similar product, order/shipment/approval/cart item, gift voucher usage, product price, attribute combination SKU, store domain, discount rule); related/similar product and cart item also get the missing [AuditReference]. An element with nothing to name it by shows only its list (Tier prices: added (Quantity: 5, Price: 9.5)), never an id.
  • Words in the admin (AuditChangeText): field names from the property names (AttributeTypeId → "Attribute type", the entity's own prefix dropped), Yes/No, names without the stored id. The history popup text is cut to 300 characters and each value to 60, with "…"; the list's Changes column to 200.
  • Detail page: the field in words, with the stored path under it; the values stay exactly as stored. CSV: new ElementNames column.
  • Stored paths and values are unchanged. Entries recorded before this get the grouping and words, but no names for elements their changes do not name.
  • The name cache in AuditReferences now keys on the reference's Key too.

Gaps found while using it, fixed here too:

  • Notes in their record's history. A note is a record of its own, so a note added to a merchandise return, shipment or customer never showed in that record's history, and an order note was recorded twice (the note and a hand-written entry on the order). [AuditOwner] marks the property holding the id of the record a note belongs to (order, merchandise return, shipment and customer notes; blog post and knowledge base comments). The tracker stamps OwnerType/OwnerId on the entry, plus the owner's store when the note has none, and a record's history also lists the entries it owns: "Merchandise return note: Note: — → Called the customer". New index OwnerType_OwnerId_CreatedOnUtc_Id (installer and the 2.5 migration). IAuditEventRecorder.OwnedChange is removed.
  • Shipment and Permissions had no Change history button. One history can now cover several entity types (entity-type="Permission,PermissionAction"), so the permissions page shows both the matrix and the actions.
  • Translations. Importing a language pack recorded an Insert entry per new resource (10k+ for a new language). IAuditTracker.CountInserts<T> counts the inserts of a type in a scope and records one bulk entry linked to the language ("Import: language: Polish; inserted: 9842"); a changed value stays an entry of its own, and the import no longer writes resources the file does not change. TranslationResource.LanguageId is the owner, so a resource edit shows in the language's history. Entries without changes show their details in the history.
  • Entries without a name: shipments, merchandise returns, payment transactions, schedule tasks, URLs, notes and news/blog/knowledge base comments now have [AuditName]; a name is cut to the stored value length.

Breaking changes

  • The CSV export has a new ElementNames column between Path and OldValue; a script that reads the columns by position needs updating.
  • IAuditEventRecorder.OwnedChange (added in Make the audit trail readable: real changes only, names instead of ids #998, not released) is removed.
  • AuditEntrySearch.EntityType may hold several comma-separated types; IAuditTracker has a new member CountInserts<T> (its one implementation is AuditTracker). New members only otherwise: AuditChange.ElementNames, AuditEntry.OwnerType/OwnerId, AuditChangeModel.Label.

Testing

  1. Unit tests: Grand.Infrastructure.Tests 274 and Grand.Web.Admin.Tests 1773 pass, with new tests for element naming (by reference, nested, removed element, unnamed element) and for the text (added/changed/removed element, keyed user field, masked field, cutting values and the whole text).
  2. Edit a product: add a specification attribute. Open Change history on the product: one line Specification attributes › <attribute>: added (…).
  3. Change the display order of that specification attribute: Specification attributes › <attribute>: Display order: 0 → 1.
  4. Delete it: Specification attributes › <attribute>: removed.
  5. Change many fields at once (e.g. the full description): the history cell ends in "…"; the date opens the full entry, where each field is in words above its stored path.
  6. Export the audit log as CSV: the ElementNames column holds the element name of the rows above.
  7. Add a note to a merchandise return, a shipment and an order: each shows once in that record's Change history, and the audit log filtered by the record's store lists it.
  8. Open Shipment details and Configuration → Permissions: both have Change history; change a group's checkbox and an action - both show.
  9. Admin → Languages → a language → Import a resource file with new and changed resources: the language's Change history shows one "Import … inserted: N" entry and one entry per changed resource; importing the same file again adds nothing.
  10. Unit tests: also Grand.Business.Common.Tests (history with owned entries, several types) and Grand.Web.Common.Tests pass.

The change history read like a database dump: one line per stored field,
list elements shown by their ids, property names and true/false.

- Changes of one list element are one line, named by the element:
  "Specification attributes › Color family: added (Specification attribute
  option: Black, Show on product page: Yes, …)"; a removed element is one
  "removed" line
- The element name is taken when the change is recorded (AuditChange
  .ElementNames), from the element's [AuditName] or Name; a reference names
  it by the referenced record. Element types without a Name are marked
- Field names in words, Yes/No, names without the stored ids
- The history popup text is cut to 300 characters and values to 60, with
  "…"; the full entry stays one click away
- The detail page shows the field in words above the stored path; the CSV
  gets an ElementNames column. Stored paths and values do not change
Copilot AI balanced review requested due to automatic review settings October 30, 2026 06:12

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.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

Comment on lines +97 to +100
catch (Exception)
{
//keep the path only
}
support added 3 commits October 30, 2026 07:24
A note is a record of its own, so adding one to a merchandise return,
shipment or customer never showed in that record's history; order notes
were recorded twice (the note itself and a hand-written entry on the order).

[AuditOwner] marks the property holding the id of the record a note belongs
to. The tracker stamps OwnerType/OwnerId on the note's entry, and the
owner's store when the note has none, and the history of a record also
lists the entries owned by it (new index OwnerType_OwnerId_CreatedOnUtc_Id).
The hand-written IAuditEventRecorder.OwnedChange is gone.
…w blog and article comments in their post's history
Comment on lines +157 to +160
catch (Exception)
{
//no store
}
… language's history

A language pack holds thousands of resources, and each new one was an Insert
entry of its own. IAuditTracker.CountInserts<T> counts the inserts of a type
in a scope and records them as one bulk entry ("Import: language: Polish;
inserted: 9842") linked to the language; a changed value stays an entry of its
own. The import no longer writes a resource the file does not change.
TranslationResource.LanguageId is the owner, so a resource edit shows in the
language's change history. The history shows the details of an entry without
changes.
@KrzysztofPajak
KrzysztofPajak merged commit c7757af into develop Oct 30, 2026
9 checks passed
@KrzysztofPajak
KrzysztofPajak deleted the feature/audit-readable-changes branch October 30, 2026 14:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants