Skip to content
This repository has been archived by the owner on Nov 13, 2024. It is now read-only.

Sightings individuals download #557

Merged
merged 6 commits into from
Sep 15, 2023
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
75 changes: 63 additions & 12 deletions src/components/dataDisplays/DataDisplay.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,16 @@ import Print from '@material-ui/icons/Print';
import FilterList from '@material-ui/icons/FilterList';
import CloudDownload from '@material-ui/icons/CloudDownload';

import axios from 'axios';
import BaoDetective from '../svg/BaoDetective';
import FilterBar from '../FilterBar';
import Text from '../Text';
import CollapsibleRow from './CollapsibleRow';
import sendCsv from './sendCsv';
import sendCsv, { downloadFileFromBackend } from './sendCsv';

import useGetMe from '../../models/users/useGetMe';
import buildSightingsQuery from './buildSightingsQuery';
import buildIndividualsQuery from './buildIndividualsQuery';

function getCellAlignment(cellIndex, columnDefinition) {
if (columnDefinition.align) return columnDefinition.align;
Expand Down Expand Up @@ -65,12 +70,18 @@ export default function DataDisplay({
cellStyles = {},
stickyHeader = true,
tableContainerStyles = {},
formFilters,
...rest
}) {
const intl = useIntl();
const theme = useTheme();
const themeColor = theme.palette.primary.main;

const { data: currentUserData } = useGetMe();

const isAdmin = get(currentUserData, 'is_admin', false);
const isExporter = get(currentUserData, 'is_exporter', false);

const initialColumnNames = columns
.filter(c => get(c, 'options.display', true))
.map(c => c.name);
Expand All @@ -81,9 +92,8 @@ export default function DataDisplay({
);
const [filter, setFilter] = useState('');
const [internalSortColumn, setInternalSortColumn] = useState(null);
const [internalSortDirection, setInternalSortDirection] = useState(
null,
);
const [internalSortDirection, setInternalSortDirection] =
useState(null);
const [anchorEl, setAnchorEl] = useState(null);
const filterPopperOpen = Boolean(anchorEl);

Expand Down Expand Up @@ -232,14 +242,55 @@ export default function DataDisplay({
</Text>
</Grid>
<Grid item>
{variant === 'primary' && !hideDownloadCsv && (
<IconButton
onClick={() => sendCsv(visibleColumns, visibleData)}
size="small"
>
<CloudDownload style={{ marginRight: 4 }} />
</IconButton>
)}
{variant === 'primary' &&
!hideDownloadCsv &&
(isAdmin || isExporter) && (
<IconButton
onClick={async () => {
const formTitle = title.split(' ')[2];
const url = `${__houston_url__}/api/v1/${formTitle}/export`;
if (
formTitle === 'sightings' ||
formTitle === 'individuals'
) {
let compositeQuery = {};

if (formTitle === 'sightings') {
compositeQuery =
buildSightingsQuery(formFilters);
} else {
compositeQuery =
buildIndividualsQuery(formFilters);
}
try {
const response = await axios.request({
url,
method: 'post',
data: compositeQuery,
params: searchParams,
responseType: 'blob',
});
const status = response?.status;
if (status === 200) {
downloadFileFromBackend(
response.data,
formTitle,
);
}
} catch (e) {
console.log(
'Error in downloading file : ',
e,
);
sendCsv(visibleColumns, visibleData);
}
} else sendCsv(visibleColumns, visibleData);
}}
size="small"
>
<CloudDownload style={{ marginRight: 4 }} />
</IconButton>
)}
{onPrint && (
<IconButton onClick={onPrint} size="small">
<Print style={{ marginRight: 4 }} />
Expand Down
2 changes: 2 additions & 0 deletions src/components/dataDisplays/ElasticsearchSightingsDisplay.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export default function ElasticsearchSightingsDisplay({
sightings,
loading,
dataCount,
formFilters,
...rest
}) {
const title = `${dataCount || sightings.length} matching sightings`;
Expand Down Expand Up @@ -100,6 +101,7 @@ export default function ElasticsearchSightingsDisplay({
title={title}
loading={loading}
showNoResultsBao
formFilters={formFilters}
// renderExpandedRow={expandedSighting => (
// <div style={{ display: 'flex' }}>
// <img
Expand Down
2 changes: 2 additions & 0 deletions src/components/dataDisplays/IndividualsDisplay.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export default function IndividualsDisplay({
dataCount,
addColumns = [],
removeColumns = [],
formFilters,
...rest
}) {
const title = dataCount
Expand Down Expand Up @@ -91,6 +92,7 @@ export default function IndividualsDisplay({
data={individuals}
title={title}
loading={loading}
formFilters={formFilters}
// onPrint={() => {
// window.open('/individuals/picturebook', '_blank');
// }}
Expand Down
40 changes: 40 additions & 0 deletions src/components/dataDisplays/buildIndividualsQuery.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { partition } from 'lodash';
import { nestQueries } from '../../utils/elasticSearchUtils';

export default function buildIndividualsQuery(formFilters) {
const [filters, mustNots] = partition(
formFilters,
q => q.clause === 'filter',
);
const [nestedFilters, normalFilters] = partition(
filters,
f => f.nested,
);
const [nestedMustNots, normalMustNots] = partition(
mustNots,
f => f.nested,
);

const normalFilterQueries = normalFilters.map(f => f.query);
const normalMustNotQueries = normalMustNots.map(f => f.query);
const filterQueriesToNest = nestedFilters.map(f => f.query);
const mustNotQueriesToNest = nestedMustNots.map(f => f.query);

const nestedFilterQueries = nestQueries(
'encounters',
filterQueriesToNest,
);
const nestedMustNotQueries = nestQueries(
'encounters',
mustNotQueriesToNest,
);

const compositeQuery = {
bool: {
filter: [...normalFilterQueries, ...nestedFilterQueries],
must_not: [...normalMustNotQueries, ...nestedMustNotQueries],
},
};

return compositeQuery;
}
15 changes: 15 additions & 0 deletions src/components/dataDisplays/buildSightingsQuery.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { partition } from 'lodash';

export default function buildSightingsQuery(formFilters) {
const [filters, mustNots] = partition(
formFilters,
q => q.clause === 'filter',
);

const filterQueries = filters.map(f => f.query);
const mustNotQueries = mustNots.map(f => f.query);
const compositeQuery = {
bool: { filter: filterQueries, must_not: mustNotQueries },
};
return compositeQuery;
}
13 changes: 12 additions & 1 deletion src/components/dataDisplays/sendCsv.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
import Papa from 'papaparse';
import { get } from 'lodash-es';

export function downloadFileFromBackend(excelData, filename) {
const blob = new Blob([excelData], { type: 'application/vnd.ms-excel' });
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', `${filename}.xlsx`);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}

function startDownload(csv, filename) {
/* copied from https://github.com/gregnb/mui-datatables/blob/ed3b8e38889d061a8cc858637b1d1dfe0fa55556/src/utils.js#L106 */
const blob = new Blob([csv], { type: 'text/csv' });
Expand Down Expand Up @@ -46,4 +57,4 @@ export default function sendCsv(columns, data) {
columns: columns.map(c => c.name),
});
startDownload(fileData, 'table_export.csv');
}
}
6 changes: 1 addition & 5 deletions src/constants/queryKeys.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,6 @@ export function getSightingFilterQueryKey(
return ['sightingFilterSearch', filters, page, rowsPerPage];
}

export function getAuditLogQueryKey(
filters,
page,
rowsPerPage,
) {
export function getAuditLogQueryKey(filters, page, rowsPerPage) {
return ['auditLogFilterSearch', filters, page, rowsPerPage];
}
4 changes: 3 additions & 1 deletion src/hooks/useFetch.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,12 @@ export default function useFetch({
onSuccess = Function.prototype,
prependHoustonApiUrl = true,
queryOptions = {},
responseType = 'json',
}) {
const [displayedError, setDisplayedError] = useState(null);
const [displayedLoading, setDisplayedLoading] = useState(
!queryOptions.disabled, // should this use enabled instead of disabled? I couldn't find anything in the react-query documentation about disabled.
// agreed, I think it should be enabled
);
const [statusCode, setStatusCode] = useState(null);

Expand All @@ -45,9 +47,9 @@ export default function useFetch({
method,
data,
params,
responseType,
});
const status = response?.status;

setStatusCode(status);
if (status === 200) onSuccess(response);
return response;
Expand Down
1 change: 0 additions & 1 deletion src/models/sighting/useFilterSightings.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ export default function useFilterSightings({ queries, params = {} }) {
const compositeQuery = {
bool: { filter: filterQueries, must_not: mustNotQueries },
};

return useFetch({
method: 'post',
queryKey: getSightingFilterQueryKey(queries, params),
Expand Down
44 changes: 23 additions & 21 deletions src/pages/changeLog/ChangeLog.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useState, useMemo } from 'react';
import React, { useState } from 'react';
import Grid from '@material-ui/core/Grid';

import useDocumentTitle from '../../hooks/useDocumentTitle';
Expand All @@ -24,7 +24,8 @@ export default function ChangeLog() {
const theme = useTheme();
const intl = useIntl();

const [inputValue, setInputValue] = useState('');
const [inputValue, setInputValue] = useState('');


const rowsPerPage = 100;
const [searchParams, setSearchParams] = useState({
Expand All @@ -42,7 +43,7 @@ export default function ChangeLog() {
}
);

const buildFilters = (inputValue) => {
const buildFilters = () => {
if(inputValue.trim() !== '') {
setFormFilters(
{
Expand Down Expand Up @@ -83,25 +84,25 @@ export default function ChangeLog() {
})?.sort((a, b) => new Date(b.time) - new Date(a.time));
}

const searchedTableRows = buildAndSortTableRows(searchedData?.results);
const tableColumns = [
{
name: 'time',
label: intl.formatMessage({ id: 'TIME_CHANGE_OCCURRED' }),
options: {
customBodyRender: labelId => (
<FormattedMessage id={labelId} defaultMessage={labelId}/>
),
},
},
{
name: 'message',
label: intl.formatMessage({ id: 'MESSAGE' }),
// options: { cellRenderer: cellRendererTypes.capitalizedString },
},
];


const tableColumns = [
{
name: 'time',
label: intl.formatMessage({ id: 'TIME_CHANGE_OCCURRED' }),
options: {
customBodyRender: labelId => (
<FormattedMessage id={labelId} defaultMessage={labelId}/>
),
},
},
{
name: 'message',
label: intl.formatMessage({ id: 'MESSAGE' }),
// options: { cellRenderer: cellRendererTypes.capitalizedString },
},
];
const searchedTableRows = buildAndSortTableRows(searchedData?.results);

if(loading) return <LoadingScreen />
if(searchedData?.statusCode === 403) return <SadScreen
statusCode={searchedData?.statusCode}
Expand Down Expand Up @@ -182,6 +183,7 @@ export default function ChangeLog() {
tableContainerStyles={{ maxHeight: 1000 }}
searchParams={searchParams}
setSearchParams={setSearchParams}

noResultsTextId="NO_SEARCH_RESULT"
/>
<Paginator
Expand Down
1 change: 1 addition & 0 deletions src/pages/individual/SearchIndividuals.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export default function SearchIndividuals() {
searchParams={searchParams}
setSearchParams={setSearchParams}
dataCount={resultCount}
formFilters={formFilters}
/>
<Paginator
searchParams={searchParams}
Expand Down
1 change: 1 addition & 0 deletions src/pages/sighting/SearchSightings.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export default function SearchSightings() {
searchParams={searchParams}
setSearchParams={setSearchParams}
dataCount={resultCount}
formFilters={formFilters}
/>
<Paginator
searchParams={searchParams}
Expand Down
Loading