Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding health status to providers #263

Merged
merged 6 commits into from
Oct 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/TagzApp.Common/ISocialMediaProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ public interface ISocialMediaProvider
/// <returns></returns>
Task<IEnumerable<Content>> GetContentForHashtag(Hashtag tag, DateTimeOffset since);

/// <summary>
/// Report the health of the provider
/// </summary>
/// <returns></returns>
Task<(SocialMediaStatus Status, string Message)> GetHealth();

/// <summary>
/// Start the provider
/// </summary>
Expand Down
12 changes: 12 additions & 0 deletions src/TagzApp.Common/SocialMediaStatus.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace TagzApp.Common;

/// <summary>
/// Defines the health status of a Social Media Provider
/// </summary>
public enum SocialMediaStatus
{
Unknown = -1,
Unhealthy = 0,
Degraded = 1,
Healthy = 2
}
13 changes: 13 additions & 0 deletions src/TagzApp.Providers.Blazot/BlazotProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ internal sealed class BlazotProvider : ISocialMediaProvider
public string Id => BlazotConstants.ProviderId;
public string DisplayName => BlazotConstants.DisplayName;

private SocialMediaStatus _Status = SocialMediaStatus.Unhealthy;
private string _StatusMessage = "Not started";

public string Description { get; init; } = "Blazot is an all new social networking platform and your launchpad to the social universe!";

public BlazotProvider(ILogger<BlazotProvider> logger, BlazotSettings settings,
Expand Down Expand Up @@ -65,12 +68,20 @@ public async Task<IEnumerable<Content>> GetContentForHashtag(Hashtag tag, DateTi

transmissions = await _TransmissionsService.GetHashtagTransmissionsAsync(tag, dateTimeOffset);

_Status = SocialMediaStatus.Healthy;
_StatusMessage = "OK";

if (transmissions == null)
return Enumerable.Empty<Content>();

}
catch (Exception ex)
{
_Logger.LogError(ex, "Error fetching Blazot Hashtag Transmissions: {message}", ex.Message);

_Status = SocialMediaStatus.Unhealthy;
_StatusMessage = $"Error fetching Blazot Hashtag Transmissions: {ex.Message}";

}

return _ContentConverter.ConvertToContent(transmissions, tag);
Expand All @@ -80,4 +91,6 @@ public Task StartAsync()
{
return Task.CompletedTask;
}

public Task<(SocialMediaStatus Status, string Message)> GetHealth() => Task.FromResult((_Status, _StatusMessage));
}
15 changes: 15 additions & 0 deletions src/TagzApp.Providers.Mastodon/MastodonProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ internal class MastodonProvider : ISocialMediaProvider, IHasNewestId

private readonly HttpClient _HttpClient;
private readonly ILogger _Logger;
private SocialMediaStatus _Status = SocialMediaStatus.Unhealthy;
private string _StatusMessage = "Not started";

