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

Added Status and $Status operation to CapabilityStatement #3381

Merged
merged 12 commits into from
Jul 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Expand Up @@ -65,6 +65,8 @@ internal static class RouteNames

internal const string PurgeHistoryDefinition = "PurgeHistoryDefinition";

internal const string SearchParameterStatusOperationDefinition = "SearchParameterStatusOperationDefinition";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Low priority: You can replace the static string with nameof(SearchParameterStatusOperationDefinition).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can add that as a fix in another PR.


internal const string SearchParameterState = "SearchParameterState";

internal const string PostSearchParameterState = "PostSearchParameterState";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,9 @@ public Uri ResolveOperationDefinitionUrl(string operationName)
case OperationsConstants.PurgeHistory:
routeName = RouteNames.PurgeHistoryDefinition;
break;
case OperationsConstants.SearchParameterStatus:
routeName = RouteNames.SearchParameterStatusOperationDefinition;
break;

default:
throw new OperationNotImplementedException(string.Format(Resources.OperationNotImplemented, operationName));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"resourceType": "OperationDefinition",
"id": "status",
"url": "[base]/OperationDefinition/search-parameter-status",
"version": "1.0.0",
"name": "Selectable search parameters",
"status": "active",
"kind": "operation",
"description": "Allows for disabling or enabling of search parameters so that only the ones that are used are enabled in order to save space in the datastore. This operation is asynchronous as defined in the [FHIR Asynchronous Request Pattern](http://hl7.org/fhir/async.html)",
"code": "status",
"system": false,
"type": true,
"instance": false,
"parameter": [
{
"name": "url",
"use": "in",
"min": 0,
"max": "1",
"documentation": "Url of the search parameter status to update.",
"type": "string"
},
{
"name": "status",
"use": "out",
"min": 0,
"max": "1",
"documentation": "Status to update search parameter to. Can be 'supported' or 'disabled'. Cannot update deleted search parameters with this operation.",
"type": "string"
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using EnsureThat;
using Hl7.Fhir.ElementModel;
using Hl7.Fhir.Model;
using Hl7.Fhir.Serialization;
using Microsoft.Extensions.Options;
using Microsoft.Health.Fhir.Core.Configs;
Expand All @@ -19,6 +22,7 @@
using Microsoft.Health.Fhir.Core.Features.Definition;
using Microsoft.Health.Fhir.Core.Features.Routing;
using Microsoft.Health.Fhir.Core.Features.Search;
using Microsoft.Health.Fhir.Core.Features.Search.Registry;
using Microsoft.Health.Fhir.Core.Features.Validation;
using Microsoft.Health.Fhir.Core.Features.Version;
using Microsoft.Health.Fhir.Core.Models;
Expand All @@ -35,28 +39,38 @@ internal class CapabilityStatementBuilder : ICapabilityStatementBuilder
private readonly ISearchParameterDefinitionManager _searchParameterDefinitionManager;
private readonly CoreFeatureConfiguration _configuration;
private readonly ISupportedProfilesStore _supportedProfiles;
private readonly SearchParameterStatusManager _searchParameterStatusManager;

private CapabilityStatementBuilder(
ListedCapabilityStatement statement,
IModelInfoProvider modelInfoProvider,
ISearchParameterDefinitionManager searchParameterDefinitionManager,
IOptions<CoreFeatureConfiguration> configuration,
ISupportedProfilesStore supportedProfiles)
ISupportedProfilesStore supportedProfiles,
SearchParameterStatusManager searchParameterStatusManager)
{
EnsureArg.IsNotNull(statement, nameof(statement));
EnsureArg.IsNotNull(modelInfoProvider, nameof(modelInfoProvider));
EnsureArg.IsNotNull(searchParameterDefinitionManager, nameof(searchParameterDefinitionManager));
EnsureArg.IsNotNull(configuration, nameof(configuration));
EnsureArg.IsNotNull(supportedProfiles, nameof(supportedProfiles));
EnsureArg.IsNotNull(searchParameterStatusManager, nameof(searchParameterStatusManager));

_statement = statement;
_modelInfoProvider = modelInfoProvider;
_searchParameterDefinitionManager = searchParameterDefinitionManager;
_configuration = configuration.Value;
_supportedProfiles = supportedProfiles;
_searchParameterStatusManager = searchParameterStatusManager;
}

public static ICapabilityStatementBuilder Create(IModelInfoProvider modelInfoProvider, ISearchParameterDefinitionManager searchParameterDefinitionManager, IOptions<CoreFeatureConfiguration> configuration, ISupportedProfilesStore supportedProfiles, IUrlResolver urlResolver)
public static ICapabilityStatementBuilder Create(
IModelInfoProvider modelInfoProvider,
ISearchParameterDefinitionManager searchParameterDefinitionManager,
IOptions<CoreFeatureConfiguration> configuration,
ISupportedProfilesStore supportedProfiles,
IUrlResolver urlResolver,
SearchParameterStatusManager searchParameterStatusManager)
{
EnsureArg.IsNotNull(modelInfoProvider, nameof(modelInfoProvider));
EnsureArg.IsNotNull(searchParameterDefinitionManager, nameof(searchParameterDefinitionManager));
Expand Down Expand Up @@ -86,7 +100,7 @@ public static ICapabilityStatementBuilder Create(IModelInfoProvider modelInfoPro
statement.Date = ProductVersionInfo.CreationTime.ToString("O");
statement.Url = urlResolver.ResolveMetadataUrl(false);

return new CapabilityStatementBuilder(statement, modelInfoProvider, searchParameterDefinitionManager, configuration, supportedProfiles);
return new CapabilityStatementBuilder(statement, modelInfoProvider, searchParameterDefinitionManager, configuration, supportedProfiles, searchParameterStatusManager);
}

public ICapabilityStatementBuilder Apply(Action<ListedCapabilityStatement> action)
Expand Down Expand Up @@ -159,7 +173,6 @@ public ICapabilityStatementBuilder AddGlobalInteraction(string systemInteraction
EnsureArg.IsNotNullOrEmpty(systemInteraction, nameof(systemInteraction));

_statement.Rest.Server().Interaction.Add(new ResourceInteractionComponent { Code = systemInteraction });

return this;
}

Expand All @@ -171,12 +184,13 @@ public ICapabilityStatementBuilder AddGlobalSearchParameters()
return this;
}

private ICapabilityStatementBuilder SyncSearchParams(string resourceType)
private async Task<ICapabilityStatementBuilder> SyncSearchParamsAsync(string resourceType, CancellationToken cancellationToken)
{
EnsureArg.IsNotNullOrEmpty(resourceType, nameof(resourceType));
EnsureArg.IsTrue(_modelInfoProvider.IsKnownResource(resourceType), nameof(resourceType), x => GenerateTypeErrorMessage(x, resourceType));

List<SearchParameterInfo> searchParams = _searchParameterDefinitionManager.GetSearchParameters(resourceType).ToList();
var searchParamStatuses = await _searchParameterStatusManager.GetAllSearchParameterStatus(cancellationToken);

if (searchParams.Any())
{
Expand All @@ -197,6 +211,8 @@ private ICapabilityStatementBuilder SyncSearchParams(string resourceType)
continue;
}

string status = string.Format("Current status of search parameter is {0}. ", searchParamStatuses.SingleOrDefault(x => x.Uri == searchParam.Definition)?.Status.ToString());
searchParam.Documentation = status = status + searchParam.Documentation;
c.SearchParam.Add(searchParam);
}
});
Expand Down Expand Up @@ -324,8 +340,10 @@ public ICapabilityStatementBuilder PopulateDefaultResourceInteractions()
return this;
}

