@@ -20,35 +20,43 @@ | |||
import { getJSON, post } from 'sonar-ui-common/helpers/request'; | |||
import throwGlobalError from '../app/utils/throwGlobalError'; | |||
import { getBaseUrl } from '../helpers/system'; | |||
import { ComponentReportStatus } from '../types/component-report'; | |||
export interface ReportStatus { | |||
canDownload?: boolean; | |||
canSubscribe: boolean; | |||
componentFrequency?: string; | |||
globalFrequency: string; | |||
subscribed?: boolean; | |||
} | |||
export function getReportStatus(component: string): Promise<ReportStatus> { | |||
return getJSON('/api/governance_reports/status', { componentKey: component }).catch( | |||
export function getReportStatus( | |||
componentKey: string, | |||
branchKey?: string | |||
): Promise<ComponentReportStatus> { | |||
return getJSON('/api/governance_reports/status', { componentKey, branchKey }).catch( | |||
throwGlobalError | |||
); | |||
} | |||
export function getReportUrl(component: string): string { | |||
return `${getBaseUrl()}/api/governance_reports/download?componentKey=${encodeURIComponent( | |||
component | |||
export function getReportUrl(componentKey: string, branchKey?: string): string { | |||
let url = `${getBaseUrl()}/api/governance_reports/download?componentKey=${encodeURIComponent( | |||
componentKey | |||
)}`; | |||
if (branchKey) { | |||
url += `&branchKey=${branchKey}`; | |||
} | |||
return url; | |||
} | |||
export function subscribe(component: string): Promise<void | Response> { | |||
return post('/api/governance_reports/subscribe', { componentKey: component }).catch( | |||
export function subscribeToEmailReport( | |||
componentKey: string, | |||
branchKey?: string | |||
): Promise<void | Response> { | |||
return post('/api/governance_reports/subscribe', { componentKey, branchKey }).catch( | |||
throwGlobalError | |||
); | |||
} | |||
export function unsubscribe(component: string): Promise<void | Response> { | |||
return post('/api/governance_reports/unsubscribe', { componentKey: component }).catch( | |||
export function unsubscribeFromEmailReport( | |||
componentKey: string, | |||
branchKey?: string | |||
): Promise<void | Response> { | |||
return post('/api/governance_reports/unsubscribe', { componentKey, branchKey }).catch( | |||
throwGlobalError | |||
); | |||
} |
@@ -23,6 +23,7 @@ import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner'; | |||
import { translate } from 'sonar-ui-common/helpers/l10n'; | |||
import { isDiffMetric } from 'sonar-ui-common/helpers/measures'; | |||
import { rawSizes } from '../../../app/theme'; | |||
import ComponentReportActions from '../../../components/controls/ComponentReportActions'; | |||
import { findMeasure } from '../../../helpers/measures'; | |||
import { ApplicationPeriod } from '../../../types/application'; | |||
import { Branch } from '../../../types/branch-like'; | |||
@@ -95,7 +96,10 @@ export function MeasuresPanel(props: MeasuresPanelProps) { | |||
return ( | |||
<div className="overview-panel" data-test="overview__measures-panel"> | |||
<h2 className="overview-panel-title">{translate('overview.measures')}</h2> | |||
<div className="display-flex-space-between display-flex-start"> | |||
<h2 className="overview-panel-title">{translate('overview.measures')}</h2> | |||
<ComponentReportActions component={component} branch={branch} /> | |||
</div> | |||
{loading ? ( | |||
<div className="overview-panel-content overview-panel-big-padded"> |
@@ -5,11 +5,47 @@ exports[`should render correctly for applications 1`] = ` | |||
className="overview-panel" | |||
data-test="overview__measures-panel" | |||
> | |||
<h2 | |||
className="overview-panel-title" | |||
<div | |||
className="display-flex-space-between display-flex-start" | |||
> | |||
overview.measures | |||
</h2> | |||
<h2 | |||
className="overview-panel-title" | |||
> | |||
overview.measures | |||
</h2> | |||
<Connect(withCurrentUser(Connect(ComponentReportActions))) | |||
branch={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"excludedFromPurge": true, | |||
"isMain": true, | |||
"name": "master", | |||
} | |||
} | |||
component={ | |||
Object { | |||
"breadcrumbs": Array [], | |||
"key": "my-project", | |||
"name": "MyProject", | |||
"qualifier": "APP", | |||
"qualityGate": Object { | |||
"isDefault": true, | |||
"key": "30", | |||
"name": "Sonar way", | |||
}, | |||
"qualityProfiles": Array [ | |||
Object { | |||
"deleted": false, | |||
"key": "my-qp", | |||
"language": "ts", | |||
"name": "Sonar way", | |||
}, | |||
], | |||
"tags": Array [], | |||
} | |||
} | |||
/> | |||
</div> | |||
<BoxedTabs | |||
onSelect={[Function]} | |||
selected={0} | |||
@@ -691,11 +727,47 @@ exports[`should render correctly for applications 2`] = ` | |||
className="overview-panel" | |||
data-test="overview__measures-panel" | |||
> | |||
<h2 | |||
className="overview-panel-title" | |||
<div | |||
className="display-flex-space-between display-flex-start" | |||
> | |||
overview.measures | |||
</h2> | |||
<h2 | |||
className="overview-panel-title" | |||
> | |||
overview.measures | |||
</h2> | |||
<Connect(withCurrentUser(Connect(ComponentReportActions))) | |||
branch={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"excludedFromPurge": true, | |||
"isMain": true, | |||
"name": "master", | |||
} | |||
} | |||
component={ | |||
Object { | |||
"breadcrumbs": Array [], | |||
"key": "my-project", | |||
"name": "MyProject", | |||
"qualifier": "APP", | |||
"qualityGate": Object { | |||
"isDefault": true, | |||
"key": "30", | |||
"name": "Sonar way", | |||
}, | |||
"qualityProfiles": Array [ | |||
Object { | |||
"deleted": false, | |||
"key": "my-qp", | |||
"language": "ts", | |||
"name": "Sonar way", | |||
}, | |||
], | |||
"tags": Array [], | |||
} | |||
} | |||
/> | |||
</div> | |||
<BoxedTabs | |||
onSelect={[Function]} | |||
selected={1} | |||
@@ -1587,11 +1659,47 @@ exports[`should render correctly for projects 1`] = ` | |||
className="overview-panel" | |||
data-test="overview__measures-panel" | |||
> | |||
<h2 | |||
className="overview-panel-title" | |||
<div | |||
className="display-flex-space-between display-flex-start" | |||
> | |||
overview.measures | |||
</h2> | |||
<h2 | |||
className="overview-panel-title" | |||
> | |||
overview.measures | |||
</h2> | |||
<Connect(withCurrentUser(Connect(ComponentReportActions))) | |||
branch={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"excludedFromPurge": true, | |||
"isMain": true, | |||
"name": "master", | |||
} | |||
} | |||
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 [], | |||
} | |||
} | |||
/> | |||
</div> | |||
<BoxedTabs | |||
onSelect={[Function]} | |||
selected={0} | |||
@@ -2273,11 +2381,47 @@ exports[`should render correctly for projects 2`] = ` | |||
className="overview-panel" | |||
data-test="overview__measures-panel" | |||
> | |||
<h2 | |||
className="overview-panel-title" | |||
<div | |||
className="display-flex-space-between display-flex-start" | |||
> | |||
overview.measures | |||
</h2> | |||
<h2 | |||
className="overview-panel-title" | |||
> | |||
overview.measures | |||
</h2> | |||
<Connect(withCurrentUser(Connect(ComponentReportActions))) | |||
branch={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"excludedFromPurge": true, | |||
"isMain": true, | |||
"name": "master", | |||
} | |||
} | |||
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 [], | |||
} | |||
} | |||
/> | |||
</div> | |||
<BoxedTabs | |||
onSelect={[Function]} | |||
selected={1} | |||
@@ -3169,11 +3313,47 @@ exports[`should render correctly if branch is misconfigured: hide settings 1`] = | |||
className="overview-panel" | |||
data-test="overview__measures-panel" | |||
> | |||
<h2 | |||
className="overview-panel-title" | |||
<div | |||
className="display-flex-space-between display-flex-start" | |||
> | |||
overview.measures | |||
</h2> | |||
<h2 | |||
className="overview-panel-title" | |||
> | |||
overview.measures | |||
</h2> | |||
<Connect(withCurrentUser(Connect(ComponentReportActions))) | |||
branch={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"excludedFromPurge": true, | |||
"isMain": false, | |||
"name": "own-reference", | |||
} | |||
} | |||
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 [], | |||
} | |||
} | |||
/> | |||
</div> | |||
<BoxedTabs | |||
onSelect={[Function]} | |||
selected={0} | |||
@@ -3274,11 +3454,50 @@ exports[`should render correctly if branch is misconfigured: show settings 1`] = | |||
className="overview-panel" | |||
data-test="overview__measures-panel" | |||
> | |||
<h2 | |||
className="overview-panel-title" | |||
<div | |||
className="display-flex-space-between display-flex-start" | |||
> | |||
overview.measures | |||
</h2> | |||
<h2 | |||
className="overview-panel-title" | |||
> | |||
overview.measures | |||
</h2> | |||
<Connect(withCurrentUser(Connect(ComponentReportActions))) | |||
branch={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"excludedFromPurge": true, | |||
"isMain": false, | |||
"name": "own-reference", | |||
} | |||
} | |||
component={ | |||
Object { | |||
"breadcrumbs": Array [], | |||
"configuration": Object { | |||
"showSettings": true, | |||
}, | |||
"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 [], | |||
} | |||
} | |||
/> | |||
</div> | |||
<BoxedTabs | |||
onSelect={[Function]} | |||
selected={0} | |||
@@ -3382,11 +3601,47 @@ exports[`should render correctly if the data is still loading 1`] = ` | |||
className="overview-panel" | |||
data-test="overview__measures-panel" | |||
> | |||
<h2 | |||
className="overview-panel-title" | |||
<div | |||
className="display-flex-space-between display-flex-start" | |||
> | |||
overview.measures | |||
</h2> | |||
<h2 | |||
className="overview-panel-title" | |||
> | |||
overview.measures | |||
</h2> | |||
<Connect(withCurrentUser(Connect(ComponentReportActions))) | |||
branch={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"excludedFromPurge": true, | |||
"isMain": true, | |||
"name": "master", | |||
} | |||
} | |||
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 [], | |||
} | |||
} | |||
/> | |||
</div> | |||
<div | |||
className="overview-panel-content overview-panel-big-padded" | |||
> | |||
@@ -3402,11 +3657,47 @@ exports[`should render correctly if there is no coverage 1`] = ` | |||
className="overview-panel" | |||
data-test="overview__measures-panel" | |||
> | |||
<h2 | |||
className="overview-panel-title" | |||
<div | |||
className="display-flex-space-between display-flex-start" | |||
> | |||
overview.measures | |||
</h2> | |||
<h2 | |||
className="overview-panel-title" | |||
> | |||
overview.measures | |||
</h2> | |||
<Connect(withCurrentUser(Connect(ComponentReportActions))) | |||
branch={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"excludedFromPurge": true, | |||
"isMain": true, | |||
"name": "master", | |||
} | |||
} | |||
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 [], | |||
} | |||
} | |||
/> | |||
</div> | |||
<BoxedTabs | |||
onSelect={[Function]} | |||
selected={0} | |||
@@ -3820,11 +4111,47 @@ exports[`should render correctly if there is no new code measures 1`] = ` | |||
className="overview-panel" | |||
data-test="overview__measures-panel" | |||
> | |||
<h2 | |||
className="overview-panel-title" | |||
<div | |||
className="display-flex-space-between display-flex-start" | |||
> | |||
overview.measures | |||
</h2> | |||
<h2 | |||
className="overview-panel-title" | |||
> | |||
overview.measures | |||
</h2> | |||
<Connect(withCurrentUser(Connect(ComponentReportActions))) | |||
branch={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"excludedFromPurge": true, | |||
"isMain": true, | |||
"name": "master", | |||
} | |||
} | |||
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 [], | |||
} | |||
} | |||
/> | |||
</div> | |||
<BoxedTabs | |||
onSelect={[Function]} | |||
selected={0} |
@@ -23,6 +23,7 @@ import { translate } from 'sonar-ui-common/helpers/l10n'; | |||
import { getChildren } from '../../../api/components'; | |||
import { getMeasures } from '../../../api/measures'; | |||
import MeasuresLink from '../../../components/common/MeasuresLink'; | |||
import ComponentReportActions from '../../../components/controls/ComponentReportActions'; | |||
import Measure from '../../../components/measure/Measure'; | |||
import { fetchMetrics } from '../../../store/rootActions'; | |||
import { getMetrics, Store } from '../../../store/rootReducer'; | |||
@@ -30,7 +31,6 @@ import '../styles.css'; | |||
import { SubComponent } from '../types'; | |||
import { convertMeasures, PORTFOLIO_METRICS, SUB_COMPONENTS_METRICS } from '../utils'; | |||
import MetricBox from './MetricBox'; | |||
import Report from './Report'; | |||
import WorstProjects from './WorstProjects'; | |||
interface OwnProps { | |||
@@ -159,8 +159,9 @@ export class App extends React.PureComponent<Props, State> { | |||
return ( | |||
<div className="page page-limited portfolio-overview"> | |||
<div className="page-actions"> | |||
<Report component={component} /> | |||
<ComponentReportActions component={component} /> | |||
</div> | |||
{component.description && ( | |||
<div className="portfolio-description display-inline-block big-spacer-bottom"> | |||
{component.description} |
@@ -1,112 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2021 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 { Button } from 'sonar-ui-common/components/controls/buttons'; | |||
import Dropdown from 'sonar-ui-common/components/controls/Dropdown'; | |||
import DropdownIcon from 'sonar-ui-common/components/icons/DropdownIcon'; | |||
import { translate } from 'sonar-ui-common/helpers/l10n'; | |||
import { getReportStatus, getReportUrl, ReportStatus } from '../../../api/report'; | |||
import Subscription from './Subscription'; | |||
interface Props { | |||
component: { key: string; name: string }; | |||
} | |||
interface State { | |||
loading: boolean; | |||
status?: ReportStatus; | |||
} | |||
export default class Report extends React.PureComponent<Props, State> { | |||
mounted = false; | |||
state: State = { loading: true }; | |||
componentDidMount() { | |||
this.mounted = true; | |||
this.loadStatus(); | |||
} | |||
componentWillUnmount() { | |||
this.mounted = false; | |||
} | |||
loadStatus = () => { | |||
getReportStatus(this.props.component.key).then( | |||
status => { | |||
if (this.mounted) { | |||
this.setState({ status, loading: false }); | |||
} | |||
}, | |||
() => { | |||
if (this.mounted) { | |||
this.setState({ loading: false }); | |||
} | |||
} | |||
); | |||
}; | |||
render() { | |||
const { component } = this.props; | |||
const { status, loading } = this.state; | |||
if (loading || !status) { | |||
return null; | |||
} | |||
return status.canSubscribe ? ( | |||
<Dropdown | |||
overlay={ | |||
<ul className="menu"> | |||
<li> | |||
<a | |||
download={component.name + ' - Executive Report.pdf'} | |||
href={getReportUrl(component.key)} | |||
target="_blank" | |||
rel="noopener noreferrer"> | |||
{translate('report.print')} | |||
</a> | |||
</li> | |||
<li> | |||
<Subscription | |||
component={component.key} | |||
onSubscribe={this.loadStatus} | |||
status={status} | |||
/> | |||
</li> | |||
</ul> | |||
} | |||
tagName="li"> | |||
<Button className="dropdown-toggle"> | |||
{translate('portfolio.pdf_report')} | |||
<DropdownIcon className="spacer-left icon-half-transparent" /> | |||
</Button> | |||
</Dropdown> | |||
) : ( | |||
<a | |||
className="button" | |||
download={component.name + ' - Executive Report.pdf'} | |||
href={getReportUrl(component.key)} | |||
target="_blank" | |||
rel="noopener noreferrer"> | |||
{translate('report.print')} | |||
</a> | |||
); | |||
} | |||
} |
@@ -1,89 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2021 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 { connect } from 'react-redux'; | |||
import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; | |||
import { ReportStatus, subscribe, unsubscribe } from '../../../api/report'; | |||
import addGlobalSuccessMessage from '../../../app/utils/addGlobalSuccessMessage'; | |||
import throwGlobalError from '../../../app/utils/throwGlobalError'; | |||
import { isLoggedIn } from '../../../helpers/users'; | |||
import { getCurrentUser, Store } from '../../../store/rootReducer'; | |||
interface Props { | |||
component: string; | |||
currentUser: T.CurrentUser; | |||
onSubscribe: () => void; | |||
status: ReportStatus; | |||
} | |||
export class Subscription extends React.PureComponent<Props> { | |||
handleSubscription = (subscribed: boolean) => { | |||
addGlobalSuccessMessage( | |||
subscribed | |||
? translateWithParameters('report.subscribe_x_success', this.getFrequencyText()) | |||
: translateWithParameters('report.unsubscribe_x_success', this.getFrequencyText()) | |||
); | |||
this.props.onSubscribe(); | |||
}; | |||
handleSubscribe = () => { | |||
subscribe(this.props.component) | |||
.then(() => this.handleSubscription(true)) | |||
.catch(throwGlobalError); | |||
}; | |||
handleUnsubscribe = () => { | |||
unsubscribe(this.props.component) | |||
.then(() => this.handleSubscription(false)) | |||
.catch(throwGlobalError); | |||
}; | |||
getFrequencyText = () => { | |||
const effectiveFrequency = | |||
this.props.status.componentFrequency || this.props.status.globalFrequency; | |||
return translate('report.frequency', effectiveFrequency); | |||
}; | |||
render() { | |||
const hasEmail = isLoggedIn(this.props.currentUser) && !!this.props.currentUser.email; | |||
const { status } = this.props; | |||
if (!hasEmail) { | |||
return <span className="text-muted-2">{translate('report.no_email_to_subscribe')}</span>; | |||
} | |||
return status.subscribed ? ( | |||
<a href="#" onClick={this.handleUnsubscribe}> | |||
{translateWithParameters('report.unsubscribe_x', this.getFrequencyText())} | |||
</a> | |||
) : ( | |||
<a href="#" onClick={this.handleSubscribe}> | |||
{translateWithParameters('report.subscribe_x', this.getFrequencyText())} | |||
</a> | |||
); | |||
} | |||
} | |||
const mapStateToProps = (state: Store) => ({ | |||
currentUser: getCurrentUser(state) | |||
}); | |||
export default connect(mapStateToProps)(Subscription); |
@@ -1,55 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2021 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. | |||
*/ | |||
/* eslint-disable import/first */ | |||
jest.mock('../../../../api/report', () => { | |||
const report = jest.requireActual('../../../../api/report'); | |||
report.getReportStatus = jest.fn(() => Promise.resolve({})); | |||
return report; | |||
}); | |||
import { mount, shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import Report from '../Report'; | |||
const getReportStatus = require('../../../../api/report').getReportStatus as jest.Mock<any>; | |||
const component = { key: 'foo', name: 'Foo' }; | |||
it('renders', () => { | |||
const wrapper = shallow(<Report component={component} />); | |||
expect(wrapper).toMatchSnapshot(); | |||
wrapper.setState({ | |||
loading: false, | |||
status: { | |||
canDownload: true, | |||
canSubscribe: true, | |||
componentFrequency: 'montly', | |||
globalFrequency: 'weekly', | |||
subscribed: true | |||
} | |||
}); | |||
expect(wrapper).toMatchSnapshot(); | |||
}); | |||
it('fetches status', () => { | |||
getReportStatus.mockClear(); | |||
mount(<Report component={component} />); | |||
expect(getReportStatus).toBeCalledWith('foo'); | |||
}); |
@@ -1,108 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2021 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. | |||
*/ | |||
/* eslint-disable import/first */ | |||
jest.mock('../../../../api/report', () => { | |||
const report = jest.requireActual('../../../../api/report'); | |||
report.subscribe = jest.fn(() => Promise.resolve()); | |||
report.unsubscribe = jest.fn(() => Promise.resolve()); | |||
return report; | |||
}); | |||
import { mount, shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import { click, waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; | |||
import { ReportStatus } from '../../../../api/report'; | |||
import { Subscription } from '../Subscription'; | |||
const subscribe = require('../../../../api/report').subscribe as jest.Mock<any>; | |||
const unsubscribe = require('../../../../api/report').unsubscribe as jest.Mock<any>; | |||
beforeEach(() => { | |||
subscribe.mockClear(); | |||
unsubscribe.mockClear(); | |||
}); | |||
it('renders when subscribed', () => { | |||
expect(shallowRender()).toMatchSnapshot(); | |||
}); | |||
it('renders when not subscribed', () => { | |||
expect(shallowRender({}, { subscribed: false })).toMatchSnapshot(); | |||
}); | |||
it('renders when no email', () => { | |||
expect(shallowRender({ currentUser: { isLoggedIn: false } })).toMatchSnapshot(); | |||
}); | |||
it('changes subscription', async () => { | |||
const status = { | |||
canDownload: true, | |||
canSubscribe: true, | |||
componentFrequency: 'montly', | |||
globalFrequency: 'weekly', | |||
subscribed: true | |||
}; | |||
const currentUser = { isLoggedIn: true, email: 'foo@example.com' }; | |||
const wrapper = mount( | |||
<Subscription | |||
component="foo" | |||
currentUser={currentUser} | |||
onSubscribe={jest.fn()} | |||
status={status} | |||
/> | |||
); | |||
click(wrapper.find('a')); | |||
expect(unsubscribe).toBeCalledWith('foo'); | |||
wrapper.setProps({ status: { ...status, subscribed: false } }); | |||
await waitAndUpdate(wrapper); | |||
click(wrapper.find('a')); | |||
expect(subscribe).toBeCalledWith('foo'); | |||
}); | |||
function shallowRender( | |||
props: Partial<Subscription['props']> = {}, | |||
statusOverrides: Partial<ReportStatus> = {} | |||
) { | |||
const status = { | |||
canDownload: true, | |||
canSubscribe: true, | |||
componentFrequency: 'montly', | |||
globalFrequency: 'weekly', | |||
subscribed: true, | |||
...statusOverrides | |||
}; | |||
const currentUser = { isLoggedIn: true, email: 'foo@example.com' }; | |||
return shallow<Subscription>( | |||
<Subscription | |||
component="foo" | |||
currentUser={currentUser} | |||
onSubscribe={jest.fn()} | |||
status={status} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -7,7 +7,7 @@ exports[`renders 1`] = ` | |||
<div | |||
className="page-actions" | |||
> | |||
<Report | |||
<Connect(withCurrentUser(Connect(ComponentReportActions))) | |||
component={ | |||
Object { | |||
"description": "accurate description", |
@@ -1,49 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`renders 1`] = `""`; | |||
exports[`renders 2`] = ` | |||
<Dropdown | |||
overlay={ | |||
<ul | |||
className="menu" | |||
> | |||
<li> | |||
<a | |||
download="Foo - Executive Report.pdf" | |||
href="/api/governance_reports/download?componentKey=foo" | |||
rel="noopener noreferrer" | |||
target="_blank" | |||
> | |||
report.print | |||
</a> | |||
</li> | |||
<li> | |||
<Connect(Subscription) | |||
component="foo" | |||
onSubscribe={[Function]} | |||
status={ | |||
Object { | |||
"canDownload": true, | |||
"canSubscribe": true, | |||
"componentFrequency": "montly", | |||
"globalFrequency": "weekly", | |||
"subscribed": true, | |||
} | |||
} | |||
/> | |||
</li> | |||
</ul> | |||
} | |||
tagName="li" | |||
> | |||
<Button | |||
className="dropdown-toggle" | |||
> | |||
portfolio.pdf_report | |||
<DropdownIcon | |||
className="spacer-left icon-half-transparent" | |||
/> | |||
</Button> | |||
</Dropdown> | |||
`; |
@@ -1,27 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`renders when no email 1`] = ` | |||
<span | |||
className="text-muted-2" | |||
> | |||
report.no_email_to_subscribe | |||
</span> | |||
`; | |||
exports[`renders when not subscribed 1`] = ` | |||
<a | |||
href="#" | |||
onClick={[Function]} | |||
> | |||
report.subscribe_x.report.frequency.montly | |||
</a> | |||
`; | |||
exports[`renders when subscribed 1`] = ` | |||
<a | |||
href="#" | |||
onClick={[Function]} | |||
> | |||
report.unsubscribe_x.report.frequency.montly | |||
</a> | |||
`; |
@@ -0,0 +1,140 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2021 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 { connect } from 'react-redux'; | |||
import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; | |||
import { | |||
getReportStatus, | |||
subscribeToEmailReport, | |||
unsubscribeFromEmailReport | |||
} from '../../api/component-report'; | |||
import addGlobalSuccessMessage from '../../app/utils/addGlobalSuccessMessage'; | |||
import { isLoggedIn } from '../../helpers/users'; | |||
import { Store } from '../../store/rootReducer'; | |||
import { Branch } from '../../types/branch-like'; | |||
import { ComponentQualifier } from '../../types/component'; | |||
import { ComponentReportStatus } from '../../types/component-report'; | |||
import { withCurrentUser } from '../hoc/withCurrentUser'; | |||
import ComponentReportActionsRenderer from './ComponentReportActionsRenderer'; | |||
interface Props { | |||
appState: Pick<T.AppState, 'qualifiers'>; | |||
component: T.Component; | |||
branch?: Branch; | |||
currentUser: T.CurrentUser; | |||
} | |||
interface State { | |||
loadingStatus?: boolean; | |||
status?: ComponentReportStatus; | |||
} | |||
export class ComponentReportActions extends React.PureComponent<Props, State> { | |||
mounted = false; | |||
state: State = {}; | |||
componentDidMount() { | |||
this.mounted = true; | |||
const governanceEnabled = this.props.appState.qualifiers.includes(ComponentQualifier.Portfolio); | |||
if (governanceEnabled) { | |||
this.loadReportStatus(); | |||
} | |||
} | |||
componentWillUnmount() { | |||
this.mounted = false; | |||
} | |||
loadReportStatus = async () => { | |||
const { component, branch } = this.props; | |||
const status = await getReportStatus(component.key, branch?.name).catch(() => undefined); | |||
if (this.mounted) { | |||
this.setState({ status, loadingStatus: false }); | |||
} | |||
}; | |||
handleSubscription = (subscribed: boolean) => { | |||
const { component } = this.props; | |||
const { status } = this.state; | |||
const translationKey = subscribed | |||
? 'component_report.subscribe_x_success' | |||
: 'component_report.unsubscribe_x_success'; | |||
const frequencyTranslation = translate( | |||
'report.frequency', | |||
status?.componentFrequency || status?.globalFrequency || '' | |||
).toLowerCase(); | |||
const qualifierTranslation = translate('qualifier', component.qualifier).toLowerCase(); | |||
addGlobalSuccessMessage( | |||
translateWithParameters(translationKey, frequencyTranslation, qualifierTranslation) | |||
); | |||
this.loadReportStatus(); | |||
}; | |||
handleSubscribe = async () => { | |||
const { component, branch } = this.props; | |||
await subscribeToEmailReport(component.key, branch?.name); | |||
this.handleSubscription(true); | |||
}; | |||
handleUnsubscribe = async () => { | |||
const { component, branch } = this.props; | |||
await unsubscribeFromEmailReport(component.key, branch?.name); | |||
this.handleSubscription(false); | |||
}; | |||
render() { | |||
const { currentUser, component, branch } = this.props; | |||
const { status, loadingStatus } = this.state; | |||
if (loadingStatus || !status || (branch && !branch.excludedFromPurge)) { | |||
return null; | |||
} | |||
const currentUserHasEmail = isLoggedIn(currentUser) && !!currentUser.email; | |||
return ( | |||
<ComponentReportActionsRenderer | |||
branch={branch} | |||
component={component} | |||
frequency={status.componentFrequency || status.globalFrequency} | |||
subscribed={status.subscribed} | |||
canSubscribe={status.canSubscribe} | |||
currentUserHasEmail={currentUserHasEmail} | |||
handleSubscription={this.handleSubscribe} | |||
handleUnsubscription={this.handleUnsubscribe} | |||
/> | |||
); | |||
} | |||
} | |||
const mapStateToProps = (state: Store) => ({ | |||
appState: state.appState | |||
}); | |||
export default withCurrentUser(connect(mapStateToProps)(ComponentReportActions)); |
@@ -0,0 +1,100 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2021 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 { Button } from 'sonar-ui-common/components/controls/buttons'; | |||
import Dropdown from 'sonar-ui-common/components/controls/Dropdown'; | |||
import DropdownIcon from 'sonar-ui-common/components/icons/DropdownIcon'; | |||
import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; | |||
import { getReportUrl } from '../../api/component-report'; | |||
import { Branch } from '../../types/branch-like'; | |||
import { isPortfolioLike } from '../../types/component'; | |||
export interface ComponentReportActionsRendererProps { | |||
component: T.Component; | |||
branch?: Branch; | |||
frequency: string; | |||
subscribed: boolean; | |||
canSubscribe: boolean; | |||
currentUserHasEmail: boolean; | |||
handleSubscription: () => void; | |||
handleUnsubscription: () => void; | |||
} | |||
export default function ComponentReportActionsRenderer(props: ComponentReportActionsRendererProps) { | |||
const { branch, component, frequency, subscribed, canSubscribe, currentUserHasEmail } = props; | |||
const renderDownloadButton = (simple = false) => { | |||
return ( | |||
<a | |||
download={[component.name, branch?.name, 'PDF Report.pdf'].filter(s => !!s).join(' - ')} | |||
href={getReportUrl(component.key, branch?.name)} | |||
target="_blank" | |||
rel="noopener noreferrer"> | |||
{simple | |||
? translate('download_verb') | |||
: translateWithParameters( | |||
'component_report.download', | |||
translate('qualifier', component.qualifier).toLowerCase() | |||
)} | |||
</a> | |||
); | |||
}; | |||
const renderSubscriptionButton = () => { | |||
if (!currentUserHasEmail) { | |||
return ( | |||
<span className="text-muted-2">{translate('component_report.no_email_to_subscribe')}</span> | |||
); | |||
} | |||
const translationKey = subscribed | |||
? 'component_report.unsubscribe_x' | |||
: 'component_report.subscribe_x'; | |||
const onClickHandler = subscribed ? props.handleUnsubscription : props.handleSubscription; | |||
const frequencyTranslation = translate('report.frequency', frequency).toLowerCase(); | |||
return ( | |||
<a href="#" onClick={onClickHandler}> | |||
{translateWithParameters(translationKey, frequencyTranslation)} | |||
</a> | |||
); | |||
}; | |||
return canSubscribe && isPortfolioLike(component.qualifier) ? ( | |||
<Dropdown | |||
overlay={ | |||
<ul className="menu"> | |||
<li>{renderDownloadButton(true)}</li> | |||
<li>{renderSubscriptionButton()}</li> | |||
</ul> | |||
}> | |||
<Button className="dropdown-toggle"> | |||
{translateWithParameters( | |||
'component_report.report', | |||
translate('qualifier', component.qualifier) | |||
)} | |||
<DropdownIcon className="spacer-left icon-half-transparent" /> | |||
</Button> | |||
</Dropdown> | |||
) : ( | |||
renderDownloadButton() | |||
); | |||
} |
@@ -0,0 +1,127 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2021 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; | |||
import { | |||
getReportStatus, | |||
subscribeToEmailReport, | |||
unsubscribeFromEmailReport | |||
} from '../../../api/component-report'; | |||
import addGlobalSuccessMessage from '../../../app/utils/addGlobalSuccessMessage'; | |||
import { mockBranch } from '../../../helpers/mocks/branch-like'; | |||
import { mockComponentReportStatus } from '../../../helpers/mocks/component-report'; | |||
import { mockComponent, mockCurrentUser } from '../../../helpers/testMocks'; | |||
import { ComponentQualifier } from '../../../types/component'; | |||
import { ComponentReportActions } from '../ComponentReportActions'; | |||
jest.mock('../../../api/component-report', () => ({ | |||
...jest.requireActual('../../../api/component-report'), | |||
getReportStatus: jest | |||
.fn() | |||
.mockResolvedValue( | |||
jest.requireActual('../../../helpers/mocks/component-report').mockComponentReportStatus() | |||
), | |||
subscribeToEmailReport: jest.fn().mockResolvedValue(undefined), | |||
unsubscribeFromEmailReport: jest.fn().mockResolvedValue(undefined) | |||
})); | |||
jest.mock('../../../helpers/system', () => ({ | |||
...jest.requireActual('../../../helpers/system'), | |||
getBaseUrl: jest.fn().mockReturnValue('baseUrl') | |||
})); | |||
jest.mock('../../../app/utils/addGlobalSuccessMessage', () => ({ default: jest.fn() })); | |||
beforeEach(jest.clearAllMocks); | |||
it('should not render anything', async () => { | |||
// loading | |||
expect(shallowRender().type()).toBeNull(); | |||
// No status | |||
(getReportStatus as jest.Mock).mockResolvedValueOnce(undefined); | |||
const w1 = shallowRender(); | |||
await waitAndUpdate(w1); | |||
expect(w1.type()).toBeNull(); | |||
// Branch purgeable | |||
const w2 = shallowRender({ branch: mockBranch({ excludedFromPurge: false }) }); | |||
await waitAndUpdate(w2); | |||
expect(w2.type()).toBeNull(); | |||
// no governance | |||
const w3 = shallowRender({ appState: { qualifiers: [] } }); | |||
await waitAndUpdate(w3); | |||
expect(w3.type()).toBeNull(); | |||
}); | |||
it('should call for status properly', async () => { | |||
const component = mockComponent(); | |||
const branch = mockBranch(); | |||
const wrapper = shallowRender({ component, branch }); | |||
await waitAndUpdate(wrapper); | |||
expect(getReportStatus).toHaveBeenCalledWith(component.key, branch.name); | |||
}); | |||
it('should handle subscription', async () => { | |||
const component = mockComponent(); | |||
const branch = mockBranch(); | |||
const wrapper = shallowRender({ component, branch }); | |||
await wrapper.instance().handleSubscribe(); | |||
expect(subscribeToEmailReport).toHaveBeenCalledWith(component.key, branch.name); | |||
expect(addGlobalSuccessMessage).toHaveBeenCalledWith( | |||
'component_report.subscribe_x_success.report.frequency..qualifier.trk' | |||
); | |||
}); | |||
it('should handle unsubscription', async () => { | |||
const component = mockComponent(); | |||
const branch = mockBranch(); | |||
const wrapper = shallowRender({ component, branch }); | |||
await waitAndUpdate(wrapper); | |||
wrapper.setState({ status: mockComponentReportStatus({ componentFrequency: 'compfreq' }) }); | |||
await wrapper.instance().handleUnsubscribe(); | |||
expect(unsubscribeFromEmailReport).toHaveBeenCalledWith(component.key, branch.name); | |||
expect(addGlobalSuccessMessage).toHaveBeenCalledWith( | |||
'component_report.unsubscribe_x_success.report.frequency.compfreq.qualifier.trk' | |||
); | |||
}); | |||
function shallowRender(props: Partial<ComponentReportActions['props']> = {}) { | |||
return shallow<ComponentReportActions>( | |||
<ComponentReportActions | |||
appState={{ qualifiers: [ComponentQualifier.Portfolio] }} | |||
component={mockComponent()} | |||
currentUser={mockCurrentUser()} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -0,0 +1,56 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2021 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import { mockComponent } from '../../../helpers/testMocks'; | |||
import { ComponentQualifier } from '../../../types/component'; | |||
import ComponentReportActionsRenderer, { | |||
ComponentReportActionsRendererProps | |||
} from '../ComponentReportActionsRenderer'; | |||
it('should render correctly', () => { | |||
expect(shallowRender({ canSubscribe: false })).toMatchSnapshot('cannot subscribe'); | |||
expect(shallowRender({ canSubscribe: true, subscribed: false })).toMatchSnapshot( | |||
'can subscribe, not subscribed' | |||
); | |||
expect(shallowRender({ canSubscribe: true, subscribed: true })).toMatchSnapshot( | |||
'can subscribe, subscribed' | |||
); | |||
expect(shallowRender({ canSubscribe: true, currentUserHasEmail: false })).toMatchSnapshot( | |||
'current user without email' | |||
); | |||
expect(shallowRender({ component: mockComponent() })).toMatchSnapshot('not a portfolio'); | |||
}); | |||
function shallowRender(props: Partial<ComponentReportActionsRendererProps> = {}) { | |||
return shallow<ComponentReportActionsRendererProps>( | |||
<ComponentReportActionsRenderer | |||
component={mockComponent({ qualifier: ComponentQualifier.Portfolio })} | |||
canSubscribe={true} | |||
subscribed={false} | |||
currentUserHasEmail={true} | |||
frequency="weekly" | |||
handleSubscription={jest.fn()} | |||
handleUnsubscription={jest.fn()} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -0,0 +1,136 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly: can subscribe, not subscribed 1`] = ` | |||
<Dropdown | |||
overlay={ | |||
<ul | |||
className="menu" | |||
> | |||
<li> | |||
<a | |||
download="MyProject - PDF Report.pdf" | |||
href="/api/governance_reports/download?componentKey=my-project" | |||
rel="noopener noreferrer" | |||
target="_blank" | |||
> | |||
download_verb | |||
</a> | |||
</li> | |||
<li> | |||
<a | |||
href="#" | |||
onClick={[MockFunction]} | |||
> | |||
component_report.subscribe_x.report.frequency.weekly | |||
</a> | |||
</li> | |||
</ul> | |||
} | |||
> | |||
<Button | |||
className="dropdown-toggle" | |||
> | |||
component_report.report.qualifier.VW | |||
<DropdownIcon | |||
className="spacer-left icon-half-transparent" | |||
/> | |||
</Button> | |||
</Dropdown> | |||
`; | |||
exports[`should render correctly: can subscribe, subscribed 1`] = ` | |||
<Dropdown | |||
overlay={ | |||
<ul | |||
className="menu" | |||
> | |||
<li> | |||
<a | |||
download="MyProject - PDF Report.pdf" | |||
href="/api/governance_reports/download?componentKey=my-project" | |||
rel="noopener noreferrer" | |||
target="_blank" | |||
> | |||
download_verb | |||
</a> | |||
</li> | |||
<li> | |||
<a | |||
href="#" | |||
onClick={[MockFunction]} | |||
> | |||
component_report.unsubscribe_x.report.frequency.weekly | |||
</a> | |||
</li> | |||
</ul> | |||
} | |||
> | |||
<Button | |||
className="dropdown-toggle" | |||
> | |||
component_report.report.qualifier.VW | |||
<DropdownIcon | |||
className="spacer-left icon-half-transparent" | |||
/> | |||
</Button> | |||
</Dropdown> | |||
`; | |||
exports[`should render correctly: cannot subscribe 1`] = ` | |||
<a | |||
download="MyProject - PDF Report.pdf" | |||
href="/api/governance_reports/download?componentKey=my-project" | |||
rel="noopener noreferrer" | |||
target="_blank" | |||
> | |||
component_report.download.qualifier.vw | |||
</a> | |||
`; | |||
exports[`should render correctly: current user without email 1`] = ` | |||
<Dropdown | |||
overlay={ | |||
<ul | |||
className="menu" | |||
> | |||
<li> | |||
<a | |||
download="MyProject - PDF Report.pdf" | |||
href="/api/governance_reports/download?componentKey=my-project" | |||
rel="noopener noreferrer" | |||
target="_blank" | |||
> | |||
download_verb | |||
</a> | |||
</li> | |||
<li> | |||
<span | |||
className="text-muted-2" | |||
> | |||
component_report.no_email_to_subscribe | |||
</span> | |||
</li> | |||
</ul> | |||
} | |||
> | |||
<Button | |||
className="dropdown-toggle" | |||
> | |||
component_report.report.qualifier.VW | |||
<DropdownIcon | |||
className="spacer-left icon-half-transparent" | |||
/> | |||
</Button> | |||
</Dropdown> | |||
`; | |||
exports[`should render correctly: not a portfolio 1`] = ` | |||
<a | |||
download="MyProject - PDF Report.pdf" | |||
href="/api/governance_reports/download?componentKey=my-project" | |||
rel="noopener noreferrer" | |||
target="_blank" | |||
> | |||
component_report.download.qualifier.trk | |||
</a> | |||
`; |
@@ -0,0 +1,36 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2021 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 { ComponentReportStatus } from '../../types/component-report'; | |||
export function mockComponentReportStatus( | |||
props?: Partial<ComponentReportStatus> | |||
): ComponentReportStatus { | |||
return { | |||
canAdmin: true, | |||
canDownload: true, | |||
canSubscribe: true, | |||
componentRecipients: [], | |||
globalFrequency: '', | |||
globalRecipients: [], | |||
subscribed: false, | |||
...props | |||
}; | |||
} |
@@ -0,0 +1,30 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2021 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. | |||
*/ | |||
export interface ComponentReportStatus { | |||
canAdmin: boolean; | |||
canDownload: boolean; | |||
canSubscribe: boolean; | |||
componentFrequency?: string; | |||
componentRecipients: Array<string>; | |||
globalFrequency: string; | |||
globalRecipients: Array<string>; | |||
subscribed: boolean; | |||
} |
@@ -3937,7 +3937,6 @@ portfolio.activity_link=Activity | |||
portfolio.measures_link=Measures | |||
portfolio.language_breakdown_link=Language breakdown | |||
portfolio.breakdown=Portfolio breakdown | |||
portfolio.pdf_report=Portfolio PDF Report | |||
portfolio.number_of_projects=Number of projects | |||
portfolio.number_of_lines=Number of lines of code | |||
@@ -4168,3 +4167,17 @@ webhooks.url.bad_format=Bad format of URL. | |||
webhooks.url.bad_protocol=URL must start with "http://" or "https://". | |||
webhooks.url.description=Server endpoint that will receive the webhook payload, for example: "http://my_server/foo". If HTTP Basic authentication is used, HTTPS is recommended to avoid man in the middle attacks. Example: "https://myLogin:myPassword@my_server/foo" | |||
webhooks.url.required=URL is required. | |||
#------------------------------------------------------------------------------ | |||
# | |||
# COMPONENT REPORT | |||
# | |||
#------------------------------------------------------------------------------ | |||
component_report.report={0} PDF report | |||
component_report.download=Download {0} PDF report | |||
component_report.no_email_to_subscribe=Email subscription requires an email address. | |||
component_report.subscribe_x=Subscribe to {0} report | |||
component_report.unsubscribe_x=Unsubscribe from {0} report | |||
component_report.subscribe_x_success=Subscription successful. You will receive a {0} report for this {1} by email. | |||
component_report.unsubscribe_x_success=Subscription successfully canceled. You won't receive a {0} report for this {1} by email. |