public MastodonProvider(IHttpClientFactory httpClientFactory, ILogger<MastodonProvider> logger,
MastodonConfiguration configuration)
Expand All @@ -34,6 +36,9 @@ public MastodonProvider(IHttpClientFactory httpClientFactory, ILogger<MastodonPr
public async Task<IEnumerable<Content>> GetContentForHashtag(Hashtag tag, DateTimeOffset since)
{

_Status = SocialMediaStatus.Healthy;
_StatusMessage = "OK";

var targetUri = FormatUri(tag);

Message[]? messages = null;
Expand All @@ -49,12 +54,17 @@ public async Task<IEnumerable<Content>> GetContentForHashtag(Hashtag tag, DateTi
{

_Logger.LogError(ex, "Error getting content from Mastodon");
_Status = SocialMediaStatus.Unhealthy;
_StatusMessage = $"Error getting content from Mastodon: {ex.Message}";

return Enumerable.Empty<Content>();

}

if (messages is null || (!messages?.Any() ?? true))
{
_Status = SocialMediaStatus.Healthy;
_StatusMessage = "No new content found";
return Enumerable.Empty<Content>();
}

Expand Down Expand Up @@ -89,6 +99,11 @@ public async Task<IEnumerable<Content>> GetContentForHashtag(Hashtag tag, DateTi

}

public Task<(SocialMediaStatus Status, string Message)> GetHealth()
{
return Task.FromResult((_Status, _StatusMessage));
}

public Task StartAsync()
{
return Task.CompletedTask;
Expand Down
1 change: 1 addition & 0 deletions src/TagzApp.Providers.TwitchChat/ChatClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ public void Init()

public string ChannelName { get; }
public string ChatBotName { get; }
public bool IsRunning => _ReceiveMessagesThread.IsAlive;

private readonly string _OAuthToken;
private readonly CancellationTokenSource _Shutdown;
Expand Down
3 changes: 3 additions & 0 deletions src/TagzApp.Providers.TwitchChat/IChatClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,7 @@ public interface IChatClient : IDisposable
event EventHandler<NewMessageEventArgs> NewMessage;

void Init();

bool IsRunning { get; }

}
50 changes: 48 additions & 2 deletions src/TagzApp.Providers.TwitchChat/TwitchChatProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ public class TwitchChatProvider : ISocialMediaProvider, IDisposable
public TimeSpan NewContentRetrievalFrequency => TimeSpan.FromSeconds(1);
public string Description { get; init; } = "Twitch is where millions of people come together live every day to chat, interact, and make their own entertainment together.";

private SocialMediaStatus _Status = SocialMediaStatus.Unhealthy;
private string _StatusMessage = "Not started";

private static readonly ConcurrentQueue<Content> _Contents = new();
private static readonly CancellationTokenSource _CancellationTokenSource = new();
private readonly TwitchChatConfiguration _Settings;
Expand Down Expand Up @@ -43,13 +46,24 @@ internal TwitchChatProvider(IOptions<TwitchChatConfiguration> settings, ILogger<
private async Task ListenForMessages(IChatClient chatClient = null)
{

_Status = SocialMediaStatus.Degraded;
_StatusMessage = "Starting TwitchChat client";

var token = _CancellationTokenSource.Token;
_Client = chatClient ?? new ChatClient(_Settings.ChannelName, _Settings.ChatBotName, _Settings.OAuthToken, _Logger);

_Client.NewMessage += async (sender, args) =>
{

var profileUrl = await IdentifyProfilePic(args.UserName);
string profileUrl = string.Empty;
try
{
profileUrl = await IdentifyProfilePic(args.UserName);
}
catch (Exception ex)
{
_Logger.LogError(ex, "Failed to identify profile pic for {UserName}", args.UserName);
}

_Contents.Enqueue(new Content
{
Expand All @@ -70,7 +84,20 @@ private async Task ListenForMessages(IChatClient chatClient = null)
});
};

_Client.Init();
try
{
_Client.Init();
}
catch (Exception ex)
{
_Logger.LogError(ex, "Failed to initialize TwitchChat client");
_Status = SocialMediaStatus.Unhealthy;
_StatusMessage = $"Failed to initialize TwitchChat client: '{ex.Message}'";
return;
}

_Status = SocialMediaStatus.Healthy;
_StatusMessage = "OK";

}

Expand All @@ -82,6 +109,17 @@ private async Task<string> IdentifyProfilePic(string userName)
public Task<IEnumerable<Content>> GetContentForHashtag(Hashtag tag, DateTimeOffset since)
{

if (!_Client.IsRunning)
{

// mark status as unhealthy and return empty list
_Status = SocialMediaStatus.Unhealthy;
_StatusMessage = "TwitchChat client is not running";

return Task.FromResult(Enumerable.Empty<Content>());

}

var messages = _Contents.ToList();
if (messages.Count() == 0) return Task.FromResult(Enumerable.Empty<Content>());

Expand All @@ -91,6 +129,9 @@ public Task<IEnumerable<Content>> GetContentForHashtag(Hashtag tag, DateTimeOffs
_Contents.TryDequeue(out _);
}

_Status = SocialMediaStatus.Healthy;
_StatusMessage = "OK";

messages.ForEach(m => m.HashtagSought = tag.Text);

return Task.FromResult(messages.AsEnumerable());
Expand Down Expand Up @@ -131,4 +172,9 @@ public Task StartAsync()
ListenForMessages();
return Task.CompletedTask;
}

public Task<(SocialMediaStatus Status, string Message)> GetHealth()
{
return Task.FromResult((_Status, _StatusMessage));
}
}
28 changes: 27 additions & 1 deletion src/TagzApp.Providers.Twitter/TwitterProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
private const int _SearchMaxResults = 100;
private const string _SearchExpansions = "author_id,attachments.media_keys";

private SocialMediaStatus _Status = SocialMediaStatus.Unhealthy;
private string _StatusMessage = "Not started";

public string Id => "TWITTER";
public string DisplayName => "Twitter";
public TimeSpan NewContentRetrievalFrequency => TimeSpan.FromSeconds(30);
Expand Down Expand Up @@ -48,6 +51,9 @@
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
{

_Status = SocialMediaStatus.Healthy;
_StatusMessage = "OK";

var tweetQuery = "#" + tag.Text.ToLowerInvariant().TrimStart('#') + " -is:retweet";
var sinceTerm = string.IsNullOrEmpty(NewestId) ? "" : $"&since_id={NewestId}";

Expand All @@ -63,13 +69,16 @@
//var response = await _HttpClient.GetAsync(targetUri);
//var rawText = await response.Content.ReadAsStringAsync();

recentTweets = await _HttpClient.GetFromJsonAsync<TwitterData>(targetUri);

Check warning on line 72 in src/TagzApp.Providers.Twitter/TwitterProvider.cs

View workflow job for this annotation

GitHub Actions / Playwright Tests

Converting null literal or possible null value to non-nullable type.
NewestId = recentTweets.meta.newest_id ?? NewestId;

}
else
{

_Status = SocialMediaStatus.Degraded;
_StatusMessage = "Twitter provider is not activated - returning sample tweets";

var assembly = Assembly.GetExecutingAssembly();
var resourceName = "TagzApp.Providers.Twitter.Models.SampleTweets.json.gz";
string sampleJson = string.Empty;
Expand All @@ -87,8 +96,14 @@
}
catch (Exception ex)
{

Console.WriteLine($"Error retrieving tweets: {ex.Message}");

_Status = SocialMediaStatus.Unhealthy;
_StatusMessage = $"Error retrieving tweets: {ex.Message}";

_Logger.LogError(ex, $"Error retrieving tweets");

}

var outTweets = ConvertToContent(recentTweets, tag);
Expand Down Expand Up @@ -187,8 +202,12 @@
}
catch (Exception ex)
{
Console.WriteLine($"Error formatting twee ('{t.text}'): ${ex.Message}");
Console.WriteLine($"Error formatting tweet ('{t.text}'): ${ex.Message}");
_Logger.LogError(ex, $"Error formatting tweet: {t.text}");

_Status = SocialMediaStatus.Degraded;
_StatusMessage = $"Error formatting tweet: {t.text}";

}

}
Expand All @@ -215,4 +234,11 @@
{
return Task.CompletedTask;
}

public Task<(SocialMediaStatus Status, string Message)> GetHealth()
{

return Task.FromResult((_Status, _StatusMessage));

}
}
24 changes: 24 additions & 0 deletions src/TagzApp.Providers.YouTubeChat/YouTubeChatProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,17 @@
private bool _DisposedValue;
private string _NextPageToken;

