diff --git a/src/components/inputs/select/dropdown.test.tsx b/src/components/inputs/select/dropdown.test.tsx
index de49702e2..3f35e6a41 100644
--- a/src/components/inputs/select/dropdown.test.tsx
+++ b/src/components/inputs/select/dropdown.test.tsx
@@ -1,5 +1,5 @@
import React from 'react';
-import { render, fireEvent, screen, cleanup, act } from '@testing-library/react';
+import { act, fireEvent, render, screen } from '@testing-library/react';
import { Formik } from 'formik';
import { type EncounterContext, FormContext } from '../../../form-context';
import Dropdown from './dropdown.component';
diff --git a/src/components/inputs/ui-select-extended/ui-select-extended.test.tsx b/src/components/inputs/ui-select-extended/ui-select-extended.test.tsx
index 47f3b18d5..7cb412c3b 100644
--- a/src/components/inputs/ui-select-extended/ui-select-extended.test.tsx
+++ b/src/components/inputs/ui-select-extended/ui-select-extended.test.tsx
@@ -1,27 +1,45 @@
import React from 'react';
-import { render, fireEvent, waitFor, act, screen } from '@testing-library/react';
+import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
import UiSelectExtended from './ui-select-extended.component';
import { type EncounterContext, FormContext } from '../../../form-context';
import { Formik } from 'formik';
import { ObsSubmissionHandler } from '../../../submission-handlers/base-handlers';
-import { type FormField } from '../../../types';
-
-const question: FormField = {
- label: 'Transfer Location',
- type: 'obs',
- questionOptions: {
- rendering: 'ui-select-extended',
- concept: '160540AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
- datasource: {
- name: 'location_datasource',
- config: {
- tag: 'test-tag',
+import { type FormField, type RenderType } from '../../../types';
+
+const questions: FormField[] = [
+ {
+ label: 'Transfer Location',
+ type: 'obs',
+ questionOptions: {
+ rendering: 'ui-select-extended',
+ concept: '160540AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
+ datasource: {
+ name: 'location_datasource',
+ config: {
+ tag: 'test-tag',
+ },
},
},
+ value: null,
+ id: 'patient_transfer_location',
},
- value: null,
- id: 'patient_transfer_location',
-};
+ {
+ label: 'Select criteria for new WHO stage:',
+ type: 'obs',
+ questionOptions: {
+ concept: '250e87b6-beb7-44a1-93a1-d3dd74d7e372',
+ rendering: 'select-concept-answers',
+ datasource: {
+ name: 'select_concept_answers_datasource',
+ config: {
+ concept: '250e87b6-beb7-44a1-93a1-d3dd74d7e372',
+ },
+ },
+ },
+ validators: [],
+ id: '__sq5ELJr7p',
+ },
+];
const encounterContext: EncounterContext = {
patient: {
@@ -54,12 +72,13 @@ const renderForm = (initialValues) => {
obsGroupsToVoid: [],
setObsGroupsToVoid: jest.fn(),
encounterContext: encounterContext,
- fields: [question],
+ fields: questions,
isFieldInitializationComplete: true,
isSubmitting: false,
formFieldHandlers: { obs: ObsSubmissionHandler },
}}>
-
+
+
)}
,
@@ -69,20 +88,35 @@ const renderForm = (initialValues) => {
// Mock the data source fetch behavior
jest.mock('../../../registry/registry', () => ({
getRegisteredDataSource: jest.fn().mockResolvedValue({
- fetchData: jest.fn().mockResolvedValue([
- {
- uuid: 'aaa-1',
- display: 'Kololo',
- },
- {
- uuid: 'aaa-2',
- display: 'Naguru',
- },
- {
- uuid: 'aaa-3',
- display: 'Muyenga',
- },
- ]),
+ fetchData: jest.fn().mockImplementation((...args) => {
+ if (args[1].concept) {
+ return Promise.resolve([
+ {
+ uuid: 'stage-1-uuid',
+ display: 'stage 1',
+ },
+ {
+ uuid: 'stage-2-uuid',
+ display: 'stage 2',
+ },
+ ]);
+ }
+
+ return Promise.resolve([
+ {
+ uuid: 'aaa-1',
+ display: 'Kololo',
+ },
+ {
+ uuid: 'aaa-2',
+ display: 'Naguru',
+ },
+ {
+ uuid: 'aaa-3',
+ display: 'Muyenga',
+ },
+ ]);
+ }),
toUuidAndDisplay: (data) => data,
}),
}));
@@ -98,7 +132,7 @@ describe('UiSelectExtended Component', () => {
// assert initial values
await act(async () => {
- expect(question.value).toBe(null);
+ expect(questions[0].value).toBe(null);
});
//Click on the UiSelectExtendedWidget to open the dropdown
@@ -110,6 +144,19 @@ describe('UiSelectExtended Component', () => {
expect(screen.getByText('Muyenga')).toBeInTheDocument();
});
+ it('renders with items from the datasource of select-concept-answers rendering', async () => {
+ await act(async () => {
+ await renderForm({});
+ });
+
+ const uiSelectExtendedWidget = screen.getByLabelText(/Select criteria for new WHO stage:/i);
+ fireEvent.click(uiSelectExtendedWidget);
+
+ // Assert that all items are displayed
+ expect(screen.getByText('stage 1')).toBeInTheDocument();
+ expect(screen.getByText('stage 2')).toBeInTheDocument();
+ });
+
it('Selects a value from the list', async () => {
await act(async () => {
await renderForm({});
@@ -127,7 +174,7 @@ describe('UiSelectExtended Component', () => {
// verify
await act(async () => {
- expect(question.value).toEqual({
+ expect(questions[0].value).toEqual({
person: '833db896-c1f0-11eb-8529-0242ac130003',
obsDatetime: new Date(2023, 8, 29),
concept: '160540AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
@@ -184,7 +231,7 @@ describe('UiSelectExtended Component', () => {
await act(async () => {
await renderForm({});
});
- const config = question.questionOptions.datasource.config;
+ const config = questions[0].questionOptions.datasource.config;
// Assert that the config is set with the expected configuration value
expect(config).toEqual(expectedConfigValue);
diff --git a/src/datasources/concept-data-source.ts b/src/datasources/concept-data-source.ts
index 5bb8bf53a..4bdaca3e4 100644
--- a/src/datasources/concept-data-source.ts
+++ b/src/datasources/concept-data-source.ts
@@ -20,6 +20,16 @@ export class ConceptDataSource extends BaseOpenMRSDataSource {
});
}
}
+
+ if (config?.useSetMembersByConcept) {
+ let urlParts = apiUrl.split('?name=&searchType=fuzzy&v=');
+ apiUrl = `${urlParts[0]}/${config.concept}?v=custom:(uuid,setMembers:(uuid,display))`;
+ return openmrsFetch(searchTerm ? `${apiUrl}&q=${searchTerm}` : apiUrl).then(({ data }) => {
+ // return the setMembers from the retrieved concept object
+ return data['setMembers'];
+ });
+ }
+
return openmrsFetch(searchTerm ? `${apiUrl}&q=${searchTerm}` : apiUrl).then(({ data }) => {
return data.results;
});
diff --git a/src/datasources/concept-set-members-data-source.ts b/src/datasources/concept-set-members-data-source.ts
new file mode 100644
index 000000000..2290b3141
--- /dev/null
+++ b/src/datasources/concept-set-members-data-source.ts
@@ -0,0 +1,17 @@
+import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
+import { BaseOpenMRSDataSource } from './data-source';
+
+export class ConceptSetMembersDataSource extends BaseOpenMRSDataSource {
+ constructor() {
+ super(`${restBaseUrl}/concept/conceptUuid?v=custom:(uuid,setMembers:(uuid,display))`);
+ }
+
+ fetchData(searchTerm: string, config?: Record): Promise {
+ let apiUrl = this.url;
+ let urlParts = apiUrl.split('conceptUuid');
+ apiUrl = `${urlParts[0]}/${config.concept}${urlParts[1]}`;
+ return openmrsFetch(apiUrl).then(({ data }) => {
+ return data['setMembers'];
+ });
+ }
+}
diff --git a/src/registry/inbuilt-components/control-templates.ts b/src/registry/inbuilt-components/control-templates.ts
index 79e9baf04..9dce18084 100644
--- a/src/registry/inbuilt-components/control-templates.ts
+++ b/src/registry/inbuilt-components/control-templates.ts
@@ -10,6 +10,15 @@ export const controlTemplates: Array = [
},
},
},
+ {
+ name: 'select-concept-answers',
+ datasource: {
+ name: 'select_concept_answers_datasource',
+ config: {
+ concept: '',
+ },
+ },
+ },
{
name: 'encounter-provider',
datasource: {
diff --git a/src/registry/inbuilt-components/inbuiltDataSources.ts b/src/registry/inbuilt-components/inbuiltDataSources.ts
index d32d67d74..2cdbe5acc 100644
--- a/src/registry/inbuilt-components/inbuiltDataSources.ts
+++ b/src/registry/inbuilt-components/inbuiltDataSources.ts
@@ -1,8 +1,9 @@
-import { type DataSource } from '../../types';
-import { type RegistryItem } from '../registry';
-import { ConceptDataSource } from '../../datasources/concept-data-source';
-import { LocationDataSource } from '../../datasources/location-data-source';
-import { ProviderDataSource } from '../../datasources/provider-datasource';
+import {type DataSource} from '../../types';
+import {type RegistryItem} from '../registry';
+import {ConceptDataSource} from '../../datasources/concept-data-source';
+import {LocationDataSource} from '../../datasources/location-data-source';
+import {ProviderDataSource} from '../../datasources/provider-datasource';
+import {ConceptSetMembersDataSource} from '../../datasources/concept-set-members-data-source';
/**
* @internal
@@ -20,6 +21,10 @@ export const inbuiltDataSources: Array>> = [
name: 'problem_datasource',
component: new ConceptDataSource(),
},
+ {
+ name: 'select_concept_answers_datasource',
+ component: new ConceptSetMembersDataSource(),
+ },
{
name: 'provider_datasource',
component: new ProviderDataSource(),
diff --git a/src/registry/inbuilt-components/template-component-map.ts b/src/registry/inbuilt-components/template-component-map.ts
index d1787d9c0..10d037a5c 100644
--- a/src/registry/inbuilt-components/template-component-map.ts
+++ b/src/registry/inbuilt-components/template-component-map.ts
@@ -17,4 +17,8 @@ export const templateToComponentMap = [
name: 'encounter-location',
baseControlComponent: UiSelectExtended,
},
+ {
+ name: 'select-concept-answers',
+ baseControlComponent: UiSelectExtended,
+ },
];
diff --git a/src/transformers/angular-fe-schema-transformer.ts b/src/transformers/angular-fe-schema-transformer.ts
index c44da8ec9..6fd4ede04 100644
--- a/src/transformers/angular-fe-schema-transformer.ts
+++ b/src/transformers/angular-fe-schema-transformer.ts
@@ -44,6 +44,9 @@ function transformByRendering(question: FormField) {
case 'numeric':
question.questionOptions.rendering = 'number';
break;
+ case 'select-concept-answers':
+ handleSelectConceptAnswers(question);
+ break;
case 'repeating':
case 'group':
handleLabOrders(question);
@@ -66,3 +69,14 @@ function updateQuestionAnswers(question: FormField) {
question.questionOptions.answers = question.questionOptions.selectableOrders || [];
delete question.questionOptions.selectableOrders;
}
+
+function handleSelectConceptAnswers(question: FormField) {
+ if (!question.questionOptions.datasource?.config) {
+ question.questionOptions.datasource = {
+ name: 'select_concept_answers_datasource',
+ config: {
+ concept: question.questionOptions.concept,
+ },
+ };
+ }
+}
diff --git a/src/types.ts b/src/types.ts
index ebb7ca75a..df1e31938 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -251,7 +251,8 @@ export type RenderType =
| 'ui-select-extended'
| 'workspace-launcher'
| 'fixed-value'
- | 'file';
+ | 'file'
+ | 'select-concept-answers';
export interface PostSubmissionAction {
applyAction(