Skip to content

Commit

Permalink
Exposure Analysis Download CSV access from anywhere
Browse files Browse the repository at this point in the history
  • Loading branch information
HarryMytilinaios committed Jul 14, 2023
1 parent bb2a000 commit 83ea49e
Show file tree
Hide file tree
Showing 7 changed files with 252 additions and 101 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,21 @@ import {
} from '@material-ui/core';
import { snakeCase } from 'lodash';
import { useSelector } from 'react-redux';
import { downloadToFile } from 'components/MapView/utils';
import {
downloadToFile,
getExposureAnalysisColumnsToRender,
getExposureAnalysisTableDataRowsToRender,
} from 'components/MapView/utils';
import { useSafeTranslation } from 'i18n';
import {
exposureLayerIdSelector,
getCurrentDefinition,
TableRow,
TableRow as AnalysisTableRow,
} from 'context/analysisResultStateSlice';
import ReportDialog from 'components/Common/ReportDialog';
import { Column, quoteAndEscapeCell } from 'utils/analysis-utils';
import { Column } from 'utils/analysis-utils';
import { ReportsDefinitions } from 'config/utils';
import { getExposureAnalysisCsvData } from 'utils/csv-utils';

function ExposureAnalysisActions({
analysisButton,
Expand All @@ -35,24 +39,17 @@ function ExposureAnalysisActions({

const [openReport, setOpenReport] = useState(false);

const getCellValue = useCallback((value: string | number, column: Column) => {
if (column.format && typeof value === 'number') {
return quoteAndEscapeCell(column.format(value));
}
return quoteAndEscapeCell(value);
}, []);

const columnsToRenderCsv = useMemo(() => {
return columns.reduce(
(acc: { [key: string]: string | number }, column: Column) => {
return {
...acc,
[column.id]: column.label,
};
},
{},
);
}, [columns]);
const exposureAnalysisColumnsToRender = getExposureAnalysisColumnsToRender(
columns,
);
const exposureAnalysisTableRowsToRender = getExposureAnalysisTableDataRowsToRender(
columns,
tableData,
);
const exposureAnalysisCsvData = getExposureAnalysisCsvData(
exposureAnalysisColumnsToRender,
exposureAnalysisTableRowsToRender,
);

const reportConfig = useMemo(() => {
// We use find here because exposure reports and layers have 1 - 1 sync.
Expand All @@ -67,35 +64,12 @@ function ExposureAnalysisActions({
return ReportsDefinitions[foundReportKeyBasedOnLayerId as string];
}, [exposureLayerId]);

const tableDataRowsToRenderCsv = useMemo(() => {
return tableData.map((tableRowData: TableRow) => {
return columns.reduce(
(acc: { [key: string]: string | number }, column: Column) => {
const value = tableRowData[column.id];
return {
...acc,
[column.id]: getCellValue(value, column),
};
},
{},
);
});
}, [columns, getCellValue, tableData]);

const analysisCsvData = useMemo(() => {
return [columnsToRenderCsv, ...tableDataRowsToRenderCsv]
.map(analysisCsvItem => {
return Object.values(analysisCsvItem);
})
.join('\n');
}, [columnsToRenderCsv, tableDataRowsToRenderCsv]);

const handleOnDownloadCsv = useCallback(
(event: MouseEvent<HTMLButtonElement>) => {
event.preventDefault();
downloadToFile(
{
content: analysisCsvData,
content: exposureAnalysisCsvData,
isUrl: false,
},
`${snakeCase(analysisDefinition?.id)}_${snakeCase(
Expand All @@ -104,7 +78,7 @@ function ExposureAnalysisActions({
'text/csv',
);
},
[analysisCsvData, analysisDefinition],
[analysisDefinition, exposureAnalysisCsvData],
);

const handleToggleReport = (toggle: boolean) => {
Expand All @@ -118,7 +92,7 @@ function ExposureAnalysisActions({
<Button className={analysisButton} onClick={clearAnalysis}>
<Typography variant="body2">{t('Clear Analysis')}</Typography>
</Button>
{analysisCsvData && (
{exposureAnalysisCsvData && (
<Button className={bottomButton} onClick={handleOnDownloadCsv}>
<Typography variant="body2">{t('Download as CSV')}</Typography>
</Button>
Expand Down
38 changes: 25 additions & 13 deletions frontend/src/components/MapView/LeftPanel/AnalysisPanel/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ import {
analysisResultSortOrderSelector,
setAnalysisResultSortByKey,
setAnalysisResultSortOrder,
exposureAnalysisResultSortByKeySelector,
exposureAnalysisResultSortOrderSelector,
setExposureAnalysisResultSortByKey,
setExposureAnalysisResultSortOrder,
TableRow,
} from 'context/analysisResultStateSlice';
import {
AdminLevelType,
Expand Down Expand Up @@ -102,6 +107,7 @@ import LoadingBlinkingDots from 'components/Common/LoadingBlinkingDots';
import AnalysisTable from './AnalysisTable';
import ExposureAnalysisTable from './AnalysisTable/ExposureAnalysisTable';
import ExposureAnalysisActions from './ExposureAnalysisActions';
import { getExposureAnalysisTableData } from '../../utils';

const tabIndex = 2;

Expand Down Expand Up @@ -132,6 +138,12 @@ const AnalysisPanel = memo(
const analysisResultSortOrder = useSelector(
analysisResultSortOrderSelector,
);
const exposureAnalysisResultSortByKey = useSelector(
exposureAnalysisResultSortByKeySelector,
);
const exposureAnalysisResultSortOrder = useSelector(
exposureAnalysisResultSortOrderSelector,
);
const isAnalysisLoading = useSelector(isAnalysisLoadingSelector);
const isExposureAnalysisLoading = useSelector(
isExposureAnalysisLoadingSelector,
Expand All @@ -142,12 +154,12 @@ const AnalysisPanel = memo(
const [
exposureAnalysisSortColumn,
setExposureAnalysisSortColumn,
] = useState<Column['id']>('name');
] = useState<Column['id']>(exposureAnalysisResultSortByKey);
// exposure analysis sort order
const [
exposureAnalysisIsAscending,
setExposureAnalysisIsAscending,
] = useState(true);
] = useState(exposureAnalysisResultSortOrder === 'asc');
// defaults the sort column of every other analysis table to 'name'
const [analysisSortColumn, setAnalysisSortColumn] = useState<Column['id']>(
analysisResultSortByKey,
Expand Down Expand Up @@ -654,22 +666,22 @@ const AnalysisPanel = memo(
);
setExposureAnalysisSortColumn(newExposureAnalysisSortColumn);
setExposureAnalysisIsAscending(newIsAsc);
// set the sort by key of exposure analysis data in redux
dispatch(
setExposureAnalysisResultSortByKey(newExposureAnalysisSortColumn),
);
// set the sort order of exposure analysis result data in redux
dispatch(setExposureAnalysisResultSortOrder(newIsAsc ? 'asc' : 'desc'));
},
[exposureAnalysisIsAscending, exposureAnalysisSortColumn],
[dispatch, exposureAnalysisIsAscending, exposureAnalysisSortColumn],
);

// The exposure analysis table data
const exposureAnalysisTableData = useMemo(() => {
return orderBy(
analysisResult?.tableData,
exposureAnalysisSortColumn,
exposureAnalysisIsAscending ? 'asc' : 'desc',
);
}, [
analysisResult,
exposureAnalysisIsAscending,
const exposureAnalysisTableData = getExposureAnalysisTableData(
analysisResult?.tableData as TableRow[],
exposureAnalysisSortColumn,
]);
exposureAnalysisIsAscending ? 'asc' : 'desc',
);

const renderedExposureAnalysisLoading = useMemo(() => {
if (!isExposureAnalysisLoading) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@ import React, { memo, useMemo, useCallback, useState } from 'react';
import { IconButton, Menu, MenuItem, Tooltip } from '@material-ui/core';
import GetAppIcon from '@material-ui/icons/GetApp';
import { useSafeTranslation } from 'i18n';
import { downloadToFile } from 'components/MapView/utils';
import {
downloadToFile,
getExposureAnalysisColumnsToRender,
getExposureAnalysisTableData,
getExposureAnalysisTableDataRowsToRender,
} from 'components/MapView/utils';
import {
BaselineLayerResult,
downloadCSVFromTableData,
Expand All @@ -11,6 +16,15 @@ import {
PolygonAnalysisResult,
useAnalysisTableColumns,
} from 'utils/analysis-utils';
import { snakeCase } from 'lodash';
import { useSelector } from 'react-redux';
import {
exposureAnalysisResultSortByKeySelector,
exposureAnalysisResultSortOrderSelector,
getCurrentDefinition,
TableRow,
} from 'context/analysisResultStateSlice';
import { getExposureAnalysisCsvData } from 'utils/csv-utils';

const AnalysisLayerSwitchItemDownloadOptions = memo(
({
Expand All @@ -26,20 +40,33 @@ const AnalysisLayerSwitchItemDownloadOptions = memo(

const { translatedColumns } = useAnalysisTableColumns(analysisData);

const exposureAnalysisResultSortByKey = useSelector(
exposureAnalysisResultSortByKeySelector,
);
const exposureAnalysisResultSortOrder = useSelector(
exposureAnalysisResultSortOrderSelector,
);
const analysisDefinition = useSelector(getCurrentDefinition);

const exposureAnalysisTableData = getExposureAnalysisTableData(
analysisData?.tableData as TableRow[],
exposureAnalysisResultSortByKey,
exposureAnalysisResultSortOrder,
);
const exposureAnalysisColumnsToRender = getExposureAnalysisColumnsToRender(
translatedColumns,
);
const exposureAnalysisTableRowsToRender = getExposureAnalysisTableDataRowsToRender(
translatedColumns,
exposureAnalysisTableData,
);

const { t } = useSafeTranslation();

const featureCollection = useMemo(() => {
return analysisData?.featureCollection;
}, [analysisData]);

const doesLayerAcceptCSVDownload = useMemo(() => {
return (
analysisData &&
(analysisData instanceof BaselineLayerResult ||
analysisData instanceof PolygonAnalysisResult)
);
}, [analysisData]);

const handleDownloadMenuClose = useCallback(() => {
setDownloadMenuAnchorEl(null);
}, []);
Expand Down Expand Up @@ -90,11 +117,23 @@ const AnalysisLayerSwitchItemDownloadOptions = memo(
}, [analysisData, analysisDate, t]);

const handleDownloadCsv = useCallback((): void => {
if (
// Explicit condition for type narrowing
!analysisData ||
analysisData instanceof ExposedPopulationResult
) {
if (!analysisData) {
return;
}
if (analysisData instanceof ExposedPopulationResult) {
downloadToFile(
{
content: getExposureAnalysisCsvData(
exposureAnalysisColumnsToRender,
exposureAnalysisTableRowsToRender,
),
isUrl: false,
},
`${snakeCase(analysisDefinition?.id)}_${snakeCase(
analysisDefinition?.legendText,
)}`,
'text/csv',
);
return;
}
downloadCSVFromTableData(
Expand All @@ -107,8 +146,11 @@ const AnalysisLayerSwitchItemDownloadOptions = memo(
}, [
analysisData,
analysisDate,
analysisDefinition,
analysisResultSortByKey,
analysisResultSortOrder,
exposureAnalysisColumnsToRender,
exposureAnalysisTableRowsToRender,
translatedColumns,
]);

Expand All @@ -127,15 +169,12 @@ const AnalysisLayerSwitchItemDownloadOptions = memo(
}, [analysisData, featureCollection, fileName]);

const renderedDownloadAsCSVMenuItem = useMemo(() => {
if (!doesLayerAcceptCSVDownload) {
return null;
}
return (
<MenuItem key="download-as-csv" onClick={handleDownloadCsv}>
{t('Download as CSV')}
</MenuItem>
);
}, [doesLayerAcceptCSVDownload, handleDownloadCsv, t]);
}, [handleDownloadCsv, t]);

return (
<>
Expand Down
Loading

0 comments on commit 83ea49e

Please sign in to comment.