]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-21656 Migrate SonarLint promotion Notification to the new UI
authorJeremy Davis <jeremy.davis@sonarsource.com>
Mon, 4 Mar 2024 09:36:33 +0000 (10:36 +0100)
committersonartech <sonartech@sonarsource.com>
Tue, 5 Mar 2024 20:02:30 +0000 (20:02 +0000)
server/sonar-web/design-system/src/theme/light.ts
server/sonar-web/src/main/js/app/components/promotion-notification/PromotionNotification.css [deleted file]
server/sonar-web/src/main/js/app/components/promotion-notification/PromotionNotification.tsx
server/sonar-web/src/main/js/app/components/promotion-notification/__tests__/PromotionNotification-test.tsx [new file with mode: 0644]

index 7687214258a1853c90fe5c9c2dea18c33d3faef9..24c872e99e9295f5d8c159d6d482d39054a3d760 100644 (file)
@@ -646,6 +646,11 @@ export const lightTheme = {
     //education principles
     educationPrincipleBackground: COLORS.indigo[25],
     educationPrincipleBorder: COLORS.indigo[300],
+
+    // SonarLint PromotionNotification
+    promotionNotification: COLORS.blueGrey[35],
+    promotionNotificationBackground: COLORS.blueGrey[700],
+    promotionNotificationSeparator: COLORS.blueGrey[500],
   },
 
   // contrast colors to be used for text when using a color background with the same name
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
deleted file mode 100644 (file)
index 9d6bf18..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2024 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-.toaster {
-  position: fixed;
-  right: 10px;
-  bottom: 10px;
-  max-width: 550px;
-  background-color: var(--darkBackground);
-  z-index: var(--popupZIndex);
-  box-shadow: 1px 1px 5px 0px black;
-  color: var(--darkBackgroundFontColor);
-  border-radius: 4px;
-}
-
-.toaster-content {
-  border-right: 1px solid var(--darkBackgroundSeparator);
-}
-
-.toaster-link {
-  color: var(--darkBackgroundFontColor);
-}
index cd423d7a9122209ffcdc320f49a8a04cd4e82276..b47b0ebf58848b56024e9a58dcd3cddd1b2ebe3e 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+import styled from '@emotion/styled';
+import { ButtonPrimary, ButtonSecondary, themeBorder, themeColor } from 'design-system';
 import * as React from 'react';
 import { dismissNotice } from '../../../api/users';
-import { ButtonLink } from '../../../components/controls/buttons';
 import { translate } from '../../../helpers/l10n';
 import { getBaseUrl } from '../../../helpers/system';
-import { isLoggedIn, NoticeType } from '../../../types/users';
+import { NoticeType, isLoggedIn } from '../../../types/users';
 import { CurrentUserContextInterface } from '../current-user/CurrentUserContext';
 import withCurrentUserContext from '../current-user/withCurrentUserContext';
