]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-19613 MIUI Regularity report
authorguillaume-peoch-sonarsource <guillaume.peoch@sonarsource.com>
Wed, 21 Jun 2023 12:28:24 +0000 (14:28 +0200)
committersonartech <sonartech@sonarsource.com>
Mon, 26 Jun 2023 20:03:55 +0000 (20:03 +0000)
server/sonar-web/design-system/src/components/buttons.tsx
server/sonar-web/src/main/js/apps/projectInformation/ProjectInformationApp.tsx
server/sonar-web/src/main/js/apps/projectInformation/projectRegulatoryReport/RegulatoryReport.tsx
server/sonar-web/src/main/js/apps/projectInformation/projectRegulatoryReport/RegulatoryReportModal.tsx [deleted file]
server/sonar-web/src/main/js/apps/projectInformation/projectRegulatoryReport/__tests__/RegulatoryReport-it.tsx
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index 6cb9c86f756a553501214c42fe2d8f1d0bc91f08..a92028afbfd42bcc58fb090bcf94762c9f3f0924 100644 (file)
@@ -158,12 +158,24 @@ const BaseButton = styled.button`
   }
 `;
 
+const PrimaryStyle = (props: ThemedProps) => css`
+  background: ${themeColor('button')(props)};
+  backgroundhover: ${themeColor('buttonHover')(props)};
+  color: ${themeContrast('primary')(props)};
+  focus: ${themeColor('button', OPACITY_20_PERCENT)(props)};
+  border: ${themeBorder('default', 'transparent')(props)};
+`;
+
 export const ButtonPrimary: React.FC<ButtonProps> = styled(Button)`
-  --background: ${themeColor('button')};
-  --backgroundHover: ${themeColor('buttonHover')};
-  --color: ${themeContrast('primary')};
-  --focus: ${themeColor('button', OPACITY_20_PERCENT)};
-  --border: ${themeBorder('default', 'transparent')};
+  ${PrimaryStyle}
+`;
+
+export const DownloadButton = styled.a`
+  ${buttonStyle}
+  ${PrimaryStyle}
+  &:hover {
+    border-bottom-color: transparent;
+  }
 `;
 
 export const ButtonSecondary: React.FC<ButtonProps> = styled(Button)`
index 08dabafccd23223734f8da10f0d3667fcd9bac09..765e19b795da05993bec67ccc73d2dc13949fc6b 100644 (file)
@@ -123,11 +123,7 @@ export class ProjectInformationApp extends React.PureComponent<Props, State> {
                 {component.qualifier === ComponentQualifier.Project &&
                   regulatoryReportFeatureEnabled && (
                     <Card>
-                      <RegulatoryReport
-                        component={component}
-                        branchLike={branchLike}
-                        onClose={() => {}}
-                      />
+                      <RegulatoryReport component={component} branchLike={branchLike} />
                     </Card>
                   )}
               </div>
index 59b3f860f6d4d1cbacbe4d144b871509cd2912e8..0a3da5446f4a5c2e11c34000b48731388cc1d7d5 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 classNames from 'classnames';
-import { orderBy } from 'lodash';
+import {
+  BasicSeparator,
+  DownloadButton,
+  FlagMessage,
+  FormField,
+  InputSelect,
+  SubTitle,
+} from 'design-system';
+import { isEmpty, orderBy } from 'lodash';
 import * as React from 'react';
+import { useState } from 'react';
 import { FormattedMessage } from 'react-intl';
 import { getBranches } from '../../../api/branches';
 import { getRegulatoryReportUrl } from '../../../api/regulatory-report';
 import DocLink from '../../../components/common/DocLink';
-import Select, { LabelValueSelectOption } from '../../../components/controls/Select';
-import { ButtonLink } from '../../../components/controls/buttons';
-import { Alert } from '../../../components/ui/Alert';
+import { LabelValueSelectOption } from '../../../components/controls/Select';
 import {
   getBranchLikeDisplayName,
   getBranchLikeKey,
@@ -39,30 +45,19 @@ import { Component } from '../../../types/types';
 interface Props {
   component: Pick<Component, 'key' | 'name'>;
   branchLike?: BranchLike;
-  onClose: () => void;
 }
 
-interface State {
-  downloadStarted: boolean;
-  selectedBranch: string;
-  branchOptions: LabelValueSelectOption[];
-}
+export default function RegulatoryReport({ component, branchLike }: Props) {
+  const [downloadStarted, setDownloadStarted] = useState(false);
+  const [selectedBranch, setSelectedBranch] = useState('');
+  const [branchOptions, setBranchOptions] = useState<LabelValueSelectOption[]>([]);
 
-export default class RegulatoryReport extends React.PureComponent<Props, State> {
-  constructor(props: Props) {
-    super(props);
-    this.state = {
-      downloadStarted: false,
-      selectedBranch: '',
-      branchOptions: [],
-    };
-  }
+  React.useEffect(() => {
+    async function fetchBranches() {
+      try {
+        const branches = await getBranches(component.key);
 
-  componentDidMount() {
-    const { component, branchLike } = this.props;
-    getBranches(component.key)
-      .then((data) => {
-        const availableBranches = data.filter(
+        const availableBranches = branches.filter(
           (br) => br.analysisDate && (isMainBranch(br) || br.excludedFromPurge)
         );
         const mainBranch = availableBranches.find(isMainBranch);
@@ -87,105 +82,94 @@ export default class RegulatoryReport extends React.PureComponent<Props, State>
         } else if (mainBranch) {
           selectedBranch = getBranchLikeDisplayName(mainBranch);
         }
-        this.setState({ selectedBranch, branchOptions: options });
-      })
-      .catch(() => {
-        this.setState({ branchOptions: [] });
-      });
-  }
+        setSelectedBranch(selectedBranch);
+        setBranchOptions(options);
+      } catch (error) {
+        setBranchOptions([]);
+      }
+    }
 
-  onBranchSelect = (newOption: LabelValueSelectOption) => {
-    this.setState({ selectedBranch: newOption.value, downloadStarted: false });
-  };
+    fetchBranches();
+  }, [component, branchLike]);
 
-  render() {
-    const { component, onClose } = this.props;
-    const { downloadStarted, selectedBranch, branchOptions } = this.state;
-    const isDownloadButtonDisabled = downloadStarted || !selectedBranch;
+  const isDownloadButtonDisabled = downloadStarted || !selectedBranch;
 
-    return (
-      <>
-        <div className="modal-head">
-          <h2>{translate('regulatory_report.page')}</h2>
-        </div>
-        <div className="modal-body">
-          <p>{translate('regulatory_report.description1')}</p>
-          <div className="markdown">
-            <ul>
-              <li>{translate('regulatory_report.bullet_point1')}</li>
-              <li>{translate('regulatory_report.bullet_point2')}</li>
-              <li>{translate('regulatory_report.bullet_point3')}</li>
-            </ul>
-          </div>
-          <p>{translate('regulatory_report.description2')}</p>
-          {branchOptions.length > 0 ? (
-            <>
-              <div className="modal-field big-spacer-top">
-                <label htmlFor="regulatory-report-branch-select">
-                  {translate('regulatory_page.select_branch')}
-                </label>
-                <Select
-                  className="width-100"
-                  inputId="regulatory-report-branch-select"
-                  id="regulatory-report-branch-select-input"
-                  onChange={this.onBranchSelect}
-                  options={branchOptions}
-                  value={branchOptions.find((o) => o.value === selectedBranch)}
-                />
-              </div>
-              <Alert variant="info">
-                <div>
-                  {translate('regulatory_page.available_branches_info.only_keep_when_inactive')}
-                </div>
-                <div>
-                  <FormattedMessage
-                    id="regulatory_page.available_branches_info.more_info"
-                    defaultMessage={translate('regulatory_page.available_branches_info.more_info')}
-                    values={{
-                      doc_link: (
-                        <DocLink to="/analyzing-source-code/branches/branch-analysis/#inactive-branches">
-                          {translate('regulatory_page.available_branches_info.more_info.doc_link')}
-                        </DocLink>
-                      ),
-                    }}
-                  />
-                </div>
-              </Alert>
-            </>
-          ) : (
-            <div className="big-spacer-top">
-              <Alert variant="warning">
-                <div>{translate('regulatory_page.no_available_branch')}</div>
-              </Alert>
-            </div>
-          )}
-          <div className="modal-field big-spacer-top">
-            {downloadStarted && (
-              <div>
-                <p>{translate('regulatory_page.download_start.sentence')}</p>
-              </div>
-            )}
+  return (
+    <>
+      <SubTitle>{translate('regulatory_report.page')}</SubTitle>
+
+      <p>{translate('regulatory_report.description1')}</p>
+      <div className="markdown">
+        <ul>
+          <li>{translate('regulatory_report.bullet_point1')}</li>
+          <li>{translate('regulatory_report.bullet_point2')}</li>
+          <li>{translate('regulatory_report.bullet_point3')}</li>
+        </ul>
+      </div>
+
+      <p className="sw-mb-4">{translate('regulatory_report.description2')}</p>
+
+      <BasicSeparator className="sw-mb-4" />
+
+      {isEmpty(branchOptions) ? (
+        <FlagMessage className="sw-mb-4" variant="warning">
+          {translate('regulatory_page.no_available_branch')}
+        </FlagMessage>
+      ) : (
+        <>
+          <div className="sw-grid sw-mb-4">
+            <FormField
+              htmlFor="regulatory-report-branch-select"
+              label={translate('regulatory_page.select_branch')}
+            >
+              <InputSelect
+                className="sw-w-abs-300"
+                inputId="regulatory-report-branch-select"
+                onChange={({ value }: LabelValueSelectOption) => {
+                  setSelectedBranch(value);
+                  setDownloadStarted(false);
+                }}
+                options={branchOptions}
+                value={branchOptions.find((o) => o.value === selectedBranch)}
+                size="full"
+              />
+            </FormField>
           </div>
-        </div>
-        <div className="modal-foot">
-          <a
-            className={classNames('button button-primary big-spacer-right', {
-              disabled: isDownloadButtonDisabled,
-            })}
-            download={[component.name, selectedBranch, 'regulatory report.zip']
-              .filter((s) => !!s)
-              .join(' - ')}
-            onClick={() => this.setState({ downloadStarted: true })}
-            href={getRegulatoryReportUrl(component.key, selectedBranch)}
-            target="_blank"
-            rel="noopener noreferrer"
-            aria-disabled={isDownloadButtonDisabled}
-          >
-            {translate('download_verb')}
-          </a>
-          <ButtonLink onClick={onClose}>{translate('close')}</ButtonLink>
-        </div>
-      </>
-    );
-  }
+          <FlagMessage className="sw-mb-4" variant="info">
+            {translate('regulatory_page.available_branches_info.only_keep_when_inactive')}
+            <FormattedMessage
+              id="regulatory_page.available_branches_info.more_info"
+              defaultMessage={translate('regulatory_page.available_branches_info.more_info')}
+              values={{
+                doc_link: (
+                  <DocLink to="/analyzing-source-code/branches/branch-analysis/#inactive-branches">
+                    {translate('regulatory_page.available_branches_info.more_info.doc_link')}
+                  </DocLink>
+                ),
+              }}
+            />
+          </FlagMessage>
+        </>
+      )}
+
+      {downloadStarted && (
+        <p className="sw-mb-4">{translate('regulatory_page.download_start.sentence')}</p>
+      )}
+
+      {!isDownloadButtonDisabled && (
+        <DownloadButton
+          download={[component.name, selectedBranch, 'regulatory report.zip']
+            .filter((s) => !!s)
+            .join(' - ')}
+          onClick={() => setDownloadStarted(true)}
+          href={getRegulatoryReportUrl(component.key, selectedBranch)}
+          target="_blank"
+          rel="noopener noreferrer"
+          aria-disabled={isDownloadButtonDisabled}
+        >
+          {translate('download_verb')}
+        </DownloadButton>
+      )}
+    </>
+  );
 }
diff --git a/server/sonar-web/src/main/js/apps/projectInformation/projectRegulatoryReport/RegulatoryReportModal.tsx b/server/sonar-web/src/main/js/apps/projectInformation/projectRegulatoryReport/RegulatoryReportModal.tsx
deleted file mode 100644 (file)
index 8311b93..0000000
+++ /dev/null
@@ -1,45 +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 * as React from 'react';
-import ClickEventBoundary from '../../../components/controls/ClickEventBoundary';
-import Modal from '../../../components/controls/Modal';
-import { translate } from '../../../helpers/l10n';
-import { BranchLike } from '../../../types/branch-like';
-import { Component } from '../../../types/types';
-import RegulatoryReport from './RegulatoryReport';
-
-interface Props {
-  component: Component;
-  branchLike?: BranchLike;
-  onClose: () => void;
-}
-
-export default function RegulatoryReportModal(props: Props) {
-  const { component, branchLike } = props;
-  return (
-    <Modal contentLabel={translate('regulatory_report.page')} onRequestClose={props.onClose}>
-      <ClickEventBoundary>
-        <form>
-          <RegulatoryReport component={component} branchLike={branchLike} onClose={props.onClose} />
-        </form>
-      </ClickEventBoundary>
-    </Modal>
-  );
-}
index 2d2a2f22c90b5a68acbefdc4e014f57792113fd6..45ea0dd508e30f167032dab2a71f069fdab8173d 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 { screen } from '@testing-library/react';
+import { act, screen } from '@testing-library/react';
 import userEvent from '@testing-library/user-event';
 import * as React from 'react';
 import BranchesServiceMock from '../../../../api/mocks/BranchesServiceMock';
 import { mockBranch, mockMainBranch } from '../../../../helpers/mocks/branch-like';
 import { renderComponent } from '../../../../helpers/testReactTestingUtils';
+import { byRole, byText } from '../../../../helpers/testSelector';
 import { BranchLike } from '../../../../types/branch-like';
 import RegulatoryReport from '../RegulatoryReport';
 
 let handler: BranchesServiceMock;
 
+const ui = {
+  page: byText('regulatory_report.page'),
+  description1: byText('regulatory_report.description1'),
+  description2: byText('regulatory_report.description2'),
+  availableBranchesInfo: byText(/regulatory_page.available_branches_info.only_keep_when_inactive/),
+  moreInfo: byText(/regulatory_page.available_branches_info.more_info$/),
+  noBranchAvailable: byText('regulatory_page.no_available_branch'),
+  branchSelect: byRole('combobox', { name: 'regulatory_page.select_branch' }),
+  downloadButton: byRole('link', { name: 'download_verb' }),
+};
+
 beforeAll(() => {
   handler = new BranchesServiceMock();
 });
@@ -38,27 +50,25 @@ describe('RegulatoryReport tests', () => {
   it('should open the regulatory report page', async () => {
     const user = userEvent.setup();
     renderRegulatoryReportApp();
-    expect(await screen.findByText('regulatory_report.page')).toBeInTheDocument();
-    expect(screen.getByText('regulatory_report.description1')).toBeInTheDocument();
-    expect(screen.getByText('regulatory_report.description2')).toBeInTheDocument();
-    expect(
-      screen.getByText('regulatory_page.available_branches_info.only_keep_when_inactive')
-    ).toBeInTheDocument();
-    expect(
-      screen.getByText('regulatory_page.available_branches_info.more_info')
-    ).toBeInTheDocument();
-
-    const branchSelect = screen.getByRole('combobox', { name: 'regulatory_page.select_branch' });
-    expect(branchSelect).toBeInTheDocument();
+    expect(await ui.page.find()).toBeInTheDocument();
+    expect(ui.description1.get()).toBeInTheDocument();
+    expect(ui.description2.get()).toBeInTheDocument();
+    expect(ui.availableBranchesInfo.get()).toBeInTheDocument();
+    expect(ui.moreInfo.get()).toBeInTheDocument();
+    expect(ui.branchSelect.get()).toBeInTheDocument();
+
+    await act(async () => {
+      await user.click(ui.branchSelect.get());
+      await user.keyboard('[ArrowDown][Enter]');
+    });
 
-    await user.click(branchSelect);
-    await user.keyboard('[ArrowDown][Enter]');
+    expect(ui.downloadButton.get()).toBeInTheDocument();
+    expect(screen.queryByText('regulatory_page.download_start.sentence')).not.toBeInTheDocument();
 
-    const downloadButton = screen.getByRole('link', { name: 'download_verb' });
-    expect(downloadButton).toBeInTheDocument();
+    await act(async () => {
+      await user.click(ui.downloadButton.get());
+    });
 
-    expect(screen.queryByText('regulatory_page.download_start.sentence')).not.toBeInTheDocument();
-    await user.click(downloadButton);
     expect(screen.getByText('regulatory_page.download_start.sentence')).toBeInTheDocument();
   });
 
@@ -66,13 +76,9 @@ describe('RegulatoryReport tests', () => {
     handler.emptyBranches();
     renderRegulatoryReportApp();
 
-    expect(await screen.findByText('regulatory_report.page')).toBeInTheDocument();
-
-    expect(screen.getByText('regulatory_page.no_available_branch')).toBeInTheDocument();
-
-    const downloadButton = screen.getByRole('link', { name: 'download_verb' });
-    expect(downloadButton).toBeInTheDocument();
-    expect(downloadButton).toHaveClass('disabled');
+    expect(await ui.page.find()).toBeInTheDocument();
+    expect(ui.noBranchAvailable.get()).toBeInTheDocument();
+    expect(ui.downloadButton.query()).not.toBeInTheDocument();
   });
 
   it('should automatically select passed branch if compatible', async () => {
@@ -80,12 +86,12 @@ describe('RegulatoryReport tests', () => {
     handler.addBranch(compatibleBranch);
     renderRegulatoryReportApp(compatibleBranch);
 
-    expect(await screen.findByText('regulatory_report.page')).toBeInTheDocument();
-
-    const downloadButton = screen.getByRole<HTMLAnchorElement>('link', { name: 'download_verb' });
-    expect(downloadButton).toBeInTheDocument();
-    expect(downloadButton).not.toHaveClass('disabled');
-    expect(downloadButton.href).toContain(compatibleBranch.name);
+    expect(await ui.page.find()).toBeInTheDocument();
+    expect(ui.downloadButton.get()).toBeInTheDocument();
+    expect(ui.downloadButton.get()).toHaveAttribute(
+      'href',
+      `/api/regulatory_reports/download?project=&branch=${compatibleBranch.name}`
+    );
   });
 
   it('should automatically select main branch if present and passed branch is not compatible', async () => {
@@ -99,21 +105,15 @@ describe('RegulatoryReport tests', () => {
     handler.addBranch(notCompatibleBranch);
     renderRegulatoryReportApp(notCompatibleBranch);
 
-    expect(await screen.findByText('regulatory_report.page')).toBeInTheDocument();
-
-    const downloadButton = screen.getByRole<HTMLAnchorElement>('link', { name: 'download_verb' });
-    expect(downloadButton).toBeInTheDocument();
-    expect(downloadButton).not.toHaveClass('disabled');
-    expect(downloadButton.href).toContain(mainBranch.name);
+    expect(await ui.page.find()).toBeInTheDocument();
+    expect(ui.downloadButton.get()).toBeInTheDocument();
+    expect(ui.downloadButton.get()).toHaveAttribute(
+      'href',
+      `/api/regulatory_reports/download?project=&branch=${mainBranch.name}`
+    );
   });
 });
 
 function renderRegulatoryReportApp(branchLike?: BranchLike) {
-  renderComponent(
-    <RegulatoryReport
-      component={{ key: '', name: '' }}
-      branchLike={branchLike}
-      onClose={() => {}}
-    />
-  );
+  renderComponent(<RegulatoryReport component={{ key: '', name: '' }} branchLike={branchLike} />);
 }
index 76613b49e155506c7f7c97f79020e1f309f66965..57699322713d689059272dd1ea88a6f416bfa066 100644 (file)
@@ -696,7 +696,7 @@ regulatory_report.description2=The generation and download of the report may tak
 regulatory_page.download_start.sentence=Your download should start shortly. This may take some time.
 regulatory_page.select_branch=Select Branch
 regulatory_page.no_available_branch=No branch has been analyzed yet, no report can be generated.
-regulatory_page.available_branches_info.only_keep_when_inactive=Only branches marked as "Keep when inactive" are available.
+regulatory_page.available_branches_info.only_keep_when_inactive=Only branches marked as "Keep when inactive" are available. 
 regulatory_page.available_branches_info.more_info=For further details, please check the {doc_link}.
 regulatory_page.available_branches_info.more_info.doc_link=related documentation