@openmrs/esm-reports-app 4.2.2-pre.402 → 4.2.2-pre.414

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 (50) hide show
  1. package/.turbo/turbo-build.log +12 -12
  2. package/dist/1650.js +1 -1
  3. package/dist/1650.js.map +1 -1
  4. package/dist/2136.js +2 -0
  5. package/dist/2136.js.map +1 -0
  6. package/dist/289.js +1 -1
  7. package/dist/289.js.map +1 -1
  8. package/dist/3715.js +1 -1
  9. package/dist/3715.js.map +1 -1
  10. package/dist/3854.js +2 -0
  11. package/dist/3854.js.map +1 -0
  12. package/dist/3877.js +1 -1
  13. package/dist/3877.js.map +1 -1
  14. package/dist/4300.js +1 -1
  15. package/dist/439.js +1 -1
  16. package/dist/439.js.map +1 -1
  17. package/dist/4584.js +1 -1
  18. package/dist/4584.js.map +1 -1
  19. package/dist/6173.js +1 -0
  20. package/dist/6173.js.map +1 -0
  21. package/dist/826.js +1 -1
  22. package/dist/826.js.map +1 -1
  23. package/dist/8390.js +1 -1
  24. package/dist/8390.js.map +1 -1
  25. package/dist/main.js +1 -1
  26. package/dist/main.js.map +1 -1
  27. package/dist/openmrs-esm-reports-app.js +1 -1
  28. package/dist/openmrs-esm-reports-app.js.buildmanifest.json +83 -83
  29. package/dist/openmrs-esm-reports-app.js.map +1 -1
  30. package/dist/routes.json +1 -1
  31. package/package.json +1 -1
  32. package/src/components/overview.component.test.tsx +156 -0
  33. package/src/components/overview.component.tsx +77 -5
  34. package/src/components/report-overview-button.component.tsx +1 -1
  35. package/src/components/reports-data-overview.component.tsx +2 -2
  36. package/src/components/reports.scss +9 -2
  37. package/src/components/run-report/run-report-form.component.tsx +22 -6
  38. package/src/config-schema.ts +14 -0
  39. package/src/index.ts +3 -1
  40. package/src/types/report-definition.ts +1 -0
  41. package/translations/en.json +5 -0
  42. package/tsconfig.json +1 -1
  43. package/dist/1917.js +0 -2
  44. package/dist/1917.js.map +0 -1
  45. package/dist/2252.js +0 -2
  46. package/dist/2252.js.map +0 -1
  47. package/dist/9090.js +0 -1
  48. package/dist/9090.js.map +0 -1
  49. /package/dist/{2252.js.LICENSE.txt → 2136.js.LICENSE.txt} +0 -0
  50. /package/dist/{1917.js.LICENSE.txt → 3854.js.LICENSE.txt} +0 -0
@@ -14,7 +14,7 @@ import {
14
14
  TableHeader,
15
15
  TableRow,
16
16
  } from '@carbon/react';
17
- import { Calendar, Download, Play, Save, TrashCan } from '@carbon/react/icons';
17
+ import { Calendar, Download, Play, Save, TrashCan, View } from '@carbon/react/icons';
18
18
  import { downloadMultipleReports, downloadReport, preserveReport, useReports } from './reports.resource';
