diff options
10 files changed, 52 insertions, 42 deletions
diff --git a/server/sonar-web/src/main/js/api/mocks/CodingRulesMock.ts b/server/sonar-web/src/main/js/api/mocks/CodingRulesMock.ts index f23c2913c7c..0f6d6af4791 100644 --- a/server/sonar-web/src/main/js/api/mocks/CodingRulesMock.ts +++ b/server/sonar-web/src/main/js/api/mocks/CodingRulesMock.ts @@ -40,7 +40,7 @@ import { SearchQualityProfilesResponse } from '../quality-profiles'; import { getRuleDetails, getRulesApp, searchRules, updateRule } from '../rules'; -import { dismissNotification, getCurrentUser } from '../users'; +import { dismissNotice, getCurrentUser } from '../users'; interface FacetFilter { languages?: string; @@ -184,7 +184,7 @@ export default class CodingRulesMock { (bulkDeactivateRules as jest.Mock).mockImplementation(this.handleBulkDeactivateRules); (getFacet as jest.Mock).mockImplementation(this.handleGetGacet); (getCurrentUser as jest.Mock).mockImplementation(this.handleGetCurrentUser); - (dismissNotification as jest.Mock).mockImplementation(this.handleDismissNotification); + (dismissNotice as jest.Mock).mockImplementation(this.handleDismissNotification); this.rules = cloneDeep(this.defaultRules); } diff --git a/server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts b/server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts index 7b83314b8d3..0188a721882 100644 --- a/server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts +++ b/server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts @@ -46,7 +46,7 @@ import { NoticeType } from '../../types/users'; import { getComponentForSourceViewer, getSources } from '../components'; import { getIssueFlowSnippets, searchIssues } from '../issues'; import { getRuleDetails } from '../rules'; -import { dismissNotification, getCurrentUser } from '../users'; +import { dismissNotice, getCurrentUser } from '../users'; function mockReferenceComponent(override?: Partial<ReferencedComponent>) { return { @@ -200,7 +200,7 @@ export default class IssuesServiceMock { this.handleGetComponentForSourceViewer ); (getCurrentUser as jest.Mock).mockImplementation(this.handleGetCurrentUser); - (dismissNotification as jest.Mock).mockImplementation(this.handleDismissNotification); + (dismissNotice as jest.Mock).mockImplementation(this.handleDismissNotification); } async getStandards(): Promise<Standards> { diff --git a/server/sonar-web/src/main/js/api/users.ts b/server/sonar-web/src/main/js/api/users.ts index dae13297e64..a5154d91839 100644 --- a/server/sonar-web/src/main/js/api/users.ts +++ b/server/sonar-web/src/main/js/api/users.ts @@ -26,7 +26,7 @@ export function getCurrentUser(): Promise<CurrentUser> { return getJSON('/api/users/current', undefined, true); } -export function dismissNotification(notice: NoticeType) { +export function dismissNotice(notice: NoticeType) { return post('/api/users/dismiss_notice', { notice }).catch(throwGlobalError); } @@ -99,7 +99,3 @@ export function deactivateUser(data: { login: string }): Promise<User> { export function setHomePage(homepage: HomePage): Promise<void | Response> { return post('/api/users/set_homepage', homepage).catch(throwGlobalError); } - -export function dismissSonarlintAd(): Promise<void | Response> { - return post('/api/users/dismiss_sonarlint_ad').catch(throwGlobalError); -} diff --git a/server/sonar-web/src/main/js/app/components/current-user/CurrentUserContext.ts b/server/sonar-web/src/main/js/app/components/current-user/CurrentUserContext.ts index efb10534705..ec40a19e7bb 100644 --- a/server/sonar-web/src/main/js/app/components/current-user/CurrentUserContext.ts +++ b/server/sonar-web/src/main/js/app/components/current-user/CurrentUserContext.ts @@ -18,12 +18,12 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import { CurrentUser, HomePage } from '../../../types/users'; +import { CurrentUser, HomePage, NoticeType } from '../../../types/users'; export interface CurrentUserContextInterface { currentUser: CurrentUser; updateCurrentUserHomepage: (homepage: HomePage) => void; - updateCurrentUserSonarLintAdSeen: () => void; + updateDismissedNotices: (key: NoticeType, value: boolean) => void; } export const CurrentUserContext = React.createContext<CurrentUserContextInterface | undefined>( diff --git a/server/sonar-web/src/main/js/app/components/current-user/CurrentUserContextProvider.tsx b/server/sonar-web/src/main/js/app/components/current-user/CurrentUserContextProvider.tsx index fb70ec97b91..2ec9e3de4f7 100644 --- a/server/sonar-web/src/main/js/app/components/current-user/CurrentUserContextProvider.tsx +++ b/server/sonar-web/src/main/js/app/components/current-user/CurrentUserContextProvider.tsx @@ -18,7 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import { CurrentUser, HomePage } from '../../../types/users'; +import { CurrentUser, HomePage, NoticeType } from '../../../types/users'; import { CurrentUserContext } from './CurrentUserContext'; interface Props { @@ -41,9 +41,12 @@ export default class CurrentUserContextProvider extends React.PureComponent<Prop })); }; - updateCurrentUserSonarLintAdSeen = () => { + updateDismissedNotices = (key: NoticeType, value: boolean) => { this.setState(prevState => ({ - currentUser: { ...prevState.currentUser, sonarLintAdSeen: true } + currentUser: { + ...prevState.currentUser, + dismissedNotices: { ...prevState.currentUser.dismissedNotices, [key]: value } + } })); }; @@ -53,7 +56,7 @@ export default class CurrentUserContextProvider extends React.PureComponent<Prop value={{ currentUser: this.state.currentUser, updateCurrentUserHomepage: this.updateCurrentUserHomepage, - updateCurrentUserSonarLintAdSeen: this.updateCurrentUserSonarLintAdSeen + updateDismissedNotices: this.updateDismissedNotices }}> {this.props.children} </CurrentUserContext.Provider> diff --git a/server/sonar-web/src/main/js/app/components/promotion-notification/PromotionNotification.tsx b/server/sonar-web/src/main/js/app/components/promotion-notification/PromotionNotification.tsx index 924b1da4cae..e4f4934f5ba 100644 --- a/server/sonar-web/src/main/js/app/components/promotion-notification/PromotionNotification.tsx +++ b/server/sonar-web/src/main/js/app/components/promotion-notification/PromotionNotification.tsx @@ -18,28 +18,32 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import { dismissSonarlintAd } from '../../../api/users'; +import { dismissNotice } from '../../../api/users'; import { ButtonLink } from '../../../components/controls/buttons'; import { translate } from '../../../helpers/l10n'; import { getBaseUrl } from '../../../helpers/system'; -import { isLoggedIn } from '../../../types/users'; +import { isLoggedIn, NoticeType } from '../../../types/users'; import { CurrentUserContextInterface } from '../current-user/CurrentUserContext'; import withCurrentUserContext from '../current-user/withCurrentUserContext'; import './PromotionNotification.css'; -export interface PromotionNotificationProps - extends Pick<CurrentUserContextInterface, 'currentUser' | 'updateCurrentUserSonarLintAdSeen'> {} +export interface PromotionNotificationProps extends CurrentUserContextInterface {} export function PromotionNotification(props: PromotionNotificationProps) { const { currentUser } = props; - if (!isLoggedIn(currentUser) || currentUser.sonarLintAdSeen) { + if (!isLoggedIn(currentUser) || currentUser.dismissedNotices[NoticeType.SONARLINT_AD]) { return null; } const onClick = () => { - dismissSonarlintAd(); - props.updateCurrentUserSonarLintAdSeen(); + dismissNotice(NoticeType.SONARLINT_AD) + .then(() => { + props.updateDismissedNotices(NoticeType.SONARLINT_AD, true); + }) + .catch(() => { + /* noop */ + }); }; return ( diff --git a/server/sonar-web/src/main/js/app/components/promotion-notification/__tests__/PromotionNotification-test.tsx b/server/sonar-web/src/main/js/app/components/promotion-notification/__tests__/PromotionNotification-test.tsx index 059d0560dbf..e686ef86c74 100644 --- a/server/sonar-web/src/main/js/app/components/promotion-notification/__tests__/PromotionNotification-test.tsx +++ b/server/sonar-web/src/main/js/app/components/promotion-notification/__tests__/PromotionNotification-test.tsx @@ -19,12 +19,14 @@ */ import { shallow } from 'enzyme'; import React from 'react'; -import { dismissSonarlintAd } from '../../../../api/users'; +import { dismissNotice } from '../../../../api/users'; import { mockCurrentUser, mockLoggedInUser } from '../../../../helpers/testMocks'; +import { waitAndUpdate } from '../../../../helpers/testUtils'; +import { NoticeType } from '../../../../types/users'; import { PromotionNotification, PromotionNotificationProps } from '../PromotionNotification'; jest.mock('../../../../api/users', () => ({ - dismissSonarlintAd: jest.fn() + dismissNotice: jest.fn().mockResolvedValue({}) })); beforeEach(() => { @@ -34,38 +36,43 @@ beforeEach(() => { it('should render correctly', () => { expect(shallowRender()).toMatchSnapshot('anonymous'); expect( - shallowRender({ currentUser: mockLoggedInUser({ sonarLintAdSeen: true }) }) + shallowRender({ + currentUser: mockLoggedInUser({ dismissedNotices: { [NoticeType.SONARLINT_AD]: true } }) + }) ).toMatchSnapshot('adAlreadySeen'); expect(shallowRender({ currentUser: mockLoggedInUser() })).toMatchSnapshot('loggedIn'); }); -it('should remove the toaster when click on dismiss', () => { - const updateCurrentUserSonarLintAdSeen = jest.fn(); +it('should remove the toaster when click on dismiss', async () => { + const updateDismissedNotices = jest.fn(); const wrapper = shallowRender({ - currentUser: mockLoggedInUser({ sonarLintAdSeen: false }), - updateCurrentUserSonarLintAdSeen + currentUser: mockLoggedInUser({ dismissedNotices: { [NoticeType.SONARLINT_AD]: false } }), + updateDismissedNotices }); wrapper.find('.toaster-actions ButtonLink').simulate('click'); - expect(dismissSonarlintAd).toBeCalled(); - expect(updateCurrentUserSonarLintAdSeen).toBeCalled(); + expect(dismissNotice).toBeCalled(); + await waitAndUpdate(wrapper); + expect(updateDismissedNotices).toBeCalled(); }); -it('should remove the toaster and navigate to sonarlint when click on learn more', () => { - const updateCurrentUserSonarLintAdSeen = jest.fn(); +it('should remove the toaster and navigate to sonarlint when click on learn more', async () => { + const updateDismissedNotices = jest.fn(); const wrapper = shallowRender({ - currentUser: mockLoggedInUser({ sonarLintAdSeen: false }), - updateCurrentUserSonarLintAdSeen + currentUser: mockLoggedInUser({ dismissedNotices: { [NoticeType.SONARLINT_AD]: false } }), + updateDismissedNotices }); wrapper.find('.toaster-actions .button-primary').simulate('click'); - expect(dismissSonarlintAd).toBeCalled(); - expect(updateCurrentUserSonarLintAdSeen).toBeCalled(); + expect(dismissNotice).toBeCalled(); + await waitAndUpdate(wrapper); + expect(updateDismissedNotices).toBeCalled(); }); function shallowRender(props: Partial<PromotionNotificationProps> = {}) { return shallow( <PromotionNotification currentUser={mockCurrentUser()} - updateCurrentUserSonarLintAdSeen={jest.fn()} + updateDismissedNotices={jest.fn()} + updateCurrentUserHomepage={jest.fn()} {...props} /> ); diff --git a/server/sonar-web/src/main/js/components/hoc/__tests__/whenLoggedIn-test.tsx b/server/sonar-web/src/main/js/components/hoc/__tests__/whenLoggedIn-test.tsx index fef59735274..94948c10831 100644 --- a/server/sonar-web/src/main/js/components/hoc/__tests__/whenLoggedIn-test.tsx +++ b/server/sonar-web/src/main/js/components/hoc/__tests__/whenLoggedIn-test.tsx @@ -57,7 +57,7 @@ function shallowRender(isLoggedIn = true) { value={{ currentUser: { isLoggedIn, dismissedNotices: {} }, updateCurrentUserHomepage: () => {}, - updateCurrentUserSonarLintAdSeen: () => {} + updateDismissedNotices: () => {} }}> <UnderTest /> </CurrentUserContext.Provider> diff --git a/server/sonar-web/src/main/js/components/rules/TabViewer.tsx b/server/sonar-web/src/main/js/components/rules/TabViewer.tsx index 0d781222ee1..47c7ac8210c 100644 --- a/server/sonar-web/src/main/js/components/rules/TabViewer.tsx +++ b/server/sonar-web/src/main/js/components/rules/TabViewer.tsx @@ -20,7 +20,7 @@ import classNames from 'classnames'; import { debounce, Dictionary } from 'lodash'; import * as React from 'react'; -import { dismissNotification, getCurrentUser } from '../../api/users'; +import { dismissNotice, getCurrentUser } from '../../api/users'; import { RuleDescriptionSection, RuleDescriptionSections } from '../../apps/coding-rules/rule'; import { RuleDetails } from '../../types/types'; import { CurrentUser, NoticeType } from '../../types/users'; @@ -112,7 +112,7 @@ export default class TabViewer extends React.PureComponent<Props, State> { const rect = this.educationPrinciplesRef.current.getBoundingClientRect(); const isView = rect.top <= (window.innerHeight || document.documentElement.clientHeight); if (isView && this.showNotification) { - dismissNotification(NoticeType.EDUCATION_PRINCIPLES) + dismissNotice(NoticeType.EDUCATION_PRINCIPLES) .then(() => { document.removeEventListener('scroll', this.checkIfConceptIsVisible, { capture: true }); this.showNotification = false; diff --git a/server/sonar-web/src/main/js/types/users.ts b/server/sonar-web/src/main/js/types/users.ts index 00537801159..e8aeae81218 100644 --- a/server/sonar-web/src/main/js/types/users.ts +++ b/server/sonar-web/src/main/js/types/users.ts @@ -32,7 +32,7 @@ export interface Notice { export enum NoticeType { EDUCATION_PRINCIPLES = 'educationPrinciples', - SONARLINT_AD_SEEN = 'sonarlint_ad_seen' + SONARLINT_AD = 'sonarlintAd' } export interface LoggedInUser extends CurrentUser, UserActive { |