Skip to content

Commit

Permalink
feat(tupaiaWeb): RN-1305: Map overlay exports (#5698)
Browse files Browse the repository at this point in the history
* Server endpoint

* Export functionality

* Working export

* Loading spinner

* Fix unintended change

* Rearrange files

* Fix missing import

* Remove unused imports

* PR fixes

* Pass down active tileset

* Make time period carry over

* Don't re-zoom to entity on load

* Remove tooltip on loading state change

* Font sizes

* Pass locale through to export

* Explicitly pass date range to export

* Disable export button when user is not logged in

* Fix error message

* Show error mesage

* Project logo in export

* Zoom in pdf

* Fix date wrapper

* Hide export button when not logged in

* Fix text width

---------

Co-authored-by: Andrew <vanbeekandrew@gmail.com>
  • Loading branch information
alexd-bes and avaek authored Jul 23, 2024
1 parent ff9a516 commit 439f16b
Show file tree
Hide file tree
Showing 37 changed files with 741 additions and 163 deletions.
2 changes: 2 additions & 0 deletions packages/server-utils/src/downloadPageAsPDF.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export const downloadPageAsPDF = async (
pdfPageUrl: string,
userCookie = '',
cookieDomain: string | undefined,
landscape = false,
) => {
let browser;
let buffer;
Expand All @@ -55,6 +56,7 @@ export const downloadPageAsPDF = async (
buffer = await page.pdf({
format: 'a4',
printBackground: true,
landscape,
});
} catch (e) {
throw new Error(`puppeteer error: ${(e as Error).message}`);
Expand Down
4 changes: 4 additions & 0 deletions packages/tupaia-web-server/src/app/createApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ export async function createApp(db: TupaiaDatabase = new TupaiaDatabase()) {
'dashboards/:projectCode/:entityCode/:dashboardCode/export',
handleWith(routes.ExportDashboardRoute),
)
.post<routes.ExportMapOverlayRequest>(
'mapOverlays/:projectCode/:entityCode/:mapOverlayCode/export',
handleWith(routes.ExportMapOverlayRoute),
)
.post<routes.EmailDashboardRequest>(
'dashboards/:projectCode/:entityCode/:dashboardCode/email',
handleWith(routes.EmailDashboardRoute),
Expand Down
77 changes: 77 additions & 0 deletions packages/tupaia-web-server/src/routes/ExportMapOverlayRoute.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* Tupaia
* Copyright (c) 2017 - 2024 Beyond Essential Systems Pty Ltd
*
*/

import { Request } from 'express';
import { Route } from '@tupaia/server-boilerplate';
import { TupaiaWebExportMapOverlayRequest } from '@tupaia/types';
import { stringifyQuery } from '@tupaia/utils';
import { downloadPageAsPDF } from '@tupaia/server-utils';

export type ExportMapOverlayRequest = Request<
TupaiaWebExportMapOverlayRequest.Params,
TupaiaWebExportMapOverlayRequest.ResBody,
TupaiaWebExportMapOverlayRequest.ReqBody,
TupaiaWebExportMapOverlayRequest.ReqQuery
>;

const downloadMapOverlayAsPdf = (
projectCode: string,
entityCode: string,
mapOverlayCode: string,
baseUrl: TupaiaWebExportMapOverlayRequest.ReqBody['baseUrl'],
cookie: string,
cookieDomain: TupaiaWebExportMapOverlayRequest.ReqBody['cookieDomain'],
zoom: TupaiaWebExportMapOverlayRequest.ReqBody['zoom'],
center: TupaiaWebExportMapOverlayRequest.ReqBody['center'],
tileset: TupaiaWebExportMapOverlayRequest.ReqBody['tileset'],
hiddenValues: TupaiaWebExportMapOverlayRequest.ReqBody['hiddenValues'],
mapOverlayPeriod?: TupaiaWebExportMapOverlayRequest.ReqBody['mapOverlayPeriod'],
locale?: TupaiaWebExportMapOverlayRequest.ReqBody['locale'],
) => {
const endpoint = `${projectCode}/${entityCode}/map-overlay-pdf-export`;
const pdfPageUrl = stringifyQuery(baseUrl, endpoint, {
zoom,
center,
tileset,
hiddenValues,
overlay: mapOverlayCode,
overlayPeriod: mapOverlayPeriod,
locale,
});

return downloadPageAsPDF(pdfPageUrl, cookie, cookieDomain, true);
};

export class ExportMapOverlayRoute extends Route<ExportMapOverlayRequest> {
protected type = 'download' as const;

public async buildResponse() {
const { projectCode, entityCode, mapOverlayCode } = this.req.params;
const { baseUrl, cookieDomain, zoom, center, tileset, hiddenValues, mapOverlayPeriod, locale } =
this.req.body;
const { cookie } = this.req.headers;

if (!cookie) {
throw new Error(`Must have a valid session to export a map overlay`);
}

const buffer = await downloadMapOverlayAsPdf(
projectCode,
entityCode,
mapOverlayCode,
baseUrl,
cookie,
cookieDomain,
zoom,
center,
tileset,
hiddenValues,
mapOverlayPeriod,
locale,
);
return { contents: buffer, type: 'application/pdf' };
}
}
2 changes: 2 additions & 0 deletions packages/tupaia-web-server/src/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,5 @@ export {
UnsubscribeDashboardMailingListRoute,
UnsubscribeDashboardMailingListRequest,
} from './UnsubscribeDashboardMailingListRoute';

export { ExportMapOverlayRequest, ExportMapOverlayRoute } from './ExportMapOverlayRoute';
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const downloadDashboardAsPdf = (
exportWithTable: false,
},
) => {
const endpoint = `${projectCode}/${entityCode}/${dashboardName}/pdf-export`;
const endpoint = `${projectCode}/${entityCode}/${dashboardName}/dashboard-pdf-export`;
const pdfPageUrl = stringifyQuery(baseUrl, endpoint, {
selectedDashboardItems: selectedDashboardItems?.join(','),
settings: JSON.stringify(settings),
Expand Down
1 change: 1 addition & 0 deletions packages/tupaia-web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-hook-form": "^6.15.1",
"react-leaflet": "^3.2.1",
"react-query": "^3.39.3",
"react-router": "6.3.0",
"react-router-dom": "6.3.0",
Expand Down
16 changes: 13 additions & 3 deletions packages/tupaia-web/src/Routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,15 @@
*/
import React from 'react';
import { Navigate, Route, Routes as RouterRoutes, useLocation } from 'react-router-dom';
import { LandingPage, PDFExport, ProjectPage, Unsubscribe } from './views';
import {
DashboardPDFExport,
LandingPage,
MapOverlayPDFExport,
ProjectPage,
Unsubscribe,
} from './views';
import { Dashboard } from './features';
import { MODAL_ROUTES, DEFAULT_URL, ROUTE_STRUCTURE } from './constants';
import { MODAL_ROUTES, DEFAULT_URL, ROUTE_STRUCTURE, MAP_OVERLAY_EXPORT_ROUTE } from './constants';
import { useUser } from './api/queries';
import { MainLayout } from './layout';
import { LoadingScreen } from './components';
Expand Down Expand Up @@ -63,7 +69,11 @@ export const Routes = () => {

return (
<RouterRoutes>
<Route path="/:projectCode/:entityCode/:dashboardName/pdf-export" element={<PDFExport />} />
<Route
path="/:projectCode/:entityCode/:dashboardName/dashboard-pdf-export"
element={<DashboardPDFExport />}
/>
<Route path={MAP_OVERLAY_EXPORT_ROUTE} element={<MapOverlayPDFExport />} />
<Route path="/unsubscribe" element={<Unsubscribe />} />
<Route element={<MainLayout />}>
<Route path="/:landingPageUrlSegment" element={<LandingPage />} />
Expand Down
1 change: 1 addition & 0 deletions packages/tupaia-web/src/api/mutations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ export { useSubscribeDashboard } from './useSubscribeDashboard';
export { useUnsubscribeDashboard } from './useUnsubscribeDashboard';
export { useUnsubscribeDashboardMailingList } from './useUnsubscribeDashboardMailingList';
export { useEmailDashboard } from './useEmailDashboard';
export { useExportMapOverlay } from './useExportMapOverlay';
7 changes: 5 additions & 2 deletions packages/tupaia-web/src/api/mutations/useExportDashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { useMutation } from 'react-query';
import { TupaiaWebExportDashboardRequest } from '@tupaia/types';
import { API_URL, post } from '../api';
import { DashboardName, EntityCode, ProjectCode } from '../../types';
import { downloadPDF } from '../../utils';

type ExportDashboardBody = {
projectCode?: ProjectCode;
Expand All @@ -16,7 +17,7 @@ type ExportDashboardBody = {
};

// Requests a dashboard PDF export from the server, and returns the response
export const useExportDashboard = ({ onSuccess }: { onSuccess?: (data: Blob) => void }) => {
export const useExportDashboard = (fileName: string) => {
return useMutation<any, Error, ExportDashboardBody, unknown>(
({
projectCode,
Expand All @@ -41,7 +42,9 @@ export const useExportDashboard = ({ onSuccess }: { onSuccess?: (data: Blob) =>
});
},
{
onSuccess,
onSuccess: data => {
downloadPDF(data, fileName);
},
},
);
};
60 changes: 60 additions & 0 deletions packages/tupaia-web/src/api/mutations/useExportMapOverlay.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Tupaia
* Copyright (c) 2017 - 2024 Beyond Essential Systems Pty Ltd
*/
import { useMutation } from 'react-query';
import { LatLng } from 'leaflet';
import { MapOverlay } from '@tupaia/types';
import { LegendProps } from '@tupaia/ui-map-components';
import { EntityCode, ProjectCode } from '../../types';
import { API_URL, post } from '../api';
import { downloadPDF } from '../../utils';

type ExportDashboardBody = {
projectCode?: ProjectCode;
entityCode?: EntityCode;
mapOverlayCode?: MapOverlay['code'];
zoom: number;
center: LatLng;
hiddenValues: LegendProps['hiddenValues'];
tileset: string;
mapOverlayPeriod?: string;
};

// Requests a map overlay PDF export from the server, and returns the response
export const useExportMapOverlay = (fileName: string) => {
return useMutation<any, Error, ExportDashboardBody, unknown>(
({
projectCode,
entityCode,
mapOverlayCode,
zoom,
center,
hiddenValues,
tileset,
mapOverlayPeriod,
}: ExportDashboardBody) => {
// Auth cookies are saved against this domain. Pass this to server, so that when it pretends to be us, it can do the same.
const cookieDomain = new URL(API_URL).hostname;

return post(`mapOverlays/${projectCode}/${entityCode}/${mapOverlayCode}/export`, {
responseType: 'blob',
data: {
cookieDomain,
baseUrl: window.location.origin,
zoom,
center: JSON.stringify(center),
hiddenValues: JSON.stringify(hiddenValues),
tileset,
mapOverlayPeriod,
locale: window.navigator?.language || 'en-AU',
},
});
},
{
onSuccess: data => {
downloadPDF(data, fileName);
},
},
);
};
2 changes: 2 additions & 0 deletions packages/tupaia-web/src/constants/url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,5 @@ export const DEFAULT_PERIOD_PARAM_STRING = 'DEFAULT_PERIOD';
export const DEFAULT_MAP_OVERLAY_ID = '126'; // 'Operational Facilities'

export const ROUTE_STRUCTURE = '/:projectCode/:entityCode/:dashboardName';

export const MAP_OVERLAY_EXPORT_ROUTE = '/:projectCode/:entityCode/map-overlay-pdf-export';
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import React from 'react';
import styled from 'styled-components';
import { useParams } from 'react-router';
import downloadJs from 'downloadjs';
import { Button, LoadingContainer } from '@tupaia/ui-components';
import { useEntity, useProject } from '../../../api/queries';
import { useExportDashboard } from '../../../api/mutations';
Expand Down Expand Up @@ -112,21 +111,10 @@ export const ExportConfig = ({ onClose, selectedDashboardItems }: ExportDashboar
const { activeDashboard } = useDashboard();
const { exportWithLabels, exportWithTable } = useExportSettings();

const handleExportSuccess = (data: Blob) => {
downloadJs(data, `${exportFileName}.pdf`);
};

const {
mutate: requestPdfExport,
error,
isLoading,
reset,
} = useExportDashboard({
onSuccess: handleExportSuccess,
});

const exportFileName = `${project?.name}-${entity?.name}-${dashboardName}-dashboard-export`;

const { mutate: requestPdfExport, error, isLoading, reset } = useExportDashboard(exportFileName);

const handleExport = () =>
requestPdfExport({
projectCode,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import React, { useState } from 'react';
import styled from 'styled-components';
import { Typography } from '@material-ui/core';
import { Pagination } from '@material-ui/lab';
import { PDFExport } from '../../../views';
import { DashboardPDFExport } from '../../../views';
import { MOBILE_BREAKPOINT } from '../../../constants';

const PreviewPanelContainer = styled.div`
Expand Down Expand Up @@ -74,7 +74,7 @@ export const Preview = ({ selectedDashboardItems }: { selectedDashboardItems: st
/>
</PreviewHeaderContainer>
<PreviewContainer>
<PDFExport selectedDashboardItems={[visualisationToPreview]} isPreview={true} />
<DashboardPDFExport selectedDashboardItems={[visualisationToPreview]} isPreview={true} />
</PreviewContainer>
</PreviewPanelContainer>
);
Expand Down
Loading

0 comments on commit 439f16b

Please sign in to comment.