private SocialMediaStatus _Status = SocialMediaStatus.Unhealthy;
private string _StatusMessage = "Not started";

public YouTubeChatProvider(YouTubeChatConfiguration config, IOptions<ApplicationConfiguration> appConfig)

Check warning on line 38 in src/TagzApp.Providers.YouTubeChat/YouTubeChatProvider.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'Description' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 38 in src/TagzApp.Providers.YouTubeChat/YouTubeChatProvider.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'NewestId' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.
{
_ChatConfig = config;

if (appConfig.Value.YouTubeChatConfiguration == "{}") return;

var youtubeConfig = JsonSerializer.Deserialize<YouTubeChatApplicationConfiguration>(appConfig.Value.YouTubeChatConfiguration);
RefreshToken = youtubeConfig.RefreshToken;

Check warning on line 45 in src/TagzApp.Providers.YouTubeChat/YouTubeChatProvider.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.
LiveChatId = youtubeConfig.LiveChatId;
YouTubeEmailId = youtubeConfig.ChannelEmail;

Expand Down Expand Up @@ -70,9 +73,16 @@
_GoogleException = $"{LiveChatId}:{ex.Message}";
LiveChatId = string.Empty;
}

_Status = SocialMediaStatus.Unhealthy;
_StatusMessage = $"Exception while fetching YouTubeChat: {ex.Message}";

