Skip to content

[DO NOT REVIEW] Add subscriptions and SqlonFhir View Definitions#5478

Open
feordin wants to merge 124 commits intomainfrom
feature/subscription-sqlonfhir
Open

[DO NOT REVIEW] Add subscriptions and SqlonFhir View Definitions#5478
feordin wants to merge 124 commits intomainfrom
feature/subscription-sqlonfhir

Conversation

@feordin
Copy link
Copy Markdown
Contributor

@feordin feordin commented Mar 31, 2026

Description

Describe the changes in this PR.

Related issues

Addresses [issue #].

Testing

Describe how this change was tested.

FHIR Team Checklist

  • Update the title of the PR to be succinct and less than 65 characters
  • Add a milestone to the PR for the sprint that it is merged (i.e. add S47)
  • Tag the PR with the type of update: Bug, Build, Dependencies, Enhancement, New-Feature or Documentation
  • Tag the PR with Open source, Azure API for FHIR (CosmosDB or common code) or Azure Healthcare APIs (SQL or common code) to specify where this change is intended to be released.
  • Tag the PR with Schema Version backward compatible or Schema Version backward incompatible or Schema Version unchanged if this adds or updates Sql script which is/is not backward compatible with the code.
  • When changing or adding behavior, if your code modifies the system design or changes design assumptions, please create and include an ADR.
  • CI is green before merge Build Status
  • Review squash-merge requirements

Semver Change (docs)

Patch|Skip|Feature|Breaking (reason)

feordin and others added 30 commits March 31, 2026 23:01
The route uses {idParameter} (from KnownActionParameterNames.Id) but the
controller parameters were named 'id' without specifying the route name.
Added FromRoute(Name = KnownActionParameterNames.Id) to match.

Also changed handler exceptions from InvalidOperationException (500) to
ResourceNotFoundException (404) for missing ViewDefinitions/tables.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…riginal

Add GET /ViewDefinition that returns all registered ViewDefinitions and
their materialization status. Useful for debugging registration state.

Reverted the status route back to ViewDefinition/{id} (not \) since
ViewDefinition is not a FHIR resource type and shouldn't collide with
the generic {typeParameter}/{idParameter} route.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…points

GET /ViewDefinition/{id} now returns a Parameters resource with named
parameters (viewDefinitionName, resourceType, status, tableExists,
subscriptionId, libraryResourceId, errorMessage, registeredAt).

GET /ViewDefinition returns a Bundle of Parameters resources.

Updated Blazor app to parse the Parameters resource format.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Changed forceOneActiveJobGroup from true to false so multiple
ViewDefinition population jobs can run concurrently. Previously,
registering a second ViewDefinition while the first was populating
caused 'There are other active job groups' error.

Made job enqueue best-effort — if it fails, the registration still
completes (subscription will handle incremental updates). This prevents
a queue failure from blocking subscription and Library creation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Bump ViewDefinitionRefreshChannel.PublishAsync entry log to Information
level so materialization events are visible in default log output.
Add startup logging to UseSqlOnFhirChannels to confirm channel factory
registration succeeds.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
SubscriptionChannelFactory wasn't resolvable from app.ApplicationServices
during Configure() because it's registered via Health Extensions DI which
resolves through the scoped provider. Changed to a one-time middleware
that registers the channel on the first HTTP request using
context.RequestServices, where the factory is guaranteed to be available.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add debug logging when Library deletion doesn't match a registration,
to help trace why cleanup doesn't fire for ViewDefinitions that
errored during registration (LibraryResourceId not set).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ion cooldown

1. Blazor app: delete existing Library with same ViewDef name before
   creating a new one, preventing duplicate Libraries on re-register.

2. Cleanup behavior: fallback matching for registrations without
   LibraryResourceId (errored during registration). Scans all
   registrations for orphaned entries when ID match fails.

3. Sync service: 30-second cooldown after eviction prevents re-adopting
   a just-deleted ViewDefinition before the soft-deleted Library
   disappears from search results.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Blazor app now uses PUT Library/viewdef-{name} with a deterministic ID,
so re-registering updates the existing Library instead of creating
duplicates. This also enables testing the update path.

Server-side: ViewDefinitionLibraryRegistrationBehavior now implements
both IPipelineBehavior<CreateResourceRequest> and
IPipelineBehavior<UpsertResourceRequest> to intercept both POST and PUT.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
FHIR resource IDs only allow [A-Za-z0-9\-\.]. ViewDefinition names like
'patient_demographics' contain underscores, making 'viewdef-patient_demographics'
an invalid FHIR ID. Now uses 'viewdef-patient-demographics'.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…eues

The job host only polls queue types listed in the
HostingBackgroundServiceQueues configuration. ViewDefinitionPopulation
was missing, so population jobs were enqueued but never dequeued.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The population processing job now publishes a
ViewDefinitionPopulationCompleteNotification when all batches are done.
The subscription manager handles this to update the in-memory status
from Populating → Active (or Error).

RegisterAsync no longer prematurely sets status to Active. Status stays
Populating until the job completes, giving clients accurate progress
tracking via GET ViewDefinition/{name}.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The job host discovers queues via OperationsConfiguration properties that
inherit from HostingBackgroundServiceQueueItem, not from the
HostingBackgroundServiceQueues JSON array. Added
ViewDefinitionPopulationJobConfiguration and wired it into
OperationsConfiguration so the job host polls QueueType 8.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Patient and Condition entries had trailing commas, plus the loop added
another comma via the if(entries.Length > 0) check — producing double
commas between the last Observation of one patient and the first Patient
of the next. Changed to use leading commas on Condition and Observation
entries instead.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Re-PUTting the same Library resource was resetting status to Populating
and trying to enqueue a new population job (which silently failed).
Now compares the ViewDefinition JSON — if unchanged and already
Active or Populating, returns the existing registration as-is.

If the content actually changed, full re-registration proceeds
(drop table, new schema, new population job).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Wire MaterializationTarget through the full registration, adoption, sync,
and materialization pipeline:

- Add MaterializationTargetExtensionUrl constant for persisting target on
  Library resources (survives restarts, visible to other nodes)
- Update IViewDefinitionSubscriptionManager.RegisterAsync/AdoptAsync to
  accept optional MaterializationTarget parameter (falls back to config
  DefaultTarget)
- Inject SqlOnFhirMaterializationConfiguration into
  ViewDefinitionSubscriptionManager for resolving default target
- Extract materialization-target extension from Library resources in both
  ViewDefinitionSyncService and ViewDefinitionLibraryRegistrationBehavior
- Update ViewDefinitionRefreshChannel to use MaterializerFactory with
  per-registration target instead of hardcoded IViewDefinitionMaterializer
- Update ViewDefinitionPopulationProcessingJob to use MaterializerFactory
  with per-registration target for bulk population
- Persist materialization-target extension on Library resources alongside
  materialization-status
- Update all affected unit tests for new constructor signatures

Customers can now specify SqlServer, Parquet, or Fabric (Delta Lake) as
the materialization target per-ViewDefinition via a FHIR extension on
the Library resource. Without the extension, the server-wide DefaultTarget
from SqlOnFhirMaterialization config is used.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add global target dropdown (SQL Server / Parquet / Fabric) in the
  ViewDefinition Registration card header
- Add per-ViewDefinition target override dropdown (visible while Pending)
- Show target badge (SQL/Parquet/Fabric) on each registered ViewDefinition
- Update FhirDemoService.RegisterViewDefinitionAsync to accept optional
  target parameter and include materialization-target extension on the
  Library resource when specified
- Add MaterializationTargetExtensionUrl constant to FhirDemoService

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants