-
Notifications
You must be signed in to change notification settings - Fork 50.6k
feat(editor): CSV download support for data tables #22048
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
base: master
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2 issues found across 7 files
Prompt for AI agents (all 2 issues)
Understand the root cause of the following 2 issues and fix them.
<file name="packages/frontend/editor-ui/src/features/core/dataTable/dataTable.store.ts">
<violation number="1" location="packages/frontend/editor-ui/src/features/core/dataTable/dataTable.store.ts:277">
Rule violated: **Tests**
`downloadDataTableCsv` introduces a new CSV export path (API call plus DOM-triggered download) but no unit/UI tests were added to cover it, violating the Community PR Guidelines §2 testing requirement for new functionality.</violation>
</file>
<file name="packages/cli/src/modules/data-table/data-table.controller.ts">
<violation number="1" location="packages/cli/src/modules/data-table/data-table.controller.ts:265">
This endpoint returns full table data but is guarded only by `dataTable:read`, letting callers who can see metadata download entire datasets without the `dataTable:readRow` permission that other row-returning routes require. Align the scope with existing row access to prevent unauthorized data export.</violation>
</file>
Reply to cubic to teach it or ask questions. Re-run a review with @cubic-dev-ai review this PR
| return result; | ||
| }; | ||
|
|
||
| const downloadDataTableCsv = async (dataTableId: string, projectId: string) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rule violated: Tests
downloadDataTableCsv introduces a new CSV export path (API call plus DOM-triggered download) but no unit/UI tests were added to cover it, violating the Community PR Guidelines §2 testing requirement for new functionality.
Prompt for AI agents
Address the following comment on packages/frontend/editor-ui/src/features/core/dataTable/dataTable.store.ts at line 277:
<comment>`downloadDataTableCsv` introduces a new CSV export path (API call plus DOM-triggered download) but no unit/UI tests were added to cover it, violating the Community PR Guidelines §2 testing requirement for new functionality.</comment>
<file context>
@@ -273,6 +274,36 @@ export const useDataTableStore = defineStore(DATA_TABLE_STORE, () => {
return result;
};
+ const downloadDataTableCsv = async (dataTableId: string, projectId: string) => {
+ // Fetch CSV content with authentication
+ const { csvContent, filename } = await downloadDataTableCsvApi(
</file context>
| } | ||
|
|
||
| @Get('/:dataTableId/download-csv') | ||
| @ProjectScope('dataTable:read') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This endpoint returns full table data but is guarded only by dataTable:read, letting callers who can see metadata download entire datasets without the dataTable:readRow permission that other row-returning routes require. Align the scope with existing row access to prevent unauthorized data export.
Prompt for AI agents
Address the following comment on packages/cli/src/modules/data-table/data-table.controller.ts at line 265:
<comment>This endpoint returns full table data but is guarded only by `dataTable:read`, letting callers who can see metadata download entire datasets without the `dataTable:readRow` permission that other row-returning routes require. Align the scope with existing row access to prevent unauthorized data export.</comment>
<file context>
@@ -261,6 +261,36 @@ export class DataTableController {
}
+ @Get('/:dataTableId/download-csv')
+ @ProjectScope('dataTable:read')
+ async downloadDataTableCsv(
+ req: AuthenticatedRequest<{ projectId: string; dataTableId: string }>,
</file context>
| @ProjectScope('dataTable:read') | |
| @ProjectScope('dataTable:readRow') |
…8n-io/n8n into feature-export-csv-data-tables
BundleMonUnchanged files (2)
No change in files bundle size Groups updated (2)
Final result: ✅ View report in BundleMon website ➡️ |
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
| ): Promise<{ csvContent: string; dataTableName: string }> { | ||
| const dataTable = await this.validateDataTableExists(dataTableId, projectId); | ||
|
|
||
| // Fetch columns (ordered by index) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nick: I guess we do not need all these comments?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same with the comments below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Claude was very generous with the comments. Cleaned them out.
| const actions = computed<Array<UserAction<IUser>>>(() => { | ||
| const availableActions = [ | ||
| { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm surprised that we added this here and not next to the search input
|
|
||
| @Get('/:dataTableId/download-csv') | ||
| @ProjectScope('dataTable:read') | ||
| async downloadDataTableCsv( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we add a integration test for this endpoint? that will automatically add coverage to all the backend code (or most of it)
| const str = String(value); | ||
|
|
||
| // RFC 4180 compliant escaping: | ||
| // - If value contains comma, quote, or newline, wrap in quotes | ||
| // - Escape quotes by doubling them | ||
| if (str.includes(',') || str.includes('"') || str.includes('\n') || str.includes('\r')) { | ||
| return `"${str.replace(/"/g, '""')}"`; | ||
| } | ||
|
|
||
| return str; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like leading/trailing whitespace won't be preserved. Was this intentional? I guess we should keep the original data as it comes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. Made the change so that if there is leading or trailing whitespace, the string will be in quotes. This helps import to google sheets.
| return result; | ||
| }; | ||
|
|
||
| const downloadDataTableCsv = async (dataTableId: string, projectId: string) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can make this readable like this:
const UTF8_BOM = '\uFEFF';
const createCsvBlob = (csvContent: string): Blob => {
// Add BOM for Excel compatibility with special characters
return new Blob([UTF8_BOM + csvContent], {
type: 'text/csv;charset=utf-8;'
});
};
const triggerBrowserDownload = (blob: Blob, filename: string): void => {
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = filename;
link.style.display = 'none';
document.body.appendChild(link);
try {
link.click();
} finally {
document.body.removeChild(link);
URL.revokeObjectURL(url);
}
};
const downloadDataTableCsv = async (dataTableId: string, projectId: string) => {
const { csvContent, filename } = await downloadDataTableCsvApi(
rootStore.restApiContext,
dataTableId,
projectId,
);
const csvBlob = createCsvBlob(csvContent);
triggerBrowserDownload(csvBlob, filename);
};
|
E2E Tests: n8n tests passed after 8m 57.9s Run Details
Groups
This message was posted automatically by
currents.dev | Integration Settings
|
|
Found 3 test failures on Blacksmith runners: Failures
|
RicardoE105
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚀
Summary
Tested with 50 MB of data in a single data table. The download happens in <3 seconds.
If there is a need to support bigger tables, we can add streaming support later
Related Linear tickets, Github issues, and Community forum posts
Review / Merge checklist
release/backport(if the PR is an urgent fix that needs to be backported)