diff --git a/CHANGELOG.md b/CHANGELOG.md index ef44ff0d..2e406801 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ This log documents significant changes for the [@aehrc/smart-forms-renderer](htt Changelog only includes changes from version 0.36.0 onwards. +## [0.44.0] - 2024-10-09 +### Added +- Added support for the [preferredTerminologyServer](https://hl7.org/fhir/uv/sdc/STU3/StructureDefinition-sdc-questionnaire-preferredTerminologyServer.html) SDC extension. + ## [0.43.1] - 2024-10-04 ### Changed - Completely removed persisting "iframe-resizer" dependencies as a follow-up to v0.43.0. diff --git a/apps/demo-renderer-app/package-lock.json b/apps/demo-renderer-app/package-lock.json index 2a65d74b..1bb4b6db 100644 --- a/apps/demo-renderer-app/package-lock.json +++ b/apps/demo-renderer-app/package-lock.json @@ -9,7 +9,7 @@ "version": "0.0.0", "dependencies": { "@aehrc/sdc-populate": "^2.3.1", - "@aehrc/smart-forms-renderer": "^0.43.1", + "@aehrc/smart-forms-renderer": "^0.44.0", "@radix-ui/react-collapsible": "^1.1.0", "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-switch": "^1.1.0", @@ -56,9 +56,9 @@ } }, "node_modules/@aehrc/smart-forms-renderer": { - "version": "0.43.1", - "resolved": "https://registry.npmjs.org/@aehrc/smart-forms-renderer/-/smart-forms-renderer-0.43.1.tgz", - "integrity": "sha512-WKhstIDRJekJOBROW6jqqD+QtVeicxzlgWvzJOs3jbrlOXpArvikw2NKvljQV92tkVC9T9QXFLohoq4gbZE00Q==", + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/@aehrc/smart-forms-renderer/-/smart-forms-renderer-0.44.0.tgz", + "integrity": "sha512-A80bYDLDBovDiXarvH3dlzxlzd2stiFEyBHrzF7zvlVdqIU/yS10zllMhJQsrxQ3/JPNk3j/5/C7h5MvHz8noA==", "dependencies": { "@aehrc/sdc-populate": "^2.3.1", "@iconify/react": "^4.1.1", diff --git a/apps/demo-renderer-app/package.json b/apps/demo-renderer-app/package.json index 639d285b..61deb342 100644 --- a/apps/demo-renderer-app/package.json +++ b/apps/demo-renderer-app/package.json @@ -12,7 +12,7 @@ }, "dependencies": { "@aehrc/sdc-populate": "^2.3.1", - "@aehrc/smart-forms-renderer": "^0.43.1", + "@aehrc/smart-forms-renderer": "^0.44.0", "@radix-ui/react-collapsible": "^1.1.0", "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-switch": "^1.1.0", diff --git a/apps/smart-forms-app/package.json b/apps/smart-forms-app/package.json index 36e73fa1..7d1a13c6 100644 --- a/apps/smart-forms-app/package.json +++ b/apps/smart-forms-app/package.json @@ -28,7 +28,7 @@ "dependencies": { "@aehrc/sdc-assemble": "^1.3.1", "@aehrc/sdc-populate": "^2.3.1", - "@aehrc/smart-forms-renderer": "^0.43.1", + "@aehrc/smart-forms-renderer": "^0.44.0", "@emotion/react": "^11.13.0", "@emotion/styled": "^11.13.0", "@fontsource/material-icons": "^5.0.18", diff --git a/apps/smart-forms-app/src/features/playground/components/StoreStateViewers/QuestionnaireStoreViewer.tsx b/apps/smart-forms-app/src/features/playground/components/StoreStateViewers/QuestionnaireStoreViewer.tsx index 98da9baa..642b0ecc 100644 --- a/apps/smart-forms-app/src/features/playground/components/StoreStateViewers/QuestionnaireStoreViewer.tsx +++ b/apps/smart-forms-app/src/features/playground/components/StoreStateViewers/QuestionnaireStoreViewer.tsx @@ -6,6 +6,7 @@ import GenericStatePropertyPicker from './GenericStatePropertyPicker.tsx'; const questionnaireStoreStatePropertyNames: string[] = [ 'sourceQuestionnaire', 'itemTypes', + 'itemPreferredTerminologyServers', 'tabs', 'currentTabIndex', 'variables', diff --git a/apps/smart-forms-app/src/features/playground/hooks/useShowQuestionnaireStoreProperty.ts b/apps/smart-forms-app/src/features/playground/hooks/useShowQuestionnaireStoreProperty.ts index 6a46b0ca..24ff890a 100644 --- a/apps/smart-forms-app/src/features/playground/hooks/useShowQuestionnaireStoreProperty.ts +++ b/apps/smart-forms-app/src/features/playground/hooks/useShowQuestionnaireStoreProperty.ts @@ -20,6 +20,8 @@ import { useQuestionnaireStore } from '@aehrc/smart-forms-renderer'; function useShowQuestionnaireStoreProperty(selectedProperty: string) { const sourceQuestionnaire = useQuestionnaireStore.use.sourceQuestionnaire(); const itemTypes = useQuestionnaireStore.use.itemTypes(); + const itemPreferredTerminologyServers = + useQuestionnaireStore.use.itemPreferredTerminologyServers(); const tabs = useQuestionnaireStore.use.tabs(); const currentTabIndex = useQuestionnaireStore.use.currentTabIndex(); const variables = useQuestionnaireStore.use.variables(); @@ -42,6 +44,7 @@ function useShowQuestionnaireStoreProperty(selectedProperty: string) { { sourceQuestionnaire, itemTypes, + itemPreferredTerminologyServers, tabs, currentTabIndex, variables, diff --git a/documentation/docs/api/smart-forms-renderer/interfaces/QuestionnaireStoreType.md b/documentation/docs/api/smart-forms-renderer/interfaces/QuestionnaireStoreType.md index 37b605b2..8c7bd9c0 100644 --- a/documentation/docs/api/smart-forms-renderer/interfaces/QuestionnaireStoreType.md +++ b/documentation/docs/api/smart-forms-renderer/interfaces/QuestionnaireStoreType.md @@ -153,6 +153,14 @@ LinkId of the currently focused item *** +### itemPreferredTerminologyServers + +> **itemPreferredTerminologyServers**: `Record`\<`string`, `string`\> + +Key-value pair of item types `Record` + +*** + ### itemTypes > **itemTypes**: `Record`\<`string`, `string`\> diff --git a/documentation/docs/api/smart-forms-renderer/variables/useQuestionnaireStore.md b/documentation/docs/api/smart-forms-renderer/variables/useQuestionnaireStore.md index 446292e4..a6440ad9 100644 --- a/documentation/docs/api/smart-forms-renderer/variables/useQuestionnaireStore.md +++ b/documentation/docs/api/smart-forms-renderer/variables/useQuestionnaireStore.md @@ -165,6 +165,14 @@ This is the React version of the store which can be used as React hooks in React `Record`\<`string`, `InitialExpression`\> +### use.itemPreferredTerminologyServers() + +> **itemPreferredTerminologyServers**: () => `Record`\<`string`, `string`\> + +#### Returns + +`Record`\<`string`, `string`\> + ### use.itemTypes() > **itemTypes**: () => `Record`\<`string`, `string`\> diff --git a/documentation/docs/sdc/terminology.mdx b/documentation/docs/sdc/terminology.mdx index a195f4ca..45e6389e 100644 --- a/documentation/docs/sdc/terminology.mdx +++ b/documentation/docs/sdc/terminology.mdx @@ -14,9 +14,10 @@ The elements/extensions that fall under this category are: - [answerValueSet](http://hl7.org/fhir/uv/sdc/behavior.html#answerValueSet) - [answerExpression](http://hl7.org/fhir/uv/sdc/expressions.html#answerExpression) -There is also another extension that can be used to define the unit of a quantity item: +Other related extensions: -- [unitOption](https://hl7.org/fhir/uv/sdc/behavior.html#unitOption) +- [unitOption](https://hl7.org/fhir/uv/sdc/behavior.html#unitOption) - Provide selection choices for the unit of a quantity item +- [preferredTerminologyServer](https://build.fhir.org/ig/HL7/sdc/StructureDefinition-sdc-questionnaire-preferredTerminologyServer.html) - Specify the preferred terminology server to use for terminology requests :::note @@ -169,3 +170,14 @@ This can be used when the unit is not fixed, and allows the user to choose from storyId="component-itemtype-quantity--quantity-unit-option-response" initialHeight={280} /> + +### PreferredTerminologyServer + +PreferredTerminologyServer is used to specify the preferred terminology server to use for terminology requests associated with the whole questionnaire or a particular group or question within the questionnaire (depending on where the extension appears). + +#### Basic usage + + diff --git a/documentation/package-lock.json b/documentation/package-lock.json index 90aa127e..0804189f 100644 --- a/documentation/package-lock.json +++ b/documentation/package-lock.json @@ -8,7 +8,7 @@ "name": "@aehrc/smart-forms-documentation", "version": "0.0.0", "dependencies": { - "@aehrc/smart-forms-renderer": "^0.40.1", + "@aehrc/smart-forms-renderer": "^0.44.0", "@docusaurus/core": "^3.4.0", "@docusaurus/preset-classic": "^3.4.0", "@docusaurus/theme-live-codeblock": "^3.4.0", diff --git a/documentation/package.json b/documentation/package.json index 576ce58d..28870fa0 100644 --- a/documentation/package.json +++ b/documentation/package.json @@ -15,7 +15,7 @@ "typecheck": "tsc" }, "dependencies": { - "@aehrc/smart-forms-renderer": "^0.43.1", + "@aehrc/smart-forms-renderer": "^0.44.0", "@docusaurus/core": "^3.4.0", "@docusaurus/preset-classic": "^3.4.0", "@docusaurus/theme-live-codeblock": "^3.4.0", diff --git a/package-lock.json b/package-lock.json index 7ac0671c..5f1fee03 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,7 @@ "dependencies": { "@aehrc/sdc-assemble": "^1.3.1", "@aehrc/sdc-populate": "^2.3.1", - "@aehrc/smart-forms-renderer": "^0.43.1", + "@aehrc/smart-forms-renderer": "^0.44.0", "@emotion/react": "^11.13.0", "@emotion/styled": "^11.13.0", "@fontsource/material-icons": "^5.0.18", @@ -451,7 +451,7 @@ "name": "@aehrc/smart-forms-documentation", "version": "0.0.0", "dependencies": { - "@aehrc/smart-forms-renderer": "^0.43.1", + "@aehrc/smart-forms-renderer": "^0.44.0", "@docusaurus/core": "^3.4.0", "@docusaurus/preset-classic": "^3.4.0", "@docusaurus/theme-live-codeblock": "^3.4.0", @@ -39532,7 +39532,7 @@ }, "packages/smart-forms-renderer": { "name": "@aehrc/smart-forms-renderer", - "version": "0.43.1", + "version": "0.44.0", "license": "Apache-2.0", "dependencies": { "@aehrc/sdc-populate": "^2.3.1", diff --git a/packages/smart-forms-renderer/.storybook/preview.tsx b/packages/smart-forms-renderer/.storybook/preview.tsx index 84861f54..4a10e05b 100644 --- a/packages/smart-forms-renderer/.storybook/preview.tsx +++ b/packages/smart-forms-renderer/.storybook/preview.tsx @@ -7,7 +7,7 @@ import '@fontsource/material-icons'; import { withThemeFromJSXProvider } from '@storybook/addon-styling'; import { createTheme } from '@mui/material/styles'; import { CssBaseline, ThemeProvider } from '@mui/material'; -import './iframeResizerChild'; +import '../src/stories/storybookWrappers/iframeResizerChild.js'; export const decorators = [ withThemeFromJSXProvider({ diff --git a/packages/smart-forms-renderer/package.json b/packages/smart-forms-renderer/package.json index 6feb9811..06980e77 100644 --- a/packages/smart-forms-renderer/package.json +++ b/packages/smart-forms-renderer/package.json @@ -1,6 +1,6 @@ { "name": "@aehrc/smart-forms-renderer", - "version": "0.43.1", + "version": "0.44.0", "description": "FHIR Structured Data Captured (SDC) rendering engine for Smart Forms", "main": "lib/index.js", "scripts": { @@ -94,6 +94,5 @@ "ts-jest": "^29.1.1", "tslib": "^2.6.3", "typescript": "^5.2.2" - }, - "sideEffects": false + } } diff --git a/packages/smart-forms-renderer/src/hooks/useTerminologyServerQuery.ts b/packages/smart-forms-renderer/src/hooks/useTerminologyServerQuery.ts index 721a1b9e..569d5357 100644 --- a/packages/smart-forms-renderer/src/hooks/useTerminologyServerQuery.ts +++ b/packages/smart-forms-renderer/src/hooks/useTerminologyServerQuery.ts @@ -29,6 +29,8 @@ function useTerminologyServerQuery( searchTerm: string ): { options: Coding[]; loading: boolean; feedback?: { message: string; color: AlertColor } } { const processedValueSetUrls = useQuestionnaireStore.use.processedValueSetUrls(); + const itemPreferredTerminologyServers = + useQuestionnaireStore.use.itemPreferredTerminologyServers(); const defaultTerminologyServerUrl = useTerminologyServerStore.use.url(); let fullUrl = ''; @@ -61,7 +63,9 @@ function useTerminologyServerQuery( } // Perform query - const terminologyServerUrl = getTerminologyServerUrl(qItem) ?? defaultTerminologyServerUrl; + const preferredTerminologyServerUrl = itemPreferredTerminologyServers[qItem.linkId]; + const terminologyServerUrl = + getTerminologyServerUrl(qItem) ?? preferredTerminologyServerUrl ?? defaultTerminologyServerUrl; const { isFetching, error, data } = useQuery( ['expandValueSet', fullUrl], () => getValueSetPromise(fullUrl, terminologyServerUrl), diff --git a/packages/smart-forms-renderer/src/hooks/useValueSetCodings.ts b/packages/smart-forms-renderer/src/hooks/useValueSetCodings.ts index 48384ba6..e452dae4 100644 --- a/packages/smart-forms-renderer/src/hooks/useValueSetCodings.ts +++ b/packages/smart-forms-renderer/src/hooks/useValueSetCodings.ts @@ -47,6 +47,8 @@ function useValueSetCodings(qItem: QuestionnaireItem): { const cachedValueSetCodings = useQuestionnaireStore.use.cachedValueSetCodings(); const addCodingToCache = useQuestionnaireStore.use.addCodingToCache(); const { xFhirQueryVariables } = useQuestionnaireStore.use.variables(); + const itemPreferredTerminologyServers = + useQuestionnaireStore.use.itemPreferredTerminologyServers(); const defaultTerminologyServerUrl = useTerminologyServerStore.use.url(); @@ -138,7 +140,11 @@ function useValueSetCodings(qItem: QuestionnaireItem): { const valueSetUrl = qItem.answerValueSet; if (!valueSetUrl || codings.length > 0) return; - const terminologyServerUrl = getTerminologyServerUrl(qItem) ?? defaultTerminologyServerUrl; + const preferredTerminologyServerUrl = itemPreferredTerminologyServers[qItem.linkId]; + const terminologyServerUrl = + getTerminologyServerUrl(qItem) ?? + preferredTerminologyServerUrl ?? + defaultTerminologyServerUrl; const promise = getValueSetPromise(valueSetUrl, terminologyServerUrl); if (promise) { promise diff --git a/packages/smart-forms-renderer/src/interfaces/questionnaireStore.interface.ts b/packages/smart-forms-renderer/src/interfaces/questionnaireStore.interface.ts index 180b0694..ba46c9c1 100644 --- a/packages/smart-forms-renderer/src/interfaces/questionnaireStore.interface.ts +++ b/packages/smart-forms-renderer/src/interfaces/questionnaireStore.interface.ts @@ -27,6 +27,7 @@ import type { InitialExpression } from './initialExpression.interface'; export interface QuestionnaireModel { itemTypes: Record; + itemPreferredTerminologyServers: Record; tabs: Tabs; pages: Pages; variables: Variables; diff --git a/packages/smart-forms-renderer/src/stores/questionnaireStore.ts b/packages/smart-forms-renderer/src/stores/questionnaireStore.ts index 2ef27efb..0d7d2917 100644 --- a/packages/smart-forms-renderer/src/stores/questionnaireStore.ts +++ b/packages/smart-forms-renderer/src/stores/questionnaireStore.ts @@ -56,6 +56,7 @@ import type { InitialExpression } from '../interfaces/initialExpression.interfac * * @property sourceQuestionnaire - FHIR R4 Questionnaire to render * @property itemTypes - Key-value pair of item types `Record` + * @property itemPreferredTerminologyServers - Key-value pair of item types `Record` * @property tabs - Key-value pair of tabs `Record` * @property currentTabIndex - Index of the current tab * @property pages - Key-value pair of pages `Record` @@ -96,6 +97,7 @@ import type { InitialExpression } from '../interfaces/initialExpression.interfac export interface QuestionnaireStoreType { sourceQuestionnaire: Questionnaire; itemTypes: Record; + itemPreferredTerminologyServers: Record; tabs: Tabs; currentTabIndex: number; pages: Pages; @@ -161,6 +163,7 @@ export interface QuestionnaireStoreType { export const questionnaireStore = createStore()((set, get) => ({ sourceQuestionnaire: structuredClone(emptyQuestionnaire), itemTypes: {}, + itemPreferredTerminologyServers: {}, tabs: {}, currentTabIndex: 0, pages: {}, @@ -223,6 +226,7 @@ export const questionnaireStore = createStore()((set, ge set({ sourceQuestionnaire: questionnaire, itemTypes: questionnaireModel.itemTypes, + itemPreferredTerminologyServers: questionnaireModel.itemPreferredTerminologyServers, tabs: questionnaireModel.tabs, currentTabIndex: firstVisibleTab, pages: questionnaireModel.pages, @@ -245,6 +249,7 @@ export const questionnaireStore = createStore()((set, ge set({ sourceQuestionnaire: structuredClone(emptyQuestionnaire), itemTypes: {}, + itemPreferredTerminologyServers: {}, tabs: {}, currentTabIndex: 0, pages: {}, diff --git a/packages/smart-forms-renderer/src/stories/assets/questionnaires/QTerminologyControl.ts b/packages/smart-forms-renderer/src/stories/assets/questionnaires/QTerminologyControl.ts new file mode 100644 index 00000000..febf12d3 --- /dev/null +++ b/packages/smart-forms-renderer/src/stories/assets/questionnaires/QTerminologyControl.ts @@ -0,0 +1,59 @@ +/* + * Copyright 2024 Commonwealth Scientific and Industrial Research + * Organisation (CSIRO) ABN 41 687 119 230. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { Questionnaire } from 'fhir/r4'; + +export const qPreferredTerminologyServer: Questionnaire = { + resourceType: 'Questionnaire', + id: 'PreferredTerminologyServer', + name: 'PreferredTerminologyServer', + title: 'Preferred Terminology Server', + version: '0.1.0', + status: 'draft', + publisher: 'AEHRC CSIRO', + date: '2024-10-08', + url: 'https://smartforms.csiro.au/docs/terminology/preferred-terminology-server', + extension: [ + { + url: 'http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-preferredTerminologyServer', + valueUrl: 'https://sqlonfhir-r4.azurewebsites.net/fhir' + } + ], + item: [ + { + linkId: 'notes', + _text: { + extension: [ + { + url: 'http://hl7.org/fhir/StructureDefinition/rendering-xhtml', + valueString: + '
\r\n

PreferredTerminologyServer is set as https://sqlonfhir-r4.azurewebsites.net/fhir for the entire questionnaire.

Developer note: Use Inspect > Network to see the request.

' + } + ] + }, + text: 'PreferredTerminologyServer is set as https://sqlonfhir-r4.azurewebsites.net/fhir for the entire questionnaire. Use Inspect > Network to see the request.', + type: 'display', + repeats: false + }, + { + linkId: 'languages', + text: 'Languages', + type: 'choice', + answerValueSet: 'http://hl7.org/fhir/ValueSet/languages' + } + ] +}; diff --git a/packages/smart-forms-renderer/src/stories/assets/questionnaires/index.ts b/packages/smart-forms-renderer/src/stories/assets/questionnaires/index.ts index 83fed1f3..b2a0edc2 100644 --- a/packages/smart-forms-renderer/src/stories/assets/questionnaires/index.ts +++ b/packages/smart-forms-renderer/src/stories/assets/questionnaires/index.ts @@ -40,3 +40,4 @@ export * from './QBehaviorValueConstraints'; export * from './QItemControlDisplay'; export * from './QItemControlGroup'; export * from './QItemControlQuestion'; +export * from './QTerminologyControl'; diff --git a/packages/smart-forms-renderer/src/stories/sdc/OtherExtensions.stories.tsx b/packages/smart-forms-renderer/src/stories/sdc/OtherExtensions.stories.tsx new file mode 100644 index 00000000..aa5a9398 --- /dev/null +++ b/packages/smart-forms-renderer/src/stories/sdc/OtherExtensions.stories.tsx @@ -0,0 +1,39 @@ +/* + * Copyright 2024 Commonwealth Scientific and Industrial Research + * Organisation (CSIRO) ABN 41 687 119 230. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { Meta, StoryObj } from '@storybook/react'; +import BuildFormWrapperForStorybook from '../storybookWrappers/BuildFormWrapperForStorybook'; +import { qPreferredTerminologyServer } from '../assets/questionnaires'; + +// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction#default-export +const meta = { + title: 'Component/SDC/Other Extensions', + component: BuildFormWrapperForStorybook, + // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/react/writing-docs/autodocs + tags: [] +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +// More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args + +export const PreferredTerminologyServer: Story = { + args: { + questionnaire: qPreferredTerminologyServer + } +}; diff --git a/packages/smart-forms-renderer/src/stories/storybookWrappers/BuildFormWrapperForStorybook.tsx b/packages/smart-forms-renderer/src/stories/storybookWrappers/BuildFormWrapperForStorybook.tsx index 3ac62e3f..3d7da66e 100644 --- a/packages/smart-forms-renderer/src/stories/storybookWrappers/BuildFormWrapperForStorybook.tsx +++ b/packages/smart-forms-renderer/src/stories/storybookWrappers/BuildFormWrapperForStorybook.tsx @@ -25,6 +25,9 @@ import { useBuildForm } from '../../hooks'; import useRendererQueryClient from '../../hooks/useRendererQueryClient'; import { STORYBOOK_TERMINOLOGY_SERVER_URL } from './globals'; +// iframeResizerChild.js needs to be called at least once in the used storybook wrappers to be included in storybook-static +import './iframeResizerChild'; + interface BuildFormWrapperForStorybookProps { questionnaire: Questionnaire; questionnaireResponse?: QuestionnaireResponse; diff --git a/packages/smart-forms-renderer/.storybook/iframeResizerChild.js b/packages/smart-forms-renderer/src/stories/storybookWrappers/iframeResizerChild.js similarity index 100% rename from packages/smart-forms-renderer/.storybook/iframeResizerChild.js rename to packages/smart-forms-renderer/src/stories/storybookWrappers/iframeResizerChild.js diff --git a/packages/smart-forms-renderer/src/utils/qItem.ts b/packages/smart-forms-renderer/src/utils/qItem.ts index 6ce13b35..ab937490 100644 --- a/packages/smart-forms-renderer/src/utils/qItem.ts +++ b/packages/smart-forms-renderer/src/utils/qItem.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import type { Extension, Questionnaire, QuestionnaireItem } from 'fhir/r4'; +import type { BackboneElement, Extension, Questionnaire, QuestionnaireItem } from 'fhir/r4'; import { getChoiceControlType } from './choice'; import { ChoiceItemControl, OpenChoiceItemControl } from '../interfaces/choice.enum'; import { getOpenChoiceControlType } from './openChoice'; @@ -130,6 +130,73 @@ export function getLinkIdTypeTuplesFromItemRecursive(qItem: QuestionnaireItem): return linkIds; } +function getPreferredTerminologyServer(element: BackboneElement): string | null { + const preferredTerminologyServerExtension = element.extension?.find( + (ext) => + ext.url === + 'http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-preferredTerminologyServer' + ); + + if (preferredTerminologyServerExtension) { + return ( + preferredTerminologyServerExtension.valueUrl ?? + preferredTerminologyServerExtension.valueString ?? + null + ); + } + + return null; +} + +export function getLinkIdPreferredTerminologyServerTuples( + questionnaire: Questionnaire +): [string, string][] { + if (!questionnaire.item || questionnaire.item.length === 0) { + return []; + } + + const preferredTerminologyServer = getPreferredTerminologyServer(questionnaire); + + const linkIds: [string, string][] = []; + for (const topLevelItem of questionnaire.item) { + linkIds.push( + ...getLinkIdPreferredTerminologyServerTuplesRecursive( + topLevelItem, + preferredTerminologyServer + ) + ); + } + + return linkIds; +} + +function getLinkIdPreferredTerminologyServerTuplesRecursive( + qItem: QuestionnaireItem, + parentPreferredTerminologyServer: string | null +): [string, string][] { + const linkIds: [string, string][] = []; + + let preferredTerminologyServer = null; + if (qItem.linkId) { + preferredTerminologyServer = + getPreferredTerminologyServer(qItem) ?? parentPreferredTerminologyServer ?? null; + + if (preferredTerminologyServer) { + linkIds.push([qItem.linkId, preferredTerminologyServer]); + } + } + + if (qItem.item) { + for (const childItem of qItem.item) { + linkIds.push( + ...getLinkIdPreferredTerminologyServerTuplesRecursive(childItem, preferredTerminologyServer) + ); + } + } + + return linkIds; +} + export type CollapsibleType = 'default-open' | 'default-closed'; function valueCodeIsCollapsibleType(valueCode: string): valueCode is CollapsibleType { diff --git a/packages/smart-forms-renderer/src/utils/questionnaireStoreUtils/createQuestionaireModel.ts b/packages/smart-forms-renderer/src/utils/questionnaireStoreUtils/createQuestionaireModel.ts index f28b4136..cc46b67e 100644 --- a/packages/smart-forms-renderer/src/utils/questionnaireStoreUtils/createQuestionaireModel.ts +++ b/packages/smart-forms-renderer/src/utils/questionnaireStoreUtils/createQuestionaireModel.ts @@ -29,7 +29,7 @@ import { extractOtherExtensions } from './extractOtherExtensions'; import type { Variables } from '../../interfaces/variables.interface'; import { resolveValueSets } from './resolveValueSets'; import { addAdditionalVariables } from './addAdditionalVariables'; -import { getLinkIdTypeTuples } from '../qItem'; +import { getLinkIdPreferredTerminologyServerTuples, getLinkIdTypeTuples } from '../qItem'; import { addDisplayToAnswerOptions, addDisplayToProcessedCodings } from './addDisplayToCodings'; export async function createQuestionnaireModel( @@ -42,6 +42,9 @@ export async function createQuestionnaireModel( } const itemTypes: Record = Object.fromEntries(getLinkIdTypeTuples(questionnaire)); + const itemPreferredTerminologyServers: Record = Object.fromEntries( + getLinkIdPreferredTerminologyServerTuples(questionnaire) + ); const tabs: Tabs = extractTabs(questionnaire); const pages: Pages = extractPages(questionnaire); @@ -62,6 +65,7 @@ export async function createQuestionnaireModel( questionnaire, variables, valueSetPromises, + itemPreferredTerminologyServers, terminologyServerUrl ); @@ -100,6 +104,7 @@ export async function createQuestionnaireModel( return { itemTypes, + itemPreferredTerminologyServers, tabs, pages, variables, @@ -119,6 +124,7 @@ export async function createQuestionnaireModel( function createEmptyModel(): QuestionnaireModel { return { itemTypes: {}, + itemPreferredTerminologyServers: {}, tabs: {}, pages: {}, variables: { fhirPathVariables: {}, xFhirQueryVariables: {} }, diff --git a/packages/smart-forms-renderer/src/utils/questionnaireStoreUtils/extractOtherExtensions.ts b/packages/smart-forms-renderer/src/utils/questionnaireStoreUtils/extractOtherExtensions.ts index 3c39389f..a9cb5ebb 100644 --- a/packages/smart-forms-renderer/src/utils/questionnaireStoreUtils/extractOtherExtensions.ts +++ b/packages/smart-forms-renderer/src/utils/questionnaireStoreUtils/extractOtherExtensions.ts @@ -65,6 +65,7 @@ export function extractOtherExtensions( questionnaire: Questionnaire, variables: Variables, valueSetPromises: Record, + itemPreferredTerminologyServers: Record, terminologyServerUrl: string ): ReturnParamsRecursive { const enableWhenItems: EnableWhenItems = { singleItems: {}, repeatItems: {} }; @@ -106,6 +107,7 @@ export function extractOtherExtensions( answerExpressions, answerOptions, valueSetPromises, + itemPreferredTerminologyServers, defaultTerminologyServerUrl: terminologyServerUrl, parentRepeatGroupLinkId: isRepeatGroup ? topLevelItem.linkId : undefined }); @@ -134,6 +136,7 @@ interface extractExtensionsFromItemRecursiveParams { answerExpressions: Record; answerOptions: Record; valueSetPromises: Record; + itemPreferredTerminologyServers: Record; defaultTerminologyServerUrl: string; parentRepeatGroupLinkId?: string; } @@ -152,6 +155,7 @@ function extractExtensionsFromItemRecursive( answerExpressions, answerOptions, valueSetPromises, + itemPreferredTerminologyServers, defaultTerminologyServerUrl, parentRepeatGroupLinkId } = params; @@ -234,7 +238,12 @@ function extractExtensionsFromItemRecursive( const valueSetUrl = item.answerValueSet; if (valueSetUrl) { if (!valueSetPromises[valueSetUrl] && !valueSetUrl.startsWith('#')) { - const terminologyServerUrl = getTerminologyServerUrl(item) ?? defaultTerminologyServerUrl; + const preferredTerminologyServerUrl = itemPreferredTerminologyServers[item.linkId]; + const terminologyServerUrl = + getTerminologyServerUrl(item) ?? + preferredTerminologyServerUrl ?? + defaultTerminologyServerUrl; + valueSetPromises[valueSetUrl] = { promise: getValueSetPromise(valueSetUrl, terminologyServerUrl) }; diff --git a/packages/smart-forms-renderer/src/utils/valueSet.ts b/packages/smart-forms-renderer/src/utils/valueSet.ts index 77786058..aa666428 100644 --- a/packages/smart-forms-renderer/src/utils/valueSet.ts +++ b/packages/smart-forms-renderer/src/utils/valueSet.ts @@ -36,6 +36,7 @@ const VALID_VALUE_SET_URL_REGEX = const VALID_FHIRPATH_VARIABLE_REGEX = /%(.*?)\./; +// Mainly for backwards compatibility, doesn't exist in the SDC spec anymore export function getTerminologyServerUrl(qItem: QuestionnaireItem): string | undefined { const itemControl = qItem.extension?.find( (extension: Extension) =>