Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Exposure Analysis Download CSV access from anywhere #920

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -148,6 +148,9 @@ exports[`renders as expected 1`] = `
keepmounted="true"
open="false"
>
<mock-menuitem>
Download as CSV
</mock-menuitem>
<mock-menuitem>
Download as GeoJSON
</mock-menuitem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ exports[`renders as expected 1`] = `
keepmounted="true"
open="false"
>
<mock-menuitem>
Download as CSV
</mock-menuitem>
<mock-menuitem>
Download as GeoJSON
</mock-menuitem>
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
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ exports[`renders as expected 1`] = `
keepmounted="true"
open="false"
>
<mock-menuitem>
Download as CSV
</mock-menuitem>
<mock-menuitem>
Download as GeoJSON
</mock-menuitem>
Expand Down
Loading
Loading