return Enumerable.Empty<Content>();
}

_Status = SocialMediaStatus.Healthy;
_StatusMessage = "OK";

return contents.Items.Select(i => new Content
{
Author = new Creator
Expand Down Expand Up @@ -115,6 +125,10 @@
catch (Exception ex)
{
Console.WriteLine($"Exception while refreshing token: {ex.Message}");

_Status = SocialMediaStatus.Unhealthy;
_StatusMessage = $"Exception while refreshing token: {ex.Message}";

throw;
}
var credential = new UserCredential(flow, "me", token);
Expand All @@ -123,6 +137,10 @@
{
HttpClientInitializer = credential
});

_Status = SocialMediaStatus.Degraded;
_StatusMessage = "Starting YouTubeChat client";

return _Service;
}

Expand Down Expand Up @@ -166,6 +184,10 @@
{
// GoogleApiException: The service youtube has thrown an exception. HttpStatusCode is Forbidden. The user is not enabled for live streaming.
Console.WriteLine($"Exception while fetching YouTube broadcasts: {ex.Message}");

_Status = SocialMediaStatus.Unhealthy;
_StatusMessage = $"Exception while fetching YouTube broadcasts: {ex.Message}";

return Enumerable.Empty<YouTubeBroadcast>();
}

Expand Down Expand Up @@ -217,6 +239,8 @@
GC.SuppressFinalize(this);
}

public Task<(SocialMediaStatus Status, string Message)> GetHealth() => Task.FromResult((_Status, _StatusMessage));

#endregion

}
Expand Down
9 changes: 9 additions & 0 deletions src/TagzApp.Providers.Youtube/YoutubeProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ internal class YoutubeProvider : ISocialMediaProvider
public string DisplayName => "Youtube";
public string Description { get; init; }

private SocialMediaStatus _Status = SocialMediaStatus.Unhealthy;
private string _StatusMessage = "Not started";

public TimeSpan NewContentRetrievalFrequency => TimeSpan.FromSeconds(30);

public YoutubeProvider(YoutubeConfiguration options)
Expand All @@ -35,6 +38,9 @@ public async Task<IEnumerable<Content>> GetContentForHashtag(Hashtag tag, DateTi

var searchListResponse = await searchListRequest.ExecuteAsync();

_Status = SocialMediaStatus.Healthy;
_StatusMessage = "OK";

if (searchListResponse.Items == null || (!searchListResponse.Items?.Any() ?? true))
{
return Enumerable.Empty<Content>();
Expand Down Expand Up @@ -63,4 +69,7 @@ public Task StartAsync()
{
return Task.CompletedTask;
}

public Task<(SocialMediaStatus Status, string Message)> GetHealth() => Task.FromResult((_Status, _StatusMessage));

}
Loading
Loading