Skip to content

Commit 7b89ce1

Browse files
committed
#RI-3903 - add imports results
1 parent b774c0f commit 7b89ce1

File tree

11 files changed

+356
-26
lines changed

11 files changed

+356
-26
lines changed

redisinsight/ui/src/components/import-databases-dialog/ImportDatabasesDialog.tsx

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ import {
22
EuiButton,
33
EuiFilePicker,
44
EuiFlexGroup,
5-
EuiFlexItem,
6-
EuiIcon,
5+
EuiFlexItem, EuiIcon,
76
EuiLoadingSpinner,
87
EuiModal,
98
EuiModalBody,
@@ -24,6 +23,7 @@ import {
2423
} from 'uiSrc/slices/instances/instances'
2524
import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry'
2625
import { Nullable } from 'uiSrc/utils'
26+
import ResultsLog from './components/ResultsLog'
2727

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

@@ -50,7 +50,9 @@ const ImportDatabasesDialog = ({ onClose }: Props) => {
5050

5151
const handleOnClose = () => {
5252
dispatch(resetImportInstances())
53-
data?.success && dispatch(fetchInstancesAction())
53+
if (data?.success?.length || data?.partial?.length) {
54+
dispatch(fetchInstancesAction())
55+
}
5456
onClose(!data)
5557
}
5658

@@ -72,17 +74,17 @@ const ImportDatabasesDialog = ({ onClose }: Props) => {
7274
<EuiModal onClose={handleOnClose} className={styles.modal} data-testid="import-dbs-dialog">
7375
<EuiModalHeader>
7476
<EuiModalHeaderTitle>
75-
<EuiTitle size="xs">
76-
<span>Import Database Connections</span>
77+
<EuiTitle size="xs" data-testid="import-dbs-dialog-title">
78+
<span>{(!data && !error) ? 'Import Database Connections' : 'Import results'}</span>
7779
</EuiTitle>
7880
</EuiModalHeaderTitle>
7981
</EuiModalHeader>
8082

8183
<EuiModalBody>
8284
<EuiFlexGroup justifyContent="center" gutterSize="none" responsive={false}>
83-
<EuiFlexItem grow={false}>
85+
<EuiFlexItem grow={!!data} style={{ maxWidth: '100%' }}>
8486
{isShowForm && (
85-
<>
87+
<EuiFlexItem>
8688
<EuiFilePicker
8789
id="import-databases-input-file"
8890
initialPromptText="Select or drag and drop a file"
@@ -98,7 +100,7 @@ const ImportDatabasesDialog = ({ onClose }: Props) => {
98100
File should not exceed {MAX_MB_FILE} MB
99101
</EuiTextColor>
100102
)}
101-
</>
103+
</EuiFlexItem>
102104
)}
103105

104106
{loading && (
@@ -107,17 +109,8 @@ const ImportDatabasesDialog = ({ onClose }: Props) => {
107109
<EuiText color="subdued" style={{ marginTop: 12 }}>Uploading...</EuiText>
108110
</div>
109111
)}
110-
111-
{data && data.success !== 0 && (
112-
<div className={styles.result} data-testid="result-success">
113-
<EuiIcon type="checkInCircleFilled" size="xxl" color="success" />
114-
<EuiText color="subdued" style={{ marginTop: 12 }}>
115-
Successfully added {data.success} of {data.total} database connections
116-
</EuiText>
117-
</div>
118-
)}
119-
120-
{(data?.success === 0 || error) && (
112+
{data && (<ResultsLog data={data} />)}
113+
{error && (
121114
<div className={styles.result} data-testid="result-failed">
122115
<EuiIcon type="crossInACircleFilled" size="xxl" color="danger" />
123116
<EuiText color="subdued" style={{ marginTop: 12 }}>
@@ -129,7 +122,7 @@ const ImportDatabasesDialog = ({ onClose }: Props) => {
129122
</EuiFlexGroup>
130123
</EuiModalBody>
131124

132-
{data && data.success !== 0 && (
125+
{data && (
133126
<EuiModalFooter>
134127
<EuiButton
135128
color="secondary"
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { EuiCollapsibleNavGroup } from '@elastic/eui'
2+
import cx from 'classnames'
3+
import React, { useState } from 'react'
4+
5+
import { ImportDatabasesData } from 'uiSrc/slices/interfaces'
6+
import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry'
7+
import TableResult from '../TableResult'
8+
9+
import styles from './styles.module.scss'
10+
11+
enum ResultsStatus {
12+
Success = 'success',
13+
Partial = 'partial',
14+
Failed = 'failed'
15+
}
16+
17+
export interface Props {
18+
data: ImportDatabasesData
19+
}
20+
21+
const ResultsLog = ({ data }: Props) => {
22+
const [openedNav, setOpenedNav] = useState<string>('')
23+
24+
const onToggle = (length: number = 0, isOpen: boolean, name: string) => {
25+
if (length === 0) return
26+
setOpenedNav(isOpen ? name : '')
27+
28+
if (isOpen) {
29+
sendEventTelemetry({
30+
event: TelemetryEvent.CONFIG_DATABASES_REDIS_IMPORT_LOG_VIEWED,
31+
eventData: {
32+
length,
33+
name
34+
}
35+
})
36+
}
37+
}
38+
39+
const CollapsibleNavTitle = ({ title, length = 0 }: { title: string, length: number }) => (
40+
<div className={styles.collapsibleNavTitle}>
41+
<span data-testid="nav-group-title">{title}</span>
42+
<span data-testid="number-of-dbs">{length}</span>
43+
</div>
44+
)
45+
46+
const getNavGroupState = (name: ResultsStatus) => (openedNav === name ? 'open' : 'closed')
47+
48+
return (
49+
<>
50+
<EuiCollapsibleNavGroup
51+
title={<CollapsibleNavTitle title="Fully imported" length={data?.success?.length} />}
52+
className={cx(styles.collapsibleNav, ResultsStatus.Success, { [styles.disabled]: !data?.success?.length })}
53+
isCollapsible
54+
initialIsOpen={false}
55+
onToggle={(isOpen) => onToggle(data?.success?.length, isOpen, ResultsStatus.Success)}
56+
forceState={getNavGroupState(ResultsStatus.Success)}
57+
data-testid={`success-results-${getNavGroupState(ResultsStatus.Success)}`}
58+
>
59+
<TableResult data={data?.success ?? []} />
60+
</EuiCollapsibleNavGroup>
61+
<EuiCollapsibleNavGroup
62+
title={<CollapsibleNavTitle title="Partially imported" length={data?.partial?.length} />}
63+
className={cx(styles.collapsibleNav, ResultsStatus.Partial, { [styles.disabled]: !data?.partial?.length })}
64+
isCollapsible
65+
initialIsOpen={false}
66+
onToggle={(isOpen) => onToggle(data?.partial?.length, isOpen, ResultsStatus.Partial)}
67+
forceState={getNavGroupState(ResultsStatus.Partial)}
68+
data-testid={`partial-results-${getNavGroupState(ResultsStatus.Partial)}`}
69+
>
70+
<TableResult data={data?.partial ?? []} />
71+
</EuiCollapsibleNavGroup>
72+
<EuiCollapsibleNavGroup
73+
title={<CollapsibleNavTitle title="Failed to import" length={data?.fail?.length} />}
74+
className={cx(styles.collapsibleNav, ResultsStatus.Failed, { [styles.disabled]: !data?.fail?.length })}
75+
isCollapsible
76+
initialIsOpen={false}
77+
onToggle={(isOpen) => onToggle(data?.fail?.length, isOpen, ResultsStatus.Failed)}
78+
forceState={getNavGroupState(ResultsStatus.Failed)}
79+
data-testid={`failed-results-${getNavGroupState(ResultsStatus.Failed)}`}
80+
>
81+
<TableResult data={data?.fail ?? []} />
82+
</EuiCollapsibleNavGroup>
83+
</>
84+
)
85+
}
86+
87+
export default ResultsLog
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import ResultsLog from './ResultsLog'
2+
3+
export default ResultsLog
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
.collapsibleNav {
2+
width: 100%;
3+
margin-top: 5px !important;
4+
position: relative;
5+
6+
&.disabled {
7+
:global {
8+
.euiAccordion__button {
9+
cursor: auto;
10+
pointer-events: none;
11+
}
12+
.euiAccordion__iconWrapper {
13+
display: none;
14+
}
15+
}
16+
}
17+
18+
&:global(.euiAccordion-isOpen) {
19+
:global {
20+
.euiAccordion__triggerWrapper {
21+
border-radius: 0;
22+
border-color: transparent;
23+
}
24+
}
25+
}
26+
27+
&:global(.success) {
28+
&:before {
29+
background-color: #13A450;
30+
}
31+
}
32+
33+
&:global(.partial) {
34+
&:before {
35+
background-color: #9D6901;
36+
}
37+
}
38+
39+
&:global(.failed) {
40+
&:before {
41+
background-color: #AD0017;
42+
}
43+
}
44+
45+
&:before {
46+
content: '';
47+
position: absolute;
48+
left: 0;
49+
top: 0;
50+
bottom: 0;
51+
width: 4px;
52+
53+
border-radius: 4px 0 0 4px;
54+
55+
z-index: 2;
56+
}
57+
58+
:global {
59+
.euiCollapsibleNavGroup__title {
60+
font-size: 12px !important;
61+
font-weight: 400 !important;
62+
color: var(--euiTextSubduedColor) !important;
63+
}
64+
.euiAccordion__triggerWrapper {
65+
background-color: var(--euiColorEmptyShade);
66+
padding: 12px 24px 12px 32px !important;
67+
border-radius: 4px;
68+
border: 1px solid #3D3D3D;
69+
}
70+
.euiCollapsibleNavGroup__children {
71+
padding: 0 !important;
72+
}
73+
74+
.euiIEFlexWrapFix {
75+
flex-grow: 1;
76+
padding-right: 4px;
77+
}
78+
}
79+
}
80+
81+
.collapsibleNavTitle {
82+
display: flex;
83+
justify-content: space-between;
84+
align-items: center;
85+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { EuiBasicTableColumn, EuiInMemoryTable } from '@elastic/eui'
2+
import cx from 'classnames'
3+
import React from 'react'
4+
5+
import { ErrorImportResult } from 'uiSrc/slices/interfaces'
6+
import { Maybe } from 'uiSrc/utils'
7+
8+
import styles from './styles.module.scss'
9+
10+
export interface DataImportResult {
11+
index: number
12+
status: string
13+
errors?: Array<ErrorImportResult>
14+
host?: string
15+
port?: number
16+
}
17+
export interface Props {
18+
data: Array<DataImportResult>
19+
}
20+
21+
const TableResult = (props: Props) => {
22+
const { data } = props
23+
24+
const ErrorResult = ({ errors }: { errors: string[] }) => (
25+
<ul>
26+
{errors.map((message, i) => (
27+
<li key={String(Math.random() * i)}>{message}</li>
28+
))}
29+
</ul>
30+
)
31+
32+
const columns: EuiBasicTableColumn<any>[] = [
33+
{
34+
name: '#',
35+
field: 'index',
36+
width: '4%',
37+
render: (index: number) => (<span data-testid={`table-index-${index}`}>({index})</span>)
38+
},
39+
{
40+
name: 'Host:Port',
41+
field: 'host',
42+
width: '25%',
43+
truncateText: true,
44+
render: (_host, { host, port, index }) => (<div data-testid={`table-host-port-${index}`}>{host}:{port}</div>)
45+
},
46+
{
47+
name: 'Result',
48+
field: 'errors',
49+
width: '25%',
50+
render: (errors: Maybe<ErrorImportResult[]>, { index }) => (
51+
<div data-testid={`table-result-${index}`}>
52+
{errors ? (<ErrorResult errors={errors.map((e) => e.message)} />) : 'Successful'}
53+
</div>
54+
)
55+
}
56+
]
57+
58+
if (data?.length === 0) return null
59+
60+
return (
61+
<div className={styles.tableWrapper}>
62+
<EuiInMemoryTable
63+
items={data ?? []}
64+
columns={columns}
65+
className={cx('inMemoryTableDefault', 'noBorders', 'stickyHeader', styles.table)}
66+
responsive={false}
67+
itemId="index"
68+
data-testid="result-log-table"
69+
/>
70+
</div>
71+
)
72+
}
73+
74+
export default TableResult
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import TableResult from './TableResult'
2+
3+
export default TableResult
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
@import "@elastic/eui/src/global_styling/mixins/helpers";
2+
@import "@elastic/eui/src/components/table/mixins";
3+
@import "@elastic/eui/src/global_styling/index";
4+
5+
.tableWrapper {
6+
max-height: 200px;
7+
@include euiScrollBar;
8+
9+
overflow: auto;
10+
11+
.table {
12+
:global {
13+
.euiTableHeaderCell {
14+
background-color: var(--browserTableRowEven);
15+
}
16+
17+
.euiTableRowCell {
18+
vertical-align: top !important;
19+
}
20+
21+
.euiTableCellContent {
22+
white-space: normal !important;
23+
font-size: 12px !important;
24+
padding: 8px 14px;
25+
}
26+
}
27+
}
28+
29+
}

redisinsight/ui/src/components/import-databases-dialog/styles.module.scss

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
.modal {
22
background: var(--euiColorLightestShade) !important;
33
min-width: 500px !important;
4+
max-width: 700px !important;
45
min-height: 270px !important;
56

67
:global {
78
.euiModalHeader {
8-
padding: 4px 42px 42px 30px;
9+
padding: 4px 42px 20px 30px;
910
}
1011

1112
.euiModalBody__overflow {

0 commit comments

Comments
 (0)