Skip to content

Commit

Permalink
TNO-2973 Send out Top Stories fixes (#2275)
Browse files Browse the repository at this point in the history
* TNO-2973 Send out Top Stories fixes

- only send out top stories that are not sent out previously
- remembers the last send out time in setting table
- added ES query to check the content posted on time / content
action as Top Story with value "true"

* TNO-2973 Minor updates
  • Loading branch information
laidaoyu committed Sep 11, 2024
1 parent f9e1839 commit 58b19cb
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 1 deletion.
57 changes: 57 additions & 0 deletions libs/net/dal/Extensions/JsonDocumentExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,63 @@ public static JsonDocument AddExcludeContent(this JsonDocument query, IEnumerabl
return JsonDocument.Parse(json.ToJsonString());
}

/// <summary>
/// Modify the Elasticsearch 'query' and add a 'should' filter to include the specified 'contentIds' and the previous published on date time.
/// </summary>
/// <param name="query"></param>
/// <param name="contentIds"></param>
/// <param name="previousInstancePublishedOn"></param>
/// <returns></returns>
/// <exception cref="InvalidOperationException"></exception>
public static JsonDocument IncludeOnlyLatestPostedAndContentIds(this JsonDocument query, IEnumerable<long> contentIds, DateTime? previousPublishedOn)
{
var json = JsonNode.Parse(query.ToJson())?.AsObject();
if (json == null) return query;
JsonNode jMustTerms = null!;
JsonNode jIncludeOnlyLatestPosted = null!;
JsonNode jShouldQuery = null!;

if (contentIds != null && contentIds.Any())
{
jMustTerms = JsonNode.Parse($"{{ \"terms\": {{ \"id\": [{String.Join(',', contentIds)}] }}}}")?.AsObject() ?? throw new InvalidOperationException("Failed to parse JSON");
jShouldQuery = JsonNode.Parse($"{jMustTerms.ToJsonString()}")!;
}
if (previousPublishedOn != null)
{
jIncludeOnlyLatestPosted = JsonNode.Parse($"{{ \"range\": {{ \"postedOn\": {{ \"gte\": \"{previousPublishedOn.Value:yyyy-MM-ddTHH:mm:ss}\", \"time_zone\": \"US/Pacific\" }} }} }}")!;
if (jIncludeOnlyLatestPosted != null)
{
if (jShouldQuery != null)
{
jShouldQuery = JsonNode.Parse($"[ {jShouldQuery.ToJsonString()}, {jIncludeOnlyLatestPosted.ToJsonString()} ]")!;
}
else
{
jShouldQuery = JsonNode.Parse($"[ {jIncludeOnlyLatestPosted.ToJsonString()} ]")!;
}
}
}
if (json.TryGetPropertyValue("query", out JsonNode? jQuery))
{
if (jQuery?.AsObject().TryGetPropertyValue("bool", out JsonNode? jQueryBool) == true)
{
if (jShouldQuery != null)
{
jQueryBool?.AsObject().Add("should", JsonNode.Parse($"{jShouldQuery.ToJsonString()}"));
}
}
else
{
jQuery?.AsObject().Add("bool", JsonNode.Parse($"{{ \"should\": [ {jShouldQuery.ToJsonString()} ]}}"));
}
}
else
{
json.Add("query", JsonNode.Parse($"{{ \"bool\": {{ \"should\": [ {jShouldQuery.ToJsonString()} ] }}}}"));
}
return JsonDocument.Parse(json.ToJsonString());
}

/// <summary>
/// Modify the Elasticsearch 'query' and add a 'must' filter to exclude content posted before the last report instance published date.
/// </summary>
Expand Down
35 changes: 34 additions & 1 deletion libs/net/dal/Services/NotificationService.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using System.Security.Claims;
using System.Text.Json;
using Microsoft.EntityFrameworkCore;
Expand All @@ -19,6 +20,10 @@ public class NotificationService : BaseService<Notification, int>, INotification
private readonly ITNOElasticClient _elasticClient;
private readonly ElasticOptions _elasticOptions;
private readonly JsonSerializerOptions _serializerOptions;
private readonly ISettingService _settingService;
private const string TopStoryLastRunOn = "TopStoryLastRunOn";
private const string TopStoryLastRunOnDescription = "The Top Stories Notification last run on time (UTC time).";
private const string ActionTopStoryName = "Top Story";
#endregion

#region Constructors
Expand All @@ -39,11 +44,13 @@ public NotificationService(
TNOContext dbContext,
ClaimsPrincipal principal,
IServiceProvider serviceProvider,
ILogger<NotificationService> logger) : base(dbContext, principal, serviceProvider, logger)
ILogger<NotificationService> logger,
ISettingService settingService) : base(dbContext, principal, serviceProvider, logger)
{
_elasticClient = elasticClient;
_elasticOptions = elasticOptions.Value;
_serializerOptions = serializerOptions.Value;
_settingService = settingService;
}
#endregion

Expand Down Expand Up @@ -276,6 +283,7 @@ public async Task<int> Unsubscribe(int userId)

var defaultIndex = filter.SearchUnpublished ? _elasticOptions.UnpublishedIndex : _elasticOptions.PublishedIndex;
var query = notification.Query;
var topStoryLastRunOnSetting = _settingService.FindByName(TopStoryLastRunOn);

// Fetch all notifications within the offset of the requested notification filter.
// Need to identify all content already notified by this notification so that we don't resend.
Expand All @@ -292,10 +300,35 @@ public async Task<int> Unsubscribe(int userId)
&& (settings.StartDate == null | ni.SentOn >= settings.StartDate))
.Select(ni => ni.ContentId).Distinct().ToArray();

if (topStoryLastRunOnSetting != null && topStoryLastRunOnSetting.Value != null)
{
var lastRunOnTime = DateTime.SpecifyKind(DateTime.Parse(topStoryLastRunOnSetting.Value), DateTimeKind.Utc);
var topStoryActionId = this.Context.Actions.Where(x => x.Name == ActionTopStoryName).FirstOrDefault()?.Id;
var actionContentIds = this.Context.ContentActions.Where(ca => ca.ActionId == topStoryActionId
&& !string.IsNullOrEmpty(ca.Value) && ca.Value.ToLower() == "true"
&& ca.UpdatedOn > lastRunOnTime)
.Select(ca => ca.ContentId).Distinct().ToArray();

DateTime localLastRunOn = DateTime.Parse(topStoryLastRunOnSetting.Value).ToLocalTime();
query = query.IncludeOnlyLatestPostedAndContentIds(actionContentIds, localLastRunOn);
}

if (sentNotificationContentIds.Any())
query = query.AddExcludeContent(sentNotificationContentIds);
}

// update top story last run on setting
if (topStoryLastRunOnSetting == null)
{
var newSetting = new Setting(TopStoryLastRunOn, DateTime.Now.ToUniversalTime().ToString());
newSetting.Description = TopStoryLastRunOnDescription;
_settingService.AddAndSave(newSetting);
}
else
{
topStoryLastRunOnSetting.Value = DateTime.Now.ToUniversalTime().ToString();
_settingService.UpdateAndSave(topStoryLastRunOnSetting);
}
return await _elasticClient.SearchAsync<API.Areas.Services.Models.Content.ContentModel>(defaultIndex, query);
}

Expand Down

0 comments on commit 58b19cb

Please sign in to comment.