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

feat(summary): AZ Func weekly task owner report worker #714

Merged
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
9d6b73d
Init WeeklyTaskOwnerReportWorker.cs
Jonathanio123 Oct 31, 2024
6e6a8b3
working
Jonathanio123 Nov 1, 2024
58b8c52
feat: Task owner report logic
Jonathanio123 Nov 4, 2024
d531e17
Added test and updated logic
Jonathanio123 Nov 5, 2024
6256b01
Merge branch 'master' into feat/summary/az-func-weekly-task-owner-rep…
Jonathanio123 Nov 7, 2024
9046803
Updated tests
Jonathanio123 Nov 11, 2024
86aaf8b
Merge branch 'master' into feat/summary/az-func-weekly-task-owner-rep…
Jonathanio123 Nov 11, 2024
11f52d6
Minor cleanup
Jonathanio123 Nov 11, 2024
213c62a
Merge branch 'master' into feat/summary/az-func-weekly-task-owner-rep…
Jonathanio123 Nov 15, 2024
018874f
Merge branch 'master' into feat/summary/az-func-weekly-task-owner-rep…
Jonathanio123 Nov 18, 2024
7ee8eaa
Removed todo
Jonathanio123 Nov 18, 2024
63c224d
Updated PositionAllocationEnding metric
Jonathanio123 Nov 19, 2024
be4608a
Updated comment and test
Jonathanio123 Nov 20, 2024
042f7ab
Merge branch 'master' into feat/summary/az-func-weekly-task-owner-rep…
Jonathanio123 Nov 20, 2024
f5c7661
Added test setup validation for GetPositionAllocationsEndingNextThree…
Jonathanio123 Nov 20, 2024
6d30e60
Merge remote-tracking branch 'origin/feat/summary/az-func-weekly-task…
Jonathanio123 Nov 20, 2024
1302688
Merge branch 'master' into feat/summary/az-func-weekly-task-owner-rep…
Jonathanio123 Nov 21, 2024
dbf1bdf
chore: Updated tests and PositionAllocation calculation
Jonathanio123 Nov 21, 2024
e8465d7
chore: Reduced weekly-department-summary-sender-parallelism default to 1
Jonathanio123 Nov 21, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public interface IOrgClient
Task<ApiChangeLog> GetChangeLog(string projectId, DateTime timestamp);

Task<List<ApiProjectV2>> GetProjectsAsync(ODataQuery? query = null, CancellationToken cancellationToken = default);
Task<ICollection<ApiPositionV2>> GetProjectPositions(string projectId, CancellationToken cancellationToken = default);
}

#region model
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
namespace Fusion.Resources.Functions.Common.ApiClients;
using Fusion.Integration.Profile;
using Fusion.Integration.Profile.ApiClient;

namespace Fusion.Resources.Functions.Common.ApiClients;

public interface IPeopleApiClient
{
Task<string> GetPersonFullDepartmentAsync(Guid? personAzureUniqueId);

Task<ICollection<ApiEnsuredProfileV2>> ResolvePersonsAsync(IEnumerable<PersonIdentifier> personAzureUniqueIds, CancellationToken cancellationToken = default);
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ public interface IResourcesApiClient
Task<IEnumerable<ApiPersonAbsence>> GetLeaveForPersonnel(string personId);
Task<IEnumerable<DelegatedresponsibleResult>> GetDelegatedResponsibleForDepartment(string departmentIdentifier);

Task<ICollection<ResourceAllocationRequest>> GetActiveRequestsForProjectAsync(Guid projectId, CancellationToken cancellationToken = default);

#region Models

public class ResourceAllocationRequest
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,16 @@ public Task PutWeeklySummaryReportAsync(string departmentSapId, ApiWeeklySummary

/// <exception cref="SummaryApiError" />
public Task<ApiProject> PutProjectAsync(ApiProject project, CancellationToken cancellationToken = default);

/// <summary>
/// When putting a weekly task owner report, it will replace the existing report for the given period based on the project id.
/// If the report does not exist, it will be created. Duration should be from Monday to Monday.
/// <para>
/// The key is the combination of the project id, period start and period end.
/// </para>
/// </summary>
/// <exception cref="SummaryApiError"></exception>
public Task PutWeeklyTaskOwnerReportAsync(Guid projectId, ApiWeeklyTaskOwnerReport report, CancellationToken cancellationToken = default);
}

#region Models
Expand All @@ -49,7 +59,6 @@ public ApiResourceOwnerDepartment()
public Guid[] ResourceOwnersAzureUniqueId { get; init; } = null!;

public Guid[] DelegateResourceOwnersAzureUniqueId { get; init; } = null!;

}

