public void Discover(ShapeTableBuilder builder) { builder.Describe("AuditTrailEvent").OnDisplaying(async context => { dynamic shape = context.Shape; var auditTrailEvent = (AuditTrailEvent)shape.AuditTrailEvent; if (auditTrailEvent.Category != "Content") { return; } var contentItemJToken = auditTrailEvent.Get(auditTrailEvent.EventName); var contentEventJToken = auditTrailEvent.Get(nameof(ContentEvent)); var contentItem = contentItemJToken.ToObject <ContentItem>(); if (context.Shape.Metadata.DisplayType == "Detail") { var session = context.ServiceProvider.GetRequiredService <IYesSqlSession>(); AuditTrailEvent previousAuditTrailEvent = null; previousAuditTrailEvent = await session.Query <AuditTrailEvent, AuditTrailEventIndex>() .Where(eventIndex => eventIndex.CreatedUtc < auditTrailEvent.CreatedUtc && eventIndex.Category == "Content" && eventIndex.EventFilterData == contentItem.ContentItemId) .OrderByDescending(eventIndex => eventIndex.CreatedUtc) .FirstOrDefaultAsync(); if (previousAuditTrailEvent != null) { var contentDefinitionManager = context.ServiceProvider.GetRequiredService <IContentDefinitionManager>(); var previousContentItemJToken = previousAuditTrailEvent.Get(previousAuditTrailEvent.EventName); var previousContentItem = previousContentItemJToken.ToObject <ContentItem>(); var currentTypeDefinition = contentDefinitionManager.LoadTypeDefinition(contentItem.ContentType); var previousTypeDefinition = contentDefinitionManager.LoadTypeDefinition(previousContentItem.ContentType); var contentParts = currentTypeDefinition.Parts .Select(typePartDefinition => typePartDefinition.Name) .Intersect(previousTypeDefinition.Parts.Select(b => b.Name)); JObject diff = JsonExtensions.FindDiff(contentItem.Content, previousContentItem.Content); var diffNodes = new List <DiffNode>(); foreach (var contentPart in contentParts) { if (diff.ContainsKey(contentPart)) { foreach (var diffNode in JsonExtensions.GenerateDiffNodes(diff[contentPart])) { diffNode.Context = contentItem.ContentType + "/" + diffNode.Context; diffNodes.Add(diffNode); } } } diffNodes.AddToListIfNotNull( contentItem.LogPropertyChange(previousContentItem, nameof(contentItem.Author))); diffNodes.AddToListIfNotNull( contentItem.LogPropertyChange(previousContentItem, nameof(contentItem.ContentItemVersionId))); diffNodes.AddToListIfNotNull( contentItem.LogPropertyChange(previousContentItem, nameof(contentItem.CreatedUtc))); diffNodes.AddToListIfNotNull( contentItem.LogPropertyChange(previousContentItem, nameof(contentItem.DisplayText))); diffNodes.AddToListIfNotNull( contentItem.LogPropertyChange(previousContentItem, nameof(contentItem.Latest))); diffNodes.AddToListIfNotNull( contentItem.LogPropertyChange(previousContentItem, nameof(contentItem.Owner))); diffNodes.AddToListIfNotNull( contentItem.LogPropertyChange(previousContentItem, nameof(contentItem.ModifiedUtc))); diffNodes.AddToListIfNotNull( contentItem.LogPropertyChange(previousContentItem, nameof(contentItem.Published))); diffNodes.AddToListIfNotNull( contentItem.LogPropertyChange(previousContentItem, nameof(contentItem.PublishedUtc))); shape.DiffNodes = diffNodes; } } shape.ContentItem = contentItem; shape.VersionNumber = contentEventJToken.Value <int>(nameof(ContentEvent.VersionNumber)); }); builder.Describe("AuditTrailEventActions").OnDisplaying(context => { dynamic shape = context.Shape; var auditTrailEvent = (AuditTrailEvent)shape.AuditTrailEvent; if (auditTrailEvent.Category != "Content") { return; } var contentItem = auditTrailEvent.Get(auditTrailEvent.EventName).ToObject <ContentItem>(); var contentEventJToken = auditTrailEvent.Get(nameof(ContentEvent)); shape.ContentItem = contentItem; shape.VersionNumber = contentEventJToken.Value <int>(nameof(ContentEvent.VersionNumber)); }); }