Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cosmos Indexing Integration #8

Merged
merged 22 commits into from Jul 3, 2019
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
7f0e4c3
Added initial work for cosmos indexing strategy
Richyl2 Jun 18, 2019
44835bd
Added settings updater and fixed all tests
Richyl2 Jun 19, 2019
453b2b8
Updated the build file to install the Cosmos emulator and updated the…
Richyl2 Jun 19, 2019
720095c
Updated build script and added more tests
Richyl2 Jun 20, 2019
70879af
Attempt to get tests running on windows
Richyl2 Jun 20, 2019
1be66b9
Updated build file
Richyl2 Jun 20, 2019
9e4373d
Updated build file
Richyl2 Jun 20, 2019
15685ed
Removed condition from build test
Richyl2 Jun 20, 2019
5afc1cc
Added steps to the test yml file
Richyl2 Jun 20, 2019
d0f569d
Updated the cosmos tests yml file to use the correct endpoint
Richyl2 Jun 20, 2019
3ccb0f1
Updated the test build script, added support for series and study que…
Richyl2 Jun 21, 2019
5f333c1
Fixed tests when running in parallel
Richyl2 Jun 21, 2019
3d9423e
Refactored PR based on comments and added Dicom Attribute for being a…
Richyl2 Jun 25, 2019
01dd8de
Fixed broken test and resolved more PR comments
Richyl2 Jun 25, 2019
1fb69cd
Added missing code comment change for the DICOM identifier validator
Richyl2 Jun 25, 2019
454ddeb
Updated the Regex so it is compiled
Richyl2 Jun 25, 2019
9baa990
Updated solution file to remove missing project
Richyl2 Jun 25, 2019
275fcc0
Refactored based on PR comments
Richyl2 Jun 26, 2019
a6cb0ee
Fixed typo in test build script file
Richyl2 Jun 26, 2019
a1fce50
Removed hard-coded paramter names from query strings and shortened un…
Richyl2 Jul 2, 2019
9f62bbf
Normalized document casing for Cosmos documents
Richyl2 Jul 2, 2019
1e712bb
Updated PR based on final comments
Richyl2 Jul 3, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
18 changes: 16 additions & 2 deletions Microsoft.Health.Dicom.sln
Expand Up @@ -28,9 +28,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Health.Dicom.Core
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Health.Dicom.Api", "src\Microsoft.Health.Dicom.Api\Microsoft.Health.Dicom.Api.csproj", "{B0570D75-E376-44AC-870B-87ECB54F0AE3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Health.Dicom.Api.UnitTests", "src\Microsoft.Health.Dicom.Api.UnitTests\Microsoft.Health.Dicom.Api.UnitTests.csproj", "{D7B538E5-8B3B-487C-8F6A-475F80C50DFE}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Health.Dicom.Api.UnitTests", "src\Microsoft.Health.Dicom.Api.UnitTests\Microsoft.Health.Dicom.Api.UnitTests.csproj", "{D7B538E5-8B3B-487C-8F6A-475F80C50DFE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Health.Dicom.Core.UnitTests", "src\Microsoft.Health.Dicom.Core.UnitTests\Microsoft.Health.Dicom.Core.UnitTests.csproj", "{FA0484A7-AA0C-4CC6-A75F-1D6B23DD847D}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Health.Dicom.Core.UnitTests", "src\Microsoft.Health.Dicom.Core.UnitTests\Microsoft.Health.Dicom.Core.UnitTests.csproj", "{FA0484A7-AA0C-4CC6-A75F-1D6B23DD847D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Health.Dicom.CosmosDb", "src\Microsoft.Health.Dicom.CosmosDb\Microsoft.Health.Dicom.CosmosDb.csproj", "{E78AB378-6CF4-442A-BC34-81A1B7431ECD}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Health.Dicom.CosmosDb.UnitTests", "src\Microsoft.Health.Dicom.CosmosDb.UnitTests\Microsoft.Health.Dicom.CosmosDb.UnitTests.csproj", "{FA0B32A4-E623-4B24-902D-BFDC6E42E612}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down Expand Up @@ -78,6 +82,14 @@ Global
{FA0484A7-AA0C-4CC6-A75F-1D6B23DD847D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FA0484A7-AA0C-4CC6-A75F-1D6B23DD847D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FA0484A7-AA0C-4CC6-A75F-1D6B23DD847D}.Release|Any CPU.Build.0 = Release|Any CPU
{E78AB378-6CF4-442A-BC34-81A1B7431ECD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E78AB378-6CF4-442A-BC34-81A1B7431ECD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E78AB378-6CF4-442A-BC34-81A1B7431ECD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E78AB378-6CF4-442A-BC34-81A1B7431ECD}.Release|Any CPU.Build.0 = Release|Any CPU
{FA0B32A4-E623-4B24-902D-BFDC6E42E612}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FA0B32A4-E623-4B24-902D-BFDC6E42E612}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FA0B32A4-E623-4B24-902D-BFDC6E42E612}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FA0B32A4-E623-4B24-902D-BFDC6E42E612}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -93,6 +105,8 @@ Global
{B0570D75-E376-44AC-870B-87ECB54F0AE3} = {176641B3-297C-4E04-A83D-8F80F80485E8}
{D7B538E5-8B3B-487C-8F6A-475F80C50DFE} = {176641B3-297C-4E04-A83D-8F80F80485E8}
{FA0484A7-AA0C-4CC6-A75F-1D6B23DD847D} = {176641B3-297C-4E04-A83D-8F80F80485E8}
{E78AB378-6CF4-442A-BC34-81A1B7431ECD} = {176641B3-297C-4E04-A83D-8F80F80485E8}
{FA0B32A4-E623-4B24-902D-BFDC6E42E612} = {176641B3-297C-4E04-A83D-8F80F80485E8}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
RESX_SortFileContentOnSave = True
Expand Down
69 changes: 33 additions & 36 deletions build/build.yml
Expand Up @@ -2,39 +2,36 @@ parameters:
packageArtifacts: true

