Skip to content

Commit a1c1a41

Browse files
authored
feat(5994): csv download entire dataset (#6036)
* chore(5994): refactor out common steps of runQuery in shared query context * feat(5994): ability to download raw csv blobs without any processing * chore(5994): add eventing for end, plus size of payload. Make as separate events, such that can diff all start versus end events.
1 parent dc1f354 commit a1c1a41

File tree

5 files changed

+270
-140
lines changed

5 files changed

+270
-140
lines changed

cypress/e2e/shared/fluxQueryBuilder.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {Organization} from '../../../src/types'
2+
const path = require('path')
23

34
const DEFAULT_SCHEMA = {
45
bucket: null,
@@ -106,6 +107,8 @@ describe('Script Builder', () => {
106107
})
107108

108109
describe('Results display', () => {
110+
const downloadsDirectory = Cypress.config('downloadsFolder')
111+
109112
beforeEach(() => {
110113
resetSession()
111114
cy.setFeatureFlags({
@@ -137,6 +140,8 @@ describe('Script Builder', () => {
137140
}
138141
})
139142
})
143+
cy.log('empty downloads directory')
144+
cy.task('deleteDownloads', {dirPath: downloadsDirectory})
140145
})
141146

142147
it('will allow querying of different data ranges', () => {
@@ -157,6 +162,14 @@ describe('Script Builder', () => {
157162
})
158163

159164
describe('data completeness', () => {
165+
const validateCsv = (csv: string, tableCnt: number) => {
166+
cy.wrap(csv)
167+
.then(doc => doc.trim().split('\n'))
168+
.then(list => {
169+
expect(list[list.length - 1]).contains(`,${tableCnt - 1},`)
170+
})
171+
}
172+
160173
const runTest = (
161174
tableCnt: number,
162175
rowCnt: number,
@@ -186,6 +199,19 @@ describe('Script Builder', () => {
186199
.contains(`${tableCnt} tables`)
187200
cy.getByTestID('query-stat').contains(`${rowCnt} rows`)
188201
}
202+
203+
cy.log('will download complete csv data')
204+
cy.getByTestID('data-explorer--csv-download').should('exist').click()
205+
const filename = path.join(downloadsDirectory, 'influx.data.csv')
206+
if (tableCnt == 0) {
207+
cy.readFile(filename, {timeout: 15000})
208+
.should('have.length.lt', 50)
209+
.then(csv => csv.trim() == '')
210+
} else {
211+
cy.readFile(filename, {timeout: 15000})
212+
.should('have.length.gt', 50)
213+
.then(csv => validateCsv(csv, tableCnt))
214+
}
189215
}
190216

191217
it('will return 0 tables and 0 rows, for an empty dataset', () => {

cypress/plugins/index.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
const clipboardy = require('clipboardy')
2+
const path = require('path')
3+
const fs = require('fs')
24

35
module.exports = on => {
46
on('before:browser:launch', (browser = {}, launchOptions) => {
@@ -37,5 +39,18 @@ module.exports = on => {
3739
getClipboard: () => {
3840
return clipboardy.readSync()
3941
},
42+
deleteDownloads({dirPath}) {
43+
fs.readdir(dirPath, (err, files) => {
44+
if (!files) {
45+
return
46+
}
47+
for (const file of files) {
48+
fs.unlink(path.join(dirPath, file), err => {
49+
console.log('Removed ' + file)
50+
})
51+
}
52+
})
53+
return null
54+
},
4055
})
4156
}

src/dataExplorer/components/ResultsPane.tsx

Lines changed: 50 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
// Libraries
2-
import React, {FC, lazy, Suspense, useContext, useCallback} from 'react'
2+
import React, {
3+
FC,
4+
lazy,
5+
Suspense,
6+
useContext,
7+
useCallback,
8+
useState,
9+
} from 'react'
310
import {
411
DraggableResizer,
512
Orientation,
@@ -15,6 +22,7 @@ import {
1522
JustifyContent,
1623
AlignItems,
1724
Icon,
25+
ComponentColor,
1826
} from '@influxdata/clockface'
1927

2028
// Contexts
@@ -34,9 +42,10 @@ import {TimeRange} from 'src/types'
3442

3543
// Utils
3644
import {getRangeVariable} from 'src/variables/utils/getTimeRangeVars'
37-
import {downloadTextFile} from 'src/shared/utils/download'
45+
import {downloadBlob} from 'src/shared/utils/download'
3846
import {event} from 'src/cloud/utils/reporting'
3947
import {notify} from 'src/shared/actions/notifications'
48+
import {bytesFormatter} from 'src/shared/copy/notifications/common'
4049
import {getWindowPeriodVariableFromVariables} from 'src/variables/utils/getWindowVars'
4150

4251
// Constants
@@ -101,24 +110,33 @@ const ResultsPane: FC = () => {
101110
setRange,
102111
selection,
103112
} = useContext(PersistanceContext)
113+
const [csvDownloadCancelID, setCancelId] = useState(null)
104114

105115
const submitButtonDisabled = !text && !selection.measurement
106116

107117
const disabledTitleText = submitButtonDisabled
108118
? 'Select measurement before running script'
109119
: ''
110120

111-
const download = () => {
121+
const download = async () => {
112122
event('CSV Download Initiated')
113-
basic(text, {
114-
vars: rangeToParam(range),
115-
}).promise.then(response => {
116-
if (response.type !== 'SUCCESS') {
117-
return
118-
}
123+
const promise = basic(
124+
text,
125+
{
126+
vars: rangeToParam(range),
127+
},
128+
{rawBlob: true}
129+
)
130+
setCancelId(promise.id)
119131

120-
downloadTextFile(response.csv, 'influx.data', '.csv', 'text/csv')
121-
})
132+
const response = await promise.promise
133+
if (response.type !== 'SUCCESS') {
134+
return
135+
}
136+
setCancelId(null)
137+
downloadBlob(response.csv, 'influx.data', '.csv')
138+
event('CSV size', {size: bytesFormatter(response.bytesRead)})
139+
event('CSV Download End')
122140
}
123141

124142
const submit = useCallback(() => {
@@ -201,15 +219,27 @@ const ResultsPane: FC = () => {
201219
margin={ComponentSize.Small}
202220
>
203221
<QueryTime />
204-
<Button
205-
titleText="Download query results as a .CSV file"
206-
text="CSV"
207-
icon={IconFont.Download_New}
208-
onClick={download}
209-
status={
210-
text ? ComponentStatus.Default : ComponentStatus.Disabled
211-
}
212-
/>
222+
{csvDownloadCancelID == null ? (
223+
<Button
224+
titleText="Download query results as a .CSV file"
225+
text="CSV"
226+
icon={IconFont.Download_New}
227+
onClick={download}
228+
status={
229+
text ? ComponentStatus.Default : ComponentStatus.Disabled
230+
}
231+
testID="data-explorer--csv-download"
232+
/>
233+
) : (
234+
<Button
235+
text="Cancel"
236+
onClick={() => {
237+
cancel(csvDownloadCancelID)
238+
setCancelId(null)
239+
}}
240+
color={ComponentColor.Danger}
241+
/>
242+
)}
213243
{isFlagEnabled('newTimeRangeComponent') ? (
214244
<NewDatePicker />
215245
) : (

0 commit comments

Comments
 (0)