]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-19018 Migrate the report actions to the new UI
authorJeremy Davis <jeremy.davis@sonarsource.com>
Fri, 14 Apr 2023 09:43:22 +0000 (11:43 +0200)
committersonartech <sonartech@sonarsource.com>
Tue, 25 Apr 2023 20:03:01 +0000 (20:03 +0000)
server/sonar-web/src/main/js/apps/overview/branches/BranchOverviewRenderer.tsx
server/sonar-web/src/main/js/components/controls/ComponentReportActionsRenderer.tsx
server/sonar-web/src/main/js/components/controls/__tests__/ComponentReportActions-test.tsx
server/sonar-web/src/main/js/components/controls/__tests__/ComponentReportActionsRenderer-test.tsx [deleted file]
server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/ComponentReportActionsRenderer-test.tsx.snap [deleted file]

index fa9ea15bc17711fe2b109c5c11de46dacc720e57..5cc97dbd2b6db71bba55ae395f2508265d9eb212 100644 (file)
@@ -17,6 +17,7 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+import { LargeCenteredLayout } from 'design-system';
 import * as React from 'react';
 import A11ySkipTarget from '../../../components/a11y/A11ySkipTarget';
 import { parseDate } from '../../../helpers/dates';
@@ -84,8 +85,8 @@ export default function BranchOverviewRenderer(props: BranchOverviewRendererProp
         detectedCIOnLastAnalysis={detectedCIOnLastAnalysis}
         projectBinding={projectBinding}
       />
-      <div className="page page-limited">
-        <div className="overview">
+      <LargeCenteredLayout>
+        <div className="overview sw-mt-6">
           <A11ySkipTarget anchor="overview_main" />
 
           {projectIsEmpty ? (
@@ -127,7 +128,7 @@ export default function BranchOverviewRenderer(props: BranchOverviewRendererProp
             </div>
           )}
         </div>
-      </div>
+      </LargeCenteredLayout>
     </>
   );
 }
index c2deafcd193ba5564c323f3ba7a203e7e7ee08d7..8bea9571e5c677be9dfac165bc7d5380afcd7a7f 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 {
+  ButtonSecondary,
+  ChevronDownIcon,
+  Dropdown,
+  ItemButton,
+  ItemDownload,
+  PopupPlacement,
+  PopupZLevel,
+} from 'design-system';
 import * as React from 'react';
 import { getReportUrl } from '../../api/component-report';
-import Dropdown from '../../components/controls/Dropdown';
-import { Button } from '../../components/controls/buttons';
-import DropdownIcon from '../../components/icons/DropdownIcon';
 import { translate, translateWithParameters } from '../../helpers/l10n';
 import { Branch } from '../../types/branch-like';
 import { Component } from '../../types/types';
@@ -37,65 +43,69 @@ export interface ComponentReportActionsRendererProps {
   handleUnsubscription: () => void;
 }
 
