Skip to content

Commit 88b8f91

Browse files
Merge branch 'feature/RI-4943_rework-tutorials' into fe/feature/RI-5256_rename-recommendations
# Conflicts: # redisinsight/ui/src/components/database-side-panels/DatabaseSidePanels.test.tsx # redisinsight/ui/src/pages/database-analysis/components/recommendations-view/Recommendations.spec.tsx
2 parents 58dd8bf + dbbe0e6 commit 88b8f91

File tree

53 files changed

+795
-141
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+795
-141
lines changed

redisinsight/api/src/__mocks__/custom-tutorial.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ export const mockCustomTutorialManifestJson = {
7474
type: CustomTutorialManifestType.InternalLink,
7575
id: 'introduction',
7676
label: 'introduction',
77+
summary: 'Introduction summary',
7778
args: {
7879
path: '/ct-folder-1/ct-sub-folder-1/introduction.md',
7980
},

redisinsight/api/src/modules/custom-tutorial/models/custom-tutorial.manifest.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,13 @@ export class CustomTutorialManifest {
6262
@IsNotEmpty()
6363
label: string;
6464

65+
@ApiProperty({ type: String })
66+
@IsOptional()
67+
@Expose()
68+
@IsString()
69+
@IsNotEmpty()
70+
summary?: string;
71+
6572
@ApiPropertyOptional({ type: CustomTutorialManifestArgs })
6673
@IsOptional()
6774
@Expose()
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: 59 additions & 2 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
*
@@ -207,4 +230,38 @@ describe('DatabaseSidePanels', () => {
207230

208231
(sendEventTelemetry as jest.Mock).mockRestore()
209232
})
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+
})
266+
})
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/EnablementArea.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,12 @@ const EnablementArea = (props: Props) => {
9797
}, [search, tutorials, guides])
9898

9999
useEffect(() => {
100-
const manifestPath = new URLSearchParams(search).get('path')
101-
const guidePath = new URLSearchParams(search).get('guidePath')
102-
const contextManifestPath = new URLSearchParams(searchEAContext).get('path')
100+
const searchParams = new URLSearchParams(search)
101+
const searchContextParams = new URLSearchParams(searchEAContext)
102+
103+
const manifestPath = searchParams.get('path')
104+
const guidePath = searchParams.get('guidePath')
105+
const contextManifestPath = searchContextParams.get('path')
103106
const { manifest, prefixFolder } = getManifestByPath(manifestPath)
104107

105108
if (guidePath || (isEmpty(manifest) && !contextManifestPath)) {

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)