public record ApiCollection<T>(ICollection<T> Items);
Expand Down Expand Up @@ -103,4 +112,43 @@ public class ApiProject
public Guid[] AssignedAdminsAzureUniqueId { get; set; } = [];
}

public class ApiWeeklyTaskOwnerReport
{
public required Guid Id { get; set; }
public required Guid ProjectId { get; set; }
public required DateTime PeriodStart { get; set; }
public required DateTime PeriodEnd { get; set; }

public required int ActionsAwaitingTaskOwnerAction { get; set; }
public required ApiAdminAccessExpiring[] AdminAccessExpiringInLessThanThreeMonths { get; set; }
public required ApiPositionAllocationEnding[] PositionAllocationsEndingInNextThreeMonths { get; set; }
public required ApiTBNPositionStartingSoon[] TBNPositionsStartingInLessThanThreeMonths { get; set; }
}

public class ApiAdminAccessExpiring
{
public required Guid AzureUniqueId { get; set; }
public required string FullName { get; set; }
public required DateTime Expires { get; set; }
}

public class ApiPositionAllocationEnding
{
public required string PositionExternalId { get; set; }

public required string PositionName { get; set; }

public required string PositionNameDetailed { get; set; }

public required DateTime PositionAppliesTo { get; set; }
}

public class ApiTBNPositionStartingSoon
{
public required string PositionExternalId { get; set; }
public required string PositionName { get; set; }
public required string PositionNameDetailed { get; set; }
public required DateTime PositionAppliesFrom { get; set; }
}

#endregion
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,10 @@ public Task<List<ApiProjectV2>> GetProjectsAsync(ODataQuery? query = null, Cance
var url = ODataQuery.ApplyQueryString("/projects", query);
return orgClient.GetAsJsonAsync<List<ApiProjectV2>>(url, cancellationToken: cancellationToken);
}

