-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
(feat) O3-1494: Add report admin dashboard (#45)
* O3-1494: Added Report Admin pages - Reports overview - Report schedules overview * O3-1494: Adjusted API calls to backend changes * O3-1494: Changes after code review * O3-1494: Changes after code review part2 * O3-1494: Updated backend dependencies --------- Co-authored-by: druchniewicz <[email protected]>
- Loading branch information
1 parent
0ac119c
commit 626b777
Showing
52 changed files
with
13,545 additions
and
1,092 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
![Node.js CI](https://github.com/openmrs/openmrs-esm-template-app/workflows/Node.js%20CI/badge.svg) | ||
|
||
# Reports Module | ||
|
||
`openmrs-esm-reports-app` is a modular reporting solution for O3, providing tools to manage report executions and schedules. | ||
|
||
## Features | ||
|
||
- `Report Execution Overview`: Monitor execution history including queued reports, with options to execute specific reports, preserve, download, or delete completed executions | ||
|
||
- `Schedule Management`: View execution schedules with capabilities to view, edit, or delete existing schedules | ||
The Reports app is available in the app switcher menu under the `Reports` entry. | ||
|
||
## Setup | ||
|
||
See the guidance in the [Developer Documentation](https://o3-docs.openmrs.org/docs/frontend-modules/development#installing-dependencies). | ||
|
||
This repository uses Yarn. | ||
|
||
To run a dev server for this package, run | ||
|
||
```bash | ||
yarn start --sources 'packages/esm-reports-app' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
const rootConfig = require('../../jest.config.js'); | ||
|
||
module.exports = rootConfig; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
{ | ||
"name": "@openmrs/esm-reports-app", | ||
"version": "4.0.1", | ||
"license": "MPL-2.0", | ||
"description": "Reports admin dashboard for 03", | ||
"browser": "dist/openmrs-esm-reports-app.js", | ||
"main": "src/index.ts", | ||
"source": true, | ||
"scripts": { | ||
"start": "openmrs develop", | ||
"serve": "webpack serve --mode=development", | ||
"build": "webpack --mode production", | ||
"analyze": "webpack --mode=production --env.analyze=true", | ||
"lint": "eslint src --ext js,jsx,ts,tsx --max-warnings=0", | ||
"typescript": "tsc", | ||
"test": "cross-env TZ=UTC jest --config jest.config.js --verbose false --passWithNoTests --color", | ||
"test:watch": "cross-env TZ=UTC jest --watch --config jest.config.js --color", | ||
"extract-translations": "i18next 'src/**/*.component.tsx' --config ../../tools/i18next-parser.config.js" | ||
}, | ||
"browserslist": [ | ||
"extends browserslist-config-openmrs" | ||
], | ||
"keywords": [ | ||
"openmrs", | ||
"microfrontends", | ||
"reports" | ||
], | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/openmrs/openmrs-esm-admin-tools.git" | ||
}, | ||
"homepage": "https://github.com/openmrs/openmrs-esm-admin-tools#readme", | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"bugs": { | ||
"url": "https://github.com/openmrs/openmrs-esm-admin-tools/issues" | ||
}, | ||
"dependencies": { | ||
"@carbon/react": "^1.33.1", | ||
"@datasert/cronjs-matcher": "^1.2.0", | ||
"@datasert/cronjs-parser": "^1.2.0", | ||
"cronstrue": "^2.41.0", | ||
"dayjs": "^1.8.36", | ||
"lodash-es": "^4.17.21", | ||
"react-image-annotate": "^1.8.0" | ||
}, | ||
"peerDependencies": { | ||
"@openmrs/esm-framework": "*", | ||
"dayjs": "1.x", | ||
"react": "18.x", | ||
"react-i18next": "11.x", | ||
"react-router-dom": "6.x", | ||
"rxjs": "6.x" | ||
} | ||
} |
149 changes: 149 additions & 0 deletions
149
...reports-app/src/components/edit-scheduled-report/edit-scheduled-report-form.component.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
import React, { useCallback, useEffect, useState } from 'react'; | ||
import { take } from 'rxjs/operators'; | ||
import styles from './edit-scheduled-report-form.scss'; | ||
import SimpleCronEditor from '../simple-cron-editor/simple-cron-editor.component'; | ||
import { useReportDefinition, useReportDesigns, useReportRequest, runReportObservable } from '../reports.resource'; | ||
import ReportParameterInput from '../report-parameter-input.component'; | ||
import { Button, ButtonSet, Form, Select, SelectItem, Stack } from '@carbon/react'; | ||
import { useTranslation } from 'react-i18next'; | ||
import { showSnackbar, useLayoutType } from '@openmrs/esm-framework'; | ||
import classNames from 'classnames'; | ||
|
||
interface EditScheduledReportForm { | ||
reportDefinitionUuid: string; | ||
reportRequestUuid: string; | ||
closePanel: () => void; | ||
} | ||
|
||
const EditScheduledReportForm: React.FC<EditScheduledReportForm> = ({ | ||
reportDefinitionUuid, | ||
reportRequestUuid, | ||
closePanel, | ||
}) => { | ||
const { t } = useTranslation(); | ||
const isTablet = useLayoutType() === 'tablet'; | ||
|
||
const reportDefinition = useReportDefinition(reportDefinitionUuid); | ||
const { reportDesigns } = useReportDesigns(reportDefinitionUuid); | ||
const { reportRequest } = useReportRequest(reportRequestUuid); | ||
|
||
const [reportParameters, setReportParameters] = useState({}); | ||
const [renderModeUuid, setRenderModeUuid] = useState(); | ||
const [initialCron, setInitialCron] = useState(); | ||
const [schedule, setSchedule] = useState(''); | ||
|
||
const [isSubmitting, setIsSubmitting] = useState(false); | ||
const [isSubmittable, setIsSubmittable] = useState(false); | ||
const [ignoreChanges, setIgnoreChanges] = useState(true); | ||
|
||
useEffect(() => { | ||
setInitialCron(reportRequest?.schedule); | ||
setRenderModeUuid(reportRequest?.renderingMode?.argument); | ||
}, [reportRequest]); | ||
|
||
const handleSubmit = useCallback( | ||
(event) => { | ||
event.preventDefault(); | ||
|
||
setIsSubmitting(true); | ||
|
||
const scheduleRequest = { | ||
uuid: reportRequestUuid ? reportRequestUuid : null, | ||
reportDefinition: { | ||
parameterizable: { | ||
uuid: reportDefinitionUuid, | ||
}, | ||
parameterMappings: reportParameters, | ||
}, | ||
renderingMode: { | ||
argument: renderModeUuid, | ||
}, | ||
schedule, | ||
}; | ||
|
||
runReportObservable(scheduleRequest) | ||
.pipe(take(1)) | ||
.subscribe( | ||
() => { | ||
showSnackbar({ | ||
kind: 'success', | ||
title: t('reportScheduled', 'Report scheduled'), | ||
subtitle: t('reportScheduledSuccessfullyMsg', 'Report scheduled successfully'), | ||
}); | ||
closePanel(); | ||
setIsSubmitting(false); | ||
}, | ||
() => { | ||
showSnackbar({ | ||
kind: 'error', | ||
title: t('reportScheduledErrorMsg', 'Failed to schedule a report'), | ||
subtitle: t('reportScheduledErrorMsg', 'Failed to schedule a report'), | ||
}); | ||
closePanel(); | ||
setIsSubmitting(false); | ||
}, | ||
); | ||
}, | ||
[closePanel, renderModeUuid, reportRequestUuid, reportParameters, schedule], | ||
); | ||
|
||
const handleOnChange = () => { | ||
setIgnoreChanges((prevState) => !prevState); | ||
}; | ||
|
||
const handleCronEditorChange = (cron: string, isValid: boolean) => { | ||
setSchedule(isValid ? cron : ''); | ||
}; | ||
|
||
useEffect(() => { | ||
setIsSubmittable(!!schedule && !!renderModeUuid); | ||
}, [schedule, renderModeUuid]); | ||
|
||
return ( | ||
<Form className={styles.desktopEditSchedule} onChange={handleOnChange} onSubmit={handleSubmit}> | ||
<Stack gap={8} className={styles.container}> | ||
<SimpleCronEditor initialCron={initialCron} onChange={handleCronEditorChange} /> | ||
{reportDefinition?.parameters.map((parameter) => ( | ||
<ReportParameterInput | ||
key={`${reportDefinition.name}-${parameter.name}-param-input`} | ||
parameter={parameter} | ||
value={reportRequest?.parameterMappings[parameter.name]} | ||
onChange={(parameterValue) => { | ||
setReportParameters((state) => ({ | ||
...state, | ||
[parameter.name]: parameterValue, | ||
})); | ||
}} | ||
/> | ||
))} | ||
<div className={styles.outputFormatDiv}> | ||
<Select | ||
className={styles.basicInputElement} | ||
labelText={t('outputFormat', 'Output format')} | ||
onChange={(e) => setRenderModeUuid(e.target.value)} | ||
value={renderModeUuid} | ||
> | ||
<SelectItem text="" value={''} /> | ||
{reportDesigns?.map((reportDesign) => ( | ||
<SelectItem key={reportDesign.uuid} text={reportDesign.name} value={reportDesign.uuid}> | ||
{reportDesign.name} | ||
</SelectItem> | ||
))} | ||
</Select> | ||
</div> | ||
</Stack> | ||
<div className={styles.buttonsDiv}> | ||
<ButtonSet className={classNames({ [styles.tablet]: isTablet, [styles.desktop]: !isTablet })}> | ||
<Button className={styles.button} kind="secondary" onClick={closePanel}> | ||
{t('cancel', 'Cancel')} | ||
</Button> | ||
<Button className={styles.button} disabled={isSubmitting || !isSubmittable} kind="primary" type="submit"> | ||
{t('save', 'Save')} | ||
</Button> | ||
</ButtonSet> | ||
</div> | ||
</Form> | ||
); | ||
}; | ||
|
||
export default EditScheduledReportForm; |
60 changes: 60 additions & 0 deletions
60
...ages/esm-reports-app/src/components/edit-scheduled-report/edit-scheduled-report-form.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
@use '@carbon/layout'; | ||
@use '@carbon/type'; | ||
@use '@openmrs/esm-styleguide/src/vars' as *; | ||
|
||
.tablet { | ||
padding: layout.$spacing-06 layout.$spacing-05; | ||
background-color: $ui-02; | ||
} | ||
|
||
.desktop { | ||
padding: 0rem; | ||
} | ||
|
||
.button { | ||
height: 4rem; | ||
display: flex; | ||
align-content: flex-start; | ||
align-items: baseline; | ||
min-width: 50%; | ||
} | ||
|
||
.container { | ||
margin: layout.$spacing-05 0rem; | ||
background-color: $ui-background; | ||
|
||
& section { | ||
margin: layout.$spacing-02 layout.$spacing-05 0; | ||
} | ||
} | ||
|
||
.desktopEditSchedule { | ||
background-color: $ui-background; | ||
display: flex; | ||
flex-direction: column; | ||
justify-content: space-between; | ||
} | ||
|
||
.outputFormatDiv { | ||
margin-bottom: 50px; | ||
display: flex; | ||
padding: 32px 16px 16px 16px; | ||
flex-direction: column; | ||
align-items: flex-start; | ||
gap: 16px; | ||
} | ||
|
||
.basicInputElement { | ||
width: 300px; | ||
height: 30px; | ||
margin-bottom: 30px; | ||
} | ||
|
||
.buttonsDiv { | ||
margin-top: 50px; | ||
} | ||
|
||
.reportButton { | ||
max-width: none !important; | ||
width: 350px !important; | ||
} |
31 changes: 31 additions & 0 deletions
31
packages/esm-reports-app/src/components/next-report-execution.component.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import React from 'react'; | ||
import * as cronjsParser from '@datasert/cronjs-parser'; | ||
import * as cronjsMatcher from '@datasert/cronjs-matcher'; | ||
import dayjs from 'dayjs'; | ||
import utc from 'dayjs/plugin/utc'; | ||
|
||
dayjs.extend(utc); | ||
|
||
interface NextReportExecutionProps { | ||
schedule: string; | ||
currentDate: Date; | ||
} | ||
|
||
const NextReportExecution: React.FC<NextReportExecutionProps> = ({ schedule, currentDate }) => { | ||
const nextReportExecutionDate = (() => { | ||
if (!schedule) { | ||
return ''; | ||
} | ||
|
||
const expression = cronjsParser.parse(schedule, { hasSeconds: true }); | ||
const nextExecutions = cronjsMatcher.getFutureMatches(expression, { | ||
startAt: currentDate.toISOString(), | ||
matchCount: 1, | ||
}); | ||
return nextExecutions.length == 1 ? dayjs.utc(nextExecutions[0].toString()).format('YYYY-MM-DD HH:mm') : ''; | ||
})(); | ||
|
||
return <span>{nextReportExecutionDate}</span>; | ||
}; | ||
|
||
export default NextReportExecution; |
53 changes: 53 additions & 0 deletions
53
packages/esm-reports-app/src/components/overlay.component.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import React from 'react'; | ||
import { Header } from '@carbon/react'; | ||
import { ArrowLeft, Close } from '@carbon/react/icons'; | ||
import { useLayoutType } from '@openmrs/esm-framework'; | ||
import { closeOverlay, useOverlay } from '../hooks/useOverlay'; | ||
import styles from './overlay.scss'; | ||
import { IconButton } from '@carbon/react'; | ||
import { t } from 'i18next'; | ||
import classNames from 'classnames'; | ||
|
||
const Overlay: React.FC = () => { | ||
const { header, component, isOverlayOpen } = useOverlay(); | ||
const layout = useLayoutType(); | ||
|
||
return ( | ||
<> | ||
{isOverlayOpen && ( | ||
<div | ||
className={classNames({ | ||
[styles.desktopOverlay]: layout !== 'tablet', | ||
[styles.tabletOverlay]: layout === 'tablet', | ||
})} | ||
> | ||
{layout === 'tablet' && ( | ||
<Header onClick={() => closeOverlay()} aria-label="Tablet overlay" className={styles.tabletOverlayHeader}> | ||
<IconButton> | ||
<ArrowLeft size={16} /> | ||
</IconButton> | ||
<div className={styles.headerContent}>{header}</div> | ||
</Header> | ||
)} | ||
|
||
{layout !== 'tablet' && ( | ||
<div className={styles.desktopHeader}> | ||
<div className={styles.headerContent}>{header}</div> | ||
<IconButton | ||
className={styles.closePanelButton} | ||
onClick={() => closeOverlay()} | ||
kind="ghost" | ||
label={t('close', 'Close')} | ||
> | ||
<Close size={16} /> | ||
</IconButton> | ||
</div> | ||
)} | ||
{component} | ||
</div> | ||
)} | ||
</> | ||
); | ||
}; | ||
|
||
export default Overlay; |
Oops, something went wrong.