@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.
Files changed (103) hide show
  1. package/LICENSE +401 -0
  2. package/README.md +16 -0
  3. package/dist/130.js +2 -0
  4. package/dist/130.js.LICENSE.txt +9 -0
  5. package/dist/139.js +1 -0
  6. package/dist/247.js +1 -0
  7. package/dist/294.js +2 -0
  8. package/dist/294.js.LICENSE.txt +9 -0
  9. package/dist/434.js +2 -0
  10. package/dist/434.js.LICENSE.txt +12 -0
  11. package/dist/480.js +2 -0
  12. package/dist/480.js.LICENSE.txt +12 -0
  13. package/dist/484.js +1 -0
  14. package/dist/574.js +1 -0
  15. package/dist/595.js +2 -0
  16. package/dist/595.js.LICENSE.txt +3 -0
  17. package/dist/690.js +1 -0
  18. package/dist/873.js +1 -0
  19. package/dist/935.js +2 -0
  20. package/dist/935.js.LICENSE.txt +19 -0
  21. package/dist/964.js +2 -0
  22. package/dist/964.js.LICENSE.txt +14 -0
  23. package/dist/main.js +1 -0
  24. package/dist/openmrs-esm-cohort-builder-app.js +1 -0
  25. package/dist/openmrs-esm-cohort-builder-app.js.buildmanifest.json +426 -0
  26. package/dist/openmrs-esm-cohort-builder-app.old +1 -0
  27. package/package.json +99 -0
  28. package/src/cohort-builder-app-menu-link.tsx +14 -0
  29. package/src/cohort-builder.resources.ts +84 -0
  30. package/src/cohort-builder.scss +126 -0
  31. package/src/cohort-builder.test.tsx +12 -0
  32. package/src/cohort-builder.tsx +213 -0
  33. package/src/cohort-builder.utils.ts +197 -0
  34. package/src/components/composition/composition.component.tsx +109 -0
  35. package/src/components/composition/composition.style.css +3 -0
  36. package/src/components/composition/composition.test.tsx +112 -0
  37. package/src/components/composition/composition.utils.ts +53 -0
  38. package/src/components/empty-data/empty-data.component.tsx +25 -0
  39. package/src/components/empty-data/empty-data.style.scss +19 -0
  40. package/src/components/saved-cohorts/saved-cohorts-options/saved-cohort-options.test.tsx +49 -0
  41. package/src/components/saved-cohorts/saved-cohorts-options/saved-cohorts-options.component.tsx +112 -0
  42. package/src/components/saved-cohorts/saved-cohorts.component.tsx +139 -0
  43. package/src/components/saved-cohorts/saved-cohorts.resources.ts +39 -0
  44. package/src/components/saved-cohorts/saved-cohorts.scss +15 -0
  45. package/src/components/saved-cohorts/saved-cohorts.test.tsx +51 -0
  46. package/src/components/saved-queries/saved-queries-options/saved-queries-options.component.tsx +112 -0
  47. package/src/components/saved-queries/saved-queries-options/saved-queries-options.test.tsx +47 -0
  48. package/src/components/saved-queries/saved-queries.component.tsx +139 -0
  49. package/src/components/saved-queries/saved-queries.resources.ts +39 -0
  50. package/src/components/saved-queries/saved-queries.scss +23 -0
  51. package/src/components/saved-queries/saved-queries.test.tsx +50 -0
  52. package/src/components/search-button-set/search-button-set.css +9 -0
  53. package/src/components/search-button-set/search-button-set.test.tsx +26 -0
  54. package/src/components/search-button-set/search-button-set.tsx +47 -0
  55. package/src/components/search-by-concepts/search-by-concepts.component.tsx +344 -0
  56. package/src/components/search-by-concepts/search-by-concepts.style.scss +48 -0
  57. package/src/components/search-by-concepts/search-by-concepts.test.tsx +129 -0
  58. package/src/components/search-by-concepts/search-concept/search-concept.component.tsx +130 -0
  59. package/src/components/search-by-concepts/search-concept/search-concept.resource.ts +53 -0
  60. package/src/components/search-by-concepts/search-concept/search-concept.style.css +25 -0
  61. package/src/components/search-by-concepts/search-concept/search-concept.test.tsx +89 -0
  62. package/src/components/search-by-demographics/search-by-demographics.component.tsx +209 -0
  63. package/src/components/search-by-demographics/search-by-demographics.style.scss +41 -0
  64. package/src/components/search-by-demographics/search-by-demographics.test.tsx +92 -0
  65. package/src/components/search-by-demographics/search-by-demographics.utils.ts +129 -0
  66. package/src/components/search-by-drug-orders/search-by-drug-orders.component.tsx +185 -0
  67. package/src/components/search-by-drug-orders/search-by-drug-orders.resources.ts +60 -0
  68. package/src/components/search-by-drug-orders/search-by-drug-orders.style.scss +4 -0
  69. package/src/components/search-by-drug-orders/search-by-drug-orders.test.tsx +159 -0
  70. package/src/components/search-by-drug-orders/search-by-drug-orders.utils.ts +118 -0
  71. package/src/components/search-by-encounters/search-by-encounters.component.tsx +188 -0
  72. package/src/components/search-by-encounters/search-by-encounters.resources.ts +51 -0
  73. package/src/components/search-by-encounters/search-by-encounters.style.scss +12 -0
  74. package/src/components/search-by-encounters/search-by-encounters.test.tsx +202 -0
  75. package/src/components/search-by-encounters/search-by-encounters.utils.ts +113 -0
  76. package/src/components/search-by-enrollments/search-by-enrollments.component.tsx +183 -0
  77. package/src/components/search-by-enrollments/search-by-enrollments.resources.ts +31 -0
  78. package/src/components/search-by-enrollments/search-by-enrollments.style.scss +4 -0
  79. package/src/components/search-by-enrollments/search-by-enrollments.test.tsx +158 -0
  80. package/src/components/search-by-enrollments/search-by-enrollments.utils.ts +69 -0
  81. package/src/components/search-by-location/search-by-location.component.tsx +97 -0
  82. package/src/components/search-by-location/search-by-location.style.scss +4 -0
  83. package/src/components/search-by-location/search-by-location.test.tsx +110 -0
  84. package/src/components/search-by-location/search-by-location.utils.ts +40 -0
  85. package/src/components/search-by-person-attributes/search-by-person-attributes.component.tsx +90 -0
  86. package/src/components/search-by-person-attributes/search-by-person-attributes.resource.ts +27 -0
  87. package/src/components/search-by-person-attributes/search-by-person-attributes.style.scss +4 -0
  88. package/src/components/search-by-person-attributes/search-by-person-attributes.test.tsx +133 -0
  89. package/src/components/search-by-person-attributes/search-by-person-attributes.utils.ts +42 -0
  90. package/src/components/search-history/search-history-options/search-history-options.component.tsx +290 -0
  91. package/src/components/search-history/search-history-options/search-history-options.resources.ts +29 -0
  92. package/src/components/search-history/search-history-options/search-history-options.test.tsx +144 -0
  93. package/src/components/search-history/search-history.component.tsx +174 -0
  94. package/src/components/search-history/search-history.style.scss +12 -0
  95. package/src/components/search-history/search-history.test.tsx +106 -0
  96. package/src/components/search-history/search-history.utils.ts +14 -0
  97. package/src/components/search-results-table/search-results-table.component.tsx +105 -0
  98. package/src/components/search-results-table/search-results-table.scss +4 -0
  99. package/src/components/search-results-table/search-results-table.test.tsx +47 -0
  100. package/src/declarations.d.tsx +2 -0
  101. package/src/index.ts +47 -0
  102. package/src/setup-tests.ts +1 -0
  103. 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
+ });
@@ -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,15 @@
1
+ .container {
2
+ background: white;
3
+ padding: 1rem;
4
+ }
5
+
6
+ .searchContainer {
7
+ display: flex;
8
+ margin-bottom: 2rem;
9
+ }
10
+
11
+ .searchBtn{
12
+ :global(.cds--btn) {
13
+ min-height: 1rem;
14
+ }
15
+ }
@@ -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
+ });