]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-16316 New place for regulatory report
authorRevanshu Paliwal <revanshu.paliwal@sonarsource.com>
Tue, 10 May 2022 10:02:41 +0000 (12:02 +0200)
committersonartech <sonartech@sonarsource.com>
Wed, 25 May 2022 20:03:16 +0000 (20:03 +0000)
14 files changed:
server/sonar-web/src/main/js/api/mocks/BranchesServiceMock.ts [new file with mode: 0644]
server/sonar-web/src/main/js/app/components/nav/component/Menu.tsx
server/sonar-web/src/main/js/app/components/nav/component/projectInformation/ProjectInformation.tsx
server/sonar-web/src/main/js/app/components/nav/component/projectInformation/ProjectInformationRenderer.tsx
server/sonar-web/src/main/js/app/components/nav/component/projectInformation/__tests__/ProjectInformationRenderer-test.tsx
server/sonar-web/src/main/js/app/components/nav/component/projectInformation/__tests__/__snapshots__/ProjectInformation-test.tsx.snap
server/sonar-web/src/main/js/app/components/nav/component/projectInformation/__tests__/__snapshots__/ProjectInformationRenderer-test.tsx.snap
server/sonar-web/src/main/js/app/components/nav/component/projectInformation/projectRegulatoryReport/RegulatoryReport.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/app/components/nav/component/projectInformation/projectRegulatoryReport/RegulatoryReportModal.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/app/components/nav/component/projectInformation/projectRegulatoryReport/__tests__/RegulatoryReport-it.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/app/utils/startReactApp.tsx
server/sonar-web/src/main/js/apps/projectRegulatoryReport/RegulatoryReport.tsx [deleted file]
server/sonar-web/src/main/js/apps/projectRegulatoryReport/__tests__/RegulatoryReport-it.tsx [deleted file]
sonar-core/src/main/resources/org/sonar/l10n/core.properties

diff --git a/server/sonar-web/src/main/js/api/mocks/BranchesServiceMock.ts b/server/sonar-web/src/main/js/api/mocks/BranchesServiceMock.ts
new file mode 100644 (file)
index 0000000..d833cb2
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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 { cloneDeep } from 'lodash';
+import { mockBranch } from '../../helpers/mocks/branch-like';
+import { BranchLike } from '../../types/branch-like';
+import { getBranches } from '../branches';
+
+export default class BranchesServiceMock {
+  branchLikes: BranchLike[];
+  defaultBranchLikes: BranchLike[] = [
+    mockBranch({ isMain: true, name: 'master' }),
+    mockBranch({ excludedFromPurge: false, name: 'delete-branch' }),
+    mockBranch({ name: 'normal-branch' })
+  ];
+
+  constructor() {
+    this.branchLikes = cloneDeep(this.defaultBranchLikes);
+    (getBranches as jest.Mock).mockImplementation(this.getBranchesHandler);
+  }
+
+  getBranchesHandler = () => {
+    return Promise.resolve(this.branchLikes);
+  };
+
+  resetBranches = () => {
+    this.branchLikes = cloneDeep(this.defaultBranchLikes);
+  };
+}
index a3e13786bfdf18b0c280fcdc55b38e80362fddbe..7b33626b62a962e8ef4d9c9706e5a49b1c93efd0 100644 (file)
@@ -329,8 +329,7 @@ export class Menu extends React.PureComponent<Props> {
       this.renderBackgroundTasksLink(query),
       this.renderUpdateKeyLink(query),
       this.renderWebhooksLink(query, isProject),
-      this.renderDeletionLink(query),
-      this.renderRegulatoryReport(query)
+      this.renderDeletionLink(query)
     ];
   };
 
@@ -542,19 +541,6 @@ export class Menu extends React.PureComponent<Props> {
     );
   };
 
