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

[RFR] Add Export feature #2034

Merged
merged 18 commits into from Jul 19, 2018
Merged

[RFR] Add Export feature #2034

merged 18 commits into from Jul 19, 2018

Conversation

fzaninotto
Copy link
Member

@fzaninotto fzaninotto commented Jul 18, 2018

This is a solution to the oldest open feature request: the Export button. It was made possible by recent changes (#2006, #2028). It's designed to be easy to override.

Comments are welcome.

Closes #94

Tasks

  • Add <ExportButton> component
  • Use it on the simple example using default settings (see export posts)
  • Use it on the simple example using custom settings (see export comments, with related posts)
  • Add documentation

Documentation

Among the default list actions, react-admin includes an <ExportButton>. By default, clicking this button will:

  1. Call the dataProvider with the current sort and filter (but without pagination),
  2. Transform the result into a CSV string,
  3. Download the CSV file.

The columns of the CSV file correspond to all the fields of the records in the dataProvider response. That means that the export doesn't take into account the selection and ordering of fields in your <List> via Field components. If you want to customize the result, pass a custom exporter function to the <List>. This function will receive the data from the dataProvider (after step 1), and replace steps 2-3 (i.e. it's in charge of transforming, converting, and downloading the file).

Tip: For CSV conversion, you can import Papaparse, a CSV parser and stringifier which is already a react-admin dependency. And for CSV download, take advantage of react-admin's downloadCSV function.

Here is an example for a Posts exporter, omitting, adding, and reordering fields:

// in PostList.js
import { List, downloadCSV } from 'react-admin';
import { unparse as convertToCSV } from 'papaparse/papaparse.min';

const exporter = (data) => data.map(post => {
    const { postForExport, backlinks, author } = post; // omit backlinks and author
    postForExport.author_name = post.author.name; // add a field
    const csv = convertToCSV({
        data: postForExport,
        fields: ['id', 'title', 'author_name', 'body'] // order fields in the export
    });
    downloadCSV(csv, 'posts'); // download as 'posts.csv` file
})

const PostList = props => (
    <List {...props} exporter={exporter}>
        ...
    </List>
)

In many cases, you'll need more than simple object manipulation. You'll need to augment your objects based on relationships. For instance, the export for comments should include the title of the related post - but the export only exposes a post_id by default. For that purpose, the exporter receives a fetchRelatedRecords() function as second parameter. It fetches related records using your dataProvider and Redux, and returns a promise.

Here is an example for a Comments exporter, fetching related Posts:

// in CommentList.js
import { List, downloadCSV } from 'react-admin';
import { unparse as convertToCSV } from 'papaparse/papaparse.min';

const exporter = (records, fetchRelatedRecords) => {
    fetchRelatedRecords(records, 'post_id', 'posts').then(posts => {
        const data = posts.map(record => ({
                ...record,
                post_title: posts[record.post_id].title,
        }));
        const csv = convertToCSV({
            data,
            fields: ['id', 'post_id', 'post_title', 'body'],
        });
        downloadCSV(csv, 'comments');
    });
};

const CommentList = props => (
    <List {...props} exporter={exporter}>
        ...
    </List>
)

Under the hood, fetchRelatedRecords() uses react-admin's sagas, which trigger the loading spinner while loading. As a bonus, all the records fetched during an export are kepts in the main Redux store, so further browsing the admin will be accelerated.

Tip: If you need to call another dataProvider verb in the exporter, take advantage of the third parameter passed to the function: dispatch(). It allows you to call any Redux action. Combine it with the callback side effect to grab the result in a callback.

Tip: The <ExportButton> limits the main request to the dataProvider to 1,000 records. If you want to increase or decrease this limit, pass a maxResults prop to the <ExportButton> in a custom <ListActions> component, as explained in the previous section.

@fzaninotto fzaninotto added this to the 2.2.0 milestone Jul 18, 2018
docs/List.md Outdated
2. Transform the result into a CSV string,
3. Download the CSV file.

The columns of the CSV file correspond to all the fields of the records in the `dataProvider` response. That means that the export doesn't take into account the selection and ordering of fields in your `<List>` via `Field` components. If you want to customize the result, pass a custom `exporter` function to the `<List>`. This function will receive the data from the `dataProvider` (after step 1), and replace steps 2-3 (i.e. it's in charge of transforming, converting, and downloading the file).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

correspond to all => matches ?

@fzaninotto fzaninotto changed the title [WIP] Add Export feature [RFR] Add Export feature Jul 19, 2018
@djhi
Copy link
Contributor

djhi commented Jul 19, 2018

Dope! What about performances when loading really huge sets of data ?

@fzaninotto
Copy link
Member Author

fzaninotto commented Jul 19, 2018

What about performances when loading really huge sets of data ?

They'll be bad. That's unfortunate, but inevitable since we do the export client-side (same as ng-admin). But there is an alternative, which I've mentioned in the doc:

For complex (or large) exports, fetching all the related records and assembling them client-side can be slow. In that case, create the CSV on the server side, and replace the <ExportButton> component by a custom one, fetching the CSV route.

It's just React™.

@@ -36,6 +36,7 @@
"classnames": "~2.2.5",
"inflection": "~1.12.0",
"lodash": "~4.17.5",
"papaparse": "^4.1.4",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

^ spotted

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

on purpose

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh ?

@djhi djhi merged commit d0f5fa4 into next Jul 19, 2018
@djhi djhi deleted the export-button branch July 19, 2018 08:41
@djhi
Copy link
Contributor

djhi commented Jul 19, 2018

🎉

This was referenced Jul 19, 2018
@katliss26
Copy link

Hi!
I would like to hide the export button, what should I do?
Greetings from Venezuela

@djhi
Copy link
Contributor

djhi commented Aug 27, 2018

Hi @katliss26, please don't ask questions on Pull Requests, use issues instead.

Beside, as explained in the react-admin contributing guide, the right place to ask a "How To" question, get usage advice, or troubleshoot your own code, is StackOverFlow.

This makes your question easy to find by the core team, and the developer community. Unlike Github, StackOverFlow has great SEO, gamification, voting, and reputation. That's why we chose it, and decided to keep GitHub issues only for bugs and feature requests

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants