Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,22 @@ describe('CodeButtonBlock', () => {
expect(onApply).toBeCalledWith({ pipeline: '10' }, expect.any(Function))
})

it('should not render run button with executable=false param', () => {
const onApply = jest.fn()

render(
<CodeButtonBlock
{...instance(mockedProps)}
label={label}
onApply={onApply}
params={{ executable: 'false' }}
content={simpleContent}
/>
)

expect(screen.queryByTestId(`run-btn-${label}`)).not.toBeInTheDocument()
})

it('should go to home page after click on change db', async () => {
const pushMock = jest.fn()
reactRouterDom.useHistory = jest.fn().mockReturnValue({ push: pushMock })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ const CodeButtonBlock = (props: Props) => {
ConfigDBStorageItem.notShowConfirmationRunTutorial
)
const isButtonHasConfirmation = params?.run_confirmation === BooleanParams.true
const isRunButtonHidden = params?.executable === BooleanParams.false
const [notLoadedModule] = getUnsupportedModulesFromQuery(modules, content)

useEffect(() => {
Expand Down Expand Up @@ -134,44 +135,46 @@ const CodeButtonBlock = (props: Props) => {
>
Copy
</EuiButton>
<EuiPopover
ownFocus
initialFocus={false}
className={styles.popoverAnchor}
panelClassName={cx('euiToolTip', 'popoverLikeTooltip', styles.popover)}
anchorClassName={styles.popoverAnchor}
anchorPosition="upLeft"
isOpen={isPopoverOpen}
panelPaddingSize="m"
closePopover={handleClosePopover}
focusTrapProps={{
scrollLock: true
}}
button={(
<EuiToolTip
anchorClassName={styles.popoverAnchor}
content={isPopoverOpen ? undefined : 'Open Workbench in the left menu to see the command results.'}
data-testid="run-btn-open-workbench-tooltip"
>
<EuiButton
onClick={handleRunClicked}
iconType={isRunned ? 'check' : 'play'}
iconSide="right"
color="success"
size="s"
disabled={isLoading || isRunned}
isLoading={isLoading}
className={cx(styles.actionBtn, styles.runBtn)}
{...rest}
data-testid={`run-btn-${label}`}
{!isRunButtonHidden && (
<EuiPopover
ownFocus
initialFocus={false}
className={styles.popoverAnchor}
panelClassName={cx('euiToolTip', 'popoverLikeTooltip', styles.popover)}
anchorClassName={styles.popoverAnchor}
anchorPosition="upLeft"
isOpen={isPopoverOpen}
panelPaddingSize="m"
closePopover={handleClosePopover}
focusTrapProps={{
scrollLock: true
}}
button={(
<EuiToolTip
anchorClassName={styles.popoverAnchor}
content={isPopoverOpen ? undefined : 'Open Workbench in the left menu to see the command results.'}
data-testid="run-btn-open-workbench-tooltip"
>
Run
</EuiButton>
</EuiToolTip>
)}
>
{getPopoverMessage()}
</EuiPopover>
<EuiButton
onClick={handleRunClicked}
iconType={isRunned ? 'check' : 'play'}
iconSide="right"
color="success"
size="s"
disabled={isLoading || isRunned}
isLoading={isLoading}
className={cx(styles.actionBtn, styles.runBtn)}
{...rest}
data-testid={`run-btn-${label}`}
>
Run
</EuiButton>
</EuiToolTip>
)}
>
{getPopoverMessage()}
</EuiPopover>
)}
</EuiFlexItem>
</EuiFlexGroup>
<div className={styles.content} data-testid="code-button-block-content">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import React from 'react'
import { cloneDeep } from 'lodash'
import reactRouterDom from 'react-router-dom'
import { cleanup, fireEvent, mockedStore, render, screen } from 'uiSrc/utils/test-utils'
import { AxiosError } from 'axios'
import { cleanup, fireEvent, mockedStore, render, screen, act } from 'uiSrc/utils/test-utils'
import { customTutorialsBulkUploadSelector, uploadDataBulk } from 'uiSrc/slices/workbench/wb-custom-tutorials'

import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry'
import { checkResourse } from 'uiSrc/services/resourcesService'
import { addErrorNotification } from 'uiSrc/slices/app/notifications'
import RedisUploadButton, { Props } from './RedisUploadButton'

jest.mock('uiSrc/slices/workbench/wb-custom-tutorials', () => ({
Expand All @@ -14,6 +17,11 @@ jest.mock('uiSrc/slices/workbench/wb-custom-tutorials', () => ({
}),
}))

jest.mock('uiSrc/services/resourcesService', () => ({
...jest.requireActual('uiSrc/services/resourcesService'),
checkResourse: jest.fn(),
}))

jest.mock('uiSrc/telemetry', () => ({
...jest.requireActual('uiSrc/telemetry'),
sendEventTelemetry: jest.fn(),
Expand All @@ -31,6 +39,10 @@ const props: Props = {
path: '/text'
}

const error = {
response: { data: { message: 'File not found. Check if this file exists and try again.' } }
} as AxiosError<any>

describe('RedisUploadButton', () => {
beforeEach(() => {
reactRouterDom.useParams = jest.fn().mockReturnValue({ instanceId: 'instanceId' })
Expand Down Expand Up @@ -72,7 +84,22 @@ describe('RedisUploadButton', () => {
expect(screen.getByTestId('database-not-opened-popover')).toBeInTheDocument()
})

it('should call proper telemetry events', () => {
it('should show error when file is not exists', async () => {
const checkResourseMock = jest.fn().mockRejectedValue('');
(checkResourse as jest.Mock).mockImplementation(checkResourseMock)

render(<RedisUploadButton {...props} />)

fireEvent.click(screen.getByTestId('upload-data-bulk-btn'))
await act(() => {
fireEvent.click(screen.getByTestId('download-redis-upload-file'))
})

expect(checkResourseMock).toBeCalledWith('http://localhost:5001/text')
expect(store.getActions()).toEqual([addErrorNotification(error)])
})

it('should call proper telemetry events', async () => {
const sendEventTelemetryMock = jest.fn();
(sendEventTelemetry as jest.Mock).mockImplementation(() => sendEventTelemetryMock)
render(<RedisUploadButton {...props} />)
Expand All @@ -88,6 +115,19 @@ describe('RedisUploadButton', () => {

(sendEventTelemetry as jest.Mock).mockRestore()

await act(() => {
fireEvent.click(screen.getByTestId('download-redis-upload-file'))
})

expect(sendEventTelemetry).toBeCalledWith({
event: TelemetryEvent.EXPLORE_PANEL_DOWNLOAD_BULK_FILE_CLICKED,
eventData: {
databaseId: 'instanceId'
}
});

(sendEventTelemetry as jest.Mock).mockRestore()

fireEvent.click(screen.getByTestId('upload-data-bulk-apply-btn'))

expect(sendEventTelemetry).toBeCalledWith({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import { EuiButton, EuiIcon, EuiPopover, EuiSpacer, EuiText } from '@elastic/eui'
import { EuiButton, EuiIcon, EuiLink, EuiPopover, EuiSpacer, EuiText } from '@elastic/eui'
import { useDispatch, useSelector } from 'react-redux'
import React, { useEffect, useState } from 'react'
import cx from 'classnames'
import { useParams } from 'react-router-dom'
import { AxiosError } from 'axios'
import { truncateText } from 'uiSrc/utils'
import { sendEventTelemetry, TELEMETRY_EMPTY_VALUE, TelemetryEvent } from 'uiSrc/telemetry'
import { customTutorialsBulkUploadSelector, uploadDataBulkAction } from 'uiSrc/slices/workbench/wb-custom-tutorials'

import { ReactComponent as BulkDataUploadIcon } from 'uiSrc/assets/img/icons/data-upload-bulk.svg'
import DatabaseNotOpened from 'uiSrc/components/messages/database-not-opened'

import { checkResourse, getPathToResource } from 'uiSrc/services/resourcesService'
import { addErrorNotification } from 'uiSrc/slices/app/notifications'
import styles from './styles.module.scss'

export interface Props {
Expand All @@ -26,6 +28,8 @@ const RedisUploadButton = ({ label, path }: Props) => {
const dispatch = useDispatch()
const { instanceId } = useParams<{ instanceId: string }>()

const urlToFile = getPathToResource(path)

useEffect(() => {
setIsLoading(pathsInProgress.includes(path))
}, [pathsInProgress])
Expand Down Expand Up @@ -54,20 +58,48 @@ const RedisUploadButton = ({ label, path }: Props) => {
})
}

const handleDownload = async (e: React.MouseEvent) => {
e.preventDefault()

try {
await checkResourse(urlToFile)

const downloadAnchor = document.createElement('a')
downloadAnchor.setAttribute('href', `${urlToFile}?download=true`)
downloadAnchor.setAttribute('download', label)
downloadAnchor.click()
} catch {
const error = {
response: { data: { message: 'File not found. Check if this file exists and try again.' } }
} as AxiosError<any>
dispatch(addErrorNotification(error))
}

sendEventTelemetry({
event: TelemetryEvent.EXPLORE_PANEL_DOWNLOAD_BULK_FILE_CLICKED,
eventData: {
databaseId: instanceId
}
})
}

return (
<div className={cx(styles.wrapper, 'mb-s mt-s')}>
<EuiPopover
ownFocus
initialFocus={false}
id="upload-data-bulk-btn"
anchorPosition="downLeft"
isOpen={isPopoverOpen}
closePopover={() => setIsPopoverOpen(false)}
panelClassName={instanceId ? styles.panelPopover : cx('euiToolTip', 'popoverLikeTooltip', styles.popover)}
panelClassName={cx('euiToolTip', 'popoverLikeTooltip', styles.popover)}
anchorClassName={styles.popoverAnchor}
panelPaddingSize="none"
button={(
<EuiButton
isLoading={isLoading}
iconType={BulkDataUploadIcon}
iconSide="right"
iconType="indexRuntime"
size="s"
className={styles.button}
onClick={openPopover}
Expand All @@ -93,16 +125,24 @@ const RedisUploadButton = ({ label, path }: Props) => {
All commands from the file in your tutorial will be automatically executed against your database.
Avoid executing them in production databases.
</div>
<EuiButton
fill
size="s"
color="secondary"
className={styles.uploadApproveBtn}
onClick={uploadData}
data-testid="upload-data-bulk-apply-btn"
>
Execute
</EuiButton>
<EuiSpacer size="m" />
<div className={styles.popoverActions}>
<EuiLink onClick={handleDownload} className={styles.link} data-testid="download-redis-upload-file">
Download file
</EuiLink>
<EuiButton
fill
size="s"
color="secondary"
iconType="playFilled"
iconSide="right"
className={styles.uploadApproveBtn}
onClick={uploadData}
data-testid="upload-data-bulk-apply-btn"
>
Execute
</EuiButton>
</div>
</EuiText>
) : (<DatabaseNotOpened />)}
</EuiPopover>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
.wrapper {
width: 100%;
max-width: 480px;

:global(.euiPopover) {
width: 100%;
Expand All @@ -16,13 +17,12 @@
&:global(.euiButton.euiButton-isDisabled) {
color: var(--buttonSecondaryDisabledTextColor) !important;
}
}
}

.containerPopover {
padding: 18px;
width: 360px;
height: 188px;
:global(.euiIcon) {
width: 14px;
height: 14px;
}
}
}

.popover {
Expand All @@ -47,25 +47,34 @@

.popoverIcon {
position: absolute;
top: 14px;
left: 14px;

color: var(--euiColorWarningLight) !important;
width: 24px !important;
height: 24px !important;
width: 18px !important;
height: 18px !important;
}

.popoverItem {
font-size: 13px !important;
line-height: 18px !important;
padding-left: 34px;
padding-left: 30px;
}

.link {
color: var(--externalLinkColor) !important;
}

.popoverActions {
display: flex;
align-items: center;
justify-content: space-between;

padding-left: 30px;
}

.popoverItemTitle {
color: var(--htmlColor) !important;
font-size: 14px !important;
line-height: 24px !important;
}

.uploadApproveBtn {
position: absolute;
right: 18px;
bottom: 18px;
}
1 change: 1 addition & 0 deletions redisinsight/ui/src/constants/workbench.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export interface CodeButtonParams {
results?: keyof typeof CodeButtonResults
mode?: keyof typeof CodeButtonRunQueryMode
run_confirmation?: keyof typeof BooleanParams
executable?: keyof typeof BooleanParams
}

export enum ExecuteButtonMode {
Expand Down
2 changes: 2 additions & 0 deletions redisinsight/ui/src/services/resourcesService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,6 @@ export const getPathToResource = (url: string = ''): string => (IS_ABSOLUTE_PATH
? url
: new URL(url, resourcesService.defaults.baseURL).toString())

export const checkResourse = async (url: string = '') => resourcesService.head(url)

export default resourcesService
Loading