-import './PromotionNotification.css';
 
 export function PromotionNotification(props: CurrentUserContextInterface) {
-  const { currentUser } = props;
+  const { currentUser, updateDismissedNotices } = props;
 
-  if (!isLoggedIn(currentUser) || currentUser.dismissedNotices[NoticeType.SONARLINT_AD]) {
-    return null;
-  }
-
-  const onClick = () => {
-    dismissNotice(NoticeType.SONARLINT_AD)
+  const onClick = React.useCallback(() => {
+    return dismissNotice(NoticeType.SONARLINT_AD)
       .then(() => {
-        props.updateDismissedNotices(NoticeType.SONARLINT_AD, true);
+        updateDismissedNotices(NoticeType.SONARLINT_AD, true);
       })
       .catch(() => {
         /* noop */
       });
-  };
+  }, [updateDismissedNotices]);
+
+  if (!isLoggedIn(currentUser) || currentUser.dismissedNotices[NoticeType.SONARLINT_AD]) {
+    return null;
+  }
 
   return (
-    <div className="toaster sw-flex sw-items-center sw-px-4">
+    <PromotionNotificationWrapper className="it__promotion_notification sw-z-global-popup sw-rounded-1 sw-flex sw-items-center sw-px-4">
       <div className="sw-mr-2">
         <img alt="SonarQube + SonarLint" height={80} src={`${getBaseUrl()}/images/sq-sl.svg`} />
       </div>
-      <div className="toaster-content sw-flex-1 sw-px-2 sw-py-4">
+      <PromotionNotificationContent className="sw-flex-1 sw-px-2 sw-py-4">
         <span className="sw-body-sm-highlight">{translate('promotion.sonarlint.title')}</span>
         <p className="sw-mt-2">{translate('promotion.sonarlint.content')}</p>
-      </div>
-      <div className="toaster-actions spacer-left padded-left display-flex-column display-flex-center">
-        <a
-          className="button button-primary sw-mb-4"
-          href="https://www.sonarsource.com/products/sonarlint/?referrer=sonarqube-welcome"
-          rel="noreferrer"
+      </PromotionNotificationContent>
+      <div className="sw-ml-2 sw-pl-2 sw-flex sw-flex-col sw-items-stretch">
+        <ButtonPrimary
+          className="sw-mb-4"
+          to="https://www.sonarsource.com/products/sonarlint/?referrer=sonarqube-welcome"
           onClick={onClick}
-          target="_blank"
         >
           {translate('learn_more')}
-        </a>
-        <ButtonLink className="toaster-link" onClick={onClick}>
+        </ButtonPrimary>
+        <ButtonSecondary className="sw-justify-center" onClick={onClick}>
           {translate('dismiss')}
-        </ButtonLink>
+        </ButtonSecondary>
       </div>
-    </div>
+    </PromotionNotificationWrapper>
   );
 }
 
 export default withCurrentUserContext(PromotionNotification);
+
+const PromotionNotificationWrapper = styled.div`
+  position: fixed;
+  right: 10px;
+  bottom: 10px;
+  max-width: 600px;
+  box-shadow: 1px 1px 5px 0px black;
+
+  background: ${themeColor('promotionNotificationBackground')};
+  color: ${themeColor('promotionNotification')};
+`;
+
+const PromotionNotificationContent = styled.div`
+  border-right: ${themeBorder('default', 'promotionNotificationSeparator')};
+`;
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
new file mode 100644 (file)
index 0000000..723c55e
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import userEvent from '@testing-library/user-event';
+import * as React from 'react';
+import { dismissNotice } from '../../../../api/users';
+import { mockCurrentUser, mockLoggedInUser } from '../../../../helpers/testMocks';
+import { renderComponent } from '../../../../helpers/testReactTestingUtils';
+import { byRole, byText } from '../../../../helpers/testSelector';
+import { CurrentUser, NoticeType } from '../../../../types/users';
+import PromotionNotification from '../PromotionNotification';
+
+jest.mock('../../../../api/users', () => ({
+  dismissNotice: jest.fn().mockResolvedValue(undefined),
+}));
+
+it('should not render when anonymous', () => {
+  renderPromotionNotification(mockCurrentUser({ isLoggedIn: false }));
+
+  expect(byText('promotion.sonarlint.title').query()).not.toBeInTheDocument();
+});
+
+it('should not render if previously dismissed', () => {
+  renderPromotionNotification(
+    mockLoggedInUser({ dismissedNotices: { [NoticeType.SONARLINT_AD]: true } }),
+  );
+
+  expect(byText('promotion.sonarlint.title').query()).not.toBeInTheDocument();
+});
+
+it('should be dismissable', async () => {
+  const user = userEvent.setup();
+
+  renderPromotionNotification();
+
+  expect(byText('promotion.sonarlint.title').get()).toBeInTheDocument();
+  const dismissButton = byRole('button', { name: 'dismiss' }).get();
+
+  expect(dismissButton).toBeInTheDocument();
+  await user.click(dismissButton);
+
+  expect(dismissNotice).toHaveBeenCalledWith(NoticeType.SONARLINT_AD);
+});
+
+function renderPromotionNotification(currentUser: CurrentUser = mockLoggedInUser()) {
+  return renderComponent(<PromotionNotification />, '', {
+    currentUser,
+  });
+}