Skip to content

Commit

Permalink
Add inspection view on mission page
Browse files Browse the repository at this point in the history
  • Loading branch information
mrica-equinor committed Dec 13, 2024
1 parent 008ae6a commit be7d191
Show file tree
Hide file tree
Showing 18 changed files with 714 additions and 66 deletions.
59 changes: 57 additions & 2 deletions backend/api/Controllers/InspectionController.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using Api.Controllers.Models;
using System.Globalization;
using Api.Controllers.Models;
using Api.Database.Models;

using Api.Services;
using Api.Services.MissionLoaders;
using Api.Services.Models;
Expand All @@ -11,7 +14,8 @@ namespace Api.Controllers
[Route("inspection")]
public class InspectionController(
ILogger<InspectionController> logger,
IEchoService echoService
IEchoService echoService,
IInspectionService inspectionService
) : ControllerBase
{
/// <summary>
Expand Down Expand Up @@ -49,5 +53,56 @@ public async Task<ActionResult<TagInspectionMetadata>> Create([FromRoute] string
throw;
}
}

/// <summary>
/// Lookup the inspection image for task with specified isarTaskId
/// </summary>
/// <remarks>
/// </remarks>
[HttpGet]
[Authorize(Roles = Role.User)]
[Route("{installationCode}/{taskId}/taskId")]
[ProducesResponseType(typeof(Inspection), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status409Conflict)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async Task<ActionResult<Inspection>> GetInspectionImageById([FromRoute] string installationCode, string taskId)
{
Inspection? inspection;
try
{
inspection = await inspectionService.ReadByIsarTaskId(taskId, readOnly: true);
if (inspection == null) return NotFound($"Could not find inspection for task with Id {taskId}.");

}
catch (Exception e)
{
logger.LogError(e, $"Error while finding an inspection with task Id {taskId}");
return StatusCode(StatusCodes.Status500InternalServerError);
}

if (inspection.Id == null) return NotFound($"Could not find Id for Inspection with task ID {taskId}.");

var inspectionData = await inspectionService.GetInspectionStorageInfo(inspection.Id);

if (inspectionData == null) return NotFound($"Could not find inspection data for inspection with Id {inspection.Id}.");

if (!inspectionData.BlobContainer.ToLower(CultureInfo.CurrentCulture).Equals(installationCode.ToLower(CultureInfo.CurrentCulture), StringComparison.Ordinal))
{
return NotFound($"Could not find inspection data for inspection with Id {inspection.Id} because blob name {inspectionData.BlobName} does not match installation {installationCode}.");
}

try
{
byte[] inspectionStream = await inspectionService.FetchInpectionImage(inspectionData.BlobName, inspectionData.BlobContainer, inspectionData.StorageAccount);
return File(inspectionStream, "image/png");
}
catch (Azure.RequestFailedException)
{
return NotFound($"Could not find inspection blob {inspectionData.BlobName} in container {inspectionData.BlobContainer} and storage account {inspectionData.StorageAccount}.");
}
}
}
}
2 changes: 2 additions & 0 deletions backend/api/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@
builder.Services.Configure<AzureAdOptions>(builder.Configuration.GetSection("AzureAd"));
builder.Services.Configure<MapBlobOptions>(builder.Configuration.GetSection("Maps"));


