@@ -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); | |||
}; | |||
} |
@@ -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 }; |
@@ -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} |
@@ -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)); |
@@ -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' })} |
@@ -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={ |
@@ -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> | |||
`; |
@@ -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> | |||
</> | |||
); | |||
} | |||
} |
@@ -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> | |||
); | |||
} |
@@ -20,9 +20,20 @@ | |||
import { screen } from '@testing-library/react'; | |||
import userEvent from '@testing-library/user-event'; | |||
import * as React from 'react'; | |||
import { renderComponent } from '../../../helpers/testReactTestingUtils'; | |||
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(); | |||
@@ -30,6 +41,12 @@ it('should open the regulatory report page', async () => { | |||
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(); | |||
@@ -39,5 +56,5 @@ it('should open the regulatory report page', async () => { | |||
}); | |||
function renderRegulatoryReportApp() { | |||
renderComponent(<RegulatoryReport branchLike={undefined} component={{ key: '', name: '' }} />); | |||
renderComponent(<RegulatoryReport component={{ key: '', name: '' }} onClose={() => {}} />); | |||
} |
@@ -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'))} |
@@ -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; |
@@ -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 | |||
#------------------------------------------------------------------------------ | |||
# |