steps:
- task: DotNetCoreInstaller@0
inputs:
version: '2.2.103'

- script: dotnet build --configuration $(buildConfiguration) --version-suffix $(build.buildNumber) /warnaserror
displayName: 'dotnet build $(buildConfiguration)'

# To run the tests we install NPM to run the 'azurite' emulator (https://github.com/Azure/Azurite).
# This allows us to test the blob storage providers without an Azure instance.
- task: NodeTool@0
displayName: 'Use Node 8.x'
inputs:
versionSpec: 8.x
checkLatest: true

- script: npm install -g azurite@2.7.0
displayName: 'Install Azurite v2.7.0'

# We start the Azurite as a separate process as the start call is blocking.
- script: azurite -s &
condition: and(succeeded(), eq( variables['Agent.OS'], 'Linux' ))
displayName: 'Start Azurite Storage Emulator (Linux)'

- script: start azurite -s
condition: and(succeeded(), eq( variables['Agent.OS'], 'Windows_NT' ))
displayName: 'Start Azurite Storage Emulator (Windows)'

- task: DotNetCoreCLI@2
displayName: 'dotnet test UnitTests'
inputs:
command: test
projects: '**\*Tests*.csproj'
arguments: '--configuration $(buildConfiguration)'

- ${{ if eq(parameters.packageArtifacts, 'true') }}:
- template: package.yml

- task: NuGetToolInstaller@0
displayName: 'Use NuGet 4.7.1'
inputs:
versionSpec: 4.7.1
checkLatest: true

- task: DotNetCoreInstaller@0
inputs:
version: '2.2.103'

- task: DotNetCoreCLI@2
displayName: 'donet restore'
inputs:
command: restore
restoreSolution: '**\*.sln'
feedsToUse: config
nugetConfigPath: nuget.config

- script: dotnet build --configuration $(buildConfiguration) --version-suffix $(build.buildNumber) /warnaserror
displayName: 'dotnet build $(buildConfiguration)'

- task: DotNetCoreCLI@2
displayName: 'dotnet test UnitTests'
inputs:
command: test
projects: '**/*UnitTests/*.csproj'
arguments: '--configuration $(buildConfiguration)'

- template: test.yml

- ${{ if eq(parameters.packageArtifacts, 'true') }}:
- template: package.yml
53 changes: 53 additions & 0 deletions build/test.yml
@@ -0,0 +1,53 @@

steps:

# To run the tests we install NPM to run the 'azurite' emulator (https://github.com/Azure/Azurite).
# This allows us to test the blob storage providers without an Azure instance.
- task: NodeTool@0
displayName: 'Use Node 8.x'
inputs:
versionSpec: 8.x
checkLatest: true

- script: npm install -g azurite@2.7.0
displayName: 'Install Azurite v2.7.0'

# We start the Azurite as a separate process as the start call is blocking.
- script: azurite -s &
condition: and(succeeded(), eq( variables['Agent.OS'], 'Linux' ))
displayName: 'Start Azurite Storage Emulator (Linux)'