public Task<ICollection<ApiPositionV2>> GetProjectPositions(string projectId, CancellationToken cancellationToken = default)
{
var url = $"/projects/{projectId}/positions";
return orgClient.GetAsJsonAsync<ICollection<ApiPositionV2>>(url, cancellationToken);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using Fusion.Resources.Functions.Common.Integration.Http;
using Fusion.Integration.Profile;
using Fusion.Integration.Profile.ApiClient;
using Fusion.Resources.Functions.Common.Integration.Http;

namespace Fusion.Resources.Functions.Common.ApiClients;

Expand All @@ -19,4 +21,15 @@ public async Task<string> GetPersonFullDepartmentAsync(Guid? personAzureUniqueId

return data.FullDepartment;
}

public async Task<ICollection<ApiEnsuredProfileV2>> ResolvePersonsAsync(IEnumerable<PersonIdentifier> personAzureUniqueIds, CancellationToken cancellationToken = default)
{
var resp = await peopleClient
.PostAsJsonAsync<ICollection<ApiEnsuredProfileV2>>($"/persons/ensure?api-version=3.0", new
{
personIdentifiers = personAzureUniqueIds.Select(p => p.ToString())
}, cancellationToken);

return resp;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public async Task<IEnumerable<ResourceAllocationRequest>> GetAllRequestsForDepar

return response.Value.ToList();
}
catch(Exception ex)
catch (Exception ex)
{
log.LogError($"Error getting requests for department '{departmentIdentifier}'", ex);

Expand All @@ -57,7 +57,7 @@ public async Task<IEnumerable<InternalPersonnelPerson>> GetAllPersonnelForDepart
try
{
var response = await resourcesClient.GetAsJsonAsync<InternalCollection<InternalPersonnelPerson>>(
$"departments/{departmentIdentifier}/resources/personnel?api-version=2.0&$includeCurrentAllocations=true");
$"departments/{departmentIdentifier}/resources/personnel?api-version=2.0&$includeCurrentAllocations=true");

return response.Value.ToList();
}
Expand Down Expand Up @@ -97,20 +97,28 @@ public async Task<bool> ReassignRequestAsync(ResourceAllocationRequest item, str
}

public async Task<IEnumerable<DelegatedresponsibleResult>> GetDelegatedResponsibleForDepartment(string departmentIdentifier)
{
{
var response = await resourcesClient.GetAsJsonAsync<List<DelegatedresponsibleResult>>($"departments/{departmentIdentifier}/delegated-resource-owners");

return response;
}

public async Task<ICollection<ResourceAllocationRequest>> GetActiveRequestsForProjectAsync(Guid projectId, CancellationToken cancellationToken = default)
{
var response = await resourcesClient
.GetAsJsonAsync<InternalCollection<ResourceAllocationRequest>>($"projects/{projectId}/resources/requests?$Filter=state neq 'completed'&$top={int.MaxValue}", cancellationToken: cancellationToken);

return response.Value;
}

internal class InternalCollection<T>
{
public InternalCollection(IEnumerable<T> items)
public InternalCollection(ICollection<T> items)
{
Value = items;
}

public IEnumerable<T> Value { get; set; }
public ICollection<T> Value { get; set; }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,15 @@ public async Task<ICollection<ApiProject>> GetProjectsAsync(CancellationToken ca
jsonSerializerOptions, cancellationToken: cancellationToken) ?? [];
}

public async Task PutWeeklyTaskOwnerReportAsync(Guid projectId, ApiWeeklyTaskOwnerReport report, CancellationToken cancellationToken = default)
{
using var body = new JsonContent(JsonSerializer.Serialize(report, jsonSerializerOptions));

using var response = await summaryClient.PutAsync($"projects/{projectId}/task-owners-summary-reports/weekly", body, cancellationToken);

await ThrowIfUnsuccessfulAsync(response);
}

private async Task ThrowIfUnsuccessfulAsync(HttpResponseMessage response)
=> await response.ThrowIfUnsuccessfulAsync((responseBody) => new SummaryApiError(response, responseBody));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
<PackageReference Include="Fusion.Integration" Version="8.0.8"/>
<PackageReference Include="Fusion.Events.Azure.Functions.Extensions" Version="6.0.5"/>
<PackageReference Include="Fusion.Events.Services" Version="8.0.2"/>
<PackageReference Include="Fusion.Services.Org.ApiModels" Version="8.0.5"/>
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.1"/>
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="8.0.10"/>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Fusion.Resources.Functions.Common.Integration.Errors;
using System.Text;
using Fusion.Resources.Functions.Common.Integration.Errors;
using Newtonsoft.Json;

namespace Fusion.Resources.Functions.Common.Integration.Http
Expand All @@ -17,6 +18,22 @@ public static async Task<T> GetAsJsonAsync<T>(this HttpClient client, string url
return deserialized;
}

public static async Task<TResponse> PostAsJsonAsync<TResponse>(this HttpClient client, string url, object data, CancellationToken cancellationToken = default)
{
var json = JsonConvert.SerializeObject(data);
var content = new StringContent(json, Encoding.UTF8, "application/json");

var response = await client.PostAsync(url, content, cancellationToken);

var body = await response.Content.ReadAsStringAsync(cancellationToken);

if (!response.IsSuccessStatusCode)
throw new ApiError(response.RequestMessage!.RequestUri!.ToString(), response.StatusCode, body, "Response from API call indicates error");

TResponse deserialized = JsonConvert.DeserializeObject<TResponse>(body);
return deserialized;
}

public static async Task<IEnumerable<string>> OptionsAsync(this HttpClient client, string url, CancellationToken cancellationToken = default)
{
var message = new HttpRequestMessage(HttpMethod.Options, url);
Expand All @@ -26,6 +43,5 @@ public static async Task<IEnumerable<string>> OptionsAsync(this HttpClient clien

return allowHeaders;
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public class ResourceOwnerReportsController : BaseController
{
[HttpGet("resource-owners-summary-reports/{sapDepartmentId}/weekly")]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status202Accepted)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ODataFilter(nameof(ApiWeeklySummaryReport.Period))]
Expand Down
Loading
Loading