Skip to content

Commit

Permalink
feat(summary): AZ Func weekly task owner report worker (#714)
Browse files Browse the repository at this point in the history
- [x] New feature
- [ ] Bug fix
- [ ] High impact

**Description of work:**
<!--- Please give a description of the work --->


AB57451

Similar to resource owners report:

- This worker retrieves relevant data to create the report metrics and
then stores it in summary api

**Testing:**
- [x] Can be tested
- [x] Automatic tests created / updated
- [x] Local tests are passing

<!--- Please give a description of how this can be tested --->

Written tests for the report mertrics. Done simple tests for 2 projects

**Checklist:**
- [x] Considered automated tests
- [x] Considered updating specification / documentation
- [x] Considered work items 
- [x] Considered security
- [x] Performed developer testing
- [x] Checklist finalized / ready for review

<!--- Other comments --->
  • Loading branch information
Jonathanio123 authored Nov 22, 2024
1 parent 687eac1 commit 196c900
Show file tree
Hide file tree
Showing 15 changed files with 822 additions and 12 deletions.
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

0 comments on commit 196c900

Please sign in to comment.