Created
December 4, 2019 10:45
-
-
Save PaulGruffyddAmaze/f39820d189228b74e151eaa6b961cdf4 to your computer and use it in GitHub Desktop.
Getting popular content from Profile Store using KQL
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; } | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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