Skip to main content

Audits / Changelog

Audit source

We track selected backend events, e.g. Patch events, via the changelog table.

See Analytics/Changelog table for more.

Audit routes

Audits in the hub

Audits, also known as Logs or Changelogs can be accessed via the Logs menu in the Admin Dashboard.

Logs menu in Admin Dashboard

Each audit can be viewed directly in the hub as well as downloaded in CSV format.

Message log in Admin Dashboard

The level of information and details shown per audit differs depending on the use case.

Audit details

Some audit entries include a details field — a JSON object describing what changed. Each audit route is responsible for transforming this raw JSON into a structured list of { property, value } pairs before serving it to the frontend.

Human-readable labels for each property key are defined in two places that must stay in sync:

When adding new changelog detail keys, both files must be updated.

Gotchas

Object values must be explicitly expanded

The default fallback in audit route formatters returns { property, value } as-is, which only works for primitive values (string, number, boolean). If a detail property's value is an object (e.g. a nested set of fields), it must be explicitly expanded into a { property, value }[] array before being returned. Without this, the UI renders [object Object] and the CSV export is broken.

Example from api/routes/content/audit/get.js:

if (changedProp === 'eventSessionDetails' || changedProp === 'changedEventSessionDetails') {
return {
property: changedProp,
value: Object.entries(newValue)
.filter(([, v]) => v !== null && v !== undefined)
.map(([k, v]) => ({property: k, value: v})),
};
}

Internally-fetched fields can leak into the changelog

The modelsRest/asset.js bulk patch handler fetches extra fields from the DB for internal logic (e.g. type is always fetched to identify event assets for email sending). Because the changelog details is built by spreading the full fetched row into the patch body, those extra fields end up in details even when they were not part of the request.

Any field fetched for internal reasons must be explicitly removed from details if it was not in the original request body. The pattern used for both parent_id and type:

if (!('type' in body)) {
delete details.type;
}