Skip to content

Commit 785278a

Browse files
committed
#RI-4181 - add to tutorial button for recommendations
1 parent fee9dde commit 785278a

File tree

11 files changed

+237
-35
lines changed

11 files changed

+237
-35
lines changed

redisinsight/ui/src/components/config/Config.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ import { checkIsAnalyticsGranted } from 'uiSrc/telemetry/checkAnalytics'
2727
import { setFavicon, isDifferentConsentsExists } from 'uiSrc/utils'
2828
import { fetchUnsupportedCliCommandsAction } from 'uiSrc/slices/cli/cli-settings'
2929
import { fetchRedisCommandsInfo } from 'uiSrc/slices/app/redis-commands'
30+
import { fetchGuides } from 'uiSrc/slices/workbench/wb-guides'
31+
import { fetchTutorials } from 'uiSrc/slices/workbench/wb-tutorials'
3032
import { ONBOARDING_FEATURES } from 'uiSrc/components/onboarding-features'
3133
import favicon from 'uiSrc/assets/favicon.ico'
3234

@@ -47,6 +49,10 @@ const Config = () => {
4749
dispatch(fetchRedisCommandsInfo())
4850
dispatch(fetchNotificationsAction())
4951

52+
// get guides & tutorials
53+
dispatch(fetchGuides())
54+
dispatch(fetchTutorials())
55+
5056
// fetch config settings, after that take spec
5157
if (pathname !== SETTINGS_PAGE_PATH) {
5258
dispatch(fetchUserConfigSettings(() => dispatch(fetchUserSettingsSpec())))

redisinsight/ui/src/constants/dbAnalysisRecommendations.json

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
"bigHashes": {
4343
"id": "bigHashes",
4444
"title": "Shard big hashes to small hashes",
45+
"tutorial": "/quick-guides/document/working-with-hashes.md",
4546
"content": [
4647
{
4748
"id": "1",
@@ -126,7 +127,7 @@
126127
{
127128
"id": "1",
128129
"type": "paragraph",
129-
"value": "Several set values with IntSet encoding exceed the set-max-intset-entries. Change the configuration in reds.conf to efficiently use the IntSet encoding."
130+
"value": "Several set values with IntSet encoding exceed the set-max-intset-entries. Change the configuration in reds.conf to efficiently use the IntSet encoding."
130131
},
131132
{
132133
"id": "2",
@@ -282,6 +283,7 @@
282283
"bigSets": {
283284
"id": "bigSets",
284285
"title": "Switch to Bloom filter, cuckoo filter, or HyperLogLog",
286+
"tutorial": "/redis_stack/probabilistic_data_structures.md",
285287
"redisStack": true,
286288
"content": [
287289
{
@@ -307,21 +309,21 @@
307309
"type": "list",
308310
"value": [
309311
[
310-
{
312+
{
311313
"id": "1",
312314
"type": "span",
313315
"value": "Count the number of unique observations in a stream"
314316
}
315317
],
316318
[
317-
{
319+
{
318320
"id": "2",
319321
"type": "span",
320322
"value": "Check if an observation already appeared in the stream"
321323
}
322324
],
323325
[
324-
{
326+
{
325327
"id": "3",
326328
"type": "span",
327329
"value": "Find the fraction or the number of observations in the stream that are smaller or larger than a given value"
@@ -347,7 +349,7 @@
347349
"name": "HyperLogLog"
348350
}
349351
},
350-
{
352+
{
351353
"id": "2",
352354
"type": "span",
353355
"value": " can be used for estimating the number of unique observations in a set."
@@ -362,7 +364,7 @@
362364
"name": "Bloom filter or cuckoo filter"
363365
}
364366
},
365-
{
367+
{
366368
"id": "2",
367369
"type": "span",
368370
"value": " can be used for checking if an observation has already appeared in the stream (false positive matches are possible, but false negatives are not)."
@@ -377,7 +379,7 @@
377379
"name": "t-digest"
378380
}
379381
},
380-
{
382+
{
381383
"id": "2",
382384
"type": "span",
383385
"value": " can be used for estimating the fraction or the number of observations in the stream that are smaller or larger than a given value."
@@ -496,6 +498,7 @@
496498
"id": "RTS",
497499
"title":"Optimize the use of time series",
498500
"redisStack": true,
501+
"tutorial": "/redis_stack/redis_for_time_series.md",
499502
"content": [
500503
{
501504
"id": "1",
@@ -579,8 +582,9 @@
579582
},
580583
"redisSearch": {
581584
"id": "redisSearch",
582-
"title":"Optimize your query and search experience",
585+
"title": "Optimize your query and search experience",
583586
"redisStack": true,
587+
"tutorial": "/redis_stack/working_with_json.md",
584588
"content": [
585589
{
586590
"id": "1",
@@ -638,6 +642,7 @@
638642
"id": "searchIndexes",
639643
"title":"Enhance your search indexes",
640644
"redisStack": true,
645+
"tutorial": "/quick-guides/document/working-with-hashes.md",
641646
"content": [
642647
{
643648
"id": "1",

redisinsight/ui/src/pages/databaseAnalysis/components/recommendation-voting/styles.module.scss

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,4 @@
11
.votingContainer {
2-
padding-top: 15px;
3-
margin-top: 15px !important;
4-
border-top: 1px solid var(--separatorColor);
5-
height: 49px;
6-
72
.vote {
83
margin-left: 10px;
94
}
@@ -42,7 +37,7 @@
4237
padding: 4px 8px 4px 4px;
4338
margin: 0 10px;
4439
height: 22px !important;
45-
40+
4641
:global(.euiButtonContent.euiButton__content) {
4742
padding: 0;
4843
}

redisinsight/ui/src/pages/databaseAnalysis/components/recommendations-view/Recommendations.spec.tsx

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React from 'react'
2+
import reactRouterDom from 'react-router-dom'
23
import { fireEvent, render, screen } from 'uiSrc/utils/test-utils'
34
import { dbAnalysisSelector } from 'uiSrc/slices/analytics/dbAnalysis'
45
import { INSTANCE_ID_MOCK } from 'uiSrc/mocks/handlers/analytics/clusterDetailsHandlers'
@@ -13,6 +14,41 @@ jest.mock('uiSrc/telemetry', () => ({
1314
sendEventTelemetry: jest.fn(),
1415
}))
1516

17+
jest.mock('react-router-dom', () => ({
18+
...jest.requireActual('react-router-dom'),
19+
useHistory: () => ({
20+
push: jest.fn,
21+
}),
22+
}))
23+
24+
jest.mock('uiSrc/slices/workbench/wb-guides', () => ({
25+
...jest.requireActual('uiSrc/slices/workbench/wb-guides'),
26+
workbenchGuidesSelector: jest.fn().mockReturnValue({
27+
loading: false,
28+
error: '',
29+
items: [{
30+
label: 'Quick guides',
31+
type: 'group',
32+
children: [
33+
{
34+
label: 'Quick guides',
35+
type: 'group',
36+
children: [
37+
{
38+
type: 'internal-link',
39+
id: 'document-capabilities',
40+
label: 'Document Capabilities',
41+
args: {
42+
path: '/quick-guides/document/working-with-hashes.md',
43+
},
44+
},
45+
]
46+
}
47+
]
48+
}],
49+
}),
50+
}))
51+
1652
jest.mock('uiSrc/slices/analytics/dbAnalysis', () => ({
1753
...jest.requireActual('uiSrc/slices/analytics/dbAnalysis'),
1854
dbAnalysisSelector: jest.fn().mockReturnValue({
@@ -389,4 +425,62 @@ describe('Recommendations', () => {
389425
expect(screen.queryByTestId('bigSets-redis-stack-link')).toBeInTheDocument()
390426
expect(screen.queryByTestId('bigSets-redis-stack-link')).toHaveAttribute('href', 'https://redis.io/docs/stack/')
391427
})
428+
429+
it('should render go to tutorial button', () => {
430+
(dbAnalysisSelector as jest.Mock).mockImplementation(() => ({
431+
...mockdbAnalysisSelector,
432+
data: {
433+
recommendations: [{ name: 'bigHashes' }]
434+
}
435+
}))
436+
437+
render(<Recommendations />)
438+
439+
expect(screen.getByTestId('bigHashes-to-tutorial-btn')).toBeInTheDocument()
440+
})
441+
442+
it('should call proper history push after click go to tutorial button', () => {
443+
const sendEventTelemetryMock = jest.fn()
444+
sendEventTelemetry.mockImplementation(() => sendEventTelemetryMock);
445+
446+
(dbAnalysisSelector as jest.Mock).mockImplementation(() => ({
447+
...mockdbAnalysisSelector,
448+
data: {
449+
recommendations: [{ name: 'bigHashes' }]
450+
}
451+
}))
452+
453+
render(<Recommendations />)
454+
455+
expect(screen.getByTestId('bigHashes-to-tutorial-btn')).toBeInTheDocument()
456+
fireEvent.click(screen.getByTestId('bigHashes-to-tutorial-btn'))
457+
458+
expect(sendEventTelemetry).toBeCalledWith({
459+
event: TelemetryEvent.DATABASE_RECOMMENDATIONS_TUTORIAL_CLICKED,
460+
eventData: {
461+
databaseId: INSTANCE_ID_MOCK,
462+
recommendation: 'bigHashes',
463+
}
464+
})
465+
sendEventTelemetry.mockRestore()
466+
})
467+
468+
it('should call proper telemetry after click go to tutorial button', () => {
469+
const pushMock = jest.fn()
470+
reactRouterDom.useHistory = jest.fn().mockReturnValue({ push: pushMock });
471+
472+
(dbAnalysisSelector as jest.Mock).mockImplementation(() => ({
473+
...mockdbAnalysisSelector,
474+
data: {
475+
recommendations: [{ name: 'bigHashes' }]
476+
}
477+
}))
478+
479+
render(<Recommendations />)
480+
481+
expect(screen.getByTestId('bigHashes-to-tutorial-btn')).toBeInTheDocument()
482+
fireEvent.click(screen.getByTestId('bigHashes-to-tutorial-btn'))
483+
484+
expect(pushMock).toBeCalledWith('/instanceId/workbench?path=quick-guides/0/0/0')
485+
})
392486
})

redisinsight/ui/src/pages/databaseAnalysis/components/recommendations-view/Recommendations.tsx

Lines changed: 59 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,48 @@
11
import React, { useContext } from 'react'
2-
import { useSelector } from 'react-redux'
3-
import { useParams } from 'react-router-dom'
2+
import { useDispatch, useSelector } from 'react-redux'
3+
import { useParams, useHistory } from 'react-router-dom'
44
import { isNull } from 'lodash'
55
import {
66
EuiAccordion,
7-
EuiPanel,
8-
EuiText,
9-
EuiToolTip,
7+
EuiButton,
108
EuiFlexGroup,
119
EuiFlexItem,
1210
EuiIcon,
1311
EuiLink,
12+
EuiPanel,
13+
EuiText,
14+
EuiToolTip,
1415
} from '@elastic/eui'
1516
import { ThemeContext } from 'uiSrc/contexts/themeContext'
1617
import { RecommendationVoting } from 'uiSrc/pages/databaseAnalysis/components'
1718
import { dbAnalysisSelector } from 'uiSrc/slices/analytics/dbAnalysis'
1819
import recommendationsContent from 'uiSrc/constants/dbAnalysisRecommendations.json'
19-
import { Theme } from 'uiSrc/constants'
20+
import { Pages, Theme } from 'uiSrc/constants'
2021
import { Vote } from 'uiSrc/constants/recommendations'
2122
import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry'
2223
import RediStackDarkMin from 'uiSrc/assets/img/modules/redistack/RediStackDark-min.svg'
2324
import RediStackLightMin from 'uiSrc/assets/img/modules/redistack/RediStackLight-min.svg'
2425
import NoRecommendationsDark from 'uiSrc/assets/img/icons/recommendations_dark.svg'
2526
import NoRecommendationsLight from 'uiSrc/assets/img/icons/recommendations_light.svg'
27+
import { workbenchGuidesSelector } from 'uiSrc/slices/workbench/wb-guides'
28+
import { workbenchTutorialsSelector } from 'uiSrc/slices/workbench/wb-tutorials'
29+
import { findMarkdownPathByPath } from 'uiSrc/pages/workbench/components/enablement-area/EnablementArea/utils'
30+
import { EAManifestFirstKey } from 'uiSrc/pages/workbench/components/enablement-area/EnablementArea/constants'
31+
import { resetWorkbenchEASearch, setWorkbenchEAMinimized } from 'uiSrc/slices/app/context'
32+
import { renderBadges, renderBadgesLegend, renderContent, sortRecommendations } from './utils'
2633

27-
import { renderContent, renderBadges, renderBadgesLegend, sortRecommendations } from './utils'
2834
import styles from './styles.module.scss'
2935

3036
const Recommendations = () => {
3137
const { data, loading } = useSelector(dbAnalysisSelector)
38+
const { items: guides } = useSelector(workbenchGuidesSelector)
39+
const { items: tutorials } = useSelector(workbenchTutorialsSelector)
3240
const { recommendations = [] } = data ?? {}
3341

3442
const { theme } = useContext(ThemeContext)
3543
const { instanceId } = useParams<{ instanceId: string }>()
44+
const history = useHistory()
45+
const dispatch = useDispatch()
3646

3747
const handleToggle = (isOpen: boolean, id: string) => sendEventTelemetry({
3848
event: isOpen
@@ -44,6 +54,32 @@ const Recommendations = () => {
4454
}
4555
})
4656

57+
const goToTutorial = (mdPath: string, id: string) => {
58+
sendEventTelemetry({
59+
event: TelemetryEvent.DATABASE_RECOMMENDATIONS_TUTORIAL_CLICKED,
60+
eventData: {
61+
databaseId: instanceId,
62+
recommendation: id,
63+
}
64+
})
65+
66+
dispatch(setWorkbenchEAMinimized(false))
67+
const quickGuidesPath = findMarkdownPathByPath(guides, mdPath)
68+
if (quickGuidesPath) {
69+
history.push(`${Pages.workbench(instanceId)}?path=${EAManifestFirstKey.GUIDES}/${quickGuidesPath}`)
70+
return
71+
}
72+
73+
const tutorialsPath = findMarkdownPathByPath(tutorials, mdPath)
74+
if (tutorialsPath) {
75+
history.push(`${Pages.workbench(instanceId)}?path=${EAManifestFirstKey.TUTORIALS}/${tutorialsPath}`)
76+
return
77+
}
78+
79+
dispatch(resetWorkbenchEASearch())
80+
history.push(Pages.workbench(instanceId))
81+
}
82+
4783
const onRedisStackClick = (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => event.stopPropagation()
4884

4985
const renderButtonContent = (redisStack: boolean, title: string, badges: string[], id: string) => (
@@ -117,7 +153,8 @@ const Recommendations = () => {
117153
title = '',
118154
content = '',
119155
badges = [],
120-
redisStack = false
156+
redisStack = false,
157+
tutorial,
121158
} = recommendationsContent[name as keyof typeof recommendationsContent]
122159

123160
return (
@@ -137,7 +174,20 @@ const Recommendations = () => {
137174
{renderContent(content, params)}
138175
</EuiPanel>
139176
</EuiAccordion>
140-
<RecommendationVoting vote={vote as Vote} name={name} />
177+
<div className={styles.footer}>
178+
<RecommendationVoting vote={vote as Vote} name={name} />
179+
{tutorial && (
180+
<EuiButton
181+
fill
182+
color="secondary"
183+
size="s"
184+
onClick={() => goToTutorial(tutorial, id)}
185+
data-testid={`${id}-to-tutorial-btn`}
186+
>
187+
To Tutorial
188+
</EuiButton>
189+
)}
190+
</div>
141191
</div>
142192
)
143193
})}

0 commit comments

Comments
 (0)