-  renderRegulatoryReport = (query: Query) => {
-    if (!this.props.appState.regulatoryReportFeatureEnabled) {
-      return null;
-    }
-    return (
-      <li key="project_regulatory_report">
-        <Link activeClassName="active" to={{ pathname: '/project/regulatory-report', query }}>
-          {translate('regulatory_report.page')}
-        </Link>
-      </li>
-    );
-  };
-
   renderExtension = ({ key, name }: Extension, isAdmin: boolean, baseQuery: Query) => {
     const pathname = isAdmin ? `/project/admin/extension/${key}` : `/project/extension/${key}`;
     const query = { ...baseQuery, qualifier: this.props.component.qualifier };
index 952b65e7d1ec8328d3d218875c6feb23ff0cd067..3010c8f5564bd0590867511b7205811392541c13 100644 (file)
@@ -97,6 +97,7 @@ export class ProjectInformation extends React.PureComponent<Props, State> {
           canConfigureNotifications={canConfigureNotifications}
           canUseBadges={canUseBadges}
           component={component}
+          branchLike={branchLike}
           measures={measures}
           onComponentChange={this.props.onComponentChange}
           onPageChange={this.setPage}
index 547e84bf445e4eabfdac14b18d07d530790d34f4..8c50d12e51c46dafc093706ff238ce58ee274710 100644 (file)
@@ -18,6 +18,8 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as React from 'react';
+import { ButtonLink } from '../../../../../components/controls/buttons';
+import ModalButton from '../../../../../components/controls/ModalButton';
 import PrivacyBadgeContainer from '../../../../../components/common/PrivacyBadgeContainer';
 import { translate } from '../../../../../helpers/l10n';
 import { ComponentQualifier } from '../../../../../types/component';
@@ -30,18 +32,31 @@ import MetaQualityProfiles from './meta/MetaQualityProfiles';
 import MetaSize from './meta/MetaSize';
 import MetaTags from './meta/MetaTags';
 import { ProjectInformationPages } from './ProjectInformationPages';
+import RegulatoryReportModal from './projectRegulatoryReport/RegulatoryReportModal';
+import withAppStateContext from '../../../app-state/withAppStateContext';
+import { AppState } from '../../../../../types/appstate';
+import { BranchLike } from '../../../../../types/branch-like';
 
 export interface ProjectInformationRendererProps {
+  appState: AppState;
   canConfigureNotifications: boolean;
   canUseBadges: boolean;
   component: Component;
+  branchLike?: BranchLike;
   measures?: Measure[];
   onComponentChange: (changes: {}) => void;
   onPageChange: (page: ProjectInformationPages) => void;
 }
 
 export function ProjectInformationRenderer(props: ProjectInformationRendererProps) {
-  const { canConfigureNotifications, canUseBadges, component, measures = [] } = props;
+  const {
+    canConfigureNotifications,
+    canUseBadges,
+    component,
+    measures = [],
+    appState,
+    branchLike
+  } = props;
 
   const isApp = component.qualifier === ComponentQualifier.Application;
 
@@ -113,9 +128,26 @@ export function ProjectInformationRenderer(props: ProjectInformationRendererProp
             to={ProjectInformationPages.notifications}
           />
         )}
+        {component.qualifier === ComponentQualifier.Project &&
+          appState.regulatoryReportFeatureEnabled && (
+            <div className="big-padded bordered-bottom">
+              <ModalButton
+                modal={({ onClose }) => (
+                  <RegulatoryReportModal
+                    component={component}
+                    branchLike={branchLike}
+                    onClose={onClose}
+                  />
+                )}>
+                {({ onClick }) => (
+                  <ButtonLink onClick={onClick}>{translate('regulatory_report.page')}</ButtonLink>
+                )}
+              </ModalButton>
+            </div>
+          )}
       </div>
     </>
   );
 }
 
-export default React.memo(ProjectInformationRenderer);
+export default withAppStateContext(React.memo(ProjectInformationRenderer));
index 42ca7b25e95ac08c9d433d3687dad87e7be246f9..33a4b340615bb4af4e9836348cfd00d42416f1ff 100644 (file)
@@ -19,6 +19,7 @@
  */
 import { shallow } from 'enzyme';
 import * as React from 'react';