-export default function ComponentReportActionsRenderer(props: ComponentReportActionsRendererProps) {
-  const { branch, component, frequency, subscribed, canSubscribe, currentUserHasEmail } = props;
+const getSubscriptionText = ({
+  currentUserHasEmail,
+  frequency,
+  subscribed,
+}: Pick<
+  ComponentReportActionsRendererProps,
+  'currentUserHasEmail' | 'frequency' | 'subscribed'
+>) => {
+  if (!currentUserHasEmail) {
+    return translate('component_report.no_email_to_subscribe');
+  }
 
-  const renderDownloadButton = (simple = false) => {
-    return (
-      <a
-        download={[component.name, branch?.name, 'PDF Report.pdf'].filter((s) => !!s).join(' - ')}
-        href={getReportUrl(component.key, branch?.name)}
-        target="_blank"
-        rel="noopener noreferrer"
-      >
-        {simple
-          ? translate('download_verb')
-          : translateWithParameters(
-              'component_report.download',
-              translate('qualifier', component.qualifier).toLowerCase()
-            )}
-      </a>
-    );
-  };
+  const translationKey = subscribed
+    ? 'component_report.unsubscribe_x'
+    : 'component_report.subscribe_x';
+  const frequencyTranslation = translate('report.frequency', frequency).toLowerCase();
 
-  const renderSubscriptionButton = () => {
-    if (!currentUserHasEmail) {
-      return (
-        <span className="text-muted-2">{translate('component_report.no_email_to_subscribe')}</span>
-      );
-    }
+  return translateWithParameters(translationKey, frequencyTranslation);
+};
 
-    const translationKey = subscribed
-      ? 'component_report.unsubscribe_x'
-      : 'component_report.subscribe_x';
-    const onClickHandler = subscribed ? props.handleUnsubscription : props.handleSubscription;
-    const frequencyTranslation = translate('report.frequency', frequency).toLowerCase();
+export default function ComponentReportActionsRenderer(props: ComponentReportActionsRendererProps) {
+  const { branch, component, frequency, subscribed, canSubscribe, currentUserHasEmail } = props;
 
-    return (
-      <a href="#" onClick={onClickHandler} data-test="overview__subscribe-to-report-button">
-        {translateWithParameters(translationKey, frequencyTranslation)}
-      </a>
-    );
-  };
+  const downloadName = [component.name, branch?.name, 'PDF Report.pdf']
+    .filter((s) => !!s)
+    .join(' - ');
+  const reportUrl = getReportUrl(component.key, branch?.name);
 
   return canSubscribe ? (
     <Dropdown
+      id="component-report"
+      size="auto"
+      placement={PopupPlacement.BottomRight}
+      zLevel={PopupZLevel.Default}
       overlay={
-        <ul className="menu">
-          <li>{renderDownloadButton(true)}</li>
-          <li>{renderSubscriptionButton()}</li>
-        </ul>
+        <>
+          <ItemDownload download={downloadName} href={reportUrl}>
+            {translate('download_verb')}
+          </ItemDownload>
+          <ItemButton
+            disabled={!currentUserHasEmail}
+            data-test="overview__subscribe-to-report-button"
+            onClick={subscribed ? props.handleUnsubscription : props.handleSubscription}
+          >
+            {getSubscriptionText({ currentUserHasEmail, frequency, subscribed })}
+          </ItemButton>
+        </>
       }
     >
-      <Button className="dropdown-toggle">
+      <ButtonSecondary>
         {translateWithParameters(
           'component_report.report',
           translate('qualifier', component.qualifier)
         )}
-        <DropdownIcon className="spacer-left" />
-      </Button>
+        <ChevronDownIcon className="sw-ml-1" />
+      </ButtonSecondary>
     </Dropdown>
   ) : (
-    renderDownloadButton()
+    <a download={downloadName} href={reportUrl} target="_blank" rel="noopener noreferrer">
+      {translateWithParameters(
+        'component_report.download',
+        translate('qualifier', component.qualifier).toLowerCase()
+      )}
+    </a>
   );
 }
index 5bcf7725e7e4d98ff411d9e4f0d93bacce7551dd..aaf3982d86ca6ff97c8557862aa819fb14b48d5c 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 { shallow } from 'enzyme';
+import { screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
 import * as React from 'react';
 import {
   getReportStatus,
   subscribeToEmailReport,
   unsubscribeFromEmailReport,
 } from '../../../api/component-report';
-import { addGlobalSuccessMessage } from '../../../helpers/globalMessages';
 import { mockBranch } from '../../../helpers/mocks/branch-like';
 import { mockComponent } from '../../../helpers/mocks/component';
 import { mockComponentReportStatus } from '../../../helpers/mocks/component-report';
-import { mockAppState, mockCurrentUser } from '../../../helpers/testMocks';
-import { waitAndUpdate } from '../../../helpers/testUtils';
+import { mockAppState, mockCurrentUser, mockLoggedInUser } from '../../../helpers/testMocks';
+import { renderApp } from '../../../helpers/testReactTestingUtils';
 import { ComponentQualifier } from '../../../types/component';
 import { ComponentReportActions } from '../ComponentReportActions';
 
@@ -49,76 +49,114 @@ jest.mock('../../../helpers/system', () => ({
   getBaseUrl: jest.fn().mockReturnValue('baseUrl'),
 }));
 
-jest.mock('../../../helpers/globalMessages', () => ({
-  addGlobalSuccessMessage: jest.fn(),
-}));
-
 beforeEach(jest.clearAllMocks);
 
-it('should not render anything', async () => {
-  // loading
-  expect(shallowRender().type()).toBeNull();
+it('should not render anything when no status', async () => {
+  jest.mocked(getReportStatus).mockRejectedValueOnce('Nope');
+
+  renderComponentReportActions();
+
+  // Loading
+  expect(screen.queryByRole('button')).not.toBeInTheDocument();
+
+  await new Promise(setImmediate); // Make sure we wait until we're done loading
 
   // No status
-  (getReportStatus as jest.Mock).mockResolvedValueOnce(undefined);
-  const w1 = shallowRender();
-  await waitAndUpdate(w1);
-  expect(w1.type()).toBeNull();
-
-  // Branch purgeable
-  const w2 = shallowRender({ branch: mockBranch({ excludedFromPurge: false }) });
-  await waitAndUpdate(w2);
-  expect(w2.type()).toBeNull();
-
-  // no governance
-  const w3 = shallowRender({ appState: mockAppState({ qualifiers: [] }) });
-  await waitAndUpdate(w3);
-  expect(w3.type()).toBeNull();
+  expect(screen.queryByRole('button')).not.toBeInTheDocument();
 });
 
-it('should call for status properly', async () => {
-  const component = mockComponent();
-  const branch = mockBranch();
+it('should not render anything when branch is purgeable', async () => {
+  renderComponentReportActions({
+    branch: mockBranch({ excludedFromPurge: false }),
+  });
 
-  const wrapper = shallowRender({ component, branch });
+  await new Promise(setImmediate); // Make sure we wait until we're done loading
 
-  await waitAndUpdate(wrapper);
+  expect(screen.queryByRole('button')).not.toBeInTheDocument();
+});
 
-  expect(getReportStatus).toHaveBeenCalledWith(component.key, branch.name);
+it('should not render anything without governance', async () => {
+  renderComponentReportActions({ appState: mockAppState({ qualifiers: [] }) });
+
+  await new Promise(setImmediate); // Make sure we wait until we're done loading
+
+  expect(screen.queryByRole('button')).not.toBeInTheDocument();
 });
 
-it('should handle subscription', async () => {
+it('should allow user to (un)subscribe', async () => {
+  jest
+    .mocked(getReportStatus)
+    .mockResolvedValueOnce(mockComponentReportStatus({ globalFrequency: 'monthly' }))
+    .mockResolvedValueOnce(
+      mockComponentReportStatus({ subscribed: true, globalFrequency: 'monthly' })
+    );
+
+  const user = userEvent.setup();
   const component = mockComponent();
   const branch = mockBranch();
-  const wrapper = shallowRender({ component, branch });
 
-  await wrapper.instance().handleSubscribe();
+  renderComponentReportActions({
+    component,
+    branch,
+    currentUser: mockLoggedInUser({ email: 'igot@nEmail.address' }),
+  });
+
+  expect(getReportStatus).toHaveBeenCalledWith(component.key, branch.name);
+
+  const button = await screen.findByRole('button', {
+    name: 'component_report.report.qualifier.TRK',
+  });
+  expect(button).toBeInTheDocument();
+  await user.click(button);
+
+  expect(screen.getByText('download_verb')).toBeInTheDocument();
+
+  // Subscribe!
+  const subscribeButton = screen.getByText('component_report.subscribe_x.report.frequency.monthly');
+  expect(subscribeButton).toBeInTheDocument();
+
+  await user.click(subscribeButton);
 
   expect(subscribeToEmailReport).toHaveBeenCalledWith(component.key, branch.name);
-  expect(addGlobalSuccessMessage).toHaveBeenCalledWith(
-    'component_report.subscribe_x_success.report.frequency..qualifier.trk'
-  );
-});
+  expect(await screen.findByRole('status')).toBeInTheDocument();
 
-it('should handle unsubscription', async () => {
-  const component = mockComponent();
-  const branch = mockBranch();
-  const wrapper = shallowRender({ component, branch });
+  await new Promise(setImmediate);
 
-  await waitAndUpdate(wrapper);
+  // And unsubscribe!
+  await user.click(button);
 
-  wrapper.setState({ status: mockComponentReportStatus({ componentFrequency: 'compfreq' }) });
+  const unsubscribeButton = screen.getByText(
+    'component_report.unsubscribe_x.report.frequency.monthly'
+  );
+  expect(unsubscribeButton).toBeInTheDocument();
 
-  await wrapper.instance().handleUnsubscribe();
+  await user.click(unsubscribeButton);
 
   expect(unsubscribeFromEmailReport).toHaveBeenCalledWith(component.key, branch.name);
-  expect(addGlobalSuccessMessage).toHaveBeenCalledWith(
-    'component_report.unsubscribe_x_success.report.frequency.compfreq.qualifier.trk'
+  expect(screen.getAllByRole('status')).toHaveLength(2);
+});
+
+it('should prevent user to subscribe if no email', async () => {
+  const user = userEvent.setup();
+
+  renderComponentReportActions({ currentUser: mockLoggedInUser({ email: undefined }) });
+
+  await new Promise(setImmediate);
+
+  await user.click(
+    await screen.findByRole('button', {
+      name: 'component_report.report.qualifier.TRK',
+    })
   );
+
+  const subscribeButton = screen.getByText('component_report.no_email_to_subscribe');
+  expect(subscribeButton).toBeInTheDocument();
+  expect(subscribeButton).toBeDisabled();
 });
 
-function shallowRender(props: Partial<ComponentReportActions['props']> = {}) {
-  return shallow<ComponentReportActions>(
+function renderComponentReportActions(props: Partial<ComponentReportActions['props']> = {}) {
+  return renderApp(
+    '/',
     <ComponentReportActions
       appState={mockAppState({ qualifiers: [ComponentQualifier.Portfolio] })}
       component={mockComponent()}
diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/ComponentReportActionsRenderer-test.tsx b/server/sonar-web/src/main/js/components/controls/__tests__/ComponentReportActionsRenderer-test.tsx
deleted file mode 100644 (file)
index fd3130e..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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 { shallow } from 'enzyme';
-import * as React from 'react';
-import { mockComponent } from '../../../helpers/mocks/component';
-import { ComponentQualifier } from '../../../types/component';
-import ComponentReportActionsRenderer, {
-  ComponentReportActionsRendererProps,
-} from '../ComponentReportActionsRenderer';
-
-it('should render correctly', () => {
-  expect(shallowRender({ canSubscribe: false })).toMatchSnapshot('cannot subscribe');
-  expect(shallowRender({ canSubscribe: true, subscribed: false })).toMatchSnapshot(
-    'can subscribe, not subscribed'
-  );
-  expect(shallowRender({ canSubscribe: true, subscribed: true })).toMatchSnapshot(
-    'can subscribe, subscribed'
-  );
-  expect(shallowRender({ canSubscribe: true, currentUserHasEmail: false })).toMatchSnapshot(
-    'current user without email'
-  );
-  expect(shallowRender({ component: mockComponent() })).toMatchSnapshot('not a portfolio');
-});
-
-function shallowRender(props: Partial<ComponentReportActionsRendererProps> = {}) {
-  return shallow<ComponentReportActionsRendererProps>(
-    <ComponentReportActionsRenderer
-      component={mockComponent({ qualifier: ComponentQualifier.Portfolio })}
-      canSubscribe={true}
-      subscribed={false}
-      currentUserHasEmail={true}
-      frequency="weekly"
-      handleSubscription={jest.fn()}
-      handleUnsubscription={jest.fn()}
-      {...props}
-    />
-  );
-}
diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/ComponentReportActionsRenderer-test.tsx.snap b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/ComponentReportActionsRenderer-test.tsx.snap
deleted file mode 100644 (file)
index 65a2215..0000000
+++ /dev/null
@@ -1,166 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly: can subscribe, not subscribed 1`] = `
-<Dropdown
-  overlay={
-    <ul
-      className="menu"
-    >
-      <li>
-        <a
-          download="MyProject - PDF Report.pdf"
-          href="/api/governance_reports/download?componentKey=my-project"
-          rel="noopener noreferrer"
-          target="_blank"
-        >
-          download_verb
-        </a>
-      </li>
-      <li>
-        <a
-          data-test="overview__subscribe-to-report-button"
-          href="#"
-          onClick={[MockFunction]}
-        >
-          component_report.subscribe_x.report.frequency.weekly
-        </a>
-      </li>
-    </ul>
-  }
->
-  <Button
-    className="dropdown-toggle"
-  >
-    component_report.report.qualifier.VW
-    <DropdownIcon
-      className="spacer-left"
-    />
-  </Button>
-</Dropdown>
-`;
-
-exports[`should render correctly: can subscribe, subscribed 1`] = `
-<Dropdown
-  overlay={
-    <ul
-      className="menu"
-    >
-      <li>
-        <a
-          download="MyProject - PDF Report.pdf"
-          href="/api/governance_reports/download?componentKey=my-project"
-          rel="noopener noreferrer"
-          target="_blank"
-        >
-          download_verb
-        </a>
-      </li>
-      <li>
-        <a
-          data-test="overview__subscribe-to-report-button"
-          href="#"
-          onClick={[MockFunction]}
-        >
-          component_report.unsubscribe_x.report.frequency.weekly
-        </a>
-      </li>
-    </ul>
-  }
->
-  <Button
-    className="dropdown-toggle"
-  >
-    component_report.report.qualifier.VW
-    <DropdownIcon
-      className="spacer-left"
-    />
-  </Button>
-</Dropdown>
-`;
-
-exports[`should render correctly: cannot subscribe 1`] = `
-<a
-  download="MyProject - PDF Report.pdf"
-  href="/api/governance_reports/download?componentKey=my-project"
-  rel="noopener noreferrer"
-  target="_blank"
->
-  component_report.download.qualifier.vw
-</a>
-`;
-
-exports[`should render correctly: current user without email 1`] = `
-<Dropdown
-  overlay={
-    <ul
-      className="menu"
-    >
-      <li>
-        <a
-          download="MyProject - PDF Report.pdf"
-          href="/api/governance_reports/download?componentKey=my-project"
-          rel="noopener noreferrer"
-          target="_blank"
-        >
-          download_verb
-        </a>
-      </li>
-      <li>
-        <span
-          className="text-muted-2"
-        >
-          component_report.no_email_to_subscribe
-        </span>
-      </li>
-    </ul>
-  }
->
-  <Button
-    className="dropdown-toggle"
-  >
-    component_report.report.qualifier.VW
-    <DropdownIcon
-      className="spacer-left"
-    />
-  </Button>
-</Dropdown>
-`;
-
-exports[`should render correctly: not a portfolio 1`] = `
-<Dropdown
-  overlay={
-    <ul
-      className="menu"
-    >
-      <li>
-        <a
-          download="MyProject - PDF Report.pdf"
-          href="/api/governance_reports/download?componentKey=my-project"
-          rel="noopener noreferrer"
-          target="_blank"
-        >
-          download_verb
-        </a>
-      </li>
-      <li>
-        <a
-          data-test="overview__subscribe-to-report-button"
-          href="#"
-          onClick={[MockFunction]}
-        >
-          component_report.subscribe_x.report.frequency.weekly
-        </a>
-      </li>
-    </ul>
-  }
->
-  <Button
-    className="dropdown-toggle"
-  >
-    component_report.report.qualifier.TRK
-    <DropdownIcon
-      className="spacer-left"
-    />
-  </Button>
-</Dropdown>
-`;