- script: start azurite -s
condition: and(succeeded(), eq( variables['Agent.OS'], 'Windows_NT' ))
displayName: 'Start Azurite Storage Emulator (Windows)'

# Currently the Cosmos DB Emulator only runs on Windows. Therefore all end to end testing
# if performed on windows.
Richyl2 marked this conversation as resolved.
Show resolved Hide resolved
- powershell: |
Richyl2 marked this conversation as resolved.
Show resolved Hide resolved
$cosmosemulator=[Environment]::GetEnvironmentVariable("ProgramFiles") + "\Azure Cosmos DB Emulator\CosmosDB.Emulator.exe"
Richyl2 marked this conversation as resolved.
Show resolved Hide resolved
& $cosmosemulator /noui
$location = 'https://localhost:8081/_explorer/index.html'
$statusCode = [int]0
while ($statusCode -ne 200)
{
Try
{
$req = [system.Net.WebRequest]::Create($location)
$response = $req.GetResponse()
$statusCode = [int]$response.StatusCode
$location + " status code: " + $statusCode
$response.Close()
} Catch {
$PSItem.ToString()
}
}
displayName: 'Start Installed Cosmos Emulator'
condition: and(succeeded(), eq( variables['Agent.OS'], 'Windows_NT' ))

- task: DotNetCoreCLI@2
displayName: 'Run all tests (Windows)'
condition: and(succeeded(), eq( variables['Agent.OS'], 'Windows_NT' ))
inputs:
command: test
projects: '**\*Tests*.csproj'
arguments: '--configuration $(buildConfiguration)'
Expand Up @@ -13,6 +13,7 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Health.Blob.Configs;
using Microsoft.Health.Dicom.Core.Features.Persistence;

