-
Couldn't load subscription status.
- Fork 562
Implementing a duplicate clinical reference (approach 1). #5079
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
base: main
Are you sure you want to change the base?
Implementing a duplicate clinical reference (approach 1). #5079
Conversation
...soft.Health.Fhir.Shared.Core.UnitTests/Features/Guidance/ClinicalReferenceDuplicatorTests.cs
Fixed
Show fixed
Hide fixed
| foreach (var r in duplicateResources) | ||
| { | ||
| var wrapper = new ResourceWrapper( | ||
| r.ToResourceElement(), | ||
| new RawResource(r.ToJson(), FhirResourceFormat.Json, false), | ||
| null, | ||
| false, | ||
| null, | ||
| null, | ||
| null); | ||
| entries.Add(new SearchResultEntry(wrapper)); | ||
| } |
Check notice
Code scanning / CodeQL
Missed opportunity to use Select Note
maps its iteration variable to another variable
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 2 months ago
To fix the problem, we should replace the foreach loop at line 384 that transforms each element in duplicateResources into a SearchResultEntry containing a ResourceWrapper with a single line that projects the transformation using LINQ's .Select. Specifically, we will use .Select to create a list of SearchResultEntry objects from the duplicateResources collection, and then assign the resulting list to entries.
- In
src/Microsoft.Health.Fhir.Shared.Core.UnitTests/Features/Guidance/ClinicalReferenceDuplicatorTests.cs, replace the initialization and population ofentrieswithin the conditional block around lines 381-396 with one assignment:entries = duplicateResources.Select(...).ToList();. - No new methods or imports are needed since
System.Linqis already imported.
-
Copy modified lines R381-R394
| @@ -378,22 +378,20 @@ | ||
| var duplicateResourceIds = duplicateResources.Select(x => x.Id).ToHashSet(StringComparer.OrdinalIgnoreCase); | ||
|
|
||
| // Set up a validation on a request for searching duplicate resources. | ||
| var entries = new List<SearchResultEntry>(); | ||
| if (duplicateResources?.Any() ?? false) | ||
| { | ||
| foreach (var r in duplicateResources) | ||
| { | ||
| var wrapper = new ResourceWrapper( | ||
| r.ToResourceElement(), | ||
| new RawResource(r.ToJson(), FhirResourceFormat.Json, false), | ||
| null, | ||
| false, | ||
| null, | ||
| null, | ||
| null); | ||
| entries.Add(new SearchResultEntry(wrapper)); | ||
| } | ||
| } | ||
| List<SearchResultEntry> entries = duplicateResources?.Any() ?? false | ||
| ? duplicateResources.Select(r => | ||
| { | ||
| var wrapper = new ResourceWrapper( | ||
| r.ToResourceElement(), | ||
| new RawResource(r.ToJson(), FhirResourceFormat.Json, false), | ||
| null, | ||
| false, | ||
| null, | ||
| null, | ||
| null); | ||
| return new SearchResultEntry(wrapper); | ||
| }).ToList() | ||
| : new List<SearchResultEntry>(); | ||
|
|
||
| _searchService.SearchAsync( | ||
| Arg.Is<string>(x => string.Equals(x, duplicateResourceType, StringComparison.OrdinalIgnoreCase)), |
...soft.Health.Fhir.Shared.Core.UnitTests/Features/Guidance/ClinicalReferenceDuplicatorTests.cs
Fixed
Show fixed
Hide fixed
| }; | ||
|
|
||
| var resourceElement = resource.ToResourceElement(); | ||
| var resourceWrapper = CreateResourceWrapper(resourceElement); |
Check warning
Code scanning / CodeQL
Useless assignment to local variable Warning
resourceWrapper
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 2 months ago
The best way to address this problem is to remove the assignment to resourceWrapper on line 149. Since the method's returned value is unused, and there's no evidence in the code that CreateResourceWrapper(resourceElement) must be called for necessary side effects, the assignment and the function call can be safely deleted.
Specifically, in DuplicateClinicalReferenceBehaviorTests.cs, within the method body of GivenDeleteRequest_WhenDuplicateClinicalReferenceIsEnabled_ThenThenDuplicateResourceShouldBeDeleted, simply delete the line:
var resourceWrapper = CreateResourceWrapper(resourceElement);No imports, method definitions, or variable definitions need to be added or changed.
| @@ -146,7 +146,6 @@ | ||
| }; | ||
|
|
||
| var resourceElement = resource.ToResourceElement(); | ||
| var resourceWrapper = CreateResourceWrapper(resourceElement); | ||
| var request = new DeleteResourceRequest( | ||
| new ResourceKey(resource.TypeName, resource.Id), | ||
| DeleteOperation.SoftDelete); |
src/Microsoft.Health.Fhir.Shared.Core/Features/Guidance/ClinicalReferenceDuplicator.cs
Fixed
Show fixed
Hide fixed
src/Microsoft.Health.Fhir.Shared.Core/Features/Guidance/ClinicalReferenceDuplicator.cs
Fixed
Show fixed
Hide fixed
src/Microsoft.Health.Fhir.Shared.Core/Features/Guidance/ClinicalReferenceDuplicator.cs
Fixed
Show fixed
Hide fixed
| EnsureArg.IsNotNull(clinicalReferenceDuplicator, nameof(clinicalReferenceDuplicator)); | ||
| EnsureArg.IsNotNull(logger, nameof(logger)); | ||
|
|
||
| _coreFeatureConfiguration = coreFeatureConfiguration.Value; |
Check warning
Code scanning / CodeQL
Dereferenced variable may be null Warning
coreFeatureConfiguration
this
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 2 months ago
To properly fix this issue, add an explicit null check for the coreFeatureConfiguration parameter before accessing its .Value property. This can be achieved by using EnsureArg.IsNotNull(coreFeatureConfiguration, nameof(coreFeatureConfiguration)); before the existing check on coreFeatureConfiguration.Value. This ensures that the constructor will fail early and clearly if a null parameter is ever passed, and will not cause a fail-later NullReferenceException. No changes to functionality are introduced; only the missing null parameter guard is added. This change should be applied at the very top of the constructor, specifically in the region of lines 29–31.
-
Copy modified line R30
| @@ -27,6 +27,7 @@ | ||
| IOptions<CoreFeatureConfiguration> coreFeatureConfiguration, | ||
| IClinicalReferenceDuplicator clinicalReferenceDuplicator) | ||
| { | ||
| EnsureArg.IsNotNull(coreFeatureConfiguration, nameof(coreFeatureConfiguration)); | ||
| EnsureArg.IsNotNull(coreFeatureConfiguration?.Value, nameof(coreFeatureConfiguration)); | ||
| EnsureArg.IsNotNull(clinicalReferenceDuplicator, nameof(clinicalReferenceDuplicator)); | ||
|
|
src/Microsoft.Health.Fhir.Shared.Core/Features/Guidance/DuplicateClinicalReferenceBehavior.cs
Fixed
Show fixed
Hide fixed
test/Microsoft.Health.Fhir.Shared.Tests.E2E/Rest/Guidance/ClinicalReferenceDuplicatorTests.cs
Fixed
Show fixed
Hide fixed
| foreach (var wrapper in resourceWrappersFound) | ||
| { | ||
| var wrapperUpdated = await UpdateDuplicateResourceInternalAsync( | ||
| resource, | ||
| wrapper, | ||
| cancellationToken); | ||
| duplicateResourceWrappers.Add(wrapperUpdated); | ||
| } |
Check notice
Code scanning / CodeQL
Missed opportunity to use Select Note
maps its iteration variable to another variable
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 2 months ago
To fix the problem, refactor the foreach loop at line 455 that accumulates asynchronously-updated wrappers into a single statement using LINQ's Select and Task.WhenAll. Specifically, project the resourceWrappersFound collection to a collection of tasks using Select, then use Task.WhenAll to await all those tasks in parallel, and finally convert the resulting array to a list. Replace the manual foreach loop with this one-liner, assigning the result to duplicateResourceWrappers.
- Changes are required in
src/Microsoft.Health.Fhir.Shared.Core/Features/Guidance/ClinicalReferenceDuplicator.cs, at the block starting at line 455. - No extra methods or imports are required; all necessary types (
Select,Task.WhenAll,.ToList()) are already available and relevant namespaces are already imported. - No side effects are performed inside the loop; no change to program logic or state is expected.
-
Copy modified lines R455-R463
| @@ -452,14 +452,15 @@ | ||
| } | ||
| else | ||
| { | ||
| foreach (var wrapper in resourceWrappersFound) | ||
| { | ||
| var wrapperUpdated = await UpdateDuplicateResourceInternalAsync( | ||
| resource, | ||
| wrapper, | ||
| cancellationToken); | ||
| duplicateResourceWrappers.Add(wrapperUpdated); | ||
| } | ||
| duplicateResourceWrappers.AddRange( | ||
| (await Task.WhenAll( | ||
| resourceWrappersFound | ||
| .Select(wrapper => | ||
| UpdateDuplicateResourceInternalAsync( | ||
| resource, | ||
| wrapper, | ||
| cancellationToken)))) | ||
| ); | ||
| } | ||
| } | ||
| else |
| }; | ||
|
|
||
| var crefs = GetClinicalReferences(documentReference); | ||
| var codes = crefs.SelectMany(x => x.codes); |
Check warning
Code scanning / CodeQL
Useless assignment to local variable Warning
codes
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 2 months ago
To fix the problem, the assignment to codes should be removed from line 595 in ClinicalReferenceDuplicator.cs. The result of crefs.SelectMany(x => x.codes) is never used via the codes variable; instead, the same expression is used directly in the subsequent foreach loop. Thus, simply delete the line. No further changes are needed. No additional imports, definitions, or method changes are required.
| @@ -592,7 +592,6 @@ | ||
| }; | ||
|
|
||
| var crefs = GetClinicalReferences(documentReference); | ||
| var codes = crefs.SelectMany(x => x.codes); | ||
| diagnosticReport.Code = new CodeableConcept(); | ||
| foreach (var code in crefs.SelectMany(x => x.codes)) | ||
| { |
| return documentReference.Content | ||
| .SelectMany(x => x?.Profile? | ||
| .Where(y => y?.Value?.GetType() == typeof(Coding) | ||
| && ClinicalReferenceSystems.Contains(((Coding)y.Value)?.System) |
Check warning
Code scanning / CodeQL
Dereferenced variable may be null Warning
y
this
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 2 months ago
To fix the problem, add a null check for y before dereferencing it in the Select clause. The best approach is to check if y is not null before casting y.Value as Coding. This can be done by modifying the lambda expression in the Select statement such that it returns (Coding)y.Value only if y is not null, otherwise returns null, and then filter out nulls before further processing (via Where, for example).
The code to change is in src/Microsoft.Health.Fhir.Shared.Core/Features/Guidance/ClinicalReferenceDuplicator.cs, specifically lines 630-634 for the block under the #else conditional. We will update the SelectMany so that y is checked for null before casting y.Value to Coding, and filter the result to non-null values.
-
Copy modified line R631 -
Copy modified lines R634-R636
| @@ -628,10 +628,12 @@ | ||
| #else | ||
| return documentReference.Content | ||
| .SelectMany(x => x?.Profile? | ||
| .Where(y => y?.Value?.GetType() == typeof(Coding) | ||
| .Where(y => y != null && y?.Value?.GetType() == typeof(Coding) | ||
| && ClinicalReferenceSystems.Contains(((Coding)y.Value)?.System) | ||
| && ClinicalReferenceCodes.Contains(((Coding)y.Value)?.Code)) | ||
| .Select(y => (Coding)y.Value)) | ||
| .Select(y => y != null ? (Coding)y.Value : null) | ||
| ) | ||
| .Where(x => x != null) | ||
| .DistinctBy(x => ClinicalReferenceDuplicatorHelper.ConvertToString(x)) | ||
| .ToList(); | ||
| #endif |
| foreach (var attachment in diagnosticReport.PresentedForm) | ||
| { | ||
| if (!documentReference.Content.Any( | ||
| x => ClinicalReferenceDuplicatorHelper.CompareCoding(x.Format, code) | ||
| && ClinicalReferenceDuplicatorHelper.CompareAttachment(x.Attachment, attachment))) | ||
| { | ||
| documentReference.Content.Add( | ||
| new DocumentReference.ContentComponent() | ||
| { | ||
| Attachment = attachment, | ||
| Format = code, | ||
| }); | ||
| resourceUpdated = true; | ||
| } | ||
| } |
Check notice
Code scanning / CodeQL
Missed opportunity to use Where Note
implicitly filters its target sequence
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 2 months ago
To fix the problem, refactor the foreach loop at line 731 so that it iterates only over those attachment elements of diagnosticReport.PresentedForm for which !documentReference.Content.Any(...) holds. This is done by replacing the loop:
foreach (var attachment in diagnosticReport.PresentedForm)
{
if (!documentReference.Content.Any(...))
{
// Loop body
}
}with:
foreach (var attachment in diagnosticReport.PresentedForm
.Where(attachment => !documentReference.Content.Any(
x => ClinicalReferenceDuplicatorHelper.CompareCoding(x.Format, code)
&& ClinicalReferenceDuplicatorHelper.CompareAttachment(x.Attachment, attachment))))
{
// Loop body (without guard if)
}Inside the rewritten loop, remove the redundant if-statement and directly execute the logic.
Only the code in file src/Microsoft.Health.Fhir.Shared.Core/Features/Guidance/ClinicalReferenceDuplicator.cs, lines around 730–745, needs to be changed.
No new imports, methods, or definitions are needed as LINQ is already imported.
-
Copy modified lines R731-R734 -
Copy modified lines R736-R742
| @@ -728,20 +728,18 @@ | ||
| #if R4 || R4B || Stu3 | ||
| foreach (var code in GetClinicalReferenceCodes(diagnosticReport)) | ||
| { | ||
| foreach (var attachment in diagnosticReport.PresentedForm) | ||
| foreach (var attachment in diagnosticReport.PresentedForm | ||
| .Where(attachment => !documentReference.Content.Any( | ||
| x => ClinicalReferenceDuplicatorHelper.CompareCoding(x.Format, code) | ||
| && ClinicalReferenceDuplicatorHelper.CompareAttachment(x.Attachment, attachment)))) | ||
| { | ||
| if (!documentReference.Content.Any( | ||
| x => ClinicalReferenceDuplicatorHelper.CompareCoding(x.Format, code) | ||
| && ClinicalReferenceDuplicatorHelper.CompareAttachment(x.Attachment, attachment))) | ||
| { | ||
| documentReference.Content.Add( | ||
| new DocumentReference.ContentComponent() | ||
| { | ||
| Attachment = attachment, | ||
| Format = code, | ||
| }); | ||
| resourceUpdated = true; | ||
| } | ||
| documentReference.Content.Add( | ||
| new DocumentReference.ContentComponent() | ||
| { | ||
| Attachment = attachment, | ||
| Format = code, | ||
| }); | ||
| resourceUpdated = true; | ||
| } | ||
| } | ||
| #else |
| foreach (var content in contents) | ||
| { | ||
| if (!ClinicalReferenceDuplicatorHelper.CompareCodings(content.Profile, profiles)) | ||
| { | ||
| content.Profile = content.Profile | ||
| .UnionBy( | ||
| profiles, | ||
| x => (x.Value is Coding) ? (((Coding)x.Value).System, ((Coding)x.Value).Code) : (string.Empty, string.Empty)) | ||
| .ToList(); | ||
| resourceUpdated = true; | ||
| } | ||
| } |
Check notice
Code scanning / CodeQL
Missed opportunity to use Where Note
implicitly filters its target sequence
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 2 months ago
The best fix is to filter contents with .Where(...) so the loop iterates only over those content objects where !ClinicalReferenceDuplicatorHelper.CompareCodings(content.Profile, profiles) holds. Specifically, refactor the foreach to:
foreach (var content in contents.Where(content => !ClinicalReferenceDuplicatorHelper.CompareCodings(content.Profile, profiles)))
{
// body as before, without the if statement
}Then, remove the inner if (now redundant), and dedent the loop body. This reduces the nesting level and clarifies that only content not matching the codings will be processed. No new imports or helpers are required; System.Linq is already imported.
Only the block spanning lines 772–782 in file src/Microsoft.Health.Fhir.Shared.Core/Features/Guidance/ClinicalReferenceDuplicator.cs needs to be changed.
-
Copy modified line R772 -
Copy modified lines R774-R779
| @@ -769,17 +769,14 @@ | ||
| continue; | ||
| } | ||
|
|
||
| foreach (var content in contents) | ||
| foreach (var content in contents.Where(content => !ClinicalReferenceDuplicatorHelper.CompareCodings(content.Profile, profiles))) | ||
| { | ||
| if (!ClinicalReferenceDuplicatorHelper.CompareCodings(content.Profile, profiles)) | ||
| { | ||
| content.Profile = content.Profile | ||
| .UnionBy( | ||
| profiles, | ||
| x => (x.Value is Coding) ? (((Coding)x.Value).System, ((Coding)x.Value).Code) : (string.Empty, string.Empty)) | ||
| .ToList(); | ||
| resourceUpdated = true; | ||
| } | ||
| content.Profile = content.Profile | ||
| .UnionBy( | ||
| profiles, | ||
| x => (x.Value is Coding) ? (((Coding)x.Value).System, ((Coding)x.Value).Code) : (string.Empty, string.Empty)) | ||
| .ToList(); | ||
| resourceUpdated = true; | ||
| } | ||
| } | ||
| #endif |
| } | ||
|
|
||
| return CompareCodings( | ||
| x.Where(xx => xx.Value?.GetType() == typeof(Coding)).Select(xx => (Coding)xx.Value).ToList(), |
Check warning
Code scanning / CodeQL
Dereferenced variable may be null Warning
x
this
|
|
||
| return CompareCodings( | ||
| x.Where(xx => xx.Value?.GetType() == typeof(Coding)).Select(xx => (Coding)xx.Value).ToList(), | ||
| y.Where(yy => yy.Value?.GetType() == typeof(Coding)).Select(yy => (Coding)yy.Value).ToList()); |
Check warning
Code scanning / CodeQL
Dereferenced variable may be null Warning
y
this
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 2 months ago
To ensure the code does not dereference a possibly-null variable, we should add an explicit check returning true if both x and y are null, and false if only one is null. This pattern matches the one used in other comparison methods throughout the file (e.g., CompareAttachment and CompareContent). Specifically, insert a block after the method signature to return true if both are null, and false if either is null. Then, the rest of the function is only used when both lists are definitely non-null. All changes belong in src/Microsoft.Health.Fhir.Shared.Core/Features/Guidance/ClinicalReferenceDuplicatorHelper.cs within the CompareCodings(IReadOnlyList<DocumentReference.ProfileComponent> x, IReadOnlyList<DocumentReference.ProfileComponent> y) method body.
-
Copy modified line R69 -
Copy modified lines R71-R75
| @@ -66,8 +66,13 @@ | ||
| #if !R4 && !R4B && !Stu3 | ||
| public static bool CompareCodings(IReadOnlyList<DocumentReference.ProfileComponent> x, IReadOnlyList<DocumentReference.ProfileComponent> y) | ||
| { | ||
| if ((x?.Count ?? 0) != (y?.Count ?? 0)) | ||
| if (x == null || y == null) | ||
| { | ||
| return x == null && y == null; | ||
| } | ||
|
|
||
| if (x.Count != y.Count) | ||
| { | ||
| return false; | ||
| } | ||
|
|
| return false; | ||
| } | ||
|
|
||
| return x.All(xx => y.Any(yy => CompareContent(xx, yy))); |
Check warning
Code scanning / CodeQL
Dereferenced variable may be null Warning
x
this
Variable
x
this
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 2 months ago
To safely handle the possibility of x and/or y being null, we should explicitly check for null before calling methods on these variables. Specifically:
- Before using
x.All()ory.Any(), we should check if either variable isnull. - Since the logic already checks for count mismatches at line 96, if both are null, this check passes and we end up dereferencing
xon line 101. - The best way to preserve functionality and avoid changing method semantics is to add an explicit check: if both
xandyare null, they are considered equal and the function should returntrue. If one is null and the other is not, returnfalse. If neither is null, proceed with the original logic. - This fix should replace the region of the code starting from the null-check and covering the method logic in
CompareContents(lines 95-102). No new imports or dependencies are needed.
-
Copy modified lines R96-R97 -
Copy modified lines R99-R103
| @@ -93,8 +93,14 @@ | ||
|
|
||
| public static bool CompareContents(IReadOnlyList<DocumentReference.ContentComponent> x, IReadOnlyList<DocumentReference.ContentComponent> y) | ||
| { | ||
| if ((x?.Count ?? 0) != (y?.Count ?? 0)) | ||
| // If both are null, consider them equal; if only one is null, consider them unequal. | ||
| if (x == null || y == null) | ||
| { | ||
| return x == null && y == null; | ||
| } | ||
|
|
||
| if (x.Count != y.Count) | ||
| { | ||
| return false; | ||
| } | ||
|
|
| public async Task GivenResource_WhenDeleting_ThenDuplicateResourceShouldBeDeleted( | ||
| Resource resource) | ||
| { | ||
| (var source, var duplicate) = await CreateResourceAsync(resource); |
Check warning
Code scanning / CodeQL
Useless assignment to local variable Warning test
duplicate
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 2 months ago
To fix the problem, simply discard the unused variable assignment by replacing var duplicate with _ in the deconstruction. This makes it clear to readers and maintainers of the code that the second value returned by CreateResourceAsync(resource) is intentionally ignored since only source is needed. This should be changed only in the relevant location in the method GivenResource_WhenDeleting_ThenDuplicateResourceShouldBeDeleted. No other method, import or definition changes are needed.
-
Copy modified line R56
| @@ -53,7 +53,7 @@ | ||
| public async Task GivenResource_WhenDeleting_ThenDuplicateResourceShouldBeDeleted( | ||
| Resource resource) | ||
| { | ||
| (var source, var duplicate) = await CreateResourceAsync(resource); | ||
| (var source, _) = await CreateResourceAsync(resource); | ||
| await DeleteResourceAsync(source); | ||
| } | ||
|
|
| string subject, | ||
| List<Attachment> attachments) | ||
| { | ||
| (var source, var duplicate) = await CreateResourceAsync(resource); |
Check warning
Code scanning / CodeQL
Useless assignment to local variable Warning test
duplicate
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 2 months ago
To fix the problem, replace the assignment to var duplicate with a discard assignment (_). This means changing the code from (var source, var duplicate) to (var source, _) on line 67 inside the method GivenResource_WhenUpdating_ThenDuplicateResourceShouldBeUpdated. This will clarify that the second item of the returned tuple is intentionally not used, and will prevent any future confusion about unused variables. No imports or additional definitions are needed for this fix, as discards are standard in C# for ignoring unused tuple elements.
-
Copy modified line R67
| @@ -64,7 +64,7 @@ | ||
| string subject, | ||
| List<Attachment> attachments) | ||
| { | ||
| (var source, var duplicate) = await CreateResourceAsync(resource); | ||
| (var source, _) = await CreateResourceAsync(resource); | ||
| await UpdateResourceAsync(source, subject, attachments); | ||
| } | ||
|
|
Description
Implementing a duplicate clinical reference feature (approach 1).
Related issues
Addresses [issue #158013].
User Story 158013: Duplicate url information on 2 properties for DocumentReference
Testing
Tested by adding UTs.
FHIR Team Checklist
Semver Change (docs)
Patch|Skip|Feature|Breaking (reason)