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

Import bundles #3720

Open
wants to merge 55 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 51 commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
3f48846
Import bundle with parameters
SergeyGaluzo Feb 18, 2024
eca3e1c
string
SergeyGaluzo Feb 19, 2024
b5cab8c
resource wrappers
SergeyGaluzo Feb 19, 2024
2905f03
2 test methods
SergeyGaluzo Feb 19, 2024
b929c8f
return bad request on parsing error
SergeyGaluzo Feb 19, 2024
1a14aed
Merge branch 'main' into users/sergal/importbundles
SergeyGaluzo Feb 19, 2024
044bb0e
trueEx
SergeyGaluzo Feb 19, 2024
4664a38
Report bad request on dups
SergeyGaluzo Feb 20, 2024
128915c
support for new bundle import endpoint
SergeyGaluzo Feb 20, 2024
b9e6b88
dedup output
SergeyGaluzo Feb 20, 2024
ebe7476
Passng loaded resources
SergeyGaluzo Feb 20, 2024
aaeed17
test name
SergeyGaluzo Feb 20, 2024
0f01ea8
import resources and bundle json
SergeyGaluzo Feb 21, 2024
58b7c22
changed endpoint
SergeyGaluzo Feb 21, 2024
4cb7e29
concat in loop
SergeyGaluzo Feb 21, 2024
b13d928
Added auth to handler
SergeyGaluzo Feb 21, 2024
4995cb7
Write
SergeyGaluzo Feb 21, 2024
28c7213
+ in importer
SergeyGaluzo Feb 21, 2024
36a7b5b
Check for PUT
SergeyGaluzo Feb 21, 2024
a035759
nice
SergeyGaluzo Feb 21, 2024
9aa05a4
messages
SergeyGaluzo Feb 22, 2024
8c8d701
comments
SergeyGaluzo Feb 24, 2024
5bed320
FormatException
SergeyGaluzo Feb 24, 2024
0e66721
Diag option
SergeyGaluzo Feb 25, 2024
1086289
Removed not needed bundle components
SergeyGaluzo Feb 26, 2024
0306d20
max byres
SergeyGaluzo Feb 26, 2024
d385d89
Test for invalid resource
SergeyGaluzo Feb 26, 2024
a17dec7
Dressing
SergeyGaluzo Feb 26, 2024
c0b5e1e
Errors
SergeyGaluzo Feb 26, 2024
6d3c2b5
list of diag searches
SergeyGaluzo Feb 27, 2024
2dab61d
var assignment
SergeyGaluzo Feb 27, 2024
7e3ab6c
Diag retries
SergeyGaluzo Feb 27, 2024
5cb7cfc
recording current writers
SergeyGaluzo Feb 28, 2024
3e5188c
Next iteration
SergeyGaluzo Mar 2, 2024
c95b0d8
Formal import bundle
SergeyGaluzo Mar 3, 2024
44b58b2
use default input format
SergeyGaluzo Mar 3, 2024
cdd2a8b
var
SergeyGaluzo Mar 3, 2024
def486a
Merge branch 'main' into users/sergal/importbundles
SergeyGaluzo Mar 7, 2024
c5240ed
comment
SergeyGaluzo Mar 7, 2024
eac79c4
batch
SergeyGaluzo Mar 9, 2024
5ac1c48
OK
SergeyGaluzo Mar 9, 2024
7098f9c
test
SergeyGaluzo Mar 9, 2024
8689a28
error example
SergeyGaluzo Mar 11, 2024
2b86a34
test fix
SergeyGaluzo Mar 12, 2024
cc52346
Merge branch 'main' into users/sergal/importbundles
SergeyGaluzo Mar 14, 2024
8c1a111
start sw after skip
SergeyGaluzo Mar 15, 2024
fdaccb7
Adjust importer to work with profile
SergeyGaluzo Mar 15, 2024
e5859d0
clarification
SergeyGaluzo Mar 15, 2024
9a0cd3a
better
SergeyGaluzo Mar 15, 2024
901ad8b
switch to import resources
SergeyGaluzo Mar 17, 2024
d238574
more tests
SergeyGaluzo Mar 18, 2024
1c789f4
removed keys
SergeyGaluzo Mar 18, 2024
12429af
simpler
SergeyGaluzo Mar 18, 2024
4bb912c
correct ResetCount()
SergeyGaluzo Mar 18, 2024
a975aad
Added soft delete import test
SergeyGaluzo Mar 20, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// -------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// -------------------------------------------------------------------------------------------------

