@openmrs/esm-cohort-builder-app 3.0.0
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.
- package/LICENSE +401 -0
- package/README.md +16 -0
- package/dist/130.js +2 -0
- package/dist/130.js.LICENSE.txt +9 -0
- package/dist/139.js +1 -0
- package/dist/247.js +1 -0
- package/dist/294.js +2 -0
- package/dist/294.js.LICENSE.txt +9 -0
- package/dist/434.js +2 -0
- package/dist/434.js.LICENSE.txt +12 -0
- package/dist/480.js +2 -0
- package/dist/480.js.LICENSE.txt +12 -0
- package/dist/484.js +1 -0
- package/dist/574.js +1 -0
- package/dist/595.js +2 -0
- package/dist/595.js.LICENSE.txt +3 -0
- package/dist/690.js +1 -0
- package/dist/873.js +1 -0
- package/dist/935.js +2 -0
- package/dist/935.js.LICENSE.txt +19 -0
- package/dist/964.js +2 -0
- package/dist/964.js.LICENSE.txt +14 -0
- package/dist/main.js +1 -0
- package/dist/openmrs-esm-cohort-builder-app.js +1 -0
- package/dist/openmrs-esm-cohort-builder-app.js.buildmanifest.json +426 -0
- package/dist/openmrs-esm-cohort-builder-app.old +1 -0
- package/package.json +99 -0
- package/src/cohort-builder-app-menu-link.tsx +14 -0
- package/src/cohort-builder.resources.ts +84 -0
- package/src/cohort-builder.scss +126 -0
- package/src/cohort-builder.test.tsx +12 -0
- package/src/cohort-builder.tsx +213 -0
- package/src/cohort-builder.utils.ts +197 -0
- package/src/components/composition/composition.component.tsx +109 -0
- package/src/components/composition/composition.style.css +3 -0
- package/src/components/composition/composition.test.tsx +112 -0
- package/src/components/composition/composition.utils.ts +53 -0
- package/src/components/empty-data/empty-data.component.tsx +25 -0
- package/src/components/empty-data/empty-data.style.scss +19 -0
- package/src/components/saved-cohorts/saved-cohorts-options/saved-cohort-options.test.tsx +49 -0
- package/src/components/saved-cohorts/saved-cohorts-options/saved-cohorts-options.component.tsx +112 -0
- package/src/components/saved-cohorts/saved-cohorts.component.tsx +139 -0
- package/src/components/saved-cohorts/saved-cohorts.resources.ts +39 -0
- package/src/components/saved-cohorts/saved-cohorts.scss +15 -0
- package/src/components/saved-cohorts/saved-cohorts.test.tsx +51 -0
- package/src/components/saved-queries/saved-queries-options/saved-queries-options.component.tsx +112 -0
- package/src/components/saved-queries/saved-queries-options/saved-queries-options.test.tsx +47 -0
- package/src/components/saved-queries/saved-queries.component.tsx +139 -0
- package/src/components/saved-queries/saved-queries.resources.ts +39 -0
- package/src/components/saved-queries/saved-queries.scss +23 -0
- package/src/components/saved-queries/saved-queries.test.tsx +50 -0
- package/src/components/search-button-set/search-button-set.css +9 -0
- package/src/components/search-button-set/search-button-set.test.tsx +26 -0
- package/src/components/search-button-set/search-button-set.tsx +47 -0
- package/src/components/search-by-concepts/search-by-concepts.component.tsx +344 -0
- package/src/components/search-by-concepts/search-by-concepts.style.scss +48 -0
- package/src/components/search-by-concepts/search-by-concepts.test.tsx +129 -0
- package/src/components/search-by-concepts/search-concept/search-concept.component.tsx +130 -0
- package/src/components/search-by-concepts/search-concept/search-concept.resource.ts +53 -0
- package/src/components/search-by-concepts/search-concept/search-concept.style.css +25 -0
- package/src/components/search-by-concepts/search-concept/search-concept.test.tsx +89 -0
- package/src/components/search-by-demographics/search-by-demographics.component.tsx +209 -0
- package/src/components/search-by-demographics/search-by-demographics.style.scss +41 -0
- package/src/components/search-by-demographics/search-by-demographics.test.tsx +92 -0
- package/src/components/search-by-demographics/search-by-demographics.utils.ts +129 -0
- package/src/components/search-by-drug-orders/search-by-drug-orders.component.tsx +185 -0
- package/src/components/search-by-drug-orders/search-by-drug-orders.resources.ts +60 -0
- package/src/components/search-by-drug-orders/search-by-drug-orders.style.scss +4 -0
- package/src/components/search-by-drug-orders/search-by-drug-orders.test.tsx +159 -0
- package/src/components/search-by-drug-orders/search-by-drug-orders.utils.ts +118 -0
- package/src/components/search-by-encounters/search-by-encounters.component.tsx +188 -0
- package/src/components/search-by-encounters/search-by-encounters.resources.ts +51 -0
- package/src/components/search-by-encounters/search-by-encounters.style.scss +12 -0
- package/src/components/search-by-encounters/search-by-encounters.test.tsx +202 -0
- package/src/components/search-by-encounters/search-by-encounters.utils.ts +113 -0
- package/src/components/search-by-enrollments/search-by-enrollments.component.tsx +183 -0
- package/src/components/search-by-enrollments/search-by-enrollments.resources.ts +31 -0
- package/src/components/search-by-enrollments/search-by-enrollments.style.scss +4 -0
- package/src/components/search-by-enrollments/search-by-enrollments.test.tsx +158 -0
- package/src/components/search-by-enrollments/search-by-enrollments.utils.ts +69 -0
- package/src/components/search-by-location/search-by-location.component.tsx +97 -0
- package/src/components/search-by-location/search-by-location.style.scss +4 -0
- package/src/components/search-by-location/search-by-location.test.tsx +110 -0
- package/src/components/search-by-location/search-by-location.utils.ts +40 -0
- package/src/components/search-by-person-attributes/search-by-person-attributes.component.tsx +90 -0
- package/src/components/search-by-person-attributes/search-by-person-attributes.resource.ts +27 -0
- package/src/components/search-by-person-attributes/search-by-person-attributes.style.scss +4 -0
- package/src/components/search-by-person-attributes/search-by-person-attributes.test.tsx +133 -0
- package/src/components/search-by-person-attributes/search-by-person-attributes.utils.ts +42 -0
- package/src/components/search-history/search-history-options/search-history-options.component.tsx +290 -0
- package/src/components/search-history/search-history-options/search-history-options.resources.ts +29 -0
- package/src/components/search-history/search-history-options/search-history-options.test.tsx +144 -0
- package/src/components/search-history/search-history.component.tsx +174 -0
- package/src/components/search-history/search-history.style.scss +12 -0
- package/src/components/search-history/search-history.test.tsx +106 -0
- package/src/components/search-history/search-history.utils.ts +14 -0
- package/src/components/search-results-table/search-results-table.component.tsx +105 -0
- package/src/components/search-results-table/search-results-table.scss +4 -0
- package/src/components/search-results-table/search-results-table.test.tsx +47 -0
- package/src/declarations.d.tsx +2 -0
- package/src/index.ts +47 -0
- package/src/setup-tests.ts +1 -0
- package/src/types/index.ts +120 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
import { render, cleanup, screen, waitFor } from "@testing-library/react";
|
|
4
|
+
import userEvent from "@testing-library/user-event";
|
|
5
|
+
|
|
6
|
+
import Composition from "./composition.component";
|
|
7
|
+
|
|
8
|
+
const mockCompositionQuery = {
|
|
9
|
+
query: {
|
|
10
|
+
type: "org.openmrs.module.reporting.dataset.definition.PatientDataSetDefinition",
|
|
11
|
+
columns: [
|
|
12
|
+
{
|
|
13
|
+
name: "firstname",
|
|
14
|
+
key: "reporting.library.patientDataDefinition.builtIn.preferredName.givenName",
|
|
15
|
+
type: "org.openmrs.module.reporting.data.patient.definition.PatientDataDefinition",
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: "lastname",
|
|
19
|
+
key: "reporting.library.patientDataDefinition.builtIn.preferredName.familyName",
|
|
20
|
+
type: "org.openmrs.module.reporting.data.patient.definition.PatientDataDefinition",
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: "gender",
|
|
24
|
+
key: "reporting.library.patientDataDefinition.builtIn.gender",
|
|
25
|
+
type: "org.openmrs.module.reporting.data.patient.definition.PatientDataDefinition",
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: "age",
|
|
29
|
+
key: "reporting.library.patientDataDefinition.builtIn.ageOnDate.fullYears",
|
|
30
|
+
type: "org.openmrs.module.reporting.data.patient.definition.PatientDataDefinition",
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: "patientId",
|
|
34
|
+
key: "reporting.library.patientDataDefinition.builtIn.patientId",
|
|
35
|
+
type: "org.openmrs.module.reporting.data.patient.definition.PatientDataDefinition",
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
rowFilters: [
|
|
39
|
+
{
|
|
40
|
+
key: "reporting.library.cohortDefinition.builtIn.codedObsSearchAdvanced",
|
|
41
|
+
parameterValues: {
|
|
42
|
+
operator1: "LESS_THAN",
|
|
43
|
+
question: "163126AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
|
44
|
+
timeModifier: "NO",
|
|
45
|
+
},
|
|
46
|
+
type: "org.openmrs.module.reporting.dataset.definition.PatientDataSetDefinition",
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
key: "reporting.library.cohortDefinition.builtIn.encounterSearchAdvanced",
|
|
50
|
+
parameterValues: {
|
|
51
|
+
locationList: [
|
|
52
|
+
"1ce1b7d4-c865-4178-82b0-5932e51503d6",
|
|
53
|
+
"ba685651-ed3b-4e63-9b35-78893060758a",
|
|
54
|
+
"44c3efb0-2583-4c80-a79e-1f756a03c0a1",
|
|
55
|
+
],
|
|
56
|
+
timeQualifier: "ANY",
|
|
57
|
+
},
|
|
58
|
+
type: "org.openmrs.module.reporting.dataset.definition.PatientDataSetDefinition",
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
customRowFilterCombination: "(1) and (2)",
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
jest.mock("./composition.utils", () => {
|
|
66
|
+
const original = jest.requireActual("./composition.utils");
|
|
67
|
+
return {
|
|
68
|
+
...original,
|
|
69
|
+
createCompositionQuery: jest
|
|
70
|
+
.fn()
|
|
71
|
+
.mockImplementation(() => mockCompositionQuery),
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe("Test the composition component", () => {
|
|
76
|
+
afterEach(() => {
|
|
77
|
+
cleanup();
|
|
78
|
+
jest.restoreAllMocks();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("should be throw an error when an invalid composition query is entered", async () => {
|
|
82
|
+
const user = userEvent.setup();
|
|
83
|
+
const submit = jest.fn();
|
|
84
|
+
render(<Composition onSubmit={submit} />);
|
|
85
|
+
|
|
86
|
+
const compositionInput = screen.getByTestId("composition-query");
|
|
87
|
+
await user.click(compositionInput);
|
|
88
|
+
await waitFor(() => user.type(compositionInput, "random text"));
|
|
89
|
+
|
|
90
|
+
await waitFor(() => user.click(screen.getByTestId("search-btn")));
|
|
91
|
+
await waitFor(() => expect(submit).not.toBeCalled());
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("should be to search a composition query", async () => {
|
|
95
|
+
const compositionQuery = "1 and 2";
|
|
96
|
+
|
|
97
|
+
const user = userEvent.setup();
|
|
98
|
+
const submit = jest.fn();
|
|
99
|
+
render(<Composition onSubmit={submit} />);
|
|
100
|
+
const compositionInput = screen.getByTestId("composition-query");
|
|
101
|
+
await user.click(compositionInput);
|
|
102
|
+
await waitFor(() => user.type(compositionInput, compositionQuery));
|
|
103
|
+
|
|
104
|
+
await waitFor(() => user.click(screen.getByTestId("search-btn")));
|
|
105
|
+
await waitFor(() =>
|
|
106
|
+
expect(submit).toBeCalledWith(
|
|
107
|
+
mockCompositionQuery,
|
|
108
|
+
`Composition of ${compositionQuery}`
|
|
109
|
+
)
|
|
110
|
+
);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { addColumnsToDisplay } from "../../cohort-builder.utils";
|
|
2
|
+
import { Query } from "../../types";
|
|
3
|
+
|
|
4
|
+
export const isCompositionValid = (search: string) => {
|
|
5
|
+
return (
|
|
6
|
+
search.match(/and|or|not|\d+|\)|\(|union|intersection|\!|\+/gi).length ===
|
|
7
|
+
search.split(/\s+/g).length
|
|
8
|
+
);
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const formatFilterCombination = (
|
|
12
|
+
filterText: string,
|
|
13
|
+
numberOfSearches: number
|
|
14
|
+
) => {
|
|
15
|
+
return filterText.replace(/\d/, (theDigit) =>
|
|
16
|
+
(parseInt(theDigit) + numberOfSearches).toString()
|
|
17
|
+
);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const createCompositionQuery = (compositionQuery: string) => {
|
|
21
|
+
const search = compositionQuery.replace(/(\(|\))+/g, (char) =>
|
|
22
|
+
char === "(" ? "( " : " )"
|
|
23
|
+
);
|
|
24
|
+
const query: Query = {
|
|
25
|
+
type: "org.openmrs.module.reporting.dataset.definition.PatientDataSetDefinition",
|
|
26
|
+
columns: addColumnsToDisplay(),
|
|
27
|
+
rowFilters: [],
|
|
28
|
+
customRowFilterCombination: "",
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const searchTokens = search.split(/\s+/);
|
|
32
|
+
|
|
33
|
+
searchTokens.forEach((eachToken) => {
|
|
34
|
+
if (eachToken.match(/\d/)) {
|
|
35
|
+
const history = JSON.parse(
|
|
36
|
+
window.sessionStorage.getItem("openmrsHistory")
|
|
37
|
+
);
|
|
38
|
+
const operandQuery = history[parseInt(eachToken) - 1];
|
|
39
|
+
|
|
40
|
+
const jsonRequestObject = operandQuery.parameters;
|
|
41
|
+
jsonRequestObject.customRowFilterCombination = formatFilterCombination(
|
|
42
|
+
jsonRequestObject.customRowFilterCombination,
|
|
43
|
+
query.rowFilters.length
|
|
44
|
+
);
|
|
45
|
+
query.customRowFilterCombination += `(${jsonRequestObject.customRowFilterCombination})`;
|
|
46
|
+
query.rowFilters = query.rowFilters.concat(jsonRequestObject.rowFilters);
|
|
47
|
+
} else {
|
|
48
|
+
query.customRowFilterCombination += ` ${eachToken} `;
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
return { query };
|
|
53
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
import { Layer, Tile } from "@carbon/react";
|
|
4
|
+
import { EmptyDataIllustration } from "@openmrs/esm-patient-common-lib/src/empty-state/index";
|
|
5
|
+
|
|
6
|
+
import styles from "./empty-data.style.scss";
|
|
7
|
+
|
|
8
|
+
export interface EmptyDataProps {
|
|
9
|
+
displayText: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const EmptyData: React.FC<EmptyDataProps> = (props) => {
|
|
13
|
+
return (
|
|
14
|
+
<Layer>
|
|
15
|
+
<Tile className={styles.tile}>
|
|
16
|
+
<EmptyDataIllustration />
|
|
17
|
+
<p className={styles.content}>
|
|
18
|
+
There are no {props.displayText.toLowerCase()} to display
|
|
19
|
+
</p>
|
|
20
|
+
</Tile>
|
|
21
|
+
</Layer>
|
|
22
|
+
);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export default EmptyData;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
@use '@carbon/styles/scss/spacing';
|
|
2
|
+
@use '@carbon/styles/scss/type';
|
|
3
|
+
@import '~@openmrs/esm-styleguide/src/vars';
|
|
4
|
+
|
|
5
|
+
.action {
|
|
6
|
+
margin-bottom: spacing.$spacing-03;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.content {
|
|
10
|
+
@include type.type-style("productive-heading-01");
|
|
11
|
+
color: $text-02;
|
|
12
|
+
margin-top: spacing.$spacing-05;
|
|
13
|
+
margin-bottom: spacing.$spacing-03;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.tile {
|
|
17
|
+
text-align: center;
|
|
18
|
+
border: 1px solid $ui-03;
|
|
19
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
import { screen, render, cleanup, waitFor } from "@testing-library/react";
|
|
4
|
+
import userEvent from "@testing-library/user-event";
|
|
5
|
+
|
|
6
|
+
import { DefinitionDataRow } from "../../../types";
|
|
7
|
+
import SavedCohortsOptions from "./saved-cohorts-options.component";
|
|
8
|
+
|
|
9
|
+
const cohort: DefinitionDataRow = {
|
|
10
|
+
id: "1",
|
|
11
|
+
name: "Female Patients",
|
|
12
|
+
description: "Female Patients that are alive",
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
describe("Test the saved cohorts options", () => {
|
|
16
|
+
afterEach(cleanup);
|
|
17
|
+
it("should be able to view the saved cohort", async () => {
|
|
18
|
+
const user = userEvent.setup();
|
|
19
|
+
const onViewCohort = jest.fn();
|
|
20
|
+
render(
|
|
21
|
+
<SavedCohortsOptions
|
|
22
|
+
cohort={cohort}
|
|
23
|
+
onViewCohort={onViewCohort}
|
|
24
|
+
onDeleteCohort={jest.fn()}
|
|
25
|
+
/>
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
await waitFor(() => user.click(screen.getByTestId("options")));
|
|
29
|
+
await waitFor(() => user.click(screen.getByTestId("view")));
|
|
30
|
+
expect(onViewCohort).toBeCalledWith(cohort.id);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("should be able delete a cohort", async () => {
|
|
34
|
+
const user = userEvent.setup();
|
|
35
|
+
const onDeleteCohort = jest.fn();
|
|
36
|
+
render(
|
|
37
|
+
<SavedCohortsOptions
|
|
38
|
+
cohort={cohort}
|
|
39
|
+
onViewCohort={jest.fn()}
|
|
40
|
+
onDeleteCohort={onDeleteCohort}
|
|
41
|
+
/>
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
await waitFor(() => user.click(screen.getByTestId("options")));
|
|
45
|
+
await waitFor(() => user.click(screen.getByTestId("delete")));
|
|
46
|
+
await waitFor(() => user.click(screen.getByText("Delete")));
|
|
47
|
+
expect(onDeleteCohort).toBeCalledWith(cohort.id);
|
|
48
|
+
});
|
|
49
|
+
});
|
package/src/components/saved-cohorts/saved-cohorts-options/saved-cohorts-options.component.tsx
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
ComposedModal,
|
|
5
|
+
ModalFooter,
|
|
6
|
+
ModalHeader,
|
|
7
|
+
OverflowMenu,
|
|
8
|
+
OverflowMenuItem,
|
|
9
|
+
} from "@carbon/react";
|
|
10
|
+
import { showToast } from "@openmrs/esm-framework";
|
|
11
|
+
import { useTranslation } from "react-i18next";
|
|
12
|
+
|
|
13
|
+
import { DefinitionDataRow } from "../../../types";
|
|
14
|
+
|
|
15
|
+
enum Options {
|
|
16
|
+
VIEW,
|
|
17
|
+
DELETE,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface SavedCohortsOptionsProps {
|
|
21
|
+
cohort: DefinitionDataRow;
|
|
22
|
+
onViewCohort: (cohortId: string) => Promise<void>;
|
|
23
|
+
onDeleteCohort: (cohortId: string) => Promise<void>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const SavedCohortsOptions: React.FC<SavedCohortsOptionsProps> = ({
|
|
27
|
+
cohort,
|
|
28
|
+
onViewCohort,
|
|
29
|
+
onDeleteCohort,
|
|
30
|
+
}) => {
|
|
31
|
+
const { t } = useTranslation();
|
|
32
|
+
const [isDeleteCohortModalVisible, setIsDeleteCohortModalVisible] =
|
|
33
|
+
useState(false);
|
|
34
|
+
|
|
35
|
+
const handleViewCohort = async () => {
|
|
36
|
+
try {
|
|
37
|
+
await onViewCohort(cohort.id);
|
|
38
|
+
} catch (error) {
|
|
39
|
+
showToast({
|
|
40
|
+
title: t("cohortViewError", "Error viewing the cohort"),
|
|
41
|
+
kind: "error",
|
|
42
|
+
critical: true,
|
|
43
|
+
description: error?.message,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const handleOption = async (option: Options) => {
|
|
49
|
+
switch (option) {
|
|
50
|
+
case Options.VIEW:
|
|
51
|
+
handleViewCohort();
|
|
52
|
+
break;
|
|
53
|
+
case Options.DELETE:
|
|
54
|
+
setIsDeleteCohortModalVisible(true);
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const handleDeleteCohort = async () => {
|
|
60
|
+
await onDeleteCohort(cohort.id);
|
|
61
|
+
setIsDeleteCohortModalVisible(false);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<>
|
|
66
|
+
<OverflowMenu
|
|
67
|
+
ariaLabel="overflow-menu"
|
|
68
|
+
size="md"
|
|
69
|
+
flipped
|
|
70
|
+
direction="bottom"
|
|
71
|
+
data-testid="options"
|
|
72
|
+
>
|
|
73
|
+
<OverflowMenuItem
|
|
74
|
+
data-testid="view"
|
|
75
|
+
itemText={t("view", "View")}
|
|
76
|
+
onClick={() => handleOption(Options.VIEW)}
|
|
77
|
+
/>
|
|
78
|
+
<OverflowMenuItem
|
|
79
|
+
data-testid="delete"
|
|
80
|
+
itemText={t("delete", "Delete")}
|
|
81
|
+
onClick={() => handleOption(Options.DELETE)}
|
|
82
|
+
/>
|
|
83
|
+
</OverflowMenu>
|
|
84
|
+
|
|
85
|
+
<ComposedModal
|
|
86
|
+
size={"sm"}
|
|
87
|
+
open={isDeleteCohortModalVisible}
|
|
88
|
+
onClose={() => setIsDeleteCohortModalVisible(false)}
|
|
89
|
+
>
|
|
90
|
+
<ModalHeader>
|
|
91
|
+
<p>
|
|
92
|
+
{t(
|
|
93
|
+
"deleteItem",
|
|
94
|
+
`Are you sure you want to delete ${cohort?.name}?`,
|
|
95
|
+
{
|
|
96
|
+
itemName: cohort?.name,
|
|
97
|
+
}
|
|
98
|
+
)}
|
|
99
|
+
</p>
|
|
100
|
+
</ModalHeader>
|
|
101
|
+
<ModalFooter
|
|
102
|
+
danger
|
|
103
|
+
onRequestSubmit={handleDeleteCohort}
|
|
104
|
+
primaryButtonText={t("delete", "Delete")}
|
|
105
|
+
secondaryButtonText={t("cancel", "Cancel")}
|
|
106
|
+
/>
|
|
107
|
+
</ComposedModal>
|
|
108
|
+
</>
|
|
109
|
+
);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
export default SavedCohortsOptions;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import React, { useState, useEffect } from "react";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
DataTable,
|
|
5
|
+
Table,
|
|
6
|
+
TableHead,
|
|
7
|
+
TableRow,
|
|
8
|
+
TableHeader,
|
|
9
|
+
TableBody,
|
|
10
|
+
TableCell,
|
|
11
|
+
Pagination,
|
|
12
|
+
} from "@carbon/react";
|
|
13
|
+
import { showToast } from "@openmrs/esm-framework";
|
|
14
|
+
import { useTranslation } from "react-i18next";
|
|
15
|
+
|
|
16
|
+
import mainStyles from "../../cohort-builder.scss";
|
|
17
|
+
import { DefinitionDataRow, PaginationData } from "../../types";
|
|
18
|
+
import EmptyData from "../empty-data/empty-data.component";
|
|
19
|
+
import SavedCohortsOptions from "./saved-cohorts-options/saved-cohorts-options.component";
|
|
20
|
+
import { onDeleteCohort, getCohorts } from "./saved-cohorts.resources";
|
|
21
|
+
import styles from "./saved-cohorts.scss";
|
|
22
|
+
|
|
23
|
+
interface SavedCohortsProps {
|
|
24
|
+
onViewCohort: (queryId: string) => Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const SavedCohorts: React.FC<SavedCohortsProps> = ({ onViewCohort }) => {
|
|
28
|
+
const [page, setPage] = useState(1);
|
|
29
|
+
const [pageSize, setPageSize] = useState(10);
|
|
30
|
+
const [cohorts, setCohorts] = useState<DefinitionDataRow[]>([]);
|
|
31
|
+
const { t } = useTranslation();
|
|
32
|
+
|
|
33
|
+
const getTableData = async () => {
|
|
34
|
+
const cohorts = await getCohorts();
|
|
35
|
+
setCohorts(cohorts);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const headers = [
|
|
39
|
+
{
|
|
40
|
+
key: "name",
|
|
41
|
+
header: t("name", "Name"),
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
key: "description",
|
|
45
|
+
header: t("description", "Description"),
|
|
46
|
+
},
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
const handlePagination = ({ page, pageSize }: PaginationData) => {
|
|
50
|
+
setPage(page);
|
|
51
|
+
setPageSize(pageSize);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const handleDeleteCohort = async (cohortId: string) => {
|
|
55
|
+
try {
|
|
56
|
+
await onDeleteCohort(cohortId);
|
|
57
|
+
showToast({
|
|
58
|
+
title: t("success", "Success"),
|
|
59
|
+
kind: "success",
|
|
60
|
+
critical: true,
|
|
61
|
+
description: t("cohortIsDeleted", "the cohort is deleted"),
|
|
62
|
+
});
|
|
63
|
+
getTableData();
|
|
64
|
+
} catch (error) {
|
|
65
|
+
showToast({
|
|
66
|
+
title: t("cohortDeleteError", "Error deleting the cohort"),
|
|
67
|
+
kind: "error",
|
|
68
|
+
critical: true,
|
|
69
|
+
description: error?.message,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
getTableData();
|
|
76
|
+
}, []);
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<div className={styles.container}>
|
|
80
|
+
<p className={mainStyles.text}>
|
|
81
|
+
{t(
|
|
82
|
+
"savedCohortDescription",
|
|
83
|
+
"You can only search for Cohort Definitions that you have saved using a Name."
|
|
84
|
+
)}
|
|
85
|
+
</p>
|
|
86
|
+
<DataTable rows={cohorts} headers={headers} useZebraStyles>
|
|
87
|
+
{({ rows, headers, getTableProps, getHeaderProps, getRowProps }) => (
|
|
88
|
+
<Table {...getTableProps()}>
|
|
89
|
+
<TableHead>
|
|
90
|
+
<TableRow>
|
|
91
|
+
{headers.map((header) => (
|
|
92
|
+
<TableHeader {...getHeaderProps({ header })}>
|
|
93
|
+
{header.header}
|
|
94
|
+
</TableHeader>
|
|
95
|
+
))}
|
|
96
|
+
<TableHeader className={mainStyles.optionHeader}></TableHeader>
|
|
97
|
+
</TableRow>
|
|
98
|
+
</TableHead>
|
|
99
|
+
<TableBody>
|
|
100
|
+
{rows
|
|
101
|
+
.slice((page - 1) * pageSize)
|
|
102
|
+
.slice(0, pageSize)
|
|
103
|
+
.map((row, index: number) => (
|
|
104
|
+
<TableRow {...getRowProps({ row })} key={index}>
|
|
105
|
+
{row.cells.map((cell, index: number) => (
|
|
106
|
+
<TableCell key={index}>{cell.value}</TableCell>
|
|
107
|
+
))}
|
|
108
|
+
<TableCell className={mainStyles.optionCell}>
|
|
109
|
+
<SavedCohortsOptions
|
|
110
|
+
cohort={cohorts[index]}
|
|
111
|
+
onViewCohort={onViewCohort}
|
|
112
|
+
onDeleteCohort={handleDeleteCohort}
|
|
113
|
+
/>
|
|
114
|
+
</TableCell>
|
|
115
|
+
</TableRow>
|
|
116
|
+
))}
|
|
117
|
+
</TableBody>
|
|
118
|
+
</Table>
|
|
119
|
+
)}
|
|
120
|
+
</DataTable>
|
|
121
|
+
{cohorts.length > 10 && (
|
|
122
|
+
<Pagination
|
|
123
|
+
backwardText={t("previousPage", "Previous page")}
|
|
124
|
+
forwardText={t("nextPage", "Next page")}
|
|
125
|
+
itemsPerPageText={t("itemsPerPage:", "Items per page:")}
|
|
126
|
+
onChange={handlePagination}
|
|
127
|
+
page={1}
|
|
128
|
+
pageSize={10}
|
|
129
|
+
pageSizes={[10, 20, 30, 40, 50]}
|
|
130
|
+
size="md"
|
|
131
|
+
totalItems={cohorts.length}
|
|
132
|
+
/>
|
|
133
|
+
)}
|
|
134
|
+
{!cohorts.length && <EmptyData displayText={t("cohorts", "cohorts")} />}
|
|
135
|
+
</div>
|
|
136
|
+
);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
export default SavedCohorts;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { FetchResponse, openmrsFetch } from "@openmrs/esm-framework";
|
|
2
|
+
|
|
3
|
+
import { Cohort, DefinitionDataRow } from "../../types";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @returns Cohorts
|
|
7
|
+
*/
|
|
8
|
+
export async function getCohorts(): Promise<DefinitionDataRow[]> {
|
|
9
|
+
const response: FetchResponse<{ results: Cohort[] }> = await openmrsFetch(
|
|
10
|
+
"/ws/rest/v1/cohort?v=full",
|
|
11
|
+
{
|
|
12
|
+
method: "GET",
|
|
13
|
+
}
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
let cohorts: DefinitionDataRow[] = [];
|
|
17
|
+
if (response.data.results.length > 0) {
|
|
18
|
+
response.data.results.map((cohort: Cohort) => {
|
|
19
|
+
const cohortData: DefinitionDataRow = {
|
|
20
|
+
id: cohort.uuid,
|
|
21
|
+
name: cohort.name,
|
|
22
|
+
description: cohort.description,
|
|
23
|
+
};
|
|
24
|
+
cohorts.push(cohortData);
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return cohorts;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const onDeleteCohort = async (cohort: string) => {
|
|
32
|
+
const result: FetchResponse = await openmrsFetch(
|
|
33
|
+
`/ws/rest/v1/cohort/${cohort}`,
|
|
34
|
+
{
|
|
35
|
+
method: "DELETE",
|
|
36
|
+
}
|
|
37
|
+
);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
import { openmrsFetch } from "@openmrs/esm-framework";
|
|
4
|
+
import { render, cleanup, screen, waitFor } from "@testing-library/react";
|
|
5
|
+
|
|
6
|
+
import { DefinitionDataRow } from "../../types";
|
|
7
|
+
import SavedCohorts from "./saved-cohorts.component";
|
|
8
|
+
import { getCohorts } from "./saved-cohorts.resources";
|
|
9
|
+
|
|
10
|
+
const mockCohorts: DefinitionDataRow[] = [
|
|
11
|
+
{
|
|
12
|
+
id: "1",
|
|
13
|
+
name: "Female alive",
|
|
14
|
+
description: "Female Patients that are alive",
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
id: "2",
|
|
18
|
+
name: "Female ages between 10 and 30",
|
|
19
|
+
description:
|
|
20
|
+
"Female Patients with ages between 10 and 30 years that are alive",
|
|
21
|
+
},
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
const mockOpenmrsFetch = openmrsFetch as jest.Mock;
|
|
25
|
+
|
|
26
|
+
jest.mock("./saved-cohorts.resources", () => {
|
|
27
|
+
const original = jest.requireActual("./saved-cohorts.resources");
|
|
28
|
+
return {
|
|
29
|
+
...original,
|
|
30
|
+
getCohorts: jest.fn(),
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe("Test the saved cohorts component", () => {
|
|
35
|
+
afterEach(() => {
|
|
36
|
+
cleanup();
|
|
37
|
+
jest.restoreAllMocks();
|
|
38
|
+
});
|
|
39
|
+
it("should be able to search for a cohort", async () => {
|
|
40
|
+
// @ts-ignore
|
|
41
|
+
getCohorts.mockImplementation(() => mockCohorts);
|
|
42
|
+
mockOpenmrsFetch.mockReturnValue({ data: { results: mockCohorts } });
|
|
43
|
+
|
|
44
|
+
render(<SavedCohorts onViewCohort={jest.fn()} />);
|
|
45
|
+
await waitFor(() =>
|
|
46
|
+
expect(screen.getByText(mockCohorts[0].name)).toBeInTheDocument()
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
expect(screen.getByText(mockCohorts[1].name)).toBeInTheDocument();
|
|
50
|
+
});
|
|
51
|
+
});
|