This repository was archived by the owner on Jul 28, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
feat: transform function skeleton and blob download functionality #14
Merged
Merged
Changes from 6 commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
40b0016
WIP: DTOSS-8925 file transform skeleton
ianfnelson 200229c
feat: Started implementing File Transform Function
alex-clayton-1 c90818c
feat: Transform function downloads file from blob storage
alex-clayton-1 56588d7
test: Started writing tests for FileTransformFunction
alex-clayton-1 2fe6582
test: Added extra test for downloading blob
alex-clayton-1 1318ab6
refactor: Extracted some code into a separate method
alex-clayton-1 770fce3
feat: Added AppConfiguration to FileTransformFunction
alex-clayton-1 9f3a883
Merge branch 'main' into feat/DTOSS-8925-transform-skeleton
alex-clayton-1 d33d146
Merge branch 'main' into feat/DTOSS-8925-transform-skeleton
alex-clayton-1 e491781
fix: Fixed namespace errors
alex-clayton-1 e0c6a73
clarified a TODO to be imperative
ianfnelson File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
12 changes: 12 additions & 0 deletions
12
src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/FileParser.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Models; | ||
|
|
||
| namespace ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents; | ||
|
|
||
| public class FileParser : IFileParser | ||
| { | ||
| public ParsedFile Parse(Stream stream) | ||
| { | ||
| // TODO - implement this | ||
| throw new NotImplementedException(); | ||
| } | ||
| } |
35 changes: 35 additions & 0 deletions
35
src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/FileTransformer.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation; | ||
| using ServiceLayer.Mesh.Models; | ||
|
|
||
| namespace ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents; | ||
|
|
||
| // TODO - NBSS appointment file specific implementation of IFileTransformer. To orchestrate parsing, validation and staging of data (delegated to separate classes) | ||
| public class FileTransformer : IFileTransformer | ||
| { | ||
| private readonly IFileParser _fileParser; | ||
| private readonly IValidationRunner _validationRunner; | ||
| private readonly IStagingPersister _stagingPersister; | ||
|
|
||
| public FileTransformer(IFileParser fileParser, IValidationRunner validationRunner, IStagingPersister stagingPersister) | ||
| { | ||
| _fileParser = fileParser; | ||
| _validationRunner = validationRunner; | ||
| _stagingPersister = stagingPersister; | ||
| } | ||
|
|
||
| public MeshFileType HandlesFileType => MeshFileType.NbssAppointmentEvents; | ||
|
|
||
| public async Task<IList<ValidationError>> TransformFileAsync(Stream stream, MeshFile metaData) | ||
| { | ||
| // TODO - consider whether we should wrap this parsing in a try-catch and return a List<ValidationError> in case of any unforeseen parsing issues (file is totally unlike anything we expect) | ||
| var parsed = _fileParser.Parse(stream); | ||
|
|
||
| var validationErrors = _validationRunner.Validate(parsed); | ||
| if (!validationErrors.Any()) | ||
| { | ||
| await _stagingPersister.WriteStagedData(parsed); | ||
| } | ||
|
|
||
| return validationErrors; | ||
| } | ||
| } |
8 changes: 8 additions & 0 deletions
8
src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/IFileParser.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Models; | ||
|
|
||
| namespace ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents; | ||
|
|
||
| public interface IFileParser | ||
| { | ||
| ParsedFile Parse(Stream stream); | ||
| } |
9 changes: 9 additions & 0 deletions
9
src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/IStagingPersister.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Models; | ||
|
|
||
| namespace ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents; | ||
|
|
||
| // TODO - interface for class to take validated AppointmentEventsFile and save the records to NbssAppointmentEvents table | ||
| public interface IStagingPersister | ||
| { | ||
| Task WriteStagedData(ParsedFile parsedFile); | ||
| } |
14 changes: 14 additions & 0 deletions
14
src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Models/FileControlRecord.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| namespace ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Models; | ||
|
|
||
| public class FileControlRecord | ||
| { | ||
| public string? RecordTypeIdentifier { get; set; } | ||
|
|
||
| public string? ExtractId { get; set; } | ||
|
|
||
| public string? TransferStartDate { get; set; } | ||
|
|
||
| public string? TransferStartTime { get; set; } | ||
|
|
||
| public string? RecordCount { get; set; } | ||
| } |
9 changes: 9 additions & 0 deletions
9
src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Models/FileDataRecord.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| namespace ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Models; | ||
|
|
||
| public class FileDataRecord | ||
| { | ||
| public int RowNumber { get; set; } | ||
| public Dictionary<string, string> Fields { get; } = []; | ||
|
|
||
| public string? this[string fieldName] => Fields.GetValueOrDefault(fieldName); | ||
| } |
9 changes: 9 additions & 0 deletions
9
src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Models/ParsedFile.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| namespace ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Models; | ||
|
|
||
| public class ParsedFile | ||
| { | ||
| public FileControlRecord? FileHeader { get; set; } | ||
| public FileControlRecord? FileTrailer { get; set; } | ||
| public required List<string> ColumnHeadings { get; set; } = []; | ||
| public required List<FileDataRecord> DataRecords { get; set; } = []; | ||
| } |
13 changes: 13 additions & 0 deletions
13
src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/StagingPersister.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Models; | ||
|
|
||
| namespace ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents; | ||
|
|
||
| // TODO - class to take validated AppointmentEventsFile and save the records to NbssAppointmentEvents table | ||
| public class StagingPersister : IStagingPersister | ||
| { | ||
| public Task WriteStagedData(ParsedFile parsedFile) | ||
| { | ||
| // TODO - implement this | ||
| throw new NotImplementedException(); | ||
| } | ||
| } |
9 changes: 9 additions & 0 deletions
9
src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/IFileValidator.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Models; | ||
|
|
||
| namespace ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation; | ||
|
|
||
| // TODO - create a whole bunch of implementations of this to perform the validation against NBSS Appointment events files | ||
| public interface IFileValidator | ||
| { | ||
| IEnumerable<ValidationError> Validate(ParsedFile file); | ||
| } |
9 changes: 9 additions & 0 deletions
9
src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/IRecordValidator.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Models; | ||
|
|
||
| namespace ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation; | ||
|
|
||
| // TODO - create a whole bunch of implementations of this to perform the validation against NBSS Appointment events records | ||
| public interface IRecordValidator | ||
| { | ||
| IEnumerable<ValidationError> Validate(FileDataRecord fileDataRecord); | ||
| } |
10 changes: 10 additions & 0 deletions
10
src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/IValidationRunner.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Models; | ||
|
|
||
| namespace ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation; | ||
|
|
||
| public interface IValidationRunner | ||
| { | ||
| IList<ValidationError> Validate(ParsedFile file); | ||
| } | ||
|
|
||
|
|
29 changes: 29 additions & 0 deletions
29
src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/ValidationRunner.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Models; | ||
|
|
||
| namespace ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation; | ||
|
|
||
| public class ValidationRunner( | ||
| IEnumerable<IFileValidator> fileValidators, | ||
| IEnumerable<IRecordValidator> recordValidators) | ||
| : IValidationRunner | ||
| { | ||
| public IList<ValidationError> Validate(ParsedFile file) | ||
| { | ||
| var errors = new List<ValidationError>(); | ||
|
|
||
| foreach (var dataRecord in file.DataRecords) | ||
| { | ||
| foreach (var recordValidator in recordValidators) | ||
| { | ||
| errors.AddRange(recordValidator.Validate(dataRecord)); | ||
| } | ||
| } | ||
|
|
||
| foreach (var validator in fileValidators) | ||
| { | ||
| errors.AddRange(validator.Validate(file)); | ||
| } | ||
|
|
||
| return errors; | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,50 @@ | ||
| using Microsoft.Azure.Functions.Worker; | ||
| using Microsoft.EntityFrameworkCore; | ||
| using Microsoft.Extensions.Logging; | ||
| using ServiceLayer.Mesh.Data; | ||
| using ServiceLayer.Mesh.Messaging; | ||
| using ServiceLayer.Mesh.Models; | ||
| using ServiceLayer.Mesh.Storage; | ||
|
|
||
| namespace ServiceLayer.Mesh.Functions; | ||
|
|
||
| public class FileTransformFunction | ||
| public class FileTransformFunction( | ||
| ILogger<FileTransformFunction> logger, | ||
| ServiceLayerDbContext serviceLayerDbContext, | ||
| IMeshFilesBlobStore meshFileBlobStore) | ||
| { | ||
| [Function("FileTransformFunction")] | ||
| public async Task Run([QueueTrigger("%FileTransformQueueName%")] FileTransformQueueMessage message) | ||
| { | ||
| await using var transaction = await serviceLayerDbContext.Database.BeginTransactionAsync(); | ||
|
|
||
| var file = await serviceLayerDbContext.MeshFiles.FirstOrDefaultAsync(f => f.FileId == message.FileId); | ||
|
|
||
| if (file == null) | ||
| { | ||
| logger.LogWarning("File with id: {fileId} not found in MeshFiles table.", message.FileId); | ||
| return; | ||
| } | ||
|
|
||
| if (file.Status != MeshFileStatus.Extracted) | ||
| { | ||
| logger.LogWarning("File with id: {fileId} found in MeshFiles table but is not suitable for transformation. Status: {status}", message.FileId, file.Status); | ||
| return; | ||
| } | ||
|
|
||
| await UpdateFileStatusForTransformation(file); | ||
| await transaction.CommitAsync(); | ||
|
|
||
| var fileContent = await meshFileBlobStore.DownloadAsync(file); | ||
|
|
||
| // TODO - take dependency on IEnumerable<IFileTransformer>. | ||
| // After initial common checks against database, find the appropriate implementation of IFileTransformer to handle the functionality that differs between file type. | ||
| } | ||
|
|
||
| private async Task UpdateFileStatusForTransformation(MeshFile file) | ||
| { | ||
| file.Status = MeshFileStatus.Transforming; | ||
| file.LastUpdatedUtc = DateTime.UtcNow; | ||
| await serviceLayerDbContext.SaveChangesAsync(); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| using ServiceLayer.Mesh.Models; | ||
|
|
||
| namespace ServiceLayer.Mesh; | ||
|
|
||
| public interface IFileTransformer | ||
| { | ||
| MeshFileType HandlesFileType { get; } | ||
| Task<IList<ValidationError>> TransformFileAsync(Stream stream, MeshFile metaData); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| namespace ServiceLayer.Mesh; | ||
|
|
||
| public class ValidationError | ||
| { | ||
| public int? RowNumber { get; set; } | ||
|
|
||
| public required string Field { get; set; } | ||
|
|
||
| public required string Code { get; set; } | ||
|
|
||
| public required string Error { get; set; } | ||
| } |
126 changes: 126 additions & 0 deletions
126
tests/ServiceLayer.Mesh.Tests/Functions/FileTransformFunctionTests.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,126 @@ | ||
| using Microsoft.EntityFrameworkCore; | ||
| using Microsoft.Extensions.Logging; | ||
| using Moq; | ||
| using ServiceLayer.Mesh.Data; | ||
| using ServiceLayer.Mesh.Functions; | ||
| using ServiceLayer.Mesh.Messaging; | ||
| using ServiceLayer.Mesh.Models; | ||
| using ServiceLayer.Mesh.Storage; | ||
|
|
||
| namespace ServiceLayer.Mesh.Tests.Functions; | ||
|
|
||
| public class FileTransformFunctionTests | ||
| { | ||
| private readonly Mock<ILogger<FileTransformFunction>> _loggerMock; | ||
| private readonly Mock<IMeshFilesBlobStore> _blobStoreMock; | ||
| private readonly ServiceLayerDbContext _dbContext; | ||
| private readonly FileTransformFunction _function; | ||
|
|
||
| public FileTransformFunctionTests() | ||
| { | ||
| _loggerMock = new Mock<ILogger<FileTransformFunction>>(); | ||
| _blobStoreMock = new Mock<IMeshFilesBlobStore>(); | ||
|
|
||
| var options = new DbContextOptionsBuilder<ServiceLayerDbContext>() | ||
| .UseInMemoryDatabase(Guid.NewGuid().ToString()) | ||
| .ConfigureWarnings(warnings => | ||
| warnings.Ignore(Microsoft.EntityFrameworkCore.Diagnostics.InMemoryEventId.TransactionIgnoredWarning)) | ||
| .Options; | ||
| _dbContext = new ServiceLayerDbContext(options); | ||
|
|
||
| _function = new FileTransformFunction( | ||
| _loggerMock.Object, | ||
| _dbContext, | ||
| _blobStoreMock.Object | ||
| ); | ||
| } | ||
|
|
||
| [Fact] | ||
| public async Task Run_FileNotFound_ExitsSilently() | ||
| { | ||
| // Arrange | ||
| var message = new FileTransformQueueMessage { FileId = "nonexistent-file" }; | ||
|
|
||
| // Act | ||
| await _function.Run(message); | ||
|
|
||
| // Assert | ||
| _loggerMock.Verify( | ||
| x => x.Log( | ||
| LogLevel.Warning, | ||
| It.IsAny<EventId>(), | ||
| It.Is<It.IsAnyType>((v, t) => v.ToString() == $"File with id: {message.FileId} not found in MeshFiles table."), | ||
| null, | ||
| It.IsAny<Func<It.IsAnyType, Exception?, string>>() | ||
| ), Times.Once); | ||
|
|
||
| Assert.Equal(0, _dbContext.MeshFiles.Count()); | ||
| _blobStoreMock.Verify(x => x.DownloadAsync(It.IsAny<MeshFile>()), Times.Never); | ||
| } | ||
|
|
||
| [Fact] | ||
| public async Task Run_FileStatusInvalid_ExitsSilently() | ||
| { | ||
| // Arrange | ||
| var file = new MeshFile | ||
| { | ||
| FileType = MeshFileType.NbssAppointmentEvents, | ||
| MailboxId = "test-mailbox", | ||
| FileId = "file-1", | ||
| Status = MeshFileStatus.FailedExtract, // Not eligible | ||
| LastUpdatedUtc = DateTime.UtcNow | ||
| }; | ||
| _dbContext.MeshFiles.Add(file); | ||
| await _dbContext.SaveChangesAsync(); | ||
|
|
||
| var message = new FileTransformQueueMessage { FileId = "file-1" }; | ||
|
|
||
| // Act | ||
| await _function.Run(message); | ||
|
|
||
| // Assert | ||
| _loggerMock.Verify( | ||
| x => x.Log( | ||
| LogLevel.Warning, | ||
| It.IsAny<EventId>(), | ||
| It.Is<It.IsAnyType>((v, t) => v.ToString() == $"File with id: {message.FileId} found in MeshFiles table but is not suitable for transformation. Status: {file.Status}"), | ||
| null, | ||
| It.IsAny<Func<It.IsAnyType, Exception?, string>>() | ||
| ), Times.Once); | ||
| var fileFromDb = await _dbContext.MeshFiles.SingleOrDefaultAsync(x => x.FileId == file.FileId); | ||
| Assert.Equal(MeshFileStatus.FailedExtract, fileFromDb?.Status); | ||
| _blobStoreMock.Verify(x => x.DownloadAsync(It.IsAny<MeshFile>()), Times.Never); | ||
| } | ||
|
|
||
| [Fact] | ||
| public async Task Run_FileValid_DownloadsBlob() | ||
| { | ||
| // Arrange | ||
| var file = new MeshFile | ||
| { | ||
| FileType = MeshFileType.NbssAppointmentEvents, | ||
| MailboxId = "test-mailbox", | ||
| FileId = "file-1", | ||
| Status = MeshFileStatus.Extracted, | ||
| LastUpdatedUtc = DateTime.UtcNow | ||
| }; | ||
| _dbContext.MeshFiles.Add(file); | ||
| await _dbContext.SaveChangesAsync(); | ||
|
|
||
| var message = new FileTransformQueueMessage { FileId = "file-1" }; | ||
|
|
||
| // Act | ||
| await _function.Run(message); | ||
|
|
||
| // Assert | ||
| _loggerMock.Verify( | ||
| x => x.Log( | ||
| LogLevel.Warning, | ||
| It.IsAny<EventId>(), | ||
| It.IsAny<It.IsAnyType>(), | ||
| null, | ||
| It.IsAny<Func<It.IsAnyType, Exception?, string>>() | ||
| ), Times.Never); | ||
| _blobStoreMock.Verify(x => x.DownloadAsync(file), Times.Once); | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.