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 @@ -92,31 +92,25 @@ describe('ImportDatabasesDialog', () => {
expect(screen.getByTestId('file-loading-indicator')).toBeInTheDocument()
})

it('should render success message when at least 1 database added', () => {
it('should not render error message without error', () => {
(importInstancesSelector as jest.Mock).mockImplementation(() => ({
loading: false,
data: {
success: 1,
total: 2
}
data: {}
}))

render(<ImportDatabasesDialog onClose={jest.fn()} />)
expect(screen.getByTestId('result-success')).toBeInTheDocument()
expect(screen.queryByTestId('result-failed')).not.toBeInTheDocument()
})

it('should render error message when 0 success databases added', () => {
(importInstancesSelector as jest.Mock).mockImplementation(() => ({
loading: false,
data: {
success: 0,
total: 2
}
data: null,
error: 'Error message'
}))

render(<ImportDatabasesDialog onClose={jest.fn()} />)
expect(screen.getByTestId('result-failed')).toBeInTheDocument()
expect(screen.queryByTestId('result-success')).not.toBeInTheDocument()
expect(screen.getByTestId('result-failed')).toHaveTextContent('Error message')
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
} from 'uiSrc/slices/instances/instances'
import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry'
import { Nullable } from 'uiSrc/utils'
import ResultsLog from './components/ResultsLog'

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

Expand All @@ -49,9 +50,11 @@ const ImportDatabasesDialog = ({ onClose }: Props) => {
}

const handleOnClose = () => {
dispatch(resetImportInstances())
data?.success && dispatch(fetchInstancesAction())
if (data?.success?.length || data?.partial?.length) {
dispatch(fetchInstancesAction())
}
onClose(!data)
dispatch(resetImportInstances())
}

const onSubmit = () => {
Expand All @@ -72,17 +75,17 @@ const ImportDatabasesDialog = ({ onClose }: Props) => {
<EuiModal onClose={handleOnClose} className={styles.modal} data-testid="import-dbs-dialog">
<EuiModalHeader>
<EuiModalHeaderTitle>
<EuiTitle size="xs">
<span>Import Database Connections</span>
<EuiTitle size="xs" data-testid="import-dbs-dialog-title">
<span>{(!data && !error) ? 'Import Database Connections' : 'Import Results'}</span>
</EuiTitle>
</EuiModalHeaderTitle>
</EuiModalHeader>

<EuiModalBody>
<EuiFlexGroup justifyContent="center" gutterSize="none" responsive={false}>
<EuiFlexItem grow={false}>
<EuiFlexItem grow={!!data} style={{ maxWidth: '100%' }}>
{isShowForm && (
<>
<EuiFlexItem>
<EuiFilePicker
id="import-databases-input-file"
initialPromptText="Select or drag and drop a file"
Expand All @@ -98,7 +101,7 @@ const ImportDatabasesDialog = ({ onClose }: Props) => {
File should not exceed {MAX_MB_FILE} MB
</EuiTextColor>
)}
</>
</EuiFlexItem>
)}

{loading && (
Expand All @@ -107,29 +110,21 @@ const ImportDatabasesDialog = ({ onClose }: Props) => {
<EuiText color="subdued" style={{ marginTop: 12 }}>Uploading...</EuiText>
</div>
)}

{data && data.success !== 0 && (
<div className={styles.result} data-testid="result-success">
<EuiIcon type="checkInCircleFilled" size="xxl" color="success" />
<EuiText color="subdued" style={{ marginTop: 12 }}>
Successfully added {data.success} of {data.total} database connections
</EuiText>
</div>
)}

{(data?.success === 0 || error) && (
{data && (<ResultsLog data={data} />)}
{error && (
<div className={styles.result} data-testid="result-failed">
<EuiIcon type="crossInACircleFilled" size="xxl" color="danger" />
<EuiText color="subdued" style={{ marginTop: 12 }}>
<EuiText color="subdued" style={{ marginTop: 16 }}>
Failed to add database connections
</EuiText>
<EuiText color="subdued">{error}</EuiText>
</div>
)}
</EuiFlexItem>
</EuiFlexGroup>
</EuiModalBody>

{data && data.success !== 0 && (
{data && (
<EuiModalFooter>
<EuiButton
color="secondary"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import React from 'react'
import { render, screen, fireEvent, within } from 'uiSrc/utils/test-utils'
import { ImportDatabasesData } from 'uiSrc/slices/interfaces'
import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry'
import ResultsLog from './ResultsLog'

jest.mock('uiSrc/telemetry', () => ({
...jest.requireActual('uiSrc/telemetry'),
sendEventTelemetry: jest.fn(),
}))

const mockedError = { statusCode: 400, message: 'message', error: 'error' }
describe('ResultsLog', () => {
it('should render', () => {
const mockedData = { total: 0, fail: [], partial: [], success: [] }
render(<ResultsLog data={mockedData} />)
})

it('should be all collapsed nav groups', () => {
const mockedData: ImportDatabasesData = {
total: 3,
fail: [{ index: 0, status: 'fail', errors: [mockedError] }],
partial: [{ index: 2, status: 'fail', errors: [mockedError] }],
success: [{ index: 1, status: 'success', port: 1233, host: 'localhost' }]
}
render(<ResultsLog data={mockedData} />)

expect(screen.getByTestId('success-results-closed')).toBeInTheDocument()
expect(screen.getByTestId('partial-results-closed')).toBeInTheDocument()
expect(screen.getByTestId('failed-results-closed')).toBeInTheDocument()
})

it('should open and collapse other groups', () => {
const mockedData: ImportDatabasesData = {
total: 3,
fail: [{ index: 0, status: 'fail', errors: [mockedError] }],
partial: [{ index: 2, status: 'fail', errors: [mockedError] }],
success: [{ index: 1, status: 'success', port: 1233, host: 'localhost' }]
}
render(<ResultsLog data={mockedData} />)

fireEvent.click(
within(screen.getByTestId('success-results-closed')).getByRole('button')
)
expect(screen.getByTestId('success-results-open')).toBeInTheDocument()

expect(screen.getByTestId('partial-results-closed')).toBeInTheDocument()
expect(screen.getByTestId('failed-results-closed')).toBeInTheDocument()

fireEvent.click(
within(screen.getByTestId('failed-results-closed')).getByRole('button')
)
expect(screen.getByTestId('failed-results-open')).toBeInTheDocument()

expect(screen.getByTestId('partial-results-closed')).toBeInTheDocument()
expect(screen.getByTestId('success-results-closed')).toBeInTheDocument()

fireEvent.click(
within(screen.getByTestId('partial-results-closed')).getByRole('button')
)
expect(screen.getByTestId('partial-results-open')).toBeInTheDocument()

expect(screen.getByTestId('failed-results-closed')).toBeInTheDocument()
expect(screen.getByTestId('success-results-closed')).toBeInTheDocument()
})

it('should show proper items length', () => {
const mockedData: ImportDatabasesData = {
total: 4,
fail: [{ index: 0, status: 'fail', errors: [mockedError] }],
partial: [{ index: 2, status: 'fail', errors: [mockedError] }],
success: [
{ index: 1, status: 'success', port: 1233, host: 'localhost' },
{ index: 3, status: 'success', port: 1233, host: 'localhost' }
]
}
render(<ResultsLog data={mockedData} />)

expect(
within(screen.getByTestId('success-results-closed')).getByTestId('number-of-dbs')
).toHaveTextContent('2')
expect(
within(screen.getByTestId('partial-results-closed')).getByTestId('number-of-dbs')
).toHaveTextContent('1')
expect(
within(screen.getByTestId('failed-results-closed')).getByTestId('number-of-dbs')
).toHaveTextContent('1')
})

it('should call proper telemetry event after click', () => {
const sendEventTelemetryMock = jest.fn();
(sendEventTelemetry as jest.Mock).mockImplementation(() => sendEventTelemetryMock)

const mockedData: ImportDatabasesData = {
total: 3,
fail: [{ index: 0, status: 'fail', errors: [mockedError] }],
partial: [{ index: 2, status: 'fail', errors: [mockedError] }],
success: [{ index: 1, status: 'success', port: 1233, host: 'localhost' }]
}
render(<ResultsLog data={mockedData} />)

fireEvent.click(
within(screen.getByTestId('success-results-closed')).getByRole('button')
)

expect(sendEventTelemetry).toBeCalledWith({
event: TelemetryEvent.CONFIG_DATABASES_REDIS_IMPORT_LOG_VIEWED,
eventData: {
length: 1,
name: 'success'
}
});

(sendEventTelemetry as jest.Mock).mockRestore()

fireEvent.click(
within(screen.getByTestId('success-results-open')).getByRole('button')
)

expect(sendEventTelemetry).not.toBeCalled()
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { EuiCollapsibleNavGroup } from '@elastic/eui'
import cx from 'classnames'
import React, { useState } from 'react'

import { ImportDatabasesData } from 'uiSrc/slices/interfaces'
import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry'
import TableResult from '../TableResult'

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

enum ResultsStatus {
Success = 'success',
Partial = 'partial',
Failed = 'failed'
}

export interface Props {
data: ImportDatabasesData
}

const ResultsLog = ({ data }: Props) => {
const [openedNav, setOpenedNav] = useState<string>('')

const onToggle = (length: number = 0, isOpen: boolean, name: string) => {
if (length === 0) return
setOpenedNav(isOpen ? name : '')

if (isOpen) {
sendEventTelemetry({
event: TelemetryEvent.CONFIG_DATABASES_REDIS_IMPORT_LOG_VIEWED,
eventData: {
length,
name
}
})
}
}

const CollapsibleNavTitle = ({ title, length = 0 }: { title: string, length: number }) => (
<div className={styles.collapsibleNavTitle}>
<span data-testid="nav-group-title">{title}</span>
<span data-testid="number-of-dbs">{length}</span>
</div>
)

const getNavGroupState = (name: ResultsStatus) => (openedNav === name ? 'open' : 'closed')

return (
<>
<EuiCollapsibleNavGroup
title={<CollapsibleNavTitle title="Fully imported" length={data?.success?.length} />}
className={cx(styles.collapsibleNav, ResultsStatus.Success, { [styles.disabled]: !data?.success?.length })}
isCollapsible
initialIsOpen={false}
onToggle={(isOpen) => onToggle(data?.success?.length, isOpen, ResultsStatus.Success)}
forceState={getNavGroupState(ResultsStatus.Success)}
data-testid={`success-results-${getNavGroupState(ResultsStatus.Success)}`}
>
<TableResult data={data?.success ?? []} />
</EuiCollapsibleNavGroup>
<EuiCollapsibleNavGroup
title={<CollapsibleNavTitle title="Partially imported" length={data?.partial?.length} />}
className={cx(styles.collapsibleNav, ResultsStatus.Partial, { [styles.disabled]: !data?.partial?.length })}
isCollapsible
initialIsOpen={false}
onToggle={(isOpen) => onToggle(data?.partial?.length, isOpen, ResultsStatus.Partial)}
forceState={getNavGroupState(ResultsStatus.Partial)}
data-testid={`partial-results-${getNavGroupState(ResultsStatus.Partial)}`}
>
<TableResult data={data?.partial ?? []} />
</EuiCollapsibleNavGroup>
<EuiCollapsibleNavGroup
title={<CollapsibleNavTitle title="Failed to import" length={data?.fail?.length} />}
className={cx(styles.collapsibleNav, ResultsStatus.Failed, { [styles.disabled]: !data?.fail?.length })}
isCollapsible
initialIsOpen={false}
onToggle={(isOpen) => onToggle(data?.fail?.length, isOpen, ResultsStatus.Failed)}
forceState={getNavGroupState(ResultsStatus.Failed)}
data-testid={`failed-results-${getNavGroupState(ResultsStatus.Failed)}`}
>
<TableResult data={data?.fail ?? []} />
</EuiCollapsibleNavGroup>
</>
)
}

export default ResultsLog
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import ResultsLog from './ResultsLog'

export default ResultsLog
Loading