Skip to content

Commit 027be05

Browse files
committed
#RI-5114 - add statuses for free db
1 parent 2aa8ff0 commit 027be05

File tree

11 files changed

+314
-25
lines changed

11 files changed

+314
-25
lines changed
Lines changed: 22 additions & 0 deletions
Loading

redisinsight/ui/src/pages/home/components/database-list-component/DatabasesListWrapper.tsx

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ import { setSocialDialogState } from 'uiSrc/slices/oauth/cloud'
5555
import { appFeatureFlagsFeaturesSelector } from 'uiSrc/slices/app/features'
5656
import { getUtmExternalLink } from 'uiSrc/utils/links'
5757
import { CREATE_CLOUD_DB_ID, HELP_LINKS } from 'uiSrc/pages/home/constants'
58+
59+
import DbStatus from '../db-status'
60+
5861
import styles from './styles.module.scss'
5962

6063
export interface Props {
@@ -295,16 +298,18 @@ const DatabasesListWrapper = (props: Props) => {
295298
)
296299
}
297300

298-
const { id, db, new: newStatus = false } = instance
301+
const { id, db, new: newStatus = false, lastConnection, createdAt, cloudDetails } = instance
299302
const cellContent = replaceSpaces(name.substring(0, 200))
300303

301304
return (
302305
<div role="presentation">
303-
{newStatus && (
304-
<EuiToolTip content="New" position="top" anchorClassName={styles.newStatusAnchor}>
305-
<div className={styles.newStatus} data-testid={`database-status-new-${id}`} />
306-
</EuiToolTip>
307-
)}
306+
<DbStatus
307+
id={id}
308+
isNew={newStatus}
309+
lastConnection={lastConnection}
310+
createdAt={createdAt}
311+
isFree={cloudDetails?.free}
312+
/>
308313
<EuiToolTip
309314
position="bottom"
310315
title="Database Alias"

redisinsight/ui/src/pages/home/components/database-list-component/styles.module.scss

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -76,21 +76,6 @@ $breakpoint-l: 1400px;
7676
}
7777
}
7878

