Skip to content

Commit 4135372

Browse files
authored
Merge pull request #1746 from RedisInsight/fe/feature/RI-4070_onboarding-wb-ft-index
#RI-4070 - Add FT.INFO index for onboarding
2 parents a37456e + 90f7c0e commit 4135372

File tree

13 files changed

+255
-16
lines changed

13 files changed

+255
-16
lines changed
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import React from 'react'
2+
import { render, screen, fireEvent } from 'uiSrc/utils/test-utils'
3+
4+
import CodeBlock from './CodeBlock'
5+
6+
const originalClipboard = { ...global.navigator.clipboard }
7+
describe('CodeBlock', () => {
8+
beforeEach(() => {
9+
// @ts-ignore
10+
global.navigator.clipboard = {
11+
writeText: jest.fn(),
12+
}
13+
})
14+
15+
afterEach(() => {
16+
jest.resetAllMocks()
17+
// @ts-ignore
18+
global.navigator.clipboard = originalClipboard
19+
})
20+
21+
it('should render', () => {
22+
expect(render(<CodeBlock>text</CodeBlock>)).toBeTruthy()
23+
})
24+
25+
it('should render proper content', () => {
26+
render(<CodeBlock data-testid="code">text</CodeBlock>)
27+
expect(screen.getByTestId('code')).toHaveTextContent('text')
28+
})
29+
30+
it('should not render copy button by default', () => {
31+
render(<CodeBlock data-testid="code">text</CodeBlock>)
32+
expect(screen.queryByTestId('copy-code-btn')).not.toBeInTheDocument()
33+
})
34+
35+
it('should copy proper text', () => {
36+
render(<CodeBlock data-testid="code" isCopyable>text</CodeBlock>)
37+
fireEvent.click(screen.getByTestId('copy-code-btn'))
38+
expect(navigator.clipboard.writeText).toHaveBeenCalledWith('text')
39+
})
40+
41+
it('should copy proper text when children is ReactNode', () => {
42+
render(<CodeBlock data-testid="code" isCopyable><span>text2</span></CodeBlock>)
43+
fireEvent.click(screen.getByTestId('copy-code-btn'))
44+
expect(navigator.clipboard.writeText).toHaveBeenCalledWith('text2')
45+
})
46+
})
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import React, { HTMLAttributes, useMemo } from 'react'
2+
import cx from 'classnames'
3+
import { EuiButtonIcon, useInnerText } from '@elastic/eui'
4+
5+
import styles from './styles.module.scss'
6+
7+
export interface Props extends HTMLAttributes<HTMLPreElement> {
8+
children: React.ReactNode
9+
className?: string
10+
isCopyable?: boolean
11+
}
12+
13+
const CodeBlock = (props: Props) => {
14+
const { isCopyable, className, children, ...rest } = props
15+
const [innerTextRef, innerTextString] = useInnerText('')
16+
17+
const innerText = useMemo(
18+
() => innerTextString?.replace(/[\r\n?]{2}|\n\n/g, '\n') || '',
19+
[innerTextString]
20+
)
21+
22+
const handleCopyClick = () => {
23+
navigator?.clipboard?.writeText(innerText)
24+
}
25+
26+
return (
27+
<div className={cx(styles.wrapper, { [styles.isCopyable]: isCopyable })}>
28+
<pre className={cx(styles.pre, className)} ref={innerTextRef} {...rest}>{children}</pre>
29+
{isCopyable && (
30+
<EuiButtonIcon
31+
onClick={handleCopyClick}
32+
className={styles.copyBtn}
33+
iconType="copy"
34+
data-testid="copy-code-btn"
35+
aria-label="copy code"
36+
/>
37+
)}
38+
</div>
39+
)
40+
}
41+
42+
export default CodeBlock
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import CodeBlock from './CodeBlock'
2+
3+
export default CodeBlock
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
.wrapper {
2+
position: relative;
3+
4+
&.isCopyable {
5+
.pre {
6+
padding: 8px 30px 8px 16px !important;
7+
}
8+
}
9+
10+
.pre {
11+
padding: 8px 16px !important;
12+
}
13+
14+
.copyBtn {
15+
position: absolute;
16+
top: 4px;
17+
right: 4px;
18+
}
19+
}

redisinsight/ui/src/components/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import PagePlaceholder from './page-placeholder'
2121
import BulkActionsConfig from './bulk-actions-config'
2222
import ImportDatabasesDialog from './import-databases-dialog'
2323
import OnboardingTour from './onboarding-tour'
24+
import CodeBlock from './code-block'
2425

