diff --git a/redisinsight/ui/src/components/import-file-modal/ImportFileModal.tsx b/redisinsight/ui/src/components/import-file-modal/ImportFileModal.tsx index f9386ae39c..d3a655baeb 100644 --- a/redisinsight/ui/src/components/import-file-modal/ImportFileModal.tsx +++ b/redisinsight/ui/src/components/import-file-modal/ImportFileModal.tsx @@ -2,7 +2,7 @@ import React from 'react' import { Nullable } from 'uiSrc/utils' import { RiFilePicker, UploadWarning } from 'uiSrc/components' -import { Col, FlexItem } from 'uiSrc/components/base/layout/flex' +import { Col, FlexItem, Row } from 'uiSrc/components/base/layout/flex' import { ColorText, Text } from 'uiSrc/components/base/text' import { Loader, Modal } from 'uiSrc/components/base/display' import { RiIcon } from 'uiSrc/components/base/icons/RiIcon' @@ -137,7 +137,7 @@ const ImportFileModal = ({ {isShowForm && ( - <> + ({ > {submitBtnText || 'Import'} - + )} {data && OK} diff --git a/redisinsight/ui/src/constants/importDatabasesTableResult.ts b/redisinsight/ui/src/constants/importDatabasesTableResult.ts new file mode 100644 index 0000000000..57807285d2 --- /dev/null +++ b/redisinsight/ui/src/constants/importDatabasesTableResult.ts @@ -0,0 +1,20 @@ +export enum ImportTableResultColumn { + Index = 'index', + Host = 'host', + Errors = 'errors', +} + +export const TABLE_IMPORT_RESULT_COLUMN_ID_HEADER_MAP = new Map< + ImportTableResultColumn, + string +>([ + [ImportTableResultColumn.Index, '#'], + [ImportTableResultColumn.Host, 'Host:Port'], + [ImportTableResultColumn.Errors, 'Result'], +]) + +export enum ImportDatabaseResultType { + Success = 'success', + Partial = 'partial', + Fail = 'fail', +} diff --git a/redisinsight/ui/src/constants/index.ts b/redisinsight/ui/src/constants/index.ts index 11e6cf97bf..bfee499623 100644 --- a/redisinsight/ui/src/constants/index.ts +++ b/redisinsight/ui/src/constants/index.ts @@ -36,4 +36,5 @@ export * from './datetime' export * from './sorting' export * from './databaseList' export * from './rdiList' +export * from './importDatabasesTableResult' export { ApiEndpoints, BrowserStorageItem, ApiStatusCode, apiErrors } diff --git a/redisinsight/ui/src/pages/home/components/cluster-connection/cluster-connection-form/ClusterConnectionForm.tsx b/redisinsight/ui/src/pages/home/components/cluster-connection/cluster-connection-form/ClusterConnectionForm.tsx index f73a905815..f433678571 100644 --- a/redisinsight/ui/src/pages/home/components/cluster-connection/cluster-connection-form/ClusterConnectionForm.tsx +++ b/redisinsight/ui/src/pages/home/components/cluster-connection/cluster-connection-form/ClusterConnectionForm.tsx @@ -16,7 +16,6 @@ import { PrimaryButton, SecondaryButton, } from 'uiSrc/components/base/forms/buttons' -import { InfoIcon } from 'uiSrc/components/base/icons' import { FormField, RiInfoIconProps, @@ -163,7 +162,6 @@ const ClusterConnectionForm = (props: Props) => { onClick={onClick} disabled={submitIsDisabled} loading={loading} - icon={submitIsDisabled ? InfoIcon : undefined} data-testid="btn-submit" > Submit diff --git a/redisinsight/ui/src/pages/home/components/import-database/ImportDatabase.tsx b/redisinsight/ui/src/pages/home/components/import-database/ImportDatabase.tsx index df24d0b484..c60a0d51e9 100644 --- a/redisinsight/ui/src/pages/home/components/import-database/ImportDatabase.tsx +++ b/redisinsight/ui/src/pages/home/components/import-database/ImportDatabase.tsx @@ -1,6 +1,7 @@ import React, { useEffect, useState } from 'react' import { useDispatch, useSelector } from 'react-redux' import ReactDOM from 'react-dom' + import { fetchInstancesAction, importInstancesSelector, @@ -9,22 +10,19 @@ import { } from 'uiSrc/slices/instances/instances' import { Nullable } from 'uiSrc/utils' import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry' -import { RiTooltip, UploadWarning, RiFilePicker } from 'uiSrc/components' +import { RiFilePicker, RiTooltip, UploadWarning } from 'uiSrc/components' import { useModalHeader } from 'uiSrc/contexts/ModalTitleProvider' import { Col, FlexItem, Row } from 'uiSrc/components/base/layout/flex' -import { Spacer } from 'uiSrc/components/base/layout/spacer' import { PrimaryButton, SecondaryButton, } from 'uiSrc/components/base/forms/buttons' -import { InfoIcon } from 'uiSrc/components/base/icons' +import { InfoIcon, RiIcon } from 'uiSrc/components/base/icons' import { Title } from 'uiSrc/components/base/text/Title' import { ColorText, Text } from 'uiSrc/components/base/text' import { Loader } from 'uiSrc/components/base/display' -import { RiIcon } from 'uiSrc/components/base/icons/RiIcon' import ResultsLog from './components/ResultsLog' -import styles from './styles.module.scss' import { ScrollableWrapper } from '../styles.module' export interface Props { @@ -102,53 +100,47 @@ const ImportDatabase = (props: Props) => { if (error) { return ReactDOM.createPortal( -
+ Retry -
, + , footerEl, ) } if (data) { return ReactDOM.createPortal( -
+ - Ok + OK -
, + , footerEl, ) } return ReactDOM.createPortal( -
+ Cancel { Submit -
, + , footerEl, ) } @@ -169,70 +161,70 @@ const ImportDatabase = (props: Props) => { return ( <> - - + + {isShowForm && ( - <> - + + Use a JSON file to import your database connections. Ensure that you only use files from trusted sources to prevent the risk of automatically executing malicious code. - + {isInvalid && ( - + {`File should not exceed ${MAX_MB_FILE} MB`} )} - + )} {loading && ( -
- - Uploading... - -
+ Uploading... + )} {error && ( -
- - - Failed to add database connections - - {error} -
+ + + Failed to add database connections + {error} + )} -
+ {isShowForm && ( - + )} {data && ( - - - + )}
diff --git a/redisinsight/ui/src/pages/home/components/import-database/components/ResultsLog/ResultLog.styles.ts b/redisinsight/ui/src/pages/home/components/import-database/components/ResultsLog/ResultLog.styles.ts new file mode 100644 index 0000000000..4c258eea95 --- /dev/null +++ b/redisinsight/ui/src/pages/home/components/import-database/components/ResultsLog/ResultLog.styles.ts @@ -0,0 +1,10 @@ +import styled from 'styled-components' + +import { Col } from 'uiSrc/components/base/layout/flex' + +// Ideally this should not be needed, but the section component +// will not let the parent cut the border, more precisely the box-shadow, +// so we need to add padding to the parent container +export const StyledColWrapper = styled(Col)` + padding: ${({ theme }) => theme.core.space.space025}; +` diff --git a/redisinsight/ui/src/pages/home/components/import-database/components/ResultsLog/ResultsLog.config.tsx b/redisinsight/ui/src/pages/home/components/import-database/components/ResultsLog/ResultsLog.config.tsx new file mode 100644 index 0000000000..995ebab154 --- /dev/null +++ b/redisinsight/ui/src/pages/home/components/import-database/components/ResultsLog/ResultsLog.config.tsx @@ -0,0 +1,17 @@ +import { ImportDatabaseResultType } from 'uiSrc/constants' +import { TableResultData } from './ResultsLog' + +export const RESULTS_DATA_CONFIG: TableResultData[] = [ + { + type: ImportDatabaseResultType.Success, + title: 'Fully imported', + }, + { + type: ImportDatabaseResultType.Partial, + title: 'Partially imported', + }, + { + type: ImportDatabaseResultType.Fail, + title: 'Failed to import', + }, +] diff --git a/redisinsight/ui/src/pages/home/components/import-database/components/ResultsLog/ResultsLog.spec.tsx b/redisinsight/ui/src/pages/home/components/import-database/components/ResultsLog/ResultsLog.spec.tsx index 9cdb7daf26..e662be473f 100644 --- a/redisinsight/ui/src/pages/home/components/import-database/components/ResultsLog/ResultsLog.spec.tsx +++ b/redisinsight/ui/src/pages/home/components/import-database/components/ResultsLog/ResultsLog.spec.tsx @@ -27,7 +27,7 @@ describe('ResultsLog', () => { expect(screen.getByTestId('success-results-closed')).toBeInTheDocument() expect(screen.getByTestId('partial-results-closed')).toBeInTheDocument() - expect(screen.getByTestId('failed-results-closed')).toBeInTheDocument() + expect(screen.getByTestId('fail-results-closed')).toBeInTheDocument() }) it('should open and collapse other groups', () => { @@ -45,12 +45,12 @@ describe('ResultsLog', () => { expect(screen.getByTestId('success-results-open')).toBeInTheDocument() expect(screen.getByTestId('partial-results-closed')).toBeInTheDocument() - expect(screen.getByTestId('failed-results-closed')).toBeInTheDocument() + expect(screen.getByTestId('fail-results-closed')).toBeInTheDocument() fireEvent.click( - within(screen.getByTestId('failed-results-closed')).getByRole('button'), + within(screen.getByTestId('fail-results-closed')).getByRole('button'), ) - expect(screen.getByTestId('failed-results-open')).toBeInTheDocument() + expect(screen.getByTestId('fail-results-open')).toBeInTheDocument() expect(screen.getByTestId('partial-results-closed')).toBeInTheDocument() expect(screen.getByTestId('success-results-closed')).toBeInTheDocument() @@ -60,7 +60,7 @@ describe('ResultsLog', () => { ) expect(screen.getByTestId('partial-results-open')).toBeInTheDocument() - expect(screen.getByTestId('failed-results-closed')).toBeInTheDocument() + expect(screen.getByTestId('fail-results-closed')).toBeInTheDocument() expect(screen.getByTestId('success-results-closed')).toBeInTheDocument() }) @@ -87,7 +87,7 @@ describe('ResultsLog', () => { ), ).toHaveTextContent('1') expect( - within(screen.getByTestId('failed-results-closed')).getByTestId( + within(screen.getByTestId('fail-results-closed')).getByTestId( 'number-of-dbs', ), ).toHaveTextContent('1') diff --git a/redisinsight/ui/src/pages/home/components/import-database/components/ResultsLog/ResultsLog.tsx b/redisinsight/ui/src/pages/home/components/import-database/components/ResultsLog/ResultsLog.tsx index 68160f51ee..d854b5b328 100644 --- a/redisinsight/ui/src/pages/home/components/import-database/components/ResultsLog/ResultsLog.tsx +++ b/redisinsight/ui/src/pages/home/components/import-database/components/ResultsLog/ResultsLog.tsx @@ -1,123 +1,75 @@ -import cx from 'classnames' -import React, { useState } from 'react' +import React, { useEffect, useMemo, useState } from 'react' import { ImportDatabasesData } from 'uiSrc/slices/interfaces' import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry' import { Nullable } from 'uiSrc/utils' +import { Row } from 'uiSrc/components/base/layout/flex' +import { Text } from 'uiSrc/components/base/text' import { RICollapsibleNavGroup } from 'uiSrc/components/base/display' -import { Col } from 'uiSrc/components/base/layout/flex' -import TableResult from '../TableResult' +import { ImportDatabaseResultType } from 'uiSrc/constants' -import styles from './styles.module.scss' +import TableResult from '../TableResult' +import { StyledColWrapper } from './ResultLog.styles' +import { RESULTS_DATA_CONFIG } from './ResultsLog.config' -enum ResultsStatus { - Success = 'success', - Partial = 'partial', - Failed = 'failed', +interface Props { + data: Nullable } -export interface Props { - data: Nullable +export interface TableResultData { + type: ImportDatabaseResultType + title: string } const ResultsLog = ({ data }: Props) => { - const [openedNav, setOpenedNav] = useState('') + const [openedNav, setOpenedNav] = + useState>(null) - const onToggle = (length: number = 0, isOpen: boolean, name: string) => { - if (length === 0) return - setOpenedNav(isOpen ? name : '') + const resultsData: TableResultData[] = useMemo(() => { + return RESULTS_DATA_CONFIG.filter( + (item) => data && data[item.type] && data[item.type].length > 0, + ) + }, [data]) - if (isOpen) { + useEffect(() => { + if (openedNav) { sendEventTelemetry({ event: TelemetryEvent.CONFIG_DATABASES_REDIS_IMPORT_LOG_VIEWED, eventData: { - length, - name, + length: data?.[openedNav]?.length ?? 0, + name: openedNav, }, }) } - } - - const CollapsibleNavTitle = ({ - title, - length = 0, - }: { - title: string - length: number - }) => ( -
- {title}: - {length} -
- ) - - const getNavGroupState = (name: ResultsStatus) => - openedNav === name ? 'open' : 'closed' + }, [openedNav]) return ( - - - } - 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)}`} - id={`success-results-${getNavGroupState(ResultsStatus.Success)}`} - > - - - - } - 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)}`} - > - - - - } - 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)}`} - > - - - + + {resultsData.map((item) => { + const navState = openedNav === item.type ? 'open' : 'closed' + return ( + + {item.title}: + + {data?.[item.type]?.length ?? 0} + + + } + data-testid={`${item.type}-results-${navState}`} + id={`${item.type}-results-${navState}`} + initialIsOpen={false} + onToggle={(isOpen) => setOpenedNav(isOpen ? item.type : null)} + forceState={navState} + open={openedNav === item.type} + > + + + ) + })} + ) } diff --git a/redisinsight/ui/src/pages/home/components/import-database/components/ResultsLog/styles.module.scss b/redisinsight/ui/src/pages/home/components/import-database/components/ResultsLog/styles.module.scss deleted file mode 100644 index c3242fa4ae..0000000000 --- a/redisinsight/ui/src/pages/home/components/import-database/components/ResultsLog/styles.module.scss +++ /dev/null @@ -1,76 +0,0 @@ -.collapsibleNav { - width: 100%; - margin-top: 5px !important; - position: relative; - - &.disabled { - :global { - .euiAccordion__button { - cursor: auto; - pointer-events: none; - } - .euiAccordion__iconWrapper { - display: none; - } - } - } - - &:global(.success) { - &:before { - background-color: var(--successBorderColor); - } - } - - &:global(.partial) { - &:before { - background-color: var(--warningBorderColor); - } - } - - &:global(.failed) { - &:before { - background-color: var(--errorBorderColor); - } - } - - &:before { - content: ''; - position: absolute; - left: 0; - top: 0; - bottom: 0; - width: 4px; - - border-radius: 4px 0 0 4px; - - z-index: 2; - } - - :global { - .euiCollapsibleNavGroup__title { - font-size: 12px !important; - font-weight: 400 !important; - color: var(--euiTextSubduedColor) !important; - } - .euiAccordion__triggerWrapper { - background-color: var(--euiColorEmptyShade); - padding: 12px 24px 12px 32px !important; - border-radius: 4px; - border: 1px solid var(--separatorColor); - } - .euiCollapsibleNavGroup__children { - padding: 0 !important; - } - - .euiIEFlexWrapFix { - flex-grow: 1; - padding-right: 4px; - } - } -} - -.collapsibleNavTitle { - display: flex; - justify-content: space-between; - align-items: center; -} diff --git a/redisinsight/ui/src/pages/home/components/import-database/components/TableResult/TableResult.tsx b/redisinsight/ui/src/pages/home/components/import-database/components/TableResult/TableResult.tsx index 75cdb013d0..329329352c 100644 --- a/redisinsight/ui/src/pages/home/components/import-database/components/TableResult/TableResult.tsx +++ b/redisinsight/ui/src/pages/home/components/import-database/components/TableResult/TableResult.tsx @@ -1,10 +1,12 @@ import React from 'react' -import { Table, ColumnDefinition } from 'uiSrc/components/base/layout/table' +import { Table, ColumnDef } from 'uiSrc/components/base/layout/table' +import { + ImportTableResultColumn, + TABLE_IMPORT_RESULT_COLUMN_ID_HEADER_MAP, +} from 'uiSrc/constants' import { ErrorImportResult } from 'uiSrc/slices/interfaces' -import styles from './styles.module.scss' - export interface DataImportResult { index: number status: string @@ -12,6 +14,7 @@ export interface DataImportResult { host?: string port?: number } + export interface Props { data: Array } @@ -27,21 +30,26 @@ const TableResult = (props: Props) => { ) - const columns: ColumnDefinition[] = [ + const columns: ColumnDef[] = [ { - header: '#', - id: 'index', - accessorKey: 'index', + header: TABLE_IMPORT_RESULT_COLUMN_ID_HEADER_MAP.get( + ImportTableResultColumn.Index, + ), + id: ImportTableResultColumn.Index, + accessorKey: ImportTableResultColumn.Index, cell: ({ row: { original: { index }, }, }) => ({index}), + size: 50, }, { - header: 'Host:Port', - id: 'host', - accessorKey: 'host', + header: TABLE_IMPORT_RESULT_COLUMN_ID_HEADER_MAP.get( + ImportTableResultColumn.Host, + ), + id: ImportTableResultColumn.Host, + accessorKey: ImportTableResultColumn.Host, cell: ({ row: { original: { host, port, index }, @@ -53,8 +61,10 @@ const TableResult = (props: Props) => { ), }, { - header: 'Result', - id: 'errors', + header: TABLE_IMPORT_RESULT_COLUMN_ID_HEADER_MAP.get( + ImportTableResultColumn.Errors, + ), + id: ImportTableResultColumn.Errors, accessorKey: 'errors', cell: ({ row: { @@ -74,11 +84,7 @@ const TableResult = (props: Props) => { if (data?.length === 0) return null - return ( -
- - - ) + return
} export default TableResult diff --git a/redisinsight/ui/src/pages/home/components/import-database/components/TableResult/styles.module.scss b/redisinsight/ui/src/pages/home/components/import-database/components/TableResult/styles.module.scss deleted file mode 100644 index cf242b91db..0000000000 --- a/redisinsight/ui/src/pages/home/components/import-database/components/TableResult/styles.module.scss +++ /dev/null @@ -1,5 +0,0 @@ -.tableWrapper { - max-height: 200px; - @include eui.scrollBar; - overflow: auto; -} diff --git a/redisinsight/ui/src/pages/home/components/import-database/styles.module.scss b/redisinsight/ui/src/pages/home/components/import-database/styles.module.scss deleted file mode 100644 index 4e188a5139..0000000000 --- a/redisinsight/ui/src/pages/home/components/import-database/styles.module.scss +++ /dev/null @@ -1,39 +0,0 @@ -.formWrapper { - .loading { - margin-top: 40px; - text-align: center; - } -} - -.fileDrop { - width: 100%; - max-width: none !important; - - :global { - .RI-File-Picker__showDrop .RI-File-Picker__prompt, .RI-File-Picker__input:focus + .RI-File-Picker__prompt { - background-color: var(--browserTableRowEven); - } - - .RI-File-Picker__prompt { - background-color: var(--browserTableRowEven); - height: 140px; - border-radius: 4px; - box-shadow: none; - border: 1px dashed var(--controlsBorderColor); - color: var(--htmlColor); - } - - .RI-File-Picker { - width: 400px; - } - - .RI-File-Picker__clearButton { - margin-top: 4px; - } - } -} - -.result { - text-align: center; - margin-top: 32px; -}