19
19
  import {
20
20
  ExtensionSlot,
@@ -22,6 +22,7 @@ import {
22
22
  navigate,
23
23
  showModal,
24
24
  showSnackbar,
25
+ useConfig,
25
26
  useLayoutType,
26
27
  userHasAccess,
27
28
  useSession,
@@ -35,10 +36,12 @@ import { COMPLETED, RAN_REPORT_STATUSES, SAVED } from './report-statuses-constan
35
36
  import { DEFAULT_PAGE_NUMBER, DEFAULT_PAGE_SIZE, DEFAULT_PAGE_SIZES } from './pagination-constants';
36
37
  import { PRIVILEGE_SYSTEM_DEVELOPER } from '../constants';
37
38
  import styles from './reports.scss';
39
+ import { type ConfigObject } from '../config-schema';
38
40
 
39
41
  const OverviewComponent: React.FC = () => {
40
42
  const { t } = useTranslation();
41
43
  const currentSession = useSession();
44
+ const { webPreviewViewReportUrl } = useConfig<ConfigObject>();
42
45
 
43
46
  let [checkedReportUuidsArray, setCheckedReportUuidsArray] = useState([]);
44
47
  const [downloadReportButtonVisible, setDownloadReportButtonVisible] = useState(false);
@@ -75,6 +78,30 @@ const OverviewComponent: React.FC = () => {
75
78
  return row?.cells.find((cell) => cell.info?.header === 'status')?.value;
76
79
  }
77
80
 
81
+ function getReportOutputFormat(row) {
82
+ return row?.cells.find((cell) => cell.info?.header === 'outputFormat')?.value;
83
+ }
84
+
85
+ function shouldShowDownloadButton(row) {
86
+ const outputFormat = getReportOutputFormat(row);
87
+ const status = getReportStatus(row);
88
+ const isWebPreviewWithUrl = outputFormat === 'Web Preview' && webPreviewViewReportUrl;
89
+
90
+ // Don't show download button for web preview when we have a view URL
91
+ if (isWebPreviewWithUrl) {
92
+ return false;
93
+ }
94
+
95
+ return status === COMPLETED || status === SAVED;
96
+ }
97
+
98
+ function shouldShowViewButton(row) {
99
+ const outputFormat = getReportOutputFormat(row);
100
+ const status = getReportStatus(row);
101
+
102
+ return outputFormat === 'Web Preview' && webPreviewViewReportUrl && (status === COMPLETED || status === SAVED);
103
+ }
104
+
78
105
  function isCurrentUserTheSameAsReportRequestedByUser(reportRequestUuid: string) {
79
106
  const report = reports.find((tableRow) => tableRow.id === reportRequestUuid);
80
107
  const requestedByUserUuid = report?.requestedByUserUuid;
@@ -162,6 +189,44 @@ const OverviewComponent: React.FC = () => {
162
189
  });
163
190
  };
164
191
 
192
+ const handleViewReport = useCallback(
193
+ (reportRequestUuid: string) => {
194
+ if (!webPreviewViewReportUrl) {
195
+ showSnackbar({
196
+ title: t('error', 'Error'),
197
+ subtitle: t('noWebPreviewUrlConfigured', 'No web preview URL configured.'),
198
+ kind: 'error',
199
+ });
200
+ return;
201
+ }
202
+
203
+ try {
204
+ // Basic URL validation
205
+ new URL(webPreviewViewReportUrl.replace('{reportRequestUuid}', 'placeholder'));
206
+ } catch {
207
+ showSnackbar({
208
+ title: t('error', 'Error'),
209
+ subtitle: t('invalidWebPreviewUrl', 'Configured web preview URL is invalid.'),
210
+ kind: 'error',
211
+ });
212
+ return;
213
+ }
214
+
215
+ if (!webPreviewViewReportUrl.includes('{reportRequestUuid}')) {
216
+ showSnackbar({
217
+ title: t('error', 'Error'),
218
+ subtitle: t('missingPlaceholder', 'Configured web preview URL must include {reportRequestUuid} placeholder.'),
219
+ kind: 'error',
220
+ });
221
+ return;
222
+ }
223
+
224
+ const viewUrl = webPreviewViewReportUrl.replace('{reportRequestUuid}', reportRequestUuid);
225
+ window.open(viewUrl, '_blank', 'noopener,noreferrer');
226
+ },
227
+ [webPreviewViewReportUrl, t],
228
+ );
229
+
165
230
  const handleDownloadReport = useCallback(
166
231
  async (reportRequestUuid: string) => {
167
232
  try {
@@ -293,13 +358,13 @@ const OverviewComponent: React.FC = () => {
293
358
  <TableRow>
294
359
  <th></th>
295
360
  {headers.map((header) => (
296
- <TableHeader>{header.header}</TableHeader>
361
+ <TableHeader key={header.key}>{header.header}</TableHeader>
297
362
  ))}
298
363
  </TableRow>
299
364
  </TableHead>
300
365
  <TableBody>
301
366
  {rows.map((row, index) => (
302
- <TableRow className={styles.tableRow}>
367
+ <TableRow className={styles.tableRow} key={index}>
303
368
  {renderRowCheckbox(row, index)}
304
369
  {row.cells.map((cell) => (
305
370
  <TableCell
@@ -310,13 +375,20 @@ const OverviewComponent: React.FC = () => {
310
375
  })}
311
376
  >
312
377
  {cell.info.header === 'actions' ? (
313
- <div>
378
+ <div className={styles.actionsContainer}>
379
+ <ReportOverviewButton
380
+ icon={() => <View size={16} className={styles.actionButtonIcon} />}
381
+ label={t('view', 'View')}
382
+ onClick={() => handleViewReport(row.id)}
383
+ reportRequestUuid={row.id}
384
+ shouldBeDisplayed={shouldShowViewButton(row)}
385
+ />
314
386
  <ReportOverviewButton
315
387
  icon={() => <Download size={16} className={styles.actionButtonIcon} />}
316
388
  label={t('download', 'Download')}
317
389
  onClick={() => handleDownloadReport(row.id)}
318
390
  reportRequestUuid={row.id}
319
- shouldBeDisplayed={getReportStatus(row) === COMPLETED || getReportStatus(row) === SAVED}
391
+ shouldBeDisplayed={shouldShowDownloadButton(row)}
320
392
  />
321
393
  <ReportOverviewButton
322
394
  icon={() => <Save size={16} className={styles.actionButtonIcon} />}
@@ -20,7 +20,7 @@ const ReportOverviewButton: React.FC<ReportOverviewButtonProps> = ({ shouldBeDis
20
20
  </div>
21
21
  );
22
22
  } else {
23
- return <div className={styles.actionButtonsWrapperDiv}></div>;
23
+ return null;
24
24
  }
25
25
  };
26
26
 
@@ -2,7 +2,7 @@ import React, { useState, useMemo, useEffect } from 'react';
2
2
  import { useTranslation } from 'react-i18next';
3
3
  import { Select, SelectItem, Button } from '@carbon/react';
4
4
  import dayjs from 'dayjs';
5
- import { ExtensionSlot, showSnackbar, getCoreTranslation } from '@openmrs/esm-framework';
5
+ import { ExtensionSlot, showSnackbar, getCoreTranslation, useConfig } from '@openmrs/esm-framework';
6
6
  import Overlay from './overlay.component';
7
7
  import ReportDataViewer from './report-data-viewer.component';
8
8
  import ReportParameter from './report-parameter.component';
@@ -67,7 +67,7 @@ const ReportsDataOverviewComponent: React.FC = () => {
67
67
  if (hasParameters) {
68
68
  // Check if all parameters have values
69
69
  const missingParameters = selectedReportDefinition?.parameters
70
- ?.filter((param) => !reportParameters[param.name])
70
+ ?.filter((param) => !reportParameters[param.name] && param.required)
71
71
  .map((param) => param.label);
72
72
 
73
73
  if (missingParameters?.length > 0) {
@@ -42,9 +42,16 @@
42
42
  width: 100px;
43
43
  }
44
44
 
45
+ .actionsContainer {
46
+ display: flex;
47
+ flex-wrap: wrap;
48
+ gap: 0.5rem;
49
+ align-items: center;
50
+ justify-content: flex-start;
51
+ }
52
+
45
53
  .actionButtonsWrapperDiv {
46
- width: 110px;
47
- display: inline-block;
54
+ flex-shrink: 0;
48
55
  }
49
56
 
50
57
  .mainActionButtonsDiv {
@@ -26,7 +26,14 @@ const RunReportForm: React.FC<RunReportForm> = ({ closePanel }) => {
26
26
  const { reportDesigns, mutateReportDesigns } = useReportDesigns(reportUuid);
27
27
 
28
28
  const supportedParameterTypes = useMemo(
29
- () => ['java.util.Date', 'java.lang.String', 'java.lang.Integer', 'org.openmrs.Location'],
29
+ () => [
30
+ 'java.util.Date',
31
+ 'java.lang.String',
32
+ 'java.lang.Integer',
33
+ 'org.openmrs.Location',
34
+ 'org.openmrs.Concept',
35
+ 'org.openmrs.EncounterType',
36
+ ],
30
37
  [],
31
38
  );
32
39
 
@@ -36,12 +43,21 @@ const RunReportForm: React.FC<RunReportForm> = ({ closePanel }) => {
36
43
 
37
44
  useEffect(() => {
38
45
  const paramTypes = currentReport?.parameters.map((param) => param.type);
39
- const isAnyNotSupportedType = !paramTypes?.every((paramType) => supportedParameterTypes.includes(paramType));
40
- const allParametersNotEmpty = currentReport?.parameters.every(
41
- (parameter) => !!reportParameters[parameter.name] && reportParameters[parameter.name] !== 'Invalid Date',
42
- );
43
46
 
44
- if (!isAnyNotSupportedType && allParametersNotEmpty && reportUuid !== '' && renderModeUuid !== '') {
47
+ const isAnyNotSupportedType =
48
+ paramTypes?.some((paramType) => !supportedParameterTypes.includes(paramType)) ?? false;
49
+ const allRequiredParametersValid =
50
+ currentReport?.parameters?.every((parameter) => {
51
+ if (!parameter.required) {
52
+ return true;
53
+ }
54
+
55
+ const paramValue = reportParameters[parameter.name];
56
+ const isValid = !!paramValue && paramValue !== 'Invalid Date' && paramValue !== '';
57
+ return isValid;
58
+ }) ?? true;
59
+
60
+ if (!isAnyNotSupportedType && allRequiredParametersValid && reportUuid !== '' && renderModeUuid !== '') {
45
61
  setIsFormValid(true);
46
62
  } else {
47
63
  setIsFormValid(false);
@@ -0,0 +1,14 @@
1
+ import { Type } from '@openmrs/esm-framework';
2
+
3
+ export const configSchema = {
4
+ webPreviewViewReportUrl: {
5
+ _type: Type.String,
6
+ _description:
7
+ 'The custom URL that the "View Report" button should open when output format is Web Preview. Use {reportRequestUuid} as placeholder.',
8
+ _default: '',
9
+ },
10
+ };
11
+
12
+ export interface ConfigObject {
13
+ webPreviewViewReportUrl: string;
14
+ }
package/src/index.ts CHANGED
@@ -1,6 +1,7 @@
1
- import { getAsyncLifecycle, registerBreadcrumbs } from '@openmrs/esm-framework';
1
+ import { defineConfigSchema, getAsyncLifecycle, registerBreadcrumbs } from '@openmrs/esm-framework';
2
2
  import { getFixedT } from 'i18next';
3
3
  import { basePath, moduleName } from './constants';
4
+ import { configSchema } from './config-schema';
4
5
 
5
6
  const options = {
6
7
  featureName: 'reports',
@@ -38,6 +39,7 @@ export function startupApp() {
38
39
  parent: `${window.getOpenmrsSpaBase()}reports`,
39
40
  },
40
41
  ]);
42
+ defineConfigSchema(moduleName, configSchema);
41
43
  }
42
44
 
43
45
  export const root = getAsyncLifecycle(() => import('./reports.component'), options);
@@ -9,4 +9,5 @@ interface ReportParameter {
9
9
  name: string;
10
10
  label: string;
11
11
  type: string;
12
+ required: boolean;
12
13
  }
@@ -21,6 +21,7 @@
21
21
  "downloadReports": "Download reports",
22
22
  "edit": "Edit",
23
23
  "editScheduledReport": "Edit scheduled report",
24
+ "error": "Error",
24
25
  "errorDownloadingReport": "Error downloading report",
25
26
  "errorDownloadingReports": "Error downloading reports",
26
27
  "errorFetchingReport": "Error fetching report",
@@ -29,13 +30,16 @@
29
30
  "exportCSV": "Export CSV",
30
31
  "failed": "Failed",
31
32
  "fetchReport": "Fetch Report",
33
+ "invalidWebPreviewUrl": "Configured web preview URL is invalid.",
32
34
  "missingParameters": "Please provide the following parameters: {parameters}",
35
+ "missingPlaceholder": "Configured web preview URL must include {reportRequestUuid} placeholder.",
33
36
  "nextPage": "Next page",
34
37
  "nextRun": "Next run",
35
38
  "no": "No",
36
39
  "noDataAvailable": "No data available",
37
40
  "notATimeText": "hh:mm 24-hr pattern required",
38
41
  "notScheduled": "Not scheduled",
42
+ "noWebPreviewUrlConfigured": "No web preview URL configured.",
39
43
  "on": "on",
40
44
  "outputFormat": "Output format",
41
45
  "parameters": "Parameters",
@@ -82,6 +86,7 @@
82
86
  "selectReportLabel": "Report",
83
87
  "status": "Status",
84
88
  "unknownParameterType": "Unknown parameter type: {type} for parameter: {label}",
89
+ "view": "View",
85
90
  "viewReports": "Reports Webview",
86
91
  "yes": "Yes"
87
92
  }
package/tsconfig.json CHANGED
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "extends": "../../tsconfig.json",
3
- "include": ["src/**/*"],
3
+ "include": ["src/**/*", "../../tools/setup-tests.ts"],
4
4
  }