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

Add csv export for bookmarks #118

Merged
merged 5 commits into from
Sep 22, 2023

Conversation

PatOConnor43
Copy link
Contributor

@PatOConnor43 PatOConnor43 commented Sep 18, 2023

This PR may close #86 unless there are additional desired formats.

Description

#86 describes a need to export the bookmarks into a friendlier format. This PR attempts to do that by creating a specific bookmarksDB function for that query and then using the csv-stringify package to send it to the client. This PR makes an additional small change to the admin/data template to export the formats as a list.

Testing

  • Go to /admin/data
  • Click the csv option in the list
  • Verify a properly formatted csv is downloaded

Alternatives Considered

I started out trying to just rely on sqlite to do the export. If you get in the console you can run:

.headers on
.mode csv

and selects will come out as csv.

I was having an issue toggling those options on as part of the db.all/exec/run. I think we could probably remove the csv-stringify dependency altogether if we could rely on this instead.

FYI @ckolderup @andypiper

<p>
You can download your bookmarks sqlite db
<a href="/admin/bookmarks.db">here</a>.
<p style='margin: 0'>
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I added a margin change here just because I thought there was a lot of space 🤷 . Can definitely remove if desired.

Copy link
Collaborator

Choose a reason for hiding this comment

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

no problem with this, the only comment I have is that to be consistent with the rest of the HTML code, switch over to " instead of ' (and throw a ; on the end of the style value)

<p style="margin: 0;">

I think overall the web UI piece needs a lot more work, but this is fine from my POV.

Copy link
Owner

Choose a reason for hiding this comment

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

yeah, the web UI is definitely still rough. I have a couple talented friends who have offered to help out with this if they can find the time, but obviously we're all volunteering what we have to give here (and I'm still honored by how much time y'all have spent on this already). I'm fine with little stopgaps here and there with the understanding that we're probably going to blow up these templates and rebuild them.

'SELECT title,url,description,tags,created_at,updated_at from bookmarks',
);
// These titles should be updated if the query changes above.
const columnTitles = {'title': 'title','url': 'url','description': 'description','tags': 'tags','created_at': 'created_at','updated_at': 'updated_at'};
Copy link
Contributor Author

Choose a reason for hiding this comment

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

csv-stringify does have a way to specify titles when it stringifies, but I wanted to keep the titles local to this function so people didn't forget to update them if the query changed.

Copy link
Owner

Choose a reason for hiding this comment

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

I think we could get a little clever here and remove the need to update two things: something like this?

const columns = ['title', 'url', 'description', 'tags', 'created_at', 'updated_at'];
const query = `SELECT ${columns.join(',')} from bookmarks`;
const columnTitles = columns.reduce((o, c) => { o[c] = c; return o}, {})

Copy link
Owner

Choose a reason for hiding this comment

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

(that probably doesn't pass eslint, apologies, haha)

router.get('/bookmarks.csv', isAuthenticated, async (req, res) => {
const bookmarksDb = req.app.get('bookmarksDb');
const bookmarks = await bookmarksDb.getBookmarksForCSVExport();
const result = csvStringify(bookmarks);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

csv-stringify does also have an streaming API such that we could asynchronously stringify a row and then write that to the client. I didn't do that because this was simpler and we probably don't need to worry about memory ballooning during an export right? That would be so many bookmarks 😬

Copy link
Owner

Choose a reason for hiding this comment

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

yeah, I definitely am in favor of keeping things as simple as possible for now. If we start getting reports that memory limits are happening (especially on the Glitch containers), we can revisit this.

@@ -1,4 +1,7 @@
import express from 'express';
/* eslint-disable import/no-unresolved, node/no-missing-import */
import { stringify as csvStringify } from 'csv-stringify/sync'; // https://github.com/adaltas/node-csv/issues/323
Copy link
Contributor Author

Choose a reason for hiding this comment

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

These eslint issues seem like a known issue. I tried using the csv package instead, but that didn't work either 😞

Copy link
Owner

Choose a reason for hiding this comment

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

I had a similar issue with string-strip-html here-- if anyone reading this knows why this happens, I'd love to know!

router.get('/bookmarks.csv', isAuthenticated, async (req, res) => {
const bookmarksDb = req.app.get('bookmarksDb');
const bookmarks = await bookmarksDb.getBookmarksForCSVExport();
const result = csvStringify(bookmarks);
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think this needs a tweak

const result = csvStringify(bookmarks, {quoted: true});

I had some bookmarks in my database with null values in some columns (from a batch import), and they caused the CSV generation to go awry until I added this option to ensure that they were correctly handled.

Copy link
Collaborator

@andypiper andypiper left a comment

Choose a reason for hiding this comment

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

Thanks for working on this! I made a couple of small suggestions, but it's working nicely for me (with the addition of the quoting on the stringify)

@andypiper
Copy link
Collaborator

I guess a follow-on step from this would be to enable the reverse via the bulk import function, ie accept a file in CSV format that has been exported, for import.

@ckolderup ckolderup mentioned this pull request Sep 20, 2023
@ckolderup
Copy link
Owner

@andypiper good point, I just updated #23 to make it specifically targeting import from this export format. (We can open separate tickets over time for import from browser bookmark HTML files as well as export formats from Pinboard, Shaarli, etc)

@ckolderup ckolderup mentioned this pull request Sep 20, 2023
@PatOConnor43
Copy link
Contributor Author

Thanks for the review @andypiper @ckolderup. I think this should be ready for review again when you have a chance.

I did start looking into a csv import as well. The csv-parser package seemed like a good way to do that since it'd give us a parsed object that we could use to support other sources that output csv.

Copy link
Owner

@ckolderup ckolderup left a comment

Choose a reason for hiding this comment

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

This looks good to me, and I tested it manually and can see that quoted strings are looking correct now, including triple-quotes around strings that have quotes in them. Thanks!

@ckolderup ckolderup merged commit 87e23da into ckolderup:main Sep 22, 2023
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.

export to CSV
3 participants