@aehrc/smart-forms-renderer 0.39.0 → 0.40.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/lib/components/FormComponents/ItemParts/ItemFieldGrid.js +6 -2
  2. package/lib/components/FormComponents/ItemParts/ItemFieldGrid.js.map +1 -1
  3. package/lib/components/FormComponents/RepeatGroup/RepeatGroup.js +2 -2
  4. package/lib/components/FormComponents/RepeatGroup/RepeatGroup.js.map +1 -1
  5. package/lib/components/FormComponents/RepeatItem/RepeatItem.js +5 -5
  6. package/lib/components/FormComponents/RepeatItem/RepeatItem.js.map +1 -1
  7. package/lib/components/FormComponents/Tables/GroupTable.js +3 -3
  8. package/lib/components/FormComponents/Tables/GroupTable.js.map +1 -1
  9. package/lib/components/Renderer/FormTopLevelItem.js +12 -1
  10. package/lib/components/Renderer/FormTopLevelItem.js.map +1 -1
  11. package/lib/hooks/useDateValidation.js +2 -2
  12. package/lib/hooks/useDateValidation.js.map +1 -1
  13. package/lib/hooks/useGroupTableRows.d.ts +1 -1
  14. package/lib/hooks/useGroupTableRows.js +2 -2
  15. package/lib/hooks/useGroupTableRows.js.map +1 -1
  16. package/lib/hooks/useInitialiseGroupTable.d.ts +1 -1
  17. package/lib/hooks/useInitialiseGroupTable.js +5 -5
  18. package/lib/hooks/useInitialiseGroupTable.js.map +1 -1
  19. package/lib/hooks/useInitialiseRepeatAnswers.d.ts +1 -1
  20. package/lib/hooks/useInitialiseRepeatAnswers.js +9 -12
  21. package/lib/hooks/useInitialiseRepeatAnswers.js.map +1 -1
  22. package/lib/hooks/useInitialiseRepeatGroups.js +4 -4
  23. package/lib/hooks/useInitialiseRepeatGroups.js.map +1 -1
  24. package/lib/index.d.ts +1 -1
  25. package/lib/index.js +1 -1
  26. package/lib/index.js.map +1 -1
  27. package/lib/tests/test-data/observationSample.d.ts +2 -0
  28. package/lib/tests/test-data/observationSample.js +523 -0
  29. package/lib/tests/test-data/observationSample.js.map +1 -1
  30. package/lib/utils/extractObservation.d.ts +5 -0
  31. package/lib/utils/extractObservation.js +121 -33
  32. package/lib/utils/extractObservation.js.map +1 -1
  33. package/lib/utils/genericRecursive.d.ts +5 -2
  34. package/lib/utils/genericRecursive.js +10 -2
  35. package/lib/utils/genericRecursive.js.map +1 -1
  36. package/lib/utils/index.d.ts +1 -0
  37. package/lib/utils/index.js +1 -0
  38. package/lib/utils/index.js.map +1 -1
  39. package/lib/utils/itemControl.js +1 -1
  40. package/lib/utils/itemControl.js.map +1 -1
  41. package/lib/utils/manageForm.js +2 -1
  42. package/lib/utils/manageForm.js.map +1 -1
  43. package/lib/utils/qItem.js +5 -0
  44. package/lib/utils/qItem.js.map +1 -1
  45. package/lib/utils/repeatId.d.ts +2 -0
  46. package/lib/utils/repeatId.js +25 -0
  47. package/lib/utils/repeatId.js.map +1 -0
  48. package/package.json +1 -1
  49. package/src/components/FormComponents/ItemParts/ItemFieldGrid.tsx +8 -2
  50. package/src/components/FormComponents/RepeatGroup/RepeatGroup.tsx +2 -2
  51. package/src/components/FormComponents/RepeatItem/RepeatItem.tsx +7 -5
  52. package/src/components/FormComponents/Tables/GroupTable.tsx +6 -3
  53. package/src/components/Renderer/FormTopLevelItem.tsx +31 -1
  54. package/src/hooks/useDateValidation.tsx +2 -2
  55. package/src/hooks/useGroupTableRows.ts +2 -2
  56. package/src/hooks/useInitialiseGroupTable.ts +8 -5
  57. package/src/hooks/useInitialiseRepeatAnswers.ts +9 -12
  58. package/src/hooks/useInitialiseRepeatGroups.ts +4 -4
  59. package/src/index.ts +2 -1
  60. package/src/stories/assets/questionnaires/QIdRemoverDebugger.ts +161 -0
  61. package/src/stories/storybookWrappers/IdRemoverButtonForStorybook.tsx +54 -0
  62. package/src/stories/storybookWrappers/IdRemoverDebuggerWrapperForStorybook.tsx +81 -0
  63. package/src/stories/testing/IdRemoverDebuggerWrapper.stories.tsx +39 -0
  64. package/src/tests/extractObservation.test.ts +129 -0
  65. package/src/tests/test-data/observationSample.ts +721 -0
  66. package/src/utils/extractObservation.ts +284 -0
  67. package/src/utils/genericRecursive.ts +27 -5
  68. package/src/utils/index.ts +1 -0
  69. package/src/utils/itemControl.ts +1 -1
  70. package/src/utils/manageForm.ts +2 -1
  71. package/src/utils/qItem.ts +6 -0
  72. package/src/utils/repeatId.ts +27 -0
  73. package/CHANGELOG.md +0 -34
  74. package/lib/hooks/useRepeatAnswers.d.ts +0 -4
  75. package/lib/hooks/useRepeatAnswers.js +0 -34
  76. package/lib/hooks/useRepeatAnswers.js.map +0 -1
  77. package/lib/interfaces/repeatItem.interface.d.ts +0 -5
  78. package/lib/interfaces/repeatItem.interface.js +0 -2
  79. package/lib/interfaces/repeatItem.interface.js.map +0 -1
  80. package/lib/utils/answerExpression.d.ts +0 -18
  81. package/lib/utils/answerExpression.js +0 -133
  82. package/lib/utils/answerExpression.js.map +0 -1
  83. package/lib/utils/dynamicValueSet.d.ts +0 -5
  84. package/lib/utils/dynamicValueSet.js +0 -96
  85. package/lib/utils/dynamicValueSet.js.map +0 -1
  86. package/lib/utils/fhirpathAsyncUtils/fhirpath-async.d.ts +0 -14
  87. package/lib/utils/fhirpathAsyncUtils/fhirpath-async.js +0 -639
  88. package/lib/utils/fhirpathAsyncUtils/fhirpath-async.js.map +0 -1
  89. package/lib/utils/fhirpathAsyncUtils/outcome-utils.d.ts +0 -3
  90. package/lib/utils/fhirpathAsyncUtils/outcome-utils.js +0 -41
  91. package/lib/utils/fhirpathAsyncUtils/outcome-utils.js.map +0 -1
  92. package/lib/utils/updateQr.d.ts +0 -9
  93. package/lib/utils/updateQr.js +0 -55
  94. package/lib/utils/updateQr.js.map +0 -1
@@ -34,8 +34,10 @@ import useHidden from '../../hooks/useHidden';
34
34
  import GroupItemSwitcher from '../FormComponents/GroupItem/GroupItemSwitcher';
35
35
  import useReadOnly from '../../hooks/useReadOnly';
36
36
  import Box from '@mui/material/Box';
37
- import { isSpecificItemControl } from '../../utils';
37
+ import { isRepeatItemAndNotCheckbox, isSpecificItemControl } from '../../utils';
38
38
  import GroupTable from '../FormComponents/Tables/GroupTable';
39
+ import RepeatItem from '../FormComponents/RepeatItem/RepeatItem';
40
+ import GridGroup from '../FormComponents/GridGroup/GridGroup';
39
41
 
40
42
  interface FormTopLevelItemProps
41
43
  extends PropsWithQrItemChangeHandler,
@@ -127,6 +129,20 @@ function FormTopLevelItem(props: FormTopLevelItemProps) {
127
129
 
128
130
  // If form is untabbed, it is rendered as a regular group
129
131
  if (itemIsGroup) {
132
+ // Item is 'grid'
133
+ const itemIsGrid = isSpecificItemControl(topLevelQItem, 'grid');
134
+ if (itemIsGrid) {
135
+ return (
136
+ <GridGroup
137
+ qItem={topLevelQItem}
138
+ qrItem={topLevelQRItem}
139
+ groupCardElevation={1}
140
+ parentIsReadOnly={parentIsReadOnly}
141
+ onQrItemChange={onQrItemChange}
142
+ />
143
+ );
144
+ }
145
+
130
146
  // GroupTable "gtable" can be rendered with either repeats:true or false
131
147
  if (isSpecificItemControl(topLevelQItem, 'gtable')) {
132
148
  return (
@@ -156,6 +172,20 @@ function FormTopLevelItem(props: FormTopLevelItemProps) {
156
172
  }
157
173
 
158
174
  // Otherwise, it is rendered as a non-group item
175
+ const itemRepeatsAndIsNotCheckbox = isRepeatItemAndNotCheckbox(topLevelQItem);
176
+ if (itemRepeatsAndIsNotCheckbox) {
177
+ return (
178
+ <RepeatItem
179
+ key={topLevelQItem.linkId}
180
+ qItem={topLevelQItem}
181
+ qrItem={topLevelQRItem}
182
+ groupCardElevation={1}
183
+ parentIsReadOnly={readOnly}
184
+ onQrItemChange={onQrItemChange}
185
+ />
186
+ );
187
+ }
188
+
159
189
  return (
160
190
  <Box mt={1}>
161
191
  <SingleItem
@@ -55,8 +55,8 @@ function useDateValidation(input: string, parseFail: boolean = false): string |
55
55
 
56
56
  const matches = input.split('/');
57
57
 
58
- if (validateTwoMatches(matches[0], matches[1])) {
59
- return null;
58
+ if (!validateTwoMatches(matches[0], matches[1])) {
59
+ return 'Input is an invalid date.';
60
60
  }
61
61
 
62
62
  return null;
@@ -19,8 +19,8 @@ import { useState } from 'react';
19
19
  import useInitialiseGroupTable from './useInitialiseGroupTable';
20
20
  import type { QuestionnaireResponseItem } from 'fhir/r4';
21
21
 
22
- function useGroupTableRows(qrItems: QuestionnaireResponseItem[]) {
23
- const initialisedGroupTableRows = useInitialiseGroupTable(qrItems);
22
+ function useGroupTableRows(linkId: string, qrItems: QuestionnaireResponseItem[]) {
23
+ const initialisedGroupTableRows = useInitialiseGroupTable(linkId, qrItems);
24
24
 
25
25
  const [tableRows, setTableRows] = useState(initialisedGroupTableRows);
26
26
  const [selectedIds, setSelectedIds] = useState<string[]>(
@@ -16,21 +16,24 @@
16
16
  */
17
17
 
18
18
  import type { QuestionnaireResponseItem } from 'fhir/r4';
19
- import { nanoid } from 'nanoid';
20
19
  import type { GroupTableRowModel } from '../interfaces/groupTable.interface';
20
+ import { generateExistingRepeatId, generateNewRepeatId } from '../utils/repeatId';
21
21
 
22
- function useInitialiseGroupTable(qrItems: QuestionnaireResponseItem[]): GroupTableRowModel[] {
22
+ function useInitialiseGroupTable(
23
+ linkId: string,
24
+ qrItems: QuestionnaireResponseItem[]
25
+ ): GroupTableRowModel[] {
23
26
  let initialGroupTableRows: GroupTableRowModel[] = [
24
27
  {
25
- nanoId: nanoid(),
28
+ nanoId: generateNewRepeatId(linkId),
26
29
  qrItem: null
27
30
  }
28
31
  ];
29
32
 
30
33
  if (qrItems.length > 0) {
31
- initialGroupTableRows = qrItems.map((qrItem) => {
34
+ initialGroupTableRows = qrItems.map((qrItem, index) => {
32
35
  return {
33
- nanoId: nanoid(),
36
+ nanoId: generateExistingRepeatId(linkId, index),
34
37
  qrItem
35
38
  };
36
39
  });
@@ -16,26 +16,23 @@
16
16
  */
17
17
 
18
18
  import type { QuestionnaireResponseItem, QuestionnaireResponseItemAnswer } from 'fhir/r4';
19
- import { nanoid } from 'nanoid';
20
19
  import { useMemo } from 'react';
20
+ import { generateExistingRepeatId, generateNewRepeatId } from '../utils/repeatId';
21
21
 
22
22
  function useInitialiseRepeatAnswers(
23
+ linkId: string,
23
24
  qrItem: QuestionnaireResponseItem | null
24
25
  ): (QuestionnaireResponseItemAnswer | null)[] {
25
26
  return useMemo(() => {
26
- let initialRepeatAnswers: (QuestionnaireResponseItemAnswer | null)[] = [{ id: nanoid() }];
27
-
28
- if (qrItem?.answer) {
29
- initialRepeatAnswers = qrItem.answer.map((answer) => {
30
- if (!answer.id) {
31
- answer.id = nanoid();
32
- }
33
- return answer;
34
- });
27
+ if (!qrItem?.answer) {
28
+ return [{ id: generateNewRepeatId(linkId) }];
35
29
  }
36
30
 
37
- return initialRepeatAnswers;
38
- }, [qrItem]);
31
+ return qrItem.answer.map((answer, index) => ({
32
+ ...answer,
33
+ id: answer.id ?? generateExistingRepeatId(linkId, index)
34
+ }));
35
+ }, [linkId, qrItem]);
39
36
  }
40
37
 
41
38
  export default useInitialiseRepeatAnswers;
@@ -16,9 +16,9 @@
16
16
  */
17
17
 
18
18
  import type { QuestionnaireItem, QuestionnaireResponseItem } from 'fhir/r4';
19
- import { nanoid } from 'nanoid';
20
19
  import type { RepeatGroupSingle } from '../interfaces/repeatGroup.interface';
21
20
  import { useMemo } from 'react';
21
+ import { generateExistingRepeatId, generateNewRepeatId } from '../utils/repeatId';
22
22
 
23
23
  function useInitialiseRepeatGroups(
24
24
  qItem: QuestionnaireItem,
@@ -28,15 +28,15 @@ function useInitialiseRepeatGroups(
28
28
  () => {
29
29
  let initialRepeatGroupAnswers: RepeatGroupSingle[] = [
30
30
  {
31
- nanoId: nanoid(),
31
+ nanoId: generateNewRepeatId(qItem.linkId),
32
32
  qrItem: null
33
33
  }
34
34
  ];
35
35
 
36
36
  if (qrItems.length > 0) {
37
- initialRepeatGroupAnswers = qrItems.map((qrItem) => {
37
+ initialRepeatGroupAnswers = qrItems.map((qrItem, index) => {
38
38
  return {
39
- nanoId: nanoid(),
39
+ nanoId: generateExistingRepeatId(qItem.linkId, index),
40
40
  qrItem
41
41
  };
42
42
  });
package/src/index.ts CHANGED
@@ -46,7 +46,8 @@ export {
46
46
  isRepeatItemAndNotCheckbox,
47
47
  initialiseQuestionnaireResponse,
48
48
  generateItemsToRepopulate,
49
- repopulateResponse
49
+ repopulateResponse,
50
+ extractObservationBased
50
51
  } from './utils';
51
52
 
52
53
  // theme provider exports
@@ -0,0 +1,161 @@
1
+ /*
2
+ * Copyright 2024 Commonwealth Scientific and Industrial Research
3
+ * Organisation (CSIRO) ABN 41 687 119 230.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ import type { Questionnaire } from 'fhir/r4';
19
+
20
+ export const qMyPatient: Questionnaire = {
21
+ resourceType: 'Questionnaire',
22
+ id: 'canshare-myPatient1',
23
+ meta: {
24
+ versionId: '9',
25
+ lastUpdated: '2024-09-18T07:23:35.7317908+00:00'
26
+ },
27
+ extension: [
28
+ {
29
+ extension: [
30
+ {
31
+ url: 'name',
32
+ valueId: 'LaunchPatient'
33
+ },
34
+ {
35
+ url: 'type',
36
+ valueCode: 'Patient'
37
+ },
38
+ {
39
+ url: 'description',
40
+ valueString: 'The patient that is to be used to pre-populate the form'
41
+ }
42
+ ],
43
+ url: 'http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-launchContext'
44
+ },
45
+ {
46
+ extension: [
47
+ {
48
+ url: 'name',
49
+ valueId: 'LaunchPractitioner'
50
+ },
51
+ {
52
+ url: 'type',
53
+ valueCode: 'Practitioner'
54
+ },
55
+ {
56
+ url: 'description',
57
+ valueString: 'The practitioner that is to be used to pre-populate the form'
58
+ }
59
+ ],
60
+ url: 'http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-launchContext'
61
+ },
62
+ {
63
+ url: 'http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-itemExtractionContext',
64
+ valueCode: 'Patient'
65
+ }
66
+ ],
67
+ url: 'http://canshare.co.nz/questionnaire/myPatient1',
68
+ name: 'myPatient1',
69
+ status: 'active',
70
+ publisher: 'DEMO: David Hay',
71
+ useContext: [
72
+ {
73
+ code: {
74
+ system: 'http://terminology.hl7.org/CodeSystem/usage-context-type',
75
+ code: 'user',
76
+ display: 'User Type'
77
+ },
78
+ valueCodeableConcept: {
79
+ coding: [
80
+ {
81
+ code: 'extract',
82
+ display: 'Demo Extract'
83
+ }
84
+ ]
85
+ }
86
+ }
87
+ ],
88
+ item: [
89
+ {
90
+ linkId: 'myPatient1',
91
+ text: 'myPatient1',
92
+ type: 'group',
93
+ item: [
94
+ {
95
+ linkId: 'myPatient1.name',
96
+ definition: 'http://hl7.org/fhir/StructureDefinition/Patient#Patient.name',
97
+ text: 'name *',
98
+ type: 'group',
99
+ repeats: true,
100
+ item: [
101
+ {
102
+ linkId: 'myPatient1.name.first',
103
+ definition: 'http://hl7.org/fhir/StructureDefinition/Patient#Patient.name.given',
104
+ text: 'firstName *',
105
+ type: 'string',
106
+ repeats: true
107
+ },
108
+ {
109
+ linkId: 'myPatient1.name.lastName',
110
+ definition: 'http://hl7.org/fhir/StructureDefinition/Patient#Patient.name.family',
111
+ text: 'lastName',
112
+ type: 'string'
113
+ }
114
+ ]
115
+ },
116
+ {
117
+ linkId: 'myPatient1.hair',
118
+ definition: 'http://hl7.org/fhir/StructureDefinition/Patient#Patient.extension',
119
+ text: 'hair',
120
+ type: 'group',
121
+ item: [
122
+ {
123
+ linkId: 'myPatient1.hair.colour',
124
+ definition:
125
+ 'http://hl7.org/fhir/StructureDefinition/Patient#Patient.extension.valueString',
126
+ text: 'colour',
127
+ type: 'string'
128
+ },
129
+ {
130
+ linkId: 'myPatient1.hair.url',
131
+ definition: 'http://hl7.org/fhir/StructureDefinition/Patient#Patient.extension.url',
132
+ text: 'url',
133
+ type: 'string'
134
+ }
135
+ ]
136
+ },
137
+ {
138
+ linkId: 'myPatient1.religion',
139
+ definition: 'http://hl7.org/fhir/StructureDefinition/Patient#Patient.extension',
140
+ text: 'religion',
141
+ type: 'group',
142
+ item: [
143
+ {
144
+ linkId: 'myPatient1.religion.brand',
145
+ definition:
146
+ 'http://hl7.org/fhir/StructureDefinition/Patient#Patient.extension.valueString',
147
+ text: 'brand',
148
+ type: 'string'
149
+ },
150
+ {
151
+ linkId: 'myPatient1.religion.url1',
152
+ definition: 'http://hl7.org/fhir/StructureDefinition/Patient#Patient.extension.url',
153
+ text: 'url',
154
+ type: 'string'
155
+ }
156
+ ]
157
+ }
158
+ ]
159
+ }
160
+ ]
161
+ };
@@ -0,0 +1,54 @@
1
+ /*
2
+ * Copyright 2024 Commonwealth Scientific and Industrial Research
3
+ * Organisation (CSIRO) ABN 41 687 119 230.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ // @ts-ignore
19
+ import React from 'react';
20
+ import type { QuestionnaireResponse } from 'fhir/r4';
21
+ import { Box, IconButton, Tooltip } from '@mui/material';
22
+ import ContentCutIcon from '@mui/icons-material/ContentCut';
23
+
24
+ interface IdRemoverButtonProps {
25
+ questionnaireResponse: QuestionnaireResponse;
26
+ }
27
+
28
+ function IdRemoverButtonForStorybook(props: IdRemoverButtonProps) {
29
+ const { questionnaireResponse } = props;
30
+
31
+ async function handleRemoveIds() {
32
+ console.log('handle remove IDs');
33
+
34
+ // return buildForm()
35
+ // updateQuestionnaireResponse(
36
+ // questionnaire,
37
+ // populatedResponse,
38
+ // initialiseItemCalculatedExpressionValueRecursive,
39
+ // calculatedExpressionsWithValues
40
+ // );
41
+ }
42
+
43
+ return (
44
+ <Box display="flex" mb={0.5} alignItems="center" columnGap={3}>
45
+ <Tooltip title="Remove IDs from questionnaire response" placement="right">
46
+ <IconButton onClick={handleRemoveIds} size="small" color="primary">
47
+ <ContentCutIcon />
48
+ </IconButton>
49
+ </Tooltip>
50
+ </Box>
51
+ );
52
+ }
53
+
54
+ export default IdRemoverButtonForStorybook;
@@ -0,0 +1,81 @@
1
+ /*
2
+ * Copyright 2024 Commonwealth Scientific and Industrial Research
3
+ * Organisation (CSIRO) ABN 41 687 119 230.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ // @ts-ignore
19
+ import React from 'react';
20
+ import type { Questionnaire, QuestionnaireResponse } from 'fhir/r4';
21
+ import { BaseRenderer } from '../../components';
22
+ import { QueryClientProvider } from '@tanstack/react-query';
23
+ import { RendererThemeProvider } from '../../theme';
24
+ import { useBuildForm, useRendererQueryClient } from '../../hooks';
25
+ import { STORYBOOK_TERMINOLOGY_SERVER_URL } from './globals';
26
+ import IdRemoverButtonForStorybook from './IdRemoverButtonForStorybook';
27
+ import { Grid } from '@mui/material';
28
+ import { useQuestionnaireResponseStore, useQuestionnaireStore } from '../../stores';
29
+
30
+ interface IdRemoverDebuggerWrapperForStorybookProps {
31
+ questionnaire: Questionnaire;
32
+ questionnaireResponse?: QuestionnaireResponse;
33
+ }
34
+
35
+ /**
36
+ * This is a wrapper which for debugging answer/item IDs in repeating items and groups.
37
+ * It features a button to remove answer/item IDs from the QuestionnaireResponse.
38
+ *
39
+ * @author Sean Fong
40
+ */
41
+ function IdRemoverDebuggerWrapperForStorybook(props: IdRemoverDebuggerWrapperForStorybookProps) {
42
+ const { questionnaire, questionnaireResponse } = props;
43
+
44
+ const queryClient = useRendererQueryClient();
45
+
46
+ const focusedLinkId = useQuestionnaireStore.use.focusedLinkId();
47
+ const updatableResponse = useQuestionnaireResponseStore.use.updatableResponse();
48
+
49
+ const isBuilding = useBuildForm(
50
+ questionnaire,
51
+ undefined,
52
+ undefined,
53
+ STORYBOOK_TERMINOLOGY_SERVER_URL
54
+ );
55
+
56
+ if (isBuilding) {
57
+ return <div>Loading...</div>;
58
+ }
59
+
60
+ return (
61
+ <RendererThemeProvider>
62
+ <QueryClientProvider client={queryClient}>
63
+ <div>
64
+ <Grid container>
65
+ <Grid item xs={6}>
66
+ <IdRemoverButtonForStorybook questionnaireResponse={questionnaireResponse} />
67
+ <BaseRenderer />
68
+ </Grid>
69
+ <Grid item xs={6}>
70
+ <pre>{JSON.stringify(focusedLinkId, null, 2)}</pre>
71
+ ----
72
+ <pre style={{ fontSize: 10 }}>{JSON.stringify(updatableResponse, null, 2)}</pre>
73
+ </Grid>
74
+ </Grid>
75
+ </div>
76
+ </QueryClientProvider>
77
+ </RendererThemeProvider>
78
+ );
79
+ }
80
+
81
+ export default IdRemoverDebuggerWrapperForStorybook;
@@ -0,0 +1,39 @@
1
+ /*
2
+ * Copyright 2024 Commonwealth Scientific and Industrial Research
3
+ * Organisation (CSIRO) ABN 41 687 119 230.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ import type { Meta, StoryObj } from '@storybook/react';
19
+ import IdRemoverDebuggerWrapperForStorybook from '../storybookWrappers/IdRemoverDebuggerWrapperForStorybook';
20
+ import { qMyPatient } from '../assets/questionnaires/QIdRemoverDebugger';
21
+
22
+ // More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
23
+ const meta = {
24
+ title: 'Component/Testing/ID Remover Debugger',
25
+ component: IdRemoverDebuggerWrapperForStorybook,
26
+ // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/react/writing-docs/autodocs
27
+ tags: []
28
+ } satisfies Meta<typeof IdRemoverDebuggerWrapperForStorybook>;
29
+
30
+ export default meta;
31
+ type Story = StoryObj<typeof meta>;
32
+
33
+ // More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args
34
+
35
+ export const MyPatient: Story = {
36
+ args: {
37
+ questionnaire: qMyPatient
38
+ }
39
+ };
@@ -0,0 +1,129 @@
1
+ import type { Questionnaire, QuestionnaireResponse } from 'fhir/r4';
2
+
3
+ import type { Extractable } from '../utils/extractObservation';
4
+ import { extractObservationBased, mapQItemsExtractable } from '../utils/extractObservation';
5
+ import {
6
+ observationResults,
7
+ qExtractSample,
8
+ qObservationSample,
9
+ qObservationSampleWithExtractExtension,
10
+ qrObservationSample
11
+ } from './test-data/observationSample';
12
+
13
+ describe('extractObservationBased', () => {
14
+ it('should correctly extract Observations from a QuestionnaireResponse', () => {
15
+ const observations = extractObservationBased(
16
+ qObservationSampleWithExtractExtension,
17
+ qrObservationSample
18
+ );
19
+ expect(observations).toHaveLength(4);
20
+ expect(observations[0]).toEqual(observationResults[0]);
21
+ });
22
+
23
+ it('should return an Observations array only if there are observation-extract extensions', () => {
24
+ const singleExtractExtension: Questionnaire = JSON.parse(
25
+ JSON.stringify(qObservationSampleWithExtractExtension)
26
+ );
27
+ singleExtractExtension.item!.at(0)!.extension!.at(0)!.valueBoolean = false;
28
+
29
+ const observations = extractObservationBased(singleExtractExtension, qrObservationSample);
30
+ expect(observations).toHaveLength(2);
31
+ expect(observations[0].id).toContain('phq2-4');
32
+ });
33
+
34
+ it('should return all Observations expect for observation-extract extensions false', () => {
35
+ const topLevelExtract: Questionnaire = JSON.parse(
36
+ JSON.stringify(qObservationSampleWithExtractExtension)
37
+ );
38
+ topLevelExtract.extension!.at(0)!.valueBoolean = true;
39
+
40
+ const observations = extractObservationBased(topLevelExtract, qrObservationSample);
41
+ expect(observations).toHaveLength(6);
42
+ });
43
+
44
+ it('should return an empty array if there are no observation-extract extensions', () => {
45
+ const observations = extractObservationBased(qObservationSample, qrObservationSample);
46
+ expect(observations).toHaveLength(0);
47
+ });
48
+
49
+ it('should return an empty array if there are no items in the Questionnaire or QuestionnaireResponse', () => {
50
+ const emptyQuestionnaire: Questionnaire = {
51
+ resourceType: 'Questionnaire',
52
+ item: [],
53
+ status: 'draft'
54
+ };
55
+ const emptyResponse: QuestionnaireResponse = {
56
+ resourceType: 'QuestionnaireResponse',
57
+ item: [],
58
+ status: 'completed'
59
+ };
60
+
61
+ const observations = extractObservationBased(emptyQuestionnaire, emptyResponse);
62
+ expect(observations).toHaveLength(0);
63
+ });
64
+
65
+ it('should return an empty array if no matching Questionnaire item is found for a QuestionnaireResponse item', () => {
66
+ const responseWithNoMatch: QuestionnaireResponse = {
67
+ resourceType: 'QuestionnaireResponse',
68
+ status: 'completed',
69
+ id: 'qr2',
70
+ item: [
71
+ {
72
+ linkId: '999', // No matching linkId in the questionnaire
73
+ answer: [
74
+ {
75
+ valueQuantity: {
76
+ value: 100,
77
+ unit: 'kg',
78
+ system: 'http://unitsofmeasure.org',
79
+ code: 'kg'
80
+ }
81
+ }
82
+ ]
83
+ }
84
+ ],
85
+ subject: {
86
+ reference: 'Patient/456'
87
+ }
88
+ };
89
+
90
+ const observations = extractObservationBased(qObservationSample, responseWithNoMatch);
91
+ expect(observations).toHaveLength(0);
92
+ });
93
+ });
94
+
95
+ describe('mapQItemsExtractable', () => {
96
+ it('should correctly return extractionMap from a Questionnaire', () => {
97
+ const extractionMap = mapQItemsExtractable(qExtractSample);
98
+ expect(extractionMap).toEqual({
99
+ 'phq-2-questionnaire': { extractable: false, extractCategories: [] },
100
+ 'phq2-1': { extractable: true, extractCategories: [] },
101
+ 'phq2-2': { extractable: false, extractCategories: [] },
102
+ 'phq2-3': { extractable: false, extractCategories: [] },
103
+ 'phq2-4': { extractable: true, extractCategories: [] },
104
+ 'phq2-5': { extractable: true, extractCategories: [] },
105
+ 'phq2-6': { extractable: false, extractCategories: [] },
106
+ 'phq2-7': { extractable: false, extractCategories: [] },
107
+ 'phq2-8': { extractable: true, extractCategories: [] }
108
+ } satisfies Record<string, Extractable>);
109
+ });
110
+
111
+ it('should correctly return extractionMap even with topLevel observation-extract extensions true', () => {
112
+ const topLevelExtract: Questionnaire = JSON.parse(JSON.stringify(qExtractSample));
113
+ topLevelExtract.extension!.at(0)!.valueBoolean = true;
114
+
115
+ const extractionMap = mapQItemsExtractable(topLevelExtract);
116
+
117
+ expect(extractionMap).toEqual({
118
+ 'phq-2-questionnaire': { extractable: true, extractCategories: [] },
119
+ 'phq2-1': { extractable: true, extractCategories: [] },
120
+ 'phq2-2': { extractable: false, extractCategories: [] },
121
+ 'phq2-3': { extractable: true, extractCategories: [] },
122
+ 'phq2-4': { extractable: true, extractCategories: [] },
123
+ 'phq2-5': { extractable: true, extractCategories: [] },
124
+ 'phq2-6': { extractable: false, extractCategories: [] },
125
+ 'phq2-7': { extractable: true, extractCategories: [] },
126
+ 'phq2-8': { extractable: true, extractCategories: [] }
127
+ } satisfies Record<string, Extractable>);
128
+ });
129
+ });