79-
.newStatus {
80-
background-color: var(--euiColorPrimary) !important;
81-
cursor: pointer;
82-
width: 11px !important;
83-
min-width: 11px !important;
84-
height: 11px !important;
85-
border-radius: 6px;
86-
}
87-
88-
.newStatusAnchor {
89-
margin-top: 20px;
90-
margin-left: -19px;
91-
position: absolute;
92-
}
93-
9479
.container {
9580
// Database alias column
9681
height: 100%;
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import React from 'react'
2+
import { mock } from 'ts-mockito'
3+
import { act, fireEvent, render, screen, waitForEuiToolTipVisible } from 'uiSrc/utils/test-utils'
4+
5+
import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry'
6+
import DbStatus, { Props, WarningTypes } from './DbStatus'
7+
8+
jest.mock('uiSrc/telemetry', () => ({
9+
...jest.requireActual('uiSrc/telemetry'),
10+
sendEventTelemetry: jest.fn(),
11+
}))
12+
13+
const mockedProps = mock<Props>()
14+
const daysToMs = (days: number) => days * 60 * 60 * 24 * 1000
15+
16+
describe('DbStatus', () => {
17+
it('should render', () => {
18+
expect(render(<DbStatus {...mockedProps} />)).toBeTruthy()
19+
})
20+
21+
it('should not render any status', () => {
22+
render(<DbStatus {...mockedProps} id="1" />)
23+
24+
expect(screen.queryByTestId('database-status-new-1')).not.toBeInTheDocument()
25+
expect(screen.queryByTestId(`database-status-${WarningTypes.TryDatabase}-1`)).not.toBeInTheDocument()
26+
expect(screen.queryByTestId(`database-status-${WarningTypes.CheckIfDeleted}-1`)).not.toBeInTheDocument()
27+
})
28+
29+
it('should render TryDatabase status', () => {
30+
const lastConnection = new Date(Date.now() - daysToMs(3))
31+
render(<DbStatus {...mockedProps} id="1" lastConnection={lastConnection} isFree />)
32+
33+
expect(screen.getByTestId(`database-status-${WarningTypes.TryDatabase}-1`)).toBeInTheDocument()
34+
expect(screen.queryByTestId('database-status-new-1')).not.toBeInTheDocument()
35+
expect(screen.queryByTestId(`database-status-${WarningTypes.CheckIfDeleted}-1`)).not.toBeInTheDocument()
36+
})
37+
38+
it('should render CheckIfDeleted status', () => {
39+
const lastConnection = new Date(Date.now() - daysToMs(16))
40+
render(<DbStatus {...mockedProps} id="1" lastConnection={lastConnection} isFree isNew />)
41+
42+
expect(screen.getByTestId(`database-status-${WarningTypes.CheckIfDeleted}-1`)).toBeInTheDocument()
43+
44+
expect(screen.queryByTestId('database-status-new-1')).not.toBeInTheDocument()
45+
expect(screen.queryByTestId(`database-status-${WarningTypes.TryDatabase}-1`)).not.toBeInTheDocument()
46+
})
47+
48+
it('should render new status', async () => {
49+
const sendEventTelemetryMock = jest.fn();
50+
(sendEventTelemetry as jest.Mock).mockImplementation(() => sendEventTelemetryMock)
51+
52+
const lastConnection = new Date(Date.now() - daysToMs(3))
53+
render(<DbStatus {...mockedProps} id="1" lastConnection={lastConnection} isFree />)
54+
55+
await act(async () => {
56+
fireEvent.mouseOver(screen.getByTestId(`database-status-${WarningTypes.TryDatabase}-1`))
57+
})
58+
59+
await waitForEuiToolTipVisible(1_000)
60+
61+
expect(sendEventTelemetry).toBeCalledWith({
62+
event: TelemetryEvent.CLOUD_NOT_USED_DB_NOTIFICATION_VIEWED,
63+
eventData: {
64+
capability: expect.any(String),
65+
databaseId: '1',
66+
type: WarningTypes.TryDatabase
67+
}
68+
})
69+
})
70+
})
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import React from 'react'
2+
import { EuiIcon, EuiToolTip } from '@elastic/eui'
3+
import cx from 'classnames'
4+
import { differenceInDays } from 'date-fns'
5+
6+
import { useSelector } from 'react-redux'
7+
import { getTutorialCapability, Maybe } from 'uiSrc/utils'
8+
9+
import { appContextCapability } from 'uiSrc/slices/app/context'
10+
11+
import AlarmIcon from 'uiSrc/assets/img/alarm.svg'
12+
import { isShowCapabilityTutorialPopover } from 'uiSrc/services'
13+
import { sendEventTelemetry, TELEMETRY_EMPTY_VALUE, TelemetryEvent } from 'uiSrc/telemetry'
14+
import { CHECK_CLOUD_DATABASE, WARNING_WITH_CAPABILITY, WARNING_WITHOUT_CAPABILITY } from './texts'
15+
import styles from './styles.module.scss'
16+
17+
export interface Props {
18+
id: string
19+
lastConnection: Maybe<Date>
20+
createdAt: Maybe<Date>
21+
isNew: boolean
22+
isFree?: boolean
23+
}
24+
25+
export enum WarningTypes {
26+
CheckIfDeleted = 'checkIfDeleted',
27+
TryDatabase = 'tryDatabase',
28+
}
29+
30+
interface WarningTooltipProps {
31+
id: string
32+
content : React.ReactNode
33+
capabilityTelemetry?: string
34+
type?: string
35+
isCapabilityNotShown?: boolean
36+
}
37+
38+
const LAST_CONNECTION_SM = 3
39+
const LAST_CONNECTION_L = 16
40+
41+
const DbStatus = (props: Props) => {
42+
const { id, lastConnection, createdAt, isNew, isFree } = props
43+
44+
const { source } = useSelector(appContextCapability)
45+
const capability = getTutorialCapability(source!)
46+
const isCapabilityNotShown = Boolean(isShowCapabilityTutorialPopover(isFree))
47+
let daysDiff = 0
48+
49+
try {
50+
daysDiff = lastConnection
51+
? differenceInDays(new Date(), new Date(lastConnection))
52+
: createdAt ? differenceInDays(new Date(), new Date(createdAt)) : 0
53+
} catch {
54+
// nothing to do
55+
}
56+
57+
const renderWarningTooltip = (content: React.ReactNode, type?: string) => (
58+
<EuiToolTip
59+
content={(
60+
<WarningTooltipContent
61+
id={id}
62+
capabilityTelemetry={capability?.telemetryName}
63+
content={content}
64+
type={type}
65+
isCapabilityNotShown={isCapabilityNotShown}
66+
/>
67+
)}
68+
position="right"
69+
className={styles.tooltip}
70+
anchorClassName={cx(styles.statusAnchor, styles.warning)}
71+
>
72+
<div className={cx(styles.status, styles.warning)} data-testid={`database-status-${type}-${id}`}>!</div>
73+
</EuiToolTip>
74+
)
75+
76+
if (isFree && daysDiff >= LAST_CONNECTION_L) {
77+
return renderWarningTooltip(CHECK_CLOUD_DATABASE, 'checkIfDeleted')
78+
}
79+
80+
if (isFree && daysDiff >= LAST_CONNECTION_SM) {
81+
return renderWarningTooltip(
82+
isCapabilityNotShown && capability.name ? WARNING_WITH_CAPABILITY(capability.name) : WARNING_WITHOUT_CAPABILITY,
83+
'tryDatabase'
84+
)
85+
}
86+
87+
if (isNew) {
88+
return (
89+
<EuiToolTip content="New" position="top" anchorClassName={cx(styles.statusAnchor)}>
90+
<div className={cx(styles.status, styles.new)} data-testid={`database-status-new-${id}`} />
91+
</EuiToolTip>
92+
)
93+
}
94+
95+
return null
96+
}
97+
98+
// separated to send event when content is displayed
99+
const WarningTooltipContent = (props: WarningTooltipProps) => {
100+
const { id, content, capabilityTelemetry, type, isCapabilityNotShown } = props
101+
102+
sendEventTelemetry({
103+
event: TelemetryEvent.CLOUD_NOT_USED_DB_NOTIFICATION_VIEWED,
104+
eventData: {
105+
databaseId: id,
106+
capability: isCapabilityNotShown ? capabilityTelemetry : TELEMETRY_EMPTY_VALUE,
107+
type
108+
}
109+
})
110+
111+
return (
112+
<div className={styles.warningTooltipContent}>
113+
<EuiIcon type={AlarmIcon} size="original" />
114+
<div>{content}</div>
115+
</div>
116+
)
117+
}
118+
119+
export default DbStatus
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import DbStatus from './DbStatus'
2+
3+
export default DbStatus
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
.status {
2+
cursor: pointer;
3+
width: 11px !important;
4+
min-width: 11px !important;
5+
height: 11px !important;
6+
border-radius: 50%;
7+
8+
&.new {
9+
background-color: var(--euiColorPrimary) !important;
10+
}
11+
12+
&.warning {
13+
width: 14px !important;
14+
height: 14px !important;
15+
background-color: var(--euiColorWarningLight) !important;
16+
17+
line-height: 14px !important;
18+
text-align: center;
19+
color: var(--euiColorEmptyShade);
20+
font-size: 11px;
21+
}
22+
}
23+
24+
.statusAnchor {
25+
margin-top: 20px;
26+
margin-left: -19px;
27+
position: absolute;
28+
29+
&.warning {
30+
margin-top: 17px;
31+
margin-left: -21px;
32+
}
33+
}
34+
35+
.tooltip {
36+
min-width: 340px !important;
37+
}
38+
39+
.warningTooltipContent {
40+
display: flex;
41+
align-items: flex-start;
42+
43+
:global(.euiIcon) {
44+
margin-top: 4px;
45+
margin-right: 12px;
46+
}
47+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { EuiSpacer, EuiTitle } from '@elastic/eui'
2+
import React from 'react'
3+
4+
export const CHECK_CLOUD_DATABASE = (
5+
<>
6+
<EuiTitle size="xxs"><span>Check your Cloud database</span></EuiTitle>
7+
<EuiSpacer size="s" />
8+
<div>
9+
Free Cloud databases are usually deleted after 15 days of inactivity.
10+
<EuiSpacer size="s" />
11+
Check your Cloud database to proceed with learning more about Redis and its capabilities.
12+
</div>
13+
</>
14+
)
15+
16+
export const WARNING_WITH_CAPABILITY = (capability: string) => (
17+
<>
18+
<EuiTitle size="xxs"><span>{capability}</span></EuiTitle>
19+
<EuiSpacer size="s" />
20+
<div>
21+
Hey, remember you expressed interest in {capability}?
22+
<br />
23+
Try your Cloud database to get started.
24+
</div>
25+
<EuiSpacer size="s" />
26+
<div><b>Notice</b>: free Cloud databases will be deleted after 15 days of inactivity.</div>
27+
</>
28+
)
29+
export const WARNING_WITHOUT_CAPABILITY = (
30+
<>
31+
<EuiTitle size="xxs"><span>Try your Cloud database</span></EuiTitle>
32+
<EuiSpacer size="s" />
33+
<div>Hey, try your Cloud database to learn more about Redis.</div>
34+
<EuiSpacer size="s" />
35+
<div><b>Notice</b>: free Cloud databases will be deleted after 15 days of inactivity.</div>
36+
</>
37+
)

redisinsight/ui/src/telemetry/events.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,7 @@ export enum TelemetryEvent {
294294
CLOUD_ACCOUNT_SWITCHED = 'CLOUD_ACCOUNT_SWITCHED',
295295
CLOUD_CONSOLE_CLICKED = 'CLOUD_CONSOLE_CLICKED',
296296
CLOUD_SIGN_OUT_CLICKED = 'CLOUD_SIGN_OUT_CLICKED',
297+
CLOUD_NOT_USED_DB_NOTIFICATION_VIEWED = 'CLOUD_NOT_USED_DB_NOTIFICATION_VIEWED',
297298

298299
RDI_INSTANCE_LIST_SORTED = 'RDI_INSTANCE_LIST_SORTED',
299300
RDI_INSTANCE_SINGLE_DELETE_CLICKED = 'RDI_INSTANCE_SINGLE_DELETE_CLICKED',

redisinsight/ui/src/utils/capability.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export const getTutorialCapability = (source: any = '') => {
2424
case getSourceTutorialByCapability(RedisDefaultModules.FTL):
2525
return getCapability(
2626
'searchAndQuery',
27-
'Redis Query Engine capability',
27+
'Redis Query Engine',
2828
findMarkdownPath(store.getState()?.workbench?.tutorials?.items, { id: 'sq-intro' })
2929
)
3030

@@ -33,7 +33,7 @@ export const getTutorialCapability = (source: any = '') => {
3333
case getSourceTutorialByCapability(RedisDefaultModules.ReJSON):
3434
return getCapability(
3535
'JSON',
36-
'JSON capability',
36+
'JSON data structure',
3737
findMarkdownPath(store.getState()?.workbench?.tutorials?.items, { id: 'ds-json-intro' })
3838
)
3939

0 commit comments

Comments
 (0)