2526
export {
2627
NavigationMenu,
@@ -48,5 +49,6 @@ export {
4849
PagePlaceholder,
4950
BulkActionsConfig,
5051
ImportDatabasesDialog,
51-
OnboardingTour
52+
OnboardingTour,
53+
CodeBlock,
5254
}

redisinsight/ui/src/components/onboarding-features/OnboardingFeatures.spec.tsx

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ import { Pages } from 'uiSrc/constants'
1515
import { setWorkbenchEAMinimized } from 'uiSrc/slices/app/context'
1616
import { dbAnalysisSelector, setDatabaseAnalysisViewTab } from 'uiSrc/slices/analytics/dbAnalysis'
1717
import { DatabaseAnalysisViewTab } from 'uiSrc/slices/interfaces/analytics'
18+
import { fetchRedisearchListAction, loadList } from 'uiSrc/slices/browser/redisearch'
19+
import { stringToBuffer } from 'uiSrc/utils'
20+
import { RedisResponseBuffer } from 'uiSrc/slices/interfaces'
1821
import { ONBOARDING_FEATURES } from './OnboardingFeatures'
1922

2023
jest.mock('uiSrc/slices/app/features', () => ({
@@ -33,6 +36,12 @@ jest.mock('uiSrc/slices/browser/keys', () => ({
3336
})
3437
}))
3538

39+
jest.mock('uiSrc/slices/browser/redisearch', () => ({
40+
...jest.requireActual('uiSrc/slices/browser/redisearch'),
41+
fetchRedisearchListAction: jest.fn()
42+
.mockImplementation(jest.requireActual('uiSrc/slices/browser/redisearch').fetchRedisearchListAction)
43+
}))
44+
3645
jest.mock('uiSrc/slices/analytics/dbAnalysis', () => ({
3746
...jest.requireActual('uiSrc/slices/analytics/dbAnalysis'),
3847
dbAnalysisSelector: jest.fn().mockReturnValue({
@@ -341,12 +350,40 @@ describe('ONBOARDING_FEATURES', () => {
341350
checkAllTelemetryButtons(OnboardingStepName.WorkbenchIntro, sendEventTelemetry as jest.Mock)
342351
})
343352

353+
it('should call proper actions on mount', () => {
354+
render(<OnboardingTour options={ONBOARDING_FEATURES.WORKBENCH_PAGE}><span /></OnboardingTour>)
355+
356+
const expectedActions = [loadList()]
357+
expect(clearStoreActions(store.getActions())).toEqual(clearStoreActions(expectedActions))
358+
})
359+
360+
it('should render FT.INFO when there are indexes in database', () => {
361+
const fetchRedisearchListActionMock = (onSuccess?: (indexes: RedisResponseBuffer[]) => void) =>
362+
jest.fn().mockImplementation(() => onSuccess?.([stringToBuffer('someIndex')]));
363+
364+
(fetchRedisearchListAction as jest.Mock).mockImplementation(fetchRedisearchListActionMock)
365+
render(<OnboardingTour options={ONBOARDING_FEATURES.WORKBENCH_PAGE}><span /></OnboardingTour>)
366+
367+
expect(screen.getByTestId('wb-onboarding-command')).toHaveTextContent('FT.INFO someIndex')
368+
})
369+
370+
it('should render CLIENT LIST when there are no indexes in database', () => {
371+
const fetchRedisearchListActionMock = (onSuccess?: (indexes: RedisResponseBuffer[]) => void) =>
372+
jest.fn().mockImplementation(() => onSuccess?.([]));
373+
374+
(fetchRedisearchListAction as jest.Mock).mockImplementation(fetchRedisearchListActionMock)
375+
render(<OnboardingTour options={ONBOARDING_FEATURES.WORKBENCH_PAGE}><span /></OnboardingTour>)
376+
377+
expect(screen.getByTestId('wb-onboarding-command')).toHaveTextContent('CLIENT LIST')
378+
})
379+
344380
it('should call proper actions on back', () => {
345381
render(<OnboardingTour options={ONBOARDING_FEATURES.WORKBENCH_PAGE}><span /></OnboardingTour>)
346382
fireEvent.click(screen.getByTestId('back-btn'))
347383

348384
const expectedActions = [showMonitor(), setOnboardPrevStep()]
349-
expect(clearStoreActions(store.getActions())).toEqual(clearStoreActions(expectedActions))
385+
expect(clearStoreActions(store.getActions().slice(-2)))
386+
.toEqual(clearStoreActions(expectedActions))
350387
})
351388

352389
it('should properly push history on back', () => {

redisinsight/ui/src/components/onboarding-features/OnboardingFeatures.tsx

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import React, { useEffect } from 'react'
1+
import React, { useEffect, useState } from 'react'
22
import { useDispatch, useSelector } from 'react-redux'
33
import { useHistory } from 'react-router-dom'
44
import { EuiIcon, EuiSpacer } from '@elastic/eui'
5-
import { partialRight } from 'lodash'
5+
import { isString, partialRight } from 'lodash'
66
import { keysDataSelector } from 'uiSrc/slices/browser/keys'
77
import { openCli, openCliHelper, resetCliHelperSettings, resetCliSettings } from 'uiSrc/slices/cli/cli-settings'
88
import { setMonitorInitialState, showMonitor } from 'uiSrc/slices/cli/monitor'
@@ -17,6 +17,9 @@ import OnboardingEmoji from 'uiSrc/assets/img/onboarding-emoji.svg'
1717
import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry'
1818
import { OnboardingStepName, OnboardingSteps } from 'uiSrc/constants/onboarding'
1919

20+
import { fetchRedisearchListAction } from 'uiSrc/slices/browser/redisearch'
21+
import { bufferToString, Nullable } from 'uiSrc/utils'
22+
import { CodeBlock } from 'uiSrc/components'
2023
import styles from './styles.module.scss'
2124

2225
const sendTelemetry = (databaseId: string, step: string, action: string) => sendEventTelemetry({
@@ -185,11 +188,22 @@ const ONBOARDING_FEATURES = {
185188
title: 'Try Workbench!',
186189
Inner: () => {
187190
const { id: connectedInstanceId = '' } = useSelector(connectedInstanceSelector)
191+
const [firstIndex, setFirstIndex] = useState<Nullable<string>>(null)
188192

189193
const dispatch = useDispatch()
190194
const history = useHistory()
191195
const telemetryArgs: TelemetryArgs = [connectedInstanceId, OnboardingStepName.WorkbenchIntro]
192196

197+
useEffect(() => {
198+
dispatch(fetchRedisearchListAction(
199+
(indexes) => {
200+
setFirstIndex(indexes?.length ? bufferToString(indexes[0]) : '')
201+
},
202+
undefined,
203+
false
204+
))
205+
}, [])
206+
193207
return {
194208
content: (
195209
<>
@@ -201,10 +215,36 @@ const ONBOARDING_FEATURES = {
201215
models such as documents, graphs, and time series.
202216
Or you <a href="https://github.com/RedisInsight/Packages" target="_blank" rel="noreferrer">can build your own visualization</a>.
203217

204-
<EuiSpacer size="s" />
205-
Run this command to see information and statistics about client connections:
206-
<EuiSpacer size="xs" />
207-
<pre className={styles.pre}>CLIENT LIST</pre>
218+
{isString(firstIndex) && (
219+
<>
220+
<EuiSpacer size="s" />
221+
{firstIndex ? (
222+
<>
223+
Run this command to see information and statistics on your index:
224+
<EuiSpacer size="xs" />
225+
<CodeBlock
226+
isCopyable
227+
className={styles.pre}
228+
data-testid="wb-onboarding-command"
229+
>
230+
FT.INFO {firstIndex}
231+
</CodeBlock>
232+
</>
233+
) : (
234+
<>
235+
Run this command to see information and statistics about client connections:
236+
<EuiSpacer size="xs" />
237+
<CodeBlock
238+
isCopyable
239+
className={styles.pre}
240+
data-testid="wb-onboarding-command"
241+
>
242+
CLIENT LIST
243+
</CodeBlock>
244+
</>
245+
)}
246+
</>
247+
)}
208248
</>
209249
),
210250
onSkip: () => sendClosedTelemetryEvent(...telemetryArgs),
Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
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+
15
.pre {
2-
padding: 8px 16px !important;
36
background-color: var(--commandGroupBadgeColor) !important;
7+
word-wrap: break-word;
8+
9+
max-height: 240px;
10+
overflow-y: auto;
11+
@include euiScrollBar;
412
}

tests/e2e/common-actions/onboard-actions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export class OnboardActions {
2727
complete onboarding process
2828
*/
2929
async verifyOnboardingCompleted(): Promise<void> {
30-
await t.expect(onboardingPage.showMeAroundButton.visible).notOk('show me around button still visible');
30+
await t.expect(onboardingPage.showMeAroundButton.exists).notOk('show me around button still visible');
3131
await t.expect(browserPage.patternModeBtn.visible).ok('browser page is not opened');
3232
}
3333
/**

tests/e2e/helpers/conf.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { Chance } from 'chance';
21
import * as os from 'os';
32
import * as fs from 'fs';
43
import { join as joinPath } from 'path';
4+
import { Chance } from 'chance';
55
const chance = new Chance();
66

77
// Urls for using in the tests
@@ -19,6 +19,14 @@ export const ossStandaloneConfig = {
1919
databasePassword: process.env.OSS_STANDALONE_PASSWORD
2020
};
2121

22+
export const ossStandaloneConfigEmpty = {
23+
host: process.env.OSS_STANDALONE_HOST || 'oss-standalone-empty',
24+
port: process.env.OSS_STANDALONE_PORT || '6379',
25+
databaseName: `${process.env.OSS_STANDALONE_DATABASE_NAME || 'test_standalone_empty'}-${uniqueId}`,
26+
databaseUsername: process.env.OSS_STANDALONE_USERNAME,
27+
databasePassword: process.env.OSS_STANDALONE_PASSWORD
28+
};
29+
2230
export const ossStandaloneV5Config = {
2331
host: process.env.OSS_STANDALONE_V5_HOST || 'oss-standalone-v5',
2432
port: process.env.OSS_STANDALONE_V5_PORT || '6379',

0 commit comments

Comments
 (0)