Skip to content

Commit 61f7060

Browse files
committed
Fetch inspection record data as sas URL
1 parent c742433 commit 61f7060

6 files changed

Lines changed: 125 additions & 13 deletions

File tree

api/Controllers/InspectionRecordController.cs

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ namespace api.Controllers;
1313
public class InspectionRecordController(
1414
ILogger<InspectionRecordController> logger,
1515
IInspectionRecordService inspectionRecordService,
16-
IThermalImageService thermalImageService
16+
IThermalImageService thermalImageService,
17+
IBlobStorageService blobStorageService
1718
) : ControllerBase
1819
{
1920
// Workflow types whose output forms the visualization base layer for an
@@ -33,17 +34,23 @@ IThermalImageService thermalImageService
3334
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
3435
[ProducesResponseType(StatusCodes.Status403Forbidden)]
3536
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
36-
public async Task<ActionResult<PagedResponse<InspectionRecord>>> GetAll(
37+
public async Task<ActionResult<PagedResponse<InspectionRecordDto>>> GetAll(
3738
[FromQuery] InspectionRecordParameters parameters
3839
)
3940
{
4041
try
4142
{
4243
var page = await inspectionRecordService.GetInspectionRecords(parameters);
44+
45+
var pageDtos = page.Select(
46+
(record) => new InspectionRecordDto(record, blobStorageService)
47+
)
48+
.ToList();
49+
4350
return Ok(
44-
new PagedResponse<InspectionRecord>
51+
new PagedResponse<InspectionRecordDto>
4552
{
46-
Items = page,
53+
Items = pageDtos,
4754
PageNumber = page.CurrentPage,
4855
PageSize = page.PageSize,
4956
TotalCount = page.TotalCount,
@@ -66,7 +73,7 @@ [FromQuery] InspectionRecordParameters parameters
6673
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
6774
[ProducesResponseType(StatusCodes.Status403Forbidden)]
6875
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
69-
public async Task<ActionResult<InspectionRecord>> GetById([FromRoute] Guid id)
76+
public async Task<ActionResult<InspectionRecordDto>> GetById([FromRoute] Guid id)
7077
{
7178
try
7279
{
@@ -75,7 +82,8 @@ public async Task<ActionResult<InspectionRecord>> GetById([FromRoute] Guid id)
7582
{
7683
return NotFound($"Could not find inspection record with id {id}");
7784
}
78-
return Ok(record);
85+
var recordDto = new InspectionRecordDto(record, blobStorageService);
86+
return Ok(recordDto);
7987
}
8088
catch (Exception e)
8189
{
@@ -92,7 +100,7 @@ public async Task<ActionResult<InspectionRecord>> GetById([FromRoute] Guid id)
92100
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
93101
[ProducesResponseType(StatusCodes.Status403Forbidden)]
94102
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
95-
public async Task<ActionResult<InspectionRecord>> GetByInspectionId(
103+
public async Task<ActionResult<InspectionRecordDto>> GetByInspectionId(
96104
[FromRoute] string inspectionId
97105
)
98106
{
@@ -106,7 +114,8 @@ [FromRoute] string inspectionId
106114
$"Could not find inspection record with inspection id {inspectionId}"
107115
);
108116
}
109-
return Ok(record);
117+
var recordDto = new InspectionRecordDto(record, blobStorageService);
118+
return Ok(recordDto);
110119
}
111120
catch (Exception e)
112121
{
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#pragma warning disable CS8618
2+
using api.Database.Models;
3+
using api.Services;
4+
5+
namespace api.Controllers.Models;
6+
7+
public class AnalysisDto
8+
{
9+
public AnalysisDto(Analysis analysis, IBlobStorageService blobService)
10+
{
11+
this.Id = analysis.Id;
12+
this.Name = analysis.Name;
13+
this.CreatedAt = analysis.CreatedAt;
14+
15+
var workflows = analysis.Runs.SelectMany(r => r.Workflows);
16+
17+
var anonymizedWorkflow = workflows
18+
.Where(w => w.WorkflowType.Equals("anonymizer", StringComparison.OrdinalIgnoreCase))
19+
.OrderByDescending(w => w.CompletedAt ?? w.StartedAt ?? DateTime.MinValue)
20+
.FirstOrDefault();
21+
if (anonymizedWorkflow != null && anonymizedWorkflow.OutputBlobStorageLocation != null)
22+
this.AnonymizedSAS = blobService
23+
.CreateUserDelegationSASUri(anonymizedWorkflow.OutputBlobStorageLocation)
24+
.Result;
25+
26+
var visualizedWorkflow = workflows
27+
.Where(w => !w.WorkflowType.Equals("anonymizer", StringComparison.OrdinalIgnoreCase))
28+
.OrderByDescending(w => w.CompletedAt ?? w.StartedAt ?? DateTime.MinValue)
29+
.FirstOrDefault();
30+
if (visualizedWorkflow != null && visualizedWorkflow.OutputBlobStorageLocation != null)
31+
this.VisualizedSAS = blobService
32+
.CreateUserDelegationSASUri(visualizedWorkflow.OutputBlobStorageLocation)
33+
.Result;
34+
}
35+
36+
public Guid Id { get; set; }
37+
38+
public string Name { get; set; }
39+
40+
public DateTime CreatedAt { get; set; }
41+
42+
public Uri? AnonymizedSAS { get; set; }
43+
44+
public Uri? VisualizedSAS { get; set; }
45+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using api.Database.Models;
2+
using api.Services;
3+
4+
namespace api.Controllers.Models;
5+
6+
public class InspectionRecordDto(InspectionRecord record, IBlobStorageService blobService)
7+
{
8+
public Guid Id { get; set; } = record.Id;
9+
public string InspectionId { get; set; } = record.InspectionId;
10+
public string InstallationCode { get; set; } = record.InstallationCode;
11+
public Uri SASToken { get; set; } =
12+
blobService.CreateUserDelegationSASUri(record.BlobStorageLocation).Result;
13+
public DateTime CreatedAt { get; set; } = record.CreatedAt;
14+
public string? InspectionType { get; set; } = record.InspectionType;
15+
public string? Tag { get; set; } = record.Tag;
16+
public Position? TargetPosition { get; set; } = record.TargetPosition;
17+
public Pose? RobotPose { get; set; } = record.RobotPose;
18+
public List<AnalysisDto> Analyses { get; set; } =
19+
[.. record.Analyses.Select((a) => new AnalysisDto(a, blobService))];
20+
public string? InspectionDescription { get; set; } = record.InspectionDescription;
21+
public string? RobotName { get; set; } = record.RobotName;
22+
public DateTime? Timestamp { get; set; } = record.Timestamp;
23+
public Guid? AnalysisGroupId { get; set; } = record.AnalysisGroupId;
24+
}

api/Services/BlobStorageService.cs

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,16 @@
22
using Azure.Core;
33
using Azure.Storage.Blobs;
44
using Azure.Storage.Blobs.Models;
5+
using Azure.Storage.Sas;
56

67
namespace api.Services;
78

89
public interface IBlobStorageService
910
{
1011
Task<MemoryStream> DownloadBlobAsync(BlobStorageLocation location);
11-
1212
Task UploadBlobAsync(BlobStorageLocation destination, Stream content, string contentType);
13-
1413
Task CopyBlobAsync(BlobStorageLocation source, BlobStorageLocation destination);
14+
Task<Uri> CreateUserDelegationSASUri(BlobStorageLocation location);
1515
}
1616

1717
public class BlobStorageService(TokenCredential credential, IConfiguration configuration)
@@ -97,4 +97,34 @@ private BlobServiceClient CreateBlobServiceClient(string accountName)
9797
credential
9898
);
9999
}
100+
101+
public async Task<Uri> CreateUserDelegationSASUri(BlobStorageLocation location)
102+
{
103+
var serviceClient = CreateBlobServiceClient(location.StorageAccount);
104+
105+
var expiryTime = DateTimeOffset.UtcNow.AddHours(1); // Valid for 1 hour
106+
107+
var userDelegationKey = await serviceClient.GetUserDelegationKeyAsync(
108+
DateTimeOffset.UtcNow,
109+
expiryTime
110+
);
111+
112+
BlobSasBuilder sasBuilder = new()
113+
{
114+
BlobContainerName = location.BlobContainer,
115+
BlobName = location.BlobName,
116+
Resource = "b",
117+
StartsOn = DateTimeOffset.UtcNow,
118+
ExpiresOn = expiryTime,
119+
};
120+
121+
sasBuilder.SetPermissions(BlobSasPermissions.Read);
122+
123+
var sas = sasBuilder
124+
.ToSasQueryParameters(userDelegationKey, location.StorageAccount)
125+
.ToString();
126+
return new Uri(
127+
$"https://{location.StorageAccount}.blob.core.windows.net/{location.BlobContainer}/{location.BlobName}?{sas}"
128+
);
129+
}
100130
}

frontend/src/api/client.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ export interface InspectionRecord {
9999
id: string;
100100
inspectionId: string;
101101
installationCode: string;
102-
blobStorageLocation: BlobStorageLocation;
102+
sasToken: string;
103103
createdAt: string;
104104
inspectionType?: string | null;
105105
tag?: string | null;

frontend/src/pages/inspection-records/detail.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import {
88
} from "@equinor/eds-core-react";
99
import { arrow_back } from "@equinor/eds-icons";
1010
import { getInspectionRecord, type InspectionRecord, type Orientation, type Position } from "../../api/client";
11-
import BlobLocation from "../../components/BlobLocation";
1211
import StatusChip from "../../components/StatusChip";
1312

1413
Icon.add({ arrow_back });
@@ -94,7 +93,12 @@ export default function InspectionRecordDetailPage() {
9493
<Table.Row>
9594
<Table.Cell>Blob</Table.Cell>
9695
<Table.Cell>
97-
<BlobLocation loc={record.blobStorageLocation} />
96+
{
97+
record.sasToken ? (
98+
<Typography link href={record.sasToken}>
99+
Link
100+
</Typography>) : "-"
101+
}
98102
</Table.Cell>
99103
</Table.Row>
100104
<Table.Row>

0 commit comments

Comments
 (0)