Skip to content

Commit d97d6ab

Browse files
committed
#RI-4840 - [Regression] Profiler logs download failure
1 parent 5da5a7e commit d97d6ab

File tree

4 files changed

+81
-4
lines changed

4 files changed

+81
-4
lines changed

redisinsight/api/src/modules/profiler/profiler.controller.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export class ProfilerController {
2424

2525
res.setHeader('Content-Type', 'application/octet-stream');
2626
res.setHeader('Content-Disposition', `attachment;filename="${filename}.txt"`);
27+
res.setHeader('Access-Control-Expose-Headers', 'Content-Disposition');
2728

2829
stream
2930
.on('error', () => res.status(404).send())

redisinsight/ui/src/components/monitor/MonitorLog/MonitorLog.spec.tsx

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,47 @@
11
import { cloneDeep } from 'lodash'
22
import React from 'react'
3-
import { resetProfiler, stopMonitor } from 'uiSrc/slices/cli/monitor'
3+
import { monitorSelector, resetProfiler, stopMonitor } from 'uiSrc/slices/cli/monitor'
44
import { cleanup, fireEvent, mockedStore, render, screen } from 'uiSrc/utils/test-utils'
55
import MonitorLog from './MonitorLog'
66

77
let store: typeof mockedStore
8+
let URLMock: jest.SpyInstance<object>
9+
const mockURLrevokeObjectURL = 123123
10+
11+
jest.mock('uiSrc/slices/cli/monitor', () => ({
12+
...jest.requireActual('uiSrc/slices/cli/monitor'),
13+
monitorSelector: jest.fn().mockReturnValue({
14+
isSaveToFile: false,
15+
logFileId: 'logFileId',
16+
timestamp: {
17+
start: 1,
18+
paused: 2,
19+
unPaused: 3,
20+
duration: 123,
21+
}
22+
}),
23+
}))
24+
25+
global.fetch = jest.fn(() =>
26+
Promise.resolve({
27+
text: () => Promise.resolve('123'),
28+
headers: {
29+
get: () => '123"filename.txt"oeu',
30+
}
31+
}))
832

933
beforeEach(() => {
1034
cleanup()
1135
store = cloneDeep(mockedStore)
1236
store.clearActions()
37+
fetch.mockClear()
1338
})
1439

1540
describe('MonitorLog', () => {
41+
beforeAll(() => {
42+
URLMock = jest.spyOn(URL, 'revokeObjectURL').mockImplementation(() => mockURLrevokeObjectURL)
43+
})
44+
1645
it('should render', () => {
1746
expect(render(<MonitorLog />)).toBeTruthy()
1847
})
@@ -24,4 +53,23 @@ describe('MonitorLog', () => {
2453
const expectedActions = [stopMonitor(), resetProfiler()]
2554
expect(store.getActions()).toEqual(expectedActions)
2655
})
56+
57+
it.skip('should call download a file', () => {
58+
const monitorSelectorMock = jest.fn().mockReturnValue({
59+
isSaveToFile: true,
60+
logFileId: 'logFileId',
61+
timestamp: {
62+
start: 1,
63+
paused: 2,
64+
unPaused: 3,
65+
duration: 123,
66+
}
67+
});
68+
(monitorSelector as jest.Mock).mockImplementation(monitorSelectorMock)
69+
70+
render(<MonitorLog />)
71+
fireEvent.click(screen.getByTestId('download-log-btn'))
72+
73+
expect(URLMock).toBeCalled()
74+
})
2775
})

redisinsight/ui/src/components/monitor/MonitorLog/MonitorLog.tsx

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import AutoSizer from 'react-virtualized-auto-sizer'
66
import { ApiEndpoints } from 'uiSrc/constants'
77
import { monitorSelector, resetProfiler, stopMonitor } from 'uiSrc/slices/cli/monitor'
88
import { cutDurationText, getBaseApiUrl } from 'uiSrc/utils'
9+
import { CustomHeaders } from 'uiSrc/constants/api'
910

1011
import styles from './styles.module.scss'
1112

@@ -26,8 +27,6 @@ const MonitorLog = () => {
2627
})
2728
)
2829
)
29-
const baseApiUrl = getBaseApiUrl()
30-
const linkToDownload = `${baseApiUrl}/api/${ApiEndpoints.PROFILER_LOGS}/${logFileId}`
3130

3231
const downloadBtnProps: any = {
3332
target: DOWNLOAD_IFRAME_NAME
@@ -44,6 +43,31 @@ const MonitorLog = () => {
4443
return 18
4544
}
4645

46+
const handleDownloadLog = async (e: React.MouseEvent<HTMLAnchorElement>) => {
47+
e.preventDefault()
48+
const baseApiUrl = getBaseApiUrl()
49+
const linkToDownload = `${baseApiUrl}/api/${ApiEndpoints.PROFILER_LOGS}/${logFileId}`
50+
51+
const response = await fetch(
52+
linkToDownload,
53+
{ headers: { [CustomHeaders.WindowId]: window.windowId || '' } },
54+
)
55+
56+
const contentDisposition = response.headers.get('Content-Disposition') || ''
57+
58+
downloadFile(await response.text(), contentDisposition.split('"')?.[1])
59+
}
60+
61+
const downloadFile = (content: string, fileName: string) => {
62+
const link = document.createElement('a')
63+
const file = new Blob([content], { type: 'text/plain' })
64+
link.href = URL.createObjectURL(file)
65+
link.download = fileName
66+
link.click()
67+
68+
URL.revokeObjectURL(link.href)
69+
}
70+
4771
return (
4872
<div className={styles.monitorLogWrapper}>
4973
<iframe title="downloadIframeTarget" name={DOWNLOAD_IFRAME_NAME} style={{ display: 'none' }} />
@@ -76,10 +100,10 @@ const MonitorLog = () => {
76100
<EuiButton
77101
size="s"
78102
color="secondary"
79-
href={linkToDownload}
80103
iconType="download"
81104
className={styles.btn}
82105
data-testid="download-log-btn"
106+
onClick={handleDownloadLog}
83107
{...downloadBtnProps}
84108
>
85109
{width > SMALL_SCREEN_RESOLUTION && ' Download '}

redisinsight/ui/src/setup-tests.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ import 'whatwg-fetch'
33

44
import { mswServer } from 'uiSrc/mocks/server'
55

6+
export const URL = 'URL'
7+
window.URL.revokeObjectURL = () => {}
8+
window.URL.createObjectURL = () => URL
9+
610
beforeAll(() => {
711
mswServer.listen()
812
})

0 commit comments

Comments
 (0)