Skip to content

Instantly share code, notes, and snippets.

@PaulGruffyddAmaze
Created December 4, 2019 10:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save PaulGruffyddAmaze/f39820d189228b74e151eaa6b961cdf4 to your computer and use it in GitHub Desktop.
Save PaulGruffyddAmaze/f39820d189228b74e151eaa6b961cdf4 to your computer and use it in GitHub Desktop.
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