+import { mockAppState } from '../../../../../../helpers/testMocks';
 import { mockComponent } from '../../../../../../helpers/mocks/component';
 import {
   ProjectInformationRenderer,
@@ -56,9 +57,22 @@ it('should handle missing quality profiles and quality gates', () => {
   ).toMatchSnapshot();
 });
 
+it('should render app correctly when regulatoryReportFeatureEnabled is false', () => {
+  expect(
+    shallowRender({
+      appState: mockAppState({
+        regulatoryReportFeatureEnabled: false
+      })
+    })
+  ).toMatchSnapshot();
+});
+
 function shallowRender(props: Partial<ProjectInformationRendererProps> = {}) {
   return shallow(
     <ProjectInformationRenderer
+      appState={mockAppState({
+        regulatoryReportFeatureEnabled: true
+      })}
       canConfigureNotifications={true}
       canUseBadges={true}
       component={mockComponent({ qualifier: 'TRK', visibility: 'public' })}
index 879dcaea3937c37feb98e69336ec134db17e84c9..a010a9957f3e4418b9dd87e1dd91001c7bd3f062 100644 (file)
@@ -2,7 +2,7 @@
 
 exports[`should render correctly: default 1`] = `
 <Fragment>
-  <Memo(ProjectInformationRenderer)
+  <withAppStateContext(Component)
     canConfigureNotifications={false}
     canUseBadges={true}
     component={
@@ -64,7 +64,7 @@ exports[`should render correctly: default 1`] = `
 
 exports[`should render correctly: logged in user 1`] = `
 <Fragment>
-  <Memo(ProjectInformationRenderer)
+  <withAppStateContext(Component)
     canConfigureNotifications={true}
     canUseBadges={true}
     component={
@@ -155,7 +155,7 @@ exports[`should render correctly: logged in user 1`] = `
 
 exports[`should render correctly: measures loaded 1`] = `
 <Fragment>
-  <Memo(ProjectInformationRenderer)
+  <withAppStateContext(Component)
     canConfigureNotifications={false}
     canUseBadges={true}
     component={
@@ -231,7 +231,7 @@ exports[`should render correctly: measures loaded 1`] = `
 
 exports[`should render correctly: private 1`] = `
 <Fragment>
-  <Memo(ProjectInformationRenderer)
+  <withAppStateContext(Component)
     canConfigureNotifications={false}
     canUseBadges={true}
     component={
index b13ddca3f78a7897a41307f78ec90157a0d334ad..3ea8de111d98604527dcfff6aa9242deb326947a 100644 (file)
@@ -88,6 +88,15 @@ exports[`should handle missing quality profiles and quality gates 1`] = `
       onPageChange={[MockFunction]}
       to={2}
     />
+    <div
+      className="big-padded bordered-bottom"
+    >
+      <ModalButton
+        modal={[Function]}
+      >
+        <Component />
+      </ModalButton>
+    </div>
   </div>
 </Fragment>
 `;
@@ -246,6 +255,15 @@ exports[`should render a private project correctly 1`] = `
       onPageChange={[MockFunction]}
       to={2}
     />
+    <div
+      className="big-padded bordered-bottom"
+    >
+      <ModalButton
+        modal={[Function]}
+      >
+        <Component />
+      </ModalButton>
+    </div>
   </div>
 </Fragment>
 `;
@@ -351,6 +369,164 @@ exports[`should render an app correctly: default 1`] = `
 </Fragment>
 `;
 
+exports[`should render app correctly when regulatoryReportFeatureEnabled is false 1`] = `
+<Fragment>
+  <div>
+    <h2
+      className="big-padded bordered-bottom"
+    >
+      project.info.title
+    </h2>
+  </div>
+  <div
+    className="overflow-y-auto"
+  >
+    <div
+      className="big-padded bordered-bottom"
+    >
+      <div
+        className="display-flex-center"
+      >
+        <h3
+          className="spacer-right"
+        >
+          project.info.description
+        </h3>
+        <PrivacyBadgeContainer
+          qualifier="TRK"
+          visibility="public"
+        />
+      </div>
+      <MetaTags
+        component={
+          Object {
+            "breadcrumbs": Array [],
+            "key": "my-project",
+            "name": "MyProject",
+            "qualifier": "TRK",
+            "qualityGate": Object {
+              "isDefault": true,
+              "key": "30",
+              "name": "Sonar way",
+            },
+            "qualityProfiles": Array [
+              Object {
+                "deleted": false,
+                "key": "my-qp",
+                "language": "ts",
+                "name": "Sonar way",
+              },
+            ],
+            "tags": Array [],
+            "visibility": "public",
+          }
+        }
+        onComponentChange={[MockFunction]}
+      />
+    </div>
+    <div
+      className="big-padded bordered-bottom it__project-loc-value"
+    >
+      <MetaSize
+        component={
+          Object {
+            "breadcrumbs": Array [],
+            "key": "my-project",
+            "name": "MyProject",
+            "qualifier": "TRK",
+            "qualityGate": Object {
+              "isDefault": true,
+              "key": "30",
+              "name": "Sonar way",
+            },
+            "qualityProfiles": Array [
+              Object {
+                "deleted": false,
+                "key": "my-qp",
+                "language": "ts",
+                "name": "Sonar way",
+              },
+            ],
+            "tags": Array [],
+            "visibility": "public",
+          }
+        }
+        measures={Array []}
+      />
+    </div>
+    <div
+      className="big-padded bordered-bottom"
+    >
+      <MetaQualityGate
+        qualityGate={
+          Object {
+            "isDefault": true,
+            "key": "30",
+            "name": "Sonar way",
+          }
+        }
+      />
+      <withLanguagesContext(MetaQualityProfiles)
+        headerClassName="big-spacer-top"
+        profiles={
+          Array [
+            Object {
+              "deleted": false,
+              "key": "my-qp",
+              "language": "ts",
+              "name": "Sonar way",
+            },
+          ]
+        }
+      />
+    </div>
+    <MetaLinks
+      component={
+        Object {
+          "breadcrumbs": Array [],
+          "key": "my-project",
+          "name": "MyProject",
+          "qualifier": "TRK",
+          "qualityGate": Object {
+            "isDefault": true,
+            "key": "30",
+            "name": "Sonar way",
+          },
+          "qualityProfiles": Array [
+            Object {
+              "deleted": false,
+              "key": "my-qp",
+              "language": "ts",
+              "name": "Sonar way",
+            },
+          ],
+          "tags": Array [],
+          "visibility": "public",
+        }
+      }
+    />
+    <div
+      className="big-padded bordered-bottom"
+    >
+      <MetaKey
+        componentKey="my-project"
+        qualifier="TRK"
+      />
+    </div>
+    <Memo(DrawerLink)
+      label="overview.badges.get_badge.TRK"
+      onPageChange={[MockFunction]}
+      to={1}
+    />
+    <Memo(DrawerLink)
+      label="project.info.to_notifications"
+      onPageChange={[MockFunction]}
+      to={2}
+    />
+  </div>
+</Fragment>
+`;
+
 exports[`should render correctly: default 1`] = `
 <Fragment>
   <div>
@@ -505,6 +681,15 @@ exports[`should render correctly: default 1`] = `
       onPageChange={[MockFunction]}
       to={2}
     />
+    <div
+      className="big-padded bordered-bottom"
+    >
+      <ModalButton
+        modal={[Function]}
+      >
+        <Component />
+      </ModalButton>
+    </div>
   </div>
 </Fragment>
 `;
@@ -658,6 +843,15 @@ exports[`should render correctly: no badges 1`] = `
       onPageChange={[MockFunction]}
       to={2}
     />
+    <div
+      className="big-padded bordered-bottom"
+    >
+      <ModalButton
+        modal={[Function]}
+      >
+        <Component />
+      </ModalButton>
+    </div>
   </div>
 </Fragment>
 `;
@@ -806,6 +1000,15 @@ exports[`should render correctly: no badges, no notifications 1`] = `
         qualifier="TRK"
       />
     </div>
+    <div
+      className="big-padded bordered-bottom"
+    >
+      <ModalButton
+        modal={[Function]}
+      >
+        <Component />
+      </ModalButton>
+    </div>
   </div>
 </Fragment>
 `;
@@ -959,6 +1162,15 @@ exports[`should render correctly: with notifications 1`] = `
       onPageChange={[MockFunction]}
       to={1}
     />
+    <div
+      className="big-padded bordered-bottom"
+    >
+      <ModalButton
+        modal={[Function]}
+      >
+        <Component />
+      </ModalButton>
+    </div>
   </div>
 </Fragment>
 `;
@@ -1118,6 +1330,15 @@ exports[`should render with description 1`] = `
       onPageChange={[MockFunction]}
       to={2}
     />
+    <div
+      className="big-padded bordered-bottom"
+    >
+      <ModalButton
+        modal={[Function]}
+      >
+        <Component />
+      </ModalButton>
+    </div>
   </div>
 </Fragment>
 `;
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/projectRegulatoryReport/RegulatoryReport.tsx b/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/projectRegulatoryReport/RegulatoryReport.tsx
new file mode 100644 (file)
index 0000000..aebf360
--- /dev/null
@@ -0,0 +1,153 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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 classNames from 'classnames';
+import * as React from 'react';
+import { BranchLike } from '../../../../../../types/branch-like';
+import { getBranches } from '../../../../../../api/branches';
+import { getRegulatoryReportUrl } from '../../../../../../api/regulatory-report';
+import { ButtonLink } from '../../../../../../components/controls/buttons';
+import Select, { BasicSelectOption } from '../../../../../../components/controls/Select';
+import {
+  getBranchLikeDisplayName,
+  isBranch,
+  isMainBranch
+} from '../../../../../../helpers/branch-like';
+import { translate } from '../../../../../../helpers/l10n';
+import { Component } from '../../../../../../types/types';
+import { orderBy } from 'lodash';
+
+interface Props {
+  component: Pick<Component, 'key' | 'name'>;
+  branchLike?: BranchLike;
+  onClose: () => void;
+}
+
+interface State {
+  downloadStarted: boolean;
+  selectedBranch: string;
+  branchLikesOptions: BasicSelectOption[];
+}
+
+export default class RegulatoryReport extends React.PureComponent<Props, State> {
+  constructor(props: Props) {
+    super(props);
+    this.state = {
+      downloadStarted: false,
+      selectedBranch: '',
+      branchLikesOptions: []
+    };
+  }
+
+  componentDidMount() {
+    const { component, branchLike } = this.props;
+    getBranches(component.key)
+      .then(data => {
+        const mainBranch = data.find(isMainBranch);
+        const otherBranchSorted = orderBy(
+          data.filter(isBranch).filter(b => !isMainBranch(b)),
+          b => b.name
+        );
+        const sortedBranch = mainBranch ? [mainBranch, ...otherBranchSorted] : otherBranchSorted;
+        const options = sortedBranch
+          .filter(br => br.excludedFromPurge)
+          .map(br => {
+            return {
+              value: getBranchLikeDisplayName(br),
+              label: getBranchLikeDisplayName(br)
+            };
+          });
+
+        let selectedBranch = '';
+        if (branchLike && isBranch(branchLike) && branchLike.excludedFromPurge) {
+          selectedBranch = getBranchLikeDisplayName(branchLike);
+        } else if (mainBranch) {
+          selectedBranch = getBranchLikeDisplayName(mainBranch);
+        }
+        this.setState({ selectedBranch, branchLikesOptions: options });
+      })
+      .catch(() => {
+        this.setState({ branchLikesOptions: [] });
+      });
+  }
+
+  onBranchSelect = (newOption: BasicSelectOption) => {
+    this.setState({ selectedBranch: newOption.value, downloadStarted: false });
+  };
+
+  render() {
+    const { component, onClose } = this.props;
+    const { downloadStarted, selectedBranch, branchLikesOptions } = this.state;
+
+    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>
+          <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={branchLikesOptions}
+              value={branchLikesOptions.find(o => o.value === selectedBranch)}
+            />
+          </div>
+          <div className="modal-field big-spacer-top">
+            {downloadStarted && (
+              <div>
+                <p>{translate('regulatory_page.download_start.sentence')}</p>
+              </div>
+            )}
+          </div>
+        </div>
+        <div className="modal-foot">
+          <a
+            className={classNames('button button-primary big-spacer-right', {
+              disabled: downloadStarted
+            })}
+            download={[component.name, selectedBranch, 'PDF Report.zip']
+              .filter(s => !!s)
+              .join(' - ')}
+            onClick={() => this.setState({ downloadStarted: true })}
+            href={getRegulatoryReportUrl(component.key, selectedBranch)}
+            target="_blank"
+            rel="noopener noreferrer">
+            {translate('download_verb')}
+          </a>
+          <ButtonLink onClick={onClose}>{translate('cancel')}</ButtonLink>
+        </div>
+      </>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/projectRegulatoryReport/RegulatoryReportModal.tsx b/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/projectRegulatoryReport/RegulatoryReportModal.tsx
new file mode 100644 (file)
index 0000000..8906f47
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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 { translate } from '../../../../../../helpers/l10n';
+import { Component } from '../../../../../../types/types';
+import Modal from '../../../../../../components/controls/Modal';
+import RegulatoryReport from './RegulatoryReport';
+import ClickEventBoundary from '../../../../../../components/controls/ClickEventBoundary';
+import { BranchLike } from '../../../../../../types/branch-like';
+
+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>
+  );
+}
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/projectRegulatoryReport/__tests__/RegulatoryReport-it.tsx b/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/projectRegulatoryReport/__tests__/RegulatoryReport-it.tsx
new file mode 100644 (file)
index 0000000..a564dcc
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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 { screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import * as React from 'react';
+import BranchesServiceMock from '../../../../../../../api/mocks/BranchesServiceMock';
+import { renderComponent } from '../../../../../../../helpers/testReactTestingUtils';
+import RegulatoryReport from '../RegulatoryReport';
+
+jest.mock('../../../../../../../api/branches');
+
+let handler: BranchesServiceMock;
+
+beforeAll(() => {
+  handler = new BranchesServiceMock();
+});
+
+afterEach(() => handler.resetBranches());
+
+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();
+
+  const branchSelect = screen.getByRole('textbox');
+  expect(branchSelect).toBeInTheDocument();
+
+  await user.click(branchSelect);
+  await user.keyboard('[ArrowDown][Enter]');
+
+  const downloadButton = screen.getByText('download_verb');
+  expect(downloadButton).toBeInTheDocument();
+
+  expect(screen.queryByText('regulatory_page.download_start.sentence')).not.toBeInTheDocument();
+  await user.click(downloadButton);
+  expect(screen.getByText('regulatory_page.download_start.sentence')).toBeInTheDocument();
+});
+
+function renderRegulatoryReportApp() {
+  renderComponent(<RegulatoryReport component={{ key: '', name: '' }} onClose={() => {}} />);
+}
index 5ad4881c39624ed2a7411cbdb30364edd7e49a6d..622c56db5853d6fee78bfc0f24faa88e6b9e639b 100644 (file)
@@ -239,12 +239,6 @@ function renderComponentRoutes() {
           path="project/deletion"
           component={lazyLoadComponent(() => import('../../apps/projectDeletion/App'))}
         />
-        <Route
-          path="project/regulatory-report"
-          component={lazyLoadComponent(() =>
-            import('../../apps/projectRegulatoryReport/RegulatoryReport')
-          )}
-        />
         <Route
           path="project/links"
           component={lazyLoadComponent(() => import('../../apps/projectLinks/App'))}
diff --git a/server/sonar-web/src/main/js/apps/projectRegulatoryReport/RegulatoryReport.tsx b/server/sonar-web/src/main/js/apps/projectRegulatoryReport/RegulatoryReport.tsx
deleted file mode 100644 (file)
index 55d3c48..0000000
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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 classNames from 'classnames';
-import * as React from 'react';
-import { getRegulatoryReportUrl } from '../../api/regulatory-report';
-import { isBranch } from '../../helpers/branch-like';
-import { translate } from '../../helpers/l10n';
-import { BranchLike } from '../../types/branch-like';
-import { Component } from '../../types/types';
-
-interface Props {
-  component: Pick<Component, 'key' | 'name'>;
-  branchLike?: BranchLike;
-}
-
-function RegulatoryReport(props: Props) {
-  const { component, branchLike } = props;
-  const branchName = branchLike && isBranch(branchLike) ? branchLike.name : undefined;
-  const [downloadStarted, setDownloadStarted] = React.useState(false);
-  return (
-    <div className="page page-limited">
-      <header className="page-header">
-        <h1 className="page-title">{translate('regulatory_report.page')}</h1>
-      </header>
-      <div className="page-description">
-        <p>{translate('regulatory_report.description1')}</p>
-        <p>{translate('regulatory_report.description2')}</p>
-        <div className="big-spacer-top">
-          <a
-            className={classNames('button button-primary', { disabled: downloadStarted })}
-            download={[component.name, branchName, 'PDF Report'].filter(s => !!s).join(' - ')}
-            onClick={() => setDownloadStarted(true)}
-            href={getRegulatoryReportUrl(component.key, branchName)}
-            target="_blank"
-            rel="noopener noreferrer">
-            {translate('download_verb')}
-          </a>
-          {downloadStarted && (
-            <div className="spacer-top">
-              <p>{translate('regulatory_page.download_start.sentence')}</p>
-            </div>
-          )}
-        </div>
-      </div>
-    </div>
-  );
-}
-
-export default RegulatoryReport;
diff --git a/server/sonar-web/src/main/js/apps/projectRegulatoryReport/__tests__/RegulatoryReport-it.tsx b/server/sonar-web/src/main/js/apps/projectRegulatoryReport/__tests__/RegulatoryReport-it.tsx
deleted file mode 100644 (file)
index 9b8cd71..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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 { screen } from '@testing-library/react';
-import userEvent from '@testing-library/user-event';
-import * as React from 'react';
-import { renderComponent } from '../../../helpers/testReactTestingUtils';
-import RegulatoryReport from '../RegulatoryReport';
-
-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();
-
-  const downloadButton = screen.getByText('download_verb');
-  expect(downloadButton).toBeInTheDocument();
-
-  expect(screen.queryByText('regulatory_page.download_start.sentence')).not.toBeInTheDocument();
-  await user.click(downloadButton);
-  expect(screen.getByText('regulatory_page.download_start.sentence')).toBeInTheDocument();
-});
-
-function renderRegulatoryReportApp() {
-  renderComponent(<RegulatoryReport branchLike={undefined} component={{ key: '', name: '' }} />);
-}
index 3639e2af689116e18257fbbfd95fc400a56e84da..5a0b00d333cad3f34457cf9a702f28a4e5dd8878 100644 (file)
@@ -647,9 +647,13 @@ baseline.branch_analyses.ranges.allTime=All time
 baseline.no_analyses=No analyses
 
 regulatory_report.page=Regulatory Report
-regulatory_report.description1=The regulatory report is a zip file containing a snapshot of the branch you selected. It contains an overview of the project, the configuration items relevant to its quality (quality profile, quality gate and analysis exclusions), as well as lists of findings for both new code and overall code.
-regulatory_report.description2=The file is created on demand when you download it. This may take some time.
+regulatory_report.description1=The regulatory report is a zip file containing a snapshot of the selected branch. It contains:
+regulatory_report.bullet_point1=An overview of the selected branch of the project.
+regulatory_report.bullet_point2=The configuration items relevant to the project's quality (quality profile, quality gate, and analysis exclusions).
+regulatory_report.bullet_point3=Lists of findings for both new and overall code on the selected branch.
+regulatory_report.description2=The generation and download of the report may take a few minutes.
 regulatory_page.download_start.sentence=Your download should start shortly. This may take some time.
+regulatory_page.select_branch=Select Branch
 
 #------------------------------------------------------------------------------
 #