Skip to content

Commit

Permalink
DicomAssociation Controller (#480)
Browse files Browse the repository at this point in the history
* dicom association controller.

Signed-off-by: Lillie Dae <lillie.dae@answerdigital.com>

---------

Signed-off-by: Lillie Dae <lillie.dae@answerdigital.com>
  • Loading branch information
lillie-dae authored Sep 22, 2023
1 parent 8ceee8a commit fefb2ea
Show file tree
Hide file tree
Showing 22 changed files with 963 additions and 1 deletion.
59 changes: 59 additions & 0 deletions docs/api/rest/dicom-association.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<!--
~ Copyright 2021-2023 MONAI Consortium
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->

# DICOM Association information

The `/dicom-associations' endpoint is for retrieving a list of information regarding dicom
associations.

## GET /dicom-associations/

#### Query Parameters

| Name | Type | Description |
|------------|----------|---------------------------------------------|
| startTime | DateTime | (Optional) Start date to query from. |
| endTime | DateTime | (Optional) End date to query from. |
| pageNumber | Number | (Optional) Page number to query.(default 0) |
| pageSize | Number | (Optional) Page size of query. |

Max & Defaults for PageSize can be set in appSettings.

```json
"endpointSettings": {
"defaultPageSize": number,
"maxPageSize": number
}
```

Endpoint returns a paged result for example

```json
{
"PageNumber": 1,
"PageSize": 10,
"FirstPage": "/payload?pageNumber=1&pageSize=10",
"LastPage": "/payload?pageNumber=1&pageSize=10",
"TotalPages": 1,
"TotalRecords": 3,
"NextPage": null,
"PreviousPage": null,
"Data": [...]
"Succeeded": true,
"Errors": null,
"Message": null
}
```
30 changes: 30 additions & 0 deletions src/Configuration/HttpEndpointSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright 2021-2023 MONAI Consortium
* Copyright 2019-2021 NVIDIA Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

using Microsoft.Extensions.Configuration;

namespace Monai.Deploy.InformaticsGateway.Configuration
{
public class HttpEndpointSettings
{
[ConfigurationKeyName("defaultPageSize")]
public int DefaultPageSize { get; set; } = 10;

[ConfigurationKeyName("maxPageSize")]
public int MaxPageSize { get; set; } = 10;
}
}
1 change: 1 addition & 0 deletions src/Configuration/InformaticsGatewayConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ public class InformaticsGatewayConfiguration
[ConfigurationKeyName("plugins")]
public PlugInConfiguration PlugInConfigurations { get; set; }


public InformaticsGatewayConfiguration()
{
Dicom = new DicomConfiguration();
Expand Down
15 changes: 15 additions & 0 deletions src/Database/Api/Repositories/IDicomAssociationInfoRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,20 @@ public interface IDicomAssociationInfoRepository
Task<List<DicomAssociationInfo>> ToListAsync(CancellationToken cancellationToken = default);

Task<DicomAssociationInfo> AddAsync(DicomAssociationInfo item, CancellationToken cancellationToken = default);

/// <summary>
/// Retrieves a list of DicomAssociationInfo in the database.
/// </summary>
Task<IList<DicomAssociationInfo>> GetAllAsync(int skip,
int? limit,
DateTime startTime,
DateTime endTime,
CancellationToken cancellationToken);

/// <summary>
/// Gets count of objects
/// </summary>
/// <returns>Count of objects.</returns>
Task<long> CountAsync();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,24 @@ public async Task<DicomAssociationInfo> AddAsync(DicomAssociationInfo item, Canc
}).ConfigureAwait(false);
}

public async Task<IList<DicomAssociationInfo>> GetAllAsync(int skip,
int? limit,
DateTime startTime,
DateTime endTime,
CancellationToken cancellationToken)
{
return await _dataset
.Where(t =>
t.DateTimeDisconnected >= startTime.ToUniversalTime() &&
t.DateTimeDisconnected <= endTime.ToUniversalTime())
.Skip(skip)
.Take(limit!.Value)
.ToListAsync(cancellationToken)
.ConfigureAwait(false);
}

public Task<long> CountAsync() => _dataset.LongCountAsync();

public async Task<List<DicomAssociationInfo>> ToListAsync(CancellationToken cancellationToken = default)
{
return await _retryPolicy.ExecuteAsync(async () =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,27 @@ public DicomAssociationInfoRepositoryTest(SqliteDatabaseFixture databaseFixture)
_logger.Setup(p => p.IsEnabled(It.IsAny<LogLevel>())).Returns(true);
}

[Fact]
public async Task GivenDestinationApplicationEntitiesInTheDatabase_WhenGetAllAsyncCalled_ExpectLimitedEntitiesToBeReturned()
{
var store = new DicomAssociationInfoRepository(_serviceScopeFactory.Object, _logger.Object, _options);
var startTime = DateTime.Now;
var endTime = DateTime.MinValue;
var filter = new Func<DicomAssociationInfo, bool>(t =>
t.DateTimeDisconnected >= startTime.ToUniversalTime() &&
t.DateTimeDisconnected <= endTime.ToUniversalTime());

var expected = _databaseFixture.DatabaseContext.Set<DicomAssociationInfo>()
.Where(filter)
.Skip(0)
.Take(1)
.ToList();
var actual = await store.GetAllAsync(0, 1, startTime, endTime, default).ConfigureAwait(false);

Assert.NotNull(actual);
Assert.Equal(expected, actual);
}

[Fact]
public async Task GivenADicomAssociationInfo_WhenAddingToDatabase_ExpectItToBeSaved()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,25 @@ public async Task GivenADicomAssociationInfo_WhenAddingToDatabase_ExpectItToBeSa
actual!.DateTimeDisconnected.Should().BeCloseTo(association.DateTimeDisconnected, TimeSpan.FromMilliseconds(500));
}

[Fact]
public async Task GivenDestinationApplicationEntitiesInTheDatabase_WhenGetAllAsyncCalled_ExpectLimitedEntitiesToBeReturned()
{
var store = new DicomAssociationInfoRepository(_serviceScopeFactory.Object, _logger.Object, _options, _databaseFixture.Options);

var collection = _databaseFixture.Database.GetCollection<DicomAssociationInfo>(nameof(DicomAssociationInfo));
var startTime = DateTime.Now;
var endTime = DateTime.MinValue;
var builder = Builders<DicomAssociationInfo>.Filter;
var filter = builder.Empty;
filter &= builder.Where(t => t.DateTimeDisconnected >= startTime.ToUniversalTime());
filter &= builder.Where(t => t.DateTimeDisconnected <= endTime.ToUniversalTime());
var expected = await collection.Find(filter).ToListAsync().ConfigureAwait(false);
var actual = await store.GetAllAsync(0, 1, startTime, endTime, default).ConfigureAwait(false);

actual.Should().NotBeNull();
actual.Should().BeEquivalentTo(expected, options => options.Excluding(p => p.DateTimeCreated));
}

[Fact]
public async Task GivenDestinationApplicationEntitiesInTheDatabase_WhenToListIsCalled_ExpectAllEntitiesToBeReturned()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@

namespace Monai.Deploy.InformaticsGateway.Database.MongoDB.Repositories
{
public class DicomAssociationInfoRepository : IDicomAssociationInfoRepository, IDisposable
public class DicomAssociationInfoRepository : MongoDBRepositoryBase, IDicomAssociationInfoRepository, IDisposable
{
private readonly ILogger<DicomAssociationInfoRepository> _logger;
private readonly IServiceScope _scope;
Expand Down Expand Up @@ -78,6 +78,29 @@ public async Task<List<DicomAssociationInfo>> ToListAsync(CancellationToken canc
}).ConfigureAwait(false);
}

public Task<IList<DicomAssociationInfo>> GetAllAsync(int skip,
int? limit,
DateTime startTime,
DateTime endTime,
CancellationToken cancellationToken)
{
var builder = Builders<DicomAssociationInfo>.Filter;
var filter = builder.Empty;
filter &= builder.Where(t => t.DateTimeDisconnected >= startTime.ToUniversalTime());
filter &= builder.Where(t => t.DateTimeDisconnected <= endTime.ToUniversalTime());

return GetAllAsync(_collection,
filter,
Builders<DicomAssociationInfo>.Sort.Descending(x => x.DateTimeDisconnected),
skip,
limit);
}

public Task<long> CountAsync()
{
return _collection.CountDocumentsAsync(Builders<DicomAssociationInfo>.Filter.Empty);
}

protected virtual void Dispose(bool disposing)
{
if (!_disposedValue)
Expand Down
64 changes: 64 additions & 0 deletions src/Database/MongoDB/Repositories/MongoDBRepositoryBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright 2021-2023 MONAI Consortium
* Copyright 2019-2021 NVIDIA Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

using System.Linq.Expressions;
using MongoDB.Driver;

namespace Monai.Deploy.InformaticsGateway.Database.MongoDB.Repositories
{
public abstract class MongoDBRepositoryBase
{
/// <summary>
/// Get All T that match filters provided.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="collection">Collection to run against.</param>
/// <param name="filterFunction">Filter function you can filter on properties of T.</param>
/// <param name="sortFunction">Function used to sort data.</param>
/// <param name="skip">Items to skip.</param>
/// <param name="limit">Items to limit results by.</param>
/// <returns></returns>
protected static async Task<IList<T>> GetAllAsync<T>(IMongoCollection<T> collection,
Expression<Func<T, bool>>? filterFunction,
SortDefinition<T> sortFunction,
int? skip = null,
int? limit = null)
{
return await collection
.Find(filterFunction)
.Skip(skip)
.Limit(limit)
.Sort(sortFunction)
.ToListAsync().ConfigureAwait(false);
}

protected static async Task<IList<T>> GetAllAsync<T>(IMongoCollection<T> collection,
FilterDefinition<T> filterFunction,
SortDefinition<T> sortFunction,
int? skip = null,
int? limit = null)
{
var result = await collection
.Find(filterFunction)
.Skip(skip)
.Limit(limit)
.Sort(sortFunction)
.ToListAsync().ConfigureAwait(false);
return result;
}
}
}
6 changes: 6 additions & 0 deletions src/InformaticsGateway/Logging/Log.8000.HttpServices.cs
Original file line number Diff line number Diff line change
Expand Up @@ -173,5 +173,11 @@ public static partial class Log

[LoggerMessage(EventId = 8204, Level = LogLevel.Error, Message = "Failed to store FHIR resource.")]
public static partial void ErrorStoringFhirResource(this ILogger logger, Exception ex);

//
// Dicom Associations Controller.
//
[LoggerMessage(EventId = 8300, Level = LogLevel.Error, Message = "Unexpected error occurred in GET /dicom-associations API..")]
public static partial void DicomAssociationsControllerGetError(this ILogger logger, Exception ex);
}
}
1 change: 1 addition & 0 deletions src/InformaticsGateway/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ internal static IHostBuilder CreateHostBuilder(string[] args) =>
.ConfigureServices((hostContext, services) =>
{
services.AddOptions<InformaticsGatewayConfiguration>().Bind(hostContext.Configuration.GetSection("InformaticsGateway"));
services.AddOptions<HttpEndpointSettings>().Bind(hostContext.Configuration.GetSection("InformaticsGateway:httpEndpointSettings"));
services.AddOptions<MessageBrokerServiceConfiguration>().Bind(hostContext.Configuration.GetSection("InformaticsGateway:messaging"));
services.AddOptions<StorageServiceConfiguration>().Bind(hostContext.Configuration.GetSection("InformaticsGateway:storage"));
services.AddOptions<AuthenticationOptions>().Bind(hostContext.Configuration.GetSection("MonaiDeployAuthentication"));
Expand Down
Loading

0 comments on commit fefb2ea

Please sign in to comment.