Getting popular content from Profile Store using KQL
[ServiceConfiguration(ServiceType = typeof(ITrackingDataInterceptor), Lifecycle = ServiceInstanceScope.Singleton)] | |
public class ContentTypeTrackingInterceptor : ITrackingDataInterceptor | |
{ | |
private IContentLoader _contentLoader; | |
private IContentTypeRepository _contentTypeRepository; | |
public int SortOrder => 100; | |
public ContentTypeTrackingInterceptor(IContentLoader contentLoader, IContentTypeRepository contentTypeRepository) | |
{ | |
_contentLoader = contentLoader; | |
_contentTypeRepository = contentTypeRepository; | |
} | |
public void Intercept<TPayload>(TrackingData<TPayload> trackingData) | |
{ | |
if (trackingData == null || trackingData.Payload == null) | |
{ | |
return; | |
} | |
if (!(trackingData.Payload is EPiServer.Tracking.PageView.EpiPageViewWrapper payload)) | |
{ | |
return; | |
} | |
// Create replacement Epi payload object | |
var pageView = new EpiPageViewWithType(payload.Epi); | |
var page = _contentLoader.Get<IContent>(payload.Epi.ContentGuid); | |
pageView.TypeId = page.ContentTypeID; | |
payload.Epi = pageView; | |
} | |
} |
public class EpiPageViewWithType : EpiPageView | |
{ | |
public EpiPageViewWithType(EpiPageView pageView) | |
{ | |
Ancestors = pageView.Ancestors; | |
ContentGuid = pageView.ContentGuid; | |
Language = pageView.Language; | |
RecommendationClick = pageView.RecommendationClick; | |
SiteId = pageView.SiteId; | |
} | |
public int TypeId { get; set; } | |
} |
[ContentType(DisplayName = "KQL Trending Articles Block", GUID = "532baaef-214e-4bc3-ac82-bedde42d0e03", Description = "")] | |
public class KqlTrendingArticlesBlock : BlockData | |
{ | |
[CultureSpecific] | |
[Display( | |
Name = "Heading", | |
Description = "Heading for this block", | |
GroupName = SystemTabNames.Content, | |
Order = 10)] | |
public virtual string Heading { get; set; } | |
[Display( | |
Name = "No of items", | |
Description = "The number of items to show", | |
GroupName = SystemTabNames.Content, | |
Order = 20)] | |
[Required] | |
public virtual int ItemCount { get; set; } | |
[Display( | |
Name = "Root page for items", | |
Description = "The ancestor of the content items returned", | |
GroupName = SystemTabNames.Content, | |
Order = 30)] | |
[Required] | |
public virtual PageReference Root { get; set; } | |
[Display( | |
Name = "Agregation period (hours)", | |
Description = "The number of hours to agregate data over", | |
GroupName = SystemTabNames.Content, | |
Order = 40)] | |
[Required] | |
public virtual int RecentHours { get; set; } | |
public override void SetDefaultValues(ContentType contentType) | |
{ | |
base.SetDefaultValues(contentType); | |
RecentHours = 24; | |
ItemCount = 5; | |
} | |
} |
@model TrendingArticlesViewModel | |
<h2>@Model.Heading</h2> | |
<hr /> | |
<ol> | |
@foreach (var article in Model.TrendingArticles) | |
{ | |
<li class="listResult"> | |
<h3> | |
@Html.PageLink(article) | |
</h3> | |
<p class="date">@Html.DisplayFor(x => article.StartPublish)</p> | |
<hr /> | |
</li> | |
} | |
</ol> |
public class KqlTrendingArticlesBlockController : BlockController<KqlTrendingArticlesBlock> | |
{ | |
private IContentLoader _contentLoader; | |
private IContentTypeRepository _contentTypeRepository; | |
public KqlTrendingArticlesBlockController(IContentTypeRepository contentTypeRepository, IContentLoader contentLoader) | |
{ | |
_contentTypeRepository = contentTypeRepository; | |
_contentLoader = contentLoader; | |
} | |
public override ActionResult Index(KqlTrendingArticlesBlock currentBlock) | |
{ | |
var lang = (currentBlock as ILocalizable).Language; | |
var profileStoreHelper = new ProfileStoreHelper(_contentTypeRepository, _contentLoader); | |
var trendingArticles = profileStoreHelper.GetPopularPages<ArticlePage>(ContentReference.StartPage, lang.Name, currentBlock.ItemCount, currentBlock.RecentHours); | |
var viewModel = new TrendingArticlesViewModel | |
{ | |
Heading = currentBlock.Heading, | |
TrendingArticles = trendingArticles | |
}; | |
return PartialView(viewModel); | |
} | |
} |
public class ProfileStoreHelper | |
{ | |
//Settings | |
private string _apiRootUrl = ConfigurationManager.AppSettings["episerver:profiles.ProfileApiBaseUrl"]; | |
private string _appKey = ConfigurationManager.AppSettings["episerver:profiles.ProfileApiSubscriptionKey"]; | |
private string _eventUrl = "/api/v2.0/TrackEvents/preview"; | |
private string _scope = ConfigurationManager.AppSettings["episerver:profiles.Scope"] ?? SiteDefinition.Current.Id.ToString(); | |
private IContentLoader _contentLoader; | |
private IContentTypeRepository _contentTypeRepository; | |
public ProfileStoreHelper(IContentTypeRepository contentTypeRepository = null, IContentLoader contentLoader = null) | |
{ | |
_contentTypeRepository = contentTypeRepository ?? ServiceLocator.Current.GetInstance<IContentTypeRepository>(); | |
_contentLoader = contentLoader ?? ServiceLocator.Current.GetInstance<IContentLoader>(); | |
} | |
/// <summary> | |
/// Get pages of a given type | |
/// </summary> | |
public IEnumerable<T> GetPopularPages<T>(ContentReference ancestor, string lang, int resultCount = 5, int recentHours = 24) where T : PageData | |
{ | |
var contentTypeId = _contentTypeRepository.Load<T>().ID; | |
var ancestorGuid = _contentLoader.Get<IContent>(ancestor).ContentGuid; | |
var hits = GetRecentContentResponse(ancestorGuid, lang, resultCount, recentHours, contentTypeId); | |
return hits?.Items?.Select(x => _contentLoader.Get<T>(x.Value)) ?? Enumerable.Empty<T>(); | |
} | |
/// <summary> | |
/// Get all popular content regardless of type | |
/// </summary> | |
public IEnumerable<IContent> GetPopularPages(ContentReference ancestor, string lang, int resultCount = 5, int recentHours = 24) | |
{ | |
var ancestorGuid = _contentLoader.Get<IContent>(ancestor).ContentGuid; | |
var hits = GetRecentContentResponse(ancestorGuid, lang, resultCount, recentHours); | |
return hits?.Items?.Select(x => _contentLoader.Get<IContent>(x.Value)) ?? Enumerable.Empty<IContent>(); | |
} | |
/// <summary> | |
/// Make request to profile store API | |
/// </summary> | |
private RecentContentResponse GetRecentContentResponse(Guid ancestorGuid, string lang, int resultCount = 5, int recentHours = 24, int typeId = 0) | |
{ | |
var requestBody = $"{{\"Query\": \"{GenerateKQLQuery(ancestorGuid, lang, resultCount, recentHours, typeId)}\", \"Scope\": \"{_scope}\" }}"; | |
var req = new RestRequest(_eventUrl, Method.POST); | |
req.AddHeader("Authorization", $"epi-single {_appKey}"); | |
req.AddParameter("application/json-patch+json", requestBody, ParameterType.RequestBody); | |
req.RequestFormat = DataFormat.Json; | |
req.AddBody(requestBody); | |
var client = new RestClient(_apiRootUrl); | |
var getEventResponse = client.Execute(req); | |
return JsonConvert.DeserializeObject<RecentContentResponse>(getEventResponse.Content); | |
} | |
/// <summary> | |
/// Construct KQL query | |
/// </summary> | |
private string GenerateKQLQuery(Guid ancestorGuid, string lang, int resultCount = 5, int recentHours = 24, int typeId = 0) | |
{ | |
var kqlQueryObj = @"Events | |
| where EventTime between(ago({0}h) .. now()) | |
and EventType == 'epiPageView' | |
and Payload.epi.language == '{1}' | |
and Payload.epi.ancestors contains('{2}') | |
{3} | |
| summarize Count = count() by Value = tostring(Payload.epi.contentGuid) | |
| top {4} by Count desc"; | |
//Only add type restriction if a type has been specified | |
var typeQuery = typeId > 0 ? $"and Payload.epi.typeId == {typeId}" : string.Empty; | |
return string.Format(kqlQueryObj, recentHours, lang, ancestorGuid.ToString(), typeQuery, resultCount); | |
} | |
} | |
public class RecentContentResponse | |
{ | |
public int Count { get; set; } | |
public RecentContentResponseItem[] Items { get; set; } | |
} | |
public class RecentContentResponseItem | |
{ | |
public Guid Value { get; set; } | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment