@@ -6,6 +6,7 @@ import { faBars } from '@fortawesome/free-solid-svg-icons/faBars';
6
6
import { buildRenderURL } from '@webapp/util/updateRequests' ;
7
7
import { dateForExportFilename } from '@webapp/util/formatDate' ;
8
8
import { Profile } from '@pyroscope/models' ;
9
+ import showModalWithInput from './Modals/ModalWithInput' ;
9
10
10
11
import styles from './ExportData.module.scss' ;
11
12
@@ -37,7 +38,7 @@ type exportHTML =
37
38
type exportFlamegraphDotCom =
38
39
| {
39
40
exportFlamegraphDotCom : true ;
40
- exportFlamegraphDotComFn : ( ) => Promise < string | null > ;
41
+ exportFlamegraphDotComFn : ( name ?: string ) => Promise < string | null > ;
41
42
flamebearer : Profile ;
42
43
}
43
44
| { exportFlamegraphDotCom ?: false } ;
@@ -75,19 +76,26 @@ function ExportData(props: ExportDataProps) {
75
76
76
77
const [ toggleMenu , setToggleMenu ] = useState ( false ) ;
77
78
78
- const downloadJSON = ( ) => {
79
+ const downloadJSON = async ( ) => {
79
80
if ( ! props . exportJSON ) {
80
81
return ;
81
82
}
82
83
83
84
// TODO additional check this won't be needed once we use strictNullChecks
84
85
if ( props . exportJSON ) {
85
86
const { flamebearer } = props ;
86
- const filename = `${ getFilename (
87
+
88
+ const defaultExportName = getFilename (
87
89
flamebearer . metadata . appName ,
88
90
flamebearer . metadata . startTime ,
89
91
flamebearer . metadata . endTime
90
- ) } .json`;
92
+ ) ;
93
+ // get user input from modal
94
+ const customExportName = await getCustomExportName ( defaultExportName ) ;
95
+ // return if user cancels the modal
96
+ if ( ! customExportName ) return ;
97
+
98
+ const filename = `${ customExportName } .json` ;
91
99
92
100
const dataStr = `data:text/json;charset=utf-8,${ encodeURIComponent (
93
101
JSON . stringify ( flamebearer )
@@ -101,14 +109,26 @@ function ExportData(props: ExportDataProps) {
101
109
}
102
110
} ;
103
111
104
- const downloadFlamegraphDotCom = ( ) => {
112
+ const downloadFlamegraphDotCom = async ( ) => {
105
113
if ( ! props . exportFlamegraphDotCom ) {
106
114
return ;
107
115
}
108
116
109
117
// TODO additional check this won't be needed once we use strictNullChecks
110
118
if ( props . exportFlamegraphDotCom ) {
111
- props . exportFlamegraphDotComFn ( ) . then ( ( url ) => {
119
+ const { flamebearer } = props ;
120
+
121
+ const defaultExportName = getFilename (
122
+ flamebearer . metadata . appName ,
123
+ flamebearer . metadata . startTime ,
124
+ flamebearer . metadata . endTime
125
+ ) ;
126
+ // get user input from modal
127
+ const customExportName = await getCustomExportName ( defaultExportName ) ;
128
+ // return if user cancels the modal
129
+ if ( ! customExportName ) return ;
130
+
131
+ props . exportFlamegraphDotComFn ( customExportName ) . then ( ( url ) => {
112
132
// there has been an error which should've been handled
113
133
// so we just ignore it
114
134
if ( ! url ) {
@@ -126,9 +146,22 @@ function ExportData(props: ExportDataProps) {
126
146
}
127
147
} ;
128
148
129
- const downloadPNG = ( ) => {
149
+ const downloadPNG = async ( ) => {
130
150
if ( props . exportPNG ) {
131
151
const { flamebearer } = props ;
152
+
153
+ const defaultExportName = getFilename (
154
+ flamebearer . metadata . appName ,
155
+ flamebearer . metadata . startTime ,
156
+ flamebearer . metadata . endTime
157
+ ) ;
158
+ // get user input from modal
159
+ const customExportName = await getCustomExportName ( defaultExportName ) ;
160
+ // return if user cancels the modal
161
+ if ( ! customExportName ) return ;
162
+
163
+ const filename = `${ customExportName } .png` ;
164
+
132
165
const mimeType = 'png' ;
133
166
// TODO use ref
134
167
// this won't work for comparison side by side
@@ -138,11 +171,6 @@ function ExportData(props: ExportDataProps) {
138
171
const MIME_TYPE = `image/${ mimeType } ` ;
139
172
const imgURL = canvasElement . toDataURL ( ) ;
140
173
const dlLink = document . createElement ( 'a' ) ;
141
- const filename = `${ getFilename (
142
- flamebearer . metadata . appName ,
143
- flamebearer . metadata . startTime ,
144
- flamebearer . metadata . endTime
145
- ) } .png`;
146
174
147
175
dlLink . download = filename ;
148
176
dlLink . href = imgURL ;
@@ -202,7 +230,7 @@ function ExportData(props: ExportDataProps) {
202
230
}
203
231
} ;
204
232
205
- const downloadHTML = function ( ) {
233
+ const downloadHTML = async function ( ) {
206
234
if ( props . exportHTML ) {
207
235
const { flamebearer } = props ;
208
236
@@ -227,11 +255,18 @@ function ExportData(props: ExportDataProps) {
227
255
maxNodes : flamebearer . metadata . maxNodes ,
228
256
} ) ;
229
257
const urlWithFormat = `${ url } &format=html` ;
230
- const filename = `${ getFilename (
258
+
259
+ const defaultExportName = getFilename (
231
260
flamebearer . metadata . appName ,
232
261
flamebearer . metadata . startTime ,
233
262
flamebearer . metadata . endTime
234
- ) } .html`;
263
+ ) ;
264
+ // get user input from modal
265
+ const customExportName = await getCustomExportName ( defaultExportName ) ;
266
+ // return if user cancels the modal
267
+ if ( ! customExportName ) return ;
268
+
269
+ const filename = `${ customExportName } .html` ;
235
270
236
271
const downloadAnchorNode = document . createElement ( 'a' ) ;
237
272
downloadAnchorNode . setAttribute ( 'href' , urlWithFormat ) ;
@@ -242,15 +277,28 @@ function ExportData(props: ExportDataProps) {
242
277
}
243
278
} ;
244
279
280
+ async function getCustomExportName ( defaultExportName : string ) {
281
+ return showModalWithInput ( {
282
+ title : 'Enter export name' ,
283
+ confirmButtonText : 'Export' ,
284
+ input : 'text' ,
285
+ inputValue : defaultExportName ,
286
+ inputPlaceholder : 'Export name' ,
287
+ type : 'normal' ,
288
+ validationMessage : 'Name must not be empty' ,
289
+ onConfirm : ( value : any ) => value ,
290
+ } ) ;
291
+ }
292
+
245
293
return (
246
294
< div className = { styles . dropdownContainer } >
247
295
< Button icon = { faBars } onClick = { handleToggleMenu } />
248
296
< div className = { toggleMenu ? styles . menuShow : styles . menuHide } >
249
297
{ exportPNG && (
250
298
< button
251
299
className = { styles . dropdownMenuItem }
252
- onClick = { ( ) => downloadPNG ( ) }
253
- onKeyPress = { ( ) => downloadPNG ( ) }
300
+ onClick = { downloadPNG }
301
+ onKeyPress = { downloadPNG }
254
302
type = "button"
255
303
>
256
304
png
@@ -260,7 +308,7 @@ function ExportData(props: ExportDataProps) {
260
308
< button
261
309
className = { styles . dropdownMenuItem }
262
310
type = "button"
263
- onClick = { ( ) => downloadJSON ( ) }
311
+ onClick = { downloadJSON }
264
312
>
265
313
json
266
314
</ button >
@@ -269,7 +317,7 @@ function ExportData(props: ExportDataProps) {
269
317
< button
270
318
className = { styles . dropdownMenuItem }
271
319
type = "button"
272
- onClick = { ( ) => downloadPprof ( ) }
320
+ onClick = { downloadPprof }
273
321
>
274
322
pprof
275
323
</ button >
@@ -278,7 +326,7 @@ function ExportData(props: ExportDataProps) {
278
326
< button
279
327
className = { styles . dropdownMenuItem }
280
328
type = "button"
281
- onClick = { ( ) => downloadHTML ( ) }
329
+ onClick = { downloadHTML }
282
330
>
283
331
{ ' ' }
284
332
html
@@ -288,7 +336,7 @@ function ExportData(props: ExportDataProps) {
288
336
< button
289
337
className = { styles . dropdownMenuItem }
290
338
type = "button"
291
- onClick = { ( ) => downloadFlamegraphDotCom ( ) }
339
+ onClick = { downloadFlamegraphDotCom }
292
340
>
293
341
{ ' ' }
294
342
flamegraph.com
0 commit comments