using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using Azure.Core;
using Microsoft.Health.Fhir.Core.Features.Operations.Import;
using Microsoft.Health.Fhir.Core.Features.Operations.Import.Models;

namespace Microsoft.Health.Fhir.Api.Features.ActionResults
{
/// <summary>
/// Used to return the result of a import bundle operation.
/// </summary>
public class ImportBundleActionResult : ResourceActionResult<ImportBundleResult>
{
public ImportBundleActionResult(ImportBundleResult importBundleResult, HttpStatusCode statusCode)
: base(importBundleResult, statusCode)
{
}

public ImportBundleResult ImportBundleResult { get; private set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,17 @@ namespace Microsoft.Health.Fhir.Core.Extensions
{
public static class ImportMediatorExtensions
{
public static async Task<ImportBundleResponse> ImportBundleAsync(
this IMediator mediator,
IReadOnlyList<ImportResource> resources,
CancellationToken cancellationToken)
{
EnsureArg.IsNotNull(mediator, nameof(mediator));
var request = new ImportBundleRequest(resources);
var response = await mediator.Send(request, cancellationToken);
return response;
}

public static async Task<CreateImportResponse> ImportAsync(
this IMediator mediator,
Uri requestUri,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// -------------------------------------------------------------------------------------------------
// 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 Microsoft.Health.Fhir.Core.Features.Metrics;
using Microsoft.Health.Fhir.ValueSets;

namespace Microsoft.Health.Fhir.Core.Features.Operations.Import
{
public class ImportBundleMetricsNotification : IMetricsNotification
{
public ImportBundleMetricsNotification(
DateTimeOffset startTime,
DateTimeOffset endTime,
long succeededCount)
{
FhirOperation = AuditEventSubType.ImportBundle;
ResourceType = null;

StartTime = startTime;
EndTime = endTime;
SucceededCount = succeededCount;
}

public string FhirOperation { get; }

public string ResourceType { get; }

public DateTimeOffset StartTime { get; }

public DateTimeOffset EndTime { get; }

public long SucceededCount { get; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// -------------------------------------------------------------------------------------------------
// 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 System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using EnsureThat;
using Hl7.Fhir.Serialization;
using MediatR;
using Microsoft.Extensions.Logging;
using Microsoft.Health.Core.Features.Security.Authorization;
using Microsoft.Health.Fhir.Core.Exceptions;
using Microsoft.Health.Fhir.Core.Features.Persistence;
using Microsoft.Health.Fhir.Core.Features.Security;
using Microsoft.Health.Fhir.Core.Messages.Import;
using Microsoft.Health.JobManagement;

namespace Microsoft.Health.Fhir.Core.Features.Operations.Import
{
/// <summary>
/// MediatR request handler. Called when the ImportController processes ImportBundleRequest.
/// </summary>
public class ImportBundleRequestHandler : IRequestHandler<ImportBundleRequest, ImportBundleResponse>
{
private readonly IFhirDataStore _store;
private readonly ILogger<ImportBundleRequestHandler> _logger;
private readonly IAuthorizationService<DataActions> _authorizationService;

public ImportBundleRequestHandler(
IFhirDataStore store,
ILogger<ImportBundleRequestHandler> logger,
IAuthorizationService<DataActions> authorizationService)
{
_store = EnsureArg.IsNotNull(store, nameof(store));
_logger = EnsureArg.IsNotNull(logger, nameof(logger));
_authorizationService = EnsureArg.IsNotNull(authorizationService, nameof(authorizationService));
}

public async Task<ImportBundleResponse> Handle(ImportBundleRequest request, CancellationToken cancellationToken)
{
EnsureArg.IsNotNull(request, nameof(request));
if (await _authorizationService.CheckAccess(DataActions.Write, cancellationToken) != DataActions.Write)
{
throw new UnauthorizedFhirActionException();
}

var errors = await _store.ImportResourcesAsync(request.Resources, ImportMode.IncrementalLoad, cancellationToken);
return await Task.FromResult(new ImportBundleResponse(request.Resources.Count - errors.Count, errors));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// -------------------------------------------------------------------------------------------------
// 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 System.Collections.Generic;
using Newtonsoft.Json;

namespace Microsoft.Health.Fhir.Core.Features.Operations.Import.Models
{
public class ImportBundleResult
{
public ImportBundleResult(int loadedResources, IReadOnlyList<string> errors)
{
LoadedResources = loadedResources;
Errors = errors;
}

[JsonConstructor]
private ImportBundleResult()
{
}

[JsonProperty("loadedResources")]
public int LoadedResources { get; private set; }

[JsonProperty("errors")]
public IReadOnlyList<string> Errors { get; private set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Health.Fhir.Core.Features.Operations.Import;

namespace Microsoft.Health.Fhir.Core.Features.Persistence
{
public interface IFhirDataStore
{
Task<IReadOnlyList<string>> ImportResourcesAsync(IReadOnlyList<ImportResource> resources, ImportMode importMode, CancellationToken cancellationToken);

Task<IDictionary<DataStoreOperationIdentifier, DataStoreOperationOutcome>> MergeAsync(IReadOnlyList<ResourceWrapperOperation> resources, CancellationToken cancellationToken);

Task<IDictionary<DataStoreOperationIdentifier, DataStoreOperationOutcome>> MergeAsync(IReadOnlyList<ResourceWrapperOperation> resources, MergeOptions mergeOptions, CancellationToken cancellationToken);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// -------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// -------------------------------------------------------------------------------------------------

using System.Linq;
using Hl7.Fhir.Model;
using Microsoft.Health.Fhir.Core.Models;

namespace Microsoft.Health.Fhir.Core.Features.Persistence;

public static class ImportBundleFhirPathExtension
{
/// <summary>
/// Return true if this resource contains the ImportBundle profile in meta data
/// </summary>
public static bool HasImportBundleProfile(this Resource resource)
{
return resource.Meta?.Profile != null && resource.Meta.Profile.Contains(KnownFhirPaths.ImportBundleProfileExtensionUrl);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// -------------------------------------------------------------------------------------------------
// 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 System.Collections.Generic;
using EnsureThat;
using MediatR;
using Microsoft.Health.Fhir.Core.Features.Operations.Import;
using Microsoft.Health.Fhir.Core.Features.Operations.Import.Models;
using Microsoft.Health.Fhir.Core.Features.Persistence;

namespace Microsoft.Health.Fhir.Core.Messages.Import
{
public class ImportBundleRequest : IRequest<ImportBundleResponse>
{
public ImportBundleRequest(IReadOnlyList<ImportResource> resources)
{
Resources = EnsureArg.IsNotNull(resources, nameof(resources));
}

/// <summary>
/// Resources to import
/// </summary>
public IReadOnlyList<ImportResource> Resources { get; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// -------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// -------------------------------------------------------------------------------------------------

using System.Collections.Generic;

namespace Microsoft.Health.Fhir.Core.Messages.Import
{
public class ImportBundleResponse
{
public ImportBundleResponse(int loaded, IReadOnlyList<string> errors)
{
LoadedResources = loaded;
Errors = errors;
}

public int LoadedResources { get; private set; }

public IReadOnlyList<string> Errors { get; private set; }
}
}
2 changes: 2 additions & 0 deletions src/Microsoft.Health.Fhir.Core/Models/KnownFhirPaths.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ public static class KnownFhirPaths
{
internal const string AzureSoftDeletedExtensionUrl = "http://azurehealthcareapis.com/data-extensions/deleted-state";

public const string ImportBundleProfileExtensionUrl = "http://azurehealthcareapis.com/data-extensions/import-bundle";

public const string BundleEntries = "Resource.entry.resource";

public const string BundleNextLink = "Resource.link.where(relation = 'next').url";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
using Microsoft.Health.Fhir.Core.Extensions;
using Microsoft.Health.Fhir.Core.Features.Conformance;
using Microsoft.Health.Fhir.Core.Features.Definition;
using Microsoft.Health.Fhir.Core.Features.Operations.Import;
using Microsoft.Health.Fhir.Core.Features.Persistence;
using Microsoft.Health.Fhir.Core.Features.Persistence.Orchestration;
using Microsoft.Health.Fhir.Core.Models;
Expand Down Expand Up @@ -118,6 +119,11 @@ public CosmosFhirDataStore(
_modelInfoProvider = modelInfoProvider;
}

public Task<IReadOnlyList<string>> ImportResourcesAsync(IReadOnlyList<ImportResource> resources, ImportMode importMode, CancellationToken cancellationToken)
{
throw new NotSupportedException();
}

public async Task<IReadOnlyList<ResourceWrapper>> GetAsync(IReadOnlyList<ResourceKey> keys, CancellationToken cancellationToken)
{
var results = new List<ResourceWrapper>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
using Microsoft.Health.Fhir.Core.Features.Context;
using Microsoft.Health.Fhir.Core.Features.Operations.Import;
using Microsoft.Health.Fhir.Core.Features.Operations.Import.Models;
using Microsoft.Health.Fhir.Core.Features.Persistence;
using Microsoft.Health.Fhir.Core.Features.Routing;
using Microsoft.Health.Fhir.Tests.Common;
using Microsoft.Health.Test.Utilities;
Expand Down Expand Up @@ -100,6 +101,8 @@ private ImportController GetController(ImportTaskConfiguration bulkImportConfig)
_urlResolver,
optionsOperationConfiguration,
optionsFeatures,
Substitute.For<IResourceWrapperFactory>(),
Substitute.For<IImportErrorSerializer>(),
NullLogger<ImportController>.Instance);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
using Microsoft.Health.Fhir.Core.Features;
using Microsoft.Health.Fhir.Core.Features.Context;
using Microsoft.Health.Fhir.Core.Features.Operations;
using Microsoft.Health.Fhir.Core.Features.Operations.Import;
using Microsoft.Health.Fhir.Core.Features.Operations.Versions;
using Microsoft.Health.Fhir.Core.Features.Persistence;
using Microsoft.Health.Fhir.Core.Features.Persistence.Orchestration;
Expand Down Expand Up @@ -68,6 +69,8 @@ public class FhirController : Controller
private readonly ILogger<FhirController> _logger;
private readonly RequestContextAccessor<IFhirRequestContext> _fhirRequestContextAccessor;
private readonly IUrlResolver _urlResolver;
private readonly IResourceWrapperFactory _resourceWrapperFactory;
private readonly IImportErrorSerializer _importErrorSerializer;

/// <summary>
/// Initializes a new instance of the <see cref="FhirController" /> class.
Expand All @@ -78,13 +81,16 @@ public class FhirController : Controller
/// <param name="urlResolver">The urlResolver.</param>
/// <param name="uiConfiguration">The UI configuration.</param>
/// <param name="authorizationService">The authorization service.</param>
/// <param name="resourceWrapperFactory">The resource Wrapper Factory.</param>
public FhirController(
IMediator mediator,
ILogger<FhirController> logger,
RequestContextAccessor<IFhirRequestContext> fhirRequestContextAccessor,
IUrlResolver urlResolver,
IOptions<FeatureConfiguration> uiConfiguration,
IAuthorizationService authorizationService)
IAuthorizationService authorizationService,
IResourceWrapperFactory resourceWrapperFactory,
IImportErrorSerializer importErrorSerializer)
{
EnsureArg.IsNotNull(mediator, nameof(mediator));
EnsureArg.IsNotNull(logger, nameof(logger));
Expand All @@ -98,6 +104,8 @@ public FhirController(
_logger = logger;
_fhirRequestContextAccessor = fhirRequestContextAccessor;
_urlResolver = urlResolver;
_resourceWrapperFactory = EnsureArg.IsNotNull(resourceWrapperFactory, nameof(resourceWrapperFactory));
_importErrorSerializer = EnsureArg.IsNotNull(importErrorSerializer, nameof(importErrorSerializer));
}

[ApiExplorerSettings(IgnoreApi = true)]
Expand Down Expand Up @@ -656,6 +664,11 @@ public async Task<IActionResult> Versions()
[AuditEventType(AuditEventSubType.BundlePost)]
public async Task<IActionResult> BatchAndTransactions([FromBody] Resource bundle)
{
if (bundle.HasImportBundleProfile())
{
return await ImportController.ImportBundleInternal(Request, bundle, _resourceWrapperFactory, _mediator, _logger, _importErrorSerializer, HttpContext.RequestAborted);
}

ResourceElement bundleResponse = await _mediator.PostBundle(bundle.ToResourceElement());

return FhirResult.Create(bundleResponse);
Expand Down
Loading
Loading