diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..9af5e2fa --- /dev/null +++ b/.editorconfig @@ -0,0 +1,14 @@ +root = true + +[*] +indent_style = space + +[*.cs] +indent_size = 4 +dotnet_style_predefined_type_for_locals_parameters_members = true : warning +csharp_new_line_before_open_brace = all +csharp_space_after_keywords_in_control_flow_statements = true + +[*.razor] +indent_size = 4 + diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.BL/Helpers/DateHelper.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.BL/Helpers/DateHelper.cs index fee137d7..9a8b5fea 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.BL/Helpers/DateHelper.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.BL/Helpers/DateHelper.cs @@ -2,26 +2,26 @@ namespace AIForOrcas.Client.BL.Helpers { - public static class DateHelper - { - public static string UTCToPDT(DateTime datetime, bool timeOnly = false) - { - TimeZoneInfo pst = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time"); - datetime = DateTime.SpecifyKind(datetime, DateTimeKind.Utc); - DateTime pstTime = TimeZoneInfo.ConvertTime(datetime, TimeZoneInfo.Utc, pst); - var zoneString = pst.IsDaylightSavingTime(pstTime) ? "PDT" : "PST"; - var format = timeOnly ? $"HH:mm:ss" : $"dd MMM HH:mm:ss '{zoneString}'"; + public static class DateHelper + { + public static string UTCToPDT(DateTime datetime, bool timeOnly = false) + { + TimeZoneInfo pst = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time"); + datetime = DateTime.SpecifyKind(datetime, DateTimeKind.Utc); + DateTime pstTime = TimeZoneInfo.ConvertTime(datetime, TimeZoneInfo.Utc, pst); + var zoneString = pst.IsDaylightSavingTime(pstTime) ? "PDT" : "PST"; + var format = timeOnly ? $"HH:mm:ss" : $"dd MMM HH:mm:ss '{zoneString}'"; return $"{pstTime.ToString(format)}"; } - public static string UTCToPDTFull(DateTime datetime) - { - TimeZoneInfo pst = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time"); - datetime = DateTime.SpecifyKind(datetime, DateTimeKind.Utc); - DateTime pstTime = TimeZoneInfo.ConvertTime(datetime, TimeZoneInfo.Utc, pst); - var zoneString = pst.IsDaylightSavingTime(pstTime) ? "PDT" : "PST"; - var format = $"dd MMM yyyy HH:mm:ss '{zoneString}'"; - return $"{pstTime.ToString(format)}"; - } - } + public static string UTCToPDTFull(DateTime datetime) + { + TimeZoneInfo pst = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time"); + datetime = DateTime.SpecifyKind(datetime, DateTimeKind.Utc); + DateTime pstTime = TimeZoneInfo.ConvertTime(datetime, TimeZoneInfo.Utc, pst); + var zoneString = pst.IsDaylightSavingTime(pstTime) ? "PDT" : "PST"; + var format = $"dd MMM yyyy HH:mm:ss '{zoneString}'"; + return $"{pstTime.ToString(format)}"; + } + } } diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.BL/Helpers/EmailHelper.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.BL/Helpers/EmailHelper.cs index 122b9653..63d143c8 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.BL/Helpers/EmailHelper.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.BL/Helpers/EmailHelper.cs @@ -1,21 +1,21 @@ namespace AIForOrcas.Client.BL.Helpers { - public static class EmailHelper - { - public static string ExtractName(string email) - { + public static class EmailHelper + { + public static string ExtractName(string email) + { if (string.IsNullOrEmpty(email) || (!email.Contains("@") && !email.Contains("#"))) return email; - var working = email; - if (working.Contains("@")) - working = working.Split('@')[0]; + var working = email; + if (working.Contains("@")) + working = working.Split('@')[0]; - if (working.Contains("#")) - working = working.Split('#')[1]; + if (working.Contains("#")) + working = working.Split('#')[1]; - return working; - } - } + return working; + } + } } diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.BL/Services/AuthTokenProviderExtensions.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.BL/Services/AuthTokenProviderExtensions.cs index f376801e..0d8ed638 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.BL/Services/AuthTokenProviderExtensions.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.BL/Services/AuthTokenProviderExtensions.cs @@ -3,13 +3,13 @@ namespace AIForOrcas.Client.BL.Services { - public static class AuthTokenProviderExtensions - { - public static void ApplyToken(this IAuthTokenProvider provider, HttpRequestMessage request) - { - var token = provider.GetToken(); - if (!string.IsNullOrWhiteSpace(token)) - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); - } - } + public static class AuthTokenProviderExtensions + { + public static void ApplyToken(this IAuthTokenProvider provider, HttpRequestMessage request) + { + var token = provider.GetToken(); + if (!string.IsNullOrWhiteSpace(token)) + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); + } + } } diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.BL/Services/DetectionService.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.BL/Services/DetectionService.cs index 1f6990e0..fdd93cdc 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.BL/Services/DetectionService.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.BL/Services/DetectionService.cs @@ -11,200 +11,200 @@ namespace AIForOrcas.Client.BL.Services { - public class DetectionService : IDetectionService - { - private string api = "api/detections"; - private JsonSerializerOptions defaultJsonSerializerOptions => new JsonSerializerOptions() { PropertyNameCaseInsensitive = true }; - private readonly IHttpClientFactory _httpClientFactory; - private readonly IAuthTokenProvider _authTokenProvider; - private readonly ILogger _logger; - - public DetectionService(IHttpClientFactory httpClientFactory, IAuthTokenProvider authTokenProvider, ILogger logger) - { - _httpClientFactory = httpClientFactory; - _authTokenProvider = authTokenProvider; - _logger = logger; - } - - // Get detections based on passed view, pagination options, and filter options - private async Task>> GetDetectionsAsync(string viewName, PaginationOptionsDTO paginationOptions, IFilterOptions filterOptions) - { - var prefix = api.Contains("?") ? $"{api}/{viewName}&" : $"{api}/{viewName}?"; - var url = $"{prefix}{paginationOptions.QueryString}&{filterOptions.QueryString}"; - - // Create client on-demand from the current scope. - var httpClient = _httpClientFactory.CreateClient("UnauthenticatedAPI"); - - HttpResponseMessage httpResponseMessage; - try - { - httpResponseMessage = await httpClient.GetAsync(url); - } - catch (Exception exception) when (exception is HttpRequestException || exception is TaskCanceledException) - { - // An unreachable or hung API must degrade like a failed status - // code; an unhandled exception here would take down the whole - // circuit. - _logger.LogError(exception, "Unable to reach the detections API at {Url}", url); - return new PaginatedResponseDTO> { Response = null, TotalAmountPages = 0, TotalNumberRecords = 0 }; - } - - if (httpResponseMessage.IsSuccessStatusCode) - { - var responseString = await httpResponseMessage.Content.ReadAsStringAsync(); - - if(string.IsNullOrWhiteSpace(responseString)) - return new PaginatedResponseDTO> { Response = new List(), TotalAmountPages = 0, TotalNumberRecords = 0 }; - - // The pagination headers are not guaranteed; a response without - // them should not kill the page. - httpResponseMessage.Headers.TryGetValues("totalAmountPages", out var pageValues); - httpResponseMessage.Headers.TryGetValues("totalNumberRecords", out var recordValues); - int.TryParse(pageValues?.FirstOrDefault(), out var totalAmountPages); - int.TryParse(recordValues?.FirstOrDefault(), out var totalNumberRecords); - - try - { - return new PaginatedResponseDTO> - { - Response = JsonSerializer.Deserialize>(responseString, defaultJsonSerializerOptions), - TotalAmountPages = totalAmountPages, - TotalNumberRecords = totalNumberRecords - }; - } - catch (JsonException exception) - { - _logger.LogError(exception, "Malformed response from the detections API at {Url}", url); - return new PaginatedResponseDTO> { Response = null, TotalAmountPages = 0, TotalNumberRecords = 0 }; - } - } - else - { - return new PaginatedResponseDTO> { Response = null, TotalAmountPages = 0, TotalNumberRecords = 0 }; - } - - } - - // Get unreviewed detections - public async Task>> GetCandidateDetectionsAsync(PaginationOptionsDTO paginationOptions, IFilterOptions filterOptions) - { - return await GetDetectionsAsync("unreviewed", paginationOptions, filterOptions); - } - - public async Task>> GetConfirmedDetectionsAsync(PaginationOptionsDTO paginationOptions, IFilterOptions filterOptions) - { - return await GetDetectionsAsync("confirmed", paginationOptions, filterOptions); - } - - public async Task>> GetFalseDetectionsAsync(PaginationOptionsDTO paginationOptions, IFilterOptions filterOptions) - { - return await GetDetectionsAsync("falsepositives", paginationOptions, filterOptions); - } - - public async Task>> GetUnconfirmedDetectionsAsync(PaginationOptionsDTO paginationOptions, IFilterOptions filterOptions) - { - return await GetDetectionsAsync("unknowns", paginationOptions, filterOptions); - } - - public async Task UpdateRequestAsync(DetectionUpdate request) - { - var url = $"{api}/{request.Id}"; - var dataJson = JsonSerializer.Serialize(request); - var stringContent = new StringContent(dataJson, Encoding.UTF8, "application/json"); - - var httpClient = _httpClientFactory.CreateClient("AuthenticatedAPI"); - var httpRequest = new HttpRequestMessage(HttpMethod.Put, url) { Content = stringContent }; - - _authTokenProvider.ApplyToken(httpRequest); - - HttpResponseMessage httpResponseMessage; - try - { - httpResponseMessage = await httpClient.SendAsync(httpRequest); - } - catch (TaskCanceledException exception) - { - // A timed-out PUT surfaces as a canceled task. Rethrow it as a - // request failure so callers handle one exception type for "the - // update was lost", whether the API refused or timed out. - _logger.LogError(exception, "Timed out updating the detection at {Url}", url); - throw new HttpRequestException( - $"Failed to update detection. The request to {url} timed out.", exception); - } - catch (HttpRequestException exception) - { - // The UI suppresses this into a toast, so log it here or the - // failure never reaches the server diagnostics. - _logger.LogError(exception, "Unable to reach the detections API to update at {Url}", url); - throw; - } - - if (!httpResponseMessage.IsSuccessStatusCode) - { - var errorContent = await httpResponseMessage.Content.ReadAsStringAsync(); - var statusCode = (int)httpResponseMessage.StatusCode; - - _logger.LogError("The detections API rejected the update at {Url} with {StatusCode}: {Details}", - url, statusCode, errorContent); - throw new HttpRequestException( - $"Failed to update detection. Status: {statusCode} {httpResponseMessage.ReasonPhrase}. Details: {errorContent}"); - } - } - - public async Task GetDetectionAsync(string id) - { - var url = $"{api}/{id}"; - - // Create client on-demand from the current scope. - var httpClient = _httpClientFactory.CreateClient("UnauthenticatedAPI"); - - HttpResponseMessage httpResponseMessage; - try - { - httpResponseMessage = await httpClient.GetAsync(url); - } - catch (Exception exception) when (exception is HttpRequestException || exception is TaskCanceledException) - { - // Null means the API could not be reached, so the page can say - // so instead of misreporting the detection as missing. - _logger.LogError(exception, "Unable to reach the detections API at {Url}", url); - return null; - } - - if (httpResponseMessage.IsSuccessStatusCode) - { - var responseString = await httpResponseMessage.Content.ReadAsStringAsync(); - - if (string.IsNullOrWhiteSpace(responseString)) - { - return new Detection(); - } - - try - { - var response = JsonSerializer.Deserialize(responseString, defaultJsonSerializerOptions); - - return response ?? new Detection(); - } - catch (JsonException exception) - { - _logger.LogError(exception, "Malformed response from the detections API at {Url}", url); - return null; - } - } - else if (httpResponseMessage.StatusCode == System.Net.HttpStatusCode.NotFound) - { - // The API answered 404: this id genuinely has no detection. - return new Detection(); - } - else - { - // Any other error status is the API failing, not a missing - // record; report it like an unreachable API. - _logger.LogError("The detections API returned {StatusCode} at {Url}", - (int)httpResponseMessage.StatusCode, url); - return null; - } - } - } + public class DetectionService : IDetectionService + { + private string api = "api/detections"; + private JsonSerializerOptions defaultJsonSerializerOptions => new JsonSerializerOptions() { PropertyNameCaseInsensitive = true }; + private readonly IHttpClientFactory _httpClientFactory; + private readonly IAuthTokenProvider _authTokenProvider; + private readonly ILogger _logger; + + public DetectionService(IHttpClientFactory httpClientFactory, IAuthTokenProvider authTokenProvider, ILogger logger) + { + _httpClientFactory = httpClientFactory; + _authTokenProvider = authTokenProvider; + _logger = logger; + } + + // Get detections based on passed view, pagination options, and filter options + private async Task>> GetDetectionsAsync(string viewName, PaginationOptionsDTO paginationOptions, IFilterOptions filterOptions) + { + var prefix = api.Contains("?") ? $"{api}/{viewName}&" : $"{api}/{viewName}?"; + var url = $"{prefix}{paginationOptions.QueryString}&{filterOptions.QueryString}"; + + // Create client on-demand from the current scope. + var httpClient = _httpClientFactory.CreateClient("UnauthenticatedAPI"); + + HttpResponseMessage httpResponseMessage; + try + { + httpResponseMessage = await httpClient.GetAsync(url); + } + catch (Exception exception) when (exception is HttpRequestException || exception is TaskCanceledException) + { + // An unreachable or hung API must degrade like a failed status + // code; an unhandled exception here would take down the whole + // circuit. + _logger.LogError(exception, "Unable to reach the detections API at {Url}", url); + return new PaginatedResponseDTO> { Response = null, TotalAmountPages = 0, TotalNumberRecords = 0 }; + } + + if (httpResponseMessage.IsSuccessStatusCode) + { + var responseString = await httpResponseMessage.Content.ReadAsStringAsync(); + + if (string.IsNullOrWhiteSpace(responseString)) + return new PaginatedResponseDTO> { Response = new List(), TotalAmountPages = 0, TotalNumberRecords = 0 }; + + // The pagination headers are not guaranteed; a response without + // them should not kill the page. + httpResponseMessage.Headers.TryGetValues("totalAmountPages", out var pageValues); + httpResponseMessage.Headers.TryGetValues("totalNumberRecords", out var recordValues); + int.TryParse(pageValues?.FirstOrDefault(), out var totalAmountPages); + int.TryParse(recordValues?.FirstOrDefault(), out var totalNumberRecords); + + try + { + return new PaginatedResponseDTO> + { + Response = JsonSerializer.Deserialize>(responseString, defaultJsonSerializerOptions), + TotalAmountPages = totalAmountPages, + TotalNumberRecords = totalNumberRecords + }; + } + catch (JsonException exception) + { + _logger.LogError(exception, "Malformed response from the detections API at {Url}", url); + return new PaginatedResponseDTO> { Response = null, TotalAmountPages = 0, TotalNumberRecords = 0 }; + } + } + else + { + return new PaginatedResponseDTO> { Response = null, TotalAmountPages = 0, TotalNumberRecords = 0 }; + } + + } + + // Get unreviewed detections + public async Task>> GetCandidateDetectionsAsync(PaginationOptionsDTO paginationOptions, IFilterOptions filterOptions) + { + return await GetDetectionsAsync("unreviewed", paginationOptions, filterOptions); + } + + public async Task>> GetConfirmedDetectionsAsync(PaginationOptionsDTO paginationOptions, IFilterOptions filterOptions) + { + return await GetDetectionsAsync("confirmed", paginationOptions, filterOptions); + } + + public async Task>> GetFalseDetectionsAsync(PaginationOptionsDTO paginationOptions, IFilterOptions filterOptions) + { + return await GetDetectionsAsync("falsepositives", paginationOptions, filterOptions); + } + + public async Task>> GetUnconfirmedDetectionsAsync(PaginationOptionsDTO paginationOptions, IFilterOptions filterOptions) + { + return await GetDetectionsAsync("unknowns", paginationOptions, filterOptions); + } + + public async Task UpdateRequestAsync(DetectionUpdate request) + { + var url = $"{api}/{request.Id}"; + var dataJson = JsonSerializer.Serialize(request); + var stringContent = new StringContent(dataJson, Encoding.UTF8, "application/json"); + + var httpClient = _httpClientFactory.CreateClient("AuthenticatedAPI"); + var httpRequest = new HttpRequestMessage(HttpMethod.Put, url) { Content = stringContent }; + + _authTokenProvider.ApplyToken(httpRequest); + + HttpResponseMessage httpResponseMessage; + try + { + httpResponseMessage = await httpClient.SendAsync(httpRequest); + } + catch (TaskCanceledException exception) + { + // A timed-out PUT surfaces as a canceled task. Rethrow it as a + // request failure so callers handle one exception type for "the + // update was lost", whether the API refused or timed out. + _logger.LogError(exception, "Timed out updating the detection at {Url}", url); + throw new HttpRequestException( + $"Failed to update detection. The request to {url} timed out.", exception); + } + catch (HttpRequestException exception) + { + // The UI suppresses this into a toast, so log it here or the + // failure never reaches the server diagnostics. + _logger.LogError(exception, "Unable to reach the detections API to update at {Url}", url); + throw; + } + + if (!httpResponseMessage.IsSuccessStatusCode) + { + var errorContent = await httpResponseMessage.Content.ReadAsStringAsync(); + var statusCode = (int)httpResponseMessage.StatusCode; + + _logger.LogError("The detections API rejected the update at {Url} with {StatusCode}: {Details}", + url, statusCode, errorContent); + throw new HttpRequestException( + $"Failed to update detection. Status: {statusCode} {httpResponseMessage.ReasonPhrase}. Details: {errorContent}"); + } + } + + public async Task GetDetectionAsync(string id) + { + var url = $"{api}/{id}"; + + // Create client on-demand from the current scope. + var httpClient = _httpClientFactory.CreateClient("UnauthenticatedAPI"); + + HttpResponseMessage httpResponseMessage; + try + { + httpResponseMessage = await httpClient.GetAsync(url); + } + catch (Exception exception) when (exception is HttpRequestException || exception is TaskCanceledException) + { + // Null means the API could not be reached, so the page can say + // so instead of misreporting the detection as missing. + _logger.LogError(exception, "Unable to reach the detections API at {Url}", url); + return null; + } + + if (httpResponseMessage.IsSuccessStatusCode) + { + var responseString = await httpResponseMessage.Content.ReadAsStringAsync(); + + if (string.IsNullOrWhiteSpace(responseString)) + { + return new Detection(); + } + + try + { + var response = JsonSerializer.Deserialize(responseString, defaultJsonSerializerOptions); + + return response ?? new Detection(); + } + catch (JsonException exception) + { + _logger.LogError(exception, "Malformed response from the detections API at {Url}", url); + return null; + } + } + else if (httpResponseMessage.StatusCode == System.Net.HttpStatusCode.NotFound) + { + // The API answered 404: this id genuinely has no detection. + return new Detection(); + } + else + { + // Any other error status is the API failing, not a missing + // record; report it like an unreachable API. + _logger.LogError("The detections API returned {StatusCode} at {Url}", + (int)httpResponseMessage.StatusCode, url); + return null; + } + } + } } diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.BL/Services/IAuthTokenProvider.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.BL/Services/IAuthTokenProvider.cs index a81cdcae..8de81052 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.BL/Services/IAuthTokenProvider.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.BL/Services/IAuthTokenProvider.cs @@ -1,7 +1,7 @@ namespace AIForOrcas.Client.BL.Services { - public interface IAuthTokenProvider - { - string GetToken(); - } + public interface IAuthTokenProvider + { + string GetToken(); + } } diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.BL/Services/IDetectionService.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.BL/Services/IDetectionService.cs index 438e3ce0..d8e83585 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.BL/Services/IDetectionService.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.BL/Services/IDetectionService.cs @@ -5,14 +5,14 @@ namespace AIForOrcas.Client.BL.Services { - public interface IDetectionService - { - Task>> GetCandidateDetectionsAsync(PaginationOptionsDTO paginationOptions, IFilterOptions filterOptions); - Task>> GetConfirmedDetectionsAsync(PaginationOptionsDTO pagination, IFilterOptions filterOptions); - Task>> GetUnconfirmedDetectionsAsync(PaginationOptionsDTO pagination, IFilterOptions filterOptions); - Task>> GetFalseDetectionsAsync(PaginationOptionsDTO pagination, IFilterOptions filterOptions); + public interface IDetectionService + { + Task>> GetCandidateDetectionsAsync(PaginationOptionsDTO paginationOptions, IFilterOptions filterOptions); + Task>> GetConfirmedDetectionsAsync(PaginationOptionsDTO pagination, IFilterOptions filterOptions); + Task>> GetUnconfirmedDetectionsAsync(PaginationOptionsDTO pagination, IFilterOptions filterOptions); + Task>> GetFalseDetectionsAsync(PaginationOptionsDTO pagination, IFilterOptions filterOptions); - Task GetDetectionAsync(string id); - Task UpdateRequestAsync(DetectionUpdate request); - } + Task GetDetectionAsync(string id); + Task UpdateRequestAsync(DetectionUpdate request); + } } diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.BL/Services/IMetricsService.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.BL/Services/IMetricsService.cs index e81c64d4..89493f7a 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.BL/Services/IMetricsService.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.BL/Services/IMetricsService.cs @@ -4,10 +4,10 @@ namespace AIForOrcas.Client.BL.Services { - public interface IMetricsService - { + public interface IMetricsService + { - Task GetSiteMetricsAsync(IFilterOptions filterOptions); - Task GetModeratorMetricsAsync(IFilterOptions filterOptions); - } + Task GetSiteMetricsAsync(IFilterOptions filterOptions); + Task GetModeratorMetricsAsync(IFilterOptions filterOptions); + } } diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.BL/Services/MetricsService.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.BL/Services/MetricsService.cs index 7b241b06..71dd5ab0 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.BL/Services/MetricsService.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.BL/Services/MetricsService.cs @@ -6,67 +6,67 @@ namespace AIForOrcas.Client.BL.Services { - public class MetricsService : IMetricsService - { - private readonly IHttpClientFactory _httpClientFactory; - private string api = "api/metrics"; - private JsonSerializerOptions defaultJsonSerializerOptions => new JsonSerializerOptions() { PropertyNameCaseInsensitive = true }; + public class MetricsService : IMetricsService + { + private readonly IHttpClientFactory _httpClientFactory; + private string api = "api/metrics"; + private JsonSerializerOptions defaultJsonSerializerOptions => new JsonSerializerOptions() { PropertyNameCaseInsensitive = true }; - public MetricsService(IHttpClientFactory httpClientFactory) - { - _httpClientFactory = httpClientFactory; - } + public MetricsService(IHttpClientFactory httpClientFactory) + { + _httpClientFactory = httpClientFactory; + } - public async Task GetModeratorMetricsAsync(IFilterOptions filterOptions) - { - var prefix = api.Contains("?") ? $"{api}/moderator&" : $"{api}/moderator?"; - var url = $"{prefix}{filterOptions.QueryString}"; + public async Task GetModeratorMetricsAsync(IFilterOptions filterOptions) + { + var prefix = api.Contains("?") ? $"{api}/moderator&" : $"{api}/moderator?"; + var url = $"{prefix}{filterOptions.QueryString}"; - var httpClient = _httpClientFactory.CreateClient("UnauthenticatedAPI"); - var httpResponseMessage = await httpClient.GetAsync(url); + var httpClient = _httpClientFactory.CreateClient("UnauthenticatedAPI"); + var httpResponseMessage = await httpClient.GetAsync(url); - if (httpResponseMessage.IsSuccessStatusCode) - { - var responseString = await httpResponseMessage.Content.ReadAsStringAsync(); + if (httpResponseMessage.IsSuccessStatusCode) + { + var responseString = await httpResponseMessage.Content.ReadAsStringAsync(); - if (httpResponseMessage.StatusCode == System.Net.HttpStatusCode.NoContent) - return new ModeratorMetrics() { HasContent = false }; + if (httpResponseMessage.StatusCode == System.Net.HttpStatusCode.NoContent) + return new ModeratorMetrics() { HasContent = false }; - var response = JsonSerializer.Deserialize(responseString, defaultJsonSerializerOptions); - response.HasContent = true; + var response = JsonSerializer.Deserialize(responseString, defaultJsonSerializerOptions); + response.HasContent = true; - return response; - } - else - { - return new ModeratorMetrics() { HasContent = false }; - } - } + return response; + } + else + { + return new ModeratorMetrics() { HasContent = false }; + } + } - public async Task GetSiteMetricsAsync(IFilterOptions filterOptions) - { - var prefix = api.Contains("?") ? $"{api}/system&" : $"{api}/system?"; - var url = $"{prefix}{filterOptions.QueryString}"; + public async Task GetSiteMetricsAsync(IFilterOptions filterOptions) + { + var prefix = api.Contains("?") ? $"{api}/system&" : $"{api}/system?"; + var url = $"{prefix}{filterOptions.QueryString}"; - var httpClient = _httpClientFactory.CreateClient("UnauthenticatedAPI"); - var httpResponseMessage = await httpClient.GetAsync(url); + var httpClient = _httpClientFactory.CreateClient("UnauthenticatedAPI"); + var httpResponseMessage = await httpClient.GetAsync(url); - if (httpResponseMessage.IsSuccessStatusCode) - { - var responseString = await httpResponseMessage.Content.ReadAsStringAsync(); + if (httpResponseMessage.IsSuccessStatusCode) + { + var responseString = await httpResponseMessage.Content.ReadAsStringAsync(); - if (httpResponseMessage.StatusCode == System.Net.HttpStatusCode.NoContent) - return new Metrics() { HasContent = false }; + if (httpResponseMessage.StatusCode == System.Net.HttpStatusCode.NoContent) + return new Metrics() { HasContent = false }; - var response = JsonSerializer.Deserialize(responseString, defaultJsonSerializerOptions); - response.HasContent = true; + var response = JsonSerializer.Deserialize(responseString, defaultJsonSerializerOptions); + response.HasContent = true; - return response; - } - else - { - return new Metrics() { HasContent = false }; - } - } - } + return response; + } + else + { + return new Metrics() { HasContent = false }; + } + } + } } diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.BL/Services/TagService.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.BL/Services/TagService.cs index 8ca6eeaf..8719bcbe 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.BL/Services/TagService.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.BL/Services/TagService.cs @@ -11,92 +11,92 @@ namespace AIForOrcas.Client.BL.Services { public class TagService : ITagService { - private readonly IHttpClientFactory _httpClientFactory; - private readonly IAuthTokenProvider _authTokenProvider; - private string api = "api/tags"; - private JsonSerializerOptions defaultJsonSerializerOptions => new JsonSerializerOptions() { PropertyNameCaseInsensitive = true }; - - public TagService(IHttpClientFactory httpClientFactory, IAuthTokenProvider authTokenProvider) - { - _httpClientFactory = httpClientFactory; - _authTokenProvider = authTokenProvider; - } - - // Get the list of unique tags - public async Task> GetUniqueTagsAsync() - { - var httpClient = _httpClientFactory.CreateClient("UnauthenticatedAPI"); - var httpResponseMessage = await httpClient.GetAsync(api); - - if (httpResponseMessage.IsSuccessStatusCode) - { - var responseString = await httpResponseMessage.Content.ReadAsStringAsync(); - - if (string.IsNullOrWhiteSpace(responseString)) - return new List(); - - return JsonSerializer.Deserialize>(responseString, defaultJsonSerializerOptions); - } - else - { - return new List(); - } - } - - // replace the current tag with a new one - public async Task UpdateTagAsync(TagUpdate payload) - { - var dataJson = JsonSerializer.Serialize(payload); - var stringContent = new StringContent(dataJson, Encoding.UTF8, "application/json"); - - var httpClient = _httpClientFactory.CreateClient("AuthenticatedAPI"); - var httpRequest = new HttpRequestMessage(HttpMethod.Put, api) { Content = stringContent }; - - _authTokenProvider.ApplyToken(httpRequest); - - var httpResponseMessage = await httpClient.SendAsync(httpRequest); - - if (httpResponseMessage.IsSuccessStatusCode) - { - var responseString = await httpResponseMessage.Content.ReadAsStringAsync(); - - if (string.IsNullOrWhiteSpace(responseString)) - return 0; - - return JsonSerializer.Deserialize(responseString, defaultJsonSerializerOptions); - } - else - { - return 0; - } - } - - // delete a tag completely from the database - public async Task DeleteTagAsync(string tag) - { - var url = $"{api}?tag={HttpUtility.UrlEncode(tag)}"; - - var httpClient = _httpClientFactory.CreateClient("AuthenticatedAPI"); - var httpRequest = new HttpRequestMessage(HttpMethod.Delete, url); - - _authTokenProvider.ApplyToken(httpRequest); - - var httpResponseMessage = await httpClient.SendAsync(httpRequest); - - if (httpResponseMessage.IsSuccessStatusCode) - { - var responseString = await httpResponseMessage.Content.ReadAsStringAsync(); - - if (string.IsNullOrWhiteSpace(responseString)) - return 0; - - return JsonSerializer.Deserialize(responseString, defaultJsonSerializerOptions); - } - else - { - return 0; - } - } - - } + private readonly IHttpClientFactory _httpClientFactory; + private readonly IAuthTokenProvider _authTokenProvider; + private string api = "api/tags"; + private JsonSerializerOptions defaultJsonSerializerOptions => new JsonSerializerOptions() { PropertyNameCaseInsensitive = true }; + + public TagService(IHttpClientFactory httpClientFactory, IAuthTokenProvider authTokenProvider) + { + _httpClientFactory = httpClientFactory; + _authTokenProvider = authTokenProvider; + } + + // Get the list of unique tags + public async Task> GetUniqueTagsAsync() + { + var httpClient = _httpClientFactory.CreateClient("UnauthenticatedAPI"); + var httpResponseMessage = await httpClient.GetAsync(api); + + if (httpResponseMessage.IsSuccessStatusCode) + { + var responseString = await httpResponseMessage.Content.ReadAsStringAsync(); + + if (string.IsNullOrWhiteSpace(responseString)) + return new List(); + + return JsonSerializer.Deserialize>(responseString, defaultJsonSerializerOptions); + } + else + { + return new List(); + } + } + + // replace the current tag with a new one + public async Task UpdateTagAsync(TagUpdate payload) + { + var dataJson = JsonSerializer.Serialize(payload); + var stringContent = new StringContent(dataJson, Encoding.UTF8, "application/json"); + + var httpClient = _httpClientFactory.CreateClient("AuthenticatedAPI"); + var httpRequest = new HttpRequestMessage(HttpMethod.Put, api) { Content = stringContent }; + + _authTokenProvider.ApplyToken(httpRequest); + + var httpResponseMessage = await httpClient.SendAsync(httpRequest); + + if (httpResponseMessage.IsSuccessStatusCode) + { + var responseString = await httpResponseMessage.Content.ReadAsStringAsync(); + + if (string.IsNullOrWhiteSpace(responseString)) + return 0; + + return JsonSerializer.Deserialize(responseString, defaultJsonSerializerOptions); + } + else + { + return 0; + } + } + + // delete a tag completely from the database + public async Task DeleteTagAsync(string tag) + { + var url = $"{api}?tag={HttpUtility.UrlEncode(tag)}"; + + var httpClient = _httpClientFactory.CreateClient("AuthenticatedAPI"); + var httpRequest = new HttpRequestMessage(HttpMethod.Delete, url); + + _authTokenProvider.ApplyToken(httpRequest); + + var httpResponseMessage = await httpClient.SendAsync(httpRequest); + + if (httpResponseMessage.IsSuccessStatusCode) + { + var responseString = await httpResponseMessage.Content.ReadAsStringAsync(); + + if (string.IsNullOrWhiteSpace(responseString)) + return 0; + + return JsonSerializer.Deserialize(responseString, defaultJsonSerializerOptions); + } + else + { + return 0; + } + } + + } } diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/App.razor b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/App.razor index 80071a72..e70b9772 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/App.razor +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/App.razor @@ -1,12 +1,12 @@  - - - - - - -

Sorry, there's nothing at this address.

-
-
-
+ + + + + + +

Sorry, there's nothing at this address.

+
+
+
diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/CandidateFilterComponent.razor b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/CandidateFilterComponent.razor index b4dfd4d3..2b84b038 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/CandidateFilterComponent.razor +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/CandidateFilterComponent.razor @@ -1,39 +1,39 @@  -
-
-
- - - - - -
-
-
-
- - - - - -
-
-
-
- - - - - - - - - - - -
-
- @if(FilterOptions.Timeframe=="range") +
+
+
+ + + + + +
+
+
+
+ + + + + +
+
+
+
+ + + + + + + + + + + +
+
+ @if(FilterOptions.Timeframe=="range") {
@@ -50,21 +50,21 @@
} -
-
- - - @foreach (var location in AllLocations) - { - - } - - -
-
-
- -
-
+
+
+ + + @foreach (var location in AllLocations) + { + + } + + +
+
+
+ +
+
diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/CandidateFilterComponent.razor.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/CandidateFilterComponent.razor.cs index 5f014969..c38fd1ac 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/CandidateFilterComponent.razor.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/CandidateFilterComponent.razor.cs @@ -2,55 +2,55 @@ public partial class CandidateFilterComponent { - [Parameter] - public CandidateFilterOptionsDTO FilterOptions { get; set; } = new CandidateFilterOptionsDTO(); - - [Parameter] - public EventCallback ApplyFilterCallback { get; set; } - - [Inject] - public AppSettings AppSettings { get; set; } - - private List AllLocations = new List(); - - // Local UI-only tracking of selected location. - private string SelectedLocation { get; set; } = "all"; - - protected override void OnInitialized() - { - AllLocations = HydrophoneLocations.Locations.ToList(); - // Don't initialize from FilterOptions.Location - keep it independent. - SelectedLocation = "all"; - } - - private async Task ApplyFilter() - { - // The HydrophoneId (e.g., rpi_orcasound_lab) is constant whereas the Location - // value can and has changed over time (e.g., Haro Strait vs Orcasound Lab). - // The UI lets the user choose among labels that are Location values, but - // we want to actually query by HydrophoneId. - - // Always set location to "all" so backend doesn't filter by location name. - FilterOptions.Location = "all"; - - if (SelectedLocation != "all") - { - var hydrophoneId = HydrophoneLocations.GetIdByLocation(SelectedLocation); - if (hydrophoneId != null) - { - FilterOptions.HydrophoneId = hydrophoneId; - } - else - { - // Location not found in map, default to "all". - FilterOptions.HydrophoneId = "all"; - } - } - else - { - FilterOptions.HydrophoneId = "all"; - } - - await ApplyFilterCallback.InvokeAsync(FilterOptions); - } + [Parameter] + public CandidateFilterOptionsDTO FilterOptions { get; set; } = new CandidateFilterOptionsDTO(); + + [Parameter] + public EventCallback ApplyFilterCallback { get; set; } + + [Inject] + public AppSettings AppSettings { get; set; } + + private List AllLocations = new List(); + + // Local UI-only tracking of selected location. + private string SelectedLocation { get; set; } = "all"; + + protected override void OnInitialized() + { + AllLocations = HydrophoneLocations.Locations.ToList(); + // Don't initialize from FilterOptions.Location - keep it independent. + SelectedLocation = "all"; + } + + private async Task ApplyFilter() + { + // The HydrophoneId (e.g., rpi_orcasound_lab) is constant whereas the Location + // value can and has changed over time (e.g., Haro Strait vs Orcasound Lab). + // The UI lets the user choose among labels that are Location values, but + // we want to actually query by HydrophoneId. + + // Always set location to "all" so backend doesn't filter by location name. + FilterOptions.Location = "all"; + + if (SelectedLocation != "all") + { + var hydrophoneId = HydrophoneLocations.GetIdByLocation(SelectedLocation); + if (hydrophoneId != null) + { + FilterOptions.HydrophoneId = hydrophoneId; + } + else + { + // Location not found in map, default to "all". + FilterOptions.HydrophoneId = "all"; + } + } + else + { + FilterOptions.HydrophoneId = "all"; + } + + await ApplyFilterCallback.InvokeAsync(FilterOptions); + } } diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/DetectionComponent.razor.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/DetectionComponent.razor.cs index 86136248..df8df127 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/DetectionComponent.razor.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/DetectionComponent.razor.cs @@ -4,283 +4,283 @@ namespace AIForOrcas.Client.Web.Components; public partial class DetectionComponent { - private string _id; - private string _userId; - private Detection _initializedDetection; - private bool _submitting; - private TextInfo _ti = new CultureInfo("en-US", false).TextInfo; + private string _id; + private string _userId; + private Detection _initializedDetection; + private bool _submitting; + private TextInfo _ti = new CultureInfo("en-US", false).TextInfo; - [Inject] - IJSRuntime JSRuntime { get; set; } + [Inject] + IJSRuntime JSRuntime { get; set; } - [Inject] - IAccountService AccountService { get; set; } - - [Inject] - AuthenticationStateProvider AuthenticationStateProvider { get; set; } + [Inject] + IAccountService AccountService { get; set; } - [Inject] - NavigationManager NavigationManager { get; set; } + [Inject] + AuthenticationStateProvider AuthenticationStateProvider { get; set; } - [Inject] - IConfiguration Configuration { get; set; } - - [Inject] - UserTagCache TagCache { get; set; } - - [Inject] - IToastService ToastService { get; set; } - - [Parameter] - public Detection Detection { get; set; } - - [Parameter] - public EventCallback SubmitCallback { get; set; } - - private string[] optionList = new string[] { "Yes", "No", "Don't Know" }; - - private string CardSpectrogramId { get => $"spectrogram-card-{_id}"; } - private string CardWaveformId { get => $"waveform-card-{_id}"; } - private string CardPlayButtonId { get => $"play-card-{_id}"; } - private string CardElapsedTimeId { get => $"elapsed-card-{_id}"; } - private string CardDurationTimeId { get => $"duration-card-{_id}"; } - - private string ModalSpectrogramPanelId { get => $"spectrogram-panel-modal-{_id}"; } - private string ModalSpectrogramId { get => $"spectrogram-modal-{_id}"; } - private string ModalWaveformId { get => $"waveform-modal-{_id}"; } - private string ModalPlayButtonId { get => $"play-modal-{_id}"; } - private string ModalElapsedTimeId { get => $"elapsed-modal-{_id}"; } - private string ModalDurationTimeId { get => $"duration-modal-{_id}"; } - - private string ModalMapPanelId { get => $"map-panel-modal-{_id}"; } - private string BingMapId { get => $"bingMap-modal-{_id}"; } - - private string ModalLinkId { get => $"link-panel-modal-{_id}"; } - - private string DetectionCount { get => (Detection.Annotations.Count == 1) ? "1 detection" : $"{Detection.Annotations.Count} detections"; } - - private string AverageConfidence { get => $"{Detection.Confidence.ToString("00.##")}% average confidence"; } - - private bool IsSubmitDisabled { get => _submitting || string.IsNullOrWhiteSpace(Detection.Found); } - - private string WasFound { get => _ti.ToTitleCase(Detection.Found); } - - private string LinkUrl { get => $"{NavigationManager.BaseUri}detections/detection/{Detection.Id}"; } - - public List GetSuggestedTagList(Detection d) - { - var suggestedTags = new List(); - - // Add any tags not in TagList that were leaf tags in the most recently moderated detection. - foreach (var tag in TagCache.GetTags(_userId)) - { - if (!d.TagList.Contains(tag, StringComparer.OrdinalIgnoreCase)) - { - suggestedTags.Add(tag); - } - } - - foreach (var tag in d.SuggestedTagList) - { - if (!suggestedTags.Contains(tag, StringComparer.OrdinalIgnoreCase)) - { - suggestedTags.Add(tag); - } - } - - // Add any default tag suggestions from the DEFAULT_TAG_SUGGESTIONS environment variable. - var defaultTagSuggestions = Configuration["DEFAULT_TAG_SUGGESTIONS"]; - if (!string.IsNullOrWhiteSpace(defaultTagSuggestions)) - { - foreach (var tag in defaultTagSuggestions.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)) - { - if (!d.TagList.Contains(tag, StringComparer.OrdinalIgnoreCase) && - !suggestedTags.Contains(tag, StringComparer.OrdinalIgnoreCase)) - { - suggestedTags.Add(tag); - } - } - } - - return suggestedTags; - } - - protected override async Task OnParametersSetAsync() - { - _id = Detection.Id; - - var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync(); - var user = authState.User; - _userId = user.FindFirst("oid")?.Value; - - // Unreviewed detections are being initially populated in the database as "No" - // I am manually resetting it here when the reviewed status is false so that the record, - // can be unsubmittable until the user has changed Found to "Yes", "No", or "Don't Know" - - // TODO: Determine whether or not we should change the initial Found state - // from No to something other than the three options we give the user - - // Only initialize each detection once. This hook runs again on every - // parent re-render with the same Detection instance, and resetting - // then would wipe a verdict the moderator already selected (e.g., right - // after a failed submit shows its retry toast). - if (!Detection.Reviewed && !ReferenceEquals(Detection, _initializedDetection)) - { - _initializedDetection = Detection; - Detection.Found = string.Empty; - - if (string.IsNullOrEmpty(Detection.Tags)) - { - if (Detection.GlobalPredictionLabel == "transient") - { - AddTag("transient"); - } - else if (Detection.GlobalPredictionLabel == "humpback") - { - AddTag("humpback"); - } - - // Don't add the "srkw" tag here because we want the user - // to explicitly select it if they see it in the audio. - } - - // If Comments is of the form "AI: A and B", then parse out the B and add it too. - if (!string.IsNullOrEmpty(Detection.Comments)) - { - var match = Regex.Match(Detection.Comments, @"AI:\s*(?.*?)\s*and\s*(?.*)"); - if (match.Success) - { - string b = match.Groups["b"].Value; - AddTag(b); - } - } - - // Handle model tag aliases / legacy tags. - // PODS-AI may include "resident"; OrcaHello uses "srkw" only when the moderator explicitly - // selects SRKW=yes, so strip "resident" during initialization to avoid an extra tag. - // If we later need to map other model labels (e.g., "transient" -> "biggs"), do it here. - // This does not block moderators from manually entering "resident" later. - if (Detection.TagList.Contains("resident", StringComparer.OrdinalIgnoreCase)) - { - RemoveTag("resident"); - - // Don't add srkw since that would make the SRKWFound - // button default to yes. Instead leave it unselected, - // like OrcaHello candidates do. - } - } - } - - protected override async Task OnAfterRenderAsync(bool firstRender) - { - // Invoked on every render because the card may not be in the DOM yet on the - // first render (e.g. while the single detection page is still loading the record); - // the JS side is idempotent and exits early once the shades exist. - await JSRuntime.InvokeVoidAsync("DrawRegionShades", _id, Detection.AudioUri, RegionsJson); - } - - private void SetFoundValue(string found) - { - Detection.Found = found; - - switch (found) - { - case "Yes": - AddTag("srkw"); - break; - default: // No or Don't Know. - RemoveTag("srkw"); - break; - } - } - - /// - /// Add a tag to the detection's tag list, ensuring that it is added before its parent tag if present, and also adding the parent tag if not already present. - /// If the tag is "srkw" and the Found value is not "Yes", it sets Found to "Yes". - /// - /// Tag to add - private void AddTag(string tag) - { - if (string.IsNullOrWhiteSpace(tag)) - { - return; - } - var tagList = Detection.TagList; - if (tagList.Contains(tag, StringComparer.OrdinalIgnoreCase)) - { - // Nothing to do. - return; - } - - // Add the suggested tag before its parent tag if present, otherwise add it to the end of the list. - Detection.TagHierarchy.TryGetValue(tag, out string parentTag); - if (!string.IsNullOrWhiteSpace(parentTag)) - { - var parentIndex = tagList.FindIndex(t => t.Equals(parentTag, StringComparison.OrdinalIgnoreCase)); - if (parentIndex >= 0) - { - tagList.Insert(parentIndex, tag); - } - else - { - tagList.Add(tag); - } - } - else - { - tagList.Add(tag); - } - Detection.Tags = string.Join(";", tagList); - - // Add parent tag if not already present. - if (!string.IsNullOrEmpty(parentTag)) - { - AddTag(parentTag); - } - - if (tag.Equals("srkw", StringComparison.OrdinalIgnoreCase) && Detection.Found != "Yes") - { - SetFoundValue("Yes"); - } - } - - /// - /// Remove a tag from the detection's tag list. - /// Also remove any child tags that have this tag as their parent in the hierarchy. - /// - /// Tag to remove - private void RemoveTag(string tag) - { - if (string.IsNullOrWhiteSpace(tag)) - { - return; - } - var tagList = Detection.TagList; - if (!tagList.Contains(tag, StringComparer.OrdinalIgnoreCase)) - { - // Nothing to do. - return; - } - - tagList.RemoveAll(t => t.Equals(tag, StringComparison.OrdinalIgnoreCase)); - Detection.Tags = string.Join(";", tagList); - - // Remove child tags if they exist in the hierarchy. - foreach (var pair in Detection.TagHierarchy) - { - if ((pair.Value != null) && pair.Value.Equals(tag, StringComparison.OrdinalIgnoreCase)) - { - RemoveTag(pair.Key); - } - } - - // If we just removed the SRKW tag and the radio button says - // SRKW=yes, clear that. - if (tag.Equals("srkw", StringComparison.OrdinalIgnoreCase) && Detection.Found == "Yes") - { - SetFoundValue(string.Empty); - } - } + [Inject] + NavigationManager NavigationManager { get; set; } + + [Inject] + IConfiguration Configuration { get; set; } + + [Inject] + UserTagCache TagCache { get; set; } + + [Inject] + IToastService ToastService { get; set; } + + [Parameter] + public Detection Detection { get; set; } + + [Parameter] + public EventCallback SubmitCallback { get; set; } + + private string[] optionList = new string[] { "Yes", "No", "Don't Know" }; + + private string CardSpectrogramId { get => $"spectrogram-card-{_id}"; } + private string CardWaveformId { get => $"waveform-card-{_id}"; } + private string CardPlayButtonId { get => $"play-card-{_id}"; } + private string CardElapsedTimeId { get => $"elapsed-card-{_id}"; } + private string CardDurationTimeId { get => $"duration-card-{_id}"; } + + private string ModalSpectrogramPanelId { get => $"spectrogram-panel-modal-{_id}"; } + private string ModalSpectrogramId { get => $"spectrogram-modal-{_id}"; } + private string ModalWaveformId { get => $"waveform-modal-{_id}"; } + private string ModalPlayButtonId { get => $"play-modal-{_id}"; } + private string ModalElapsedTimeId { get => $"elapsed-modal-{_id}"; } + private string ModalDurationTimeId { get => $"duration-modal-{_id}"; } + + private string ModalMapPanelId { get => $"map-panel-modal-{_id}"; } + private string BingMapId { get => $"bingMap-modal-{_id}"; } + + private string ModalLinkId { get => $"link-panel-modal-{_id}"; } + + private string DetectionCount { get => (Detection.Annotations.Count == 1) ? "1 detection" : $"{Detection.Annotations.Count} detections"; } + + private string AverageConfidence { get => $"{Detection.Confidence.ToString("00.##")}% average confidence"; } + + private bool IsSubmitDisabled { get => _submitting || string.IsNullOrWhiteSpace(Detection.Found); } + + private string WasFound { get => _ti.ToTitleCase(Detection.Found); } + + private string LinkUrl { get => $"{NavigationManager.BaseUri}detections/detection/{Detection.Id}"; } + + public List GetSuggestedTagList(Detection d) + { + var suggestedTags = new List(); + + // Add any tags not in TagList that were leaf tags in the most recently moderated detection. + foreach (var tag in TagCache.GetTags(_userId)) + { + if (!d.TagList.Contains(tag, StringComparer.OrdinalIgnoreCase)) + { + suggestedTags.Add(tag); + } + } + + foreach (var tag in d.SuggestedTagList) + { + if (!suggestedTags.Contains(tag, StringComparer.OrdinalIgnoreCase)) + { + suggestedTags.Add(tag); + } + } + + // Add any default tag suggestions from the DEFAULT_TAG_SUGGESTIONS environment variable. + var defaultTagSuggestions = Configuration["DEFAULT_TAG_SUGGESTIONS"]; + if (!string.IsNullOrWhiteSpace(defaultTagSuggestions)) + { + foreach (var tag in defaultTagSuggestions.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)) + { + if (!d.TagList.Contains(tag, StringComparer.OrdinalIgnoreCase) && + !suggestedTags.Contains(tag, StringComparer.OrdinalIgnoreCase)) + { + suggestedTags.Add(tag); + } + } + } + + return suggestedTags; + } + + protected override async Task OnParametersSetAsync() + { + _id = Detection.Id; + + var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync(); + var user = authState.User; + _userId = user.FindFirst("oid")?.Value; + + // Unreviewed detections are being initially populated in the database as "No" + // I am manually resetting it here when the reviewed status is false so that the record, + // can be unsubmittable until the user has changed Found to "Yes", "No", or "Don't Know" + + // TODO: Determine whether or not we should change the initial Found state + // from No to something other than the three options we give the user + + // Only initialize each detection once. This hook runs again on every + // parent re-render with the same Detection instance, and resetting + // then would wipe a verdict the moderator already selected (e.g., right + // after a failed submit shows its retry toast). + if (!Detection.Reviewed && !ReferenceEquals(Detection, _initializedDetection)) + { + _initializedDetection = Detection; + Detection.Found = string.Empty; + + if (string.IsNullOrEmpty(Detection.Tags)) + { + if (Detection.GlobalPredictionLabel == "transient") + { + AddTag("transient"); + } + else if (Detection.GlobalPredictionLabel == "humpback") + { + AddTag("humpback"); + } + + // Don't add the "srkw" tag here because we want the user + // to explicitly select it if they see it in the audio. + } + + // If Comments is of the form "AI: A and B", then parse out the B and add it too. + if (!string.IsNullOrEmpty(Detection.Comments)) + { + var match = Regex.Match(Detection.Comments, @"AI:\s*(?.*?)\s*and\s*(?.*)"); + if (match.Success) + { + string b = match.Groups["b"].Value; + AddTag(b); + } + } + + // Handle model tag aliases / legacy tags. + // PODS-AI may include "resident"; OrcaHello uses "srkw" only when the moderator explicitly + // selects SRKW=yes, so strip "resident" during initialization to avoid an extra tag. + // If we later need to map other model labels (e.g., "transient" -> "biggs"), do it here. + // This does not block moderators from manually entering "resident" later. + if (Detection.TagList.Contains("resident", StringComparer.OrdinalIgnoreCase)) + { + RemoveTag("resident"); + + // Don't add srkw since that would make the SRKWFound + // button default to yes. Instead leave it unselected, + // like OrcaHello candidates do. + } + } + } + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + // Invoked on every render because the card may not be in the DOM yet on the + // first render (e.g. while the single detection page is still loading the record); + // the JS side is idempotent and exits early once the shades exist. + await JSRuntime.InvokeVoidAsync("DrawRegionShades", _id, Detection.AudioUri, RegionsJson); + } + + private void SetFoundValue(string found) + { + Detection.Found = found; + + switch (found) + { + case "Yes": + AddTag("srkw"); + break; + default: // No or Don't Know. + RemoveTag("srkw"); + break; + } + } + + /// + /// Add a tag to the detection's tag list, ensuring that it is added before its parent tag if present, and also adding the parent tag if not already present. + /// If the tag is "srkw" and the Found value is not "Yes", it sets Found to "Yes". + /// + /// Tag to add + private void AddTag(string tag) + { + if (string.IsNullOrWhiteSpace(tag)) + { + return; + } + var tagList = Detection.TagList; + if (tagList.Contains(tag, StringComparer.OrdinalIgnoreCase)) + { + // Nothing to do. + return; + } + + // Add the suggested tag before its parent tag if present, otherwise add it to the end of the list. + Detection.TagHierarchy.TryGetValue(tag, out string parentTag); + if (!string.IsNullOrWhiteSpace(parentTag)) + { + var parentIndex = tagList.FindIndex(t => t.Equals(parentTag, StringComparison.OrdinalIgnoreCase)); + if (parentIndex >= 0) + { + tagList.Insert(parentIndex, tag); + } + else + { + tagList.Add(tag); + } + } + else + { + tagList.Add(tag); + } + Detection.Tags = string.Join(";", tagList); + + // Add parent tag if not already present. + if (!string.IsNullOrEmpty(parentTag)) + { + AddTag(parentTag); + } + + if (tag.Equals("srkw", StringComparison.OrdinalIgnoreCase) && Detection.Found != "Yes") + { + SetFoundValue("Yes"); + } + } + + /// + /// Remove a tag from the detection's tag list. + /// Also remove any child tags that have this tag as their parent in the hierarchy. + /// + /// Tag to remove + private void RemoveTag(string tag) + { + if (string.IsNullOrWhiteSpace(tag)) + { + return; + } + var tagList = Detection.TagList; + if (!tagList.Contains(tag, StringComparer.OrdinalIgnoreCase)) + { + // Nothing to do. + return; + } + + tagList.RemoveAll(t => t.Equals(tag, StringComparison.OrdinalIgnoreCase)); + Detection.Tags = string.Join(";", tagList); + + // Remove child tags if they exist in the hierarchy. + foreach (var pair in Detection.TagHierarchy) + { + if ((pair.Value != null) && pair.Value.Equals(tag, StringComparison.OrdinalIgnoreCase)) + { + RemoveTag(pair.Key); + } + } + + // If we just removed the SRKW tag and the radio button says + // SRKW=yes, clear that. + if (tag.Equals("srkw", StringComparison.OrdinalIgnoreCase) && Detection.Found == "Yes") + { + SetFoundValue(string.Empty); + } + } /// /// Process a change in the tags string, updating the Detection's TagList accordingly. @@ -316,92 +316,92 @@ private void OnTagsChanged(string tags) } private async Task SubmitUpdate() - { - // Guard before any await: a second click can be dispatched before the - // disabled attribute reaches the browser, and it must not enter here. - if (_submitting) - { - return; - } - _submitting = true; - - try - { - var request = new DetectionUpdate() - { - Id = Detection.Id, - Comments = Detection.Comments, - Tags = Detection.Tags, - Moderator = await AccountService.GetUsername(), - Moderated = DateTime.Now, - Reviewed = true, - Found = Detection.Found - }; - - await SubmitCallback.InvokeAsync(request); - } - catch (Exception exception) when (exception is HttpRequestException || exception is TaskCanceledException) - { - // One guard for every page that renders this component. Keep the - // card and the moderator's selections untouched for a retry. The - // wording stays generic: the same exception covers an unreachable - // server and an error response, and the service logs the detail. - ToastService.ShowError("The verdict was not saved. Please try again."); - } - finally - { - _submitting = false; - } - } - - private async Task ToggleCardPlayer() - { - await JSRuntime.InvokeVoidAsync("CardSpectrogram", _id, Detection.AudioUri); - } - - private async Task ToggleModalPlayer() - { - var isPlaying = await JSRuntime.InvokeAsync("IsPlayerActive"); - - if (!isPlaying) - { - await InitializeModalPlayer(); - } - - await JSRuntime.InvokeVoidAsync("ToggleModalSpectrogram"); - } - - private string RegionsJson => - JsonSerializer.Serialize(Detection.Annotations.Select(annotation => new - { - start = annotation.StartTime, - end = annotation.EndTime, - color = "rgba(255, 255, 255, 0.1)" - })); - - private async Task InitializeModalPlayer() - { - await JSRuntime.InvokeVoidAsync("DestroyActivePlayer"); - await JSRuntime.InvokeVoidAsync("InitializeModalSpectrogram", _id, - Detection.AudioUri); - } - - private async Task InitializeModalMap() - { - await JSRuntime.InvokeVoidAsync("DestroyActivePlayer"); - await JSRuntime.InvokeVoidAsync("LoadBingMap", _id, - Detection.Location?.Latitude, Detection.Location?.Longitude); - } - - private async Task KillPlayer() - { - await JSRuntime.InvokeVoidAsync("DestroyActivePlayer"); - } - - private async Task ActivateLink(string url) - { - var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync(); - - NavigationManager.NavigateTo(url, true); - } + { + // Guard before any await: a second click can be dispatched before the + // disabled attribute reaches the browser, and it must not enter here. + if (_submitting) + { + return; + } + _submitting = true; + + try + { + var request = new DetectionUpdate() + { + Id = Detection.Id, + Comments = Detection.Comments, + Tags = Detection.Tags, + Moderator = await AccountService.GetUsername(), + Moderated = DateTime.Now, + Reviewed = true, + Found = Detection.Found + }; + + await SubmitCallback.InvokeAsync(request); + } + catch (Exception exception) when (exception is HttpRequestException || exception is TaskCanceledException) + { + // One guard for every page that renders this component. Keep the + // card and the moderator's selections untouched for a retry. The + // wording stays generic: the same exception covers an unreachable + // server and an error response, and the service logs the detail. + ToastService.ShowError("The verdict was not saved. Please try again."); + } + finally + { + _submitting = false; + } + } + + private async Task ToggleCardPlayer() + { + await JSRuntime.InvokeVoidAsync("CardSpectrogram", _id, Detection.AudioUri); + } + + private async Task ToggleModalPlayer() + { + var isPlaying = await JSRuntime.InvokeAsync("IsPlayerActive"); + + if (!isPlaying) + { + await InitializeModalPlayer(); + } + + await JSRuntime.InvokeVoidAsync("ToggleModalSpectrogram"); + } + + private string RegionsJson => + JsonSerializer.Serialize(Detection.Annotations.Select(annotation => new + { + start = annotation.StartTime, + end = annotation.EndTime, + color = "rgba(255, 255, 255, 0.1)" + })); + + private async Task InitializeModalPlayer() + { + await JSRuntime.InvokeVoidAsync("DestroyActivePlayer"); + await JSRuntime.InvokeVoidAsync("InitializeModalSpectrogram", _id, + Detection.AudioUri); + } + + private async Task InitializeModalMap() + { + await JSRuntime.InvokeVoidAsync("DestroyActivePlayer"); + await JSRuntime.InvokeVoidAsync("LoadBingMap", _id, + Detection.Location?.Latitude, Detection.Location?.Longitude); + } + + private async Task KillPlayer() + { + await JSRuntime.InvokeVoidAsync("DestroyActivePlayer"); + } + + private async Task ActivateLink(string url) + { + var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync(); + + NavigationManager.NavigateTo(url, true); + } } diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/LoginWarningComponent.razor b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/LoginWarningComponent.razor index a60dfcd1..49346f70 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/LoginWarningComponent.razor +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/LoginWarningComponent.razor @@ -1,17 +1,17 @@  diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/LogoutComponent.razor b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/LogoutComponent.razor index b502d73d..ad9c3a20 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/LogoutComponent.razor +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/LogoutComponent.razor @@ -1,19 +1,19 @@  diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/MetricsFilterComponent.razor b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/MetricsFilterComponent.razor index fd5c63fe..50e5e2d4 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/MetricsFilterComponent.razor +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/MetricsFilterComponent.razor @@ -1,19 +1,19 @@  -
-
-
- - - - - - - - -
-
-
- -
-
+
+
+
+ + + + + + + + +
+
+
+ +
+
diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/MetricsFilterComponent.razor.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/MetricsFilterComponent.razor.cs index 661b214b..bdfbe0ba 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/MetricsFilterComponent.razor.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/MetricsFilterComponent.razor.cs @@ -2,14 +2,14 @@ public partial class MetricsFilterComponent { - [Parameter] - public MetricsFilterDTO FilterOptions { get; set; } = new MetricsFilterDTO(); + [Parameter] + public MetricsFilterDTO FilterOptions { get; set; } = new MetricsFilterDTO(); - [Parameter] - public EventCallback ApplyFilterCallback { get; set; } + [Parameter] + public EventCallback ApplyFilterCallback { get; set; } - private async Task ApplyFilter() - { - await ApplyFilterCallback.InvokeAsync(FilterOptions); - } + private async Task ApplyFilter() + { + await ApplyFilterCallback.InvokeAsync(FilterOptions); + } } diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/PageHeadingComponent.razor b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/PageHeadingComponent.razor index 0577f725..86921fe4 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/PageHeadingComponent.razor +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/PageHeadingComponent.razor @@ -1,5 +1,5 @@ 

@Title

@if (PillCount > 0) { - @PillCount + @PillCount } diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/PageHeadingComponent.razor.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/PageHeadingComponent.razor.cs index ee753751..ebbc9256 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/PageHeadingComponent.razor.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/PageHeadingComponent.razor.cs @@ -2,9 +2,9 @@ public partial class PageHeadingComponent { - [Parameter] - public string Title { get; set; } + [Parameter] + public string Title { get; set; } - [Parameter] - public int PillCount { get; set; } + [Parameter] + public int PillCount { get; set; } } diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/PaginationComponent.razor b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/PaginationComponent.razor index 7e4fe231..03a2ab58 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/PaginationComponent.razor +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/PaginationComponent.razor @@ -1,12 +1,12 @@  diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/PaginationComponent.razor.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/PaginationComponent.razor.cs index edc06616..1b83cb5b 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/PaginationComponent.razor.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/PaginationComponent.razor.cs @@ -2,54 +2,54 @@ public partial class PaginationComponent { - [Parameter] - public PaginationOptionsDTO PaginationOptions { get; set; } - - [Parameter] - public PaginationResultsDTO PaginationResults { get; set; } - - [Parameter] - public EventCallback SelectPageCallback { get; set; } - - List links; - - protected override void OnParametersSet() - { - BuildPaginationLinks(); - } - - private async Task SelectPage(PageLinkDTO link) - { - if (link.Page == PaginationResults.CurrentPage || !link.Enabled) - return; - - PaginationOptions.Page = link.Page; - await SelectPageCallback.InvokeAsync(PaginationOptions); - } - - private void BuildPaginationLinks() - { - links = new List(); - var isPreviousPageLinkEnabled = PaginationResults.CurrentPage != 1; - var previousPage = PaginationResults.CurrentPage - 1; - links.Add(new PageLinkDTO(previousPage, isPreviousPageLinkEnabled, "Previous")); - - for (int i = 1; i <= PaginationResults.TotalNumberOfPages; i++) - { - if (i == 1 || - i == PaginationResults.TotalNumberOfPages || - (i >= PaginationResults.CurrentPage - PaginationOptions.Radius && - i <= PaginationResults.CurrentPage + PaginationOptions.Radius)) - { - links.Add(new PageLinkDTO(i) { Active = PaginationResults.CurrentPage == i }); - } - } - - var isNextPageLinkEnabled = PaginationResults.TotalNumberOfPages > 0 - && PaginationResults.CurrentPage != PaginationResults.TotalNumberOfPages; - var nextPage = PaginationResults.CurrentPage + 1; - - links.Add(new PageLinkDTO(nextPage, isNextPageLinkEnabled, "Next")); - } + [Parameter] + public PaginationOptionsDTO PaginationOptions { get; set; } + + [Parameter] + public PaginationResultsDTO PaginationResults { get; set; } + + [Parameter] + public EventCallback SelectPageCallback { get; set; } + + List links; + + protected override void OnParametersSet() + { + BuildPaginationLinks(); + } + + private async Task SelectPage(PageLinkDTO link) + { + if (link.Page == PaginationResults.CurrentPage || !link.Enabled) + return; + + PaginationOptions.Page = link.Page; + await SelectPageCallback.InvokeAsync(PaginationOptions); + } + + private void BuildPaginationLinks() + { + links = new List(); + var isPreviousPageLinkEnabled = PaginationResults.CurrentPage != 1; + var previousPage = PaginationResults.CurrentPage - 1; + links.Add(new PageLinkDTO(previousPage, isPreviousPageLinkEnabled, "Previous")); + + for (int i = 1; i <= PaginationResults.TotalNumberOfPages; i++) + { + if (i == 1 || + i == PaginationResults.TotalNumberOfPages || + (i >= PaginationResults.CurrentPage - PaginationOptions.Radius && + i <= PaginationResults.CurrentPage + PaginationOptions.Radius)) + { + links.Add(new PageLinkDTO(i) { Active = PaginationResults.CurrentPage == i }); + } + } + + var isNextPageLinkEnabled = PaginationResults.TotalNumberOfPages > 0 + && PaginationResults.CurrentPage != PaginationResults.TotalNumberOfPages; + var nextPage = PaginationResults.CurrentPage + 1; + + links.Add(new PageLinkDTO(nextPage, isNextPageLinkEnabled, "Next")); + } } diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/ReviewedFilterComponent.razor b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/ReviewedFilterComponent.razor index 6e874218..0d417abb 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/ReviewedFilterComponent.razor +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/ReviewedFilterComponent.razor @@ -1,41 +1,41 @@  -
-
-
- - - - - - - -
-
-
-
- - - - - -
-
-
-
- - - - - - - - - - - -
-
- @if(FilterOptions.Timeframe=="range") +
+
+
+ + + + + + + +
+
+
+
+ + + + + +
+
+
+
+ + + + + + + + + + + +
+
+ @if(FilterOptions.Timeframe=="range") {
@@ -52,20 +52,20 @@
} -
-
- - - @foreach (var location in AllLocations) - { - - } - - -
-
-
- -
-
+
+
+ + + @foreach (var location in AllLocations) + { + + } + + +
+
+
+ +
+
diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/ReviewedFilterComponent.razor.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/ReviewedFilterComponent.razor.cs index c382deaa..25176ef2 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/ReviewedFilterComponent.razor.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/ReviewedFilterComponent.razor.cs @@ -2,24 +2,24 @@ public partial class ReviewedFilterComponent { - [Parameter] - public ReviewedFilterOptionsDTO FilterOptions { get; set; } = new ReviewedFilterOptionsDTO(); + [Parameter] + public ReviewedFilterOptionsDTO FilterOptions { get; set; } = new ReviewedFilterOptionsDTO(); - [Parameter] - public EventCallback ApplyFilterCallback { get; set; } + [Parameter] + public EventCallback ApplyFilterCallback { get; set; } - [Inject] - public AppSettings AppSettings { get; set; } + [Inject] + public AppSettings AppSettings { get; set; } - private List AllLocations = new List(); + private List AllLocations = new List(); - protected override void OnInitialized() - { - AllLocations = HydrophoneLocations.Locations.ToList(); - } + protected override void OnInitialized() + { + AllLocations = HydrophoneLocations.Locations.ToList(); + } - private async Task ApplyFilter() - { - await ApplyFilterCallback.InvokeAsync(FilterOptions); - } + private async Task ApplyFilter() + { + await ApplyFilterCallback.InvokeAsync(FilterOptions); + } } diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/SideBarComponent.razor.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/SideBarComponent.razor.cs index 33728f7d..472f412b 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/SideBarComponent.razor.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/SideBarComponent.razor.cs @@ -2,11 +2,11 @@ public partial class SideBarComponent { - [Inject] - IJSRuntime JSRuntime { get; set; } - - private async Task ToggleDisplay() - { - await JSRuntime.InvokeVoidAsync("ToggleSideBar"); - } + [Inject] + IJSRuntime JSRuntime { get; set; } + + private async Task ToggleDisplay() + { + await JSRuntime.InvokeVoidAsync("ToggleSideBar"); + } } diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/TopBarComponent.razor b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/TopBarComponent.razor index 6f926339..4e3c0ca5 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/TopBarComponent.razor +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/TopBarComponent.razor @@ -1,57 +1,57 @@ 
diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/TopBarComponent.razor.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/TopBarComponent.razor.cs index 1280c4cf..48c507a4 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/TopBarComponent.razor.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Components/TopBarComponent.razor.cs @@ -2,69 +2,69 @@ public partial class TopBarComponent { - [Inject] - IJSRuntime JSRuntime { get; set; } + [Inject] + IJSRuntime JSRuntime { get; set; } [Inject] - IAccountService AccountService { get; set; } + IAccountService AccountService { get; set; } - [Parameter] - public string CurrentUrl { get; set; } + [Parameter] + public string CurrentUrl { get; set; } - private string DisplayName { get; set; } + private string DisplayName { get; set; } - private string DisplayDate { get; set; } + private string DisplayDate { get; set; } - private string ShortDisplayDate { get; set; } + private string ShortDisplayDate { get; set; } - public CancellationTokenSource CancellationTokenSource { get; set; } + public CancellationTokenSource CancellationTokenSource { get; set; } - [Parameter] - public EventCallback ToggleThemeCallback { get; set; } + [Parameter] + public EventCallback ToggleThemeCallback { get; set; } - private string theme = "Dark"; + private string theme = "Dark"; - private void SetDateTime() - { - var now = DateTime.UtcNow; - DisplayDate = DateHelper.UTCToPDT(now); - ShortDisplayDate = DateHelper.UTCToPDT(now, true); - } + private void SetDateTime() + { + var now = DateTime.UtcNow; + DisplayDate = DateHelper.UTCToPDT(now); + ShortDisplayDate = DateHelper.UTCToPDT(now, true); + } - protected override async Task OnInitializedAsync() - { - DisplayName = await AccountService.GetDisplayname(); - SetDateTime(); - CancellationTokenSource = new CancellationTokenSource(); - await RealTimeUpdate(CancellationTokenSource.Token); - } + protected override async Task OnInitializedAsync() + { + DisplayName = await AccountService.GetDisplayname(); + SetDateTime(); + CancellationTokenSource = new CancellationTokenSource(); + await RealTimeUpdate(CancellationTokenSource.Token); + } - private async Task ToggleTheme() - { - theme = (theme == "Dark") ? "Light" : "Dark"; - await ToggleThemeCallback.InvokeAsync(null); - } + private async Task ToggleTheme() + { + theme = (theme == "Dark") ? "Light" : "Dark"; + await ToggleThemeCallback.InvokeAsync(null); + } - private async Task ToggleSidebar() - { - await JSRuntime.InvokeVoidAsync("ToggleSideBar"); - } + private async Task ToggleSidebar() + { + await JSRuntime.InvokeVoidAsync("ToggleSideBar"); + } - private async Task Login() + private async Task Login() { - await AccountService.Login(); + await AccountService.Login(); } - public async Task RealTimeUpdate(CancellationToken cancellationToken) - { - while(!cancellationToken.IsCancellationRequested) - { - await Task.Delay(1000, cancellationToken); - if (!cancellationToken.IsCancellationRequested) - { - SetDateTime(); - await InvokeAsync(() => this.StateHasChanged()); - } - } - } + public async Task RealTimeUpdate(CancellationToken cancellationToken) + { + while (!cancellationToken.IsCancellationRequested) + { + await Task.Delay(1000, cancellationToken); + if (!cancellationToken.IsCancellationRequested) + { + SetDateTime(); + await InvokeAsync(() => this.StateHasChanged()); + } + } + } } diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Extensions/Services.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Extensions/Services.cs index a919e7b4..33f53c83 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Extensions/Services.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Extensions/Services.cs @@ -9,7 +9,7 @@ public static void ConfigureDataServices(this WebApplicationBuilder builder) { // Register server-side token store as singleton. builder.Services.AddSingleton(); - + // Register circuit handler. builder.Services.AddScoped(); builder.Services.AddScoped(sp => sp.GetRequiredService()); diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Helpers/InputRadio.razor b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Helpers/InputRadio.razor index f1e27998..2ab113a4 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Helpers/InputRadio.razor +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Helpers/InputRadio.razor @@ -3,35 +3,35 @@ @inherits InputBase + checked="@(SelectedValue.Equals(Value))" @onchange="OnChange" /> @code { - [Parameter] - public TValue SelectedValue { get; set; } + [Parameter] + public TValue SelectedValue { get; set; } - private void OnChange(ChangeEventArgs args) - { - CurrentValueAsString = args.Value.ToString(); - } + private void OnChange(ChangeEventArgs args) + { + CurrentValueAsString = args.Value.ToString(); + } - protected override bool TryParseValueFromString(string value, - out TValue result, out string errorMessage) - { - var success = BindConverter.TryConvertTo( - value, CultureInfo.CurrentCulture, out var parsedValue); - if (success) - { - result = parsedValue; - errorMessage = null; + protected override bool TryParseValueFromString(string value, + out TValue result, out string errorMessage) + { + var success = BindConverter.TryConvertTo( + value, CultureInfo.CurrentCulture, out var parsedValue); + if (success) + { + result = parsedValue; + errorMessage = null; - return true; - } - else - { - result = default; - errorMessage = $"{FieldIdentifier.FieldName} field isn't valid."; + return true; + } + else + { + result = default; + errorMessage = $"{FieldIdentifier.FieldName} field isn't valid."; - return false; - } - } + return false; + } + } } \ No newline at end of file diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Pages/Dashboard.razor b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Pages/Dashboard.razor index 785acac5..f83e1996 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Pages/Dashboard.razor +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Pages/Dashboard.razor @@ -8,170 +8,170 @@
-
+
-
+
- -
- -
-
Detections
-
- -
-
- -
-
- - Reviewed - - - Unreviewed - -
-
-
+ +
+ +
+
Detections
+
+ +
+
+ +
+
+ + Reviewed + + + Unreviewed + +
+
+
-
-
+
+
- + -
- -
-
Results
-
- -
-
- -
-
- - Confirmed - - - False Positives - - - Don't Know - -
-
+
+ +
+
Results
+
+ +
+
+ +
+
+ + Confirmed + + + False Positives + + + Don't Know + +
+
-
-
-
+
+
+
-
-
-
- -
-
-
- @if (metrics != null) - { - @foreach (var entry in metrics.Tags) - { -
@entry.Tag
-
    - @foreach (var link in entry.Ids) - { -
  • @link
  • - } -
- } - } -
-
-
-
+
+
+
+ +
+
+
+ @if (metrics != null) + { + @foreach (var entry in metrics.Tags) + { +
@entry.Tag
+
    + @foreach (var link in entry.Ids) + { +
  • @link
  • + } +
+ } + } +
+
+
+
-
- -
-
- @if (metrics != null) - {
    - @foreach (var entry in metrics.ConfirmedComments) - { -
  • -
    -
    - -
    -
    -
    - @entry.Id -
    - @DateHelper.UTCToPDTFull(entry.Timestamp)
    -
    -
    -
    -

    @entry.Comment

    -
    @entry.Moderator
    -
    -
    -
    -
  • - } -
- } -
-
-
+
+ +
+
+ @if (metrics != null) + {
    + @foreach (var entry in metrics.ConfirmedComments) + { +
  • +
    +
    + +
    +
    +
    + @entry.Id +
    + @DateHelper.UTCToPDTFull(entry.Timestamp)
    +
    +
    +
    +

    @entry.Comment

    +
    @entry.Moderator
    +
    +
    +
    +
  • + } +
+ } +
+
+
-
- -
-
- @if (metrics != null) - {
    - @foreach (var entry in metrics.UnconfirmedComments) - { -
  • -
    -
    - -
    -
    -
    - @entry.Id -
    - @DateHelper.UTCToPDTFull(entry.Timestamp)
    -
    -
    -
    -

    @entry.Comment

    -
    @entry.Moderator
    -
    -
    -
    -
  • - } -
- } -
-
-
-
-
+
+ +
+
+ @if (metrics != null) + {
    + @foreach (var entry in metrics.UnconfirmedComments) + { +
  • +
    +
    + +
    +
    +
    + @entry.Id +
    + @DateHelper.UTCToPDTFull(entry.Timestamp)
    +
    +
    +
    +

    @entry.Comment

    +
    @entry.Moderator
    +
    +
    +
    +
  • + } +
+ } +
+
+
+
+
@@ -179,88 +179,88 @@ diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Pages/Dashboard.razor.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Pages/Dashboard.razor.cs index 3b85b83d..4f515128 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Pages/Dashboard.razor.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Pages/Dashboard.razor.cs @@ -2,55 +2,55 @@ public partial class Dashboard { - [Inject] - IMetricsService Service { get; set; } - - [Inject] - IJSRuntime JSRuntime { get; set; } - - private Metrics metrics = null; - - private MetricsFilterDTO filterOptions = - new MetricsFilterDTO() { Timeframe = "1m" }; - - private string messageStyle = "d-none"; - private string message = string.Empty; - private string displayStyle = "d-none"; - - protected override async Task OnInitializedAsync() - { - await LoadMetrics(); - } - - private async Task LoadMetrics() - { - displayStyle = "d-none"; - messageStyle = ""; - message = "Loading metrics..."; - - metrics = await Service.GetSiteMetricsAsync(filterOptions); - - if(!metrics.HasContent) - { - message = "No metrics found for the selected filter options. Please select a different set of filter options..."; - displayStyle = "d-none"; - } - else - { - messageStyle = "d-none"; - displayStyle = ""; - } - - StateHasChanged(); - - await JSRuntime.InvokeVoidAsync("DrawDetectionsChart", metrics.DetectionsArray); - await JSRuntime.InvokeVoidAsync("DrawDetectionResultsChart", metrics.DetectionResultsArray); - } - - private async Task ActOnApplyFilterCallback(MetricsFilterDTO returnedFilterOptions) - { - filterOptions = returnedFilterOptions; - await LoadMetrics(); - } + [Inject] + IMetricsService Service { get; set; } + + [Inject] + IJSRuntime JSRuntime { get; set; } + + private Metrics metrics = null; + + private MetricsFilterDTO filterOptions = + new MetricsFilterDTO() { Timeframe = "1m" }; + + private string messageStyle = "d-none"; + private string message = string.Empty; + private string displayStyle = "d-none"; + + protected override async Task OnInitializedAsync() + { + await LoadMetrics(); + } + + private async Task LoadMetrics() + { + displayStyle = "d-none"; + messageStyle = ""; + message = "Loading metrics..."; + + metrics = await Service.GetSiteMetricsAsync(filterOptions); + + if (!metrics.HasContent) + { + message = "No metrics found for the selected filter options. Please select a different set of filter options..."; + displayStyle = "d-none"; + } + else + { + messageStyle = "d-none"; + displayStyle = ""; + } + + StateHasChanged(); + + await JSRuntime.InvokeVoidAsync("DrawDetectionsChart", metrics.DetectionsArray); + await JSRuntime.InvokeVoidAsync("DrawDetectionResultsChart", metrics.DetectionResultsArray); + } + + private async Task ActOnApplyFilterCallback(MetricsFilterDTO returnedFilterOptions) + { + filterOptions = returnedFilterOptions; + await LoadMetrics(); + } } diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Pages/Detections/Candidates.razor b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Pages/Detections/Candidates.razor index 5bff96bf..35d8d4f0 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Pages/Detections/Candidates.razor +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Pages/Detections/Candidates.razor @@ -5,15 +5,15 @@ + ApplyFilterCallback="ActOnApplyFilterCallback" /> @if (loadStatus != null) { -
@loadStatus
+
@loadStatus
} else { - + @if (detections != null && detections.Count > 0) {
@@ -23,22 +23,22 @@ else
} - - @for (var i = 0; i < detections.Count(); i++) - { - - } + + @for (var i = 0; i < detections.Count(); i++) + { + + } } @if (detections != null && detections.Count > 0) { -
- -
+
+ +
} diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Pages/Detections/Candidates.razor.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Pages/Detections/Candidates.razor.cs index a2f7d432..6882d038 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Pages/Detections/Candidates.razor.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Pages/Detections/Candidates.razor.cs @@ -2,111 +2,111 @@ public partial class Candidates : IDisposable { - [Inject] - IJSRuntime JSRuntime { get; set; } - - [Inject] - IDetectionService Service { get; set; } - - [Inject] - IToastService ToastService { get; set; } - - [Inject] - UserTagCache TagCache { get; set; } - - [Inject] - AuthenticationStateProvider AuthenticationStateProvider { get; set; } - - private string _userId; - private List detections = null; - - private PaginationOptionsDTO paginationOptions = - new PaginationOptionsDTO() { RecordsPerPage = 5, Page = 1 }; - - private CandidateFilterOptionsDTO filterOptions = - new CandidateFilterOptionsDTO() { SortBy = "timestamp", SortOrder = "desc", Timeframe = "6h", Location = "all", HydrophoneId = "all" }; - - private PaginationResultsDTO pagination = new PaginationResultsDTO(); - - private string loadStatus = null; - - protected override async Task OnInitializedAsync() - { - await LoadDetections(); - - var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync(); - var user = authState.User; - _userId = user.FindFirst("oid")?.Value; - } - - private async Task LoadDetections() - { - loadStatus = "Loading records..."; - detections = null; - var paginatedResponse = await Service.GetCandidateDetectionsAsync(paginationOptions, filterOptions); - - pagination.TotalNumberOfRecords = paginatedResponse.TotalNumberRecords; - pagination.TotalNumberOfPages = paginatedResponse.TotalAmountPages; - - // The page we requested may no longer exist. - // Clamp to the actual last valid page. re-fetch to render real data - if (pagination.TotalNumberOfPages > 0 && paginationOptions.Page > pagination.TotalNumberOfPages) - { - paginationOptions.Page = pagination.TotalNumberOfPages; - paginatedResponse = await Service.GetCandidateDetectionsAsync(paginationOptions, filterOptions); - pagination.TotalNumberOfRecords = paginatedResponse.TotalNumberRecords; - pagination.TotalNumberOfPages = paginatedResponse.TotalAmountPages; - } - pagination.CurrentPage = paginationOptions.Page; - - if (paginatedResponse.Response == null) - loadStatus = "An unknown error occurred while loading records..."; - else if (paginatedResponse.Response.Count == 0) - { - loadStatus = pagination.TotalNumberOfRecords == 0 - ? "You're caught up, no records match the selected filter options..." - : "No records found for the selected filter options. Please select a different set of filter options..."; - } - else - { - loadStatus = null; - detections = paginatedResponse.Response; - } - } - - private async Task ActOnSelectPageCallback(PaginationOptionsDTO returnedPaginationOptions) - { - paginationOptions = returnedPaginationOptions; - await LoadDetections(); - await JSRuntime.InvokeVoidAsync("DestroyActivePlayer"); - StateHasChanged(); - } - - private async Task ActOnApplyFilterCallback(CandidateFilterOptionsDTO returnedFilterOptions) - { - filterOptions = returnedFilterOptions; - paginationOptions.Page = 1; - await LoadDetections(); - await JSRuntime.InvokeVoidAsync("DestroyActivePlayer"); - StateHasChanged(); - } - - private async Task ActOnSubmitCallback(DetectionUpdate request) - { - await Service.UpdateRequestAsync(request); - - List leafTags = Detection.GetLeafTags(request.Tags); - TagCache.SetTags(_userId, leafTags); - - ToastService.ShowSuccess("Detection successfully updated."); - - await JSRuntime.InvokeVoidAsync("DestroyActivePlayer"); - await LoadDetections(); - } - - void IDisposable.Dispose() - { - JSRuntime.InvokeVoidAsync("DestroyActivePlayer"); - } + [Inject] + IJSRuntime JSRuntime { get; set; } + + [Inject] + IDetectionService Service { get; set; } + + [Inject] + IToastService ToastService { get; set; } + + [Inject] + UserTagCache TagCache { get; set; } + + [Inject] + AuthenticationStateProvider AuthenticationStateProvider { get; set; } + + private string _userId; + private List detections = null; + + private PaginationOptionsDTO paginationOptions = + new PaginationOptionsDTO() { RecordsPerPage = 5, Page = 1 }; + + private CandidateFilterOptionsDTO filterOptions = + new CandidateFilterOptionsDTO() { SortBy = "timestamp", SortOrder = "desc", Timeframe = "6h", Location = "all", HydrophoneId = "all" }; + + private PaginationResultsDTO pagination = new PaginationResultsDTO(); + + private string loadStatus = null; + + protected override async Task OnInitializedAsync() + { + await LoadDetections(); + + var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync(); + var user = authState.User; + _userId = user.FindFirst("oid")?.Value; + } + + private async Task LoadDetections() + { + loadStatus = "Loading records..."; + detections = null; + var paginatedResponse = await Service.GetCandidateDetectionsAsync(paginationOptions, filterOptions); + + pagination.TotalNumberOfRecords = paginatedResponse.TotalNumberRecords; + pagination.TotalNumberOfPages = paginatedResponse.TotalAmountPages; + + // The page we requested may no longer exist. + // Clamp to the actual last valid page. re-fetch to render real data + if (pagination.TotalNumberOfPages > 0 && paginationOptions.Page > pagination.TotalNumberOfPages) + { + paginationOptions.Page = pagination.TotalNumberOfPages; + paginatedResponse = await Service.GetCandidateDetectionsAsync(paginationOptions, filterOptions); + pagination.TotalNumberOfRecords = paginatedResponse.TotalNumberRecords; + pagination.TotalNumberOfPages = paginatedResponse.TotalAmountPages; + } + pagination.CurrentPage = paginationOptions.Page; + + if (paginatedResponse.Response == null) + loadStatus = "An unknown error occurred while loading records..."; + else if (paginatedResponse.Response.Count == 0) + { + loadStatus = pagination.TotalNumberOfRecords == 0 + ? "You're caught up, no records match the selected filter options..." + : "No records found for the selected filter options. Please select a different set of filter options..."; + } + else + { + loadStatus = null; + detections = paginatedResponse.Response; + } + } + + private async Task ActOnSelectPageCallback(PaginationOptionsDTO returnedPaginationOptions) + { + paginationOptions = returnedPaginationOptions; + await LoadDetections(); + await JSRuntime.InvokeVoidAsync("DestroyActivePlayer"); + StateHasChanged(); + } + + private async Task ActOnApplyFilterCallback(CandidateFilterOptionsDTO returnedFilterOptions) + { + filterOptions = returnedFilterOptions; + paginationOptions.Page = 1; + await LoadDetections(); + await JSRuntime.InvokeVoidAsync("DestroyActivePlayer"); + StateHasChanged(); + } + + private async Task ActOnSubmitCallback(DetectionUpdate request) + { + await Service.UpdateRequestAsync(request); + + List leafTags = Detection.GetLeafTags(request.Tags); + TagCache.SetTags(_userId, leafTags); + + ToastService.ShowSuccess("Detection successfully updated."); + + await JSRuntime.InvokeVoidAsync("DestroyActivePlayer"); + await LoadDetections(); + } + + void IDisposable.Dispose() + { + JSRuntime.InvokeVoidAsync("DestroyActivePlayer"); + } } diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Pages/Detections/Confirmed.razor b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Pages/Detections/Confirmed.razor index 3a5ea655..f6d84e9d 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Pages/Detections/Confirmed.razor +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Pages/Detections/Confirmed.razor @@ -4,38 +4,38 @@ + ApplyFilterCallback="ActOnApplyFilterCallback" /> @if (loadStatus != null) { -
@loadStatus
+
@loadStatus
} else { - - @if (detections != null && detections.Count > 0) - { -
- -
- } + + @if (detections != null && detections.Count > 0) + { +
+ +
+ } - - @for (var i = 0; i < detections.Count(); i++) - { - - } + + @for (var i = 0; i < detections.Count(); i++) + { + + } } @if (detections != null && detections.Count > 0) { -
- -
+
+ +
} \ No newline at end of file diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Pages/Detections/Confirmed.razor.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Pages/Detections/Confirmed.razor.cs index 4304c9a5..0da13bd8 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Pages/Detections/Confirmed.razor.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Pages/Detections/Confirmed.razor.cs @@ -2,94 +2,94 @@ public partial class Confirmed : IDisposable { - [Inject] - IJSRuntime JSRuntime { get; set; } - - [Inject] - IDetectionService Service { get; set; } - - [Inject] - IToastService ToastService { get; set; } - - [Inject] - UserTagCache TagCache { get; set; } - - [Inject] - AuthenticationStateProvider AuthenticationStateProvider { get; set; } - - private string _userId; - private List detections = null; - - private PaginationOptionsDTO paginationOptions = - new PaginationOptionsDTO() { RecordsPerPage = 5, Page = 1 }; - - private ReviewedFilterOptionsDTO filterOptions = - new ReviewedFilterOptionsDTO() { SortBy = "timestamp", SortOrder = "desc", Timeframe = "24h", Location = "all" }; - - private PaginationResultsDTO pagination = new PaginationResultsDTO(); - - private string loadStatus = null; - - protected override async Task OnInitializedAsync() - { - await LoadDetections(); - - var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync(); - var user = authState.User; - _userId = user.FindFirst("oid")?.Value; - } - - private async Task LoadDetections() - { - loadStatus = "Loading records..."; - var paginatedResponse = await Service.GetConfirmedDetectionsAsync(paginationOptions, filterOptions); - - pagination.TotalNumberOfRecords = paginatedResponse.TotalNumberRecords; - pagination.TotalNumberOfPages = paginatedResponse.TotalAmountPages; - pagination.CurrentPage = paginationOptions.Page; - - if (paginatedResponse.Response == null) - loadStatus = "An unknown error occurred while loading records..."; - else if (paginatedResponse.Response.Count == 0) - loadStatus = "No records found for the selected filter options. Please select a different set of filter options..."; - else - { - loadStatus = null; - detections = paginatedResponse.Response; - } - } - private async Task ActOnSelectPageCallback(PaginationOptionsDTO returnedPaginationOptions) - { - paginationOptions = returnedPaginationOptions; - detections = null; - await LoadDetections(); - await JSRuntime.InvokeVoidAsync("DestroyActivePlayer"); - StateHasChanged(); - } - - private async Task ActOnSubmitCallback(DetectionUpdate request) - { - await Service.UpdateRequestAsync(request); - - List leafTags = Detection.GetLeafTags(request.Tags); - TagCache.SetTags(_userId, leafTags); - - ToastService.ShowSuccess("Detection successfully updated."); - - await LoadDetections(); - } - - private async Task ActOnApplyFilterCallback(ReviewedFilterOptionsDTO returnedFilterOptions) - { - filterOptions = returnedFilterOptions; - detections = null; - await LoadDetections(); - await JSRuntime.InvokeVoidAsync("DestroyActivePlayer"); - StateHasChanged(); - } - - void IDisposable.Dispose() - { - JSRuntime.InvokeVoidAsync("DestroyActivePlayer"); - } + [Inject] + IJSRuntime JSRuntime { get; set; } + + [Inject] + IDetectionService Service { get; set; } + + [Inject] + IToastService ToastService { get; set; } + + [Inject] + UserTagCache TagCache { get; set; } + + [Inject] + AuthenticationStateProvider AuthenticationStateProvider { get; set; } + + private string _userId; + private List detections = null; + + private PaginationOptionsDTO paginationOptions = + new PaginationOptionsDTO() { RecordsPerPage = 5, Page = 1 }; + + private ReviewedFilterOptionsDTO filterOptions = + new ReviewedFilterOptionsDTO() { SortBy = "timestamp", SortOrder = "desc", Timeframe = "24h", Location = "all" }; + + private PaginationResultsDTO pagination = new PaginationResultsDTO(); + + private string loadStatus = null; + + protected override async Task OnInitializedAsync() + { + await LoadDetections(); + + var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync(); + var user = authState.User; + _userId = user.FindFirst("oid")?.Value; + } + + private async Task LoadDetections() + { + loadStatus = "Loading records..."; + var paginatedResponse = await Service.GetConfirmedDetectionsAsync(paginationOptions, filterOptions); + + pagination.TotalNumberOfRecords = paginatedResponse.TotalNumberRecords; + pagination.TotalNumberOfPages = paginatedResponse.TotalAmountPages; + pagination.CurrentPage = paginationOptions.Page; + + if (paginatedResponse.Response == null) + loadStatus = "An unknown error occurred while loading records..."; + else if (paginatedResponse.Response.Count == 0) + loadStatus = "No records found for the selected filter options. Please select a different set of filter options..."; + else + { + loadStatus = null; + detections = paginatedResponse.Response; + } + } + private async Task ActOnSelectPageCallback(PaginationOptionsDTO returnedPaginationOptions) + { + paginationOptions = returnedPaginationOptions; + detections = null; + await LoadDetections(); + await JSRuntime.InvokeVoidAsync("DestroyActivePlayer"); + StateHasChanged(); + } + + private async Task ActOnSubmitCallback(DetectionUpdate request) + { + await Service.UpdateRequestAsync(request); + + List leafTags = Detection.GetLeafTags(request.Tags); + TagCache.SetTags(_userId, leafTags); + + ToastService.ShowSuccess("Detection successfully updated."); + + await LoadDetections(); + } + + private async Task ActOnApplyFilterCallback(ReviewedFilterOptionsDTO returnedFilterOptions) + { + filterOptions = returnedFilterOptions; + detections = null; + await LoadDetections(); + await JSRuntime.InvokeVoidAsync("DestroyActivePlayer"); + StateHasChanged(); + } + + void IDisposable.Dispose() + { + JSRuntime.InvokeVoidAsync("DestroyActivePlayer"); + } } diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Pages/Detections/FalsePositives.razor.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Pages/Detections/FalsePositives.razor.cs index 9be78aa6..9979aed9 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Pages/Detections/FalsePositives.razor.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Pages/Detections/FalsePositives.razor.cs @@ -2,96 +2,96 @@ public partial class FalsePositives : IDisposable { - [Inject] - IJSRuntime JSRuntime { get; set; } - - [Inject] - IDetectionService Service { get; set; } - - [Inject] - IToastService ToastService { get; set; } - - [Inject] - UserTagCache TagCache { get; set; } - - [Inject] - AuthenticationStateProvider AuthenticationStateProvider { get; set; } - - private string _userId; - private List detections = null; - - private PaginationOptionsDTO paginationOptions = - new PaginationOptionsDTO() { RecordsPerPage = 5, Page = 1 }; - - private ReviewedFilterOptionsDTO filterOptions = - new ReviewedFilterOptionsDTO() { SortBy = "timestamp", SortOrder = "desc", Timeframe = "24h", Location = "all" }; - - private PaginationResultsDTO pagination = new PaginationResultsDTO(); - - private string loadStatus = null; - - protected override async Task OnInitializedAsync() - { - await LoadDetections(); - - var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync(); - var user = authState.User; - _userId = user.FindFirst("oid")?.Value; - } - - private async Task LoadDetections() - { - loadStatus = "Loading records..."; - var paginatedResponse = await Service.GetFalseDetectionsAsync(paginationOptions, filterOptions); - - pagination.TotalNumberOfRecords = paginatedResponse.TotalNumberRecords; - pagination.TotalNumberOfPages = paginatedResponse.TotalAmountPages; - pagination.CurrentPage = paginationOptions.Page; - - if (paginatedResponse.Response == null) - loadStatus = "An unknown error occurred while loading records..."; - else if (paginatedResponse.Response.Count == 0) - loadStatus = "No records found for the selected filter options. Please select a different set of filter options..."; - else - { - loadStatus = null; - detections = paginatedResponse.Response; - } - } - - private async Task ActOnSelectPageCallback(PaginationOptionsDTO returnedPaginationOptions) - { - paginationOptions = returnedPaginationOptions; - detections = null; - await LoadDetections(); - await JSRuntime.InvokeVoidAsync("DestroyActivePlayer"); - StateHasChanged(); - } - - private async Task ActOnApplyFilterCallback(ReviewedFilterOptionsDTO returnedFilterOptions) - { - filterOptions = returnedFilterOptions; - paginationOptions.Page = 1; - detections = null; - await LoadDetections(); - await JSRuntime.InvokeVoidAsync("DestroyActivePlayer"); - StateHasChanged(); - } - - private async Task ActOnSubmitCallback(DetectionUpdate request) - { - await Service.UpdateRequestAsync(request); - - List leafTags = Detection.GetLeafTags(request.Tags); - TagCache.SetTags(_userId, leafTags); - - ToastService.ShowSuccess("Detection successfully updated."); - - await LoadDetections(); - } - - void IDisposable.Dispose() - { - JSRuntime.InvokeVoidAsync("DestroyActivePlayer"); - } + [Inject] + IJSRuntime JSRuntime { get; set; } + + [Inject] + IDetectionService Service { get; set; } + + [Inject] + IToastService ToastService { get; set; } + + [Inject] + UserTagCache TagCache { get; set; } + + [Inject] + AuthenticationStateProvider AuthenticationStateProvider { get; set; } + + private string _userId; + private List detections = null; + + private PaginationOptionsDTO paginationOptions = + new PaginationOptionsDTO() { RecordsPerPage = 5, Page = 1 }; + + private ReviewedFilterOptionsDTO filterOptions = + new ReviewedFilterOptionsDTO() { SortBy = "timestamp", SortOrder = "desc", Timeframe = "24h", Location = "all" }; + + private PaginationResultsDTO pagination = new PaginationResultsDTO(); + + private string loadStatus = null; + + protected override async Task OnInitializedAsync() + { + await LoadDetections(); + + var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync(); + var user = authState.User; + _userId = user.FindFirst("oid")?.Value; + } + + private async Task LoadDetections() + { + loadStatus = "Loading records..."; + var paginatedResponse = await Service.GetFalseDetectionsAsync(paginationOptions, filterOptions); + + pagination.TotalNumberOfRecords = paginatedResponse.TotalNumberRecords; + pagination.TotalNumberOfPages = paginatedResponse.TotalAmountPages; + pagination.CurrentPage = paginationOptions.Page; + + if (paginatedResponse.Response == null) + loadStatus = "An unknown error occurred while loading records..."; + else if (paginatedResponse.Response.Count == 0) + loadStatus = "No records found for the selected filter options. Please select a different set of filter options..."; + else + { + loadStatus = null; + detections = paginatedResponse.Response; + } + } + + private async Task ActOnSelectPageCallback(PaginationOptionsDTO returnedPaginationOptions) + { + paginationOptions = returnedPaginationOptions; + detections = null; + await LoadDetections(); + await JSRuntime.InvokeVoidAsync("DestroyActivePlayer"); + StateHasChanged(); + } + + private async Task ActOnApplyFilterCallback(ReviewedFilterOptionsDTO returnedFilterOptions) + { + filterOptions = returnedFilterOptions; + paginationOptions.Page = 1; + detections = null; + await LoadDetections(); + await JSRuntime.InvokeVoidAsync("DestroyActivePlayer"); + StateHasChanged(); + } + + private async Task ActOnSubmitCallback(DetectionUpdate request) + { + await Service.UpdateRequestAsync(request); + + List leafTags = Detection.GetLeafTags(request.Tags); + TagCache.SetTags(_userId, leafTags); + + ToastService.ShowSuccess("Detection successfully updated."); + + await LoadDetections(); + } + + void IDisposable.Dispose() + { + JSRuntime.InvokeVoidAsync("DestroyActivePlayer"); + } } diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Pages/Detections/SingleDetection.razor b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Pages/Detections/SingleDetection.razor index 06236dad..95e00014 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Pages/Detections/SingleDetection.razor +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Pages/Detections/SingleDetection.razor @@ -5,19 +5,19 @@ @if (isUnavailable) { -
An unknown error occurred while loading the record...
+
An unknown error occurred while loading the record...
} else if (detection == null) { -
Loading record...
+
Loading record...
} else if (isFound) { - + } else { -
Requested Detection with ID '@Id' could not be found.
+
Requested Detection with ID '@Id' could not be found.
} \ No newline at end of file diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Pages/Detections/SingleDetection.razor.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Pages/Detections/SingleDetection.razor.cs index 19d8cc15..344273dc 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Pages/Detections/SingleDetection.razor.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Pages/Detections/SingleDetection.razor.cs @@ -2,60 +2,60 @@ public partial class SingleDetection : ComponentBase, IDisposable { - [Parameter] - public string Id { get; set; } + [Parameter] + public string Id { get; set; } - [Inject] - IJSRuntime JSRuntime { get; set; } + [Inject] + IJSRuntime JSRuntime { get; set; } - [Inject] - IDetectionService Service { get; set; } + [Inject] + IDetectionService Service { get; set; } - [Inject] - IToastService ToastService { get; set; } + [Inject] + IToastService ToastService { get; set; } - [Inject] - UserTagCache TagCache { get; set; } + [Inject] + UserTagCache TagCache { get; set; } - [Inject] - AuthenticationStateProvider AuthenticationStateProvider { get; set; } + [Inject] + AuthenticationStateProvider AuthenticationStateProvider { get; set; } - private string _userId; - private Detection detection = null; - private bool isFound = true; - private bool isUnavailable = false; + private string _userId; + private Detection detection = null; + private bool isFound = true; + private bool isUnavailable = false; - protected override async Task OnInitializedAsync() - { - await LoadDetection(); + protected override async Task OnInitializedAsync() + { + await LoadDetection(); - var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync(); - var user = authState.User; - _userId = user.FindFirst("oid")?.Value; - } + var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync(); + var user = authState.User; + _userId = user.FindFirst("oid")?.Value; + } - private async Task LoadDetection() - { - detection = await Service.GetDetectionAsync(Id); - isUnavailable = detection == null; - if (!isUnavailable && detection.Id == null) - isFound = false; - } + private async Task LoadDetection() + { + detection = await Service.GetDetectionAsync(Id); + isUnavailable = detection == null; + if (!isUnavailable && detection.Id == null) + isFound = false; + } - private async Task ActOnSubmitCallback(DetectionUpdate request) - { - await Service.UpdateRequestAsync(request); + private async Task ActOnSubmitCallback(DetectionUpdate request) + { + await Service.UpdateRequestAsync(request); - List leafTags = Detection.GetLeafTags(request.Tags); - TagCache.SetTags(_userId, leafTags); + List leafTags = Detection.GetLeafTags(request.Tags); + TagCache.SetTags(_userId, leafTags); - ToastService.ShowSuccess("Detection successfully updated."); + ToastService.ShowSuccess("Detection successfully updated."); - await LoadDetection(); - } + await LoadDetection(); + } - void IDisposable.Dispose() - { - JSRuntime.InvokeVoidAsync("DestroyActivePlayer"); - } + void IDisposable.Dispose() + { + JSRuntime.InvokeVoidAsync("DestroyActivePlayer"); + } } diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Pages/Detections/Unknown.razor b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Pages/Detections/Unknown.razor index c1bd2ff8..647abfb4 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Pages/Detections/Unknown.razor +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Pages/Detections/Unknown.razor @@ -4,40 +4,40 @@ + ApplyFilterCallback="ActOnApplyFilterCallback" /> @if (loadStatus != null) { -
@loadStatus
+
@loadStatus
} else { - - @if (detections != null && detections.Count > 0) - { -
- -
- } + + @if (detections != null && detections.Count > 0) + { +
+ +
+ } - - @for (var i = 0; i < detections.Count(); i++) - { - - } + + @for (var i = 0; i < detections.Count(); i++) + { + + } } @if (detections != null && detections.Count > 0) { -
- -
+
+ +
} diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Pages/Detections/Unknown.razor.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Pages/Detections/Unknown.razor.cs index 26aa3a66..81005dac 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Pages/Detections/Unknown.razor.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Pages/Detections/Unknown.razor.cs @@ -2,96 +2,96 @@ public partial class Unknown : IDisposable { - [Inject] - IJSRuntime JSRuntime { get; set; } - - [Inject] - IDetectionService Service { get; set; } - - [Inject] - IToastService ToastService { get; set; } - - [Inject] - UserTagCache TagCache { get; set; } - - [Inject] - AuthenticationStateProvider AuthenticationStateProvider { get; set; } - - private string _userId; - private List detections = null; - - private PaginationOptionsDTO paginationOptions = - new PaginationOptionsDTO() { RecordsPerPage = 5, Page = 1 }; - - private CandidateFilterOptionsDTO filterOptions = - new CandidateFilterOptionsDTO() { SortBy = "timestamp", SortOrder = "desc", Timeframe = "24h", Location = "all", HydrophoneId = "all" }; - - private PaginationResultsDTO pagination = new PaginationResultsDTO(); - - private string loadStatus = null; - - protected override async Task OnInitializedAsync() - { - await LoadDetections(); - - var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync(); - var user = authState.User; - _userId = user.FindFirst("oid")?.Value; - } - - private async Task LoadDetections() - { - loadStatus = "Loading records..."; - var paginatedResponse = await Service.GetUnconfirmedDetectionsAsync(paginationOptions, filterOptions); - - pagination.TotalNumberOfRecords = paginatedResponse.TotalNumberRecords; - pagination.TotalNumberOfPages = paginatedResponse.TotalAmountPages; - pagination.CurrentPage = paginationOptions.Page; - - if (paginatedResponse.Response == null) - loadStatus = "An unknown error occurred while loading records..."; - else if (paginatedResponse.Response.Count == 0) - loadStatus = "No records found for the selected filter options. Please select a different set of filter options..."; - else - { - loadStatus = null; - detections = paginatedResponse.Response; - } - } - - private async Task ActOnSelectPageCallback(PaginationOptionsDTO returnedPaginationOptions) - { - paginationOptions = returnedPaginationOptions; - detections = null; - await LoadDetections(); - await JSRuntime.InvokeVoidAsync("DestroyActivePlayer"); - StateHasChanged(); - } - - private async Task ActOnApplyFilterCallback(CandidateFilterOptionsDTO returnedFilterOptions) - { - filterOptions = returnedFilterOptions; - paginationOptions.Page = 1; - detections = null; - await LoadDetections(); - await JSRuntime.InvokeVoidAsync("DestroyActivePlayer"); - StateHasChanged(); - } - - private async Task ActOnSubmitCallback(DetectionUpdate request) - { - await Service.UpdateRequestAsync(request); - - List leafTags = Detection.GetLeafTags(request.Tags); - TagCache.SetTags(_userId, leafTags); - - ToastService.ShowSuccess("Detection successfully updated."); - - await LoadDetections(); - } - - void IDisposable.Dispose() - { - JSRuntime.InvokeVoidAsync("DestroyActivePlayer"); - } + [Inject] + IJSRuntime JSRuntime { get; set; } + + [Inject] + IDetectionService Service { get; set; } + + [Inject] + IToastService ToastService { get; set; } + + [Inject] + UserTagCache TagCache { get; set; } + + [Inject] + AuthenticationStateProvider AuthenticationStateProvider { get; set; } + + private string _userId; + private List detections = null; + + private PaginationOptionsDTO paginationOptions = + new PaginationOptionsDTO() { RecordsPerPage = 5, Page = 1 }; + + private CandidateFilterOptionsDTO filterOptions = + new CandidateFilterOptionsDTO() { SortBy = "timestamp", SortOrder = "desc", Timeframe = "24h", Location = "all", HydrophoneId = "all" }; + + private PaginationResultsDTO pagination = new PaginationResultsDTO(); + + private string loadStatus = null; + + protected override async Task OnInitializedAsync() + { + await LoadDetections(); + + var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync(); + var user = authState.User; + _userId = user.FindFirst("oid")?.Value; + } + + private async Task LoadDetections() + { + loadStatus = "Loading records..."; + var paginatedResponse = await Service.GetUnconfirmedDetectionsAsync(paginationOptions, filterOptions); + + pagination.TotalNumberOfRecords = paginatedResponse.TotalNumberRecords; + pagination.TotalNumberOfPages = paginatedResponse.TotalAmountPages; + pagination.CurrentPage = paginationOptions.Page; + + if (paginatedResponse.Response == null) + loadStatus = "An unknown error occurred while loading records..."; + else if (paginatedResponse.Response.Count == 0) + loadStatus = "No records found for the selected filter options. Please select a different set of filter options..."; + else + { + loadStatus = null; + detections = paginatedResponse.Response; + } + } + + private async Task ActOnSelectPageCallback(PaginationOptionsDTO returnedPaginationOptions) + { + paginationOptions = returnedPaginationOptions; + detections = null; + await LoadDetections(); + await JSRuntime.InvokeVoidAsync("DestroyActivePlayer"); + StateHasChanged(); + } + + private async Task ActOnApplyFilterCallback(CandidateFilterOptionsDTO returnedFilterOptions) + { + filterOptions = returnedFilterOptions; + paginationOptions.Page = 1; + detections = null; + await LoadDetections(); + await JSRuntime.InvokeVoidAsync("DestroyActivePlayer"); + StateHasChanged(); + } + + private async Task ActOnSubmitCallback(DetectionUpdate request) + { + await Service.UpdateRequestAsync(request); + + List leafTags = Detection.GetLeafTags(request.Tags); + TagCache.SetTags(_userId, leafTags); + + ToastService.ShowSuccess("Detection successfully updated."); + + await LoadDetections(); + } + + void IDisposable.Dispose() + { + JSRuntime.InvokeVoidAsync("DestroyActivePlayer"); + } } diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Pages/MainLayout.razor b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Pages/MainLayout.razor index ae1454aa..cf257133 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Pages/MainLayout.razor +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Pages/MainLayout.razor @@ -4,35 +4,35 @@
- -
+ +
- + - -
+ +
- -
+ +
- + - -
- @Body -
-
+ +
+ @Body +
+
-
- -
- - - - - +
+ +
+ + + + + - +
+ ApplyFilterCallback="ActOnApplyFilterCallback" />
@message
-
+
-
+
- -
- -
-
Detections
-
- -
-
- -
-
- - Reviewed - - - Unreviewed - -
-
-
+ +
+ +
+
Detections
+
+ +
+
+ +
+
+ + Reviewed + + + Unreviewed + +
+
+
-
-
+
+
- + -
- -
-
Results
-
- -
-
- -
-
- - Confirmed - - - False Positives - - - Don't Know - -
-
+
+ +
+
Results
+
+ +
+
+ +
+
+ + Confirmed + + + False Positives + + + Don't Know + +
+
-
-
-
+
+
+
-
-
-
- -
-
-
- @if (metrics != null) - { - @foreach (var entry in metrics.Tags) - { -
@entry.Tag
-
    - @foreach (var link in entry.Ids) - { -
  • @link
  • - } -
- } - } -
-
-
-
+
+
+
+ +
+
+
+ @if (metrics != null) + { + @foreach (var entry in metrics.Tags) + { +
@entry.Tag
+
    + @foreach (var link in entry.Ids) + { +
  • @link
  • + } +
+ } + } +
+
+
+
-
- -
-
- @if (metrics != null) - {
    - @foreach (var entry in metrics.ConfirmedComments) - { -
  • -
    -
    - -
    -
    -
    - @entry.Id -
    - @DateHelper.UTCToPDTFull(entry.Timestamp)
    -
    -
    -
    -

    @entry.Comment

    -
    @entry.Moderator
    -
    -
    -
    -
  • - } -
- } -
-
-
+
+ +
+
+ @if (metrics != null) + {
    + @foreach (var entry in metrics.ConfirmedComments) + { +
  • +
    +
    + +
    +
    +
    + @entry.Id +
    + @DateHelper.UTCToPDTFull(entry.Timestamp)
    +
    +
    +
    +

    @entry.Comment

    +
    @entry.Moderator
    +
    +
    +
    +
  • + } +
+ } +
+
+
-
- -
-
- @if (metrics != null) - {
    - @foreach (var entry in metrics.UnconfirmedComments) - { -
  • -
    -
    - -
    -
    -
    - @entry.Id -
    - @DateHelper.UTCToPDTFull(entry.Timestamp)
    -
    -
    -
    -

    @entry.Comment

    -
    @entry.Moderator
    -
    -
    -
    -
  • - } -
- } -
-
-
-
-
+
+ +
+
+ @if (metrics != null) + {
    + @foreach (var entry in metrics.UnconfirmedComments) + { +
  • +
    +
    + +
    +
    +
    + @entry.Id +
    + @DateHelper.UTCToPDTFull(entry.Timestamp)
    +
    +
    +
    +

    @entry.Comment

    +
    @entry.Moderator
    +
    +
    +
    +
  • + } +
+ } +
+
+
+
+
@@ -180,88 +180,88 @@ diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Pages/User/UserActivity.razor.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Pages/User/UserActivity.razor.cs index 5e0cb4e8..33066a0d 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Pages/User/UserActivity.razor.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Pages/User/UserActivity.razor.cs @@ -2,60 +2,60 @@ public partial class UserActivity { - [Inject] - IMetricsService Service { get; set; } - - [Inject] - IJSRuntime JSRuntime { get; set; } - - [Inject] - //AuthenticationStateProvider AuthenticationStateProvider { get; set; } - IAccountService AccountService { get; set; } - - private ModeratorMetrics metrics = null; - - private ModeratorMetricsFilterDTO filterOptions = - new ModeratorMetricsFilterDTO() { Timeframe = "1m" }; - - private string messageStyle = "d-none"; - private string message = string.Empty; - private string displayStyle = "d-none"; - - protected override async Task OnInitializedAsync() - { - await LoadMetrics(); - } - - private async Task LoadMetrics() - { - filterOptions.Moderator = await AccountService.GetUsername(); - - displayStyle = "d-none"; - messageStyle = ""; - message = "Loading metrics..."; - - metrics = await Service.GetModeratorMetricsAsync(filterOptions); - - if (!metrics.HasContent) - { - message = "No metrics found for the selected filter options. Please select a different set of filter options..."; - displayStyle = "d-none"; - } - else - { - messageStyle = "d-none"; - displayStyle = ""; - } - - StateHasChanged(); - - await JSRuntime.InvokeVoidAsync("DrawDetectionsChart", metrics.DetectionsArray); - await JSRuntime.InvokeVoidAsync("DrawDetectionResultsChart", metrics.DetectionResultsArray); - } - - private async Task ActOnApplyFilterCallback(MetricsFilterDTO returnedFilterOptions) - { - filterOptions.Timeframe = returnedFilterOptions.Timeframe; - await LoadMetrics(); - } + [Inject] + IMetricsService Service { get; set; } + + [Inject] + IJSRuntime JSRuntime { get; set; } + + [Inject] + //AuthenticationStateProvider AuthenticationStateProvider { get; set; } + IAccountService AccountService { get; set; } + + private ModeratorMetrics metrics = null; + + private ModeratorMetricsFilterDTO filterOptions = + new ModeratorMetricsFilterDTO() { Timeframe = "1m" }; + + private string messageStyle = "d-none"; + private string message = string.Empty; + private string displayStyle = "d-none"; + + protected override async Task OnInitializedAsync() + { + await LoadMetrics(); + } + + private async Task LoadMetrics() + { + filterOptions.Moderator = await AccountService.GetUsername(); + + displayStyle = "d-none"; + messageStyle = ""; + message = "Loading metrics..."; + + metrics = await Service.GetModeratorMetricsAsync(filterOptions); + + if (!metrics.HasContent) + { + message = "No metrics found for the selected filter options. Please select a different set of filter options..."; + displayStyle = "d-none"; + } + else + { + messageStyle = "d-none"; + displayStyle = ""; + } + + StateHasChanged(); + + await JSRuntime.InvokeVoidAsync("DrawDetectionsChart", metrics.DetectionsArray); + await JSRuntime.InvokeVoidAsync("DrawDetectionResultsChart", metrics.DetectionResultsArray); + } + + private async Task ActOnApplyFilterCallback(MetricsFilterDTO returnedFilterOptions) + { + filterOptions.Timeframe = returnedFilterOptions.Timeframe; + await LoadMetrics(); + } } diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Services/AccountService.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Services/AccountService.cs index b876a5c0..5cdcdf9d 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Services/AccountService.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Services/AccountService.cs @@ -94,7 +94,7 @@ public async Task Login() { await apiProvider.MarkUserAsAuthenticated(token.AccessToken); } - + _logger.LogInformation("User logged in successfully"); } catch (Exception ex) @@ -109,9 +109,9 @@ public Task Logout() { apiProvider.MarkUserAsLoggedOut(); } - + _logger.LogInformation("User logged out"); - + return Task.CompletedTask; } } diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Services/ApiAuthenticationStateProvider.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Services/ApiAuthenticationStateProvider.cs index 14e06c5e..67e561bd 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Services/ApiAuthenticationStateProvider.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Services/ApiAuthenticationStateProvider.cs @@ -8,7 +8,7 @@ public class ApiAuthenticationStateProvider : AuthenticationStateProvider private readonly CircuitHandlerService _circuitHandler; private readonly ILogger _logger; private ClaimsPrincipal _currentUser = new ClaimsPrincipal(new ClaimsIdentity()); - + private static int _instanceCount = 0; private readonly int _instanceId; @@ -20,7 +20,7 @@ public ApiAuthenticationStateProvider( _tokenStore = tokenStore; _circuitHandler = circuitHandler; _logger = logger; - + _instanceId = Interlocked.Increment(ref _instanceCount); _logger.LogDebug("ApiAuthenticationStateProvider Instance #{InstanceId} created", _instanceId); } @@ -28,7 +28,7 @@ public ApiAuthenticationStateProvider( public override Task GetAuthenticationStateAsync() { _logger.LogDebug("GetAuthenticationStateAsync called on Instance #{InstanceId}", _instanceId); - + var circuitId = _circuitHandler.CircuitId; if (!string.IsNullOrWhiteSpace(circuitId)) { @@ -40,7 +40,7 @@ public override Task GetAuthenticationStateAsync() { var claims = ParseClaimsFromJwt(token).ToList(); _currentUser = new ClaimsPrincipal(new ClaimsIdentity(claims, "jwt")); - + _logger.LogDebug("Instance #{InstanceId} - User authenticated from token store", _instanceId); } catch (Exception ex) @@ -61,14 +61,14 @@ public override Task GetAuthenticationStateAsync() _currentUser = new ClaimsPrincipal(new ClaimsIdentity()); _logger.LogWarning("Instance #{InstanceId} has no circuit ID; treating user as anonymous", _instanceId); } - + return Task.FromResult(new AuthenticationState(_currentUser)); } public Task MarkUserAsAuthenticated(string token) { _logger.LogDebug("MarkUserAsAuthenticated called on Instance #{InstanceId}", _instanceId); - + if (string.IsNullOrWhiteSpace(token)) { _logger.LogWarning("Attempted to mark user as authenticated with empty token"); @@ -88,15 +88,15 @@ public Task MarkUserAsAuthenticated(string token) // Parse claims and update local state. var claims = ParseClaimsFromJwt(token).ToList(); - + _currentUser = new ClaimsPrincipal(new ClaimsIdentity(claims, "jwt")); - - _logger.LogDebug("Instance #{InstanceId} - _currentUser.IsAuthenticated = {IsAuth}", + + _logger.LogDebug("Instance #{InstanceId} - _currentUser.IsAuthenticated = {IsAuth}", _instanceId, _currentUser.Identity?.IsAuthenticated ?? false); - + // Notify authentication state changed. NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(_currentUser))); - + _logger.LogInformation("User authenticated successfully"); } catch (Exception ex) @@ -110,7 +110,7 @@ public Task MarkUserAsAuthenticated(string token) public void MarkUserAsLoggedOut() { _logger.LogDebug("MarkUserAsLoggedOut called on Instance #{InstanceId}", _instanceId); - + var circuitId = _circuitHandler.CircuitId; if (!string.IsNullOrWhiteSpace(circuitId)) { @@ -118,9 +118,9 @@ public void MarkUserAsLoggedOut() } _currentUser = new ClaimsPrincipal(new ClaimsIdentity()); - + NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(_currentUser))); - + _logger.LogInformation("User logged out"); } @@ -138,7 +138,7 @@ public string GetToken() private IEnumerable ParseClaimsFromJwt(string jwt) { var claims = new List(); - + try { var payload = jwt.Split('.')[1]; diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Services/UserTagCache.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Services/UserTagCache.cs index c9a00223..1ae6a2d0 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Services/UserTagCache.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Client.Web/Services/UserTagCache.cs @@ -22,7 +22,7 @@ public void SetTags(string userId, List tags) { if (string.IsNullOrWhiteSpace(userId) || tags == null) { - return; + return; } _cache[userId] = tags.ToList(); diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/API/Detections/Annotation.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/API/Detections/Annotation.cs index f8f0b3dd..d28b3afb 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/API/Detections/Annotation.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/API/Detections/Annotation.cs @@ -1,37 +1,37 @@ namespace AIForOrcas.DTO.API { - /// - /// Section within the detection that might contain whale sounds. - /// - public class Annotation - { - /// - /// Unique identifier (within the detection) of the annotation. - /// - /// 1 - public int Id { get; set; } + /// + /// Section within the detection that might contain whale sounds. + /// + public class Annotation + { + /// + /// Unique identifier (within the detection) of the annotation. + /// + /// 1 + public int Id { get; set; } - /// - /// Start time (within the detection) of the annotation as measured in seconds. - /// - /// 35 - public decimal StartTime { get; set; } + /// + /// Start time (within the detection) of the annotation as measured in seconds. + /// + /// 35 + public decimal StartTime { get; set; } - /// - /// End time (within the detection) of the annotation as measured in seconds. - /// - /// 37.5 - public decimal EndTime { get; set; } + /// + /// End time (within the detection) of the annotation as measured in seconds. + /// + /// 37.5 + public decimal EndTime { get; set; } - /// - /// Calculated confidence that the annotation contains a whale sound. - /// - /// 84.39 - public decimal Confidence { get; set; } + /// + /// Calculated confidence that the annotation contains a whale sound. + /// + /// 84.39 + public decimal Confidence { get; set; } - /// - /// Predicted label, if any. - /// - public string Label { get; set; } = string.Empty; - } + /// + /// Predicted label, if any. + /// + public string Label { get; set; } = string.Empty; + } } diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/API/Detections/Detection.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/API/Detections/Detection.cs index 3016b77a..670f8dd4 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/API/Detections/Detection.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/API/Detections/Detection.cs @@ -4,214 +4,214 @@ namespace AIForOrcas.DTO.API { - /// - /// A hydrophone sampling that might contain whale sounds. - /// - public class Detection - { - /// - /// The detection's generated unique Id. - /// - /// 00000000-0000-0000-0000-000000000000 - public string Id { get; set; } - - /// - /// URI of the detection's audio file (.wav) in blob storage. - /// - /// https://storagesite.blob.core.windows.net/audiowavs/audiofilename.wav - public string AudioUri { get; set; } - - /// - /// URI of the detection's image file (.png) in blob storage. - /// - /// https://storagesite.blob.core.windows.net/spectrogramspng/imagefilename.png - public string SpectrogramUri { get; set; } - - /// - /// Location of the microphone that collected the detection. - /// - public Location Location { get; set; } - - /// - /// Date and time of when the detection occurred. - /// - /// 2020-09-30T11:03:56.057346Z - public DateTime Timestamp { get; set; } - - /// - /// List of sections within the detection that might contain whale sounds. - /// - public List Annotations { get; set; } = new List(); - - /// - /// Flag indicating whether or not the dection has been reviewed by a human moderator. - /// - /// true - public bool Reviewed { get; set; } - - /// - /// Flag indicating whether the human moderator heard whale sounds in the detection. - /// - /// yes - public string Found { get; set; } - - /// - /// Any text comments entered by the human moderator during review. - /// - /// Clear whale sounds detected. - public string Comments { get; set; } - - /// - /// Calculated average confidence that the detection contains a whale sound. - /// - /// 84.39 - public decimal Confidence { get; set; } - - /// - /// Identity of the human moderator (User Principal Name for AzureAD) performing the review. - /// - /// user@gmail.com - public string Moderator { get; set; } - - /// - /// Date and time of when the detection was reviewed by the human moderator. - /// - /// 2020-09-30T11:03:56Z - public DateTime Moderated { get; set; } - - /// - /// Any text comments entered by the human moderator during review (separated by semi-colon). - /// - /// S7;S10 - public string Tags { get; set; } - - /// - /// Split tags into a list. - /// - /// Tags string to split - /// List of tags - public static List GetTagList(string tags) - { - if (string.IsNullOrWhiteSpace(tags)) - return new List(); - - string[] delimiters = new string[] { ";", "," }; - var rawTags = tags.Split(delimiters, StringSplitOptions.RemoveEmptyEntries); - var tagList = new List(rawTags.Length); - foreach (var rawTag in rawTags) - { - var trimmed = rawTag.Trim(); - if (trimmed.Length > 0) - tagList.Add(trimmed); - } - return tagList; - } - - /// - /// Get the leaf tags from the given tags string. - /// A leaf tag is a tag that does not have any child tags in the input list. - /// - /// - /// - public static List GetLeafTags(string tags) - { - List tagList = GetTagList(tags); - List leafTags = new List(); - foreach (var tag in tagList) - { - bool isLeaf = true; - foreach (var pair in TagHierarchy) - { - if (tag.Equals(pair.Value, StringComparison.OrdinalIgnoreCase) && tagList.Contains(pair.Key, StringComparer.OrdinalIgnoreCase)) - { - isLeaf = false; - break; - } - } - if (isLeaf) - { - leafTags.Add(tag); - } - } - return leafTags; - } - - /// - /// Tags in a list (parsed from the Tags string). - /// - public List TagList => GetTagList(Tags); - - /// - /// Hierarchy of tags, where the key is the child tag and the value is the parent tag. - /// A null value indicates a top-level tag. Within tags at the same level, more likely - /// entries should typically appear before less likely entries. - /// - public static readonly Dictionary TagHierarchy = new Dictionary(StringComparer.OrdinalIgnoreCase) - { - { "whale", null }, - { "orca", "whale" }, - { "srkw", "orca" }, - { "J pod", "srkw" }, - { "K pod", "srkw" }, - { "L pod", "srkw" }, - { "transient", "orca" }, - { "humpback", "whale" }, - { "vessel", null }, - { "train", "vessel" }, - { "bird", null }, - { "pigu", "bird" }, - { "keir", "bird" }, - { "human", null }, - { "jingle", null }, - { "water", null }, - { "hum", null }, - }; - - /// - /// List of suggested tags for the detection based on the machine prediction, the tag hierarchy, - /// and the most recently moderated detection. Tags that are already in the TagList are not - /// included in the suggestions. - /// - public List SuggestedTagList - { - get - { - List suggestions = new List(); - - // For each tag in the tags list, add any child tags not already in the tags list. - var tagList = TagList; - foreach (var tag in tagList) - { - foreach (var pair in TagHierarchy) - { - if (tag.Equals(pair.Value, StringComparison.OrdinalIgnoreCase) && !tagList.Contains(pair.Key, StringComparer.OrdinalIgnoreCase)) - { - suggestions.Add(pair.Key); - } - } - } - - // Add any top-level tags not already in the tags list. - foreach (var pair in TagHierarchy) - { - if (pair.Value == null && !tagList.Contains(pair.Key, StringComparer.OrdinalIgnoreCase)) - { - suggestions.Add(pair.Key); - } - } - - return suggestions; - } - } - - /// - /// Machine-generated label for the detection based on the global prediction model. - /// - public string GlobalPredictionLabel { get; set; } - - /// - /// AI Model that reported this detection. - /// - public string AIModel => string.IsNullOrEmpty(GlobalPredictionLabel) ? "OrcaHello" : "PODS-AI"; - } + /// + /// A hydrophone sampling that might contain whale sounds. + /// + public class Detection + { + /// + /// The detection's generated unique Id. + /// + /// 00000000-0000-0000-0000-000000000000 + public string Id { get; set; } + + /// + /// URI of the detection's audio file (.wav) in blob storage. + /// + /// https://storagesite.blob.core.windows.net/audiowavs/audiofilename.wav + public string AudioUri { get; set; } + + /// + /// URI of the detection's image file (.png) in blob storage. + /// + /// https://storagesite.blob.core.windows.net/spectrogramspng/imagefilename.png + public string SpectrogramUri { get; set; } + + /// + /// Location of the microphone that collected the detection. + /// + public Location Location { get; set; } + + /// + /// Date and time of when the detection occurred. + /// + /// 2020-09-30T11:03:56.057346Z + public DateTime Timestamp { get; set; } + + /// + /// List of sections within the detection that might contain whale sounds. + /// + public List Annotations { get; set; } = new List(); + + /// + /// Flag indicating whether or not the dection has been reviewed by a human moderator. + /// + /// true + public bool Reviewed { get; set; } + + /// + /// Flag indicating whether the human moderator heard whale sounds in the detection. + /// + /// yes + public string Found { get; set; } + + /// + /// Any text comments entered by the human moderator during review. + /// + /// Clear whale sounds detected. + public string Comments { get; set; } + + /// + /// Calculated average confidence that the detection contains a whale sound. + /// + /// 84.39 + public decimal Confidence { get; set; } + + /// + /// Identity of the human moderator (User Principal Name for AzureAD) performing the review. + /// + /// user@gmail.com + public string Moderator { get; set; } + + /// + /// Date and time of when the detection was reviewed by the human moderator. + /// + /// 2020-09-30T11:03:56Z + public DateTime Moderated { get; set; } + + /// + /// Any text comments entered by the human moderator during review (separated by semi-colon). + /// + /// S7;S10 + public string Tags { get; set; } + + /// + /// Split tags into a list. + /// + /// Tags string to split + /// List of tags + public static List GetTagList(string tags) + { + if (string.IsNullOrWhiteSpace(tags)) + return new List(); + + string[] delimiters = new string[] { ";", "," }; + var rawTags = tags.Split(delimiters, StringSplitOptions.RemoveEmptyEntries); + var tagList = new List(rawTags.Length); + foreach (var rawTag in rawTags) + { + var trimmed = rawTag.Trim(); + if (trimmed.Length > 0) + tagList.Add(trimmed); + } + return tagList; + } + + /// + /// Get the leaf tags from the given tags string. + /// A leaf tag is a tag that does not have any child tags in the input list. + /// + /// + /// + public static List GetLeafTags(string tags) + { + List tagList = GetTagList(tags); + List leafTags = new List(); + foreach (var tag in tagList) + { + bool isLeaf = true; + foreach (var pair in TagHierarchy) + { + if (tag.Equals(pair.Value, StringComparison.OrdinalIgnoreCase) && tagList.Contains(pair.Key, StringComparer.OrdinalIgnoreCase)) + { + isLeaf = false; + break; + } + } + if (isLeaf) + { + leafTags.Add(tag); + } + } + return leafTags; + } + + /// + /// Tags in a list (parsed from the Tags string). + /// + public List TagList => GetTagList(Tags); + + /// + /// Hierarchy of tags, where the key is the child tag and the value is the parent tag. + /// A null value indicates a top-level tag. Within tags at the same level, more likely + /// entries should typically appear before less likely entries. + /// + public static readonly Dictionary TagHierarchy = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "whale", null }, + { "orca", "whale" }, + { "srkw", "orca" }, + { "J pod", "srkw" }, + { "K pod", "srkw" }, + { "L pod", "srkw" }, + { "transient", "orca" }, + { "humpback", "whale" }, + { "vessel", null }, + { "train", "vessel" }, + { "bird", null }, + { "pigu", "bird" }, + { "keir", "bird" }, + { "human", null }, + { "jingle", null }, + { "water", null }, + { "hum", null }, + }; + + /// + /// List of suggested tags for the detection based on the machine prediction, the tag hierarchy, + /// and the most recently moderated detection. Tags that are already in the TagList are not + /// included in the suggestions. + /// + public List SuggestedTagList + { + get + { + List suggestions = new List(); + + // For each tag in the tags list, add any child tags not already in the tags list. + var tagList = TagList; + foreach (var tag in tagList) + { + foreach (var pair in TagHierarchy) + { + if (tag.Equals(pair.Value, StringComparison.OrdinalIgnoreCase) && !tagList.Contains(pair.Key, StringComparer.OrdinalIgnoreCase)) + { + suggestions.Add(pair.Key); + } + } + } + + // Add any top-level tags not already in the tags list. + foreach (var pair in TagHierarchy) + { + if (pair.Value == null && !tagList.Contains(pair.Key, StringComparer.OrdinalIgnoreCase)) + { + suggestions.Add(pair.Key); + } + } + + return suggestions; + } + } + + /// + /// Machine-generated label for the detection based on the global prediction model. + /// + public string GlobalPredictionLabel { get; set; } + + /// + /// AI Model that reported this detection. + /// + public string AIModel => string.IsNullOrEmpty(GlobalPredictionLabel) ? "OrcaHello" : "PODS-AI"; + } } diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/API/Detections/DetectionQueryParameters.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/API/Detections/DetectionQueryParameters.cs index 583bab22..0e272868 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/API/Detections/DetectionQueryParameters.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/API/Detections/DetectionQueryParameters.cs @@ -2,73 +2,73 @@ namespace AIForOrcas.DTO.API { - /// - /// Query parameters to present to the detections endpoint. - /// - public class DetectionQueryParameters - { - /// - /// Page number to retrieve. - /// - /// 1 - public int Page { get; set; } = 1; + /// + /// Query parameters to present to the detections endpoint. + /// + public class DetectionQueryParameters + { + /// + /// Page number to retrieve. + /// + /// 1 + public int Page { get; set; } = 1; - /// - /// Property to sort by (confidence, timestamp). - /// - /// timestamp - public string SortBy { get; set; } = "timestamp"; + /// + /// Property to sort by (confidence, timestamp). + /// + /// timestamp + public string SortBy { get; set; } = "timestamp"; - /// - /// Order in which to sort the results (asc, desc). - /// - /// desc - public string SortOrder { get; set; } = "desc"; + /// + /// Order in which to sort the results (asc, desc). + /// + /// desc + public string SortOrder { get; set; } = "desc"; - /// - /// Timeframe for the record set (last 30m, 3h, 6h, 24h, 1w, 1m, range, all). - /// - /// all - public string Timeframe { get; set; } = "all"; + /// + /// Timeframe for the record set (last 30m, 3h, 6h, 24h, 1w, 1m, range, all). + /// + /// all + public string Timeframe { get; set; } = "all"; - /// - /// Date range filter for from Date (mm/dd/yyyy) - /// - /// 12/01/2021 - public DateTime? DateFrom { get; set; } + /// + /// Date range filter for from Date (mm/dd/yyyy) + /// + /// 12/01/2021 + public DateTime? DateFrom { get; set; } - /// - /// Date range filter for To Date (mm/dd/yyyy) - /// - /// 01/15/2022 - public DateTime? DateTo { get; set; } + /// + /// Date range filter for To Date (mm/dd/yyyy) + /// + /// 01/15/2022 + public DateTime? DateTo { get; set; } - /// - /// Location of the hydrophone (all, Orcasound Lab, Port Townsend, etc.). - /// - /// all - public string Location { get; set; } = "all"; + /// + /// Location of the hydrophone (all, Orcasound Lab, Port Townsend, etc.). + /// + /// all + public string Location { get; set; } = "all"; - /// - /// Hydrophone ID (rpi_orcasound_lab, etc., or all). - /// - /// all - public string HydrophoneId { get; set; } = "all"; + /// + /// Hydrophone ID (rpi_orcasound_lab, etc., or all). + /// + /// all + public string HydrophoneId { get; set; } = "all"; - /// - /// Number of records per page to retrieve. - /// - /// 5 - public int RecordsPerPage - { - get => _recordsPerPage; - set - { - _recordsPerPage = (value > _maxRecordsPerPage) ? _maxRecordsPerPage : value; - } - } + /// + /// Number of records per page to retrieve. + /// + /// 5 + public int RecordsPerPage + { + get => _recordsPerPage; + set + { + _recordsPerPage = (value > _maxRecordsPerPage) ? _maxRecordsPerPage : value; + } + } - private int _recordsPerPage = 10; - private readonly int _maxRecordsPerPage = 50; - } + private int _recordsPerPage = 10; + private readonly int _maxRecordsPerPage = 50; + } } diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/API/Detections/DetectionUpdate.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/API/Detections/DetectionUpdate.cs index 45a34f9e..3865243c 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/API/Detections/DetectionUpdate.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/API/Detections/DetectionUpdate.cs @@ -2,51 +2,51 @@ namespace AIForOrcas.DTO.API { - /// - /// Detection data to be updated. - /// - public class DetectionUpdate - { - /// - /// The detection's unique ID. - /// - /// AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA - public string Id { get; set; } + /// + /// Detection data to be updated. + /// + public class DetectionUpdate + { + /// + /// The detection's unique ID. + /// + /// AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA + public string Id { get; set; } - /// - /// Comments provided by the human moderator. - /// - /// Didn't hear anything of interest. - public string Comments { get; set; } + /// + /// Comments provided by the human moderator. + /// + /// Didn't hear anything of interest. + public string Comments { get; set; } - /// - /// Tags provided by the human moderator (separated by semi-colon) - /// - /// call;snr-medium - public string Tags { get; set; } + /// + /// Tags provided by the human moderator (separated by semi-colon) + /// + /// call;snr-medium + public string Tags { get; set; } - /// - /// Identity of the human moderator (User Principal Name for AzureAD) reviewing the detection. - /// - /// live.com#user@gmail.com - public string Moderator { get; set; } + /// + /// Identity of the human moderator (User Principal Name for AzureAD) reviewing the detection. + /// + /// live.com#user@gmail.com + public string Moderator { get; set; } - /// - /// Date and time when the detection was moderated. - /// - /// 2020-11-21T16:52:45Z - public DateTime Moderated { get; set; } + /// + /// Date and time when the detection was moderated. + /// + /// 2020-11-21T16:52:45Z + public DateTime Moderated { get; set; } - /// - /// Flag indicating whether or not the detection has been reviewed. - /// - /// true - public bool Reviewed { get; set; } + /// + /// Flag indicating whether or not the detection has been reviewed. + /// + /// true + public bool Reviewed { get; set; } - /// - /// Indicates whether whale sounds were heard in the detection (yes, no, don't know). - /// - /// no - public string Found { get; set; } - } + /// + /// Indicates whether whale sounds were heard in the detection (yes, no, don't know). + /// + /// no + public string Found { get; set; } + } } diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/API/Detections/Location.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/API/Detections/Location.cs index 6dc5c50d..4e2df745 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/API/Detections/Location.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/API/Detections/Location.cs @@ -1,26 +1,26 @@ namespace AIForOrcas.DTO.API { - /// - /// Geographical location of the hydrophone that collected the detection. - /// - public class Location - { - /// - /// Name of the hydrophone location. - /// - /// Orcasound Lab - public string Name { get; set; } + /// + /// Geographical location of the hydrophone that collected the detection. + /// + public class Location + { + /// + /// Name of the hydrophone location. + /// + /// Orcasound Lab + public string Name { get; set; } - /// - /// Longitude of the hydrophone's location. - /// - /// -123.2166658 - public double Longitude { get; set; } + /// + /// Longitude of the hydrophone's location. + /// + /// -123.2166658 + public double Longitude { get; set; } - /// - /// Latitude of the hydrophone's location. - /// - /// 48.5499978 - public double Latitude { get; set; } - } + /// + /// Latitude of the hydrophone's location. + /// + /// 48.5499978 + public double Latitude { get; set; } + } } diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/API/Metrics/Metrics.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/API/Metrics/Metrics.cs index 53fdd9c1..58091052 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/API/Metrics/Metrics.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/API/Metrics/Metrics.cs @@ -2,77 +2,77 @@ namespace AIForOrcas.DTO.API { - /// - /// Activity metrics for the entire system. - /// - public class Metrics - { - /// - /// Activity timeframe (30m, 24h, etc.). - /// - /// 30d - public string Timeframe { get; set; } + /// + /// Activity metrics for the entire system. + /// + public class Metrics + { + /// + /// Activity timeframe (30m, 24h, etc.). + /// + /// 30d + public string Timeframe { get; set; } - /// - /// Number of reviewed detections in timeframe. - /// - public int Reviewed { get; set; } + /// + /// Number of reviewed detections in timeframe. + /// + public int Reviewed { get; set; } - /// - /// Number of detections not reviewed in timeframe. - /// - /// 15 - public int Unreviewed { get; set; } + /// + /// Number of detections not reviewed in timeframe. + /// + /// 15 + public int Unreviewed { get; set; } - /// - /// Number of detections in timeframe confirmed by human moderator to have whale sound. - /// - /// 100 - public int ConfirmedDetection { get; set; } + /// + /// Number of detections in timeframe confirmed by human moderator to have whale sound. + /// + /// 100 + public int ConfirmedDetection { get; set; } - /// - /// Number of detections in timeframe confirmed by human moderator to not have whale sound. - /// - /// 5 - public int FalseDetection { get; set; } + /// + /// Number of detections in timeframe confirmed by human moderator to not have whale sound. + /// + /// 5 + public int FalseDetection { get; set; } - /// - /// Number of detections in timeframe where human moderator could not determine if there was whale sound. - /// - /// 1 - public int UnknownDetection { get; set; } + /// + /// Number of detections in timeframe where human moderator could not determine if there was whale sound. + /// + /// 1 + public int UnknownDetection { get; set; } - /// - /// List of all comments in timeframe concerning confirmed detections. - /// - public List ConfirmedComments { get; set; } = new List(); + /// + /// List of all comments in timeframe concerning confirmed detections. + /// + public List ConfirmedComments { get; set; } = new List(); - /// - /// List of all comments in timeframe concerning unconfirmed or unknown detections. - /// - public List UnconfirmedComments { get; set; } = new List(); + /// + /// List of all comments in timeframe concerning unconfirmed or unknown detections. + /// + public List UnconfirmedComments { get; set; } = new List(); - /// - /// List of all tags in timeframe. - /// - public List Tags { get; set; } = new List(); + /// + /// List of all tags in timeframe. + /// + public List Tags { get; set; } = new List(); - /// - /// Formatted detections reviewed/unreviewed for passing to JSInterop. - /// - /// [5, 20] - public string DetectionsArray => $"[{Reviewed}, {Unreviewed}]"; + /// + /// Formatted detections reviewed/unreviewed for passing to JSInterop. + /// + /// [5, 20] + public string DetectionsArray => $"[{Reviewed}, {Unreviewed}]"; - /// - /// Formatted detection results for passing to JSInterop. - /// - /// [6, 3, 30] - public string DetectionResultsArray => $"[{ConfirmedDetection}, {FalseDetection}, {UnknownDetection}]"; + /// + /// Formatted detection results for passing to JSInterop. + /// + /// [6, 3, 30] + public string DetectionResultsArray => $"[{ConfirmedDetection}, {FalseDetection}, {UnknownDetection}]"; - /// - /// Flag to be set if no metrics retrieved. - /// - /// true - public bool HasContent { get; set; } = false; - } + /// + /// Flag to be set if no metrics retrieved. + /// + /// true + public bool HasContent { get; set; } = false; + } } diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/API/Metrics/MetricsComment.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/API/Metrics/MetricsComment.cs index 338e5965..4f09858b 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/API/Metrics/MetricsComment.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/API/Metrics/MetricsComment.cs @@ -2,33 +2,33 @@ namespace AIForOrcas.DTO.API { - /// - /// Information about entered comment. - /// - public class MetricsComment - { - /// - /// The text of the comment. - /// - /// This is the thing the Moderator said about the detection. - public string Comment { get; set; } + /// + /// Information about entered comment. + /// + public class MetricsComment + { + /// + /// The text of the comment. + /// + /// This is the thing the Moderator said about the detection. + public string Comment { get; set; } - /// - /// The detection's unique Id. - /// - /// 00000000-0000-0000-0000-000000000000 - public string Id { get; set; } + /// + /// The detection's unique Id. + /// + /// 00000000-0000-0000-0000-000000000000 + public string Id { get; set; } - /// - /// Date and time when the comment was submitted. - /// - /// 2020-11-19T13:42:32.473918Z - public DateTime Timestamp { get; set; } + /// + /// Date and time when the comment was submitted. + /// + /// 2020-11-19T13:42:32.473918Z + public DateTime Timestamp { get; set; } - /// - /// Identity of the human moderator (User Principal Name for AzureAD) submitting the comment. - /// - /// live.com#user@gmail.com - public string Moderator { get; set; } - } + /// + /// Identity of the human moderator (User Principal Name for AzureAD) submitting the comment. + /// + /// live.com#user@gmail.com + public string Moderator { get; set; } + } } diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/API/Metrics/MetricsTag.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/API/Metrics/MetricsTag.cs index a2b3d141..ecba6106 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/API/Metrics/MetricsTag.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/API/Metrics/MetricsTag.cs @@ -2,21 +2,21 @@ namespace AIForOrcas.DTO.API { - /// - /// Information about entered tag. - /// - public class MetricsTag - { - /// - /// Tag name. - /// - /// CLANG - public string Tag { get; set; } + /// + /// Information about entered tag. + /// + public class MetricsTag + { + /// + /// Tag name. + /// + /// CLANG + public string Tag { get; set; } - /// - /// List of detection unique Ids associated with this tag. - /// - /// ["00000000-0000-0000-0000-000000000000","00000000-0000-0000-0000-000000000000"] - public List Ids { get; set; } = new List(); - } + /// + /// List of detection unique Ids associated with this tag. + /// + /// ["00000000-0000-0000-0000-000000000000","00000000-0000-0000-0000-000000000000"] + public List Ids { get; set; } = new List(); + } } diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/API/Metrics/ModeratorMetrics.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/API/Metrics/ModeratorMetrics.cs index 798455f0..6d574499 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/API/Metrics/ModeratorMetrics.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/API/Metrics/ModeratorMetrics.cs @@ -1,13 +1,13 @@ namespace AIForOrcas.DTO.API { - /// - /// Activity metrics for the specified human moderator. - /// - public class ModeratorMetrics : Metrics - { - /// - /// Identity of the human moderator (User Principal Name for AzureAD) performing the review. - /// - public string Moderator { get; set; } - } + /// + /// Activity metrics for the specified human moderator. + /// + public class ModeratorMetrics : Metrics + { + /// + /// Identity of the human moderator (User Principal Name for AzureAD) performing the review. + /// + public string Moderator { get; set; } + } } diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/API/Tags/TagUpdate.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/API/Tags/TagUpdate.cs index dab2fccb..826a0b63 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/API/Tags/TagUpdate.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/API/Tags/TagUpdate.cs @@ -17,7 +17,7 @@ public class TagUpdate /// What the Tag is being change to ///
/// NewTag - [Required(ErrorMessage="Please enter the new tag.")] + [Required(ErrorMessage = "Please enter the new tag.")] public string NewTag { get; set; } } } diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/Pagination/CandidateFilterOptionsDTO.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/Pagination/CandidateFilterOptionsDTO.cs index d892e650..275aea2c 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/Pagination/CandidateFilterOptionsDTO.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/Pagination/CandidateFilterOptionsDTO.cs @@ -2,15 +2,15 @@ namespace AIForOrcas.DTO { - public class CandidateFilterOptionsDTO : IFilterOptions - { - public string SortOrder { get; set; } - public string SortBy { get; set; } - public string Timeframe { get; set; } - public string Location { get; set; } - public string HydrophoneId { get; set; } - public DateTime? DateFrom { get; set; } - public DateTime? DateTo { get; set; } - public string QueryString { get => $"sortBy={SortBy}&sortOrder={SortOrder}&timeframe={Timeframe}&location={Location}&hydrophoneId={HydrophoneId}&dateFrom={DateFrom}&dateTo={DateTo}"; } - } + public class CandidateFilterOptionsDTO : IFilterOptions + { + public string SortOrder { get; set; } + public string SortBy { get; set; } + public string Timeframe { get; set; } + public string Location { get; set; } + public string HydrophoneId { get; set; } + public DateTime? DateFrom { get; set; } + public DateTime? DateTo { get; set; } + public string QueryString { get => $"sortBy={SortBy}&sortOrder={SortOrder}&timeframe={Timeframe}&location={Location}&hydrophoneId={HydrophoneId}&dateFrom={DateFrom}&dateTo={DateTo}"; } + } } diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/Pagination/IFilterOptions.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/Pagination/IFilterOptions.cs index ab27d6f4..70a19d15 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/Pagination/IFilterOptions.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/Pagination/IFilterOptions.cs @@ -1,7 +1,7 @@ namespace AIForOrcas.DTO { - public interface IFilterOptions - { - string QueryString { get; } - } + public interface IFilterOptions + { + string QueryString { get; } + } } diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/Pagination/MetricsFilterDTO.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/Pagination/MetricsFilterDTO.cs index 61f08075..dd9e6120 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/Pagination/MetricsFilterDTO.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/Pagination/MetricsFilterDTO.cs @@ -3,20 +3,20 @@ namespace AIForOrcas.DTO { - /// - /// Query parameters for metrics endpoint. - /// - [DataContract] - public class MetricsFilterDTO : IFilterOptions - { - /// - /// Timeframe for the record set (last 30m, 3h, 6h, 24h, 1w, 1m, all). - /// - /// all - [DataMember] - public string Timeframe { get; set; } + /// + /// Query parameters for metrics endpoint. + /// + [DataContract] + public class MetricsFilterDTO : IFilterOptions + { + /// + /// Timeframe for the record set (last 30m, 3h, 6h, 24h, 1w, 1m, all). + /// + /// all + [DataMember] + public string Timeframe { get; set; } - [JsonIgnore] - public virtual string QueryString => $"timeframe={Timeframe}"; - } + [JsonIgnore] + public virtual string QueryString => $"timeframe={Timeframe}"; + } } diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/Pagination/ModeratorMetricsFilterDTO.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/Pagination/ModeratorMetricsFilterDTO.cs index 0ba5fafc..d1cb687f 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/Pagination/ModeratorMetricsFilterDTO.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/Pagination/ModeratorMetricsFilterDTO.cs @@ -1,18 +1,18 @@ namespace AIForOrcas.DTO.API { - /// - /// Query parameters for user metrics endpoint. - /// - public class ModeratorMetricsFilterDTO : MetricsFilterDTO - { - /// - /// Identity of the human moderator (User Principal Name for AzureAD) reviewing metrics. - /// - public string Moderator { get; set; } + /// + /// Query parameters for user metrics endpoint. + /// + public class ModeratorMetricsFilterDTO : MetricsFilterDTO + { + /// + /// Identity of the human moderator (User Principal Name for AzureAD) reviewing metrics. + /// + public string Moderator { get; set; } - /// - /// Constructed queryString. - /// - public override string QueryString => $"moderator={Moderator}&{base.QueryString}"; - } + /// + /// Constructed queryString. + /// + public override string QueryString => $"moderator={Moderator}&{base.QueryString}"; + } } diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/Pagination/PageLinkDTO.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/Pagination/PageLinkDTO.cs index dfad1bdd..c587eb51 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/Pagination/PageLinkDTO.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/Pagination/PageLinkDTO.cs @@ -1,24 +1,24 @@ namespace AIForOrcas.DTO { - public class PageLinkDTO - { - public PageLinkDTO(int page) - : this(page, true) { } + public class PageLinkDTO + { + public PageLinkDTO(int page) + : this(page, true) { } - public PageLinkDTO(int page, bool enabled) - : this(page, enabled, page.ToString()) - { } + public PageLinkDTO(int page, bool enabled) + : this(page, enabled, page.ToString()) + { } - public PageLinkDTO(int page, bool enabled, string text) - { - Page = page; - Enabled = enabled; - Text = text; - } + public PageLinkDTO(int page, bool enabled, string text) + { + Page = page; + Enabled = enabled; + Text = text; + } - public string Text { get; set; } - public int Page { get; set; } - public bool Enabled { get; set; } = true; - public bool Active { get; set; } = false; - } + public string Text { get; set; } + public int Page { get; set; } + public bool Enabled { get; set; } = true; + public bool Active { get; set; } = false; + } } diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/Pagination/PaginatedResponseDTO.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/Pagination/PaginatedResponseDTO.cs index d9f4031a..7a153a6d 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/Pagination/PaginatedResponseDTO.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/Pagination/PaginatedResponseDTO.cs @@ -1,9 +1,9 @@ namespace AIForOrcas.DTO { - public class PaginatedResponseDTO - { - public T Response { get; set; } - public int TotalAmountPages { get; set; } - public int TotalNumberRecords { get; set; } - } + public class PaginatedResponseDTO + { + public T Response { get; set; } + public int TotalAmountPages { get; set; } + public int TotalNumberRecords { get; set; } + } } diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/Pagination/PaginationOptionsDTO.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/Pagination/PaginationOptionsDTO.cs index f78140ff..e5273933 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/Pagination/PaginationOptionsDTO.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/Pagination/PaginationOptionsDTO.cs @@ -1,13 +1,13 @@ namespace AIForOrcas.DTO { - public class PaginationOptionsDTO - { - public int Page { get; set; } = 1; + public class PaginationOptionsDTO + { + public int Page { get; set; } = 1; - public int RecordsPerPage { get; set; } = 10; + public int RecordsPerPage { get; set; } = 10; - public int Radius { get; set; } = 3; + public int Radius { get; set; } = 3; - public string QueryString { get => $"page={Page}&recordsPerPage={RecordsPerPage}"; } - } + public string QueryString { get => $"page={Page}&recordsPerPage={RecordsPerPage}"; } + } } diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/Pagination/PaginationResultsDTO.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/Pagination/PaginationResultsDTO.cs index 17e41015..39cbb0b0 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/Pagination/PaginationResultsDTO.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/Pagination/PaginationResultsDTO.cs @@ -1,9 +1,9 @@ namespace AIForOrcas.DTO { - public class PaginationResultsDTO - { - public int CurrentPage { get; set; } = 1; - public int TotalNumberOfPages { get; set; } = 0; - public int TotalNumberOfRecords { get; set; } = 0; - } + public class PaginationResultsDTO + { + public int CurrentPage { get; set; } = 1; + public int TotalNumberOfPages { get; set; } = 0; + public int TotalNumberOfRecords { get; set; } = 0; + } } diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/Pagination/ReviewedFilterOptionsDTO.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/Pagination/ReviewedFilterOptionsDTO.cs index 9e4f8f27..e5bf173e 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/Pagination/ReviewedFilterOptionsDTO.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.DTO/Pagination/ReviewedFilterOptionsDTO.cs @@ -2,14 +2,14 @@ namespace AIForOrcas.DTO { - public class ReviewedFilterOptionsDTO : IFilterOptions - { - public string SortOrder { get; set; } - public string SortBy { get; set; } - public string Timeframe { get; set; } - public string Location { get; set; } - public DateTime? DateFrom { get; set; } - public DateTime? DateTo { get; set; } - public string QueryString { get => $"sortBy={SortBy}&sortOrder={SortOrder}&timeframe={Timeframe}&location={Location}&DateFrom={DateFrom}&DateTo={DateTo}"; } - } + public class ReviewedFilterOptionsDTO : IFilterOptions + { + public string SortOrder { get; set; } + public string SortBy { get; set; } + public string Timeframe { get; set; } + public string Location { get; set; } + public DateTime? DateFrom { get; set; } + public DateTime? DateTo { get; set; } + public string QueryString { get => $"sortBy={SortBy}&sortOrder={SortOrder}&timeframe={Timeframe}&location={Location}&DateFrom={DateFrom}&DateTo={DateTo}"; } + } } diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Server.BL/Context/ApplicationDbContext.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Server.BL/Context/ApplicationDbContext.cs index d7b19d78..34005fe9 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Server.BL/Context/ApplicationDbContext.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Server.BL/Context/ApplicationDbContext.cs @@ -3,22 +3,22 @@ namespace AIForOrcas.Server.BL.Context { - public class ApplicationDbContext : DbContext - { - public DbSet Metadata { get; set; } + public class ApplicationDbContext : DbContext + { + public DbSet Metadata { get; set; } - public ApplicationDbContext(DbContextOptions options) - : base(options) - { } + public ApplicationDbContext(DbContextOptions options) + : base(options) + { } - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - modelBuilder.Entity().ToContainer("metadata"); - modelBuilder.Entity().HasPartitionKey(o => o.source_guid); - modelBuilder.Entity().OwnsOne(p => p.location); - modelBuilder.Entity().OwnsMany(p => p.predictions); - modelBuilder.Entity().HasNoDiscriminator(); - base.OnModelCreating(modelBuilder); - } - } + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity().ToContainer("metadata"); + modelBuilder.Entity().HasPartitionKey(o => o.source_guid); + modelBuilder.Entity().OwnsOne(p => p.location); + modelBuilder.Entity().OwnsMany(p => p.predictions); + modelBuilder.Entity().HasNoDiscriminator(); + base.OnModelCreating(modelBuilder); + } + } } diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Server.BL/Models/CosmosDB/Location.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Server.BL/Models/CosmosDB/Location.cs index 362c4f0f..10378e97 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Server.BL/Models/CosmosDB/Location.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Server.BL/Models/CosmosDB/Location.cs @@ -1,10 +1,10 @@ namespace AIForOrcas.Server.BL.Models.CosmosDB { - public class Location - { - public string id { get; set; } - public string name { get; set; } - public double longitude { get; set; } - public double latitude { get; set; } - } + public class Location + { + public string id { get; set; } + public string name { get; set; } + public double longitude { get; set; } + public double latitude { get; set; } + } } diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Server.BL/Models/CosmosDB/Metadata.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Server.BL/Models/CosmosDB/Metadata.cs index 4316d012..2406a5ac 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Server.BL/Models/CosmosDB/Metadata.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Server.BL/Models/CosmosDB/Metadata.cs @@ -3,22 +3,22 @@ namespace AIForOrcas.Server.BL.Models.CosmosDB { - public class Metadata - { - public string id { get; set; } - public string source_guid { get; set; } - public string audioUri { get; set; } - public string imageUri { get; set; } - public bool reviewed { get; set; } - public DateTime timestamp { get; set; } - public decimal whaleFoundConfidence { get; set; } - public Location location { get; set; } - public List predictions { get; set; } = new List(); - public string SRKWFound { get; set; } - public string comments { get; set; } - public string dateModerated { get; set; } - public string moderator { get; set; } - public string tags { get; set; } - public string globalPredictionLabel { get; set; } - } + public class Metadata + { + public string id { get; set; } + public string source_guid { get; set; } + public string audioUri { get; set; } + public string imageUri { get; set; } + public bool reviewed { get; set; } + public DateTime timestamp { get; set; } + public decimal whaleFoundConfidence { get; set; } + public Location location { get; set; } + public List predictions { get; set; } = new List(); + public string SRKWFound { get; set; } + public string comments { get; set; } + public string dateModerated { get; set; } + public string moderator { get; set; } + public string tags { get; set; } + public string globalPredictionLabel { get; set; } + } } diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Server.BL/Models/CosmosDB/Prediction.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Server.BL/Models/CosmosDB/Prediction.cs index 1388dccc..6985f583 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Server.BL/Models/CosmosDB/Prediction.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Server.BL/Models/CosmosDB/Prediction.cs @@ -1,11 +1,11 @@ namespace AIForOrcas.Server.BL.Models.CosmosDB { - public class Prediction - { - public int id { get; set; } - public decimal startTime { get; set; } - public decimal duration { get; set; } - public decimal confidence { get; set; } - public string label { get; set; } = string.Empty; - } + public class Prediction + { + public int id { get; set; } + public decimal startTime { get; set; } + public decimal duration { get; set; } + public decimal confidence { get; set; } + public string label { get; set; } = string.Empty; + } } diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Server.BL/Services/MetadataRepository.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Server.BL/Services/MetadataRepository.cs index b80046fb..bb60cef4 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Server.BL/Services/MetadataRepository.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Server.BL/Services/MetadataRepository.cs @@ -9,51 +9,51 @@ namespace AIForOrcas.Server.BL.Services { - public class MetadataRepository - { - private readonly ApplicationDbContext _db; + public class MetadataRepository + { + private readonly ApplicationDbContext _db; - public MetadataRepository(ApplicationDbContext db) - { - _db = db; - } + public MetadataRepository(ApplicationDbContext db) + { + _db = db; + } + + public IQueryable GetAll() + { + return _db.Metadata.AsQueryable(); + } + + public async Task GetByIdAsync(string id) + { + return await _db.Metadata.FirstOrDefaultAsync(x => x.id == id); + } + + public async Task CommitAsync() + { + try + { + await _db.SaveChangesAsync(); + } + catch (Exception ex) + { + throw new DataException(ex.Message); + } + } - public IQueryable GetAll() + public IQueryable GetAllTags() { - return _db.Metadata.AsQueryable(); + return _db.Metadata + .Where(x => x.tags != null && x.tags != "") + .Select(x => x.tags) + .Distinct(); } - public async Task GetByIdAsync(string id) - { - return await _db.Metadata.FirstOrDefaultAsync(x => x.id == id); - } - - public async Task CommitAsync() - { - try - { - await _db.SaveChangesAsync(); - } - catch (Exception ex) - { - throw new DataException(ex.Message); - } - } - - public IQueryable GetAllTags() - { - return _db.Metadata - .Where(x => x.tags != null && x.tags != "") - .Select(x => x.tags) - .Distinct(); - } - - public IQueryable GetAllWithTag(string tag) + public IQueryable GetAllWithTag(string tag) { - return _db.Metadata.AsEnumerable() - .Where(x => x.tags != null && x.tags.Contains(tag)) - .AsQueryable(); + return _db.Metadata.AsEnumerable() + .Where(x => x.tags != null && x.tags.Contains(tag)) + .AsQueryable(); } - } + } } diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Server/Controllers/DetectionsController.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Server/Controllers/DetectionsController.cs index c1d9d835..5bd88299 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Server/Controllers/DetectionsController.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Server/Controllers/DetectionsController.cs @@ -8,521 +8,521 @@ [ApiController] public class DetectionsController : ControllerBase { - private readonly MetadataRepository _repository; + private readonly MetadataRepository _repository; - public DetectionsController(MetadataRepository repository) - { - _repository = repository; - } + public DetectionsController(MetadataRepository repository) + { + _repository = repository; + } - #region Helpers + #region Helpers - private void SetHeaderCounts(double totalRecords, int recordsPerPage) - { - double totalAmountPages = Math.Ceiling(totalRecords / recordsPerPage); + private void SetHeaderCounts(double totalRecords, int recordsPerPage) + { + double totalAmountPages = Math.Ceiling(totalRecords / recordsPerPage); - HttpContext.Response.Headers.Add("totalNumberRecords", totalRecords.ToString()); - HttpContext.Response.Headers.Add("totalAmountPages", totalAmountPages.ToString()); - } + HttpContext.Response.Headers.Add("totalNumberRecords", totalRecords.ToString()); + HttpContext.Response.Headers.Add("totalAmountPages", totalAmountPages.ToString()); + } - private static void ValidateQueryParameters(DetectionQueryParameters queryParameters) - { - if (string.IsNullOrWhiteSpace(queryParameters.Timeframe)) - throw new ArgumentNullException("Timeframe"); + private static void ValidateQueryParameters(DetectionQueryParameters queryParameters) + { + if (string.IsNullOrWhiteSpace(queryParameters.Timeframe)) + throw new ArgumentNullException("Timeframe"); - if (queryParameters.DateFrom > queryParameters.DateTo) - throw new Exception("From Date should be less than To date"); + if (queryParameters.DateFrom > queryParameters.DateTo) + throw new Exception("From Date should be less than To date"); - if (string.IsNullOrWhiteSpace(queryParameters.SortBy)) - throw new ArgumentNullException("SortBy"); + if (string.IsNullOrWhiteSpace(queryParameters.SortBy)) + throw new ArgumentNullException("SortBy"); - if (string.IsNullOrWhiteSpace(queryParameters.SortOrder)) - throw new ArgumentNullException("SortOrder"); + if (string.IsNullOrWhiteSpace(queryParameters.SortOrder)) + throw new ArgumentNullException("SortOrder"); - if (string.IsNullOrWhiteSpace(queryParameters.Location)) - throw new ArgumentNullException("Location"); + if (string.IsNullOrWhiteSpace(queryParameters.Location)) + throw new ArgumentNullException("Location"); - if (queryParameters.Page == 0) - throw new ArgumentNullException("Page"); + if (queryParameters.Page == 0) + throw new ArgumentNullException("Page"); - if (queryParameters.RecordsPerPage == 0) - throw new ArgumentNullException("RecordsPerPage"); - } + if (queryParameters.RecordsPerPage == 0) + throw new ArgumentNullException("RecordsPerPage"); + } - private static void ApplyOptionalLocationAndHydrophoneFilters(ref IQueryable queryable, DetectionQueryParameters queryParameters) - { - if (queryParameters.Location.ToLower() != "all") - MetadataFilters.ApplyLocationFilter(ref queryable, queryParameters.Location); + private static void ApplyOptionalLocationAndHydrophoneFilters(ref IQueryable queryable, DetectionQueryParameters queryParameters) + { + if (queryParameters.Location.ToLower() != "all") + MetadataFilters.ApplyLocationFilter(ref queryable, queryParameters.Location); - if (queryParameters.HydrophoneId.ToLower() != "all") - MetadataFilters.ApplyHydrophoneIdFilter(ref queryable, queryParameters.HydrophoneId); - } + if (queryParameters.HydrophoneId.ToLower() != "all") + MetadataFilters.ApplyHydrophoneIdFilter(ref queryable, queryParameters.HydrophoneId); + } - private void ApplySortPaginationAndHeaders(ref List results, double recordCount, DetectionQueryParameters queryParameters) - { - if (queryParameters.SortBy.ToLower() == "confidence") - DetectionFilters.ApplyConfidenceSortFilter(ref results, queryParameters.SortOrder); - else if (queryParameters.SortBy.ToLower() == "timestamp") - DetectionFilters.ApplyTimestampSortFilter(ref results, queryParameters.SortOrder); + private void ApplySortPaginationAndHeaders(ref List results, double recordCount, DetectionQueryParameters queryParameters) + { + if (queryParameters.SortBy.ToLower() == "confidence") + DetectionFilters.ApplyConfidenceSortFilter(ref results, queryParameters.SortOrder); + else if (queryParameters.SortBy.ToLower() == "timestamp") + DetectionFilters.ApplyTimestampSortFilter(ref results, queryParameters.SortOrder); - DetectionFilters.ApplyPaginationFilter(ref results, queryParameters.Page, queryParameters.RecordsPerPage); + DetectionFilters.ApplyPaginationFilter(ref results, queryParameters.Page, queryParameters.RecordsPerPage); - SetHeaderCounts(recordCount, - (queryParameters.RecordsPerPage > 0 ? queryParameters.RecordsPerPage : - MetadataFilters.DefaultRecordsPerPage)); - } + SetHeaderCounts(recordCount, + (queryParameters.RecordsPerPage > 0 ? queryParameters.RecordsPerPage : + MetadataFilters.DefaultRecordsPerPage)); + } - #endregion + #endregion - /// - /// List all AI/ML generated detections, regardless of review status. - /// - [HttpGet] + /// + /// List all AI/ML generated detections, regardless of review status. + /// + [HttpGet] [AllowAnonymous] - [SwaggerResponse(StatusCodes.Status200OK, "Returns the list of all Detections.", typeof(IQueryable))] - [SwaggerResponse(StatusCodes.Status204NoContent, "If there are no Detections for the specified timeframe.")] - [SwaggerResponse(StatusCodes.Status400BadRequest, "If the request was malformed (missing parameters).")] - [SwaggerResponse(StatusCodes.Status500InternalServerError, "If there is an internal error reading or processing data from the data source.")] - public ActionResult> Get([FromQuery] DetectionQueryParameters queryParameters) - { - try - { - ValidateQueryParameters(queryParameters); - - // start with all records - var queryable = _repository.GetAll(); - - // apply timeframe filter - MetadataFilters.ApplyTimeframeFilter(ref queryable, queryParameters.Timeframe, queryParameters.DateFrom, queryParameters.DateTo); - - // apply location and hydrophone filters - ApplyOptionalLocationAndHydrophoneFilters(ref queryable, queryParameters); - - // If no detections found - if (queryable == null || queryable.Count() == 0) - { - return NoContent(); - } - - // total number of records - double recordCount = queryable.Count(); - - var results = queryable - .Select(x => DetectionProcessors.ToDetection(x)).ToList(); - - // apply sort, pagination filters and set page count headers - ApplySortPaginationAndHeaders(ref results, recordCount, queryParameters); - - // map to returnable data type and return - return Ok(results); - } - catch (ArgumentNullException ex) - { - var details = new ProblemDetails() - { - Detail = ex.Message - }; - return BadRequest(details); - } - catch (Exception ex) - { - var details = new ProblemDetails() - { - Title = ex.GetType().ToString(), - Detail = ex.Message - }; - - return StatusCode(StatusCodes.Status500InternalServerError, details); - } - } - - /// - /// Fetch a specific detection based on a unique ID. - /// - /// Detection's unique ID - [HttpGet("{id}")] - [AllowAnonymous] - [SwaggerResponse(StatusCodes.Status200OK, "Returns the Detection.", typeof(Detection))] - [SwaggerResponse(StatusCodes.Status400BadRequest, "If the request was malformed (missing parameters).")] - [SwaggerResponse(StatusCodes.Status404NotFound, "If the Detection defined by the unique ID could not be found.")] - [SwaggerResponse(StatusCodes.Status500InternalServerError, "If there is an internal error reading or processing data from the data source.")] - public async ValueTask> GetByIdAsync(string id) - { - try - { - if (string.IsNullOrWhiteSpace(id)) - throw new ArgumentNullException("id"); - - var metadata = await _repository.GetByIdAsync(id); - - if (metadata == null) - return NotFound(); - - return Ok(DetectionProcessors.ToDetection(metadata)); - } - catch (ArgumentNullException ex) - { - var details = new ProblemDetails() - { - Detail = ex.Message - }; - return BadRequest(details); - } - catch (Exception ex) - { - var details = new ProblemDetails() - { - Title = ex.GetType().ToString(), - Detail = ex.Message - }; - - return StatusCode(StatusCodes.Status500InternalServerError, details); - } - } - - /// - /// List all AI/ML generated detections that have not yet been reviewed (confirmed or rejected) by a human moderator. - /// - [HttpGet("unreviewed")] - [AllowAnonymous] - [SwaggerResponse(StatusCodes.Status200OK, "Returns the list of unreviewed Detections.", typeof(IQueryable))] - [SwaggerResponse(StatusCodes.Status204NoContent, "If there are no unreviewed Detections for the specified timeframe.")] - [SwaggerResponse(StatusCodes.Status400BadRequest, "If the request was malformed (missing parameters).")] - [SwaggerResponse(StatusCodes.Status500InternalServerError, "If there is an internal error reading or processing data from the data source.")] - public ActionResult> GetUnreviewed([FromQuery] DetectionQueryParameters queryParameters) - { - try - { - ValidateQueryParameters(queryParameters); - - // start with all records - var queryable = _repository.GetAll(); - - // apply reviewed status - MetadataFilters.ApplyReviewedFilter(ref queryable, false); - - // apply location and hydrophone filters - ApplyOptionalLocationAndHydrophoneFilters(ref queryable, queryParameters); - - // apply timeframe filter - MetadataFilters.ApplyTimeframeFilter(ref queryable, queryParameters.Timeframe, queryParameters.DateFrom, queryParameters.DateTo); - - // If no detections found - if (queryable == null || queryable.Count() == 0) - { - return NoContent(); - } - - // total number of records - double recordCount = queryable.Count(); - - var results = queryable - .Select(x => DetectionProcessors.ToDetection(x)).ToList(); - - // NOTE: Have to apply SortBy timestamp and pagination filter after - // executing the select because of how EF for Cosmos deals with DateTime. - // Had to convert from string (how stored in Cosmos) to DateTime in order to apply the - // select, but that messed up the SortBy since Cosmos is expecting a string. - - // apply sort, pagination filters and set page count headers - ApplySortPaginationAndHeaders(ref results, recordCount, queryParameters); - - // map to returnable data type and return - return Ok(results); - } - catch (ArgumentNullException ex) - { - var details = new ProblemDetails() - { - Detail = ex.Message - }; - return BadRequest(details); - } - catch (Exception ex) - { - var details = new ProblemDetails() - { - Title = ex.GetType().ToString(), - Detail = ex.Message - }; - - return StatusCode(StatusCodes.Status500InternalServerError, details); - } - } - - /// - /// List all AI/ML generated detections that have been reviewed by a human moderator and have confirmed whale sounds. - /// - [HttpGet("confirmed")] - [AllowAnonymous] - [SwaggerResponse(StatusCodes.Status200OK, "Returns the list of confirmed Detections.", typeof(IQueryable))] - [SwaggerResponse(StatusCodes.Status204NoContent, "If there are no confirmed Detections for the specified timeframe.")] - [SwaggerResponse(StatusCodes.Status400BadRequest, "If the request was malformed (missing parameters).")] - [SwaggerResponse(StatusCodes.Status500InternalServerError, "If there is an internal error reading or processing data from the data source.")] - public ActionResult> GetConfirmed([FromQuery] DetectionQueryParameters queryParameters) - { - try - { - ValidateQueryParameters(queryParameters); - - // start with all records - var queryable = _repository.GetAll(); - - // apply desired status - MetadataFilters.ApplyReviewedFilter(ref queryable, true); - - // apply desired found state - MetadataFilters.ApplyFoundFilter(ref queryable, "yes"); - - // apply timeframe filter - MetadataFilters.ApplyTimeframeFilter(ref queryable, queryParameters.Timeframe, queryParameters.DateFrom, queryParameters.DateTo); - - // apply location and hydrophone filters - ApplyOptionalLocationAndHydrophoneFilters(ref queryable, queryParameters); - - // If no detections found - if (queryable == null || queryable.Count() == 0) - { - return NoContent(); - } - - // total number of records - double recordCount = queryable.Count(); - - var results = queryable - .Select(x => DetectionProcessors.ToDetection(x)).ToList(); - - // apply sort, pagination filters and set page count headers - ApplySortPaginationAndHeaders(ref results, recordCount, queryParameters); - - // map to returnable data type and return - return Ok(results); - } - catch (ArgumentNullException ex) - { - var details = new ProblemDetails() - { - Detail = ex.Message - }; - return BadRequest(details); - } - catch (Exception ex) - { - var details = new ProblemDetails() - { - Title = ex.GetType().ToString(), - Detail = ex.Message - }; - - return StatusCode(StatusCodes.Status500InternalServerError, details); - } - } - - /// - /// List all AI/ML generated detections that have been reviewed by a human moderator, but do not have whale sounds. - /// - [HttpGet("falsepositives")] - [AllowAnonymous] - [SwaggerResponse(StatusCodes.Status200OK, "Returns the list of false positive (unconfirmed) Detections.", typeof(IQueryable))] - [SwaggerResponse(StatusCodes.Status204NoContent, "If there are no false positive Detections for the specified timeframe.")] - [SwaggerResponse(StatusCodes.Status400BadRequest, "If the request was malformed (missing parameters).")] - [SwaggerResponse(StatusCodes.Status500InternalServerError, "If there is an internal error reading or processing data from the data source.")] - public ActionResult> GetFalsePositives([FromQuery] DetectionQueryParameters queryParameters) - { - try - { - ValidateQueryParameters(queryParameters); - - // start with all records - var queryable = _repository.GetAll(); - - // apply desired status - MetadataFilters.ApplyReviewedFilter(ref queryable, true); - - // apply desired found state - MetadataFilters.ApplyFoundFilter(ref queryable, "no"); - - // apply timeframe filter - MetadataFilters.ApplyTimeframeFilter(ref queryable, queryParameters.Timeframe, queryParameters.DateFrom, queryParameters.DateTo); - - // apply location and hydrophone filters - ApplyOptionalLocationAndHydrophoneFilters(ref queryable, queryParameters); - - // If no detections found - if (queryable == null || queryable.Count() == 0) - { - return NoContent(); - } - - // total number of records - double recordCount = queryable.Count(); - - var results = queryable - .Select(x => DetectionProcessors.ToDetection(x)).ToList(); - - // apply sort, pagination filters and set page count headers - ApplySortPaginationAndHeaders(ref results, recordCount, queryParameters); - - // map to returnable data type and return - return Ok(results); - } - catch (ArgumentNullException ex) - { - var details = new ProblemDetails() - { - Detail = ex.Message - }; - return BadRequest(details); - } - catch (Exception ex) - { - var details = new ProblemDetails() - { - Title = ex.GetType().ToString(), - Detail = ex.Message - }; - - return StatusCode(StatusCodes.Status500InternalServerError, details); - } - } - - /// - /// List all AI/ML generated detections that have been reviewed by a human moderator, but whale sounds could not be conclusively confirmed or denied. - /// - [HttpGet("unknowns")] - [AllowAnonymous] - [SwaggerResponse(StatusCodes.Status200OK, "Returns the list of unknown Detections.", typeof(IQueryable))] - [SwaggerResponse(StatusCodes.Status204NoContent, "If there are no unknown Detections for the specified timeframe.")] - [SwaggerResponse(StatusCodes.Status400BadRequest, "If the request was malformed (missing parameters).")] - [SwaggerResponse(StatusCodes.Status500InternalServerError, "If there is an internal error reading or processing data from the data source.")] - public ActionResult> GetUnknowns([FromQuery] DetectionQueryParameters queryParameters) - { - try - { - ValidateQueryParameters(queryParameters); - - // start with all records - var queryable = _repository.GetAll(); - - // apply desired status - MetadataFilters.ApplyReviewedFilter(ref queryable, true); - - // apply desired found state - MetadataFilters.ApplyFoundFilter(ref queryable, "don't know"); - - // apply timeframe filter - MetadataFilters.ApplyTimeframeFilter(ref queryable, queryParameters.Timeframe, queryParameters.DateFrom, queryParameters.DateTo); - - // apply location and hydrophone filters - ApplyOptionalLocationAndHydrophoneFilters(ref queryable, queryParameters); - - // If no detections found - if (queryable == null || queryable.Count() == 0) - { - return NoContent(); - } - - // total number of records - double recordCount = queryable.Count(); - - var results = queryable - .Select(x => DetectionProcessors.ToDetection(x)).ToList(); - - // apply sort, pagination filters and set page count headers - ApplySortPaginationAndHeaders(ref results, recordCount, queryParameters); - - // map to returnable data type and return - return Ok(results); - } - catch (ArgumentNullException ex) - { - var details = new ProblemDetails() - { - Detail = ex.Message - }; - return BadRequest(details); - } - catch (Exception ex) - { - var details = new ProblemDetails() - { - Title = ex.GetType().ToString(), - Detail = ex.Message - }; - - return StatusCode(StatusCodes.Status500InternalServerError, details); - } - } - - /// - /// Updates the detection with information provided by a human moderator. - /// - /// Detection's unique Id (AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA). - /// The Detection's values to be updated. - [HttpPut("{id}")] - [Authorize("Moderators")] - [SwaggerResponse(StatusCodes.Status200OK, "Returns the contents of the updated Detection.", typeof(Detection))] - [SwaggerResponse(StatusCodes.Status400BadRequest, "If the request was malformed (missing parameters).")] - [SwaggerResponse(StatusCodes.Status401Unauthorized, "If the user is not logged in.")] - [SwaggerResponse(StatusCodes.Status403Forbidden, "If the user is logged in, but is not an authorized Moderator.")] - [SwaggerResponse(StatusCodes.Status404NotFound, "Indicates the Detection was not found to update.")] - [SwaggerResponse(StatusCodes.Status500InternalServerError, "If there is an internal error reading or processing data from the data source.")] - public async ValueTask> Put(string id, [FromBody] DetectionUpdate detectionUpdate) - { - try - { - if (string.IsNullOrWhiteSpace(id)) - throw new ArgumentNullException("id"); - - if (detectionUpdate == null) - throw new ArgumentNullException("postedDetection"); - - var metadata = await _repository.GetByIdAsync(id); - - if (metadata == null) - return NotFound(); - - metadata.comments = detectionUpdate.Comments; - metadata.moderator = detectionUpdate.Moderator; - metadata.dateModerated = detectionUpdate.Moderated.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"); - metadata.reviewed = detectionUpdate.Reviewed; - metadata.SRKWFound = (string.IsNullOrWhiteSpace(detectionUpdate.Found)) ? "no" : detectionUpdate.Found.ToLower(); - - // Normalize the tags - if (!string.IsNullOrWhiteSpace(detectionUpdate.Tags)) - { - var working = detectionUpdate.Tags.Replace(",", ";"); - - var tagList = new List(); - tagList.AddRange(working.Split(';').ToList().Select(x => x.Trim())); - metadata.tags = string.Join(";", tagList); - } - else - { - metadata.tags = string.Empty; - } - - await _repository.CommitAsync(); - - return Ok(DetectionProcessors.ToDetection(metadata)); - } - catch (ArgumentNullException ex) - { - var details = new ProblemDetails() - { - Detail = ex.Message - }; - return BadRequest(details); - } - catch (Exception ex) - { - var details = new ProblemDetails() - { - Title = ex.GetType().ToString(), - Detail = ex.Message - }; - - return StatusCode(StatusCodes.Status500InternalServerError, details); - } - } + [SwaggerResponse(StatusCodes.Status200OK, "Returns the list of all Detections.", typeof(IQueryable))] + [SwaggerResponse(StatusCodes.Status204NoContent, "If there are no Detections for the specified timeframe.")] + [SwaggerResponse(StatusCodes.Status400BadRequest, "If the request was malformed (missing parameters).")] + [SwaggerResponse(StatusCodes.Status500InternalServerError, "If there is an internal error reading or processing data from the data source.")] + public ActionResult> Get([FromQuery] DetectionQueryParameters queryParameters) + { + try + { + ValidateQueryParameters(queryParameters); + + // start with all records + var queryable = _repository.GetAll(); + + // apply timeframe filter + MetadataFilters.ApplyTimeframeFilter(ref queryable, queryParameters.Timeframe, queryParameters.DateFrom, queryParameters.DateTo); + + // apply location and hydrophone filters + ApplyOptionalLocationAndHydrophoneFilters(ref queryable, queryParameters); + + // If no detections found + if (queryable == null || queryable.Count() == 0) + { + return NoContent(); + } + + // total number of records + double recordCount = queryable.Count(); + + var results = queryable + .Select(x => DetectionProcessors.ToDetection(x)).ToList(); + + // apply sort, pagination filters and set page count headers + ApplySortPaginationAndHeaders(ref results, recordCount, queryParameters); + + // map to returnable data type and return + return Ok(results); + } + catch (ArgumentNullException ex) + { + var details = new ProblemDetails() + { + Detail = ex.Message + }; + return BadRequest(details); + } + catch (Exception ex) + { + var details = new ProblemDetails() + { + Title = ex.GetType().ToString(), + Detail = ex.Message + }; + + return StatusCode(StatusCodes.Status500InternalServerError, details); + } + } + + /// + /// Fetch a specific detection based on a unique ID. + /// + /// Detection's unique ID + [HttpGet("{id}")] + [AllowAnonymous] + [SwaggerResponse(StatusCodes.Status200OK, "Returns the Detection.", typeof(Detection))] + [SwaggerResponse(StatusCodes.Status400BadRequest, "If the request was malformed (missing parameters).")] + [SwaggerResponse(StatusCodes.Status404NotFound, "If the Detection defined by the unique ID could not be found.")] + [SwaggerResponse(StatusCodes.Status500InternalServerError, "If there is an internal error reading or processing data from the data source.")] + public async ValueTask> GetByIdAsync(string id) + { + try + { + if (string.IsNullOrWhiteSpace(id)) + throw new ArgumentNullException("id"); + + var metadata = await _repository.GetByIdAsync(id); + + if (metadata == null) + return NotFound(); + + return Ok(DetectionProcessors.ToDetection(metadata)); + } + catch (ArgumentNullException ex) + { + var details = new ProblemDetails() + { + Detail = ex.Message + }; + return BadRequest(details); + } + catch (Exception ex) + { + var details = new ProblemDetails() + { + Title = ex.GetType().ToString(), + Detail = ex.Message + }; + + return StatusCode(StatusCodes.Status500InternalServerError, details); + } + } + + /// + /// List all AI/ML generated detections that have not yet been reviewed (confirmed or rejected) by a human moderator. + /// + [HttpGet("unreviewed")] + [AllowAnonymous] + [SwaggerResponse(StatusCodes.Status200OK, "Returns the list of unreviewed Detections.", typeof(IQueryable))] + [SwaggerResponse(StatusCodes.Status204NoContent, "If there are no unreviewed Detections for the specified timeframe.")] + [SwaggerResponse(StatusCodes.Status400BadRequest, "If the request was malformed (missing parameters).")] + [SwaggerResponse(StatusCodes.Status500InternalServerError, "If there is an internal error reading or processing data from the data source.")] + public ActionResult> GetUnreviewed([FromQuery] DetectionQueryParameters queryParameters) + { + try + { + ValidateQueryParameters(queryParameters); + + // start with all records + var queryable = _repository.GetAll(); + + // apply reviewed status + MetadataFilters.ApplyReviewedFilter(ref queryable, false); + + // apply location and hydrophone filters + ApplyOptionalLocationAndHydrophoneFilters(ref queryable, queryParameters); + + // apply timeframe filter + MetadataFilters.ApplyTimeframeFilter(ref queryable, queryParameters.Timeframe, queryParameters.DateFrom, queryParameters.DateTo); + + // If no detections found + if (queryable == null || queryable.Count() == 0) + { + return NoContent(); + } + + // total number of records + double recordCount = queryable.Count(); + + var results = queryable + .Select(x => DetectionProcessors.ToDetection(x)).ToList(); + + // NOTE: Have to apply SortBy timestamp and pagination filter after + // executing the select because of how EF for Cosmos deals with DateTime. + // Had to convert from string (how stored in Cosmos) to DateTime in order to apply the + // select, but that messed up the SortBy since Cosmos is expecting a string. + + // apply sort, pagination filters and set page count headers + ApplySortPaginationAndHeaders(ref results, recordCount, queryParameters); + + // map to returnable data type and return + return Ok(results); + } + catch (ArgumentNullException ex) + { + var details = new ProblemDetails() + { + Detail = ex.Message + }; + return BadRequest(details); + } + catch (Exception ex) + { + var details = new ProblemDetails() + { + Title = ex.GetType().ToString(), + Detail = ex.Message + }; + + return StatusCode(StatusCodes.Status500InternalServerError, details); + } + } + + /// + /// List all AI/ML generated detections that have been reviewed by a human moderator and have confirmed whale sounds. + /// + [HttpGet("confirmed")] + [AllowAnonymous] + [SwaggerResponse(StatusCodes.Status200OK, "Returns the list of confirmed Detections.", typeof(IQueryable))] + [SwaggerResponse(StatusCodes.Status204NoContent, "If there are no confirmed Detections for the specified timeframe.")] + [SwaggerResponse(StatusCodes.Status400BadRequest, "If the request was malformed (missing parameters).")] + [SwaggerResponse(StatusCodes.Status500InternalServerError, "If there is an internal error reading or processing data from the data source.")] + public ActionResult> GetConfirmed([FromQuery] DetectionQueryParameters queryParameters) + { + try + { + ValidateQueryParameters(queryParameters); + + // start with all records + var queryable = _repository.GetAll(); + + // apply desired status + MetadataFilters.ApplyReviewedFilter(ref queryable, true); + + // apply desired found state + MetadataFilters.ApplyFoundFilter(ref queryable, "yes"); + + // apply timeframe filter + MetadataFilters.ApplyTimeframeFilter(ref queryable, queryParameters.Timeframe, queryParameters.DateFrom, queryParameters.DateTo); + + // apply location and hydrophone filters + ApplyOptionalLocationAndHydrophoneFilters(ref queryable, queryParameters); + + // If no detections found + if (queryable == null || queryable.Count() == 0) + { + return NoContent(); + } + + // total number of records + double recordCount = queryable.Count(); + + var results = queryable + .Select(x => DetectionProcessors.ToDetection(x)).ToList(); + + // apply sort, pagination filters and set page count headers + ApplySortPaginationAndHeaders(ref results, recordCount, queryParameters); + + // map to returnable data type and return + return Ok(results); + } + catch (ArgumentNullException ex) + { + var details = new ProblemDetails() + { + Detail = ex.Message + }; + return BadRequest(details); + } + catch (Exception ex) + { + var details = new ProblemDetails() + { + Title = ex.GetType().ToString(), + Detail = ex.Message + }; + + return StatusCode(StatusCodes.Status500InternalServerError, details); + } + } + + /// + /// List all AI/ML generated detections that have been reviewed by a human moderator, but do not have whale sounds. + /// + [HttpGet("falsepositives")] + [AllowAnonymous] + [SwaggerResponse(StatusCodes.Status200OK, "Returns the list of false positive (unconfirmed) Detections.", typeof(IQueryable))] + [SwaggerResponse(StatusCodes.Status204NoContent, "If there are no false positive Detections for the specified timeframe.")] + [SwaggerResponse(StatusCodes.Status400BadRequest, "If the request was malformed (missing parameters).")] + [SwaggerResponse(StatusCodes.Status500InternalServerError, "If there is an internal error reading or processing data from the data source.")] + public ActionResult> GetFalsePositives([FromQuery] DetectionQueryParameters queryParameters) + { + try + { + ValidateQueryParameters(queryParameters); + + // start with all records + var queryable = _repository.GetAll(); + + // apply desired status + MetadataFilters.ApplyReviewedFilter(ref queryable, true); + + // apply desired found state + MetadataFilters.ApplyFoundFilter(ref queryable, "no"); + + // apply timeframe filter + MetadataFilters.ApplyTimeframeFilter(ref queryable, queryParameters.Timeframe, queryParameters.DateFrom, queryParameters.DateTo); + + // apply location and hydrophone filters + ApplyOptionalLocationAndHydrophoneFilters(ref queryable, queryParameters); + + // If no detections found + if (queryable == null || queryable.Count() == 0) + { + return NoContent(); + } + + // total number of records + double recordCount = queryable.Count(); + + var results = queryable + .Select(x => DetectionProcessors.ToDetection(x)).ToList(); + + // apply sort, pagination filters and set page count headers + ApplySortPaginationAndHeaders(ref results, recordCount, queryParameters); + + // map to returnable data type and return + return Ok(results); + } + catch (ArgumentNullException ex) + { + var details = new ProblemDetails() + { + Detail = ex.Message + }; + return BadRequest(details); + } + catch (Exception ex) + { + var details = new ProblemDetails() + { + Title = ex.GetType().ToString(), + Detail = ex.Message + }; + + return StatusCode(StatusCodes.Status500InternalServerError, details); + } + } + + /// + /// List all AI/ML generated detections that have been reviewed by a human moderator, but whale sounds could not be conclusively confirmed or denied. + /// + [HttpGet("unknowns")] + [AllowAnonymous] + [SwaggerResponse(StatusCodes.Status200OK, "Returns the list of unknown Detections.", typeof(IQueryable))] + [SwaggerResponse(StatusCodes.Status204NoContent, "If there are no unknown Detections for the specified timeframe.")] + [SwaggerResponse(StatusCodes.Status400BadRequest, "If the request was malformed (missing parameters).")] + [SwaggerResponse(StatusCodes.Status500InternalServerError, "If there is an internal error reading or processing data from the data source.")] + public ActionResult> GetUnknowns([FromQuery] DetectionQueryParameters queryParameters) + { + try + { + ValidateQueryParameters(queryParameters); + + // start with all records + var queryable = _repository.GetAll(); + + // apply desired status + MetadataFilters.ApplyReviewedFilter(ref queryable, true); + + // apply desired found state + MetadataFilters.ApplyFoundFilter(ref queryable, "don't know"); + + // apply timeframe filter + MetadataFilters.ApplyTimeframeFilter(ref queryable, queryParameters.Timeframe, queryParameters.DateFrom, queryParameters.DateTo); + + // apply location and hydrophone filters + ApplyOptionalLocationAndHydrophoneFilters(ref queryable, queryParameters); + + // If no detections found + if (queryable == null || queryable.Count() == 0) + { + return NoContent(); + } + + // total number of records + double recordCount = queryable.Count(); + + var results = queryable + .Select(x => DetectionProcessors.ToDetection(x)).ToList(); + + // apply sort, pagination filters and set page count headers + ApplySortPaginationAndHeaders(ref results, recordCount, queryParameters); + + // map to returnable data type and return + return Ok(results); + } + catch (ArgumentNullException ex) + { + var details = new ProblemDetails() + { + Detail = ex.Message + }; + return BadRequest(details); + } + catch (Exception ex) + { + var details = new ProblemDetails() + { + Title = ex.GetType().ToString(), + Detail = ex.Message + }; + + return StatusCode(StatusCodes.Status500InternalServerError, details); + } + } + + /// + /// Updates the detection with information provided by a human moderator. + /// + /// Detection's unique Id (AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA). + /// The Detection's values to be updated. + [HttpPut("{id}")] + [Authorize("Moderators")] + [SwaggerResponse(StatusCodes.Status200OK, "Returns the contents of the updated Detection.", typeof(Detection))] + [SwaggerResponse(StatusCodes.Status400BadRequest, "If the request was malformed (missing parameters).")] + [SwaggerResponse(StatusCodes.Status401Unauthorized, "If the user is not logged in.")] + [SwaggerResponse(StatusCodes.Status403Forbidden, "If the user is logged in, but is not an authorized Moderator.")] + [SwaggerResponse(StatusCodes.Status404NotFound, "Indicates the Detection was not found to update.")] + [SwaggerResponse(StatusCodes.Status500InternalServerError, "If there is an internal error reading or processing data from the data source.")] + public async ValueTask> Put(string id, [FromBody] DetectionUpdate detectionUpdate) + { + try + { + if (string.IsNullOrWhiteSpace(id)) + throw new ArgumentNullException("id"); + + if (detectionUpdate == null) + throw new ArgumentNullException("postedDetection"); + + var metadata = await _repository.GetByIdAsync(id); + + if (metadata == null) + return NotFound(); + + metadata.comments = detectionUpdate.Comments; + metadata.moderator = detectionUpdate.Moderator; + metadata.dateModerated = detectionUpdate.Moderated.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"); + metadata.reviewed = detectionUpdate.Reviewed; + metadata.SRKWFound = (string.IsNullOrWhiteSpace(detectionUpdate.Found)) ? "no" : detectionUpdate.Found.ToLower(); + + // Normalize the tags + if (!string.IsNullOrWhiteSpace(detectionUpdate.Tags)) + { + var working = detectionUpdate.Tags.Replace(",", ";"); + + var tagList = new List(); + tagList.AddRange(working.Split(';').ToList().Select(x => x.Trim())); + metadata.tags = string.Join(";", tagList); + } + else + { + metadata.tags = string.Empty; + } + + await _repository.CommitAsync(); + + return Ok(DetectionProcessors.ToDetection(metadata)); + } + catch (ArgumentNullException ex) + { + var details = new ProblemDetails() + { + Detail = ex.Message + }; + return BadRequest(details); + } + catch (Exception ex) + { + var details = new ProblemDetails() + { + Title = ex.GetType().ToString(), + Detail = ex.Message + }; + + return StatusCode(StatusCodes.Status500InternalServerError, details); + } + } } diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Server/Controllers/MetricsController.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Server/Controllers/MetricsController.cs index 47b2c75d..03614b4f 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Server/Controllers/MetricsController.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Server/Controllers/MetricsController.cs @@ -8,180 +8,180 @@ [ApiController] public class MetricsController : ControllerBase { - private readonly MetadataRepository _repository; + private readonly MetadataRepository _repository; - public MetricsController(MetadataRepository repository) - { - _repository = repository; - } + public MetricsController(MetadataRepository repository) + { + _repository = repository; + } - private IQueryable BuildQueryableAsync(string timeframe, string moderator = null) - { - // start with all records - var queryable = _repository.GetAll(); + private IQueryable BuildQueryableAsync(string timeframe, string moderator = null) + { + // start with all records + var queryable = _repository.GetAll(); - // apply timeframe filter - MetadataFilters.ApplyTimeframeFilter(ref queryable, timeframe); + // apply timeframe filter + MetadataFilters.ApplyTimeframeFilter(ref queryable, timeframe); - // apply moderator filter, if applicable - MetadataFilters.ApplyModeratorFilter(ref queryable, moderator); + // apply moderator filter, if applicable + MetadataFilters.ApplyModeratorFilter(ref queryable, moderator); - return queryable; - } + return queryable; + } - /// - /// Fetch system metrics. - /// - [HttpGet("system")] + /// + /// Fetch system metrics. + /// + [HttpGet("system")] [AllowAnonymous] - [SwaggerResponse(StatusCodes.Status200OK, "Returns the system's metrics.", typeof(Metrics))] - [SwaggerResponse(StatusCodes.Status204NoContent, "If there are no metrics for the specified timeframe.")] - [SwaggerResponse(StatusCodes.Status400BadRequest, "If the request was malformed (missing parameters).")] - [SwaggerResponse(StatusCodes.Status500InternalServerError, "If there is an internal error reading or processing data from the data source.")] - public ActionResult GetSystemMetrics([FromQuery] MetricsFilterDTO queryParameters) - { - try - { - if (string.IsNullOrWhiteSpace(queryParameters.Timeframe)) - throw new ArgumentNullException("Timeframe"); - - var metrics = new Metrics(); - - metrics.Timeframe = queryParameters.Timeframe; - - // Build base queryable - var queryable = BuildQueryableAsync(queryParameters.Timeframe); - - // If not metrics to return - if (queryable == null || queryable.Count() == 0) - { - return NoContent(); - } - - var results = queryable - .Select(x => DetectionProcessors.ToDetection(x)).ToList(); - - // Pull reviewed/unreviewed metrics from querable - var reviewed = DetectionProcessors.GetReviewed(results); - - metrics.Reviewed = reviewed.ReviewedCount; - metrics.Unreviewed = reviewed.UnreviewedCount; - - // Pull results metrics from queryable - var detections = DetectionProcessors.GetResults(results); - - metrics.ConfirmedDetection = detections.ConfirmedCount; - metrics.FalseDetection = detections.FalseCount; - metrics.UnknownDetection = detections.UnknownCount; - - // Pull comments from queryable - metrics.ConfirmedComments = DetectionProcessors.GetComments(results, "yes"); - metrics.UnconfirmedComments = DetectionProcessors.GetComments(results, "no"); - metrics.UnconfirmedComments.AddRange(DetectionProcessors.GetComments(results, "don't know")); - metrics.UnconfirmedComments = metrics.UnconfirmedComments.OrderByDescending(x => x.Timestamp).ToList(); - - // Pull tags from queryable - metrics.Tags = DetectionProcessors.GetTags(results); - - return Ok(metrics); - } - catch (ArgumentNullException ex) - { - var details = new ProblemDetails() - { - Detail = ex.Message - }; - return BadRequest(details); - } - catch (Exception ex) - { - var details = new ProblemDetails() - { - Title = ex.GetType().ToString(), - Detail = ex.Message - }; - - return StatusCode(StatusCodes.Status500InternalServerError, details); - } - } - - /// - /// Fetch user metrics. - /// - [HttpGet("moderator")] - [AllowAnonymous] - [SwaggerResponse(StatusCodes.Status200OK, "Returns the user's metrics.", typeof(Metrics))] - [SwaggerResponse(StatusCodes.Status204NoContent, "If the user has no activity for the specified timeframe.")] - [SwaggerResponse(StatusCodes.Status400BadRequest, "If the request was malformed (missing parameters).")] - [SwaggerResponse(StatusCodes.Status500InternalServerError, "If there is an internal error reading or processing data from the data source.")] - public ActionResult GetModeratorMetrics([FromQuery] ModeratorMetricsFilterDTO queryParameters) - { - try - { - if (string.IsNullOrWhiteSpace(queryParameters.Timeframe)) - throw new ArgumentNullException("Timeframe"); - - if (string.IsNullOrWhiteSpace(queryParameters.Moderator)) - throw new ArgumentNullException("Moderator"); - - var metrics = new ModeratorMetrics(); - - metrics.Timeframe = queryParameters.Timeframe; - metrics.Moderator = queryParameters.Moderator; - - // Build base queryable - var queryable = BuildQueryableAsync(queryParameters.Timeframe, queryParameters.Moderator); - - // If not metrics to return - if (queryable == null || queryable.Count() == 0) - { - return NoContent(); - } - - var results = queryable - .Select(x => DetectionProcessors.ToDetection(x)).ToList(); - - //// Pull reviewed/unreviewed metrics from querable - var reviewed = DetectionProcessors.GetReviewed(results); - - metrics.Reviewed = reviewed.ReviewedCount; - metrics.Unreviewed = reviewed.UnreviewedCount; - - // Pull results metrics from queryable - var detections = DetectionProcessors.GetResults(results); - - metrics.ConfirmedDetection = detections.ConfirmedCount; - metrics.FalseDetection = detections.FalseCount; - metrics.UnknownDetection = detections.UnknownCount; - - // Pull comments from queryable - metrics.ConfirmedComments = DetectionProcessors.GetComments(results, "yes"); - metrics.UnconfirmedComments = DetectionProcessors.GetComments(results, "no"); - metrics.UnconfirmedComments.AddRange(DetectionProcessors.GetComments(results, "don't know")); - metrics.UnconfirmedComments = metrics.UnconfirmedComments.OrderByDescending(x => x.Timestamp).ToList(); - - // Pull tags from queryable - metrics.Tags = DetectionProcessors.GetTags(results); - - return Ok(metrics); - } - catch (ArgumentNullException ex) - { - var details = new ProblemDetails() - { - Detail = ex.Message - }; - return BadRequest(details); - } - catch (Exception ex) - { - var details = new ProblemDetails() - { - Title = ex.GetType().ToString(), - Detail = ex.Message - }; - - return StatusCode(StatusCodes.Status500InternalServerError, details); - } - } + [SwaggerResponse(StatusCodes.Status200OK, "Returns the system's metrics.", typeof(Metrics))] + [SwaggerResponse(StatusCodes.Status204NoContent, "If there are no metrics for the specified timeframe.")] + [SwaggerResponse(StatusCodes.Status400BadRequest, "If the request was malformed (missing parameters).")] + [SwaggerResponse(StatusCodes.Status500InternalServerError, "If there is an internal error reading or processing data from the data source.")] + public ActionResult GetSystemMetrics([FromQuery] MetricsFilterDTO queryParameters) + { + try + { + if (string.IsNullOrWhiteSpace(queryParameters.Timeframe)) + throw new ArgumentNullException("Timeframe"); + + var metrics = new Metrics(); + + metrics.Timeframe = queryParameters.Timeframe; + + // Build base queryable + var queryable = BuildQueryableAsync(queryParameters.Timeframe); + + // If not metrics to return + if (queryable == null || queryable.Count() == 0) + { + return NoContent(); + } + + var results = queryable + .Select(x => DetectionProcessors.ToDetection(x)).ToList(); + + // Pull reviewed/unreviewed metrics from querable + var reviewed = DetectionProcessors.GetReviewed(results); + + metrics.Reviewed = reviewed.ReviewedCount; + metrics.Unreviewed = reviewed.UnreviewedCount; + + // Pull results metrics from queryable + var detections = DetectionProcessors.GetResults(results); + + metrics.ConfirmedDetection = detections.ConfirmedCount; + metrics.FalseDetection = detections.FalseCount; + metrics.UnknownDetection = detections.UnknownCount; + + // Pull comments from queryable + metrics.ConfirmedComments = DetectionProcessors.GetComments(results, "yes"); + metrics.UnconfirmedComments = DetectionProcessors.GetComments(results, "no"); + metrics.UnconfirmedComments.AddRange(DetectionProcessors.GetComments(results, "don't know")); + metrics.UnconfirmedComments = metrics.UnconfirmedComments.OrderByDescending(x => x.Timestamp).ToList(); + + // Pull tags from queryable + metrics.Tags = DetectionProcessors.GetTags(results); + + return Ok(metrics); + } + catch (ArgumentNullException ex) + { + var details = new ProblemDetails() + { + Detail = ex.Message + }; + return BadRequest(details); + } + catch (Exception ex) + { + var details = new ProblemDetails() + { + Title = ex.GetType().ToString(), + Detail = ex.Message + }; + + return StatusCode(StatusCodes.Status500InternalServerError, details); + } + } + + /// + /// Fetch user metrics. + /// + [HttpGet("moderator")] + [AllowAnonymous] + [SwaggerResponse(StatusCodes.Status200OK, "Returns the user's metrics.", typeof(Metrics))] + [SwaggerResponse(StatusCodes.Status204NoContent, "If the user has no activity for the specified timeframe.")] + [SwaggerResponse(StatusCodes.Status400BadRequest, "If the request was malformed (missing parameters).")] + [SwaggerResponse(StatusCodes.Status500InternalServerError, "If there is an internal error reading or processing data from the data source.")] + public ActionResult GetModeratorMetrics([FromQuery] ModeratorMetricsFilterDTO queryParameters) + { + try + { + if (string.IsNullOrWhiteSpace(queryParameters.Timeframe)) + throw new ArgumentNullException("Timeframe"); + + if (string.IsNullOrWhiteSpace(queryParameters.Moderator)) + throw new ArgumentNullException("Moderator"); + + var metrics = new ModeratorMetrics(); + + metrics.Timeframe = queryParameters.Timeframe; + metrics.Moderator = queryParameters.Moderator; + + // Build base queryable + var queryable = BuildQueryableAsync(queryParameters.Timeframe, queryParameters.Moderator); + + // If not metrics to return + if (queryable == null || queryable.Count() == 0) + { + return NoContent(); + } + + var results = queryable + .Select(x => DetectionProcessors.ToDetection(x)).ToList(); + + //// Pull reviewed/unreviewed metrics from querable + var reviewed = DetectionProcessors.GetReviewed(results); + + metrics.Reviewed = reviewed.ReviewedCount; + metrics.Unreviewed = reviewed.UnreviewedCount; + + // Pull results metrics from queryable + var detections = DetectionProcessors.GetResults(results); + + metrics.ConfirmedDetection = detections.ConfirmedCount; + metrics.FalseDetection = detections.FalseCount; + metrics.UnknownDetection = detections.UnknownCount; + + // Pull comments from queryable + metrics.ConfirmedComments = DetectionProcessors.GetComments(results, "yes"); + metrics.UnconfirmedComments = DetectionProcessors.GetComments(results, "no"); + metrics.UnconfirmedComments.AddRange(DetectionProcessors.GetComments(results, "don't know")); + metrics.UnconfirmedComments = metrics.UnconfirmedComments.OrderByDescending(x => x.Timestamp).ToList(); + + // Pull tags from queryable + metrics.Tags = DetectionProcessors.GetTags(results); + + return Ok(metrics); + } + catch (ArgumentNullException ex) + { + var details = new ProblemDetails() + { + Detail = ex.Message + }; + return BadRequest(details); + } + catch (Exception ex) + { + var details = new ProblemDetails() + { + Title = ex.GetType().ToString(), + Detail = ex.Message + }; + + return StatusCode(StatusCodes.Status500InternalServerError, details); + } + } } \ No newline at end of file diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Server/Controllers/TagsController.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Server/Controllers/TagsController.cs index 64b951d2..cbaab595 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Server/Controllers/TagsController.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Server/Controllers/TagsController.cs @@ -74,7 +74,7 @@ public async ValueTask> Put([FromBody] TagUpdate tagUpdate) if (detectionsToUpdate.Count() == 0) return NoContent(); - foreach(var detection in detectionsToUpdate) + foreach (var detection in detectionsToUpdate) { detection.tags = detection.tags.Replace(tagUpdate.OldTag, tagUpdate.NewTag); } diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Server/Extensions/Authentication.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Server/Extensions/Authentication.cs index b8861e2a..0e8ad0fc 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Server/Extensions/Authentication.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Server/Extensions/Authentication.cs @@ -43,7 +43,7 @@ public static void ConfigureModeratorPolicy(this WebApplicationBuilder builder, // Set up Swagger so users can use OAuth to authenticate against it public static void ConfigureSwagger(this WebApplicationBuilder builder, AppSettings appSettings) { - var instance = !string.IsNullOrWhiteSpace(appSettings.AzureAd.Instance) ? + var instance = !string.IsNullOrWhiteSpace(appSettings.AzureAd.Instance) ? appSettings.AzureAd.Instance : string.Empty; var tenantId = !string.IsNullOrWhiteSpace(appSettings.AzureAd.TenantId) ? appSettings.AzureAd.TenantId : Guid.NewGuid().ToString(); diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Server/Helpers/DetectionFilters.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Server/Helpers/DetectionFilters.cs index e2018ed2..80ea628a 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Server/Helpers/DetectionFilters.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Server/Helpers/DetectionFilters.cs @@ -1,47 +1,47 @@ namespace AIForOrcas.Server.Helpers; public static class DetectionFilters - { - public static int DefaultRecordsPerPage = 5; +{ + public static int DefaultRecordsPerPage = 5; - public static void ApplyTimestampSortFilter(ref List list, string sortOrder) - { - if (sortOrder == "asc") - list = list.OrderBy(x => x.Timestamp) - .ThenByDescending(x => x.Confidence) - .ThenBy(x => x.Id) - .ToList(); + public static void ApplyTimestampSortFilter(ref List list, string sortOrder) + { + if (sortOrder == "asc") + list = list.OrderBy(x => x.Timestamp) + .ThenByDescending(x => x.Confidence) + .ThenBy(x => x.Id) + .ToList(); - if (sortOrder == "desc") - list = list.OrderByDescending(x => x.Timestamp) - .ThenByDescending(x => x.Confidence) - .ThenBy(x => x.Id) - .ToList(); - } + if (sortOrder == "desc") + list = list.OrderByDescending(x => x.Timestamp) + .ThenByDescending(x => x.Confidence) + .ThenBy(x => x.Id) + .ToList(); + } - public static void ApplyConfidenceSortFilter(ref List list, string sortOrder) - { - if (sortOrder == "asc") - list = list.OrderBy(x => x.Confidence) - .ThenBy(x => x.Timestamp) - .ThenBy(x => x.Id) - .ToList(); + public static void ApplyConfidenceSortFilter(ref List list, string sortOrder) + { + if (sortOrder == "asc") + list = list.OrderBy(x => x.Confidence) + .ThenBy(x => x.Timestamp) + .ThenBy(x => x.Id) + .ToList(); - if (sortOrder == "desc") - list = list.OrderByDescending(x => x.Confidence) - .ThenBy(x => x.Timestamp) - .ThenBy(x => x.Id) - .ToList(); - } + if (sortOrder == "desc") + list = list.OrderByDescending(x => x.Confidence) + .ThenBy(x => x.Timestamp) + .ThenBy(x => x.Id) + .ToList(); + } - public static void ApplyPaginationFilter(ref List list, int page, int take) - { - var skip = page > 0 ? page - 1 : 0; - var recordsPerPage = take > 0 ? take : DefaultRecordsPerPage; + public static void ApplyPaginationFilter(ref List list, int page, int take) + { + var skip = page > 0 ? page - 1 : 0; + var recordsPerPage = take > 0 ? take : DefaultRecordsPerPage; - list = list - .Skip(skip * recordsPerPage) - .Take(recordsPerPage) - .ToList(); - } + list = list + .Skip(skip * recordsPerPage) + .Take(recordsPerPage) + .ToList(); + } } diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Server/Helpers/DetectionProcessors.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Server/Helpers/DetectionProcessors.cs index e9364f05..6322e8e3 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Server/Helpers/DetectionProcessors.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Server/Helpers/DetectionProcessors.cs @@ -2,127 +2,127 @@ public static class DetectionProcessors { - public static (int ReviewedCount, int UnreviewedCount) GetReviewed(List list) - { - var status = list.GroupBy(n => n.Reviewed); - - var reviewed = status.Where(x => x.Key == true) - .Select(x => x.Count()).FirstOrDefault(); - - var unreviewed = status.Where(x => x.Key == false) - .Select(x => x.Count()).FirstOrDefault(); - - return (reviewed, unreviewed); - } - - public static (int ConfirmedCount, int FalseCount, int UnknownCount) GetResults(List list) - { - // grab results metrics - var results = list.GroupBy(n => n.Found); - - var confirmed = results.Where(x => x.Key == "yes") - .Select(x => x.Count()).FirstOrDefault(); - - var unconfirmed = results.Where(x => x.Key == "no") - .Select(x => x.Count()).FirstOrDefault(); - - var unknown = results.Where(x => x.Key == "don't know") - .Select(x => x.Count()).FirstOrDefault(); - - return (confirmed, unconfirmed, unknown); - } - - public static List GetComments(List list, string status) - { - var results = new List(); - - list - .Where(x => x.Found == status && !string.IsNullOrWhiteSpace(x.Comments)) - .ToList().ForEach(x => - { - results.Add(new MetricsComment() - { - Comment = x.Comments, - Moderator = x.Moderator, - Timestamp = x.Moderated, - Id = x.Id - }); - }); - - return results.OrderByDescending(x => x.Timestamp).ToList(); - } - - public static List GetTags(List list) - { - var results = new List(); - - list - .Where(x => !string.IsNullOrWhiteSpace(x.Tags)) - .ToList().ForEach(y => - { - var id = y.Id; - y.Tags.Split(";") - .ToList().ForEach(z => - { - var tag = results.Where(t => t.Tag == z.ToUpper()).FirstOrDefault(); - if (tag != null) - { - tag.Ids.Add(id); - } - else - { - var newTag = new MetricsTag() - { - Tag = z.ToUpper() - }; - newTag.Ids.Add(id); - results.Add(newTag); - } - }); - }); - - return results.OrderBy(x => x.Tag).ToList(); - } - - public static Detection ToDetection(Metadata metadata) - { - var detection = new Detection() - { - Id = string.IsNullOrEmpty(metadata.id) ? Guid.NewGuid().ToString() : metadata.id, - SpectrogramUri = string.IsNullOrWhiteSpace(metadata.imageUri) ? string.Empty : metadata.imageUri, - AudioUri = string.IsNullOrWhiteSpace(metadata.audioUri) ? string.Empty : metadata.audioUri, - Reviewed = metadata.reviewed, - Confidence = metadata.whaleFoundConfidence, - Found = string.IsNullOrWhiteSpace(metadata.SRKWFound) ? "No" : metadata.SRKWFound, - Timestamp = metadata.timestamp, - Comments = metadata.comments, - Tags = metadata.tags, - GlobalPredictionLabel = metadata.globalPredictionLabel ?? string.Empty, - Moderated = string.IsNullOrWhiteSpace(metadata.dateModerated) ? DateTime.MinValue : DateTime.Parse(metadata.dateModerated), - Moderator = metadata.moderator, - Location = new DTO.API.Location() - { - Name = metadata.location.name, - Longitude = metadata.location.longitude, - Latitude = metadata.location.latitude - } - }; - - if (metadata.predictions?.Count > 0) - { - metadata.predictions.ForEach(x => - { - detection.Annotations.Add(new Annotation() - { - Id = x.id, - Confidence = x.confidence, - StartTime = x.startTime, - EndTime = x.startTime + x.duration, - Label = x.label ?? string.Empty, - }); - }); - } - - return detection; - } + public static (int ReviewedCount, int UnreviewedCount) GetReviewed(List list) + { + var status = list.GroupBy(n => n.Reviewed); + + var reviewed = status.Where(x => x.Key == true) + .Select(x => x.Count()).FirstOrDefault(); + + var unreviewed = status.Where(x => x.Key == false) + .Select(x => x.Count()).FirstOrDefault(); + + return (reviewed, unreviewed); + } + + public static (int ConfirmedCount, int FalseCount, int UnknownCount) GetResults(List list) + { + // grab results metrics + var results = list.GroupBy(n => n.Found); + + var confirmed = results.Where(x => x.Key == "yes") + .Select(x => x.Count()).FirstOrDefault(); + + var unconfirmed = results.Where(x => x.Key == "no") + .Select(x => x.Count()).FirstOrDefault(); + + var unknown = results.Where(x => x.Key == "don't know") + .Select(x => x.Count()).FirstOrDefault(); + + return (confirmed, unconfirmed, unknown); + } + + public static List GetComments(List list, string status) + { + var results = new List(); + + list + .Where(x => x.Found == status && !string.IsNullOrWhiteSpace(x.Comments)) + .ToList().ForEach(x => + { + results.Add(new MetricsComment() + { + Comment = x.Comments, + Moderator = x.Moderator, + Timestamp = x.Moderated, + Id = x.Id + }); + }); + + return results.OrderByDescending(x => x.Timestamp).ToList(); + } + + public static List GetTags(List list) + { + var results = new List(); + + list + .Where(x => !string.IsNullOrWhiteSpace(x.Tags)) + .ToList().ForEach(y => + { + var id = y.Id; + y.Tags.Split(";") + .ToList().ForEach(z => + { + var tag = results.Where(t => t.Tag == z.ToUpper()).FirstOrDefault(); + if (tag != null) + { + tag.Ids.Add(id); + } + else + { + var newTag = new MetricsTag() + { + Tag = z.ToUpper() + }; + newTag.Ids.Add(id); + results.Add(newTag); + } + }); + }); + + return results.OrderBy(x => x.Tag).ToList(); + } + + public static Detection ToDetection(Metadata metadata) + { + var detection = new Detection() + { + Id = string.IsNullOrEmpty(metadata.id) ? Guid.NewGuid().ToString() : metadata.id, + SpectrogramUri = string.IsNullOrWhiteSpace(metadata.imageUri) ? string.Empty : metadata.imageUri, + AudioUri = string.IsNullOrWhiteSpace(metadata.audioUri) ? string.Empty : metadata.audioUri, + Reviewed = metadata.reviewed, + Confidence = metadata.whaleFoundConfidence, + Found = string.IsNullOrWhiteSpace(metadata.SRKWFound) ? "No" : metadata.SRKWFound, + Timestamp = metadata.timestamp, + Comments = metadata.comments, + Tags = metadata.tags, + GlobalPredictionLabel = metadata.globalPredictionLabel ?? string.Empty, + Moderated = string.IsNullOrWhiteSpace(metadata.dateModerated) ? DateTime.MinValue : DateTime.Parse(metadata.dateModerated), + Moderator = metadata.moderator, + Location = new DTO.API.Location() + { + Name = metadata.location.name, + Longitude = metadata.location.longitude, + Latitude = metadata.location.latitude + } + }; + + if (metadata.predictions?.Count > 0) + { + metadata.predictions.ForEach(x => + { + detection.Annotations.Add(new Annotation() + { + Id = x.id, + Confidence = x.confidence, + StartTime = x.startTime, + EndTime = x.startTime + x.duration, + Label = x.label ?? string.Empty, + }); + }); + } + + return detection; + } } diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Server/Helpers/MetadataFilters.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Server/Helpers/MetadataFilters.cs index f96fba66..4643983b 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Server/Helpers/MetadataFilters.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Server/Helpers/MetadataFilters.cs @@ -1,10 +1,10 @@ namespace AIForOrcas.Server.Helpers; public static class MetadataFilters - { - public static int DefaultRecordsPerPage = 5; +{ + public static int DefaultRecordsPerPage = 5; - public static void ApplyTimeframeFilter(ref IQueryable queryable, string timeframe, DateTime? dateFrom=null, DateTime? dateTo=null) + public static void ApplyTimeframeFilter(ref IQueryable queryable, string timeframe, DateTime? dateFrom = null, DateTime? dateTo = null) { if (!string.IsNullOrWhiteSpace(timeframe)) { @@ -58,36 +58,36 @@ public static void ApplyTimeframeFilter(ref IQueryable queryable, stri } public static void ApplyModeratorFilter(ref IQueryable queryable, string moderator) - { - if (!string.IsNullOrWhiteSpace(moderator)) - { - queryable = queryable.Where(x => x.moderator == moderator); - } - } + { + if (!string.IsNullOrWhiteSpace(moderator)) + { + queryable = queryable.Where(x => x.moderator == moderator); + } + } - public static void ApplyLocationFilter(ref IQueryable queryable, string location) - { - if (!string.IsNullOrWhiteSpace(location)) - { - queryable = queryable.Where(x => x.location.name == location); - } - } + public static void ApplyLocationFilter(ref IQueryable queryable, string location) + { + if (!string.IsNullOrWhiteSpace(location)) + { + queryable = queryable.Where(x => x.location.name == location); + } + } - public static void ApplyHydrophoneIdFilter(ref IQueryable queryable, string hydrophoneId) - { - if (!string.IsNullOrWhiteSpace(hydrophoneId)) - { - queryable = queryable.Where(x => x.source_guid == hydrophoneId); - } - } + public static void ApplyHydrophoneIdFilter(ref IQueryable queryable, string hydrophoneId) + { + if (!string.IsNullOrWhiteSpace(hydrophoneId)) + { + queryable = queryable.Where(x => x.source_guid == hydrophoneId); + } + } - public static void ApplyReviewedFilter(ref IQueryable queryable, bool reviewed) - { - queryable = queryable.Where(x => x.reviewed == reviewed); - } + public static void ApplyReviewedFilter(ref IQueryable queryable, bool reviewed) + { + queryable = queryable.Where(x => x.reviewed == reviewed); + } - public static void ApplyFoundFilter(ref IQueryable queryable, string foundState) - { - queryable = queryable.Where(x => x.SRKWFound == foundState); - } - } + public static void ApplyFoundFilter(ref IQueryable queryable, string foundState) + { + queryable = queryable.Where(x => x.SRKWFound == foundState); + } +} diff --git a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Server/Program.cs b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Server/Program.cs index 7870c95e..3804a9fa 100644 --- a/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Server/Program.cs +++ b/ModeratorFrontEnd/AIForOrcas/AIForOrcas.Server/Program.cs @@ -32,7 +32,7 @@ app.UseSwagger(); app.UseSwaggerUI(c => { - var clientId = !string.IsNullOrWhiteSpace(appSettings.AzureAd.ClientId) ? + var clientId = !string.IsNullOrWhiteSpace(appSettings.AzureAd.ClientId) ? appSettings.AzureAd.ClientId : Guid.NewGuid().ToString(); c.OAuthClientId(clientId); diff --git a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api.Tests.Unit/Controllers/DetectionsControllerTests/Default.GetDetectionByIdAsync.cs b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api.Tests.Unit/Controllers/DetectionsControllerTests/Default.GetDetectionByIdAsync.cs index f364923b..d1778f62 100644 --- a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api.Tests.Unit/Controllers/DetectionsControllerTests/Default.GetDetectionByIdAsync.cs +++ b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api.Tests.Unit/Controllers/DetectionsControllerTests/Default.GetDetectionByIdAsync.cs @@ -14,7 +14,7 @@ public async Task Default_GetDetectionByIdAsync_Expect_Detection() ActionResult actionResult = await _controller.GetDetectionByIdAsync(Guid.NewGuid().ToString()); - + var contentResult = actionResult.Result as ObjectResult; Assert.IsNotNull(contentResult); diff --git a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api.Tests.Unit/Controllers/DetectionsControllerTests/Default.GetPaginatedDetectionsAsync.cs b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api.Tests.Unit/Controllers/DetectionsControllerTests/Default.GetPaginatedDetectionsAsync.cs index 0fb4cb5c..a4fc3ed8 100644 --- a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api.Tests.Unit/Controllers/DetectionsControllerTests/Default.GetPaginatedDetectionsAsync.cs +++ b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api.Tests.Unit/Controllers/DetectionsControllerTests/Default.GetPaginatedDetectionsAsync.cs @@ -18,7 +18,7 @@ public async Task Default_GetPaginatedDetectionsAsync_Expect_DetectionListRespon }; _orchestrationServiceMock.Setup(service => - service.RetrieveFilteredDetectionsAsync(It.IsAny(), It.IsAny(), It.IsAny(), + service.RetrieveFilteredDetectionsAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(response); diff --git a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api.Tests.Unit/Controllers/ModeratorsControllerTests/Default.GetPaginatedDetectionsForGivenTimeframeTagAndModeratorAsync.cs b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api.Tests.Unit/Controllers/ModeratorsControllerTests/Default.GetPaginatedDetectionsForGivenTimeframeTagAndModeratorAsync.cs index b00329ed..e07e5d15 100644 --- a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api.Tests.Unit/Controllers/ModeratorsControllerTests/Default.GetPaginatedDetectionsForGivenTimeframeTagAndModeratorAsync.cs +++ b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api.Tests.Unit/Controllers/ModeratorsControllerTests/Default.GetPaginatedDetectionsForGivenTimeframeTagAndModeratorAsync.cs @@ -19,8 +19,8 @@ public async Task Default_GetPaginatedDetectionsForGivenTimeframeTagAndModerator Tag = "Tag", Detections = new List { - new() { - State = "Positive", + new() { + State = "Positive", Id = Guid.NewGuid().ToString(), Moderator = "Moderator", Tags = new List { "Tag" } diff --git a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api.Tests.Unit/Controllers/TagsControllerTests/Default.GetAllTagsAsync.cs b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api.Tests.Unit/Controllers/TagsControllerTests/Default.GetAllTagsAsync.cs index 25b4d237..726077ce 100644 --- a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api.Tests.Unit/Controllers/TagsControllerTests/Default.GetAllTagsAsync.cs +++ b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api.Tests.Unit/Controllers/TagsControllerTests/Default.GetAllTagsAsync.cs @@ -7,7 +7,7 @@ public async Task Default_GetAllTagsAsync_Expect_TagRemovalResponse() { TagListResponse response = new() { - Tags = new List() { "Tag1", "Tag2" }, + Tags = new List() { "Tag1", "Tag2" }, Count = 2 }; diff --git a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api.Tests.Unit/Controllers/TagsControllerTests/TryCatch.GetTagsForGivenTimeframeAsync.cs b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api.Tests.Unit/Controllers/TagsControllerTests/TryCatch.GetTagsForGivenTimeframeAsync.cs index 29993395..f1eb6dd4 100644 --- a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api.Tests.Unit/Controllers/TagsControllerTests/TryCatch.GetTagsForGivenTimeframeAsync.cs +++ b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api.Tests.Unit/Controllers/TagsControllerTests/TryCatch.GetTagsForGivenTimeframeAsync.cs @@ -28,7 +28,7 @@ public async Task TryCatch_GetTagsForGivenTimeframeAsync_Expect_Exception() private async Task ExecuteRetrieveTags(int count, int statusCode) { - for(int x = 0; x < count; x++) + for (int x = 0; x < count; x++) { ActionResult actionResult = await _controller.GetTagsForGivenTimeframeAsync(DateTime.Now, DateTime.Now.AddDays(1)); diff --git a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api.Tests.Unit/Services/DetectionOrchestrationServiceTests/Default.RetrieveFilteredDetectionsAsync.cs b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api.Tests.Unit/Services/DetectionOrchestrationServiceTests/Default.RetrieveFilteredDetectionsAsync.cs index 2063e5e3..e58cd034 100644 --- a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api.Tests.Unit/Services/DetectionOrchestrationServiceTests/Default.RetrieveFilteredDetectionsAsync.cs +++ b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api.Tests.Unit/Services/DetectionOrchestrationServiceTests/Default.RetrieveFilteredDetectionsAsync.cs @@ -27,12 +27,12 @@ public async Task Default_RetrieveFilteredDetectionsAsync_Expect() }; _metadataServiceMock.Setup(service => - service.RetrievePaginatedMetadataAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), + service.RetrievePaginatedMetadataAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(expectedResults); DetectionListResponse result = await _orchestrationService. - RetrieveFilteredDetectionsAsync(DateTime.Now, DateTime.Now.AddDays(1), "Positive", "timestamp",true, null!, 1, 10); + RetrieveFilteredDetectionsAsync(DateTime.Now, DateTime.Now.AddDays(1), "Positive", "timestamp", true, null!, 1, 10); Assert.AreEqual(expectedResults.QueryableRecords.Count(), result.Detections.Count); diff --git a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api.Tests.Unit/Services/HydrophoneOrchestrationServiceTests/Default.RetrieveHydrophoneLocations.cs b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api.Tests.Unit/Services/HydrophoneOrchestrationServiceTests/Default.RetrieveHydrophoneLocations.cs index 0822eef6..88e93888 100644 --- a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api.Tests.Unit/Services/HydrophoneOrchestrationServiceTests/Default.RetrieveHydrophoneLocations.cs +++ b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api.Tests.Unit/Services/HydrophoneOrchestrationServiceTests/Default.RetrieveHydrophoneLocations.cs @@ -7,7 +7,7 @@ public async Task Default_RetrieveHydrophoneLocations_Expect() { var expectedResults = new QueryableHydrophoneData { - QueryableRecords = (new List { new() { Attributes = new() { NodeName = "test_id", Name = "test" } } } ).AsQueryable(), + QueryableRecords = (new List { new() { Attributes = new() { NodeName = "test_id", Name = "test" } } }).AsQueryable(), TotalCount = 1 }; diff --git a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api.Tests.Unit/Services/MetadataServiceTests/Default.RetrievePaginatedMetadataAsync.cs b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api.Tests.Unit/Services/MetadataServiceTests/Default.RetrievePaginatedMetadataAsync.cs index 52245208..4f561fe2 100644 --- a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api.Tests.Unit/Services/MetadataServiceTests/Default.RetrievePaginatedMetadataAsync.cs +++ b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api.Tests.Unit/Services/MetadataServiceTests/Default.RetrievePaginatedMetadataAsync.cs @@ -15,7 +15,7 @@ public async Task Default_Expect_RetrievePaginatedMetdataAsync() }; _storageBrokerMock.Setup(broker => - broker.GetMetadataListFiltered(It.IsAny(), It.IsAny(), It.IsAny(), + broker.GetMetadataListFiltered(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(expectedResult); diff --git a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api.Tests.Unit/Services/MetadataServiceTests/Default.RetrievePositiveMetadataForGivenTimeframeAsync.cs b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api.Tests.Unit/Services/MetadataServiceTests/Default.RetrievePositiveMetadataForGivenTimeframeAsync.cs index 7839a934..05e71d01 100644 --- a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api.Tests.Unit/Services/MetadataServiceTests/Default.RetrievePositiveMetadataForGivenTimeframeAsync.cs +++ b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api.Tests.Unit/Services/MetadataServiceTests/Default.RetrievePositiveMetadataForGivenTimeframeAsync.cs @@ -30,7 +30,7 @@ public async Task Default_Expect_RetrievePositiveMetadataForGivenTimeframeAsync( Assert.AreEqual(expectedResult.PaginatedRecords.Count(), result.QueryableRecords.Count()); _storageBrokerMock.Verify(broker => - broker.GetPositiveMetadataListByTimeframe(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), + broker.GetPositiveMetadataListByTimeframe(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); } diff --git a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api.Tests.Unit/Services/ModeratorOrchestrationServiceTests/Default.RetrieveDetectionsForGivenTimeframeTagAndModeratorAsync.cs b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api.Tests.Unit/Services/ModeratorOrchestrationServiceTests/Default.RetrieveDetectionsForGivenTimeframeTagAndModeratorAsync.cs index 09e67830..9ada1acb 100644 --- a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api.Tests.Unit/Services/ModeratorOrchestrationServiceTests/Default.RetrieveDetectionsForGivenTimeframeTagAndModeratorAsync.cs +++ b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api.Tests.Unit/Services/ModeratorOrchestrationServiceTests/Default.RetrieveDetectionsForGivenTimeframeTagAndModeratorAsync.cs @@ -24,7 +24,7 @@ public async Task Default_RetrieveDetectionsForGivenTimeframeTagAndModeratorAsyn It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(expectedResult); - DetectionListForModeratorAndTagResponse response = + DetectionListForModeratorAndTagResponse response = await _orchestrationService.RetrieveDetectionsForGivenTimeframeTagAndModeratorAsync(DateTime.Now, DateTime.Now.AddDays(1), "Moderator", "Tag", 1, 10); Assert.AreEqual(expectedResult.QueryableRecords.Count(), response.Detections.Count()); diff --git a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api.Tests.Unit/Services/ModeratorOrchestrationServiceTests/Guards.cs b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api.Tests.Unit/Services/ModeratorOrchestrationServiceTests/Guards.cs index 042470ef..f3d9487c 100644 --- a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api.Tests.Unit/Services/ModeratorOrchestrationServiceTests/Guards.cs +++ b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api.Tests.Unit/Services/ModeratorOrchestrationServiceTests/Guards.cs @@ -8,7 +8,7 @@ public partial class ModeratorOrchestrationServiceTests public void Guard_AllGuardConditions_Expect_Exception() { var wrapper = new ModeratorOrchestrationServiceWrapper(); - + DateTime? invalidDate = DateTime.MinValue; Assert.ThrowsException(() => diff --git a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api.Tests.Unit/Services/ModeratorOrchestrationServiceTests/ModeratorOrchestrationServiceWrapper.cs b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api.Tests.Unit/Services/ModeratorOrchestrationServiceTests/ModeratorOrchestrationServiceWrapper.cs index bd757bc9..56724b9e 100644 --- a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api.Tests.Unit/Services/ModeratorOrchestrationServiceTests/ModeratorOrchestrationServiceWrapper.cs +++ b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api.Tests.Unit/Services/ModeratorOrchestrationServiceTests/ModeratorOrchestrationServiceWrapper.cs @@ -12,7 +12,7 @@ public class ModeratorOrchestrationServiceWrapper : ModeratorOrchestrationServic public new void Validate(string propertyValue, string propertyName) => base.Validate(propertyValue, propertyName); - public new void ValidatePage(int page) => + public new void ValidatePage(int page) => base.ValidatePage(page); public new void ValidatePageSize(int pageSize) => diff --git a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api.Tests.Unit/Services/TagOrchestrationServiceTests/TryCatch.ReturningGenericFunction.cs b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api.Tests.Unit/Services/TagOrchestrationServiceTests/TryCatch.ReturningGenericFunction.cs index 74f82922..e21ce281 100644 --- a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api.Tests.Unit/Services/TagOrchestrationServiceTests/TryCatch.ReturningGenericFunction.cs +++ b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api.Tests.Unit/Services/TagOrchestrationServiceTests/TryCatch.ReturningGenericFunction.cs @@ -10,18 +10,18 @@ public void TryCatch_ReturningGenericFunction_Expect_Exception() var wrapper = new TagOrchestrationServiceWrapper(); var delegateMock = new Mock>(); - delegateMock - .SetupSequence(p => p()) + delegateMock + .SetupSequence(p => p()) - .Throws(new InvalidTagOrchestrationException()) + .Throws(new InvalidTagOrchestrationException()) - .Throws(new MetadataValidationException()) - .Throws(new MetadataDependencyValidationException()) + .Throws(new MetadataValidationException()) + .Throws(new MetadataDependencyValidationException()) - .Throws(new MetadataDependencyException()) - .Throws(new MetadataServiceException()) + .Throws(new MetadataDependencyException()) + .Throws(new MetadataServiceException()) - .Throws(new Exception()); + .Throws(new Exception()); Assert.ThrowsExceptionAsync(async () => await wrapper.TryCatch(delegateMock.Object)); diff --git a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Brokers/Storages/IStorageBroker.Metadatas.cs b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Brokers/Storages/IStorageBroker.Metadatas.cs index 9a56b778..eb9b68bf 100644 --- a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Brokers/Storages/IStorageBroker.Metadatas.cs +++ b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Brokers/Storages/IStorageBroker.Metadatas.cs @@ -7,7 +7,7 @@ public partial interface IStorageBroker Task> GetTagListByTimeframeAndModerator(DateTime fromDate, DateTime toDate, string moderator); Task GetMetadataListByTimeframeAndTag(DateTime fromDate, DateTime toDate, List tags, string tagOperator, int page = 1, int pageSize = 10); - Task GetPositiveMetadataListByTimeframe(DateTime fromDate, DateTime toDate, + Task GetPositiveMetadataListByTimeframe(DateTime fromDate, DateTime toDate, int page = 1, int pageSize = 10); Task GetPositiveMetadataListByTimeframeAndModerator(DateTime fromDate, DateTime toDate, string moderator, int page = 1, int pageSize = 10); diff --git a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Brokers/Storages/StorageBroker.cs b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Brokers/Storages/StorageBroker.cs index a2af966a..12f7538d 100644 --- a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Brokers/Storages/StorageBroker.cs +++ b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Brokers/Storages/StorageBroker.cs @@ -10,7 +10,7 @@ public partial class StorageBroker : IStorageBroker, IDisposable public StorageBroker(AppSettings appSettings) { _appSettings = appSettings; - + _cosmosClient = new CosmosClient(_appSettings.CosmosConnectionString); Database database; @@ -20,7 +20,7 @@ public StorageBroker(AppSettings appSettings) database = _cosmosClient.GetDatabase(_appSettings.DetectionsDatabaseName); database.ReadAsync().Wait(); } - catch(Exception exception) + catch (Exception exception) { throw new Exception($"Database '{_appSettings.DetectionsDatabaseName}' was not found or could not be opened: {exception.Message}"); } @@ -30,7 +30,7 @@ public StorageBroker(AppSettings appSettings) _detectionsContainer = database.GetContainer(_appSettings.MetadataContainerName); _detectionsContainer.ReadContainerAsync().Wait(); } - catch(Exception exception) + catch (Exception exception) { throw new Exception($"Container '{_appSettings.MetadataContainerName}' was not found or could not be opened: {exception.Message}."); } diff --git a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Controllers/1HomeController.cs b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Controllers/1HomeController.cs index 41c8efef..e98336c1 100644 --- a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Controllers/1HomeController.cs +++ b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Controllers/1HomeController.cs @@ -10,7 +10,7 @@ public class HomeController : ControllerBase [SwaggerResponse(StatusCodes.Status200OK, "Indicates the API is operational.")] [AllowAnonymous] - [ExcludeFromCodeCoverage ] + [ExcludeFromCodeCoverage] public ActionResult Get() => Ok("Welcome to the OrcaHello API v2.0!"); diff --git a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Controllers/3DetectionsController.cs b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Controllers/3DetectionsController.cs index 8f7d4782..5918d9ef 100644 --- a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Controllers/3DetectionsController.cs +++ b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Controllers/3DetectionsController.cs @@ -53,7 +53,7 @@ public async ValueTask> GetDetectionByIdAsync( [SwaggerResponse(StatusCodes.Status500InternalServerError, "If there is an internal error reading or processing data from the data source.")] [AllowAnonymous] public async ValueTask> GetPaginatedDetectionsForGivenTimeframeAndTagAsync( - [SwaggerParameter("The desired tag(s) (i.e. tag1,tag2 for AND tag1|tag2 for OR).", Required = true)] string tag, + [SwaggerParameter("The desired tag(s) (i.e. tag1,tag2 for AND tag1|tag2 for OR).", Required = true)] string tag, [SwaggerParameter("The start date of the search (MM/DD/YYYY).", Required = true)] DateTime? fromDate, [SwaggerParameter("The end date of the search (MM/DD/YYYY).", Required = true)] DateTime? toDate, [SwaggerParameter("The page in the list to request.", Required = true)] int page, diff --git a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Controllers/7TagsController.cs b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Controllers/7TagsController.cs index 5f3e238d..6bb965d5 100644 --- a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Controllers/7TagsController.cs +++ b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Controllers/7TagsController.cs @@ -53,16 +53,16 @@ public async ValueTask> GetTagsForGive try { var tagListForTimeframeResponse = await _tagOrchestrationService.RetrieveTagsForGivenTimePeriodAsync(fromDate, toDate); - + return Ok(tagListForTimeframeResponse); } - catch(Exception exception) + catch (Exception exception) { if (exception is TagOrchestrationValidationException || exception is TagOrchestrationDependencyValidationException) return BadRequest(ValidatorUtilities.GetInnerMessage(exception)); - if(exception is TagOrchestrationDependencyException || + if (exception is TagOrchestrationDependencyException || exception is TagOrchestrationServiceException) return Problem(exception.Message); diff --git a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Services/Foundations/Hydrophones/HydrophoneService.TryCatchValueTaskT.cs b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Services/Foundations/Hydrophones/HydrophoneService.TryCatchValueTaskT.cs index 065e11f4..030e39fc 100644 --- a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Services/Foundations/Hydrophones/HydrophoneService.TryCatchValueTaskT.cs +++ b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Services/Foundations/Hydrophones/HydrophoneService.TryCatchValueTaskT.cs @@ -20,7 +20,7 @@ protected async ValueTask TryCatch(ReturningGenericFunction returningGe var statusCode = exception1.StatusCode; var innerException = new InvalidHydrophoneException($"Error encountered accessing down range service defined by 'HydrophoneFeedUrl' setting: {exception1.Message}"); - if(statusCode == HttpStatusCode.BadRequest || + if (statusCode == HttpStatusCode.BadRequest || statusCode == HttpStatusCode.NotFound) throw LoggingUtilities.CreateAndLogException(_logger, innerException); diff --git a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Services/Foundations/Hydrophones/HydrophoneService.cs b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Services/Foundations/Hydrophones/HydrophoneService.cs index 80fdf1dc..b5d60881 100644 --- a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Services/Foundations/Hydrophones/HydrophoneService.cs +++ b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Services/Foundations/Hydrophones/HydrophoneService.cs @@ -1,5 +1,5 @@ namespace OrcaHello.Web.Api.Services -{ +{ public partial class HydrophoneService : IHydrophoneService { private readonly IHydrophoneBroker _hydrophoneBroker; diff --git a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Services/Foundations/Metadatas/IMetadataService.cs b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Services/Foundations/Metadatas/IMetadataService.cs index eaee4409..595a7718 100644 --- a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Services/Foundations/Metadatas/IMetadataService.cs +++ b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Services/Foundations/Metadatas/IMetadataService.cs @@ -7,7 +7,7 @@ public interface IMetadataService ValueTask RetrieveTagsForGivenTimePeriodAndModeratorAsync(DateTime fromDate, DateTime toDate, string moderator); ValueTask RetrieveMetadataForGivenTimeframeAndTagAsync(DateTime fromDate, DateTime toDate, string tag, int page, int pageSize); ValueTask RetrievePositiveMetadataForGivenTimeframeAsync(DateTime fromDate, DateTime toDate, int page, int pageSize); - ValueTask RetrievePositiveMetadataForGivenTimeframeAndModeratorAsync(DateTime fromDate, DateTime toDate, + ValueTask RetrievePositiveMetadataForGivenTimeframeAndModeratorAsync(DateTime fromDate, DateTime toDate, string moderator, int page, int pageSize); ValueTask RetrieveNegativeAndUnknownMetadataForGivenTimeframeAsync(DateTime fromDate, DateTime toDate, int page, int pageSize); ValueTask RetrieveNegativeAndUnknownMetadataForGivenTimeframeAndModeratorAsync(DateTime fromDate, DateTime toDate, diff --git a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Services/Foundations/Metadatas/MetadataService.Guards.cs b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Services/Foundations/Metadatas/MetadataService.Guards.cs index 5ad3cd1d..29898d50 100644 --- a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Services/Foundations/Metadatas/MetadataService.Guards.cs +++ b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Services/Foundations/Metadatas/MetadataService.Guards.cs @@ -28,7 +28,7 @@ protected void ValidateMetadataOnCreate(Metadata metadata) // TODO: Are there any other required fields - switch(metadata) + switch (metadata) { case { } when ValidatorUtilities.IsInvalid(metadata.Id): throw new InvalidMetadataException(LoggingUtilities.MissingRequiredProperty(nameof(metadata.Id))); diff --git a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Services/Foundations/Metadatas/MetadataService.cs b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Services/Foundations/Metadatas/MetadataService.cs index ed569060..973727d8 100644 --- a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Services/Foundations/Metadatas/MetadataService.cs +++ b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Services/Foundations/Metadatas/MetadataService.cs @@ -101,7 +101,7 @@ public ValueTask RetrieveUnreviewedMetadataForGiv }); public ValueTask RetrievePaginatedMetadataAsync(string state, DateTime fromDate, DateTime toDate, string sortBy, bool isDescending, string location, int page, int pageSize) => - TryCatch(async() => + TryCatch(async () => { Validate(fromDate, nameof(fromDate)); Validate(toDate, nameof(toDate)); @@ -394,12 +394,12 @@ public ValueTask RetrieveMetadataForGivenTi List tags = new(); string tagOperator = ""; - if(tag.Contains(',')) + if (tag.Contains(',')) { tagOperator = "AND"; tags = tag.Split(',').ToList(); - } - else if(tag.Contains('|')) + } + else if (tag.Contains('|')) { tagOperator = "OR"; tags = tag.Split('|').ToList(); @@ -505,7 +505,7 @@ private static string GetSortField(string sortBy) }; } - [ExcludeFromCodeCoverage] + [ExcludeFromCodeCoverage] private static string GetSortOrder(bool isDescending) { return isDescending ? "DESC" : "ASC"; diff --git a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Services/Orchestrations/Detections/DetectionOrchestrationService.Guards.cs b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Services/Orchestrations/Detections/DetectionOrchestrationService.Guards.cs index 74879b07..5a04f5ac 100644 --- a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Services/Orchestrations/Detections/DetectionOrchestrationService.Guards.cs +++ b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Services/Orchestrations/Detections/DetectionOrchestrationService.Guards.cs @@ -21,15 +21,15 @@ protected void ValidateModerateRequestOnUpdate(ModerateDetectionsRequest request if (request is null) throw new NullModerateDetectionRequestException(); - switch(request) + switch (request) { case { } when request.Ids is null || !request.Ids.Any(): throw new InvalidDetectionOrchestrationException(LoggingUtilities.MissingRequiredProperty(nameof(request.Ids))); - case { } when ValidatorUtilities.IsInvalid(request.State) : + case { } when ValidatorUtilities.IsInvalid(request.State): throw new InvalidDetectionOrchestrationException(LoggingUtilities.MissingRequiredProperty(nameof(request.State))); - case { } when ValidatorUtilities.IsInvalid(request.Moderator) : + case { } when ValidatorUtilities.IsInvalid(request.Moderator): throw new InvalidDetectionOrchestrationException(LoggingUtilities.MissingRequiredProperty(nameof(request.Moderator))); } diff --git a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Services/Orchestrations/Hydrohpones/HydrophoneOrchestrationService.cs b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Services/Orchestrations/Hydrohpones/HydrophoneOrchestrationService.cs index e53a43c0..ead606c7 100644 --- a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Services/Orchestrations/Hydrohpones/HydrophoneOrchestrationService.cs +++ b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Services/Orchestrations/Hydrohpones/HydrophoneOrchestrationService.cs @@ -43,9 +43,9 @@ private static Hydrophone AsHydrophone(HydrophoneData hydrophoneData) IntroHtml = attributes.IntroHtml }; - if(attributes.LocationPoint is not null) + if (attributes.LocationPoint is not null) { - if(attributes.LocationPoint.Coordinates != null && attributes.LocationPoint.Coordinates.Count == 2) + if (attributes.LocationPoint.Coordinates != null && attributes.LocationPoint.Coordinates.Count == 2) { result.Longitude = attributes.LocationPoint.Coordinates[0]; result.Latitude = attributes.LocationPoint.Coordinates[1]; diff --git a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Services/Orchestrations/Moderators/ModeratorOrchestrationService.cs b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Services/Orchestrations/Moderators/ModeratorOrchestrationService.cs index 6dcbfa8d..4327df4e 100644 --- a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Services/Orchestrations/Moderators/ModeratorOrchestrationService.cs +++ b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Services/Orchestrations/Moderators/ModeratorOrchestrationService.cs @@ -104,7 +104,7 @@ public ValueTask RetrieveTagsForGivenTimePeriodAndM Count = candidateRecords.TotalCount, Moderator = moderator }; -}); + }); public ValueTask RetrievePositiveCommentsForGivenTimeframeAndModeratorAsync(DateTime? fromDate, DateTime? toDate, string moderator, int page, int pageSize) => TryCatch(async () => @@ -131,7 +131,7 @@ public ValueTask RetrievePositiveCommentsForGiv TotalCount = results.TotalCount, Count = results.QueryableRecords.Count(), Moderator = moderator, - + }; }); diff --git a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Services/Orchestrations/Tags/TagOrchestrationService.TryCatchValueTaskT.cs b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Services/Orchestrations/Tags/TagOrchestrationService.TryCatchValueTaskT.cs index 722c7437..cecae11f 100644 --- a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Services/Orchestrations/Tags/TagOrchestrationService.TryCatchValueTaskT.cs +++ b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Services/Orchestrations/Tags/TagOrchestrationService.TryCatchValueTaskT.cs @@ -10,12 +10,12 @@ protected async ValueTask TryCatch(ReturningGenericFunction returningGe { return await returningGenericFunction(); } - catch(Exception exception) + catch (Exception exception) { if (exception is InvalidTagOrchestrationException) throw LoggingUtilities.CreateAndLogException(_logger, exception); - if(exception is MetadataValidationException || + if (exception is MetadataValidationException || exception is MetadataDependencyValidationException) throw LoggingUtilities.CreateAndLogException(_logger, exception); diff --git a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Services/Orchestrations/Tags/TagOrchestrationService.cs b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Services/Orchestrations/Tags/TagOrchestrationService.cs index 46536a6d..66e46e32 100644 --- a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Services/Orchestrations/Tags/TagOrchestrationService.cs +++ b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Api/Services/Orchestrations/Tags/TagOrchestrationService.cs @@ -58,7 +58,7 @@ public ValueTask RemoveTagFromAllDetectionsAsync(string tagT int totalRemoved = 0; - foreach(Metadata item in allMetadataWithTag.QueryableRecords) + foreach (Metadata item in allMetadataWithTag.QueryableRecords) { item.Tags.Remove(tagToRemove); diff --git a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Shared/Models/Comments/Comment.cs b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Shared/Models/Comments/Comment.cs index 2d37057c..06894f3b 100644 --- a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Shared/Models/Comments/Comment.cs +++ b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Shared/Models/Comments/Comment.cs @@ -21,7 +21,7 @@ public class Comment [SwaggerSchema("Date and time of when the detection was collected.")] public DateTime Timestamp { get; set; } - + [SwaggerSchema("URI of the detection's audio file (.wav) in blob storage.")] public string AudioUri { get; set; } diff --git a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Shared/Models/Detections/DetectionListResponse.cs b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Shared/Models/Detections/DetectionListResponse.cs index 5edd5a03..a348c325 100644 --- a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Shared/Models/Detections/DetectionListResponse.cs +++ b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Shared/Models/Detections/DetectionListResponse.cs @@ -16,7 +16,7 @@ public class DetectionListForTagResponse : DetectionListResponseBase { [SwaggerSchema("The starting date of the timeframe.")] public DateTime FromDate { get; set; } - + [SwaggerSchema("The ending date of the timeframe.")] public DateTime ToDate { get; set; } diff --git a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Shared/Models/Hydrophones/HydrophoneListResponse.cs b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Shared/Models/Hydrophones/HydrophoneListResponse.cs index 15610f07..c670267a 100644 --- a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Shared/Models/Hydrophones/HydrophoneListResponse.cs +++ b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Shared/Models/Hydrophones/HydrophoneListResponse.cs @@ -6,7 +6,7 @@ public class HydrophoneListResponse { [SwaggerSchema("The list of hydrophones.")] public List Hydrophones { get; set; } = new List(); - + [SwaggerSchema("The total number of hydrophones in the list")] public int Count { get; set; } } diff --git a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Shared/Services/HttpService.cs b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Shared/Services/HttpService.cs index 9c6a3473..e7413429 100644 --- a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Shared/Services/HttpService.cs +++ b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Shared/Services/HttpService.cs @@ -6,7 +6,8 @@ namespace OrcaHello.Web.Shared.Services public class HttpService : IHttpService { private readonly HttpClient _httpClient; - private JsonSerializerOptions _jsonSerializeOptions = new() { + private JsonSerializerOptions _jsonSerializeOptions = new() + { PropertyNameCaseInsensitive = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, }; diff --git a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Shared/Utilities/ValidatorUtilities.cs b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Shared/Utilities/ValidatorUtilities.cs index 68fd5b05..4f0c437c 100644 --- a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Shared/Utilities/ValidatorUtilities.cs +++ b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.Shared/Utilities/ValidatorUtilities.cs @@ -10,7 +10,7 @@ public static class ValidatorUtilities public static bool IsNegative(long input) => input < 0; public static bool IsNegative(int input) => input < 0; public static bool IsZeroOrLess(int input) => input <= 0; - public static bool IsInvalid(Object input) => input == null; + public static bool IsInvalid(object input) => input == null; public static bool IsInvalid(DateTime input) => input == default(DateTime); public static bool IsInvalidGuidString(string input) => !Guid.TryParse(input, out Guid dummy); public static string GetInnerMessage(Exception exception) => exception.InnerException.Message; diff --git a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI.Tests.Unit/Services/DashboardViewServiceTests/Default.RetrieveFilteredTagsAsync.cs b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI.Tests.Unit/Services/DashboardViewServiceTests/Default.RetrieveFilteredTagsAsync.cs index 6dd6e203..65c21880 100644 --- a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI.Tests.Unit/Services/DashboardViewServiceTests/Default.RetrieveFilteredTagsAsync.cs +++ b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI.Tests.Unit/Services/DashboardViewServiceTests/Default.RetrieveFilteredTagsAsync.cs @@ -7,7 +7,7 @@ public async Task Default_Expect_RetrieveFilteredTagsAsync() { TagListForTimeframeResponse expectedResponse = new() { - Tags = new() { "Tag 1", "Tag 2" }, + Tags = new() { "Tag 1", "Tag 2" }, FromDate = DateTime.UtcNow.AddDays(-14), ToDate = DateTime.UtcNow, Count = 2 diff --git a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI.Tests.Unit/Services/DetectionServiceTests/Default.ModerateDetectionsAsync.cs b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI.Tests.Unit/Services/DetectionServiceTests/Default.ModerateDetectionsAsync.cs index 0ad7a9d9..3007d23f 100644 --- a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI.Tests.Unit/Services/DetectionServiceTests/Default.ModerateDetectionsAsync.cs +++ b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI.Tests.Unit/Services/DetectionServiceTests/Default.ModerateDetectionsAsync.cs @@ -28,7 +28,7 @@ public async Task Default_Expect_ModerateDetectionsAsync() { Ids = new() { id }, Moderator = "Moderator", - DateModerated = DateTime.UtcNow, + DateModerated = DateTime.UtcNow, State = "Positive" }; diff --git a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI.Tests.Unit/Services/DetectionServiceTests/Default.RetrieveDetectionAsync.cs b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI.Tests.Unit/Services/DetectionServiceTests/Default.RetrieveDetectionAsync.cs index 989130da..3b7f3982 100644 --- a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI.Tests.Unit/Services/DetectionServiceTests/Default.RetrieveDetectionAsync.cs +++ b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI.Tests.Unit/Services/DetectionServiceTests/Default.RetrieveDetectionAsync.cs @@ -9,8 +9,8 @@ public async Task Default_Expect_RetrieveDetectionAsync() Detection expectedResponse = new() { - Id = id, - Moderator = "John Smith", + Id = id, + Moderator = "John Smith", Tags = new() { "Tag 1" } }; diff --git a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI.Tests.Unit/Services/DetectionViewServiceTests/Default.ModerateDetectionsAsync.cs b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI.Tests.Unit/Services/DetectionViewServiceTests/Default.ModerateDetectionsAsync.cs index 1c67bd22..aab41697 100644 --- a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI.Tests.Unit/Services/DetectionViewServiceTests/Default.ModerateDetectionsAsync.cs +++ b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI.Tests.Unit/Services/DetectionViewServiceTests/Default.ModerateDetectionsAsync.cs @@ -25,7 +25,7 @@ public async Task Default_Expect_ModerateDetectionsAsync() _detectionServiceMock.Verify(service => service.ModerateDetectionsAsync(It.IsAny()), Times.Once); - } + } [TestMethod] public async Task Default_Expect_ModerateDetectionsAsync_EmptyCommentsAndTags() diff --git a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI.Tests.Unit/Services/ModeratorServiceTests/Default.GetFilteredDetectionsForTagAndModeratorAsync.cs b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI.Tests.Unit/Services/ModeratorServiceTests/Default.GetFilteredDetectionsForTagAndModeratorAsync.cs index c67c9cfb..7c7ade1a 100644 --- a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI.Tests.Unit/Services/ModeratorServiceTests/Default.GetFilteredDetectionsForTagAndModeratorAsync.cs +++ b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI.Tests.Unit/Services/ModeratorServiceTests/Default.GetFilteredDetectionsForTagAndModeratorAsync.cs @@ -12,7 +12,7 @@ public async Task Default_Expect_GetFilteredDetectionsForTagAndModeratorAsync() new() { Id = Guid.NewGuid().ToString(), Moderator = "John Smith", Tags = new() { "Tag 1" } } }, Moderator = "John Smith", - Tag = "Tag 1" + Tag = "Tag 1" }; _apiBrokerMock.Setup(broker => diff --git a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI.Tests.Unit/Services/TagViewServiceTests/Default.RetrieveDetectionsByTagsAsync.cs b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI.Tests.Unit/Services/TagViewServiceTests/Default.RetrieveDetectionsByTagsAsync.cs index 7202acb5..0c8d1f1d 100644 --- a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI.Tests.Unit/Services/TagViewServiceTests/Default.RetrieveDetectionsByTagsAsync.cs +++ b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI.Tests.Unit/Services/TagViewServiceTests/Default.RetrieveDetectionsByTagsAsync.cs @@ -9,8 +9,8 @@ public async Task Default_Expect_RetrieveDetectionsByTagsAsync_And() { Detections = new() { - new() { - Id = Guid.NewGuid().ToString(), + new() { + Id = Guid.NewGuid().ToString(), Comments = "These are the comments.", Location = new() { @@ -29,7 +29,7 @@ public async Task Default_Expect_RetrieveDetectionsByTagsAsync_And() PaginatedDetectionsByTagsAndDateRequest request = new() { - Tags = new() { "Tag 1", "Tag 2"}, + Tags = new() { "Tag 1", "Tag 2" }, Logic = LogicalOperator.And, Page = 1, PageSize = 10, diff --git a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI.Tests.Unit/Services/TagViewServiceTests/Guards.cs b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI.Tests.Unit/Services/TagViewServiceTests/Guards.cs index 853b5c37..3417c420 100644 --- a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI.Tests.Unit/Services/TagViewServiceTests/Guards.cs +++ b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI.Tests.Unit/Services/TagViewServiceTests/Guards.cs @@ -6,7 +6,7 @@ public partial class TagViewServiceTests public void Guard_AllGuardConditions_Expect_Exception() { var wrapper = new TagViewServiceWrapper(); - + List badIds = null!; Assert.ThrowsException(() => diff --git a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI/App.razor b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI/App.razor index 80071a72..e70b9772 100644 --- a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI/App.razor +++ b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI/App.razor @@ -1,12 +1,12 @@  - - - - - - -

Sorry, there's nothing at this address.

-
-
-
+ + + + + + +

Sorry, there's nothing at this address.

+
+
+
diff --git a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI/Brokers/DetectionAPIBroker/DetectionAPIBroker.cs b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI/Brokers/DetectionAPIBroker/DetectionAPIBroker.cs index 9b01c409..8c5b5f3d 100644 --- a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI/Brokers/DetectionAPIBroker/DetectionAPIBroker.cs +++ b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI/Brokers/DetectionAPIBroker/DetectionAPIBroker.cs @@ -9,7 +9,7 @@ public partial class DetectionAPIBroker : IDetectionAPIBroker public DetectionAPIBroker(IHttpService apiClient, AppSettings appSettings) { _apiClient = apiClient; - _appSettings = appSettings; + _appSettings = appSettings; } private async ValueTask GetAsync(string relativeUrl) => diff --git a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI/Brokers/DetectionAPIBroker/IDetectionAPIBroker.cs b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI/Brokers/DetectionAPIBroker/IDetectionAPIBroker.cs index fb43de6d..5e9f190c 100644 --- a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI/Brokers/DetectionAPIBroker/IDetectionAPIBroker.cs +++ b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI/Brokers/DetectionAPIBroker/IDetectionAPIBroker.cs @@ -1,5 +1,5 @@ namespace OrcaHello.Web.UI.Brokers -{ +{ public partial interface IDetectionAPIBroker { } diff --git a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI/Models/Configurations/AuthenticationServiceProviders.cs b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI/Models/Configurations/AuthenticationServiceProviders.cs index 91edcb32..55e61acb 100644 --- a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI/Models/Configurations/AuthenticationServiceProviders.cs +++ b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI/Models/Configurations/AuthenticationServiceProviders.cs @@ -20,7 +20,7 @@ public static void ConfigureModeratorPolicy(this WebApplicationBuilder builder, public static void ConfigureAuthProviders(this WebApplicationBuilder builder) { builder.Services.AddScoped(); - builder.Services.AddScoped(provider => + builder.Services.AddScoped(provider => provider.GetRequiredService()); builder.Services.AddScoped(); } diff --git a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI/Models/Requests/ModerateDetectionRequest.cs b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI/Models/Requests/ModerateDetectionRequest.cs index 9fbf9e01..5560b5ef 100644 --- a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI/Models/Requests/ModerateDetectionRequest.cs +++ b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI/Models/Requests/ModerateDetectionRequest.cs @@ -3,7 +3,7 @@ [ExcludeFromCodeCoverage] public class ModerateDetectionRequest { - public string Id { get; set; } = null!; + public string Id { get; set; } = null!; public string State { get; set; } = null!; public string Comments { get; set; } = null!; public string Moderator { get; set; } = null!; diff --git a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI/Models/ViewItems/DetectionItemView.cs b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI/Models/ViewItems/DetectionItemView.cs index d3817cb5..be42ec37 100644 --- a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI/Models/ViewItems/DetectionItemView.cs +++ b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI/Models/ViewItems/DetectionItemView.cs @@ -155,7 +155,7 @@ public string EnteredTags SpectrogramUri = detection.SpectrogramUri, Confidence = detection.Confidence, State = detection.State, - Location = LocationItemView.AsLocationItemView(detection.Location), + Location = LocationItemView.AsLocationItemView(detection.Location), Comments = detection.Comments, Moderator = detection.Moderator, Moderated = detection.Moderated, diff --git a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI/Models/ViewStates/CommentStateView.cs b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI/Models/ViewStates/CommentStateView.cs index cb83c253..df35e75f 100644 --- a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI/Models/ViewStates/CommentStateView.cs +++ b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI/Models/ViewStates/CommentStateView.cs @@ -16,7 +16,7 @@ public void Toggle() { IsExpanded = !IsExpanded; - if(IsExpanded && Items == null) + if (IsExpanded && Items == null) { Items = new(); Page = 1; diff --git a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI/Pages/Components/AuthenticationComponent.razor b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI/Pages/Components/AuthenticationComponent.razor index ccbc2ac9..dffece29 100644 --- a/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI/Pages/Components/AuthenticationComponent.razor +++ b/ModeratorFrontEnd/OrcaHello/OrcaHello.Web.UI/Pages/Components/AuthenticationComponent.razor @@ -1,7 +1,7 @@ @inherits ComponentManager - +