diff options
Diffstat (limited to 'server/sonar-web/src')
8 files changed, 98 insertions, 15 deletions
diff --git a/server/sonar-web/src/main/js/api/users.ts b/server/sonar-web/src/main/js/api/users.ts index 9d8282ba5be..73cee845733 100644 --- a/server/sonar-web/src/main/js/api/users.ts +++ b/server/sonar-web/src/main/js/api/users.ts @@ -97,3 +97,7 @@ export function setHomePage(homepage: T.HomePage): Promise<void | Response> { export function setUserSetting(setting: T.CurrentUserSetting): Promise<void | Response> { return post('/api/users/set_setting', setting).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/__tests__/__snapshots__/GlobalContainer-test.tsx.snap b/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalContainer-test.tsx.snap index 03e12641665..5f47fc1d4a2 100644 --- a/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalContainer-test.tsx.snap +++ b/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalContainer-test.tsx.snap @@ -37,7 +37,7 @@ exports[`should render correctly 1`] = ` </Connect(withAppState(IndexationContextProvider))> </Workspace> </div> - <Connect(withCurrentUser(PromotionNotification)) /> + <Connect(Connect(withCurrentUser(PromotionNotification))) /> </div> <Connect(GlobalFooter) /> </div> diff --git a/server/sonar-web/src/main/js/app/components/promotion-notification/PromotionNotification.css b/server/sonar-web/src/main/js/app/components/promotion-notification/PromotionNotification.css index 52217e8226d..313f1500416 100644 --- a/server/sonar-web/src/main/js/app/components/promotion-notification/PromotionNotification.css +++ b/server/sonar-web/src/main/js/app/components/promotion-notification/PromotionNotification.css @@ -32,3 +32,7 @@ .toaster-content { border-right: 1px solid var(--darkBackgroundSeparator); } + +.toaster-link { + color: var(--darkBackgroundFontColor); +} 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 92765402c3c..3e907843a69 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,12 +18,17 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; +import { connect } from 'react-redux'; +import { dismissSonarlintAd } from '../../../api/users'; +import { ButtonLink } from '../../../components/controls/buttons'; import { withCurrentUser } from '../../../components/hoc/withCurrentUser'; import { translate } from '../../../helpers/l10n'; import { isLoggedIn } from '../../../helpers/users'; +import { setSonarlintAd } from '../../../store/users'; import './PromotionNotification.css'; export interface PromotionNotificationProps { + setSonarlintAd: () => void; currentUser: T.CurrentUser; } @@ -34,28 +39,39 @@ export function PromotionNotification(props: PromotionNotificationProps) { return null; } + const onClick = () => { + dismissSonarlintAd(); + props.setSonarlintAd(); + }; + return ( - <div className="toaster display-flex-center big-padded"> + <div className="toaster display-flex-center big-padded-left big-padded-right"> <div className="toaster-icon spacer-right"> <img alt="SonarQube + SonarLint" src="/images/sq-sl.png" /> </div> - <div className="toaster-content flex-1 padded-left padded-right"> + <div className="toaster-content flex-1 padded-left padded-right big-padded-top big-padded-bottom"> <span className="toaster-title text-bold medium"> {translate('promotion.sonarlint.title')} </span> <p className="spacer-top">{translate('promotion.sonarlint.content')}</p> </div> - <div className="toaster-actions spacer-left padded-left"> + <div className="toaster-actions spacer-left padded-left display-flex-column display-flex-center"> <a - className="button" - href="https://www.sonarqube.org/sonarlint/" + className="button button-primary big-spacer-bottom" + href="https://www.sonarqube.org/sonarlint/?referrer=sonarqube-welcome" rel="noreferrer" + onClick={onClick} target="_blank"> {translate('learn_more')} </a> + <ButtonLink className="toaster-link" onClick={onClick}> + {translate('dismiss')} + </ButtonLink> </div> </div> ); } -export default withCurrentUser(PromotionNotification); +const dispatchToProps = { setSonarlintAd }; + +export default connect(null, dispatchToProps)(withCurrentUser(PromotionNotification)); 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 cbc90b77fc1..1168776e576 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 @@ -20,9 +20,18 @@ import { shallow } from 'enzyme'; import React from 'react'; +import { dismissSonarlintAd } from '../../../../api/users'; import { mockCurrentUser, mockLoggedInUser } from '../../../../helpers/testMocks'; import { PromotionNotification, PromotionNotificationProps } from '../PromotionNotification'; +jest.mock('../../../../api/users', () => ({ + dismissSonarlintAd: jest.fn() +})); + +beforeEach(() => { + jest.clearAllMocks(); +}); + it('should render correctly', () => { expect(shallowRender()).toMatchSnapshot('anonymous'); expect( @@ -31,6 +40,30 @@ it('should render correctly', () => { expect(shallowRender({ currentUser: mockLoggedInUser() })).toMatchSnapshot('loggedIn'); }); +it('should remove the toaster when click on dismiss', () => { + const setSonarlintAd = jest.fn(); + const wrapper = shallowRender({ + currentUser: mockLoggedInUser({ sonarLintAdSeen: false }), + setSonarlintAd + }); + wrapper.find('.toaster-actions ButtonLink').simulate('click'); + expect(dismissSonarlintAd).toBeCalled(); + expect(setSonarlintAd).toBeCalled(); +}); + +it('should remove the toaster and navigate to sonarlint when click on learn more', () => { + const setSonarlintAd = jest.fn(); + const wrapper = shallowRender({ + currentUser: mockLoggedInUser({ sonarLintAdSeen: false }), + setSonarlintAd + }); + wrapper.find('.toaster-actions .button-primary').simulate('click'); + expect(dismissSonarlintAd).toBeCalled(); + expect(setSonarlintAd).toBeCalled(); +}); + function shallowRender(props: Partial<PromotionNotificationProps> = {}) { - return shallow(<PromotionNotification currentUser={mockCurrentUser()} {...props} />); + return shallow( + <PromotionNotification currentUser={mockCurrentUser()} setSonarlintAd={jest.fn()} {...props} /> + ); } diff --git a/server/sonar-web/src/main/js/app/components/promotion-notification/__tests__/__snapshots__/PromotionNotification-test.tsx.snap b/server/sonar-web/src/main/js/app/components/promotion-notification/__tests__/__snapshots__/PromotionNotification-test.tsx.snap index 2726d40d53e..d2daa0ec162 100644 --- a/server/sonar-web/src/main/js/app/components/promotion-notification/__tests__/__snapshots__/PromotionNotification-test.tsx.snap +++ b/server/sonar-web/src/main/js/app/components/promotion-notification/__tests__/__snapshots__/PromotionNotification-test.tsx.snap @@ -6,7 +6,7 @@ exports[`should render correctly: anonymous 1`] = `""`; exports[`should render correctly: loggedIn 1`] = ` <div - className="toaster display-flex-center big-padded" + className="toaster display-flex-center big-padded-left big-padded-right" > <div className="toaster-icon spacer-right" @@ -17,7 +17,7 @@ exports[`should render correctly: loggedIn 1`] = ` /> </div> <div - className="toaster-content flex-1 padded-left padded-right" + className="toaster-content flex-1 padded-left padded-right big-padded-top big-padded-bottom" > <span className="toaster-title text-bold medium" @@ -31,16 +31,23 @@ exports[`should render correctly: loggedIn 1`] = ` </p> </div> <div - className="toaster-actions spacer-left padded-left" + className="toaster-actions spacer-left padded-left display-flex-column display-flex-center" > <a - className="button" - href="https://www.sonarqube.org/sonarlint/" + className="button button-primary big-spacer-bottom" + href="https://www.sonarqube.org/sonarlint/?referrer=sonarqube-welcome" + onClick={[Function]} rel="noreferrer" target="_blank" > learn_more </a> + <ButtonLink + className="toaster-link" + onClick={[Function]} + > + dismiss + </ButtonLink> </div> </div> `; diff --git a/server/sonar-web/src/main/js/store/__tests__/users-test.tsx b/server/sonar-web/src/main/js/store/__tests__/users-test.tsx index cf2615fd0ab..2547c9aca88 100644 --- a/server/sonar-web/src/main/js/store/__tests__/users-test.tsx +++ b/server/sonar-web/src/main/js/store/__tests__/users-test.tsx @@ -19,6 +19,7 @@ */ import { mockCurrentUser, mockLoggedInUser, mockUser } from '../../helpers/testMocks'; +import { isLoggedIn } from '../../helpers/users'; import reducer, { getCurrentUser, getCurrentUserSetting, @@ -27,6 +28,7 @@ import reducer, { receiveCurrentUser, setCurrentUserSettingAction, setHomePageAction, + setSonarlintAd, State } from '../users'; @@ -76,6 +78,14 @@ describe('reducer and actions', () => { }) ); }); + + it('should allow to set the sonarLintAdSeen flag', () => { + const currentUser = mockLoggedInUser(); + const initialState: State = createState({ currentUser }); + + const newState = reducer(initialState, setSonarlintAd()); + expect(isLoggedIn(newState.currentUser) && newState.currentUser.sonarLintAdSeen).toBe(true); + }); }); describe('getters', () => { diff --git a/server/sonar-web/src/main/js/store/users.ts b/server/sonar-web/src/main/js/store/users.ts index 31b3a329eee..e637a551f63 100644 --- a/server/sonar-web/src/main/js/store/users.ts +++ b/server/sonar-web/src/main/js/store/users.ts @@ -26,13 +26,15 @@ import { ActionType } from './utils/actions'; const enum Actions { ReceiveCurrentUser = 'RECEIVE_CURRENT_USER', SetCurrentUserSetting = 'SET_CURRENT_USER_SETTING', - SetHomePageAction = 'SET_HOMEPAGE' + SetHomePageAction = 'SET_HOMEPAGE', + SetSonarlintAd = 'SET_SONARLINT_AD' } type Action = | ActionType<typeof receiveCurrentUser, Actions.ReceiveCurrentUser> | ActionType<typeof setCurrentUserSettingAction, Actions.SetCurrentUserSetting> - | ActionType<typeof setHomePageAction, Actions.SetHomePageAction>; + | ActionType<typeof setHomePageAction, Actions.SetHomePageAction> + | ActionType<typeof setSonarlintAd, Actions.SetSonarlintAd>; export interface State { usersByLogin: T.Dict<any>; @@ -52,6 +54,10 @@ export function setCurrentUserSettingAction(setting: T.CurrentUserSetting) { return { type: Actions.SetCurrentUserSetting, setting }; } +export function setSonarlintAd() { + return { type: Actions.SetSonarlintAd }; +} + export function setHomePage(homepage: T.HomePage) { return (dispatch: Dispatch) => { api.setHomePage(homepage).then( @@ -117,6 +123,9 @@ function currentUser( } return { ...state, settings } as T.LoggedInUser; } + if (action.type === Actions.SetSonarlintAd && isLoggedIn(state)) { + return { ...state, sonarLintAdSeen: true } as T.LoggedInUser; + } return state; } |