builder.Services
.AddControllers()
.AddJsonOptions(
Expand All @@ -142,6 +143,7 @@
.AddInMemoryTokenCaches()
.AddDownstreamApi(EchoService.ServiceName, builder.Configuration.GetSection("Echo"))
.AddDownstreamApi(StidService.ServiceName, builder.Configuration.GetSection("Stid"))
.AddDownstreamApi(InspectionService.ServiceName, builder.Configuration.GetSection("IDA"))
.AddDownstreamApi(IsarService.ServiceName, builder.Configuration.GetSection("Isar"));

builder.Services.AddAuthorizationBuilder().AddFallbackPolicy(
Expand Down
54 changes: 53 additions & 1 deletion backend/api/Services/InspectionService.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Net;
using System.Text.Json;
using Api.Controllers.Models;
using Api.Database.Context;
using Api.Database.Models;
using Api.Services.Models;
using Api.Utilities;
using Microsoft.EntityFrameworkCore;
using Microsoft.Identity.Abstractions;
namespace Api.Services
{
public interface IInspectionService
{
public Task<byte[]> FetchInpectionImage(string inpectionName, string installationCode, string storageAccount);
public Task<Inspection> UpdateInspectionStatus(string isarTaskId, IsarTaskStatus isarTaskStatus);
public Task<Inspection?> ReadByIsarTaskId(string id, bool readOnly = true);
public Task<Inspection?> AddFinding(InspectionFindingQuery inspectionFindingsQuery, string isarTaskId);
public Task<IDAInspectionDataResponse?> GetInspectionStorageInfo(string inspectionId);

}

Expand All @@ -21,8 +26,16 @@ public interface IInspectionService
"CA1309:Use ordinal StringComparison",
Justification = "EF Core refrains from translating string comparison overloads to SQL"
)]
public class InspectionService(FlotillaDbContext context, ILogger<InspectionService> logger, IAccessRoleService accessRoleService) : IInspectionService
public class InspectionService(FlotillaDbContext context, ILogger<InspectionService> logger, IDownstreamApi idaApi, IAccessRoleService accessRoleService,
IBlobService blobService) : IInspectionService
{
public const string ServiceName = "IDA";

public async Task<byte[]> FetchInpectionImage(string inpectionName, string installationCode, string storageAccount)
{
return await blobService.DownloadBlob(inpectionName, installationCode, storageAccount);
}

public async Task<Inspection> UpdateInspectionStatus(string isarTaskId, IsarTaskStatus isarTaskStatus)
{
var inspection = await ReadByIsarTaskId(isarTaskId, readOnly: false);
Expand Down Expand Up @@ -96,5 +109,44 @@ private IQueryable<Inspection> GetInspections(bool readOnly = true)
inspection = await Update(inspection);
return inspection;
}

public async Task<IDAInspectionDataResponse?> GetInspectionStorageInfo(string inspectionId)
{
string relativePath = $"InspectionData/{inspectionId}/inspection-data-storage-location";

var response = await idaApi.CallApiForUserAsync(
ServiceName,
options =>
{
options.HttpMethod = HttpMethod.Get.Method;
options.RelativePath = relativePath;
}
);


if (response.StatusCode == HttpStatusCode.Accepted)
{
logger.LogInformation("Inspection data storage location for inspection with Id {inspectionId} is not yet available", inspectionId);
return null;
}

if (response.StatusCode == HttpStatusCode.InternalServerError)
{
logger.LogError("Inetrnal server error when trying to get inspection data for inspection with Id {inspectionId}", inspectionId);
return null;
}

if (response.StatusCode == HttpStatusCode.NotFound)
{
logger.LogError("Could not find inspection data for inspection with Id {inspectionId}", inspectionId);
return null;
}

var inspectionData = await response.Content.ReadFromJsonAsync<
IDAInspectionDataResponse
>() ?? throw new JsonException("Failed to deserialize inspection data from IDA.");

return inspectionData;
}
}
}
17 changes: 17 additions & 0 deletions backend/api/Services/Models/IDAInspectionDataResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System.Text.Json.Serialization;