public ICapabilityStatementBuilder SyncSearchParameters()
public async Task<ICapabilityStatementBuilder> SyncSearchParametersAsync(CancellationToken cancellationToken)
{
EnsureArg.IsNotNull(_searchParameterStatusManager, nameof(_searchParameterStatusManager));

foreach (string resource in _modelInfoProvider.GetResourceTypeNames())
{
ApplyToResource(resource, c => c.SearchRevInclude.Clear());
Expand All @@ -339,7 +357,7 @@ public ICapabilityStatementBuilder SyncSearchParameters()
continue;
}

SyncSearchParams(resource);
await SyncSearchParamsAsync(resource, cancellationToken);
}

return this;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
// -------------------------------------------------------------------------------------------------

using System;
using System.Threading;
using System.Threading.Tasks;
using Hl7.Fhir.ElementModel;
using Microsoft.Health.Fhir.Core.Features.Conformance.Models;

Expand Down Expand Up @@ -45,7 +47,8 @@ public interface ICapabilityStatementBuilder
/// <summary>
/// Updates capability statement to latest supported search paramaters by checkin in memory storage for search parameters.
/// </summary>
ICapabilityStatementBuilder SyncSearchParameters();
/// <param name="cancellationToken">Cancellation token.</param>
Task<ICapabilityStatementBuilder> SyncSearchParametersAsync(CancellationToken cancellationToken);

