Skip to content

add Data Profiler CSV and JSON export - #759

Merged
cevheri merged 5 commits into
libredb:mainfrom
na12334:data-profiler-export
Sep 10, 2026
Merged

add Data Profiler CSV and JSON export#759
cevheri merged 5 commits into
libredb:mainfrom
na12334:data-profiler-export

Conversation

@na12334

@na12334 na12334 commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Description

Adds CSV and JSON export support to the Data Profiler so users can download computed profiling statistics instead of manually copying the results.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Code refactoring
  • Performance improvement
  • Test addition or update

Related Issue

Closes #750

Changes Made

  • Added an Export menu to the Data Profiler with CSV and JSON options
  • Reused the project's existing shared CSV writer and download utility
  • Exported column profiling statistics including null counts, null percentage, distinct counts, min/max values, and sample values
  • Preserved masking of sensitive column values in exported files
  • Added component tests for the export menu and CSV download behavior

Testing

  • I have tested this locally
  • I have added/updated tests
  • All existing tests pass

The changes were made through the GitHub web editor, so the full test suite was not run locally. CI is expected to verify the implementation.

Test Environment

  • LibreDB Studio Version: Current main
  • Browser: Microsoft Edge
  • OS: Windows
  • Node.js/Bun Version: N/A — tests not run locally
  • Database Type: N/A

Screenshots (if applicable)

N/A

Checklist

  • My code follows the project's code style guidelines
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have updated the documentation accordingly
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • The required CI test job passes the 100% line-coverage gate (bun run test:coverage and bun run coverage:check)
  • If I changed src/lib/db/providers/, I updated the matching docs/providers/ documentation and tests/integration/db/ tests in the same PR (provider triad)
  • Any dependent changes have been merged and published

Additional Notes

The implementation follows the existing export approach used elsewhere in LibreDB Studio. Full test and coverage verification is left to CI because the contribution was completed through the GitHub web editor.

@cevheri cevheri left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Welcome, and thank you for this. Two things in here are the parts people usually miss, so I want to name them before the problems.

You used csvRow and downloadText instead of building a writer of your own. That is the whole point of src/lib/export/csv.ts, and its docstring exists because three exports each rolled their own quoting and got it wrong. And you masked minValue, maxValue and sampleValues through maskValue without being asked. A profiler export that leaked raw values from a column the UI masks would have been a real defect, and you closed it unprompted.

Three things to fix, in this order.

1. bun run format fails, so CI is red before anything else runs. The failing step is Check formatting (Biome). Your file is wrapped at about 80 columns; this repo's Biome config is wider, so the import list and several expressions are split where Biome would keep them on one line. bun run format:fix does all of it. Worth knowing: that step is the FIRST in the job, so everything after it, lint, typecheck, knip, the four drift guards, build, build:lib and attw, has not run. None of it is green yet, it is unmeasured.

2. The two new tests are outside the describe block. Line 816's }); closes it, and your tests come after. So they never get the beforeEach that calls mockGlobalFetch, the profile fetch is never mocked, profile stays null, and the whole {profile && ...} block including the Export button never renders. That is why queryByText("Export") is null.

3. That failure takes 69 unrelated tests with it. Measured on Group 15/16, the 25 files that share one process: 890 pass / 0 fail on main, 821 pass / 71 fail on your branch, and 890 pass / 0 fail again with only your two tests deleted. The collateral lands in admin/OverviewTab (20), AuditTab (15), OperationsTab (11), QueryHistory (8) and four more. Cause: your test throws at its first assertion, so the last line, document.createElement = originalCreateElement, never runs, and every later file in the group keeps your spy. The URL.createObjectURL override is never restored at all. Restore both in a finally, or in an afterEach.

One more, once those pass. QueryHistory.test.tsx:350 is the working version of this exact test and it differs in a way that will bite you next: it drives the menu with userEvent and finds the items with within(document.body), because Radix renders DropdownMenuContent in a portal rather than inside your container.

And a test-quality note rather than a blocker: the CSV case asserts only that createObjectURL and click were called, never what was written. So the headers, the column order, csvRow's escaping and your own masking are all unpinned, and the JSON branch has no test at all. Asserting the CSV text as one string is what would make the masking you added actually hold.

@na12334

na12334 commented Sep 10, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the review feedback: moved the export tests inside the describe block, added userEvent/portal handling, restored global mocks safely, and added CSV content/masking plus JSON export coverage. Updated commits are pushed and ready for CI/re-review.

@cevheri

cevheri commented Sep 10, 2026

Copy link
Copy Markdown
Member

there are some github actions error, can you fix them
also you can run all gate on your local machine( to catch before github actions)

@cevheri cevheri closed this Sep 10, 2026
@cevheri cevheri reopened this Sep 10, 2026
…here

Three jobs were red for two causes, both in the test file. Biome formatting on
one wrapped `mockImplementation` call, and `mock.calls[0][0]` typed `[]`
because the `URL.createObjectURL` double declared no parameters; the second is
what `Engine Smoke - Build Payload` died on too, since `next build` type-checks
`tests/` as well.

The expected CSV also carried a leading BOM. `downloadText` does write one for
`text/csv`, but `blob.text()` is a UTF-8 decode and strips it, which
`tests/unit/lib/export/download.test.ts:92` already records.

Building the text now lives in `src/lib/export/data-profile.ts`, the shape libredb#740
established for the query-history export, and goes through `jsonText` instead
of `JSON.stringify`, so the one JSON serializer stays the one. Its headers,
column order, escaping and masking are asserted there as plain strings, and the
two component tests keep only the wiring: that each menu item exports in its
own format, and that the masking map the screen uses is the one handed to the
export.
@cevheri

cevheri commented Sep 10, 2026

Copy link
Copy Markdown
Member

I pushed the rest onto your branch instead of sending you a third round, since what was left was mechanical. The substance here is yours and it stays: the export itself, csvRow and downloadText rather than a writer of your own, and the masking you added without being asked.

What I changed, in order.

  1. format:fix on the one wrapped mockImplementation call.

  2. Dropped  from the expected CSV. downloadText does write the mark for text/csv, but blob.text() is a UTF-8 decode and strips a leading BOM, which tests/unit/lib/export/download.test.ts:92 records. The mark is really there: I read ef bb bf off a real download.

  3. mock.calls[0][0] was typed [], because the URL.createObjectURL double declared no parameters. That was also why Engine Smoke - Build Payload was red, since next build type-checks tests/ too. One cause, two jobs.

  4. Merged main, which had moved: feat(audit): export filtered operations and queries #740 pulled the query-history export into src/lib/export/query-history.ts. The issue asks for that pattern, so your text building now lives in src/lib/export/data-profile.ts and goes through jsonText. Headers, column order, escaping and the masking rules are asserted there as plain strings, and your two component tests keep the wiring.

Worth knowing for next time: I first mocked @/lib/export/download in the component test, which is much shorter, and it broke QueryHistory's own download test. mock.module is process-wide and the group does not run its files in the order the script lists them.

Verified in a browser against Postgres and MongoDB before pushing. The email column arrives masked in both files and the real addresses appear nowhere in either.

Good first contribution, thank you.

@codecov

codecov Bot commented Sep 10, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@cevheri
cevheri merged commit ea5730c into libredb:main Sep 10, 2026
22 checks passed
@cevheri

cevheri commented Sep 10, 2026

Copy link
Copy Markdown
Member

@na12334 short note: follow this standard: https://www.conventionalcommits.org/en/v1.0.0/

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.

Data Profiler has no export

2 participants