mirror of
https://github.com/nowcommunity/nowdatabase.git
synced 2026-10-12 19:13:52 +03:00
Merge pull request #924 from nowcommunity/improve-export-filenames
Fix export related issues and change export file name to include name of table and a timestamp
This commit is contained in:
@@ -132,8 +132,8 @@ router.get(`/export/:columnfilters/:sorting`, async (req, res) => {
|
||||
})
|
||||
res.attachment(`cross_search${currentDateAsString()}.csv`) // filename will get overwritten in frontend when fetching data from this route
|
||||
|
||||
// quoteColumns is needed to make sure linebreaks do not mess up the data
|
||||
const stream = format({ headers: true, quoteColumns: true }).transform(
|
||||
// this should match the csvConfig in frontend exportRows() function as closely as possible
|
||||
const stream = format({ delimiter: ',', headers: true, quoteColumns: true }).transform(
|
||||
transformFunction as FormatterRowTransformFunction<FormatterRow, FormatterRow>
|
||||
)
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
### Cross Search Export
|
||||
|
||||
Cross search has a [`/export` route](../../backend/src/routes/crossSearch.ts) which is used by the ["Export Table"](../../../frontend/src/components/CrossSearch/CrossSearchExportButton.tsx) button in a cross-search table view. This route uses the same parameter parsing and validation function as the `/all` route, and it also uses the same SQL query, but with LIMIT set to undefined. The resulting data is [streamed](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API) to the user, where the browser will download it as a csv file. See the "Exporting" section in [TableView docs](../frontend/components/TableView.md) for more information.
|
||||
Cross search has a [`/export` route](../../backend/src/routes/crossSearch.ts) which is used by the ["Export Table"](../../frontend/src/components/CrossSearch/CrossSearchExportMenuItem.tsx) button in a cross-search table view. This route uses the same parameter parsing and validation function as the `/all` route, and it also uses the same SQL query, but with LIMIT set to undefined. The resulting data is [streamed](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API) to the user, where the browser will download it as a csv file. See the "Exporting" section in [TableView docs](../frontend/components/TableView.md) for more information.
|
||||
|
||||
@@ -14,7 +14,7 @@ export const CrossSearchExportMenuItem = ({ handleClose }: { handleClose: () =>
|
||||
const token = useUser().token
|
||||
const fetchOptions = token ? { headers: { Authorization: `Bearer ${token}` } } : {}
|
||||
|
||||
const filename = `cross_search${currentDateAsString()}.csv`
|
||||
const filename = `locality-species-${currentDateAsString()}.csv`
|
||||
|
||||
const fetchCSVFile = async () => {
|
||||
setLoading(true)
|
||||
|
||||
@@ -25,6 +25,7 @@ export const StagingView = <T extends MRT_RowData>() => {
|
||||
<SelectingTable<Reference, T>
|
||||
buttonText="Add existing reference"
|
||||
data={data}
|
||||
title="References"
|
||||
isError={isError}
|
||||
columns={referenceTableColumns}
|
||||
idFieldName="rid"
|
||||
|
||||
@@ -16,6 +16,7 @@ export const SelectingTable = <T extends MRT_RowData, ParentType extends object>
|
||||
buttonText,
|
||||
data,
|
||||
columns,
|
||||
title,
|
||||
editingAction,
|
||||
tableRowAction,
|
||||
fieldName,
|
||||
@@ -28,6 +29,7 @@ export const SelectingTable = <T extends MRT_RowData, ParentType extends object>
|
||||
buttonText: string
|
||||
data: T[] | undefined
|
||||
columns: MRT_ColumnDef<T>[]
|
||||
title: string
|
||||
editingAction?: (object: T) => void
|
||||
tableRowAction?: (row: T) => void
|
||||
fieldName: keyof ParentType
|
||||
@@ -76,6 +78,7 @@ export const SelectingTable = <T extends MRT_RowData, ParentType extends object>
|
||||
<TableView<T>
|
||||
data={filteredData}
|
||||
columns={columns}
|
||||
title={title}
|
||||
isFetching={false}
|
||||
visibleColumns={visibleColumns}
|
||||
selectorFn={editingAction ?? defaultEditingAction}
|
||||
|
||||
@@ -9,6 +9,7 @@ import { generateKml } from '@/util/kml'
|
||||
import { generateSvg } from '../Map/generateSvg'
|
||||
import { usePageContext } from '../Page'
|
||||
import { LocalitySynonymsModal } from './LocalitySynonymsModal'
|
||||
import { currentDateAsString } from '@/shared/currentDateAsString'
|
||||
|
||||
const decimalCount = (num: number) => {
|
||||
const numAsString = num.toString()
|
||||
@@ -403,7 +404,8 @@ export const LocalityTable = ({ selectorFn }: { selectorFn?: (newObject: Localit
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = 'localitiesWithSpecies.csv'
|
||||
|
||||
a.download = `localities-with-species-${currentDateAsString()}.csv`
|
||||
a.click()
|
||||
}
|
||||
|
||||
@@ -416,7 +418,7 @@ export const LocalityTable = ({ selectorFn }: { selectorFn?: (newObject: Localit
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = 'localities.kml'
|
||||
a.download = `localities-${currentDateAsString()}.kml`
|
||||
a.click()
|
||||
}
|
||||
|
||||
@@ -427,7 +429,7 @@ export const LocalityTable = ({ selectorFn }: { selectorFn?: (newObject: Localit
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = 'map.svg'
|
||||
a.download = `localities-map-${currentDateAsString()}.svg`
|
||||
a.click()
|
||||
}
|
||||
|
||||
|
||||
@@ -192,6 +192,7 @@ export const LithologyTab = () => {
|
||||
isError={isError}
|
||||
columns={selectingTableColumns}
|
||||
data={sedimentaryStructuresData}
|
||||
title="Sedimentary structures"
|
||||
fieldName="now_ss"
|
||||
idFieldName="ss_value"
|
||||
editingAction={(newSed: SedimentaryStructureValues) => {
|
||||
|
||||
@@ -38,6 +38,7 @@ export const MuseumTab = () => {
|
||||
<SelectingTable<Museum, LocalityDetailsType>
|
||||
buttonText="Select Museum"
|
||||
data={museumData}
|
||||
title="Museums"
|
||||
isError={isError}
|
||||
columns={columns}
|
||||
fieldName="now_mus"
|
||||
|
||||
@@ -43,6 +43,7 @@ export const ProjectTab = () => {
|
||||
<SelectingTable<Project, LocalityDetailsType>
|
||||
buttonText="Select Project"
|
||||
data={projectData}
|
||||
title="Projects"
|
||||
columns={columns}
|
||||
isError={isError}
|
||||
idFieldName="pid"
|
||||
|
||||
@@ -44,6 +44,7 @@ export const SpeciesTab = () => {
|
||||
<SelectingTable<Species, Species>
|
||||
dataCy="copy_existing_taxonomy_button"
|
||||
buttonText="Copy existing taxonomy"
|
||||
title="Copy existing taxonomy"
|
||||
data={speciesData}
|
||||
isError={isError}
|
||||
columns={smallSpeciesTableColumns}
|
||||
@@ -222,6 +223,7 @@ export const SpeciesTab = () => {
|
||||
<SelectingTable<Species, LocalityDetailsType>
|
||||
buttonText="Select Species"
|
||||
data={speciesData}
|
||||
title="Species"
|
||||
isError={isError}
|
||||
columns={speciesColumns}
|
||||
fieldName="now_ls"
|
||||
|
||||
@@ -64,7 +64,7 @@ export const MuseumTable = ({ selectorFn }: { selectorFn?: (museum: Museum) => v
|
||||
|
||||
return (
|
||||
<TableView<Museum>
|
||||
title="Museum"
|
||||
title="Museums"
|
||||
selectorFn={selectorFn}
|
||||
idFieldName="museum"
|
||||
columns={columns}
|
||||
|
||||
@@ -124,6 +124,7 @@ export const AuthorTab: React.FC<AuthorTabProps> = ({ field_num_param, tab_name
|
||||
<SelectingTable<ReferenceAuthorType, ReferenceDetailsType>
|
||||
buttonText={`Select ${buttonText}`}
|
||||
data={authorData}
|
||||
title="Authors"
|
||||
isError={isError}
|
||||
columns={authorColumns}
|
||||
fieldName="ref_authors"
|
||||
|
||||
@@ -88,6 +88,7 @@ export const JournalTab: React.FC<JournalTabProps> = ({ tab_name = 'Journal' })
|
||||
<SelectingTable<ReferenceJournalType, ReferenceDetailsType>
|
||||
buttonText="Select Journal"
|
||||
data={journalData}
|
||||
title="Journals"
|
||||
isError={isError}
|
||||
columns={journalColumns}
|
||||
fieldName="ref_journal"
|
||||
|
||||
@@ -106,6 +106,7 @@ export const CoordinatorTab = () => {
|
||||
<SelectingTable<PersonDetailsType, RegionDetails>
|
||||
buttonText="Select Coordinator"
|
||||
data={personsData}
|
||||
title="Coordinators"
|
||||
isError={isError}
|
||||
columns={personTableColumns}
|
||||
fieldName="now_reg_coord_people"
|
||||
|
||||
@@ -31,6 +31,7 @@ export const SequenceTable = ({ selectorFn }: { selectorFn?: (id: Sequence) => v
|
||||
selectorFn={selectorFn}
|
||||
idFieldName="sequence"
|
||||
columns={columns}
|
||||
title="Sequences"
|
||||
isFetching={isFetching}
|
||||
visibleColumns={visibleColumns}
|
||||
data={sequenceQueryData}
|
||||
|
||||
@@ -50,6 +50,7 @@ export const LocalityTab = () => {
|
||||
<SelectingTable<Editable<Locality>, SpeciesDetailsType>
|
||||
buttonText="Select Locality"
|
||||
columns={columns}
|
||||
title="Localities"
|
||||
isError={isError}
|
||||
data={localitiesData}
|
||||
fieldName="now_ls"
|
||||
|
||||
@@ -27,6 +27,7 @@ export const TaxonomyTab = () => {
|
||||
dataCy="copy_existing_taxonomy_button"
|
||||
buttonText="Copy existing taxonomy"
|
||||
data={speciesQueryData}
|
||||
title="Species"
|
||||
isError={isError}
|
||||
columns={smallSpeciesTableColumns}
|
||||
fieldName="order_name" // this doesn't do anything here but is required
|
||||
|
||||
@@ -12,6 +12,7 @@ import { usePageContext } from '../Page'
|
||||
|
||||
export const TableToolBar = <T extends MRT_RowData>({
|
||||
table,
|
||||
tableName,
|
||||
combinedExport,
|
||||
kmlExport,
|
||||
svgExport,
|
||||
@@ -21,6 +22,7 @@ export const TableToolBar = <T extends MRT_RowData>({
|
||||
showNewButton,
|
||||
}: {
|
||||
table: MRT_TableInstance<T>
|
||||
tableName: string
|
||||
combinedExport?: (lids: number[]) => Promise<void>
|
||||
kmlExport?: (table: MRT_TableInstance<T>) => void
|
||||
svgExport?: (table: MRT_TableInstance<T>) => void
|
||||
@@ -87,7 +89,7 @@ export const TableToolBar = <T extends MRT_RowData>({
|
||||
) : (
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
exportRows(table)
|
||||
exportRows(table, tableName)
|
||||
handleClose()
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -57,10 +57,9 @@ export const TableView = <T extends MRT_RowData>({
|
||||
idFieldName: keyof T
|
||||
checkRowRestriction?: (row: T) => boolean
|
||||
selectorFn?: (id: T) => void
|
||||
// here
|
||||
tableRowAction?: (row: T) => void
|
||||
url?: string
|
||||
title?: string
|
||||
title: string
|
||||
combinedExport?: (lids: number[]) => Promise<void>
|
||||
kmlExport?: (table: MRT_TableInstance<T>) => void
|
||||
svgExport?: (table: MRT_TableInstance<T>) => void
|
||||
@@ -259,6 +258,7 @@ export const TableView = <T extends MRT_RowData>({
|
||||
{title && <Box className="title">{title}</Box>}
|
||||
<TableToolBar<T>
|
||||
table={table}
|
||||
tableName={title}
|
||||
combinedExport={combinedExport}
|
||||
kmlExport={kmlExport}
|
||||
svgExport={svgExport}
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import { MRT_RowData, MRT_TableInstance } from 'material-react-table'
|
||||
import { mkConfig, generateCsv, download } from 'export-to-csv'
|
||||
import { AcceptedData } from 'node_modules/export-to-csv/output/lib/types'
|
||||
import { currentDateAsString } from '@/shared/currentDateAsString'
|
||||
|
||||
export type ExportFn<T> = (data: T) => { [k: string]: AcceptedData }
|
||||
|
||||
export const exportRows = <T extends MRT_RowData>(table: MRT_TableInstance<T>) => {
|
||||
export const exportRows = <T extends MRT_RowData>(table: MRT_TableInstance<T>, tableName: string) => {
|
||||
const csvConfig = mkConfig({
|
||||
fieldSeparator: ',',
|
||||
decimalSeparator: '.',
|
||||
useKeysAsHeaders: true,
|
||||
quoteStrings: true, // If true, will wrap all values in double quotes
|
||||
filename: `${tableName.toLowerCase()}-${currentDateAsString()}`,
|
||||
})
|
||||
const rowData = table.getPrePaginationRowModel().rows.map(row => {
|
||||
const r = {} as Record<string, AcceptedData>
|
||||
|
||||
Reference in New Issue
Block a user