Skip to content

Commit 8fca6d0

Browse files
Merge pull request #2913 from RedisInsight/feature/RI-5222_show_relevant_tutorial
#RI-5222 - Show a relevant tutorial for new Cloud users
2 parents e4d933f + 2bb896f commit 8fca6d0

File tree

34 files changed

+654
-51
lines changed

34 files changed

+654
-51
lines changed
Lines changed: 37 additions & 0 deletions
Loading

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { getWBTutorials } from 'uiSrc/slices/workbench/wb-tutorials'
2020
import { getContentRecommendations } from 'uiSrc/slices/recommendations/recommendations'
2121
import { getGuideLinks } from 'uiSrc/slices/content/guide-links'
2222
import { getWBCustomTutorials } from 'uiSrc/slices/workbench/wb-custom-tutorials'
23+
import { setCapability } from 'uiSrc/slices/app/context'
2324
import Config from './Config'
2425

2526
let store: typeof mockedStore
@@ -60,6 +61,7 @@ describe('Config', () => {
6061
it('should render', () => {
6162
render(<Config />)
6263
const afterRenderActions = [
64+
setCapability(),
6365
getServerInfo(),
6466
processCliClient(),
6567
getRedisCommands(),
@@ -97,6 +99,7 @@ describe('Config', () => {
9799
userSettingsSelector.mockImplementation(userSettingsSelectorMock)
98100
render(<Config />)
99101
const afterRenderActions = [
102+
setCapability(),
100103
getServerInfo(),
101104
processCliClient(),
102105
getRedisCommands(),

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { fetchCustomTutorials } from 'uiSrc/slices/workbench/wb-custom-tutorials
2929
import { ONBOARDING_FEATURES } from 'uiSrc/components/onboarding-features'
3030
import { fetchContentRecommendations } from 'uiSrc/slices/recommendations/recommendations'
3131
import { fetchGuideLinksAction } from 'uiSrc/slices/content/guide-links'
32+
import { setCapability } from 'uiSrc/slices/app/context'
3233

3334
import favicon from 'uiSrc/assets/favicon.ico'
3435

@@ -42,6 +43,8 @@ const Config = () => {
4243
useEffect(() => {
4344
setFavicon(favicon)
4445

46+
dispatch(setCapability(localStorageService?.get(BrowserStorageItem.capability)))
47+
4548
dispatch(fetchServerInfo())
4649
dispatch(fetchUnsupportedCliCommandsAction())
4750
dispatch(fetchRedisCommandsInfo())

redisinsight/ui/src/components/database-side-panels/DatabaseSidePanels.test.tsx

Lines changed: 65 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,13 @@ import {
77
} from 'uiSrc/slices/recommendations/recommendations'
88
import { fireEvent, screen, cleanup, mockedStore, render } from 'uiSrc/utils/test-utils'
99
import { MOCK_RECOMMENDATIONS } from 'uiSrc/constants/mocks/mock-recommendations'
10-
import { insightsPanelSelector } from 'uiSrc/slices/panels/insights'
11-
10+
import { changeSelectedTab, insightsPanelSelector, resetExplorePanelSearch, setExplorePanelIsPageOpen, toggleInsightsPanel } from 'uiSrc/slices/panels/insights'
1211
import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry'
1312
import { Pages } from 'uiSrc/constants'
13+
import { connectedInstanceCDSelector } from 'uiSrc/slices/instances/instances'
14+
import { InsightsPanelTabs } from 'uiSrc/slices/interfaces/insights'
15+
import { getTutorialCapability } from 'uiSrc/utils'
16+
import { isShowCapabilityTutorialPopover } from 'uiSrc/services'
1417
import DatabaseSidePanels from './DatabaseSidePanels'
1518

1619
let store: typeof mockedStore
@@ -36,6 +39,16 @@ jest.mock('uiSrc/slices/instances/instances', () => ({
3639
connectionType: 'CLUSTER',
3740
provider: 'RE_CLOUD'
3841
}),
42+
connectedInstanceCDSelector: jest.fn().mockReturnValue({
43+
free: false,
44+
}),
45+
}))
46+
47+
jest.mock('uiSrc/slices/app/context', () => ({
48+
...jest.requireActual('uiSrc/slices/app/context'),
49+
appContextCapability: jest.fn().mockReturnValue({
50+
source: 'workbench RediSearch',
51+
}),
3952
}))
4053

4154
jest.mock('uiSrc/slices/recommendations/recommendations', () => ({
@@ -61,6 +74,16 @@ jest.mock('uiSrc/telemetry', () => ({
6174
sendEventTelemetry: jest.fn(),
6275
}))
6376

77+
jest.mock('uiSrc/utils', () => ({
78+
...jest.requireActual('uiSrc/utils'),
79+
getTutorialCapability: jest.fn().mockReturnValue({ tutorialPage: { id: 'id' }, telemetryName: 'searchAndQuery' }),
80+
}))
81+
82+
jest.mock('uiSrc/services', () => ({
83+
...jest.requireActual('uiSrc/services'),
84+
isShowCapabilityTutorialPopover: jest.fn(),
85+
}))
86+
6487
/**
6588
* DatabaseSidePanels tests
6689
*
@@ -153,9 +176,9 @@ describe('DatabaseSidePanels', () => {
153176
page: '/triggered-functions/libraries',
154177
tab: 'recommendations'
155178
},
156-
})
179+
});
157180

158-
sendEventTelemetry.mockRestore()
181+
(sendEventTelemetry as jest.Mock).mockRestore()
159182
})
160183

161184
it('should call proper telemetry events on change tab', () => {
@@ -180,9 +203,9 @@ describe('DatabaseSidePanels', () => {
180203
prevTab: 'recommendations',
181204
currentTab: 'explore',
182205
},
183-
})
206+
});
184207

185-
sendEventTelemetry.mockRestore()
208+
(sendEventTelemetry as jest.Mock).mockRestore()
186209
})
187210

188211
it('should call proper telemetry events on fullscreen', () => {
@@ -203,8 +226,42 @@ describe('DatabaseSidePanels', () => {
203226
databaseId: 'instanceId',
204227
state: 'open'
205228
},
206-
})
229+
});
207230

208-
sendEventTelemetry.mockRestore()
231+
(sendEventTelemetry as jest.Mock).mockRestore()
232+
})
233+
234+
describe('capability', () => {
235+
beforeEach(() => {
236+
(connectedInstanceCDSelector as jest.Mock).mockReturnValueOnce({ free: true });
237+
(isShowCapabilityTutorialPopover as jest.Mock).mockImplementation(() => true)
238+
})
239+
it('should call store actions', () => {
240+
(getTutorialCapability as jest.Mock).mockImplementation(() => ({
241+
tutorialPage: { args: { path: 'path' } }
242+
}))
243+
render(<DatabaseSidePanels />)
244+
245+
const expectedActions = [
246+
getRecommendations(),
247+
changeSelectedTab(InsightsPanelTabs.Explore),
248+
toggleInsightsPanel(true),
249+
]
250+
expect(store.getActions()).toEqual(expectedActions);
251+
252+
(getTutorialCapability as jest.Mock).mockRestore()
253+
})
254+
it('should call resetExplorePanelSearch if capability was not found', () => {
255+
render(<DatabaseSidePanels />)
256+
257+
const expectedActions = [
258+
getRecommendations(),
259+
resetExplorePanelSearch(),
260+
setExplorePanelIsPageOpen(false),
261+
changeSelectedTab(InsightsPanelTabs.Explore),
262+
toggleInsightsPanel(true),
263+
]
264+
expect(store.getActions()).toEqual(expectedActions)
265+
})
209266
})
210267
})

redisinsight/ui/src/components/database-side-panels/DatabaseSidePanels.tsx

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,18 @@ import React, { useCallback, useEffect, useRef, useState } from 'react'
22
import cx from 'classnames'
33
import { EuiButtonIcon, EuiTab, EuiTabs, keys } from '@elastic/eui'
44
import { useDispatch, useSelector } from 'react-redux'
5+
import { useHistory, useLocation, useParams } from 'react-router-dom'
56

6-
import { useLocation, useParams } from 'react-router-dom'
7-
import { changeSelectedTab, insightsPanelSelector, toggleInsightsPanel } from 'uiSrc/slices/panels/insights'
7+
import { changeSelectedTab, insightsPanelSelector, resetExplorePanelSearch, setExplorePanelIsPageOpen, toggleInsightsPanel } from 'uiSrc/slices/panels/insights'
88
import { InsightsPanelTabs } from 'uiSrc/slices/interfaces/insights'
99
import { recommendationsSelector } from 'uiSrc/slices/recommendations/recommendations'
1010
import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry'
11-
import { connectedInstanceSelector } from 'uiSrc/slices/instances/instances'
11+
import { connectedInstanceCDSelector, connectedInstanceSelector } from 'uiSrc/slices/instances/instances'
1212
import { ONBOARDING_FEATURES } from 'uiSrc/components/onboarding-features'
1313
import { FullScreen, OnboardingTour } from 'uiSrc/components'
14+
import { appContextCapability } from 'uiSrc/slices/app/context'
15+
import { getTutorialCapability } from 'uiSrc/utils'
16+
import { isShowCapabilityTutorialPopover } from 'uiSrc/services'
1417
import LiveTimeRecommendations from './panels/live-time-recommendations'
1518
import EnablementAreaWrapper from './panels/enablement-area'
1619

@@ -25,9 +28,12 @@ const DatabaseSidePanels = (props: Props) => {
2528
const { isOpen, tabSelected } = useSelector(insightsPanelSelector)
2629
const { data: { totalUnread } } = useSelector(recommendationsSelector)
2730
const { provider } = useSelector(connectedInstanceSelector)
31+
const { source: capabilitySource } = useSelector(appContextCapability)
32+
const { free = false } = useSelector(connectedInstanceCDSelector) ?? {}
2833

2934
const [isFullScreen, setIsFullScreen] = useState<boolean>(false)
3035

36+
const history = useHistory()
3137
const { pathname } = useLocation()
3238
const dispatch = useDispatch()
3339
const { instanceId } = useParams<{ instanceId: string }>()
@@ -52,6 +58,28 @@ const DatabaseSidePanels = (props: Props) => {
5258
pathnameRef.current = pathname
5359
}, [pathname, isFullScreen])
5460

61+
useEffect(() => {
62+
if (!capabilitySource || !isShowCapabilityTutorialPopover(free)) {
63+
return
64+
}
65+
66+
const tutorialCapabilityPath = getTutorialCapability(capabilitySource)?.tutorialPage?.args?.path || ''
67+
68+
// set 'guidPath' with the path to capability tutorial
69+
if (tutorialCapabilityPath) {
70+
const search = new URLSearchParams(window.location.search)
71+
search.set('guidePath', tutorialCapabilityPath)
72+
history.push({ search: search.toString() })
73+
} else {
74+
// reset explore if tutorial is not found
75+
dispatch(resetExplorePanelSearch())
76+
dispatch(setExplorePanelIsPageOpen(false))
77+
}
78+
79+
dispatch(changeSelectedTab(InsightsPanelTabs.Explore))
80+
dispatch(toggleInsightsPanel(true))
81+
}, [capabilitySource, free])
82+
5583
const handleEscFullScreen = (event: KeyboardEvent) => {
5684
if (event.key === keys.ESCAPE && isFullScreen) {
5785
handleFullScreen()

redisinsight/ui/src/components/database-side-panels/panels/enablement-area/EnablementArea/components/InternalPage/InternalPage.spec.tsx

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,45 @@
11
import React from 'react'
22
import { instance, mock } from 'ts-mockito'
33
import { fireEvent, render } from 'uiSrc/utils/test-utils'
4+
import { TelemetryEvent, sendEventTelemetry } from 'uiSrc/telemetry'
5+
import { isShowCapabilityTutorialPopover, setCapabilityPopoverShown } from 'uiSrc/services'
6+
import { connectedInstanceCDSelector } from 'uiSrc/slices/instances/instances'
7+
import { getTutorialCapability } from 'uiSrc/utils'
8+
49
import InternalPage, { Props } from './InternalPage'
510

611
const mockedProps = mock<Props>()
712

13+
jest.mock('uiSrc/telemetry', () => ({
14+
...jest.requireActual('uiSrc/telemetry'),
15+
sendEventTelemetry: jest.fn(),
16+
}))
17+
18+
jest.mock('uiSrc/slices/app/context', () => ({
19+
...jest.requireActual('uiSrc/slices/app/context'),
20+
appContextCapability: jest.fn().mockReturnValue({
21+
source: 'workbench RediSearch',
22+
}),
23+
}))
24+
25+
jest.mock('uiSrc/services', () => ({
26+
...jest.requireActual('uiSrc/services'),
27+
isShowCapabilityTutorialPopover: jest.fn(),
28+
setCapabilityPopoverShown: jest.fn(),
29+
}))
30+
31+
jest.mock('uiSrc/utils', () => ({
32+
...jest.requireActual('uiSrc/utils'),
33+
getTutorialCapability: jest.fn().mockReturnValue({ tutorialPage: { id: 'id' }, telemetryName: 'searchAndQuery' }),
34+
}))
35+
36+
jest.mock('uiSrc/slices/instances/instances', () => ({
37+
...jest.requireActual('uiSrc/slices/instances/instances'),
38+
connectedInstanceCDSelector: jest.fn().mockReturnValue({
39+
free: false,
40+
}),
41+
}))
42+
843
/**
944
* InternalPage tests
1045
*
@@ -39,4 +74,37 @@ describe('InternalPage', () => {
3974

4075
expect(queryByTestId('header')).toBeInTheDocument()
4176
})
77+
78+
describe('capability', () => {
79+
beforeEach(() => {
80+
(connectedInstanceCDSelector as jest.Mock).mockReturnValueOnce({ free: true })
81+
})
82+
it('should call isShowCapabilityTutorialPopover, setCapabilityPopoverShown and getTutorialCapability', async () => {
83+
const isShowCapabilityTutorialPopoverMock = jest.fn()
84+
const setCapabilityPopoverShownMock = jest.fn();
85+
(isShowCapabilityTutorialPopover as jest.Mock).mockImplementation(() => isShowCapabilityTutorialPopoverMock);
86+
(setCapabilityPopoverShown as jest.Mock).mockImplementation(() => setCapabilityPopoverShownMock)
87+
88+
render(<InternalPage {...instance(mockedProps)} />)
89+
90+
expect(isShowCapabilityTutorialPopover).toBeCalled()
91+
expect(setCapabilityPopoverShown).toBeCalled()
92+
expect(getTutorialCapability).toBeCalled()
93+
})
94+
95+
it('should send CAPABILITY_POPOVER_DISPLAYED telemetry event', () => {
96+
const sendEventTelemetryMock = jest.fn();
97+
(sendEventTelemetry as jest.Mock).mockImplementation(() => sendEventTelemetryMock)
98+
99+
render(<InternalPage {...instance(mockedProps)} />)
100+
101+
expect(sendEventTelemetry).toBeCalledWith({
102+
event: TelemetryEvent.CAPABILITY_POPOVER_DISPLAYED,
103+
eventData: {
104+
databaseId: 'instanceId',
105+
capabilityName: 'searchAndQuery',
106+
}
107+
})
108+
})
109+
})
42110
})

0 commit comments

Comments
 (0)