Rate this page:
In Jira 8.8 we announced the new and improved auditing feature which came with its own API.
As for the audit API that was already present, we’re going to mark it as deprecated in Jira version 8.12, and we will still support it in parallel with the new API to give you time to migrate.
In this article we’re going to focus on the migration to the new auditing API.
First, let’s look at the connection between the legacy and the new entities that the APIs use and how the legacy APIs can be replaced with the new ones. Then, we’ll look at the actual code sample on how to do the migration.
Legacy audit API entities
New audit API entities
(since Jira 8.12.2)
You’ll notice that some APIs can have more than one equivalent. The right API depends on your use case, and the main difference is how the results are provided back to you. For more details, we recommend exploring the Javadoc that comes with the APIs.
Legacy audit API entities
New audit API entities
Now, we’re going to look at some code examples on how to migrate to the new API.
Most of the entities used in the legacy API were replaced by new entities used by the new API, so migrating to the new API means you will have to migrate dependent entities as well.
Here is an example of the old way to create an audit event. Note that we omitted the instantiation part of some of the variables to make the code more readable.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
// Creating an affected object AssociatedItem affectedObject = ...; // Creating associatedItems Iterable<AssociatedItem> associatedItems = ...; // Creating changed values Iterable<ChangedValue> changedValues = ...; // Creating the request to publish new audit event RecordRequest recordRequest = new RecordRequest(AuditingCategory.APPLICATIONS, "some.custom.action.key") .forObject(affectedObject) .withAssociatedItems(associatedItems) .withChangedValues(changedValues); // Publishing the audit event auditingManager.store(recordRequest);
Adapting the above code to the new API should look like the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
// Details about the event coverage area, level and AuditType auditType = ...; /* see below */ // Creating an affected object AuditResource affectedObject = ...; /* see below */ // Creating associatedItems List<AuditResource> auditResources = ... /* similar to creating the 'affectedObject' above */ // Creating changed values List<ChangedValue> changedValues = ...; /* see below */ // Creating the request to publish new audit event AuditEvent event = AuditEvent.builder(auditType) .affectedObject(affectedObject) /* equivalent of affectedObject from legacy API example */ // It is important to append the extra affectedObjects only after the affectedObject was added // or if you do it in one go with the AuditEvent.Builder#affectedObjects method, make sure the affectedObject is the first item in the list .appendAffectedObjects(auditResources) /* equivalent of associatedItems from the legacy API example */ .changedValues(changedValues) .build(); // Publishing the audit event auditService.audit(event);
Now let’s look closer at how to create the variables passed to the builder.
By using the coverage area and level your events will be even more customizable. In addition to providing an action and a category like in the legacy API, you will be able to provide coverage area (event product area descriptor) and level (event verbosity level) to better group the events provided by your code. More than that, the supports translation for the event summary (action) and coverage.
1 2 3 4 5 6 7 8
AuditType auditType = AuditType.fromI18nKeys( CoverageArea.ECOSYSTEM, /* recommended area for ecosystem events */ AuditCategory.APPLICATIONS, /* on top of Jira predefined values, it can be any string value */ "some.custom.action.key", /* an action internationalization key */ CoverageLevel.FULL) /* the level of coverage necessary to show this event */ .withCategoryTranslation("translation for category key passed above") /* optional, equivalent of AuditingCategory#getId return value in the legacy API */ .withActionTranslation("translation for action key passed above") /* optional, equivalent of RecordRequest#getSummary return value in the legacy API */ .build();
The coverage area is recommendend when publishing events from plug-ins since it will define a better separation between Jira and 3rd party events.
builder will take the same parameters values you had to provide before for the implementation of the methods.
1 2 3
AuditResource affectedObject = AuditResource.builder(name, type) /* 'name' and 'type' are the equivalents of AssociatedItem#getObjectName and AssociatedItem.Type#name return values in the legacy API */ .id(id) /* equivalent of AssociatedItem#getObjectId return value in the legacy API */ .build();
Changed values look more or less the same as the ones from the legacy API. The only change is that the builder now requires the internationalization key to support other locales.
1 2 3 4 5 6
List<ChangedValue> changedValues = singletonList( ChangedValue.fromI18nKeys(i18nKey) .withKeyTranslation("translation for the i18nKey") /* optional, equivalent of the return value of `ChangedValue.getName()` in the legacy API */ .from(fromValue) /* equivalent of the return value of `ChangedValue#getFrom` in the legacy API */ .to(toValue) /* equivalent of the return value of `ChangedValue#getTo` in the legacy API */ .build());
Using the new API for searching is more straightforward in comparison to the old one. In the old API the configuration for the query was provided either to the or to the while searching.
In the new API, the same configurations are held either by the or the . One nice feature that will stand out from the beginning is that the results are returned in a paginated format for easier processing.
An example of migration can be seen below. Note that some of the fields are nullable so they are not required all the time, allowing you to adjust the querying based on your specific needs.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
// Create query AuditingFilter auditingFilter = AuditingFilter.builder() .filter(filter) .fromTimestamp(fromTimestamp) .toTimestamp(toTimestamp) .userIds(userIds) .build(); // Query Records records = auditingManager.getRecords( maxId, sinceId, count, offset, auditingFilter); // Get actual results from the response List<AuditRecord> results = records.getResults();
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
// Create query AuditQuery auditQuery = AuditQuery.builder() .maxId(maxId) /* similar to the value from legacy API example */ .minId(sinceId) /* similar to the value from legacy API example */ .searchText(filter) /* similar to the value from legacy API example */ .usersIds(userIds) /* similar to the values from legacy API example */ .from(fromTimestamp) /* similar to the value from legacy API example */ .to(toTimestamp) /* similar to the value from legacy API example */ .build(); PageRequest<AuditEntityCursor> auditEntityCursorPageRequest = new PageRequest.Builder<>() .offset(offset) /* similar to the value from legacy API example */ .limit(limit) /* number of results, up to PageRequest.Builder.MAX_PAGE_LIMIT */ .build(); // Query Page<AuditEntity, AuditEntityCursor> auditEntityCursorPage = auditSearchService.findBy( auditQuery, auditEntityCursorPageRequest); // Get actual results from the response List<AuditEntity> results = auditEntityCursorPage.getValues();
As you can see, the new code makes use of the same information to search as the legacy API, it is just organized differently.
This article covers the Java APIs. For the REST API migration information, please refer to the Audit log improvements update.
Rate this page: