-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #4051 from beyondessential/dev
merge latest dev into test branch
- Loading branch information
Showing
102 changed files
with
3,224 additions
and
1,872 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
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
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,96 @@ | ||
/* | ||
* Tupaia | ||
* Copyright (c) 2017 - 2022 Beyond Essential Systems Pty Ltd | ||
* | ||
*/ | ||
import { Request, Response, NextFunction } from 'express'; | ||
import { Route } from '@tupaia/server-boilerplate'; | ||
import puppeteer from 'puppeteer'; | ||
import Cookies from 'cookies'; | ||
|
||
type SessionCookies = { | ||
sessionCookieName: string; | ||
sessionCookieValue: string; | ||
}; | ||
|
||
type Body = { | ||
pdfPageUrl: string; | ||
}; | ||
|
||
export type PDFExportRequest = Request< | ||
Record<string, never>, | ||
{ contents: Buffer }, | ||
Body, | ||
Record<string, any> | ||
>; | ||
|
||
export class PDFExportRoute extends Route<PDFExportRequest> { | ||
public constructor(req: PDFExportRequest, res: Response, next: NextFunction) { | ||
super(req, res, next); | ||
this.type = 'download'; | ||
} | ||
|
||
private extractSessionCookie = (): SessionCookies => { | ||
const cookies = new Cookies(this.req, this.res); | ||
const sessionCookieName = 'sessionCookie'; | ||
const sessionCookieValue = cookies.get(sessionCookieName) || ''; | ||
return { sessionCookieName, sessionCookieValue }; | ||
}; | ||
|
||
private verifyBody = (body: any): Body => { | ||
const { pdfPageUrl } = body; | ||
if (!pdfPageUrl || typeof pdfPageUrl !== 'string') { | ||
throw new Error(`'pdfPageUrl' should be provided in request body, got: ${pdfPageUrl}`); | ||
} | ||
const location = new URL(pdfPageUrl); | ||
if (!location.hostname.endsWith('.tupaia.org') && !location.hostname.endsWith('localhost')) { | ||
throw new Error(`'pdfPageUrl' is not valid, got: ${pdfPageUrl}`); | ||
} | ||
return { pdfPageUrl }; | ||
}; | ||
|
||
private exportPDF = async (): Promise<Buffer> => { | ||
const { sessionCookieName, sessionCookieValue } = this.extractSessionCookie(); | ||
const { pdfPageUrl } = this.verifyBody(this.req.body); | ||
const { host: apiDomain } = this.req.headers; | ||
const location = new URL(pdfPageUrl); | ||
|
||
let browser; | ||
let result; | ||
|
||
try { | ||
browser = await puppeteer.launch(); | ||
const page = await browser.newPage(); | ||
await page.setCookie({ | ||
name: sessionCookieName, | ||
domain: apiDomain, | ||
url: location.origin, | ||
httpOnly: true, | ||
value: sessionCookieValue, | ||
}); | ||
await page.goto(pdfPageUrl, { timeout: 60000, waitUntil: 'networkidle0' }); | ||
result = await page.pdf({ | ||
format: 'a4', | ||
printBackground: true, | ||
}); | ||
} catch (e) { | ||
throw new Error(`puppeteer error: ${(e as Error).message}`); | ||
} finally { | ||
if (browser) { | ||
await browser.close(); | ||
} | ||
} | ||
|
||
return result; | ||
}; | ||
|
||
public async buildResponse() { | ||
const buffer = await this.exportPDF(); | ||
this.res.set({ | ||
'Content-Type': 'application/pdf', | ||
'Content-Length': buffer.length, | ||
'Content-Disposition': 'attachment', | ||
}); | ||
return { contents: buffer }; | ||
} | ||
} |
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
136 changes: 136 additions & 0 deletions
136
packages/lesmis/src/components/DashboardExportModal/DashboardExportModal.js
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,136 @@ | ||
/* | ||
* Tupaia | ||
* Copyright (c) 2017 - 2022 Beyond Essential Systems Pty Ltd | ||
*/ | ||
import React, { useState } from 'react'; | ||
import { useIsFetching } from 'react-query'; | ||
import PropTypes from 'prop-types'; | ||
import styled from 'styled-components'; | ||
import DownloadIcon from '@material-ui/icons/GetApp'; | ||
import { | ||
Dialog, | ||
DialogHeader, | ||
DialogContent as BaseDialogContent, | ||
FlexSpaceBetween as BaseFlexSpaceBetween, | ||
LoadingContainer, | ||
} from '@tupaia/ui-components'; | ||
import MuiIconButton from '@material-ui/core/Button'; | ||
import { OptionsBar } from './components'; | ||
import { I18n, useDashboardItemsExportToPDF, useUrlParams, useUrlSearchParam } from '../../utils'; | ||
import { DEFAULT_DATA_YEAR } from '../../constants'; | ||
import { DASHBOARD_EXPORT_PREVIEW, ExportView } from '../../views/ExportView'; | ||
|
||
const FlexSpaceBetween = styled(BaseFlexSpaceBetween)` | ||
width: 95%; | ||
`; | ||
|
||
const DialogContent = styled(BaseDialogContent)` | ||
background-color: white; | ||
`; | ||
|
||
const MuiButton = styled(MuiIconButton)` | ||
margin: 0px 20px; | ||
background-color: transparent; | ||
color: #666666; | ||
`; | ||
|
||
export const DashboardExportModal = ({ title, totalPage, isOpen, setIsOpen }) => { | ||
const [exportWithLabels, setExportWithLabels] = useState(null); | ||
const [exportWithTable, setExportWithTable] = useState(null); | ||
const [selectedYear] = useUrlSearchParam('year', DEFAULT_DATA_YEAR); | ||
const { locale, entityCode } = useUrlParams(); | ||
|
||
const toggleExportWithLabels = () => { | ||
setExportWithLabels(exportWithLabels ? null : true); | ||
}; | ||
const toggleExportWithTable = () => { | ||
setExportWithTable(exportWithTable ? null : true); | ||
}; | ||
|
||
const fileName = `${title}-dashboards-export`; | ||
const { isExporting, exportToPDF, errorMessage, onReset } = useDashboardItemsExportToPDF({ | ||
exportWithLabels, | ||
exportWithTable, | ||
year: selectedYear, | ||
locale, | ||
entityCode, | ||
}); | ||
|
||
const isFetching = useIsFetching() > 0; | ||
const isError = !!errorMessage; | ||
const isDisabled = isError || isExporting || isFetching; | ||
const [page, setPage] = useState(1); | ||
|
||
const handleClickExport = async () => { | ||
await exportToPDF(fileName); | ||
}; | ||
const onClose = () => { | ||
setIsOpen(false); | ||
}; | ||
|
||
return ( | ||
<Dialog onClose={onClose} open={isOpen} maxWidth="lg"> | ||
<DialogHeader onClose={onClose} title={title}> | ||
<FlexSpaceBetween> | ||
<MuiButton | ||
startIcon={<DownloadIcon />} | ||
variant="outlined" | ||
onClick={handleClickExport} | ||
disableElevation | ||
disabled={isDisabled} | ||
> | ||
<I18n t="dashboards.download" /> | ||
</MuiButton> | ||
<OptionsBar | ||
totalPage={totalPage} | ||
page={page} | ||
setPage={setPage} | ||
isDisabled={isDisabled} | ||
exportOptions={{ | ||
exportWithLabels, | ||
exportWithTable, | ||
toggleExportWithLabels, | ||
toggleExportWithTable, | ||
}} | ||
/> | ||
</FlexSpaceBetween> | ||
</DialogHeader> | ||
<DialogContent> | ||
<LoadingContainer | ||
heading={I18n({ | ||
t: isFetching ? 'dashboards.fetchingAllReportsData' : 'dashboards.exportingChartsToPDF', | ||
})} | ||
text={I18n({ t: 'dashboards.pleaseDoNotRefreshTheBrowserOrCloseThisPage' })} | ||
isLoading={isDisabled} | ||
errorMessage={errorMessage} | ||
onReset={onReset} | ||
> | ||
<ExportView | ||
viewType={DASHBOARD_EXPORT_PREVIEW} | ||
viewProps={{ | ||
currentPage: page, | ||
isExporting, | ||
isError, | ||
exportOptions: { | ||
exportWithLabels, | ||
exportWithTable, | ||
}, | ||
}} | ||
/> | ||
</LoadingContainer> | ||
</DialogContent> | ||
</Dialog> | ||
); | ||
}; | ||
|
||
DashboardExportModal.propTypes = { | ||
title: PropTypes.string, | ||
totalPage: PropTypes.number, | ||
isOpen: PropTypes.bool.isRequired, | ||
setIsOpen: PropTypes.func.isRequired, | ||
}; | ||
|
||
DashboardExportModal.defaultProps = { | ||
title: null, | ||
totalPage: 1, | ||
}; |
Oops, something went wrong.