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

♻️ Move Graph client to DI #65

Merged
merged 3 commits into from
Oct 10, 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
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"dotnet.defaultSolution": "TimesheetGPT.sln"
}
48 changes: 28 additions & 20 deletions src/TimesheetGPT.Core/Services/GraphService.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
using Microsoft.Graph;
using Microsoft.Graph.Models;
using Microsoft.Graph.Models.ODataErrors;
using TimesheetGPT.Core.Interfaces;
using TimesheetGPT.Core.Models;

namespace TimesheetGPT.Core.Services;

public class GraphService : IGraphService
{
private GraphServiceClient _client;
private readonly GraphServiceClient _client;

public GraphService(GraphServiceClient client)
{
if (client == null)
throw new ArgumentNullException(nameof(client));
ArgumentNullException.ThrowIfNull(client);

_client = client ?? throw new ArgumentNullException(nameof(client));
}
Expand All @@ -24,7 +24,6 @@
var dateUtc = date.ToUniversalTime();
var nextDayUtc = nextDay.ToUniversalTime();

// GET https://graph.microsoft.com/v1.0/me/mailFolders('sentitems')/messages
var messages = await _client.Me.MailFolders["sentitems"].Messages
.GetAsync(rc =>
{
Expand All @@ -40,7 +39,7 @@

if (messages is { Value.Count: > 1 })
{
return messages.Value.Select(m => m.Subject).ToList();

Check warning on line 42 in src/TimesheetGPT.Core/Services/GraphService.cs

View workflow job for this annotation

GitHub Actions / build

Nullability of reference types in value of type 'List<string?>' doesn't match target type 'List<string>'.
}

return new List<string>(); //slack
Expand All @@ -52,7 +51,7 @@
var nextDay = date.AddDays(1);
var dateUtc = date.ToUniversalTime();
var nextDayUtc = nextDay.ToUniversalTime();

var meetings = await _client.Me.CalendarView.GetAsync(rc =>
{
rc.QueryParameters.Top = 999;
Expand All @@ -61,39 +60,48 @@
rc.QueryParameters.Orderby = new[] { "start/dateTime" };
rc.QueryParameters.Select = new[] { "subject", "start", "end", "occurrenceId" };
});

if (meetings is { Value.Count: > 1 })
{
return meetings.Value.Select(m => new Meeting
{
Name = m.Subject,

Check warning on line 68 in src/TimesheetGPT.Core/Services/GraphService.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference assignment.
Length = DateTime.Parse(m.End.DateTime) - DateTime.Parse(m.Start.DateTime),
Repeating = m.Type == EventType.Occurrence,
// Sender = m.EmailAddress.Address TODO: Why is Organizer and attendees null? permissions?
}).ToList();
}

return new List<Meeting>(); //slack
}
public async Task<List<TeamsCall>> GetTeamsCalls(DateTime date) {
public async Task<List<TeamsCall>> GetTeamsCalls(DateTime date)
{
var nextDay = date.AddDays(1);
var dateUtc = date.ToUniversalTime();
var nextDayUtc = nextDay.ToUniversalTime();

var calls = await _client.Communications.CallRecords.GetAsync(rc =>
{
rc.QueryParameters.Top = 999;
rc.QueryParameters.Orderby = new[] { "start/dateTime" };
rc.QueryParameters.Select = new[] { "subject", "start", "end", "occurrenceId" };
});

if (calls is { Value.Count: > 1 })
try
{
return calls.Value.Select(m => new TeamsCall
var calls = await _client.Communications.CallRecords.GetAsync(rc =>
{
Attendees = m.Participants.Select(p => p.User.DisplayName).ToList(),
Length = m.EndDateTime - m.StartDateTime ?? TimeSpan.Zero,
}).ToList();
rc.QueryParameters.Top = 999;
rc.QueryParameters.Orderby = new[] { "startDateTime" };
rc.QueryParameters.Select = new[] { "startDateTime", "endDateTime", "participants" };
rc.QueryParameters.Filter = $"startDateTime ge {dateUtc:o} and endDateTime lt {nextDayUtc:o}";
});

if (calls is { Value.Count: > 1 })
{
return calls.Value.Select(m => new TeamsCall
{
Attendees = m.Participants.Select(p => p.User.DisplayName).ToList(),
Length = m.EndDateTime - m.StartDateTime ?? TimeSpan.Zero,
}).ToList();
}
}
catch (ODataError e)
{
throw new Exception("Need CallRecords.Read.All scopes", e);
}

return new List<TeamsCall>();
Expand All @@ -102,6 +110,6 @@

public class TeamsCall
{
public List<string> Attendees { get; set; }

Check warning on line 113 in src/TimesheetGPT.Core/Services/GraphService.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'Attendees' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.
public TimeSpan Length { get; set; }
}
8 changes: 2 additions & 6 deletions src/TimesheetGPT.Core/Services/SemKerAiService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,13 @@ public class SemKerAiService : IAiService
private readonly string _apiKey;
public SemKerAiService(IConfiguration configuration)
{
if (configuration == null)
throw new ArgumentNullException(nameof(configuration));

ArgumentNullException.ThrowIfNull(configuration);

_apiKey = configuration["OpenAI:ApiKey"] ?? "";
}

public async Task<string> GetSummary(string text, string extraPrompts, string additionalNotes = "")
{
Console.WriteLine(text);
var builder = new KernelBuilder();

// builder.WithAzureChatCompletionService(
Expand All @@ -43,8 +41,6 @@ public async Task<string> GetSummary(string text, string extraPrompts, string ad

var summary = await summarizeFunction.InvokeAsync(context);

Console.WriteLine(summary.ModelResults);

return summary.Result;
}
}
20 changes: 8 additions & 12 deletions src/TimesheetGPT.Core/Services/TimesheetService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,18 @@

namespace TimesheetGPT.Core.Services;

public class TimesheetService
public class TimesheetService(IAiService aiService, GraphServiceClient graphServiceClient)
{
private readonly IAiService _aiService;

public TimesheetService(IAiService aiService)
public async Task<SummaryWithRaw> GenerateSummary(DateTime date, string extraPrompts = "", string additionalNotes = "")
{
_aiService = aiService;
}

public async Task<SummaryWithRaw> GenerateSummary(DateTime date, GraphServiceClient client, string extraPrompts = "", string additionalNotes = "")
{
IGraphService graphService = new GraphService(client);
var graphService = new GraphService(graphServiceClient);

var emailSubjects = await graphService.GetEmailSubjects(date);
var meetings = await graphService.GetMeetings(date);
// var calls = await graphService.GetTeamsCalls(date);
// TODO: SSW needs to allow the CallRecords.Read.All scope for this to work

var summary = await _aiService.GetSummary(StringifyData(emailSubjects, meetings), extraPrompts, additionalNotes);
var summary = await aiService.GetSummary(StringifyData(emailSubjects, meetings), extraPrompts, additionalNotes);

return new SummaryWithRaw
{
Expand All @@ -32,14 +27,15 @@ public async Task<SummaryWithRaw> GenerateSummary(DateTime date, GraphServiceCli
};
}

private string StringifyData(List<string> emails, List<Meeting> meetings)
private string StringifyData(IEnumerable<string> emails, IList<Meeting> meetings)
{
var result = "Sent emails (subject) \n";
foreach (var email in emails)
{
result += email + "\n";
}
result += "\n Calendar Events (name - length) \n";

foreach (var meeting in meetings)
{
result += $"{meeting.Name} - {meeting.Length} \n";
Expand Down
5 changes: 1 addition & 4 deletions src/TimesheetGPT.Gateway/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,12 @@
builder.Configuration.AddUserSecrets(typeof(Program).Assembly);

builder.Services.AddTimesheetGptUi(builder.Configuration);
/*
builder.Services.AddTimesheetGptApi(builder.Configuration);
*/

var app = builder.Build();

/*
app.UseTimesheetGptApi();
*/

app.UseTimesheetGptUi();

app.Run();
34 changes: 6 additions & 28 deletions src/TimesheetGPT.WebAPI/DependencyInjection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Identity.Web;

using TimesheetGPT.WebAPI.Endpoints;

namespace TimesheetGPT.WebAPI;
Expand All @@ -11,44 +11,22 @@ public static class DependencyInjection
{
public static IServiceCollection AddTimesheetGptApi(this IServiceCollection services, IConfiguration config)
{
// Add services to the container.
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
services.AddEndpointsApiExplorer();
services.AddSwaggerGen();

services.AddMicrosoftIdentityWebApiAuthentication(config, "AzureAd")
.EnableTokenAcquisitionToCallDownstreamApi()
.AddInMemoryTokenCaches();

// Get tenantId, clientId and clientSecret from appsettings.json
// and pass them to GraphService
//
// var tenantId = configuration["AzureAd:TenantId"];
// var clientId = configuration["AzureAd:ClientId"];
// var clientSecret = configuration["AzureAd:ClientSecret"];
//
// // ensure the above values are set
// if (string.IsNullOrEmpty(tenantId) ||
// string.IsNullOrEmpty(clientId) ||
// string.IsNullOrEmpty(clientSecret))
// {
// throw new Exception("TenantId, ClientId and ClientSecret must be set in appsettings.json");
// }


return services;
}

public static WebApplication UseTimesheetGptApi(this WebApplication app)
{
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}

app.MapGetSubjects();

app.MapGetSubjects();

return app;
}
}
}
2 changes: 1 addition & 1 deletion src/TimesheetGPT.WebUI/Pages/Index.razor
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@
return;
}
var dateTime = _date ?? DateTime.Today;
var summary = await TimesheetService.GenerateSummary(dateTime, GraphServiceClient, _extraPrompt ?? "", _additionalNotes);
var summary = await TimesheetService.GenerateSummary(dateTime, _extraPrompt ?? "", _additionalNotes);

_emailSubjects = summary.Emails;
_meetings = summary.Meetings;
Expand Down
1 change: 0 additions & 1 deletion src/TimesheetGPT.WebUI/Shared/ResultCard.razor
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@
catch (Exception e)
{
Snackbar.Add("Failed to copy summary to clipboard", Severity.Warning);
Console.WriteLine(e.Message);
}
}
}
Loading