/// <summary>
/// Updates capability statement to lastest supported profiles by pulling them from database.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
using Microsoft.Health.Fhir.Core.Features.Conformance.Models;
using Microsoft.Health.Fhir.Core.Features.Definition;
using Microsoft.Health.Fhir.Core.Features.Routing;
using Microsoft.Health.Fhir.Core.Features.Search.Registry;
using Microsoft.Health.Fhir.Core.Features.Validation;
using Microsoft.Health.Fhir.Core.Messages.CapabilityStatement;
using Microsoft.Health.Fhir.Core.Models;
Expand All @@ -43,6 +44,7 @@ public sealed class SystemConformanceProvider
private readonly IOptions<CoreFeatureConfiguration> _configuration;
private readonly ISupportedProfilesStore _supportedProfiles;
private readonly ILogger _logger;
private readonly SearchParameterStatusManager _searchParameterStatusManager;

private ResourceElement _listedCapabilityStatement;
private ResourceElement _metadata;
Expand All @@ -57,7 +59,8 @@ public SystemConformanceProvider(
IOptions<CoreFeatureConfiguration> configuration,
ISupportedProfilesStore supportedProfiles,
ILogger<SystemConformanceProvider> logger,
IUrlResolver urlResolver)
IUrlResolver urlResolver,
SearchParameterStatusManager searchParameterStatusManager)
{
EnsureArg.IsNotNull(modelInfoProvider, nameof(modelInfoProvider));
EnsureArg.IsNotNull(searchParameterDefinitionManagerResolver, nameof(searchParameterDefinitionManagerResolver));
Expand All @@ -66,6 +69,7 @@ public SystemConformanceProvider(
EnsureArg.IsNotNull(supportedProfiles, nameof(supportedProfiles));
EnsureArg.IsNotNull(logger, nameof(logger));
EnsureArg.IsNotNull(urlResolver, nameof(urlResolver));
EnsureArg.IsNotNull(searchParameterStatusManager, nameof(searchParameterStatusManager));

_modelInfoProvider = modelInfoProvider;
_searchParameterDefinitionManager = searchParameterDefinitionManagerResolver();
Expand All @@ -75,6 +79,7 @@ public SystemConformanceProvider(
_logger = logger;
_disposed = false;
_urlResolver = urlResolver;
_searchParameterStatusManager = searchParameterStatusManager;
}

public override async Task<ResourceElement> GetCapabilityStatementOnStartup(CancellationToken cancellationToken = default(CancellationToken))
Expand All @@ -94,7 +99,7 @@ public SystemConformanceProvider(
{
if (_listedCapabilityStatement == null)
{
_builder = CapabilityStatementBuilder.Create(_modelInfoProvider, _searchParameterDefinitionManager, _configuration, _supportedProfiles, _urlResolver);
_builder = CapabilityStatementBuilder.Create(_modelInfoProvider, _searchParameterDefinitionManager, _configuration, _supportedProfiles, _urlResolver, _searchParameterStatusManager);

using (IScoped<IEnumerable<IProvideCapability>> providerFactory = _capabilityProviders())
{
Expand Down Expand Up @@ -169,7 +174,7 @@ public async Task BackgroudLoop()
if (_builder != null)
{
// Update search params;
_builder.SyncSearchParameters();
await _builder.SyncSearchParametersAsync(CancellationToken.None);

// Update supported profiles;
_builder.SyncProfiles();
Expand Down Expand Up @@ -241,7 +246,7 @@ public async Task Handle(RebuildCapabilityStatement notification, CancellationTo
{
case RebuildPart.SearchParameter:
// Update search params;
_builder.SyncSearchParameters();
await _builder.SyncSearchParametersAsync(cancellationToken);
break;

case RebuildPart.Profiles:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<None Remove="Data\OperationDefinition\member-match.json" />
<None Remove="Data\OperationDefinition\search-parameter-status.json" />
<None Remove="Features\Security\roles.schema.json" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Data\OperationDefinition\convert-data.json" />
<EmbeddedResource Include="Data\OperationDefinition\member-match.json" />
<EmbeddedResource Include="Data\OperationDefinition\purge-history.json" />
<EmbeddedResource Include="Data\OperationDefinition\search-parameter-status.json" />
<EmbeddedResource Include="Data\R4\BaseCapabilities.json" />
<EmbeddedResource Include="Data\R4\compartment.json" />
<EmbeddedResource Include="Data\R4\search-parameters.json" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -693,7 +693,9 @@ public void Build(ICapabilityStatementBuilder builder)
try
{
watch = Stopwatch.StartNew();
builder = builder.SyncSearchParameters();
var builderTask = Task.Run(() => builder.SyncSearchParametersAsync(CancellationToken.None));
builderTask.Wait();
builder = builderTask.Result;
_logger.LogInformation("CosmosFhirDataStore. 'Search Parameters' built. Elapsed: {ElapsedTime}. Memory: {MemoryInUse}.", watch.Elapsed, GC.GetTotalMemory(forceFullCollection: false));
}
catch (Exception e)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// -------------------------------------------------------------------------------------------------
// 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.Extensions.Options;
using Microsoft.Health.Fhir.Api.Configs;
using Microsoft.Health.Fhir.Api.Features.Operations;
using Microsoft.Health.Fhir.Core.Configs;
using Microsoft.Health.Fhir.Core.Features.Conformance;
using Microsoft.Health.Fhir.Core.Features.Conformance.Models;
using Microsoft.Health.Fhir.Core.Features.Routing;
using Microsoft.Health.Fhir.Tests.Common;
using Microsoft.Health.Test.Utilities;
using NSubstitute;
using Xunit;

namespace Microsoft.Health.Fhir.Api.UnitTests.Features.Operations
{
/// <summary>
/// shared conformance tests
/// </summary>
[Trait(Traits.OwningTeam, OwningTeam.Fhir)]
[Trait(Traits.Category, Categories.Operations)]
public partial class OperationsCapabilityProviderTests
{
private IUrlResolver _urlResolver;
private IOptions<OperationsConfiguration> _operationsOptions = Substitute.For<IOptions<OperationsConfiguration>>();
private IOptions<FeatureConfiguration> _featureOptions = Substitute.For<IOptions<FeatureConfiguration>>();
private IOptions<CoreFeatureConfiguration> _coreFeatureOptions = Substitute.For<IOptions<CoreFeatureConfiguration>>();
private OperationsConfiguration _operationsConfiguration = new OperationsConfiguration();
private CoreFeatureConfiguration _coreFeatureConfiguration = new CoreFeatureConfiguration();
private FeatureConfiguration _featureConfiguration = new FeatureConfiguration();

public OperationsCapabilityProviderTests()
{
_urlResolver = Substitute.For<IUrlResolver>();
_urlResolver.ResolveMetadataUrl(Arg.Any<bool>()).Returns(new System.Uri("https://test.com"));
_operationsOptions.Value.Returns(_operationsConfiguration);
_featureOptions.Value.Returns(_featureConfiguration);
_coreFeatureOptions.Value.Returns(_coreFeatureConfiguration);
}

[Fact]
public void GivenAConformanceBuilder_WhenSupportsSelectableSearchParametersIsEnabled_ThenStatusOperationIsNotAdded()
{
_coreFeatureConfiguration.SupportsSelectableSearchParameters = true;
ListedCapabilityStatement listedCapabilityStatement = new ListedCapabilityStatement();
OperationsCapabilityProvider provider = new OperationsCapabilityProvider(_operationsOptions, _featureOptions, _coreFeatureOptions, _urlResolver);
ICapabilityStatementBuilder builder = Substitute.For<ICapabilityStatementBuilder>();
provider.Build(builder);

builder.Received(3).Apply(Arg.Any<Action<ListedCapabilityStatement>>());
}

[Fact]
public void GivenAConformanceBuilder_WhenSupportsSelectableSearchParametersIsDisabled_ThenStatusOperationIsNotAdded()
{
_coreFeatureConfiguration.SupportsSelectableSearchParameters = false;
ListedCapabilityStatement listedCapabilityStatement = new ListedCapabilityStatement();
OperationsCapabilityProvider provider = new OperationsCapabilityProvider(_operationsOptions, _featureOptions, _coreFeatureOptions, _urlResolver);
ICapabilityStatementBuilder builder = Substitute.For<ICapabilityStatementBuilder>();
provider.Build(builder);

builder.Received(2).Apply(Arg.Any<Action<ListedCapabilityStatement>>());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Features\Headers\FhirResultExtensionsTests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Features\Operations\Import\ImportRequestExtensionsTests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Features\Operations\Import\InitialImportLockMiddlewareTests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Features\Operations\OperationsCapabilityProviderTests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Features\Resources\Bundle\BundleSerializerTests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Features\Resources\Bundle\TransactionExceptionHandlerTests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Features\Resources\Bundle\TransactionBundleValidatorTests.cs" />
Expand Down
Loading