}
`;
+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)`
{component.qualifier === ComponentQualifier.Project &&
regulatoryReportFeatureEnabled && (
<Card>
- <RegulatoryReport
- component={component}
- branchLike={branchLike}
- onClose={() => {}}
- />
+ <RegulatoryReport component={component} branchLike={branchLike} />
</Card>
)}
</div>
* 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,
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);
} 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>
+ )}
+ </>
+ );
}
+++ /dev/null
-/*
- * 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>
- );
-}
* 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();
});
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();
});
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 () => {
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 () => {
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} />);
}
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