@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, { 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 SavedQueriesOptionsProps {
21
+ query: DefinitionDataRow;
22
+ onViewQuery: (queryId: string) => Promise<void>;
23
+ deleteQuery: (queryId: string) => Promise<void>;
24
+ }
25
+
26
+ const SavedQueriesOptions: React.FC<SavedQueriesOptionsProps> = ({
27
+ query,
28
+ onViewQuery,
29
+ deleteQuery,
30
+ }) => {
31
+ const { t } = useTranslation();
32
+ const [isDeleteQueryModalVisible, setIsDeleteQueryModalVisible] =
33
+ useState(false);
34
+
35
+ const handleOption = async (option: Options) => {
36
+ switch (option) {
37
+ case Options.VIEW:
38
+ handleViewQuery();
39
+ break;
40
+ case Options.DELETE:
41
+ setIsDeleteQueryModalVisible(true);
42
+ break;
43
+ }
44
+ };
45
+
46
+ const handleViewQuery = async () => {
47
+ try {
48
+ await onViewQuery(query.id);
49
+ } catch (error) {
50
+ showToast({
51
+ title: t("QueryDeleteError", "Something went wrong"),
52
+ kind: "error",
53
+ critical: true,
54
+ description: error?.message,
55
+ });
56
+ }
57
+ };
58
+
59
+ const handleDeleteQuery = async () => {
60
+ await deleteQuery(query.id);
61
+ setIsDeleteQueryModalVisible(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={isDeleteQueryModalVisible}
88
+ onClose={() => setIsDeleteQueryModalVisible(false)}
89
+ >
90
+ <ModalHeader>
91
+ <p>
92
+ {t(
93
+ "deleteItem",
94
+ `Are you sure you want to delete ${query?.name}?`,
95
+ {
96
+ item: query?.name,
97
+ }
98
+ )}
99
+ </p>
100
+ </ModalHeader>
101
+ <ModalFooter
102
+ danger
103
+ onRequestSubmit={handleDeleteQuery}
104
+ primaryButtonText={t("delete", "Delete")}
105
+ secondaryButtonText={t("cancel", "Cancel")}
106
+ />
107
+ </ComposedModal>
108
+ </>
109
+ );
110
+ };
111
+
112
+ export default SavedQueriesOptions;
@@ -0,0 +1,47 @@
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 SavedQueriesOptions from "./saved-queries-options.component";
8
+
9
+ const query: DefinitionDataRow = {
10
+ id: "1",
11
+ name: "Female Patients",
12
+ description: "Female Patients that are alive",
13
+ };
14
+
15
+ const testProps = {
16
+ query: query,
17
+ onViewQuery: jest.fn(),
18
+ deleteQuery: jest.fn(),
19
+ };
20
+
21
+ const renderSavedQueriesOptions = (props = testProps) => {
22
+ render(<SavedQueriesOptions {...props} />);
23
+ };
24
+
25
+ describe("Test the saved queries options", () => {
26
+ afterEach(cleanup);
27
+ it("should be able to view the saved query", async () => {
28
+ const user = userEvent.setup();
29
+ const onViewQuery = jest.fn();
30
+ renderSavedQueriesOptions({ ...testProps, onViewQuery });
31
+
32
+ await waitFor(() => user.click(screen.getByTestId("options")));
33
+ await waitFor(() => user.click(screen.getByTestId("view")));
34
+ expect(onViewQuery).toBeCalledWith(query.id);
35
+ });
36
+
37
+ it("should be able delete a query", async () => {
38
+ const user = userEvent.setup();
39
+ const deleteQuery = jest.fn();
40
+ renderSavedQueriesOptions({ ...testProps, deleteQuery });
41
+
42
+ await waitFor(() => user.click(screen.getByTestId("options")));
43
+ await waitFor(() => user.click(screen.getByTestId("delete")));
44
+ await waitFor(() => user.click(screen.getByText("Delete")));
45
+ expect(deleteQuery).toBeCalledWith(query.id);
46
+ });
47
+ });
@@ -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 SavedQueriesOptions from "./saved-queries-options/saved-queries-options.component";
20
+ import { deleteDataSet, getQueries } from "./saved-queries.resources";
21
+ import styles from "./saved-queries.scss";
22
+
23
+ interface SavedQueriesProps {
24
+ onViewQuery: (queryId: string) => Promise<void>;
25
+ }
26
+
27
+ const SavedQueries: React.FC<SavedQueriesProps> = ({ onViewQuery }) => {
28
+ const [page, setPage] = useState(1);
29
+ const [pageSize, setPageSize] = useState(10);
30
+ const { t } = useTranslation();
31
+ const [queries, setQueries] = useState<DefinitionDataRow[]>([]);
32
+
33
+ const getTableData = async () => {
34
+ const queries = await getQueries();
35
+ setQueries(queries);
36
+ };
37
+
38
+ const deleteQuery = async (queryId: string) => {
39
+ try {
40
+ await deleteDataSet(queryId);
41
+ showToast({
42
+ title: t("success", "Success"),
43
+ kind: "success",
44
+ critical: true,
45
+ description: t("queryIsDeleted", "the query is deleted"),
46
+ });
47
+ getTableData();
48
+ } catch (error) {
49
+ showToast({
50
+ title: t("queryDeleteError", "Error saving the query"),
51
+ kind: "error",
52
+ critical: true,
53
+ description: error?.message,
54
+ });
55
+ }
56
+ };
57
+
58
+ useEffect(() => {
59
+ getTableData();
60
+ }, []);
61
+
62
+ const headers = [
63
+ {
64
+ key: "name",
65
+ header: t("name", "Name"),
66
+ },
67
+ {
68
+ key: "description",
69
+ header: t("description", "Description"),
70
+ },
71
+ ];
72
+
73
+ const handlePagination = ({ page, pageSize }: PaginationData) => {
74
+ setPage(page);
75
+ setPageSize(pageSize);
76
+ };
77
+
78
+ return (
79
+ <div className={styles.container}>
80
+ <p className={mainStyles.text}>
81
+ {t(
82
+ "savedQueryDescription",
83
+ "You can only search for Query Definitions that you have saved using a Name."
84
+ )}
85
+ </p>
86
+ <DataTable rows={queries} 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) => (
106
+ <TableCell key={index}>{cell.value}</TableCell>
107
+ ))}
108
+ <TableCell className={mainStyles.optionCell}>
109
+ <SavedQueriesOptions
110
+ query={queries[index]}
111
+ onViewQuery={onViewQuery}
112
+ deleteQuery={deleteQuery}
113
+ />
114
+ </TableCell>
115
+ </TableRow>
116
+ ))}
117
+ </TableBody>
118
+ </Table>
119
+ )}
120
+ </DataTable>
121
+ {queries.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={queries.length}
132
+ />
133
+ )}
134
+ {!queries.length && <EmptyData displayText={t("queries", "queries")} />}
135
+ </div>
136
+ );
137
+ };
138
+
139
+ export default SavedQueries;
@@ -0,0 +1,39 @@
1
+ import { FetchResponse, openmrsFetch } from "@openmrs/esm-framework";
2
+
3
+ import { Response, DefinitionDataRow } from "../../types";
4
+
5
+ /**
6
+ * @returns Queries
7
+ */
8
+ export async function getQueries(): Promise<DefinitionDataRow[]> {
9
+ const response: FetchResponse<{ results: Response[] }> = await openmrsFetch(
10
+ "/ws/rest/v1/reportingrest/dataSetDefinition?v=full",
11
+ {
12
+ method: "GET",
13
+ }
14
+ );
15
+
16
+ let queries: DefinitionDataRow[] = [];
17
+ if (response.data.results.length > 0) {
18
+ response.data.results.map((query: Response) => {
19
+ const queryData: DefinitionDataRow = {
20
+ id: query.uuid,
21
+ name: query.name.replace("[AdHocDataExport]", ""),
22
+ description: query.description,
23
+ };
24
+ queries.push(queryData);
25
+ });
26
+ }
27
+
28
+ return queries;
29
+ }
30
+
31
+ export const deleteDataSet = async (queryID: string) => {
32
+ const dataset: FetchResponse = await openmrsFetch(
33
+ `/ws/rest/v1/reportingrest/adhocdataset/${queryID}?purge=true`,
34
+ {
35
+ method: "DELETE",
36
+ }
37
+ );
38
+ return dataset;
39
+ };
@@ -0,0 +1,23 @@
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
+ }
16
+
17
+ .optionCell {
18
+ padding: 0 !important;
19
+ }
20
+
21
+ .optionHeader {
22
+ width: 1.5rem !important;
23
+ }
@@ -0,0 +1,50 @@
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 SavedQueries from "./saved-queries.component";
8
+ import { getQueries } from "./saved-queries.resources";
9
+
10
+ const mockQueries: DefinitionDataRow[] = [
11
+ {
12
+ id: "1",
13
+ name: "male alive",
14
+ description: "male Patients that are alive",
15
+ },
16
+ {
17
+ id: "2",
18
+ name: "Female ages between 10 and 30",
19
+ description:
20
+ "male 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-queries.resources", () => {
27
+ const original = jest.requireActual("./saved-queries.resources");
28
+ return {
29
+ ...original,
30
+ getQueries: jest.fn(),
31
+ };
32
+ });
33
+
34
+ describe("Test the saved queries component", () => {
35
+ afterEach(cleanup);
36
+ it("should be able to search for a query", async () => {
37
+ // @ts-ignore
38
+ getQueries.mockImplementation(() => mockQueries);
39
+ mockOpenmrsFetch.mockReturnValueOnce({
40
+ data: { results: mockQueries },
41
+ });
42
+
43
+ render(<SavedQueries onViewQuery={jest.fn()} />);
44
+
45
+ await waitFor(() =>
46
+ expect(screen.getByText(mockQueries[0].name)).toBeInTheDocument()
47
+ );
48
+ expect(screen.getByText(mockQueries[1].name)).toBeInTheDocument();
49
+ });
50
+ });
@@ -0,0 +1,9 @@
1
+ .container {
2
+ padding-bottom: 0.5rem;
3
+ padding-top: 1rem;
4
+ }
5
+
6
+ .buttonSet {
7
+ justify-content: end;
8
+ }
9
+
@@ -0,0 +1,26 @@
1
+ import React from "react";
2
+
3
+ import { screen, render, waitFor } from "@testing-library/react";
4
+ import userEvent from "@testing-library/user-event";
5
+
6
+ import SearchButtonSet from "./search-button-set";
7
+
8
+ describe("Test the search button set component", () => {
9
+ it("should be able search and reset", async () => {
10
+ const user = userEvent.setup();
11
+ const handleSubmit = jest.fn();
12
+ const handleReset = jest.fn();
13
+ render(
14
+ <SearchButtonSet
15
+ onHandleReset={handleReset}
16
+ onHandleSubmit={handleSubmit}
17
+ isLoading={false}
18
+ />
19
+ );
20
+
21
+ await user.click(screen.getByTestId("reset-btn"));
22
+ await waitFor(() => expect(handleReset).toBeCalled());
23
+ await user.click(screen.getByTestId("search-btn"));
24
+ await waitFor(() => expect(handleSubmit).toBeCalled());
25
+ });
26
+ });
@@ -0,0 +1,47 @@
1
+ import React from "react";
2
+
3
+ import { Button, ButtonSet, Column, InlineLoading } from "@carbon/react";
4
+ import { useTranslation } from "react-i18next";
5
+
6
+ import styles from "./search-button-set.css";
7
+
8
+ interface SearchButtonSet {
9
+ isLoading: boolean;
10
+ onHandleSubmit: () => void;
11
+ onHandleReset: () => void;
12
+ }
13
+
14
+ const SearchButtonSet: React.FC<SearchButtonSet> = ({
15
+ isLoading,
16
+ onHandleSubmit,
17
+ onHandleReset,
18
+ }) => {
19
+ const { t } = useTranslation();
20
+
21
+ return (
22
+ <Column sm={2} md={{ offset: 4 }} className={styles.container}>
23
+ <ButtonSet className={styles.buttonSet}>
24
+ <Button
25
+ kind="secondary"
26
+ onClick={onHandleReset}
27
+ data-testid="reset-btn"
28
+ >
29
+ {t("reset", "Reset")}
30
+ </Button>
31
+ <Button
32
+ kind="primary"
33
+ onClick={onHandleSubmit}
34
+ data-testid="search-btn"
35
+ >
36
+ {isLoading ? (
37
+ <InlineLoading description={t("loading", "Loading")} />
38
+ ) : (
39
+ t("search", "Search")
40
+ )}
41
+ </Button>
42
+ </ButtonSet>
43
+ </Column>
44
+ );
45
+ };
46
+
47
+ export default SearchButtonSet;