namespace Microsoft.Health.Dicom.Blob.Features.Storage
{
Expand Down
@@ -0,0 +1,63 @@
// -------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// -------------------------------------------------------------------------------------------------

using System;
using Dicom;
using Microsoft.Health.Dicom.Core.Features.Persistence;
using Xunit;

namespace Microsoft.Health.Dicom.Core.UnitTests.Features.Persistence
{
public class DicomAttributeIdExtensions
Richyl2 marked this conversation as resolved.
Show resolved Hide resolved
{
[Fact]
public void GivenDatasetAndInvalidParameters_WhenFetchedAttributeValues_ArgumentExceptionIsThrown()
{
var dicomDataset = new DicomDataset();
Assert.Throws<ArgumentNullException>(() => dicomDataset.TryGetValues((DicomAttributeId)null, out object[] values));
Assert.Throws<ArgumentOutOfRangeException>(() => dicomDataset.TryGetValues(new DicomAttributeId(DicomTag.StudyDate), out object[] values, -1));
}

[Fact]
public void GivenDatasetWithSequence_WhenFetchingAttributeValues_IsReturnedCorrectly()
{
var sequence1 = new DicomSequence(
DicomTag.ReferencedPatientSequence,
new DicomDataset() { { DicomTag.PatientName, "Patient1" } },
new DicomDataset() { { DicomTag.PatientName, "Patient2" } },
new DicomDataset() { { DicomTag.PatientName, "Patient3" } });

var fetchedValues1 = new DicomDataset() { sequence1 }.TryGetValues(
new DicomAttributeId(DicomTag.ReferencedPatientSequence, DicomTag.PatientName),
out object[] values1);
Assert.True(fetchedValues1);
Assert.Equal(3, values1.Length);
Assert.Contains("Patient1", values1);
Assert.Contains("Patient2", values1);
Assert.Contains("Patient3", values1);

var sequence2 = new DicomSequence(
DicomTag.ReferencedStudySequence,
new DicomDataset() { { DicomTag.StudyDate, new DateTime(2019, 6, 25) } },
new DicomDataset() { { new DicomSequence(DicomTag.ReferencedStudySequence, new DicomDataset() { { DicomTag.StudyDate, new DateTime(2019, 6, 24) } }) } },
new DicomDataset() { { DicomTag.StudyDate, new DateTime(2019, 6, 23) } });

var fetchedValues2 = new DicomDataset() { sequence2 }.TryGetValues(
new DicomAttributeId(DicomTag.ReferencedStudySequence, DicomTag.StudyDate),
out object[] values2);
Assert.True(fetchedValues2);
Assert.Equal(2, values2.Length);
Assert.Contains(new DateTime(2019, 6, 25), values2);
Assert.Contains(new DateTime(2019, 6, 23), values2);

var fetchedValues3 = new DicomDataset() { sequence2 }.TryGetValues(
new DicomAttributeId(DicomTag.ReferencedStudySequence, DicomTag.ReferencedStudySequence, DicomTag.StudyDate),
out object[] values3);
Assert.True(fetchedValues3);
Assert.Single(values3);
Assert.Contains(new DateTime(2019, 6, 24), values3);
}
}
}
@@ -0,0 +1,66 @@
// -------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// -------------------------------------------------------------------------------------------------
using System;
using Dicom;
using Microsoft.Health.Dicom.Core.Features.Persistence;
using Newtonsoft.Json;
using Xunit;

namespace Microsoft.Health.Dicom.Core.UnitTests.Features.Persistence
{
public class DicomAttributeIdTests
{
[Fact]
public void GivenDicomAttributeId_WhenConstructingWithInvalidParameters_ArgumentExceptionThrown()
{
Assert.Throws<ArgumentNullException>(() => new DicomAttributeId((DicomTag[])null));
Assert.Throws<ArgumentException>(() => new DicomAttributeId(Array.Empty<DicomTag>()));
Assert.Throws<ArgumentException>(() => new DicomAttributeId(DicomTag.RightImageSequence));
Assert.Throws<ArgumentException>(() => new DicomAttributeId(DicomTag.RightImageSequence, DicomTag.ROIContourSequence));
Assert.Throws<ArgumentException>(() => new DicomAttributeId(DicomTag.StudyDate, DicomTag.RightLensSequence));

Assert.Throws<ArgumentNullException>(() => new DicomAttributeId((string)null));
Assert.Throws<ArgumentException>(() => new DicomAttributeId(string.Empty));
Assert.Throws<ArgumentException>(() => new DicomAttributeId("INVALID"));
Assert.Throws<ArgumentException>(() => new DicomAttributeId("INVALID.INVALID"));

Assert.Throws<ArgumentOutOfRangeException>(() => new DicomAttributeId(DicomTag.StudyDate).GetDicomTag(-1));
Assert.Throws<ArgumentOutOfRangeException>(() => new DicomAttributeId(DicomTag.StudyDate).GetDicomTag(1));
}

[Fact]
public void GivenValidDicomAttributeId_WhenSerialized_IsDeserializedCorrectly()
{
SerializeAndDeserialize(new DicomAttributeId(DicomTag.StudyDate));
SerializeAndDeserialize(new DicomAttributeId(DicomTag.RelatedSeriesSequence, DicomTag.RegistrationSequence, DicomTag.RegisteredLocalizerUnits));
SerializeAndDeserialize(new DicomAttributeId(DicomTag.RegionPixelShiftSequence, DicomTag.NumberOfVerticalPixels));
SerializeAndDeserialize(new DicomAttributeId("0020000D"));
SerializeAndDeserialize(new DicomAttributeId("00101002.00100020"));
SerializeAndDeserialize(new DicomAttributeId("00101002.00100024.00400032"));
SerializeAndDeserialize(new DicomAttributeId("StudyInstanceUID"));
SerializeAndDeserialize(new DicomAttributeId("OtherPatientIDsSequence.PatientID"));
SerializeAndDeserialize(new DicomAttributeId("OtherPatientIDsSequence.IssuerOfPatientIDQualifiersSequence.UniversalEntityID"));
}

[Fact]
public void GivenValidDicomAttributeId_WhenCompared_IsComparedCorrectly()
{
Assert.Equal(new DicomAttributeId(DicomTag.StudyInstanceUID), new DicomAttributeId("StudyInstanceUID"));
Assert.Equal(new DicomAttributeId(DicomTag.OtherPatientIDsSequence, DicomTag.PatientID), new DicomAttributeId("OtherPatientIDsSequence.PatientID"));

Assert.Equal(
new DicomAttributeId(DicomTag.OtherPatientIDsSequence, DicomTag.IssuerOfPatientIDQualifiersSequence, DicomTag.UniversalEntityID),
new DicomAttributeId("OtherPatientIDsSequence.IssuerOfPatientIDQualifiersSequence.UniversalEntityID"));
}

private void SerializeAndDeserialize(DicomAttributeId dicomAttributeId)
{
var json = JsonConvert.SerializeObject(dicomAttributeId);
var deserialized = JsonConvert.DeserializeObject<DicomAttributeId>(json);

Assert.Equal(dicomAttributeId, deserialized);
}
}
}