-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adding Reports Dashboard
- Loading branch information
Showing
56 changed files
with
3,383 additions
and
92 deletions.
There are no files selected for viewing
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,10 @@ | ||
![Node.js CI](https://github.com/openmrs/openmrs-esm-template-app/workflows/Node.js%20CI/badge.svg) | ||
|
||
# Reports Module | ||
|
||
The `openmrs-esm-reports-app` is a package which provides Report admin pages: | ||
- An overview of Report execution history included currently queued reports, with possibilities to execute specific report | ||
, preserve, download and delete completed execution | ||
- An overview of an execution schedule with possibilities to view, edit and delete a schedule | ||
|
||
The pages are available in the app's main menu under Reports entry. |
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 |
---|---|---|
@@ -1,3 +1,3 @@ | ||
const rootConfig = require("../../jest.config.js"); | ||
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 |
---|---|---|
@@ -1,55 +1,57 @@ | ||
{ | ||
"name": "@sjthc/esm-reports-app", | ||
"version": "1.0.0", | ||
"description": "Microfrontend for managing reports O3", | ||
"version": "1.0.1", | ||
"license": "MPL-2.0", | ||
"description": "Reports Admin page for OpenMRS", | ||
"browser": "dist/sjthc-esm-reports-app.js", | ||
"main": "src/index.ts", | ||
"source": true, | ||
"license": "MPL-2.0", | ||
"homepage": "", | ||
"scripts": { | ||
"start": "openmrs develop", | ||
"serve": "webpack serve --mode=development", | ||
"debug": "npm run serve", | ||
"build": "webpack --mode production", | ||
"analyze": "webpack --mode=production --env.analyze=true", | ||
"lint": "cross-env eslint src --ext ts,tsx", | ||
"lint": "eslint src --ext js,jsx,ts,tsx", | ||
"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", | ||
"coverage": "yarn test --coverage", | ||
"typescript": "tsc", | ||
"extract-translations": "i18next 'src/**/*.component.tsx' --config ../../tools/i18next-parser.config.js" | ||
}, | ||
"browserslist": [ | ||
"extends browserslist-config-openmrs" | ||
], | ||
"keywords": [ | ||
"openmrs" | ||
"openmrs", | ||
"microfrontends", | ||
"reports" | ||
], | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "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": "" | ||
"url": "https://github.com/openmrs/openmrs-esm-admin-tools/issues" | ||
}, | ||
"dependencies": { | ||
"@carbon/react": "~1.37.0", | ||
"dexie": "^3.0.3", | ||
"fuzzy": "^0.1.3", | ||
"lodash-es": "^4.17.15" | ||
"@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": "5.x", | ||
"@openmrs/esm-framework": "*", | ||
"dayjs": "1.x", | ||
"react": "18.x", | ||
"react-i18next": "11.x", | ||
"react-router-dom": "6.x", | ||
"swr": "2.x" | ||
"rxjs": "6.x" | ||
}, | ||
"devDependencies": { | ||
"webpack": "^5.74.0" | ||
} | ||
"packageManager": "[email protected]" | ||
} |
153 changes: 153 additions & 0 deletions
153
...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,153 @@ | ||
import React, { useCallback, useEffect, useState } from 'react'; | ||
import { first } 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, | ||
RunReportRequest, | ||
} 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 { showToast, useLayoutType } from '@openmrs/esm-framework'; | ||
|
||
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<string>(); | ||
const [initialCron, setInitialCron] = useState<string>(); | ||
const [schedule, setSchedule] = useState<string>(); | ||
|
||
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 runReportRequest: RunReportRequest = { | ||
existingRequestUuid: reportRequestUuid, | ||
reportDefinitionUuid, | ||
renderModeUuid, | ||
reportParameters, | ||
schedule, | ||
}; | ||
|
||
const abortController = new AbortController(); | ||
runReportObservable(runReportRequest, abortController) | ||
.pipe(first()) | ||
.subscribe( | ||
() => { | ||
showToast({ | ||
critical: true, | ||
kind: 'success', | ||
title: t('reportScheduled', 'Report scheduled'), | ||
description: t('reportScheduledSuccessfullyMsg', 'Report scheduled successfully'), | ||
}); | ||
closePanel(); | ||
setIsSubmitting(false); | ||
}, | ||
(error) => { | ||
console.error(error); | ||
showToast({ | ||
critical: true, | ||
kind: 'error', | ||
title: t('reportScheduledErrorMsg', 'Failed to schedule a report'), | ||
description: t('reportScheduledErrorMsg', 'Failed to schedule a report'), | ||
}); | ||
closePanel(); | ||
setIsSubmitting(false); | ||
}, | ||
); | ||
}, | ||
[closePanel, renderModeUuid, reportRequestUuid, reportRequestUuid, reportParameters, schedule], | ||
); | ||
|
||
const handleOnChange = () => { | ||
setIgnoreChanges((prevState) => !prevState); | ||
}; | ||
|
||
const handleCronEditorChange = (cron: string, isValid: boolean) => { | ||
setSchedule(isValid ? cron : null); | ||
}; | ||
|
||
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 && | ||
reportDefinition.parameters.map((parameter) => ( | ||
<ReportParameterInput | ||
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 value={null} /> | ||
{reportDesigns && | ||
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={isTablet ? styles.tablet : styles.desktop}> | ||
<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/styles/scss/spacing'; | ||
@use '@carbon/styles/scss/type'; | ||
@import '~@openmrs/esm-styleguide/src/vars'; | ||
|
||
.tablet { | ||
padding: spacing.$spacing-06 spacing.$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: spacing.$spacing-05 0rem; | ||
background-color: $ui-background; | ||
|
||
& section { | ||
margin: spacing.$spacing-02 spacing.$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; | ||
} |
28 changes: 28 additions & 0 deletions
28
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,28 @@ | ||
import React from 'react'; | ||
import * as cronjsParser from '@datasert/cronjs-parser'; | ||
import * as cronjsMatcher from '@datasert/cronjs-matcher'; | ||
import moment from 'moment'; | ||
|
||
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 ? moment.utc(nextExecutions[0].toString()).format('YYYY-MM-DD HH:mm') : ''; | ||
})(); | ||
|
||
return <span>{nextReportExecutionDate}</span>; | ||
}; | ||
|
||
export default NextReportExecution; |
40 changes: 40 additions & 0 deletions
40
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,40 @@ | ||
import React from 'react'; | ||
import { Button, 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'; | ||
|
||
const Overlay: React.FC = () => { | ||
const { header, component, isOverlayOpen } = useOverlay(); | ||
const layout = useLayoutType(); | ||
const overlayClass = layout !== 'tablet' ? styles.desktopOverlay : styles.tabletOverlay; | ||
return ( | ||
<> | ||
{isOverlayOpen && ( | ||
<div className={overlayClass}> | ||
{layout === 'tablet' && ( | ||
<Header onClick={() => closeOverlay()} aria-label="Tablet overlay" className={styles.tabletOverlayHeader}> | ||
<Button hasIconOnly> | ||
<ArrowLeft size={16} /> | ||
</Button> | ||
<div className={styles.headerContent}>{header}</div> | ||
</Header> | ||
)} | ||
|
||
{layout !== 'tablet' && ( | ||
<div className={styles.desktopHeader}> | ||
<div className={styles.headerContent}>{header}</div> | ||
<Button className={styles.closePanelButton} onClick={() => closeOverlay()} kind="ghost" hasIconOnly> | ||
<Close size={16} /> | ||
</Button> | ||
</div> | ||
)} | ||
{component} | ||
</div> | ||
)} | ||
</> | ||
); | ||
}; | ||
|
||
export default Overlay; |
Oops, something went wrong.