Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions redisinsight/ui/src/assets/img/icons/double_like.svg

This file was deleted.

3 changes: 3 additions & 0 deletions redisinsight/ui/src/assets/img/icons/github-white.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions redisinsight/ui/src/assets/img/icons/petard.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import React from 'react'
import { cloneDeep } from 'lodash'
import { instance, mock } from 'ts-mockito'
import { setRecommendationVote } from 'uiSrc/slices/analytics/dbAnalysis'
import { userSettingsConfigSelector } from 'uiSrc/slices/user/user-settings'
import { Vote } from 'uiSrc/constants/recommendations'

import {
act,
Expand Down Expand Up @@ -47,51 +45,18 @@ describe('RecommendationVoting', () => {
expect(render(<RecommendationVoting {...instance(mockedProps)} />)).toBeTruthy()
})

it('should call "setRecommendationVote" action be called after click "very-useful-vote-btn"', () => {
render(<RecommendationVoting {...instance(mockedProps)} />)
expect(screen.queryByTestId('very-useful-vote-btn')).toBeInTheDocument()
fireEvent.click(screen.getByTestId('very-useful-vote-btn'))

const expectedActions = [setRecommendationVote()]
expect(store.getActions()).toEqual(expectedActions)
})

it('should call "setRecommendationVote" action be called after click "useful-vote-btn"', () => {
render(<RecommendationVoting {...instance(mockedProps)} />)
fireEvent.click(screen.getByTestId('useful-vote-btn'))

const expectedActions = [setRecommendationVote()]
expect(store.getActions()).toEqual(expectedActions)
})

it('should call "setRecommendationVote" action be called after click "not-useful-vote-btn"', () => {
render(<RecommendationVoting {...instance(mockedProps)} />)
fireEvent.click(screen.getByTestId('not-useful-vote-btn'))

const expectedActions = [setRecommendationVote()]
expect(store.getActions()).toEqual(expectedActions)
})

it('should render popover after click "not-useful-vote-btn"', async () => {
render(<RecommendationVoting {...instance(mockedProps)} />)

expect(document.querySelector('[data-test-subj="github-repo-link"]')).not.toBeInTheDocument()

fireEvent.click(screen.getByTestId('not-useful-vote-btn'))
fireEvent.click(screen.getByTestId('not useful-vote-btn'))
await waitForEuiPopoverVisible()

expect(document.querySelector('[data-test-subj="github-repo-link"]')).toHaveAttribute('href', 'https://github.com/RedisInsight/RedisInsight/issues/new/choose')
})

it('should render component where all buttons are disabled"', async () => {
render(<RecommendationVoting {...instance(mockedProps)} vote={Vote.Like} />)

expect(screen.getByTestId('very-useful-vote-btn')).toBeDisabled()
expect(screen.getByTestId('useful-vote-btn')).toBeDisabled()
expect(screen.getByTestId('not-useful-vote-btn')).toBeDisabled()
})

it('should render popover after click "not-useful-vote-btn"', async () => {
it('should render proper popover and btn should be disabled"', async () => {
userSettingsConfigSelector.mockImplementation(() => ({
agreements: {
analytics: false,
Expand All @@ -100,10 +65,11 @@ describe('RecommendationVoting', () => {
render(<RecommendationVoting {...instance(mockedProps)} />)

await act(async () => {
fireEvent.mouseOver(screen.getByTestId('not-useful-vote-btn'))
fireEvent.mouseOver(screen.getByTestId('not useful-vote-btn'))
})
await waitForEuiToolTipVisible()

expect(screen.getByTestId('not-useful-vote-tooltip')).toHaveTextContent('Enable Analytics on the Settings page to vote for a recommendation')
expect(screen.getByTestId('not useful-vote-tooltip')).toHaveTextContent('Enable Analytics on the Settings page to vote for a recommendation')
expect(screen.getByTestId('not useful-vote-btn')).toBeDisabled()
})
})
Original file line number Diff line number Diff line change
@@ -1,31 +1,15 @@
import React, { useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { useSelector } from 'react-redux'
import cx from 'classnames'
import {
EuiButton,
EuiButtonIcon,
EuiPopover,
EuiText,
EuiToolTip,
EuiFlexGroup,
EuiIcon,
EuiLink,
} from '@elastic/eui'
import { userSettingsConfigSelector } from 'uiSrc/slices/user/user-settings'
import { putRecommendationVote } from 'uiSrc/slices/analytics/dbAnalysis'
import { IRecommendationsStatic } from 'uiSrc/slices/interfaces/recommendations'
import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry'
import { EXTERNAL_LINKS } from 'uiSrc/constants/links'
import { Vote } from 'uiSrc/constants/recommendations'
import _content from 'uiSrc/constants/dbAnalysisRecommendations.json'
import { ReactComponent as LikeIcon } from 'uiSrc/assets/img/icons/like.svg'
import { ReactComponent as DoubleLikeIcon } from 'uiSrc/assets/img/icons/double_like.svg'
import { ReactComponent as DislikeIcon } from 'uiSrc/assets/img/icons/dislike.svg'
import GithubSVG from 'uiSrc/assets/img/sidebar/github.svg'
import { updateLiveRecommendation } from 'uiSrc/slices/recommendations/recommendations'
import { Nullable } from 'uiSrc/utils'

import { connectedInstanceSelector } from 'uiSrc/slices/instances/instances'
import VoteOption from './components/vote-option'
import styles from './styles.module.scss'

export interface Props {
Expand All @@ -36,43 +20,9 @@ export interface Props {
containerClass?: string
}

const recommendationsContent = _content as IRecommendationsStatic

const RecommendationVoting = ({ vote, name, id = '', live = false, containerClass = '' }: Props) => {
const config = useSelector(userSettingsConfigSelector)
const { id: instanceId = '', provider } = useSelector(connectedInstanceSelector)
const [isPopoverOpen, setIsPopoverOpen] = useState(false)
const dispatch = useDispatch()

const onSuccessVoted = ({ vote, name }: { name: string, vote: Nullable<Vote> }) => {
sendEventTelemetry({
event: live
? TelemetryEvent.INSIGHTS_RECOMMENDATION_VOTED
: TelemetryEvent.DATABASE_ANALYSIS_RECOMMENDATIONS_VOTED,
eventData: {
databaseId: instanceId,
name: recommendationsContent[name]?.telemetryEvent ?? name,
vote,
provider
}
})
}

const handleClick = (name: string, vote: Vote) => {
if (vote === Vote.Dislike) {
setIsPopoverOpen(true)
}

if (live) {
dispatch(updateLiveRecommendation(id, { vote }, onSuccessVoted))
} else {
dispatch(putRecommendationVote(name, vote, onSuccessVoted))
}
}

const getTooltipContent = (recommendationsContent: string) => (config?.agreements?.analytics
? recommendationsContent
: 'Enable Analytics on the Settings page to vote for a recommendation')
const [popover, setPopover] = useState<string>('')

return (
<EuiFlexGroup
Expand All @@ -81,97 +31,21 @@ const RecommendationVoting = ({ vote, name, id = '', live = false, containerClas
gutterSize={live ? 'none' : 'l'}
data-testid="recommendation-voting"
>
<EuiText size="m">Rate Recommendation</EuiText>
<EuiText size="m">Is this useful?</EuiText>
<div className="voteContent">
<EuiToolTip
content={getTooltipContent('Very Useful')}
position="bottom"
data-testid="very-useful-vote-tooltip"
>
<EuiButtonIcon
disabled={!!vote || !config?.agreements?.analytics}
iconType={DoubleLikeIcon}
className={cx('vote__btn', { selected: vote === Vote.DoubleLike })}
aria-label="vote very useful"
data-testid="very-useful-vote-btn"
onClick={() => handleClick(name, Vote.DoubleLike)}
/>
</EuiToolTip>
<EuiToolTip
content={getTooltipContent('Useful')}
position="bottom"
data-testid="useful-vote-tooltip"
>
<EuiButtonIcon
disabled={!!vote || !config?.agreements?.analytics}
iconType={LikeIcon}
className={cx('vote__btn', { selected: vote === Vote.Like })}
aria-label="vote useful"
data-testid="useful-vote-btn"
onClick={() => handleClick(name, Vote.Like)}
{Object.values(Vote).map((option) => (
<VoteOption
key={option}
voteOption={option}
vote={vote}
popover={popover}
isAnalyticsEnable={config?.agreements?.analytics}
setPopover={setPopover}
name={name}
id={id}
live={live}
/>
</EuiToolTip>
<EuiToolTip
content={getTooltipContent('Not Useful')}
position="bottom"
data-testid="not-useful-vote-tooltip"
>
<EuiPopover
initialFocus={false}
anchorPosition="rightCenter"
isOpen={isPopoverOpen}
closePopover={() => setIsPopoverOpen(false)}
anchorClassName={styles.popoverAnchor}
panelClassName={cx('euiToolTip', 'popoverLikeTooltip', styles.popover)}
button={(
<EuiButtonIcon
disabled={!!vote || !config?.agreements?.analytics}
iconType={DislikeIcon}
className={cx('vote__btn', { selected: vote === Vote.Dislike })}
aria-label="vote not useful"
data-testid="not-useful-vote-btn"
onClick={() => handleClick(name, Vote.Dislike)}
/>
)}
>
<div>
Thank you for your feedback, Tell us how we can improve
<EuiButton
aria-label="recommendation feedback"
fill
data-testid="recommendation-feedback-btn"
className={styles.feedbackBtn}
color="secondary"
size="s"
>
<EuiLink
external={false}
className={styles.link}
href={EXTERNAL_LINKS.recommendationFeedback}
target="_blank"
data-test-subj="github-repo-link"
>
<EuiIcon
className={styles.githubIcon}
aria-label="redis insight github issues"
type={GithubSVG}
data-testid="github-repo-icon"
/>
To Github
</EuiLink>
</EuiButton>
<EuiButtonIcon
iconType="cross"
color="primary"
id="close-monitor"
aria-label="close popover"
data-testid="close-popover"
className={styles.icon}
onClick={() => setIsPopoverOpen(false)}
/>
</div>
</EuiPopover>
</EuiToolTip>
))}
</div>
</EuiFlexGroup>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import React from 'react'
import { cloneDeep } from 'lodash'
import { instance, mock } from 'ts-mockito'
import { setRecommendationVote } from 'uiSrc/slices/analytics/dbAnalysis'
import { Vote } from 'uiSrc/constants/recommendations'
import {
cleanup,
mockedStore,
fireEvent,
render,
screen,
} from 'uiSrc/utils/test-utils'

import VoteOption, { Props } from './VoteOption'

const mockedProps = mock<Props>()

let store: typeof mockedStore

beforeEach(() => {
cleanup()
store = cloneDeep(mockedStore)
store.clearActions()
})

jest.mock('uiSrc/telemetry', () => ({
...jest.requireActual('uiSrc/telemetry'),
sendEventTelemetry: jest.fn(),
}))

jest.mock('uiSrc/slices/user/user-settings', () => ({
...jest.requireActual('uiSrc/slices/user/user-settings'),
userSettingsConfigSelector: jest.fn().mockReturnValue({
agreements: {
analytics: true,
}
}),
}))

describe('VoteOption', () => {
it('should render', () => {
expect(render(<VoteOption {...instance(mockedProps)} />)).toBeTruthy()
})

it('should render proper text for Like vote', async () => {
render(<VoteOption {...instance(mockedProps)} voteOption={Vote.Like} popover={Vote.Like} />)

expect(screen.getByTestId('common-text')).toHaveTextContent('Thank you for the feedback.')
expect(screen.getByTestId('custom-text')).toHaveTextContent('Share your ideas with us.')
})

it('should render proper text for Dislike vote', () => {
render(<VoteOption {...instance(mockedProps)} vote={Vote.Dislike} />)
expect(screen.getByTestId('common-text')).toHaveTextContent('Thank you for the feedback.')
expect(screen.getByTestId('custom-text')).toHaveTextContent('Tell us how we can improve.')
})

it('should call "setRecommendationVote" action be called after click "useful-vote-btn"', () => {
render(
<VoteOption
{...instance(mockedProps)}
isAnalyticsEnable
voteOption={Vote.Like}
vote={Vote.Like}
setPopover={() => {}}
/>
)
fireEvent.click(screen.getByTestId('useful-vote-btn'))

const expectedActions = [setRecommendationVote()]
expect(store.getActions()).toEqual(expectedActions)
})

it('should call "setRecommendationVote" action be called after click "not-useful-vote-btn"', () => {
render(
<VoteOption
{...instance(mockedProps)}
isAnalyticsEnable
voteOption={Vote.Dislike}
vote={Vote.Dislike}
setPopover={() => {}}
/>
)
fireEvent.click(screen.getByTestId('not useful-vote-btn'))

const expectedActions = [setRecommendationVote()]
expect(store.getActions()).toEqual(expectedActions)
})
})
Loading