namespace Api.Services.Models
{
public class IDAInspectionDataResponse
{
[JsonPropertyName("storageAccount")]
public required string StorageAccount { get; set; }

[JsonPropertyName("blobContainer")]
public required string BlobContainer { get; set; }

[JsonPropertyName("blobName")]
public required string BlobName { get; set; }

}
}
4 changes: 4 additions & 0 deletions backend/api/appsettings.Development.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
"Isar": {
"Scopes": ["fd384acd-5c1b-4c44-a1ac-d41d720ed0fe/.default"]
},
"IDA": {
"BaseUrl": "http://ida.robotics-analytics-dev.svc.cluster.local:8100/",
"Scopes": ["bd4b0a3e-af88-4b7c-aab2-ad4956f2f789/.default"]
},
"Maps": {
"StorageAccount": "flotillamaps"
},
Expand Down
8 changes: 5 additions & 3 deletions backend/api/appsettings.Local.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@
"VaultUri": "https://flotilladevkv.vault.azure.net/"
},
"Isar": {
"Scopes": [
"fd384acd-5c1b-4c44-a1ac-d41d720ed0fe/.default"
]
"Scopes": ["fd384acd-5c1b-4c44-a1ac-d41d720ed0fe/.default"]
},
"Maps": {
"StorageAccount": "flotillamaps"
},
"IDA": {
"BaseUrl": "https://localhost:8100/",
"Scopes": ["bd4b0a3e-af88-4b7c-aab2-ad4956f2f789/.default"]
},
"AllowedHosts": "*",
"AllowedOrigins": [
"https://*.equinor.com/",
Expand Down
4 changes: 4 additions & 0 deletions backend/api/appsettings.Production.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
"Isar": {
"Scopes": ["e08edece-ba2d-4fe1-8cd1-ee7b05ba7155/.default"]
},
"IDA": {
"BaseUrl": "http://ida.robotics-analytics-prod.svc.cluster.local:8100/",
"Scopes": ["16df0336-e42b-45c6-a380-8f6fe66e1fa3/.default"]
},
"Maps": {
"StorageAccount": "flotillamaps"
},
Expand Down
4 changes: 4 additions & 0 deletions backend/api/appsettings.Staging.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
"Isar": {
"Scopes": ["9cd787ea-8ce2-4d18-8bc8-279e7a8e6289/.default"]
},
"IDA": {
"BaseUrl": "http://ida.robotics-analytics-staging.svc.cluster.local:8100/",
"Scopes": ["6f40ba9b-2029-400e-85e9-f1922cbf12c1/.default"]
},
"Maps": {
"StorageAccount": "flotillamaps"
},
Expand Down
12 changes: 3 additions & 9 deletions backend/api/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,11 @@
},
"Echo": {
"BaseUrl": "https://echohubapi.equinor.com/api",
"Scopes": [
"bf0b2569-e09c-42f0-8095-5a52a873eb7b/.default"
]
"Scopes": ["bf0b2569-e09c-42f0-8095-5a52a873eb7b/.default"]
},
"Stid": {
"BaseUrl": "https://stidapi.equinor.com/",
"Scopes": [
"1734406c-3449-4192-a50d-7c3a63d3f57d/.default"
]
"Scopes": ["1734406c-3449-4192-a50d-7c3a63d3f57d/.default"]
},
"IsarConnectionTimeout": 10,
"Logging": {
Expand All @@ -29,9 +25,7 @@
}
},
"AllowedHosts": "*",
"AllowedOrigins": [
"https://*.equinor.com/"
],
"AllowedOrigins": ["https://*.equinor.com/"],
"Database": {
"ConnectionString": "",
"InitializeInMemDb": false
Expand Down
55 changes: 29 additions & 26 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { config } from 'config'
import { MissionDefinitionsProvider } from 'components/Contexts/MissionDefinitionsContext'
import { MediaStreamProvider } from 'components/Contexts/MediaStreamContext'
import { DockProvider } from 'components/Contexts/DockContext'
import { InspectionsProvider } from 'components/Contexts/InpectionsContext'

const appInsights = new ApplicationInsights({
config: {
Expand All @@ -32,32 +33,34 @@ const App = () => (
<LanguageProvider>
<SignalRProvider>
<InstallationProvider>
<MissionDefinitionsProvider>
<RobotProvider>
<MissionRunsProvider>
<AlertProvider>
<DockProvider>
<MissionRunsProvider>
<MissionControlProvider>
<UnauthenticatedTemplate>
<div className="sign-in-page">
<AssetSelectionPage></AssetSelectionPage>
</div>
</UnauthenticatedTemplate>
<AuthenticatedTemplate>
<MissionFilterProvider>
<MediaStreamProvider>
<FlotillaSite />
</MediaStreamProvider>
</MissionFilterProvider>
</AuthenticatedTemplate>
</MissionControlProvider>
</MissionRunsProvider>
</DockProvider>
</AlertProvider>
</MissionRunsProvider>
</RobotProvider>
</MissionDefinitionsProvider>
<InspectionsProvider>
<MissionDefinitionsProvider>
<RobotProvider>
<MissionRunsProvider>
<AlertProvider>
<DockProvider>
<MissionRunsProvider>
<MissionControlProvider>
<UnauthenticatedTemplate>
<div className="sign-in-page">
<AssetSelectionPage></AssetSelectionPage>
</div>
</UnauthenticatedTemplate>
<AuthenticatedTemplate>
<MissionFilterProvider>
<MediaStreamProvider>
<FlotillaSite />
</MediaStreamProvider>
</MissionFilterProvider>
</AuthenticatedTemplate>
</MissionControlProvider>
</MissionRunsProvider>
</DockProvider>
</AlertProvider>
</MissionRunsProvider>
</RobotProvider>
</MissionDefinitionsProvider>
</InspectionsProvider>
</InstallationProvider>
</SignalRProvider>
</LanguageProvider>
Expand Down
8 changes: 8 additions & 0 deletions frontend/src/api/ApiCaller.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -430,4 +430,12 @@ export class BackendAPICaller {
})
return result.content
}

static async getInspection(installationCode: string, taskId: string): Promise<Blob> {
const path: string = 'inspection/' + installationCode + '/' + taskId + '/taskId'

return BackendAPICaller.GET<Blob>(path, 'image/png')
.then((response) => response.content)
.catch(BackendAPICaller.handleError('GET', path))
}
}
Loading

0 comments on commit be7d191

Please sign in to comment.