@@ -19,6 +19,11 @@ | |||
*/ | |||
import { getJSON } from '../helpers/request'; | |||
export function getLanguages(): Promise<any> { | |||
export interface Language { | |||
key: string; | |||
name: string; | |||
} | |||
export function getLanguages(): Promise<Language[]> { | |||
return getJSON('/api/languages/list').then(r => r.languages); | |||
} |
@@ -18,15 +18,16 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { getJSON, RequestData } from '../helpers/request'; | |||
import throwGlobalError from '../app/utils/throwGlobalError'; | |||
export function getMeasures( | |||
componentKey: string, | |||
metrics: string[], | |||
branch?: string | |||
): Promise<any> { | |||
): Promise<Array<{ metric: string; value?: string }>> { | |||
const url = '/api/measures/component'; | |||
const data = { componentKey, metricKeys: metrics.join(','), branch }; | |||
return getJSON(url, data).then(r => r.component.measures); | |||
return getJSON(url, data).then(r => r.component.measures, throwGlobalError); | |||
} | |||
export function getMeasuresAndMeta( |
@@ -18,7 +18,8 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { getJSON } from '../helpers/request'; | |||
import { Metric } from '../app/types'; | |||
export function getMetrics(): Promise<any> { | |||
export function getMetrics(): Promise<Metric[]> { | |||
return getJSON('/api/metrics/search', { ps: 9999 }).then(r => r.metrics); | |||
} |
@@ -0,0 +1,55 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact 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 { getJSON, post } from '../helpers/request'; | |||
import throwGlobalError from '../app/utils/throwGlobalError'; | |||
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( | |||
throwGlobalError | |||
); | |||
} | |||
export function getReportUrl(component: string): string { | |||
return ( | |||
(window as any).baseUrl + | |||
'/api/governance_reports/download?componentKey=' + | |||
encodeURIComponent(component) | |||
); | |||
} | |||
export function subscribe(component: string): Promise<void | Response> { | |||
return post('/api/governance_reports/subscribe', { componentKey: component }).catch( | |||
throwGlobalError | |||
); | |||
} | |||
export function unsubscribe(component: string): Promise<void | Response> { | |||
return post('/api/governance_reports/unsubscribe', { componentKey: component }).catch( | |||
throwGlobalError | |||
); | |||
} |
@@ -49,15 +49,15 @@ export default class ComponentContainer extends React.PureComponent<Props, State | |||
componentDidMount() { | |||
this.mounted = true; | |||
this.fetchComponent(); | |||
this.fetchComponent(this.props); | |||
} | |||
componentDidUpdate(prevProps: Props) { | |||
componentWillReceiveProps(nextProps: Props) { | |||
if ( | |||
prevProps.location.query.id !== this.props.location.query.id || | |||
prevProps.location.query.branch !== this.props.location.query.branch | |||
nextProps.location.query.id !== this.props.location.query.id || | |||
nextProps.location.query.branch !== this.props.location.query.branch | |||
) { | |||
this.fetchComponent(); | |||
this.fetchComponent(nextProps); | |||
} | |||
} | |||
@@ -70,8 +70,8 @@ export default class ComponentContainer extends React.PureComponent<Props, State | |||
qualifier: component.breadcrumbs[component.breadcrumbs.length - 1].qualifier | |||
}); | |||
fetchComponent() { | |||
const { branch, id } = this.props.location.query; | |||
fetchComponent(props: Props) { | |||
const { branch, id } = props.location.query; | |||
this.setState({ loading: true }); | |||
const onError = (error: any) => { |
@@ -65,7 +65,7 @@ export default class ComponentNavMenu extends React.PureComponent<Props> { | |||
return this.props.component.qualifier === 'DEV'; | |||
} | |||
isView() { | |||
isPortfolio() { | |||
const { qualifier } = this.props.component; | |||
return qualifier === 'VW' || qualifier === 'SVW'; | |||
} | |||
@@ -79,7 +79,7 @@ export default class ComponentNavMenu extends React.PureComponent<Props> { | |||
return null; | |||
} | |||
const pathname = this.isView() ? '/portfolio' : '/dashboard'; | |||
const pathname = this.isPortfolio() ? '/portfolio' : '/dashboard'; | |||
return ( | |||
<li> | |||
<Link | |||
@@ -113,7 +113,7 @@ export default class ComponentNavMenu extends React.PureComponent<Props> { | |||
} | |||
}} | |||
activeClassName="active"> | |||
{this.isView() || this.isApplication() ? ( | |||
{this.isPortfolio() || this.isApplication() ? ( | |||
translate('view_projects.page') | |||
) : ( | |||
translate('code.page') | |||
@@ -124,7 +124,7 @@ export default class ComponentNavMenu extends React.PureComponent<Props> { | |||
} | |||
renderActivityLink() { | |||
if (!this.isProject() && !this.isApplication()) { | |||
if (!this.isProject() && !this.isApplication() && !this.isPortfolio()) { | |||
return null; | |||
} | |||
@@ -252,7 +252,7 @@ export default class ComponentNavMenu extends React.PureComponent<Props> { | |||
} | |||
renderSettingsLink() { | |||
if (!this.props.conf.showSettings || this.isApplication() || this.isView()) { | |||
if (!this.props.conf.showSettings || this.isApplication() || this.isPortfolio()) { | |||
return null; | |||
} | |||
return ( | |||
@@ -432,8 +432,7 @@ export default class ComponentNavMenu extends React.PureComponent<Props> { | |||
renderExtensions() { | |||
const extensions = this.props.component.extensions || []; | |||
const withoutGovernance = extensions.filter(ext => ext.name !== 'Governance'); | |||
if (!withoutGovernance.length) { | |||
if (!extensions.length) { | |||
return null; | |||
} | |||
@@ -448,7 +447,7 @@ export default class ComponentNavMenu extends React.PureComponent<Props> { | |||
<i className="icon-dropdown" /> | |||
</a> | |||
<ul className="dropdown-menu"> | |||
{withoutGovernance.map(e => this.renderExtension(e, false))} | |||
{extensions.map(e => this.renderExtension(e, false))} | |||
</ul> | |||
</li> | |||
); |
@@ -99,3 +99,24 @@ it('should work for long-living branches', () => { | |||
).toMatchSnapshot() | |||
); | |||
}); | |||
it('should work for all qualifiers', () => { | |||
['TRK', 'BRC', 'VW', 'SVW', 'APP'].forEach(checkWithQualifier); | |||
expect.assertions(5); | |||
function checkWithQualifier(qualifier: string) { | |||
const component = { key: 'foo', qualifier } as Component; | |||
expect( | |||
shallow( | |||
<ComponentNavMenu | |||
branch={mainBranch} | |||
component={component} | |||
conf={{ showSettings: true }} | |||
/>, | |||
{ | |||
context: { branchesEnabled: true } | |||
} | |||
) | |||
).toMatchSnapshot(); | |||
} | |||
}); |
@@ -1,5 +1,651 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should work for all qualifiers 1`] = ` | |||
<NavBarTabs> | |||
<li> | |||
<Link | |||
activeClassName="active" | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
} | |||
> | |||
overview.page | |||
</Link> | |||
</li> | |||
<li> | |||
<Link | |||
activeClassName="active" | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/project/issues", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
"resolved": "false", | |||
}, | |||
} | |||
} | |||
> | |||
issues.page | |||
</Link> | |||
</li> | |||
<li> | |||
<Link | |||
activeClassName="active" | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/component_measures", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
} | |||
> | |||
layout.measures | |||
</Link> | |||
</li> | |||
<li> | |||
<Link | |||
activeClassName="active" | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/code", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
} | |||
> | |||
code.page | |||
</Link> | |||
</li> | |||
<li> | |||
<Link | |||
activeClassName="active" | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/project/activity", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
} | |||
> | |||
project_activity.page | |||
</Link> | |||
</li> | |||
<li | |||
className="dropdown" | |||
> | |||
<a | |||
className="dropdown-toggle is-admin" | |||
data-toggle="dropdown" | |||
href="#" | |||
id="component-navigation-admin" | |||
> | |||
layout.settings | |||
<i | |||
className="icon-dropdown" | |||
/> | |||
</a> | |||
<ul | |||
className="dropdown-menu" | |||
> | |||
<li> | |||
<Link | |||
activeClassName="active" | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/project/settings", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
} | |||
> | |||
project_settings.page | |||
</Link> | |||
</li> | |||
<li> | |||
<Link | |||
activeClassName="active" | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/project/branches", | |||
"query": Object { | |||
"id": "foo", | |||
}, | |||
} | |||
} | |||
> | |||
project_branches.page | |||
</Link> | |||
</li> | |||
<li> | |||
<Link | |||
activeClassName="active" | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/project/deletion", | |||
"query": Object { | |||
"id": "foo", | |||
}, | |||
} | |||
} | |||
> | |||
deletion.page | |||
</Link> | |||
</li> | |||
</ul> | |||
</li> | |||
</NavBarTabs> | |||
`; | |||
exports[`should work for all qualifiers 2`] = ` | |||
<NavBarTabs> | |||
<li> | |||
<Link | |||
activeClassName="active" | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
} | |||
> | |||
overview.page | |||
</Link> | |||
</li> | |||
<li> | |||
<Link | |||
activeClassName="active" | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/project/issues", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
"resolved": "false", | |||
}, | |||
} | |||
} | |||
> | |||
issues.page | |||
</Link> | |||
</li> | |||
<li> | |||
<Link | |||
activeClassName="active" | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/component_measures", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
} | |||
> | |||
layout.measures | |||
</Link> | |||
</li> | |||
<li> | |||
<Link | |||
activeClassName="active" | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/code", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
} | |||
> | |||
code.page | |||
</Link> | |||
</li> | |||
<li | |||
className="dropdown" | |||
> | |||
<a | |||
className="dropdown-toggle is-admin" | |||
data-toggle="dropdown" | |||
href="#" | |||
id="component-navigation-admin" | |||
> | |||
layout.settings | |||
<i | |||
className="icon-dropdown" | |||
/> | |||
</a> | |||
<ul | |||
className="dropdown-menu" | |||
> | |||
<li> | |||
<Link | |||
activeClassName="active" | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/project/settings", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
} | |||
> | |||
project_settings.page | |||
</Link> | |||
</li> | |||
</ul> | |||
</li> | |||
</NavBarTabs> | |||
`; | |||
exports[`should work for all qualifiers 3`] = ` | |||
<NavBarTabs> | |||
<li> | |||
<Link | |||
activeClassName="active" | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/portfolio", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
} | |||
> | |||
overview.page | |||
</Link> | |||
</li> | |||
<li> | |||
<Link | |||
activeClassName="active" | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/project/issues", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
"resolved": "false", | |||
}, | |||
} | |||
} | |||
> | |||
issues.page | |||
</Link> | |||
</li> | |||
<li> | |||
<Link | |||
activeClassName="active" | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/component_measures", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
} | |||
> | |||
layout.measures | |||
</Link> | |||
</li> | |||
<li> | |||
<Link | |||
activeClassName="active" | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/code", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
} | |||
> | |||
view_projects.page | |||
</Link> | |||
</li> | |||
<li> | |||
<Link | |||
activeClassName="active" | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/project/activity", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
} | |||
> | |||
project_activity.page | |||
</Link> | |||
</li> | |||
<li | |||
className="dropdown" | |||
> | |||
<a | |||
className="dropdown-toggle is-admin" | |||
data-toggle="dropdown" | |||
href="#" | |||
id="component-navigation-admin" | |||
> | |||
layout.settings | |||
<i | |||
className="icon-dropdown" | |||
/> | |||
</a> | |||
<ul | |||
className="dropdown-menu" | |||
> | |||
<li> | |||
<Link | |||
activeClassName="active" | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/project/deletion", | |||
"query": Object { | |||
"id": "foo", | |||
}, | |||
} | |||
} | |||
> | |||
deletion.page | |||
</Link> | |||
</li> | |||
</ul> | |||
</li> | |||
</NavBarTabs> | |||
`; | |||
exports[`should work for all qualifiers 4`] = ` | |||
<NavBarTabs> | |||
<li> | |||
<Link | |||
activeClassName="active" | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/portfolio", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
} | |||
> | |||
overview.page | |||
</Link> | |||
</li> | |||
<li> | |||
<Link | |||
activeClassName="active" | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/project/issues", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
"resolved": "false", | |||
}, | |||
} | |||
} | |||
> | |||
issues.page | |||
</Link> | |||
</li> | |||
<li> | |||
<Link | |||
activeClassName="active" | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/component_measures", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
} | |||
> | |||
layout.measures | |||
</Link> | |||
</li> | |||
<li> | |||
<Link | |||
activeClassName="active" | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/code", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
} | |||
> | |||
view_projects.page | |||
</Link> | |||
</li> | |||
<li> | |||
<Link | |||
activeClassName="active" | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/project/activity", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
} | |||
> | |||
project_activity.page | |||
</Link> | |||
</li> | |||
</NavBarTabs> | |||
`; | |||
exports[`should work for all qualifiers 5`] = ` | |||
<NavBarTabs> | |||
<li> | |||
<Link | |||
activeClassName="active" | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
} | |||
> | |||
overview.page | |||
</Link> | |||
</li> | |||
<li> | |||
<Link | |||
activeClassName="active" | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/project/issues", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
"resolved": "false", | |||
}, | |||
} | |||
} | |||
> | |||
issues.page | |||
</Link> | |||
</li> | |||
<li> | |||
<Link | |||
activeClassName="active" | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/component_measures", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
} | |||
> | |||
layout.measures | |||
</Link> | |||
</li> | |||
<li> | |||
<Link | |||
activeClassName="active" | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/code", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
} | |||
> | |||
view_projects.page | |||
</Link> | |||
</li> | |||
<li> | |||
<Link | |||
activeClassName="active" | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/project/activity", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
} | |||
> | |||
project_activity.page | |||
</Link> | |||
</li> | |||
<li | |||
className="dropdown" | |||
> | |||
<a | |||
className="dropdown-toggle is-admin" | |||
data-toggle="dropdown" | |||
href="#" | |||
id="component-navigation-admin" | |||
> | |||
layout.settings | |||
<i | |||
className="icon-dropdown" | |||
/> | |||
</a> | |||
<ul | |||
className="dropdown-menu" | |||
> | |||
<li> | |||
<Link | |||
activeClassName="active" | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/project/deletion", | |||
"query": Object { | |||
"id": "foo", | |||
}, | |||
} | |||
} | |||
> | |||
deletion.page | |||
</Link> | |||
</li> | |||
</ul> | |||
</li> | |||
</NavBarTabs> | |||
`; | |||
exports[`should work for long-living branches 1`] = ` | |||
<NavBarTabs> | |||
<li> |
@@ -67,6 +67,7 @@ export interface Component { | |||
qualifier: string; | |||
}>; | |||
configuration?: ComponentConfiguration; | |||
description?: string; | |||
extensions?: ComponentExtension[]; | |||
isFavorite?: boolean; | |||
key: string; |
@@ -21,6 +21,7 @@ import * as ReactRedux from 'react-redux'; | |||
import * as ReactRouter from 'react-router'; | |||
import Select from 'react-select'; | |||
import Modal from 'react-modal'; | |||
import throwGlobalError from './throwGlobalError'; | |||
import * as measures from '../../helpers/measures'; | |||
import * as request from '../../helpers/request'; | |||
import * as icons from '../../components/icons-components/icons'; | |||
@@ -41,7 +42,7 @@ const exposeLibraries = () => { | |||
window.ReactRouter = ReactRouter; | |||
window.SonarIcons = icons; | |||
window.SonarMeasures = measures; | |||
window.SonarRequest = request; | |||
window.SonarRequest = { ...request, throwGlobalError }; | |||
window.SonarComponents = { | |||
DateFromNow, | |||
DateFormatter, |
@@ -32,7 +32,6 @@ import Landing from '../components/Landing'; | |||
import ProjectAdminContainer from '../components/ProjectAdminContainer'; | |||
import ProjectPageExtension from '../components/extensions/ProjectPageExtension'; | |||
import ProjectAdminPageExtension from '../components/extensions/ProjectAdminPageExtension'; | |||
import PortfolioDashboard from '../components/extensions/PortfolioDashboard'; | |||
import PortfoliosPage from '../components/extensions/PortfoliosPage'; | |||
import AdminContainer from '../components/AdminContainer'; | |||
import GlobalPageExtension from '../components/extensions/GlobalPageExtension'; | |||
@@ -53,6 +52,7 @@ import metricsRoutes from '../../apps/metrics/routes'; | |||
import overviewRoutes from '../../apps/overview/routes'; | |||
import organizationsRoutes from '../../apps/organizations/routes'; | |||
import permissionTemplatesRoutes from '../../apps/permission-templates/routes'; | |||
import portfolioRoutes from '../../apps/portfolio/routes'; | |||
import projectActivityRoutes from '../../apps/projectActivity/routes'; | |||
import projectAdminRoutes from '../../apps/project-admin/routes'; | |||
import projectBranchesRoutes from '../../apps/projectBranches/routes'; | |||
@@ -125,7 +125,6 @@ const startReactApp = () => { | |||
<Redirect from="/dashboard/index" to="/dashboard" /> | |||
<Redirect from="/governance" to="/portfolio" /> | |||
<Redirect from="/groups" to="/admin/groups" /> | |||
<Redirect from="/extension/governance/governance" to="/portfolio" /> | |||
<Redirect from="/extension/governance/portfolios" to="/portfolios" /> | |||
<Redirect from="/metrics" to="/admin/custom_metrics" /> | |||
<Redirect from="/permission_templates" to="/admin/permission_templates" /> | |||
@@ -189,7 +188,7 @@ const startReactApp = () => { | |||
<Route path="code" childRoutes={codeRoutes} /> | |||
<Route path="component_measures" childRoutes={componentMeasuresRoutes} /> | |||
<Route path="dashboard" childRoutes={overviewRoutes} /> | |||
<Route path="portfolio" component={PortfolioDashboard} /> | |||
<Route path="portfolio" childRoutes={portfolioRoutes} /> | |||
<Route path="project/activity" childRoutes={projectActivityRoutes} /> | |||
<Route | |||
path="project/extension/:pluginKey/:extensionKey" |
@@ -42,10 +42,7 @@ export default function ComponentMeasure({ component, metricKey, metricType }: P | |||
return <span />; | |||
} | |||
// TODO | |||
const AnyMeasure = Measure as any; | |||
return ( | |||
<AnyMeasure measure={{ ...measure, metric: { key: finalMetricKey, type: finalMetricType } }} /> | |||
<Measure measure={{ ...measure, metric: { key: finalMetricKey, type: finalMetricType } }} /> | |||
); | |||
} |
@@ -23,13 +23,13 @@ import { Link } from 'react-router'; | |||
import ComplexityDistribution from '../../../components/shared/ComplexityDistribution'; | |||
import HistoryIcon from '../../../components/icons-components/HistoryIcon'; | |||
import IssueTypeIcon from '../../../components/ui/IssueTypeIcon'; | |||
import LanguageDistribution from '../../../components/charts/LanguageDistribution'; | |||
import LanguageDistributionContainer from '../../../components/charts/LanguageDistributionContainer'; | |||
import LeakPeriodLegend from './LeakPeriodLegend'; | |||
import Measure from '../../../components/measure/Measure'; | |||
import Tooltip from '../../../components/controls/Tooltip'; | |||
import { isFileType } from '../utils'; | |||
import { getLocalizedMetricName, translate, translateWithParameters } from '../../../helpers/l10n'; | |||
import { getComponentMeasureHistory } from '../../../helpers/urls'; | |||
import { getMeasureHistoryUrl } from '../../../helpers/urls'; | |||
import { isDiffMetric } from '../../../helpers/measures'; | |||
/*:: import type { Component, Period } from '../types'; */ | |||
/*:: import type { MeasureEnhanced } from '../../../components/measure/types'; */ | |||
@@ -121,7 +121,7 @@ export default class MeasureHeader extends React.PureComponent { | |||
overlay={translate('component_measures.show_metric_history')}> | |||
<Link | |||
className="js-show-history spacer-left button button-small button-compact" | |||
to={getComponentMeasureHistory(component.key, metric.key, branch)}> | |||
to={getMeasureHistoryUrl(component.key, metric.key, branch)}> | |||
<HistoryIcon /> | |||
</Link> | |||
</Tooltip> | |||
@@ -137,7 +137,10 @@ export default class MeasureHeader extends React.PureComponent { | |||
{secondaryMeasure && | |||
secondaryMeasure.metric.key === 'ncloc_language_distribution' && ( | |||
<div className="measure-details-secondary"> | |||
<LanguageDistribution alignTicks={true} distribution={secondaryMeasure.value} /> | |||
<LanguageDistributionContainer | |||
alignTicks={true} | |||
distribution={secondaryMeasure.value} | |||
/> | |||
</div> | |||
)} | |||
{secondaryMeasure && |
@@ -82,7 +82,7 @@ it('should render with branch', () => { | |||
it('should display secondary measure too', () => { | |||
const wrapper = shallow(<MeasureHeader {...PROPS} secondaryMeasure={SECONDARY} />); | |||
expect(wrapper.find('LanguageDistribution')).toHaveLength(1); | |||
expect(wrapper.find('Connect(LanguageDistribution)')).toHaveLength(1); | |||
}); | |||
it('shohuld display correctly for open file', () => { |
@@ -19,7 +19,7 @@ | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import { AutoSizer } from 'react-virtualized'; | |||
import AutoSizer from 'react-virtualized/dist/commonjs/AutoSizer'; | |||
import { scaleLinear, scaleOrdinal } from 'd3-scale'; | |||
import ColorBoxLegend from '../../../components/charts/ColorBoxLegend'; | |||
import ColorGradientLegend from '../../../components/charts/ColorGradientLegend'; |
@@ -21,9 +21,9 @@ | |||
import React from 'react'; | |||
import { Link } from 'react-router'; | |||
import Analysis from './Analysis'; | |||
import PreviewGraph from './PreviewGraph'; | |||
import { getMetrics } from '../../../api/metrics'; | |||
import { getProjectActivity } from '../../../api/projectActivity'; | |||
import PreviewGraph from '../../../components/preview-graph/PreviewGraph'; | |||
import { translate } from '../../../helpers/l10n'; | |||
/*:: import type { Analysis as AnalysisType } from '../../projectActivity/types'; */ | |||
/*:: import type { History, Metric } from '../types'; */ | |||
@@ -114,7 +114,6 @@ export default class AnalysesList extends React.PureComponent { | |||
history={this.props.history} | |||
project={this.props.project} | |||
metrics={this.state.metrics} | |||
router={this.props.router} | |||
/> | |||
{this.renderList(analyses)} |
@@ -39,7 +39,7 @@ import { getPeriodDate } from '../../../helpers/periods'; | |||
import { | |||
getComponentDrilldownUrl, | |||
getComponentIssuesUrl, | |||
getComponentMeasureHistory | |||
getMeasureHistoryUrl | |||
} from '../../../helpers/urls'; | |||
export default function enhance(ComposedComponent) { | |||
@@ -175,7 +175,7 @@ export default function enhance(ComposedComponent) { | |||
return ( | |||
<Link | |||
className={linkClass} | |||
to={getComponentMeasureHistory(this.props.component.key, metricKey, this.props.branch)}> | |||
to={getMeasureHistoryUrl(this.props.component.key, metricKey, this.props.branch)}> | |||
<HistoryIcon /> | |||
</Link> | |||
); |
@@ -21,7 +21,7 @@ import React from 'react'; | |||
import PropTypes from 'prop-types'; | |||
import classNames from 'classnames'; | |||
import { DrilldownLink } from '../../../components/shared/drilldown-link'; | |||
import LanguageDistribution from '../../../components/charts/LanguageDistribution'; | |||
import LanguageDistributionContainer from '../../../components/charts/LanguageDistributionContainer'; | |||
import SizeRating from '../../../components/ui/SizeRating'; | |||
import { formatMeasure } from '../../../helpers/measures'; | |||
import { getMetricName } from '../helpers/metrics'; | |||
@@ -57,7 +57,7 @@ export default class MetaSize extends React.PureComponent { | |||
return languageDistribution ? ( | |||
<div id="overview-language-distribution" className="overview-meta-size-lang-dist"> | |||
<LanguageDistribution distribution={languageDistribution.value} /> | |||
<LanguageDistributionContainer distribution={languageDistribution.value} /> | |||
</div> | |||
) : null; | |||
}; |
@@ -0,0 +1,121 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact 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 { getDisplayedHistoryMetrics, DEFAULT_GRAPH } from '../../projectActivity/utils'; | |||
import PreviewGraph from '../../../components/preview-graph/PreviewGraph'; | |||
import { getMetrics } from '../../../api/metrics'; | |||
import { getAllTimeMachineData } from '../../../api/time-machine'; | |||
import { Metric } from '../../../app/types'; | |||
import { parseDate } from '../../../helpers/dates'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { getCustomGraph, getGraph } from '../../../helpers/storage'; | |||
const AnyPreviewGraph = PreviewGraph as any; | |||
interface History { | |||
[metric: string]: Array<{ date: Date; value: string }>; | |||
} | |||
interface Props { | |||
component: string; | |||
} | |||
interface State { | |||
history?: History; | |||
loading: boolean; | |||
metrics?: Metric[]; | |||
} | |||
export default class Activity extends React.PureComponent<Props> { | |||
mounted: boolean; | |||
state: State = { loading: true }; | |||
componentDidMount() { | |||
this.mounted = true; | |||
this.fetchHistory(); | |||
} | |||
componentDidUpdate(prevProps: Props) { | |||
if (prevProps.component !== this.props.component) { | |||
this.fetchHistory(); | |||
} | |||
} | |||
componentWillUnmount() { | |||
this.mounted = false; | |||
} | |||
fetchHistory = () => { | |||
const { component } = this.props; | |||
let graphMetrics = getDisplayedHistoryMetrics(getGraph(), getCustomGraph()); | |||
if (!graphMetrics || graphMetrics.length <= 0) { | |||
graphMetrics = getDisplayedHistoryMetrics(DEFAULT_GRAPH, []); | |||
} | |||
this.setState({ loading: true }); | |||
return Promise.all([getAllTimeMachineData(component, graphMetrics), getMetrics()]).then( | |||
([timeMachine, metrics]) => { | |||
if (this.mounted) { | |||
const history: History = {}; | |||
timeMachine.measures.forEach(measure => { | |||
const measureHistory = measure.history.map(analysis => ({ | |||
date: parseDate(analysis.date), | |||
value: analysis.value | |||
})); | |||
history[measure.metric] = measureHistory; | |||
}); | |||
this.setState({ history, loading: false, metrics }); | |||
} | |||
}, | |||
() => { | |||
if (this.mounted) { | |||
this.setState({ loading: false }); | |||
} | |||
} | |||
); | |||
}; | |||
renderWhenEmpty = () => <div className="note">{translate('component_measures.no_history')}</div>; | |||
render() { | |||
return ( | |||
<div className="huge-spacer-top"> | |||
<header className="page-header"> | |||
<h3 className="page-title">{translate('project_activity.page')}</h3> | |||
</header> | |||
{this.state.loading ? ( | |||
<i className="spinner" /> | |||
) : ( | |||
this.state.metrics != undefined && | |||
this.state.history != undefined && ( | |||
<AnyPreviewGraph | |||
history={this.state.history} | |||
metrics={this.state.metrics} | |||
project={this.props.component} | |||
renderWhenEmpty={this.renderWhenEmpty} | |||
/> | |||
) | |||
)} | |||
</div> | |||
); | |||
} | |||
} |
@@ -0,0 +1,150 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact 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 Summary from './Summary'; | |||
import Report from './Report'; | |||
import WorstProjects from './WorstProjects'; | |||
import ReleasabilityBox from './ReleasabilityBox'; | |||
import ReliabilityBox from './ReliabilityBox'; | |||
import SecurityBox from './SecurityBox'; | |||
import MaintainabilityBox from './MaintainabilityBox'; | |||
import Activity from './Activity'; | |||
import { getMeasures } from '../../../api/measures'; | |||
import { getChildren } from '../../../api/components'; | |||
import { PORTFOLIO_METRICS, SUB_COMPONENTS_METRICS, convertMeasures } from '../utils'; | |||
import { SubComponent } from '../types'; | |||
import '../styles.css'; | |||
interface Props { | |||
component: { key: string; name: string }; | |||
} | |||
interface State { | |||
loading: boolean; | |||
measures?: { [key: string]: string | undefined }; | |||
subComponents?: SubComponent[]; | |||
totalSubComponents?: number; | |||
} | |||
export default class App extends React.PureComponent<Props, State> { | |||
mounted: boolean; | |||
state: State = { loading: true }; | |||
componentDidMount() { | |||
this.mounted = true; | |||
const html = document.querySelector('html'); | |||
if (html) { | |||
html.classList.add('dashboard-page'); | |||
} | |||
this.fetchData(); | |||
} | |||
componentDidUpdate(prevProps: Props) { | |||
if (prevProps.component !== this.props.component) { | |||
this.fetchData(); | |||
} | |||
} | |||
componentWillUnmount() { | |||
this.mounted = false; | |||
const html = document.querySelector('html'); | |||
if (html) { | |||
html.classList.remove('dashboard-page'); | |||
} | |||
} | |||
fetchData() { | |||
this.setState({ loading: true }); | |||
Promise.all([ | |||
getMeasures(this.props.component.key, PORTFOLIO_METRICS), | |||
getChildren(this.props.component.key, SUB_COMPONENTS_METRICS, { ps: 20 }) | |||
]).then( | |||
([measures, subComponents]) => { | |||
if (this.mounted) { | |||
this.setState({ | |||
loading: false, | |||
measures: convertMeasures(measures), | |||
subComponents: subComponents.components.map((component: any) => ({ | |||
...component, | |||
measures: convertMeasures(component.measures) | |||
})), | |||
totalSubComponents: subComponents.paging.total | |||
}); | |||
} | |||
}, | |||
() => { | |||
if (this.mounted) { | |||
this.setState({ loading: false }); | |||
} | |||
} | |||
); | |||
} | |||
renderSpinner() { | |||
return ( | |||
<div className="page page-limited"> | |||
<div className="text-center"> | |||
<i className="spinner spinner-margin" /> | |||
</div> | |||
</div> | |||
); | |||
} | |||
render() { | |||
const { component } = this.props; | |||
const { loading, measures, subComponents, totalSubComponents } = this.state; | |||
if (loading) { | |||
return this.renderSpinner(); | |||
} | |||
return ( | |||
<div className="page page-limited"> | |||
<div className="page-with-sidebar"> | |||
<div className="page-main"> | |||
{measures != undefined && ( | |||
<div className="portfolio-boxes"> | |||
<ReleasabilityBox component={component.key} measures={measures} /> | |||
<ReliabilityBox component={component.key} measures={measures} /> | |||
<SecurityBox component={component.key} measures={measures} /> | |||
<MaintainabilityBox component={component.key} measures={measures} /> | |||
</div> | |||
)} | |||
{subComponents != undefined && | |||
totalSubComponents != undefined && ( | |||
<WorstProjects | |||
component={component.key} | |||
subComponents={subComponents} | |||
total={totalSubComponents} | |||
/> | |||
)} | |||
</div> | |||
<aside className="page-sidebar-fixed"> | |||
{measures != undefined && <Summary component={component} measures={measures} />} | |||
<Activity component={component.key} /> | |||
<Report component={component} /> | |||
</aside> | |||
</div> | |||
</div> | |||
); | |||
} | |||
} |
@@ -0,0 +1,59 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact 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 { Link } from 'react-router'; | |||
import { FormattedMessage } from 'react-intl'; | |||
import Rating from '../../../components/ui/Rating'; | |||
import Measure from '../../../components/measure/Measure'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { getComponentDrilldownUrl } from '../../../helpers/urls'; | |||
interface Props { | |||
component: string; | |||
effort: { projects: number; rating: number }; | |||
metricKey: string; | |||
} | |||
export default function Effort({ component, effort, metricKey }: Props) { | |||
return ( | |||
<div className="portfolio-effort"> | |||
<FormattedMessage | |||
defaultMessage={translate('portfolio.x_in_y')} | |||
id="portfolio.x_in_y" | |||
values={{ | |||
projects: ( | |||
<Link to={getComponentDrilldownUrl(component, metricKey)}> | |||
<span> | |||
<Measure | |||
measure={{ | |||
metric: { key: 'projects', type: 'SHORT_INT' }, | |||
value: String(effort.projects) | |||
}} | |||
/>{' '} | |||
{translate('projects_')} | |||
</span> | |||
</Link> | |||
), | |||
rating: <Rating small={true} value={effort.rating} /> | |||
}} | |||
/> | |||
</div> | |||
); | |||
} |
@@ -0,0 +1,38 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact 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 { Link } from 'react-router'; | |||
import { HistoryIcon } from '../../../components/icons-components/icons'; | |||
import { getMeasureHistoryUrl } from '../../../helpers/urls'; | |||
interface Props { | |||
component: string; | |||
metric: string; | |||
} | |||
export default function HistoryButtonLink({ component, metric }: Props) { | |||
return ( | |||
<Link | |||
className="button button-small button-compact spacer-left text-text-bottom" | |||
to={getMeasureHistoryUrl(component, metric)}> | |||
<HistoryIcon size={14} /> | |||
</Link> | |||
); | |||
} |
@@ -0,0 +1,37 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact 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 { Link } from 'react-router'; | |||
import Rating from '../../../components/ui/Rating'; | |||
import { getMeasureTreemapUrl } from '../../../helpers/urls'; | |||
interface Props { | |||
component: string; | |||
metric: string; | |||
value: string; | |||
} | |||
export default function MainRating({ component, metric, value }: Props) { | |||
return ( | |||
<Link to={getMeasureTreemapUrl(component, metric)} className="portfolio-box-rating"> | |||
<Rating value={value} /> | |||
</Link> | |||
); | |||
} |
@@ -0,0 +1,54 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact 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 Effort from './Effort'; | |||
import MainRating from './MainRating'; | |||
import MeasuresButtonLink from './MeasuresButtonLink'; | |||
import HistoryButtonLink from './HistoryButtonLink'; | |||
import RatingFreshness from './RatingFreshness'; | |||
import { translate } from '../../../helpers/l10n'; | |||
interface Props { | |||
component: string; | |||
measures: { [key: string]: string | undefined }; | |||
} | |||
export default function MaintainabilityBox({ component, measures }: Props) { | |||
const rating = measures['sqale_rating']; | |||
const lastMaintainabilityChange = measures['last_change_on_maintainability_rating']; | |||
const rawEffort = measures['maintainability_rating_effort']; | |||
const effort = rawEffort ? JSON.parse(rawEffort) : undefined; | |||
return ( | |||
<div className="portfolio-box portfolio-maintainability"> | |||
<h2 className="portfolio-box-title"> | |||
{translate('metric_domain.Maintainability')} | |||
<MeasuresButtonLink component={component} metric="Maintainability" /> | |||
<HistoryButtonLink component={component} metric="sqale_rating" /> | |||
</h2> | |||
{rating && <MainRating component={component} metric={'sqale_rating'} value={rating} />} | |||
<RatingFreshness lastChange={lastMaintainabilityChange} /> | |||
{effort && <Effort component={component} effort={effort} metricKey={'sqale_rating'} />} | |||
</div> | |||
); | |||
} |
@@ -0,0 +1,38 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact 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 { Link } from 'react-router'; | |||
import BubblesIcon from '../../../components/icons-components/BubblesIcon'; | |||
import { getComponentDrilldownUrl } from '../../../helpers/urls'; | |||
interface Props { | |||
component: string; | |||
metric: string; | |||
} | |||
export default function MeasuresButtonLink({ component, metric }: Props) { | |||
return ( | |||
<Link | |||
className="button button-small button-compact spacer-left text-text-bottom" | |||
to={getComponentDrilldownUrl(component, metric)}> | |||
<BubblesIcon size={14} /> | |||
</Link> | |||
); | |||
} |
@@ -0,0 +1,49 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact 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 { FormattedMessage } from 'react-intl'; | |||
import DateFromNow from '../../../components/intl/DateFromNow'; | |||
import Rating from '../../../components/ui/Rating'; | |||
import { translate } from '../../../helpers/l10n'; | |||
interface Props { | |||
lastChange?: string; | |||
} | |||
export default function RatingFreshness({ lastChange }: Props) { | |||
if (!lastChange) { | |||
return <div className="portfolio-freshness"> </div>; | |||
} | |||
const data = JSON.parse(lastChange); | |||
return ( | |||
<div className="portfolio-freshness"> | |||
<FormattedMessage | |||
defaultMessage={translate('portfolio.was_x_y')} | |||
id="portfolio.was_x_y" | |||
values={{ | |||
rating: <Rating value={data.value} small={true} />, | |||
date: <DateFromNow date={data.date} /> | |||
}} | |||
/> | |||
</div> | |||
); | |||
} |
@@ -0,0 +1,68 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact 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 { Link } from 'react-router'; | |||
import RatingFreshness from './RatingFreshness'; | |||
import Rating from '../../../components/ui/Rating'; | |||
import Measure from '../../../components/measure/Measure'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { getComponentDrilldownUrl } from '../../../helpers/urls'; | |||
interface Props { | |||
component: string; | |||
measures: { [key: string]: string | undefined }; | |||
} | |||
export default function ReleasabilityBox({ component, measures }: Props) { | |||
const rating = measures['releasability_rating']; | |||
const lastReleasabilityChange = measures['last_change_on_releasability_rating']; | |||
const effort = measures['releasability_effort']; | |||
return ( | |||
<div className="portfolio-box portfolio-releasability"> | |||
<h2 className="portfolio-box-title">{translate('metric_domain.Releasability')}</h2> | |||
{rating && ( | |||
<Link | |||
to={getComponentDrilldownUrl(component, 'alert_status')} | |||
className="portfolio-box-rating"> | |||
<Rating value={rating} /> | |||
</Link> | |||
)} | |||
<RatingFreshness lastChange={lastReleasabilityChange} /> | |||
{effort && | |||
Number(effort) > 0 && ( | |||
<div className="portfolio-effort"> | |||
<Link to={getComponentDrilldownUrl(component, 'alert_status')}> | |||
<span> | |||
<Measure | |||
measure={{ metric: { key: 'projects', type: 'SHORT_INT' }, value: effort }} | |||
/>{' '} | |||
{Number(effort) === 1 ? 'project' : 'projects'} | |||
</span> | |||
</Link>{' '} | |||
<span className="level level-ERROR level-small">{translate('metric.level.ERROR')}</span> | |||
</div> | |||
)} | |||
</div> | |||
); | |||
} |
@@ -0,0 +1,54 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact 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 Effort from './Effort'; | |||
import MeasuresButtonLink from './MeasuresButtonLink'; | |||
import HistoryButtonLink from './HistoryButtonLink'; | |||
import MainRating from './MainRating'; | |||
import RatingFreshness from './RatingFreshness'; | |||
import { translate } from '../../../helpers/l10n'; | |||
interface Props { | |||
component: string; | |||
measures: { [key: string]: string | undefined }; | |||
} | |||
export default function ReliabilityBox({ component, measures }: Props) { | |||
const rating = measures['reliability_rating']; | |||
const lastReliabilityChange = measures['last_change_on_reliability_rating']; | |||
const rawEffort = measures['reliability_rating_effort']; | |||
const effort = rawEffort ? JSON.parse(rawEffort) : undefined; | |||
return ( | |||
<div className="portfolio-box portfolio-reliability"> | |||
<h2 className="portfolio-box-title"> | |||
{translate('metric_domain.Reliability')} | |||
<MeasuresButtonLink component={component} metric="Reliability" /> | |||
<HistoryButtonLink component={component} metric="reliability_rating" /> | |||
</h2> | |||
{rating && <MainRating component={component} metric="reliability_rating" value={rating} />} | |||
<RatingFreshness lastChange={lastReliabilityChange} /> | |||
{effort && <Effort component={component} effort={effort} metricKey="reliability_rating" />} | |||
</div> | |||
); | |||
} |
@@ -0,0 +1,112 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact 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 SubscriptionContainer from './SubscriptionContainer'; | |||
import { getReportStatus, ReportStatus, getReportUrl } from '../../../api/report'; | |||
import { translate } from '../../../helpers/l10n'; | |||
interface Props { | |||
component: { key: string; name: string }; | |||
} | |||
interface State { | |||
loading: boolean; | |||
status?: ReportStatus; | |||
} | |||
export default class Report extends React.PureComponent<Props, State> { | |||
mounted: boolean; | |||
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 }); | |||
} | |||
} | |||
); | |||
} | |||
renderHeader = () => ( | |||
<header className="page-header"> | |||
<h3 className="page-title">{translate('report.page')}</h3> | |||
</header> | |||
); | |||
render() { | |||
const { component } = this.props; | |||
const { status, loading } = this.state; | |||
if (loading) { | |||
return ( | |||
<div className="huge-spacer-top"> | |||
{this.renderHeader()} | |||
<i className="spinner" /> | |||
</div> | |||
); | |||
} | |||
if (!status) { | |||
return null; | |||
} | |||
return ( | |||
<div className="huge-spacer-top"> | |||
{this.renderHeader()} | |||
{!status.canDownload && ( | |||
<div className="note js-report-cant-download">{translate('report.cant_download')}</div> | |||
)} | |||
{status.canDownload && ( | |||
<div className="js-report-can-download"> | |||
{translate('report.can_download')} | |||
<div className="spacer-top"> | |||
<a | |||
className="button js-report-download" | |||
href={getReportUrl(component.key)} | |||
target="_blank" | |||
download={component.name + ' - Executive Report.pdf'}> | |||
{translate('report.print')} | |||
</a> | |||
</div> | |||
</div> | |||
)} | |||
{status.canSubscribe && <SubscriptionContainer component={component.key} status={status} />} | |||
</div> | |||
); | |||
} | |||
} |
@@ -0,0 +1,54 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact 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 Effort from './Effort'; | |||
import MeasuresButtonLink from './MeasuresButtonLink'; | |||
import HistoryButtonLink from './HistoryButtonLink'; | |||
import RatingFreshness from './RatingFreshness'; | |||
import MainRating from './MainRating'; | |||
import { translate } from '../../../helpers/l10n'; | |||
interface Props { | |||
component: string; | |||
measures: { [key: string]: string | undefined }; | |||
} | |||
export default function SecurityBox({ component, measures }: Props) { | |||
const rating = measures['security_rating']; | |||
const lastSecurityChange = measures['last_change_on_security_rating']; | |||
const rawEffort = measures['security_rating_effort']; | |||
const effort = rawEffort ? JSON.parse(rawEffort) : undefined; | |||
return ( | |||
<div className="portfolio-box portfolio-security"> | |||
<h2 className="portfolio-box-title"> | |||
{translate('metric_domain.Security')} | |||
<MeasuresButtonLink component={component} metric="Security" /> | |||
<HistoryButtonLink component={component} metric="security_rating" /> | |||
</h2> | |||
{rating && <MainRating component={component} metric="security_rating" value={rating} />} | |||
<RatingFreshness lastChange={lastSecurityChange} /> | |||
{effort && <Effort component={component} effort={effort} metricKey="security_rating" />} | |||
</div> | |||
); | |||
} |
@@ -0,0 +1,133 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact 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 { ReportStatus, subscribe, unsubscribe } from '../../../api/report'; | |||
import { translate, translateWithParameters } from '../../../helpers/l10n'; | |||
interface Props { | |||
component: string; | |||
currentUser: { email?: string }; | |||
status: ReportStatus; | |||
} | |||
interface State { | |||
loading: boolean; | |||
subscribed?: boolean; | |||
} | |||
export default class Subscription extends React.PureComponent<Props, State> { | |||
mounted: boolean; | |||
constructor(props: Props) { | |||
super(props); | |||
this.state = { subscribed: props.status.subscribed, loading: false }; | |||
} | |||
componentDidMount() { | |||
this.mounted = true; | |||
} | |||
componentWillReceiveProps(nextProps: Props) { | |||
if (nextProps.status.subscribed !== this.props.status.subscribed) { | |||
this.setState({ subscribed: nextProps.status.subscribed }); | |||
} | |||
} | |||
componentWillUnmount() { | |||
this.mounted = false; | |||
} | |||
stopLoading = () => { | |||
if (this.mounted) { | |||
this.setState({ loading: false }); | |||
} | |||
}; | |||
handleSubscription = (subscribed: boolean) => { | |||
if (this.mounted) { | |||
this.setState({ loading: false, subscribed }); | |||
} | |||
}; | |||
handleSubscribe = (e: React.SyntheticEvent<HTMLButtonElement>) => { | |||
e.preventDefault(); | |||
e.currentTarget.blur(); | |||
this.setState({ loading: true }); | |||
subscribe(this.props.component) | |||
.then(() => this.handleSubscription(true)) | |||
.catch(this.stopLoading); | |||
}; | |||
handleUnsubscribe = (e: React.SyntheticEvent<HTMLButtonElement>) => { | |||
e.preventDefault(); | |||
e.currentTarget.blur(); | |||
this.setState({ loading: true }); | |||
unsubscribe(this.props.component) | |||
.then(() => this.handleSubscription(false)) | |||
.catch(this.stopLoading); | |||
}; | |||
getEffectiveFrequencyText = () => { | |||
const effectiveFrequency = | |||
this.props.status.componentFrequency || this.props.status.globalFrequency; | |||
return translate('report.frequency', effectiveFrequency, 'effective'); | |||
}; | |||
renderLoading = () => this.state.loading && <i className="spacer-left spinner" />; | |||
renderWhenSubscribed = () => ( | |||
<div className="js-subscribed"> | |||
<div className="spacer-bottom"> | |||
<i className="icon-check pull-left spacer-right" /> | |||
<div className="overflow-hidden"> | |||
{translateWithParameters('report.subscribed', this.getEffectiveFrequencyText())} | |||
</div> | |||
</div> | |||
<button onClick={this.handleUnsubscribe}>{translate('report.unsubscribe')}</button> | |||
{this.renderLoading()} | |||
</div> | |||
); | |||
renderWhenNotSubscribed = () => ( | |||
<div className="js-not-subscribed"> | |||
<p className="spacer-bottom"> | |||
{translateWithParameters('report.unsubscribed', this.getEffectiveFrequencyText())} | |||
</p> | |||
<button className="js-report-subscribe" onClick={this.handleSubscribe}> | |||
{translate('report.subscribe')} | |||
</button> | |||
{this.renderLoading()} | |||
</div> | |||
); | |||
render() { | |||
const hasEmail = !!this.props.currentUser.email; | |||
const { subscribed } = this.state; | |||
let inner; | |||
if (hasEmail) { | |||
inner = subscribed ? this.renderWhenSubscribed() : this.renderWhenNotSubscribed(); | |||
} else { | |||
inner = <p className="note js-no-email">{translate('report.no_email_to_subscribe')}</p>; | |||
} | |||
return <div className="big-spacer-top js-report-subscription">{inner}</div>; | |||
} | |||
} |
@@ -0,0 +1,28 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact 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 { connect } from 'react-redux'; | |||
import Subscription from './Subscription'; | |||
import { getCurrentUser } from '../../../store/rootReducer'; | |||
const mapStateToProps = (state: any) => ({ | |||
currentUser: getCurrentUser(state) | |||
}); | |||
export default connect<any, any, any>(mapStateToProps)(Subscription); |
@@ -0,0 +1,69 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact 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 { Link } from 'react-router'; | |||
import LanguageDistributionContainer from '../../../components/charts/LanguageDistributionContainer'; | |||
import Measure from '../../../components/measure/Measure'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { getComponentDrilldownUrl } from '../../../helpers/urls'; | |||
interface Props { | |||
component: { description?: string; key: string }; | |||
measures: { [key: string]: string | undefined }; | |||
} | |||
export default function Summary({ component, measures }: Props) { | |||
const projects = measures['projects']; | |||
const ncloc = measures['ncloc']; | |||
const nclocDistribution = measures['ncloc_language_distribution']; | |||
return ( | |||
<section id="portfolio-summary" className="portfolio-section portfolio-section-summary"> | |||
{component.description && <div className="big-spacer-bottom">{component.description}</div>} | |||
<ul className="portfolio-grid"> | |||
<li> | |||
<div className="portfolio-measure-secondary-value"> | |||
<Link to={getComponentDrilldownUrl(component.key, 'projects')}> | |||
<Measure | |||
measure={{ metric: { key: 'projects', type: 'SHORT_INT' }, value: projects }} | |||
/> | |||
</Link> | |||
</div> | |||
{translate('projects')} | |||
</li> | |||
<li> | |||
<div className="portfolio-measure-secondary-value"> | |||
<Link to={getComponentDrilldownUrl(component.key, 'ncloc')}> | |||
<Measure measure={{ metric: { key: 'ncloc', type: 'SHORT_INT' }, value: ncloc }} /> | |||
</Link> | |||
</div> | |||
{translate('metric.ncloc.name')} | |||
</li> | |||
</ul> | |||
{nclocDistribution && ( | |||
<div className="huge-spacer-top" style={{ width: 260 }}> | |||
<LanguageDistributionContainer distribution={nclocDistribution} /> | |||
</div> | |||
)} | |||
</section> | |||
); | |||
} |
@@ -0,0 +1,140 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact 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 { Link } from 'react-router'; | |||
import { max } from 'lodash'; | |||
import { SubComponent } from '../types'; | |||
import Measure from '../../../components/measure/Measure'; | |||
import QualifierIcon from '../../../components/shared/QualifierIcon'; | |||
import { translate, translateWithParameters } from '../../../helpers/l10n'; | |||
import { formatMeasure } from '../../../helpers/measures'; | |||
import { getProjectUrl } from '../../../helpers/urls'; | |||
interface Props { | |||
component: string; | |||
subComponents: SubComponent[]; | |||
total: number; | |||
} | |||
export default function WorstProjects({ component, subComponents, total }: Props) { | |||
const count = subComponents.length; | |||
if (!count) { | |||
return null; | |||
} | |||
const maxLoc = max( | |||
subComponents.map(component => Number(component.measures['ncloc'] || 0)) | |||
) as number; | |||
const projectsPageUrl = { pathname: '/code', query: { id: component } }; | |||
return ( | |||
<div className="panel panel-white portfolio-sub-components" id="portfolio-sub-components"> | |||
<table className="data zebra"> | |||
<thead> | |||
<tr> | |||
<th> </th> | |||
<th className="text-center portfolio-sub-components-cell"> | |||
{translate('metric_domain.Releasability')} | |||
</th> | |||
<th className="text-center portfolio-sub-components-cell"> | |||
{translate('metric_domain.Reliability')} | |||
</th> | |||
<th className="text-center portfolio-sub-components-cell"> | |||
{translate('metric_domain.Security')} | |||
</th> | |||
<th className="text-center portfolio-sub-components-cell"> | |||
{translate('metric_domain.Maintainability')} | |||
</th> | |||
<th className="text-center portfolio-sub-components-cell"> | |||
{translate('metric.ncloc.name')} | |||
</th> | |||
</tr> | |||
</thead> | |||
<tbody> | |||
{subComponents.map(component => ( | |||
<tr key={component.key}> | |||
<td> | |||
<Link | |||
to={getProjectUrl(component.refKey || component.key)} | |||
className="link-with-icon"> | |||
<QualifierIcon qualifier={component.qualifier} /> {component.name} | |||
</Link> | |||
</td> | |||
{component.qualifier === 'TRK' ? ( | |||
renderCell(component.measures, 'alert_status', 'LEVEL') | |||
) : ( | |||
renderCell(component.measures, 'releasability_rating', 'RATING') | |||
)} | |||
{renderCell(component.measures, 'reliability_rating', 'RATING')} | |||
{renderCell(component.measures, 'security_rating', 'RATING')} | |||
{renderCell(component.measures, 'sqale_rating', 'RATING')} | |||
{renderNcloc(component.measures, maxLoc)} | |||
</tr> | |||
))} | |||
</tbody> | |||
</table> | |||
{total > count && ( | |||
<footer className="spacer-top note text-center"> | |||
{translateWithParameters( | |||
'x_of_y_shown', | |||
formatMeasure(count, 'INT'), | |||
formatMeasure(total, 'INT') | |||
)} | |||
<Link to={projectsPageUrl} className="spacer-left"> | |||
{translate('show_more')} | |||
</Link> | |||
</footer> | |||
)} | |||
</div> | |||
); | |||
} | |||
function renderCell(measures: { [key: string]: string | undefined }, metric: string, type: string) { | |||
return ( | |||
<td className="text-center"> | |||
<Measure measure={{ metric: { key: metric, type }, value: measures[metric] }} /> | |||
</td> | |||
); | |||
} | |||
function renderNcloc(measures: { [key: string]: string | undefined }, maxLoc: number) { | |||
const ncloc = Number(measures['ncloc'] || 0); | |||
const barWidth = maxLoc > 0 ? Math.max(1, Math.round(ncloc / maxLoc * 50)) : 0; | |||
return ( | |||
<td className="text-right"> | |||
<span className="note"> | |||
<Measure | |||
measure={{ | |||
metric: { key: 'ncloc', type: 'SHORT_INT' }, | |||
value: measures['ncloc'] | |||
}} | |||
/> | |||
</span> | |||
{maxLoc > 0 && ( | |||
<svg width="50" height="16" className="spacer-left"> | |||
<rect className="bar-chart-bar" x="0" y="3" width={barWidth} height="10" /> | |||
</svg> | |||
)} | |||
</td> | |||
); | |||
} |
@@ -0,0 +1,77 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact 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. | |||
*/ | |||
jest.mock('../../../../helpers/storage', () => ({ | |||
getCustomGraph: () => ['coverage'], | |||
getGraph: () => 'custom' | |||
})); | |||
jest.mock('../../../../api/metrics', () => ({ | |||
getMetrics: jest.fn(() => Promise.resolve([])) | |||
})); | |||
jest.mock('../../../../api/time-machine', () => ({ | |||
getAllTimeMachineData: jest.fn(() => | |||
Promise.resolve({ | |||
measures: [ | |||
{ | |||
metric: 'coverage', | |||
history: [ | |||
{ date: '2017-01-01T00:00:00.000Z', value: '73' }, | |||
{ date: '2017-01-02T00:00:00.000Z', value: '82' } | |||
] | |||
} | |||
] | |||
}) | |||
) | |||
})); | |||
import * as React from 'react'; | |||
import { mount, shallow } from 'enzyme'; | |||
import Activity from '../Activity'; | |||
const getMetrics = require('../../../../api/metrics').getMetrics as jest.Mock<any>; | |||
const getAllTimeMachineData = require('../../../../api/time-machine') | |||
.getAllTimeMachineData as jest.Mock<any>; | |||
beforeEach(() => { | |||
getMetrics.mockClear(); | |||
getAllTimeMachineData.mockClear(); | |||
}); | |||
it('renders', () => { | |||
const wrapper = shallow(<Activity component="foo" />); | |||
wrapper.setState({ | |||
history: { | |||
coverage: [ | |||
{ date: '2017-01-01T00:00:00.000Z', value: '73' }, | |||
{ date: '2017-01-02T00:00:00.000Z', value: '82' } | |||
] | |||
}, | |||
loading: false, | |||
metrics: [{ key: 'coverage' }] | |||
}); | |||
expect(wrapper).toMatchSnapshot(); | |||
}); | |||
it('fetches history', () => { | |||
mount(<Activity component="foo" />); | |||
expect(getMetrics).toBeCalled(); | |||
expect(getAllTimeMachineData).toBeCalledWith('foo', ['coverage']); | |||
}); |
@@ -0,0 +1,89 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact 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. | |||
*/ | |||
jest.mock('../../../../api/measures', () => ({ | |||
getMeasures: jest.fn(() => Promise.resolve([])) | |||
})); | |||
jest.mock('../../../../api/components', () => ({ | |||
getChildren: jest.fn(() => Promise.resolve({ components: [], paging: { total: 0 } })) | |||
})); | |||
// mock Activity to not deal with localstorage | |||
jest.mock('../Activity', () => ({ | |||
default: function Activity() { | |||
return null; | |||
} | |||
})); | |||
jest.mock('../Report', () => ({ | |||
default: function Report() { | |||
return null; | |||
} | |||
})); | |||
import * as React from 'react'; | |||
import { shallow, mount } from 'enzyme'; | |||
import App from '../App'; | |||
const getMeasures = require('../../../../api/measures').getMeasures as jest.Mock<any>; | |||
const getChildren = require('../../../../api/components').getChildren as jest.Mock<any>; | |||
const component = { key: 'foo', name: 'Foo' }; | |||
it('renders', () => { | |||
const wrapper = shallow(<App component={component} />); | |||
wrapper.setState({ loading: false, measures: {}, subComponents: [], totalSubComponents: 0 }); | |||
expect(wrapper).toMatchSnapshot(); | |||
}); | |||
it('fetches measures and children components', () => { | |||
getMeasures.mockClear(); | |||
getChildren.mockClear(); | |||
mount(<App component={component} />); | |||
expect(getMeasures).toBeCalledWith('foo', [ | |||
'projects', | |||
'ncloc', | |||
'ncloc_language_distribution', | |||
'releasability_rating', | |||
'releasability_effort', | |||
'sqale_rating', | |||
'maintainability_rating_effort', | |||
'reliability_rating', | |||
'reliability_rating_effort', | |||
'security_rating', | |||
'security_rating_effort', | |||
'last_change_on_releasability_rating', | |||
'last_change_on_maintainability_rating', | |||
'last_change_on_security_rating', | |||
'last_change_on_reliability_rating' | |||
]); | |||
expect(getChildren).toBeCalledWith( | |||
'foo', | |||
[ | |||
'ncloc', | |||
'releasability_rating', | |||
'security_rating', | |||
'reliability_rating', | |||
'sqale_rating', | |||
'alert_status' | |||
], | |||
{ ps: 20 } | |||
); | |||
}); |
@@ -0,0 +1,30 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact 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 { shallow } from 'enzyme'; | |||
import Effort from '../Effort'; | |||
it('renders', () => { | |||
expect( | |||
shallow( | |||
<Effort component="foo" effort={{ projects: 3, rating: 2 }} metricKey="security_rating" /> | |||
) | |||
).toMatchSnapshot(); | |||
}); |
@@ -0,0 +1,26 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact 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 { shallow } from 'enzyme'; | |||
import HistoryButtonLink from '../HistoryButtonLink'; | |||
it('renders', () => { | |||
expect(shallow(<HistoryButtonLink component="foo" metric="security_rating" />)).toMatchSnapshot(); | |||
}); |
@@ -0,0 +1,28 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact 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 { shallow } from 'enzyme'; | |||
import MainRating from '../MainRating'; | |||
it('renders', () => { | |||
expect( | |||
shallow(<MainRating component="foo" metric="security_rating" value="3" />) | |||
).toMatchSnapshot(); | |||
}); |
@@ -0,0 +1,31 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact 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 { shallow } from 'enzyme'; | |||
import MaintainabilityBox from '../MaintainabilityBox'; | |||
it('renders', () => { | |||
const measures = { | |||
sqale_rating: '3', | |||
last_change_on_maintainability_rating: '{"date":"2017-01-02T00:00:00.000Z","value":2}', | |||
maintainability_rating_effort: '{"rating":3,"projects":1}' | |||
}; | |||
expect(shallow(<MaintainabilityBox component="foo" measures={measures} />)).toMatchSnapshot(); | |||
}); |
@@ -0,0 +1,28 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact 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 { shallow } from 'enzyme'; | |||
import MeasuresButtonLink from '../MeasuresButtonLink'; | |||
it('renders', () => { | |||
expect( | |||
shallow(<MeasuresButtonLink component="foo" metric="security_rating" />) | |||
).toMatchSnapshot(); | |||
}); |
@@ -0,0 +1,31 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact 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 { shallow } from 'enzyme'; | |||
import RatingFreshness from '../RatingFreshness'; | |||
it('renders', () => { | |||
const lastChange = '{"date":"2017-01-02T00:00:00.000Z","value":2}'; | |||
expect(shallow(<RatingFreshness lastChange={lastChange} />)).toMatchSnapshot(); | |||
}); | |||
it('renders empty', () => { | |||
expect(shallow(<RatingFreshness />)).toMatchSnapshot(); | |||
}); |
@@ -0,0 +1,31 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact 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 { shallow } from 'enzyme'; | |||
import ReleasabilityBox from '../ReleasabilityBox'; | |||
it('renders', () => { | |||
const measures = { | |||
releasability_rating: '3', | |||
last_change_on_releasability_rating: '{"date":"2017-01-02T00:00:00.000Z","value":2}', | |||
releasability_effort: '7' | |||
}; | |||
expect(shallow(<ReleasabilityBox component="foo" measures={measures} />)).toMatchSnapshot(); | |||
}); |
@@ -0,0 +1,31 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact 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 { shallow } from 'enzyme'; | |||
import ReliabilityBox from '../ReliabilityBox'; | |||
it('renders', () => { | |||
const measures = { | |||
reliability_rating: '3', | |||
last_change_on_reliability_rating: '{"date":"2017-01-02T00:00:00.000Z","value":2}', | |||
reliability_rating_effort: '{"rating":3,"projects":1}' | |||
}; | |||
expect(shallow(<ReliabilityBox component="foo" measures={measures} />)).toMatchSnapshot(); | |||
}); |
@@ -0,0 +1,54 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact 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. | |||
*/ | |||
jest.mock('../../../../api/report', () => { | |||
const report = require.requireActual('../../../../api/report'); | |||
report.getReportStatus = jest.fn(() => Promise.resolve({})); | |||
return report; | |||
}); | |||
import * as React from 'react'; | |||
import { mount, shallow } from 'enzyme'; | |||
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'); | |||
}); |
@@ -0,0 +1,31 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact 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 { shallow } from 'enzyme'; | |||
import SecurityBox from '../SecurityBox'; | |||
it('renders', () => { | |||
const measures = { | |||
security_rating: '3', | |||
last_change_on_security_rating: '{"date":"2017-01-02T00:00:00.000Z","value":2}', | |||
security_rating_effort: '{"rating":3,"projects":1}' | |||
}; | |||
expect(shallow(<SecurityBox component="foo" measures={measures} />)).toMatchSnapshot(); | |||
}); |
@@ -0,0 +1,84 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact 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. | |||
*/ | |||
jest.mock('../../../../api/report', () => { | |||
const report = require.requireActual('../../../../api/report'); | |||
report.subscribe = jest.fn(() => Promise.resolve()); | |||
report.unsubscribe = jest.fn(() => Promise.resolve()); | |||
return report; | |||
}); | |||
import * as React from 'react'; | |||
import { mount, shallow } from 'enzyme'; | |||
import Subscription from '../Subscription'; | |||
import { click } from '../../../../helpers/testUtils'; | |||
const subscribe = require('../../../../api/report').subscribe as jest.Mock<any>; | |||
const unsubscribe = require('../../../../api/report').unsubscribe as jest.Mock<any>; | |||
const status = { | |||
canDownload: true, | |||
canSubscribe: true, | |||
componentFrequency: 'montly', | |||
globalFrequency: 'weekly', | |||
subscribed: true | |||
}; | |||
const currentUser = { email: 'foo@example.com' }; | |||
beforeEach(() => { | |||
subscribe.mockClear(); | |||
unsubscribe.mockClear(); | |||
}); | |||
it('renders when subscribed', () => { | |||
expect( | |||
shallow(<Subscription component="foo" currentUser={currentUser} status={status} />) | |||
).toMatchSnapshot(); | |||
}); | |||
it('renders when not subscribed', () => { | |||
expect( | |||
shallow( | |||
<Subscription | |||
component="foo" | |||
currentUser={currentUser} | |||
status={{ ...status, subscribed: false }} | |||
/> | |||
) | |||
).toMatchSnapshot(); | |||
}); | |||
it('renders when no email', () => { | |||
expect( | |||
shallow(<Subscription component="foo" currentUser={{}} status={status} />) | |||
).toMatchSnapshot(); | |||
}); | |||
it('changes subscription', async () => { | |||
const wrapper = mount(<Subscription component="foo" currentUser={currentUser} status={status} />); | |||
click(wrapper.find('button')); | |||
expect(unsubscribe).toBeCalledWith('foo'); | |||
await new Promise(setImmediate); | |||
wrapper.update(); | |||
click(wrapper.find('button')); | |||
expect(subscribe).toBeCalledWith('foo'); | |||
}); |
@@ -0,0 +1,33 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact 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 { shallow } from 'enzyme'; | |||
import Summary from '../Summary'; | |||
it('renders', () => { | |||
expect( | |||
shallow( | |||
<Summary | |||
component={{ description: 'blabla', key: 'foo' }} | |||
measures={{ ncloc: '1234', ncloc_language_distribution: 'java=13;js=17', projects: '15' }} | |||
/> | |||
) | |||
).toMatchSnapshot(); | |||
}); |
@@ -0,0 +1,68 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact 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 { shallow } from 'enzyme'; | |||
import WorstProjects from '../WorstProjects'; | |||
it('renders', () => { | |||
const subComponents = [ | |||
{ | |||
key: 'foo', | |||
measures: { | |||
releasability_rating: '3', | |||
reliability_rating: '2', | |||
security_rating: '1', | |||
sqale_rating: '4', | |||
ncloc: '200' | |||
}, | |||
name: 'Foo', | |||
qualifier: 'SVW' | |||
}, | |||
{ | |||
key: 'bar', | |||
measures: { | |||
alert_status: 'ERROR', | |||
reliability_rating: '2', | |||
security_rating: '1', | |||
sqale_rating: '4', | |||
ncloc: '100' | |||
}, | |||
name: 'Bar', | |||
qualifier: 'TRK', | |||
refKey: 'barbar' | |||
}, | |||
{ | |||
key: 'baz', | |||
measures: { | |||
alert_status: 'WARN', | |||
reliability_rating: '2', | |||
security_rating: '1', | |||
sqale_rating: '4', | |||
ncloc: '150' | |||
}, | |||
name: 'Baz', | |||
qualifier: 'TRK', | |||
refKey: 'bazbaz' | |||
} | |||
]; | |||
expect( | |||
shallow(<WorstProjects component="comp" subComponents={subComponents} total={3} />) | |||
).toMatchSnapshot(); | |||
}); |
@@ -0,0 +1,42 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`renders 1`] = ` | |||
<div | |||
className="huge-spacer-top" | |||
> | |||
<header | |||
className="page-header" | |||
> | |||
<h3 | |||
className="page-title" | |||
> | |||
project_activity.page | |||
</h3> | |||
</header> | |||
<PreviewGraph | |||
history={ | |||
Object { | |||
"coverage": Array [ | |||
Object { | |||
"date": "2017-01-01T00:00:00.000Z", | |||
"value": "73", | |||
}, | |||
Object { | |||
"date": "2017-01-02T00:00:00.000Z", | |||
"value": "82", | |||
}, | |||
], | |||
} | |||
} | |||
metrics={ | |||
Array [ | |||
Object { | |||
"key": "coverage", | |||
}, | |||
] | |||
} | |||
project="foo" | |||
renderWhenEmpty={[Function]} | |||
/> | |||
</div> | |||
`; |
@@ -0,0 +1,65 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`renders 1`] = ` | |||
<div | |||
className="page page-limited" | |||
> | |||
<div | |||
className="page-with-sidebar" | |||
> | |||
<div | |||
className="page-main" | |||
> | |||
<div | |||
className="portfolio-boxes" | |||
> | |||
<ReleasabilityBox | |||
component="foo" | |||
measures={Object {}} | |||
/> | |||
<ReliabilityBox | |||
component="foo" | |||
measures={Object {}} | |||
/> | |||
<SecurityBox | |||
component="foo" | |||
measures={Object {}} | |||
/> | |||
<MaintainabilityBox | |||
component="foo" | |||
measures={Object {}} | |||
/> | |||
</div> | |||
<WorstProjects | |||
component="foo" | |||
subComponents={Array []} | |||
total={0} | |||
/> | |||
</div> | |||
<aside | |||
className="page-sidebar-fixed" | |||
> | |||
<Summary | |||
component={ | |||
Object { | |||
"key": "foo", | |||
"name": "Foo", | |||
} | |||
} | |||
measures={Object {}} | |||
/> | |||
<Activity | |||
component="foo" | |||
/> | |||
<Report | |||
component={ | |||
Object { | |||
"key": "foo", | |||
"name": "Foo", | |||
} | |||
} | |||
/> | |||
</aside> | |||
</div> | |||
</div> | |||
`; |
@@ -0,0 +1,50 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`renders 1`] = ` | |||
<div | |||
className="portfolio-effort" | |||
> | |||
<FormattedMessage | |||
defaultMessage="portfolio.x_in_y" | |||
id="portfolio.x_in_y" | |||
values={ | |||
Object { | |||
"projects": <Link | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/component_measures", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
"metric": "security_rating", | |||
}, | |||
} | |||
} | |||
> | |||
<span> | |||
<Measure | |||
measure={ | |||
Object { | |||
"metric": Object { | |||
"key": "projects", | |||
"type": "SHORT_INT", | |||
}, | |||
"value": "3", | |||
} | |||
} | |||
/> | |||
projects_ | |||
</span> | |||
</Link>, | |||
"rating": <Rating | |||
small={true} | |||
value={2} | |||
/>, | |||
} | |||
} | |||
/> | |||
</div> | |||
`; |
@@ -0,0 +1,24 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`renders 1`] = ` | |||
<Link | |||
className="button button-small button-compact spacer-left text-text-bottom" | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/project/activity", | |||
"query": Object { | |||
"branch": undefined, | |||
"custom_metrics": "security_rating", | |||
"graph": "custom", | |||
"id": "foo", | |||
}, | |||
} | |||
} | |||
> | |||
<IconHistory | |||
size={14} | |||
/> | |||
</Link> | |||
`; |
@@ -0,0 +1,24 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`renders 1`] = ` | |||
<Link | |||
className="portfolio-box-rating" | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/component_measures", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
"metric": "security_rating", | |||
"view": "treemap", | |||
}, | |||
} | |||
} | |||
> | |||
<Rating | |||
value="3" | |||
/> | |||
</Link> | |||
`; |
@@ -0,0 +1,39 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`renders 1`] = ` | |||
<div | |||
className="portfolio-box portfolio-maintainability" | |||
> | |||
<h2 | |||
className="portfolio-box-title" | |||
> | |||
metric_domain.Maintainability | |||
<MeasuresButtonLink | |||
component="foo" | |||
metric="Maintainability" | |||
/> | |||
<HistoryButtonLink | |||
component="foo" | |||
metric="sqale_rating" | |||
/> | |||
</h2> | |||
<MainRating | |||
component="foo" | |||
metric="sqale_rating" | |||
value="3" | |||
/> | |||
<RatingFreshness | |||
lastChange="{\\"date\\":\\"2017-01-02T00:00:00.000Z\\",\\"value\\":2}" | |||
/> | |||
<Effort | |||
component="foo" | |||
effort={ | |||
Object { | |||
"projects": 1, | |||
"rating": 3, | |||
} | |||
} | |||
metricKey="sqale_rating" | |||
/> | |||
</div> | |||
`; |
@@ -0,0 +1,23 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`renders 1`] = ` | |||
<Link | |||
className="button button-small button-compact spacer-left text-text-bottom" | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/component_measures", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
"metric": "security_rating", | |||
}, | |||
} | |||
} | |||
> | |||
<BubblesIcon | |||
size={14} | |||
/> | |||
</Link> | |||
`; |
@@ -0,0 +1,31 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`renders 1`] = ` | |||
<div | |||
className="portfolio-freshness" | |||
> | |||
<FormattedMessage | |||
defaultMessage="portfolio.was_x_y" | |||
id="portfolio.was_x_y" | |||
values={ | |||
Object { | |||
"date": <DateFromNow | |||
date="2017-01-02T00:00:00.000Z" | |||
/>, | |||
"rating": <Rating | |||
small={true} | |||
value={2} | |||
/>, | |||
} | |||
} | |||
/> | |||
</div> | |||
`; | |||
exports[`renders empty 1`] = ` | |||
<div | |||
className="portfolio-freshness" | |||
> | |||
</div> | |||
`; |
@@ -0,0 +1,75 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`renders 1`] = ` | |||
<div | |||
className="portfolio-box portfolio-releasability" | |||
> | |||
<h2 | |||
className="portfolio-box-title" | |||
> | |||
metric_domain.Releasability | |||
</h2> | |||
<Link | |||
className="portfolio-box-rating" | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/component_measures", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
"metric": "alert_status", | |||
}, | |||
} | |||
} | |||
> | |||
<Rating | |||
value="3" | |||
/> | |||
</Link> | |||
<RatingFreshness | |||
lastChange="{\\"date\\":\\"2017-01-02T00:00:00.000Z\\",\\"value\\":2}" | |||
/> | |||
<div | |||
className="portfolio-effort" | |||
> | |||
<Link | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/component_measures", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
"metric": "alert_status", | |||
}, | |||
} | |||
} | |||
> | |||
<span> | |||
<Measure | |||
measure={ | |||
Object { | |||
"metric": Object { | |||
"key": "projects", | |||
"type": "SHORT_INT", | |||
}, | |||
"value": "7", | |||
} | |||
} | |||
/> | |||
projects | |||
</span> | |||
</Link> | |||
<span | |||
className="level level-ERROR level-small" | |||
> | |||
metric.level.ERROR | |||
</span> | |||
</div> | |||
</div> | |||
`; |
@@ -0,0 +1,39 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`renders 1`] = ` | |||
<div | |||
className="portfolio-box portfolio-reliability" | |||
> | |||
<h2 | |||
className="portfolio-box-title" | |||
> | |||
metric_domain.Reliability | |||
<MeasuresButtonLink | |||
component="foo" | |||
metric="Reliability" | |||
/> | |||
<HistoryButtonLink | |||
component="foo" | |||
metric="reliability_rating" | |||
/> | |||
</h2> | |||
<MainRating | |||
component="foo" | |||
metric="reliability_rating" | |||
value="3" | |||
/> | |||
<RatingFreshness | |||
lastChange="{\\"date\\":\\"2017-01-02T00:00:00.000Z\\",\\"value\\":2}" | |||
/> | |||
<Effort | |||
component="foo" | |||
effort={ | |||
Object { | |||
"projects": 1, | |||
"rating": 3, | |||
} | |||
} | |||
metricKey="reliability_rating" | |||
/> | |||
</div> | |||
`; |
@@ -0,0 +1,65 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`renders 1`] = ` | |||
<div | |||
className="huge-spacer-top" | |||
> | |||
<header | |||
className="page-header" | |||
> | |||
<h3 | |||
className="page-title" | |||
> | |||
report.page | |||
</h3> | |||
</header> | |||
<i | |||
className="spinner" | |||
/> | |||
</div> | |||
`; | |||
exports[`renders 2`] = ` | |||
<div | |||
className="huge-spacer-top" | |||
> | |||
<header | |||
className="page-header" | |||
> | |||
<h3 | |||
className="page-title" | |||
> | |||
report.page | |||
</h3> | |||
</header> | |||
<div | |||
className="js-report-can-download" | |||
> | |||
report.can_download | |||
<div | |||
className="spacer-top" | |||
> | |||
<a | |||
className="button js-report-download" | |||
download="Foo - Executive Report.pdf" | |||
href="/api/governance_reports/download?componentKey=foo" | |||
target="_blank" | |||
> | |||
report.print | |||
</a> | |||
</div> | |||
</div> | |||
<Connect(Subscription) | |||
component="foo" | |||
status={ | |||
Object { | |||
"canDownload": true, | |||
"canSubscribe": true, | |||
"componentFrequency": "montly", | |||
"globalFrequency": "weekly", | |||
"subscribed": true, | |||
} | |||
} | |||
/> | |||
</div> | |||
`; |
@@ -0,0 +1,39 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`renders 1`] = ` | |||
<div | |||
className="portfolio-box portfolio-security" | |||
> | |||
<h2 | |||
className="portfolio-box-title" | |||
> | |||
metric_domain.Security | |||
<MeasuresButtonLink | |||
component="foo" | |||
metric="Security" | |||
/> | |||
<HistoryButtonLink | |||
component="foo" | |||
metric="security_rating" | |||
/> | |||
</h2> | |||
<MainRating | |||
component="foo" | |||
metric="security_rating" | |||
value="3" | |||
/> | |||
<RatingFreshness | |||
lastChange="{\\"date\\":\\"2017-01-02T00:00:00.000Z\\",\\"value\\":2}" | |||
/> | |||
<Effort | |||
component="foo" | |||
effort={ | |||
Object { | |||
"projects": 1, | |||
"rating": 3, | |||
} | |||
} | |||
metricKey="security_rating" | |||
/> | |||
</div> | |||
`; |
@@ -0,0 +1,63 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`renders when no email 1`] = ` | |||
<div | |||
className="big-spacer-top js-report-subscription" | |||
> | |||
<p | |||
className="note js-no-email" | |||
> | |||
report.no_email_to_subscribe | |||
</p> | |||
</div> | |||
`; | |||
exports[`renders when not subscribed 1`] = ` | |||
<div | |||
className="big-spacer-top js-report-subscription" | |||
> | |||
<div | |||
className="js-not-subscribed" | |||
> | |||
<p | |||
className="spacer-bottom" | |||
> | |||
report.unsubscribed.report.frequency.montly.effective | |||
</p> | |||
<button | |||
className="js-report-subscribe" | |||
onClick={[Function]} | |||
> | |||
report.subscribe | |||
</button> | |||
</div> | |||
</div> | |||
`; | |||
exports[`renders when subscribed 1`] = ` | |||
<div | |||
className="big-spacer-top js-report-subscription" | |||
> | |||
<div | |||
className="js-subscribed" | |||
> | |||
<div | |||
className="spacer-bottom" | |||
> | |||
<i | |||
className="icon-check pull-left spacer-right" | |||
/> | |||
<div | |||
className="overflow-hidden" | |||
> | |||
report.subscribed.report.frequency.montly.effective | |||
</div> | |||
</div> | |||
<button | |||
onClick={[Function]} | |||
> | |||
report.unsubscribe | |||
</button> | |||
</div> | |||
</div> | |||
`; |
@@ -0,0 +1,96 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`renders 1`] = ` | |||
<section | |||
className="portfolio-section portfolio-section-summary" | |||
id="portfolio-summary" | |||
> | |||
<div | |||
className="big-spacer-bottom" | |||
> | |||
blabla | |||
</div> | |||
<ul | |||
className="portfolio-grid" | |||
> | |||
<li> | |||
<div | |||
className="portfolio-measure-secondary-value" | |||
> | |||
<Link | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/component_measures", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
"metric": "projects", | |||
}, | |||
} | |||
} | |||
> | |||
<Measure | |||
measure={ | |||
Object { | |||
"metric": Object { | |||
"key": "projects", | |||
"type": "SHORT_INT", | |||
}, | |||
"value": "15", | |||
} | |||
} | |||
/> | |||
</Link> | |||
</div> | |||
projects | |||
</li> | |||
<li> | |||
<div | |||
className="portfolio-measure-secondary-value" | |||
> | |||
<Link | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/component_measures", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
"metric": "ncloc", | |||
}, | |||
} | |||
} | |||
> | |||
<Measure | |||
measure={ | |||
Object { | |||
"metric": Object { | |||
"key": "ncloc", | |||
"type": "SHORT_INT", | |||
}, | |||
"value": "1234", | |||
} | |||
} | |||
/> | |||
</Link> | |||
</div> | |||
metric.ncloc.name | |||
</li> | |||
</ul> | |||
<div | |||
className="huge-spacer-top" | |||
style={ | |||
Object { | |||
"width": 260, | |||
} | |||
} | |||
> | |||
<Connect(LanguageDistribution) | |||
distribution="java=13;js=17" | |||
/> | |||
</div> | |||
</section> | |||
`; |
@@ -0,0 +1,395 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`renders 1`] = ` | |||
<div | |||
className="panel panel-white portfolio-sub-components" | |||
id="portfolio-sub-components" | |||
> | |||
<table | |||
className="data zebra" | |||
> | |||
<thead> | |||
<tr> | |||
<th> | |||
</th> | |||
<th | |||
className="text-center portfolio-sub-components-cell" | |||
> | |||
metric_domain.Releasability | |||
</th> | |||
<th | |||
className="text-center portfolio-sub-components-cell" | |||
> | |||
metric_domain.Reliability | |||
</th> | |||
<th | |||
className="text-center portfolio-sub-components-cell" | |||
> | |||
metric_domain.Security | |||
</th> | |||
<th | |||
className="text-center portfolio-sub-components-cell" | |||
> | |||
metric_domain.Maintainability | |||
</th> | |||
<th | |||
className="text-center portfolio-sub-components-cell" | |||
> | |||
metric.ncloc.name | |||
</th> | |||
</tr> | |||
</thead> | |||
<tbody> | |||
<tr> | |||
<td> | |||
<Link | |||
className="link-with-icon" | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
} | |||
> | |||
<QualifierIcon | |||
qualifier="SVW" | |||
/> | |||
Foo | |||
</Link> | |||
</td> | |||
<td | |||
className="text-center" | |||
> | |||
<Measure | |||
measure={ | |||
Object { | |||
"metric": Object { | |||
"key": "releasability_rating", | |||
"type": "RATING", | |||
}, | |||
"value": "3", | |||
} | |||
} | |||
/> | |||
</td> | |||
<td | |||
className="text-center" | |||
> | |||
<Measure | |||
measure={ | |||
Object { | |||
"metric": Object { | |||
"key": "reliability_rating", | |||
"type": "RATING", | |||
}, | |||
"value": "2", | |||
} | |||
} | |||
/> | |||
</td> | |||
<td | |||
className="text-center" | |||
> | |||
<Measure | |||
measure={ | |||
Object { | |||
"metric": Object { | |||
"key": "security_rating", | |||
"type": "RATING", | |||
}, | |||
"value": "1", | |||
} | |||
} | |||
/> | |||
</td> | |||
<td | |||
className="text-center" | |||
> | |||
<Measure | |||
measure={ | |||
Object { | |||
"metric": Object { | |||
"key": "sqale_rating", | |||
"type": "RATING", | |||
}, | |||
"value": "4", | |||
} | |||
} | |||
/> | |||
</td> | |||
<td | |||
className="text-right" | |||
> | |||
<span | |||
className="note" | |||
> | |||
<Measure | |||
measure={ | |||
Object { | |||
"metric": Object { | |||
"key": "ncloc", | |||
"type": "SHORT_INT", | |||
}, | |||
"value": "200", | |||
} | |||
} | |||
/> | |||
</span> | |||
<svg | |||
className="spacer-left" | |||
height="16" | |||
width="50" | |||
> | |||
<rect | |||
className="bar-chart-bar" | |||
height="10" | |||
width={50} | |||
x="0" | |||
y="3" | |||
/> | |||
</svg> | |||
</td> | |||
</tr> | |||
<tr> | |||
<td> | |||
<Link | |||
className="link-with-icon" | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "barbar", | |||
}, | |||
} | |||
} | |||
> | |||
<QualifierIcon | |||
qualifier="TRK" | |||
/> | |||
Bar | |||
</Link> | |||
</td> | |||
<td | |||
className="text-center" | |||
> | |||
<Measure | |||
measure={ | |||
Object { | |||
"metric": Object { | |||
"key": "alert_status", | |||
"type": "LEVEL", | |||
}, | |||
"value": "ERROR", | |||
} | |||
} | |||
/> | |||
</td> | |||
<td | |||
className="text-center" | |||
> | |||
<Measure | |||
measure={ | |||
Object { | |||
"metric": Object { | |||
"key": "reliability_rating", | |||
"type": "RATING", | |||
}, | |||
"value": "2", | |||
} | |||
} | |||
/> | |||
</td> | |||
<td | |||
className="text-center" | |||
> | |||
<Measure | |||
measure={ | |||
Object { | |||
"metric": Object { | |||
"key": "security_rating", | |||
"type": "RATING", | |||
}, | |||
"value": "1", | |||
} | |||
} | |||
/> | |||
</td> | |||
<td | |||
className="text-center" | |||
> | |||
<Measure | |||
measure={ | |||
Object { | |||
"metric": Object { | |||
"key": "sqale_rating", | |||
"type": "RATING", | |||
}, | |||
"value": "4", | |||
} | |||
} | |||
/> | |||
</td> | |||
<td | |||
className="text-right" | |||
> | |||
<span | |||
className="note" | |||
> | |||
<Measure | |||
measure={ | |||
Object { | |||
"metric": Object { | |||
"key": "ncloc", | |||
"type": "SHORT_INT", | |||
}, | |||
"value": "100", | |||
} | |||
} | |||
/> | |||
</span> | |||
<svg | |||
className="spacer-left" | |||
height="16" | |||
width="50" | |||
> | |||
<rect | |||
className="bar-chart-bar" | |||
height="10" | |||
width={25} | |||
x="0" | |||
y="3" | |||
/> | |||
</svg> | |||
</td> | |||
</tr> | |||
<tr> | |||
<td> | |||
<Link | |||
className="link-with-icon" | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "bazbaz", | |||
}, | |||
} | |||
} | |||
> | |||
<QualifierIcon | |||
qualifier="TRK" | |||
/> | |||
Baz | |||
</Link> | |||
</td> | |||
<td | |||
className="text-center" | |||
> | |||
<Measure | |||
measure={ | |||
Object { | |||
"metric": Object { | |||
"key": "alert_status", | |||
"type": "LEVEL", | |||
}, | |||
"value": "WARN", | |||
} | |||
} | |||
/> | |||
</td> | |||
<td | |||
className="text-center" | |||
> | |||
<Measure | |||
measure={ | |||
Object { | |||
"metric": Object { | |||
"key": "reliability_rating", | |||
"type": "RATING", | |||
}, | |||
"value": "2", | |||
} | |||
} | |||
/> | |||
</td> | |||
<td | |||
className="text-center" | |||
> | |||
<Measure | |||
measure={ | |||
Object { | |||
"metric": Object { | |||
"key": "security_rating", | |||
"type": "RATING", | |||
}, | |||
"value": "1", | |||
} | |||
} | |||
/> | |||
</td> | |||
<td | |||
className="text-center" | |||
> | |||
<Measure | |||
measure={ | |||
Object { | |||
"metric": Object { | |||
"key": "sqale_rating", | |||
"type": "RATING", | |||
}, | |||
"value": "4", | |||
} | |||
} | |||
/> | |||
</td> | |||
<td | |||
className="text-right" | |||
> | |||
<span | |||
className="note" | |||
> | |||
<Measure | |||
measure={ | |||
Object { | |||
"metric": Object { | |||
"key": "ncloc", | |||
"type": "SHORT_INT", | |||
}, | |||
"value": "150", | |||
} | |||
} | |||
/> | |||
</span> | |||
<svg | |||
className="spacer-left" | |||
height="16" | |||
width="50" | |||
> | |||
<rect | |||
className="bar-chart-bar" | |||
height="10" | |||
width={38} | |||
x="0" | |||
y="3" | |||
/> | |||
</svg> | |||
</td> | |||
</tr> | |||
</tbody> | |||
</table> | |||
</div> | |||
`; |
@@ -0,0 +1,30 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact 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 { RouterState, IndexRouteProps } from 'react-router'; | |||
const routes = [ | |||
{ | |||
getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) { | |||
import('./components/App').then(i => callback(null, { component: (i as any).default })); | |||
} | |||
} | |||
]; | |||
export default routes; |
@@ -0,0 +1,95 @@ | |||
.portfolio-measure-secondary-value { | |||
line-height: 1.4; | |||
margin-bottom: 4px; | |||
font-size: 24px; | |||
font-weight: 300; | |||
} | |||
.portfolio-grid { | |||
position: relative; | |||
z-index: 10; | |||
display: flex; | |||
height: 80px; | |||
justify-content: space-around; | |||
align-items: center; | |||
} | |||
.portfolio-grid > li { | |||
vertical-align: top; | |||
width: 50%; | |||
text-align: center; | |||
} | |||
.portfolio-grid > li.text-middle { | |||
vertical-align: middle; | |||
} | |||
.portfolio-freshness { | |||
line-height: 24px; | |||
margin-top: 12px; | |||
color: #777; | |||
font-size: 12px; | |||
white-space: nowrap; | |||
} | |||
.portfolio-effort { | |||
margin-top: 12px; | |||
padding-top: 12px; | |||
border-top: 1px solid #e6e6e6; | |||
} | |||
.portfolio-boxes { | |||
display: flex; | |||
justify-content: space-between; | |||
align-items: stretch; | |||
margin-bottom: 20px; | |||
padding: 15px 0; | |||
border: 1px solid #e6e6e6; | |||
background-color: #fff; | |||
} | |||
.portfolio-box { | |||
position: relative; | |||
width: 25%; | |||
padding: 0 5px; | |||
border-radius: 3px; | |||
box-sizing: border-box; | |||
text-align: center; | |||
} | |||
.portfolio-box-title { | |||
margin-bottom: 25px; | |||
font-size: 16px; | |||
} | |||
.portfolio-box-title > .button-small > svg { | |||
margin-top: 0; | |||
} | |||
.portfolio-box-rating, | |||
.portfolio-box-rating .rating { | |||
display: block; | |||
width: 120px; | |||
height: 120px; | |||
line-height: 120px; | |||
} | |||
.portfolio-box-rating { | |||
margin: 0 auto; | |||
border: none; | |||
} | |||
.portfolio-box-rating .rating { | |||
border-radius: 120px; | |||
font-size: 60px; | |||
text-align: center; | |||
} | |||
.portfolio-sub-components table.data > thead > tr > th { | |||
font-size: 13px; | |||
text-transform: none; | |||
} | |||
.portfolio-sub-components-cell { | |||
width: 90px; | |||
} |
@@ -0,0 +1,26 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact 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 SubComponent { | |||
key: string; | |||
measures: { [key: string]: string | undefined }; | |||
name: string; | |||
refKey?: string; | |||
qualifier: string; | |||
} |
@@ -0,0 +1,92 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact 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 function getNextRating(rating: number): number | undefined { | |||
return rating > 1 ? rating - 1 : undefined; | |||
} | |||
function getWorstSeverity(data: string): { severity: string; count: number } | undefined { | |||
const SEVERITY_ORDER = ['BLOCKER', 'CRITICAL', 'MAJOR', 'MINOR', 'INFO']; | |||
const severities: { [key: string]: number } = {}; | |||
data.split(';').forEach(equality => { | |||
const [key, count] = equality.split('='); | |||
severities[key] = Number(count); | |||
}); | |||
for (let i = 0; i < SEVERITY_ORDER.length; i++) { | |||
const count = severities[SEVERITY_ORDER[i]]; | |||
if (count > 0) { | |||
return { severity: SEVERITY_ORDER[i], count }; | |||
} | |||
} | |||
return undefined; | |||
} | |||
export function getEffortToNextRating( | |||
measures: Array<{ metric: { key: string }; value: string }>, | |||
metricKey: string | |||
) { | |||
const measure = measures.find(measure => measure.metric.key === metricKey); | |||
if (!measure) { | |||
return undefined; | |||
} | |||
return getWorstSeverity(measure.value); | |||
} | |||
export const PORTFOLIO_METRICS = [ | |||
'projects', | |||
'ncloc', | |||
'ncloc_language_distribution', | |||
'releasability_rating', | |||
'releasability_effort', | |||
'sqale_rating', | |||
'maintainability_rating_effort', | |||
'reliability_rating', | |||
'reliability_rating_effort', | |||
'security_rating', | |||
'security_rating_effort', | |||
'last_change_on_releasability_rating', | |||
'last_change_on_maintainability_rating', | |||
'last_change_on_security_rating', | |||
'last_change_on_reliability_rating' | |||
]; | |||
export const SUB_COMPONENTS_METRICS = [ | |||
'ncloc', | |||
'releasability_rating', | |||
'security_rating', | |||
'reliability_rating', | |||
'sqale_rating', | |||
'alert_status' | |||
]; | |||
export function convertMeasures(measures: Array<{ metric: string; value?: string }>) { | |||
const result: { [key: string]: string | undefined } = {}; | |||
measures.forEach(measure => { | |||
result[measure.metric] = measure.value; | |||
}); | |||
return result; | |||
} |
@@ -221,8 +221,8 @@ describe('parseQuery', () => { | |||
expect( | |||
utils.parseQuery({ | |||
from: '2017-04-27T08:21:32.000Z', | |||
id: 'foo', | |||
custom_metrics: 'foo,bar,baz' | |||
custom_metrics: 'foo,bar,baz', | |||
id: 'foo' | |||
}) | |||
).toEqual(QUERY); | |||
}); |
@@ -18,7 +18,7 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import React from 'react'; | |||
import { AutoSizer } from 'react-virtualized'; | |||
import AutoSizer from 'react-virtualized/dist/commonjs/AutoSizer'; | |||
import AdvancedTimeline from '../../../components/charts/AdvancedTimeline'; | |||
import GraphsTooltips from './GraphsTooltips'; | |||
import GraphsLegendCustom from './GraphsLegendCustom'; |
@@ -35,7 +35,7 @@ type Props = { | |||
graphs: Array<Array<Serie>>, | |||
graphEndDate: ?Date, | |||
graphStartDate: ?Date, | |||
leakPeriodDate: Date, | |||
leakPeriodDate?: Date, | |||
loading: boolean, | |||
measuresHistory: Array<MeasureHistory>, | |||
removeCustomMetric: (metric: string) => void, |
@@ -19,7 +19,7 @@ | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import { AutoSizer } from 'react-virtualized'; | |||
import AutoSizer from 'react-virtualized/dist/commonjs/AutoSizer'; | |||
import ZoomTimeLine from '../../../components/charts/ZoomTimeLine'; | |||
import { hasHistoryData } from '../utils'; | |||
/*:: import type { Serie } from '../../../components/charts/AdvancedTimeline'; */ | |||
@@ -28,7 +28,7 @@ import { hasHistoryData } from '../utils'; | |||
type Props = { | |||
graphEndDate: ?Date, | |||
graphStartDate: ?Date, | |||
leakPeriodDate: Date, | |||
leakPeriodDate?: Date, | |||
loading: boolean, | |||
metricsType: string, | |||
series: Array<Serie>, |
@@ -170,12 +170,13 @@ export default class ProjectActivityAnalysesList extends React.PureComponent { | |||
const selectedDate = this.props.query.selectedDate | |||
? this.props.query.selectedDate.valueOf() | |||
: null; | |||
return ( | |||
<ul | |||
className={classNames('project-activity-versions-list', this.props.className)} | |||
onScroll={this.handleScroll} | |||
ref={element => (this.scrollContainer = element)} | |||
style={{ paddingTop: this.props.project.qualifier === 'APP' ? undefined : 52 }}> | |||
style={{ paddingTop: this.props.project.qualifier === 'TRK' ? 52 : undefined }}> | |||
{byVersionByDay.map((version, idx) => { | |||
const days = Object.keys(version.byDay); | |||
if (days.length <= 0) { | |||
@@ -205,7 +206,7 @@ export default class ProjectActivityAnalysesList extends React.PureComponent { | |||
addVersion={this.props.addVersion} | |||
analysis={analysis} | |||
canAdmin={this.props.canAdmin} | |||
canCreateVersion={this.props.project.qualifier !== 'APP'} | |||
canCreateVersion={this.props.project.qualifier === 'TRK'} | |||
changeEvent={this.props.changeEvent} | |||
deleteAnalysis={this.props.deleteAnalysis} | |||
deleteEvent={this.props.deleteEvent} |
@@ -42,7 +42,7 @@ type Props = { | |||
project: { | |||
configuration?: { showHistory: boolean }, | |||
key: string, | |||
leakPeriodDate: string, | |||
leakPeriodDate?: string, | |||
qualifier: string | |||
}, | |||
metrics: Array<Metric>, | |||
@@ -55,7 +55,9 @@ type Props = { | |||
export default function ProjectActivityApp(props /*: Props */) { | |||
const { analyses, measuresHistory, query } = props; | |||
const { configuration } = props.project; | |||
const canAdmin = configuration ? configuration.showHistory : false; | |||
const canAdmin = | |||
(props.project.qualifier === 'TRK' || props.project.qualifier === 'APP') && | |||
(configuration ? configuration.showHistory : false); | |||
return ( | |||
<div id="project-activity" className="page page-limited"> | |||
<Helmet title={translate('project_activity.page')} /> | |||
@@ -89,7 +91,9 @@ export default function ProjectActivityApp(props /*: Props */) { | |||
<div className="project-activity-layout-page-main"> | |||
<ProjectActivityGraphs | |||
analyses={analyses} | |||
leakPeriodDate={parseDate(props.project.leakPeriodDate)} | |||
leakPeriodDate={ | |||
props.project.leakPeriodDate ? parseDate(props.project.leakPeriodDate) : undefined | |||
} | |||
loading={props.graphLoading} | |||
measuresHistory={measuresHistory} | |||
metrics={props.metrics} |
@@ -42,15 +42,18 @@ import { | |||
/*:: import type { Analysis, MeasureHistory, Metric, Paging, Query } from '../types'; */ | |||
/*:: | |||
type Component = { | |||
breadcrumbs: Array<{ key: string, qualifier: string}>, | |||
configuration?: { showHistory: boolean }, | |||
key: string, | |||
leakPeriodDate?: string, | |||
qualifier: string | |||
}; | |||
type Props = { | |||
branch?: {}, | |||
location: { pathname: string, query: RawQuery }, | |||
component: { | |||
configuration?: { showHistory: boolean }, | |||
key: string, | |||
leakPeriodDate: string, | |||
qualifier: string | |||
} | |||
component: Component | |||
}; | |||
*/ | |||
@@ -106,7 +109,7 @@ export default class ProjectActivityAppContainer extends React.PureComponent { | |||
} | |||
}); | |||
} else { | |||
this.firstLoadData(this.state.query); | |||
this.firstLoadData(this.state.query, this.props.component); | |||
} | |||
} | |||
@@ -117,7 +120,7 @@ export default class ProjectActivityAppContainer extends React.PureComponent { | |||
if (this.state.initialized) { | |||
this.updateGraphData(query.graph, query.customMetrics); | |||
} else { | |||
this.firstLoadData(query); | |||
this.firstLoadData(query, nextProps.component); | |||
} | |||
} | |||
this.setState({ query }); | |||
@@ -177,7 +180,7 @@ export default class ProjectActivityAppContainer extends React.PureComponent { | |||
branch: this.props.branch && getBranchName(this.props.branch) | |||
}; | |||
return api | |||
.getProjectActivity({ ...parameters, ...additional }) | |||
.getProjectActivity({ ...additional, ...parameters }) | |||
.then(({ analyses, paging }) => ({ | |||
analyses: analyses.map(analysis => ({ ...analysis, date: parseDate(analysis.date) })), | |||
paging | |||
@@ -227,10 +230,22 @@ export default class ProjectActivityAppContainer extends React.PureComponent { | |||
}); | |||
}; | |||
firstLoadData(query /*: Query */) { | |||
getTopLevelComponent = (component /*: Component */) => { | |||
let current = component.breadcrumbs.length - 1; | |||
while ( | |||
current > 0 && | |||
!['TRK', 'VW', 'APP'].includes(component.breadcrumbs[current].qualifier) | |||
) { | |||
current--; | |||
} | |||
return component.breadcrumbs[current].key; | |||
}; | |||
firstLoadData(query /*: Query */, component /*: Component */) { | |||
const graphMetrics = getHistoryMetrics(query.graph, query.customMetrics); | |||
const topLevelComponent = this.getTopLevelComponent(component); | |||
Promise.all([ | |||
this.fetchActivity(query.project, 1, 100, serializeQuery(query)), | |||
this.fetchActivity(topLevelComponent, 1, 100, serializeQuery(query)), | |||
this.fetchMetrics(), | |||
this.fetchMeasuresHistory(graphMetrics) | |||
]).then( | |||
@@ -246,7 +261,7 @@ export default class ProjectActivityAppContainer extends React.PureComponent { | |||
paging: response[0].paging | |||
}); | |||
this.loadAllActivities(query.project).then(({ analyses, paging }) => { | |||
this.loadAllActivities(topLevelComponent).then(({ analyses, paging }) => { | |||
if (this.mounted) { | |||
this.setState({ | |||
analyses, |
@@ -40,7 +40,7 @@ import { | |||
/*:: | |||
type Props = { | |||
analyses: Array<Analysis>, | |||
leakPeriodDate: Date, | |||
leakPeriodDate?: Date, | |||
loading: boolean, | |||
measuresHistory: Array<MeasureHistory>, | |||
metrics: Array<Metric>, |
@@ -54,17 +54,19 @@ export default class ProjectActivityPageHeader extends React.PureComponent { | |||
return ( | |||
<header className="page-header"> | |||
<Select | |||
className="input-medium pull-left big-spacer-right" | |||
placeholder={translate('project_activity.filter_events') + '...'} | |||
clearable={true} | |||
searchable={false} | |||
value={this.props.category} | |||
optionComponent={ProjectActivityEventSelectOption} | |||
valueComponent={ProjectActivityEventSelectValue} | |||
options={this.options} | |||
onChange={this.handleCategoryChange} | |||
/> | |||
{!['VW', 'SVW'].includes(this.props.project.qualifier) && ( | |||
<Select | |||
className="input-medium pull-left big-spacer-right" | |||
placeholder={translate('project_activity.filter_events') + '...'} | |||
clearable={true} | |||
searchable={false} | |||
value={this.props.category} | |||
optionComponent={ProjectActivityEventSelectOption} | |||
valueComponent={ProjectActivityEventSelectValue} | |||
options={this.options} | |||
onChange={this.handleCategoryChange} | |||
/> | |||
)} | |||
<ProjectActivityDateInput | |||
className="pull-left" | |||
from={this.props.from} |
@@ -38,7 +38,7 @@ export default function ProjectCardLeakMeasures({ measures }: Props) { | |||
<Measure | |||
className="spacer-right" | |||
measure={{ | |||
metric: { key: 'new_bugs', name: 'new_bugs', type: 'SHORT_INT' }, | |||
metric: { key: 'new_bugs', type: 'SHORT_INT' }, | |||
leak: measures['new_bugs'] | |||
}} | |||
/> | |||
@@ -57,11 +57,7 @@ export default function ProjectCardLeakMeasures({ measures }: Props) { | |||
<Measure | |||
className="spacer-right" | |||
measure={{ | |||
metric: { | |||
key: 'new_vulnerabilities', | |||
name: 'new_vulnerabilities', | |||
type: 'SHORT_INT' | |||
}, | |||
metric: { key: 'new_vulnerabilities', type: 'SHORT_INT' }, | |||
leak: measures['new_vulnerabilities'] | |||
}} | |||
/> | |||
@@ -80,7 +76,7 @@ export default function ProjectCardLeakMeasures({ measures }: Props) { | |||
<Measure | |||
className="spacer-right" | |||
measure={{ | |||
metric: { key: 'new_code_smells', name: 'new_code_smells', type: 'SHORT_INT' }, | |||
metric: { key: 'new_code_smells', type: 'SHORT_INT' }, | |||
leak: measures['new_code_smells'] | |||
}} | |||
/> | |||
@@ -98,7 +94,7 @@ export default function ProjectCardLeakMeasures({ measures }: Props) { | |||
<div className="project-card-measure-number"> | |||
<Measure | |||
measure={{ | |||
metric: { key: 'new_coverage', name: 'new_coverage', type: 'PERCENT' }, | |||
metric: { key: 'new_coverage', type: 'PERCENT' }, | |||
leak: measures['new_coverage'] | |||
}} | |||
/> | |||
@@ -112,11 +108,7 @@ export default function ProjectCardLeakMeasures({ measures }: Props) { | |||
<div className="project-card-measure-number"> | |||
<Measure | |||
measure={{ | |||
metric: { | |||
key: 'new_duplicated_lines_density', | |||
name: 'new_duplicated_lines_density', | |||
type: 'PERCENT' | |||
}, | |||
metric: { key: 'new_duplicated_lines_density', type: 'PERCENT' }, | |||
leak: measures['new_duplicated_lines_density'] | |||
}} | |||
/> | |||
@@ -132,7 +124,7 @@ export default function ProjectCardLeakMeasures({ measures }: Props) { | |||
<div className="project-card-measure-number"> | |||
<Measure | |||
measure={{ | |||
metric: { key: 'new_lines', name: 'new_lines', type: 'SHORT_INT' }, | |||
metric: { key: 'new_lines', type: 'SHORT_INT' }, | |||
leak: measures['new_lines'] | |||
}} | |||
/> |
@@ -76,7 +76,7 @@ export default function ProjectCardOverallMeasures({ measures }: Props) { | |||
)} | |||
<Measure | |||
measure={{ | |||
metric: { key: 'coverage', name: 'coverage', type: 'PERCENT' }, | |||
metric: { key: 'coverage', type: 'PERCENT' }, | |||
value: measures['coverage'] | |||
}} | |||
/> | |||
@@ -95,11 +95,7 @@ export default function ProjectCardOverallMeasures({ measures }: Props) { | |||
)} | |||
<Measure | |||
measure={{ | |||
metric: { | |||
key: 'duplicated_lines_density', | |||
name: 'duplicated_lines_density', | |||
type: 'PERCENT' | |||
}, | |||
metric: { key: 'duplicated_lines_density', type: 'PERCENT' }, | |||
value: measures['duplicated_lines_density'] | |||
}} | |||
/> | |||
@@ -119,7 +115,7 @@ export default function ProjectCardOverallMeasures({ measures }: Props) { | |||
</span> | |||
<Measure | |||
measure={{ | |||
metric: { key: 'ncloc', name: 'ncloc', type: 'SHORT_INT' }, | |||
metric: { key: 'ncloc', type: 'SHORT_INT' }, | |||
value: measures['ncloc'] | |||
}} | |||
/> |
@@ -21,7 +21,6 @@ exports[`should render correctly with all data 1`] = ` | |||
"leak": "8", | |||
"metric": Object { | |||
"key": "new_bugs", | |||
"name": "new_bugs", | |||
"type": "SHORT_INT", | |||
}, | |||
} | |||
@@ -58,7 +57,6 @@ exports[`should render correctly with all data 1`] = ` | |||
"leak": "2", | |||
"metric": Object { | |||
"key": "new_vulnerabilities", | |||
"name": "new_vulnerabilities", | |||
"type": "SHORT_INT", | |||
}, | |||
} | |||
@@ -95,7 +93,6 @@ exports[`should render correctly with all data 1`] = ` | |||
"leak": "0", | |||
"metric": Object { | |||
"key": "new_code_smells", | |||
"name": "new_code_smells", | |||
"type": "SHORT_INT", | |||
}, | |||
} | |||
@@ -131,7 +128,6 @@ exports[`should render correctly with all data 1`] = ` | |||
"leak": "26.55", | |||
"metric": Object { | |||
"key": "new_coverage", | |||
"name": "new_coverage", | |||
"type": "PERCENT", | |||
}, | |||
} | |||
@@ -161,7 +157,6 @@ exports[`should render correctly with all data 1`] = ` | |||
"leak": "0.55", | |||
"metric": Object { | |||
"key": "new_duplicated_lines_density", | |||
"name": "new_duplicated_lines_density", | |||
"type": "PERCENT", | |||
}, | |||
} | |||
@@ -191,7 +186,6 @@ exports[`should render correctly with all data 1`] = ` | |||
"leak": "87", | |||
"metric": Object { | |||
"key": "new_lines", | |||
"name": "new_lines", | |||
"type": "SHORT_INT", | |||
}, | |||
} | |||
@@ -229,7 +223,6 @@ exports[`should render no data style new coverage, new duplications and new line | |||
"leak": "8", | |||
"metric": Object { | |||
"key": "new_bugs", | |||
"name": "new_bugs", | |||
"type": "SHORT_INT", | |||
}, | |||
} | |||
@@ -266,7 +259,6 @@ exports[`should render no data style new coverage, new duplications and new line | |||
"leak": "2", | |||
"metric": Object { | |||
"key": "new_vulnerabilities", | |||
"name": "new_vulnerabilities", | |||
"type": "SHORT_INT", | |||
}, | |||
} | |||
@@ -303,7 +295,6 @@ exports[`should render no data style new coverage, new duplications and new line | |||
"leak": "0", | |||
"metric": Object { | |||
"key": "new_code_smells", | |||
"name": "new_code_smells", | |||
"type": "SHORT_INT", | |||
}, | |||
} | |||
@@ -339,7 +330,6 @@ exports[`should render no data style new coverage, new duplications and new line | |||
"leak": undefined, | |||
"metric": Object { | |||
"key": "new_coverage", | |||
"name": "new_coverage", | |||
"type": "PERCENT", | |||
}, | |||
} | |||
@@ -369,7 +359,6 @@ exports[`should render no data style new coverage, new duplications and new line | |||
"leak": undefined, | |||
"metric": Object { | |||
"key": "new_duplicated_lines_density", | |||
"name": "new_duplicated_lines_density", | |||
"type": "PERCENT", | |||
}, | |||
} | |||
@@ -399,7 +388,6 @@ exports[`should render no data style new coverage, new duplications and new line | |||
"leak": undefined, | |||
"metric": Object { | |||
"key": "new_lines", | |||
"name": "new_lines", | |||
"type": "SHORT_INT", | |||
}, | |||
} |
@@ -16,7 +16,6 @@ exports[`should not render coverage 1`] = ` | |||
Object { | |||
"metric": Object { | |||
"key": "coverage", | |||
"name": "coverage", | |||
"type": "PERCENT", | |||
}, | |||
"value": undefined, | |||
@@ -49,7 +48,6 @@ exports[`should not render duplications 1`] = ` | |||
Object { | |||
"metric": Object { | |||
"key": "duplicated_lines_density", | |||
"name": "duplicated_lines_density", | |||
"type": "PERCENT", | |||
}, | |||
"value": undefined, | |||
@@ -155,7 +153,6 @@ exports[`should render correctly with all data 1`] = ` | |||
Object { | |||
"metric": Object { | |||
"key": "coverage", | |||
"name": "coverage", | |||
"type": "PERCENT", | |||
}, | |||
"value": "88.3", | |||
@@ -192,7 +189,6 @@ exports[`should render correctly with all data 1`] = ` | |||
Object { | |||
"metric": Object { | |||
"key": "duplicated_lines_density", | |||
"name": "duplicated_lines_density", | |||
"type": "PERCENT", | |||
}, | |||
"value": "9.8", | |||
@@ -229,7 +225,6 @@ exports[`should render correctly with all data 1`] = ` | |||
Object { | |||
"metric": Object { | |||
"key": "ncloc", | |||
"name": "ncloc", | |||
"type": "SHORT_INT", | |||
}, | |||
"value": "2053", | |||
@@ -270,7 +265,6 @@ exports[`should render ncloc correctly 1`] = ` | |||
Object { | |||
"metric": Object { | |||
"key": "ncloc", | |||
"name": "ncloc", | |||
"type": "SHORT_INT", | |||
}, | |||
"value": "16549887", |
@@ -152,22 +152,21 @@ export default ModalView.extend({ | |||
.filter(metric => metric.type !== 'DATA' && !metric.hidden) | |||
.map(metric => metric.key); | |||
return getMeasures( | |||
this.options.component.key, | |||
metricsToRequest, | |||
this.options.branch | |||
).then(measures => { | |||
let nextMeasures = this.options.component.measures || {}; | |||
measures.forEach(measure => { | |||
const metric = metrics.find(metric => metric.key === measure.metric); | |||
nextMeasures[metric.key] = formatMeasure(measure.value, metric.type); | |||
nextMeasures[metric.key + '_raw'] = measure.value; | |||
metric.value = nextMeasures[metric.key]; | |||
}); | |||
nextMeasures = this.calcAdditionalMeasures(nextMeasures); | |||
this.measures = nextMeasures; | |||
this.measuresToDisplay = this.prepareMetrics(metrics); | |||
}); | |||
return getMeasures(this.options.component.key, metricsToRequest, this.options.branch).then( | |||
measures => { | |||
let nextMeasures = this.options.component.measures || {}; | |||
measures.forEach(measure => { | |||
const metric = metrics.find(metric => metric.key === measure.metric); | |||
nextMeasures[metric.key] = formatMeasure(measure.value, metric.type); | |||
nextMeasures[metric.key + '_raw'] = measure.value; | |||
metric.value = nextMeasures[metric.key]; | |||
}); | |||
nextMeasures = this.calcAdditionalMeasures(nextMeasures); | |||
this.measures = nextMeasures; | |||
this.measuresToDisplay = this.prepareMetrics(metrics); | |||
}, | |||
() => {} | |||
); | |||
}); | |||
}, | |||
@@ -1,89 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2017 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 { find, sortBy } from 'lodash'; | |||
import PropTypes from 'prop-types'; | |||
import React from 'react'; | |||
import { Histogram } from './histogram'; | |||
import { formatMeasure } from '../../helpers/measures'; | |||
import { getLanguages } from '../../api/languages'; | |||
import { translate } from '../../helpers/l10n'; | |||
export default class LanguageDistribution extends React.PureComponent { | |||
static propTypes = { | |||
alignTicks: PropTypes.bool, | |||
distribution: PropTypes.string.isRequired | |||
}; | |||
state = {}; | |||
componentDidMount() { | |||
this.mounted = true; | |||
this.requestLanguages(); | |||
} | |||
componentWillUnmount() { | |||
this.mounted = false; | |||
} | |||
requestLanguages() { | |||
getLanguages().then(languages => { | |||
if (this.mounted) { | |||
this.setState({ languages }); | |||
} | |||
}); | |||
} | |||
getLanguageName(langKey) { | |||
if (this.state.languages) { | |||
const lang = find(this.state.languages, { key: langKey }); | |||
return lang ? lang.name : translate('unknown'); | |||
} else { | |||
return langKey; | |||
} | |||
} | |||
cutLanguageName(name) { | |||
return name.length > 10 ? `${name.substr(0, 7)}...` : name; | |||
} | |||
render() { | |||
let data = this.props.distribution.split(';').map((point, index) => { | |||
const tokens = point.split('='); | |||
return { x: parseInt(tokens[1], 10), y: index, value: tokens[0] }; | |||
}); | |||
data = sortBy(data, d => -d.x); | |||
const yTicks = data.map(point => this.getLanguageName(point.value)).map(this.cutLanguageName); | |||
const yValues = data.map(point => formatMeasure(point.x, 'SHORT_INT')); | |||
return ( | |||
<Histogram | |||
alignTicks={this.props.alignTicks} | |||
data={data} | |||
yTicks={yTicks} | |||
yValues={yValues} | |||
barsWidth={10} | |||
height={data.length * 25} | |||
padding={[0, 60, 0, 80]} | |||
/> | |||
); | |||
} | |||
} |
@@ -0,0 +1,64 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2017 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* 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. | |||
* | |||
* 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 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 { find, sortBy } from 'lodash'; | |||
import { Histogram } from './histogram'; | |||
import { formatMeasure } from '../../helpers/measures'; | |||
import { Language } from '../../api/languages'; | |||
import { translate } from '../../helpers/l10n'; | |||
interface Props { | |||
alignTicks?: boolean; | |||
distribution: string; | |||
languages?: Language[]; | |||
} | |||
export default function LanguageDistribution(props: Props) { | |||
let data = props.distribution.split(';').map((point, index) => { | |||
const tokens = point.split('='); | |||
return { x: parseInt(tokens[1], 10), y: index, value: tokens[0] }; | |||
}); | |||
data = sortBy(data, d => -d.x); | |||
const yTicks = data.map(point => getLanguageName(point.value)).map(cutLanguageName); | |||
const yValues = data.map(point => formatMeasure(point.x, 'SHORT_INT')); | |||
return ( | |||
<Histogram | |||
alignTicks={props.alignTicks} | |||
data={data} | |||
yTicks={yTicks} | |||
yValues={yValues} | |||
barsWidth={10} | |||
height={data.length * 25} | |||
padding={[0, 60, 0, 80]} | |||
/> | |||
); | |||
function getLanguageName(langKey: string) { | |||
const lang = find(props.languages, { key: langKey }); | |||
return lang ? lang.name : translate('unknown'); | |||
} | |||
function cutLanguageName(name: string) { | |||
return name.length > 10 ? `${name.substr(0, 7)}...` : name; | |||
} | |||
} |
@@ -17,20 +17,12 @@ | |||
* 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 ProjectPageExtension from './ProjectPageExtension'; | |||
import { Component } from '../../types'; | |||
import { connect } from 'react-redux'; | |||
import { getLanguages } from '../../store/rootReducer'; | |||
import LanguageDistribution from './LanguageDistribution'; | |||
interface Props { | |||
component: Component; | |||
location: { query: { id: string } }; | |||
} | |||
const mapStateToProps = (state: any) => ({ | |||
languages: getLanguages(state) | |||
}); | |||
export default function PortfolioDashboard(props: Props) { | |||
return ( | |||
<ProjectPageExtension | |||
{...props} | |||
params={{ pluginKey: 'governance', extensionKey: 'governance' }} | |||
/> | |||
); | |||
} | |||
export default connect<any, any, any>(mapStateToProps)(LanguageDistribution); |
@@ -38,7 +38,7 @@ type Props = { | |||
endDate: ?Date, | |||
height: number, | |||
width: number, | |||
leakPeriodDate: Date, | |||
leakPeriodDate?: Date, | |||
padding: Array<number>, | |||
series: Array<Serie>, | |||
showAreas?: boolean, | |||
@@ -394,6 +394,7 @@ export default class ZoomTimeLine extends React.PureComponent { | |||
} | |||
const { xScale, yScale } = this.getScales(); | |||
return ( | |||
<svg className="line-chart " width={this.props.width} height={this.props.height}> | |||
<g transform={`translate(${this.props.padding[3]}, ${this.props.padding[0] + 2})`}> |
@@ -17,14 +17,14 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import * as React from 'react'; | |||
/*:: | |||
type Props = { className?: string, size?: number }; | |||
*/ | |||
interface Props { | |||
className?: string; | |||
size?: number; | |||
} | |||
export default function BubblesIcon({ className, size = 16 } /*: Props */) { | |||
export default function BubblesIcon({ className, size = 16 }: Props) { | |||
return ( | |||
<svg | |||
xmlns="http://www.w3.org/2000/svg" |
@@ -17,15 +17,14 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import * as React from 'react'; | |||
/*:: | |||
type Props = { className?: string, size?: number }; | |||
*/ | |||
interface Props { | |||
className?: string; | |||
size?: number; | |||
} | |||
export default function IconHistory({ className, size = 16 } /*: Props */) { | |||
/* eslint max-len: 0 */ | |||
export default function IconHistory({ className, size = 16 }: Props) { | |||
return ( | |||
<svg | |||
className={className} |
@@ -25,7 +25,6 @@ interface Props { | |||
} | |||
export default function LinkIcon({ className, size = 14 }: Props) { | |||
/* eslint-disable max-len */ | |||
return ( | |||
<svg | |||
xmlns="http://www.w3.org/2000/svg" |
@@ -27,10 +27,14 @@ import { formatLeak, getRatingTooltip, MeasureEnhanced } from './utils'; | |||
interface Props { | |||
className?: string; | |||
decimals?: number | null; | |||
measure: MeasureEnhanced; | |||
measure?: MeasureEnhanced; | |||
} | |||
export default function Measure({ className, decimals, measure }: Props) { | |||
if (measure == undefined) { | |||
return <span>{'–'}</span>; | |||
} | |||
const metric = measure.metric; | |||
const value = isDiffMetric(metric.key) ? measure.leak : measure.value; | |||
@@ -44,7 +48,7 @@ export default function Measure({ className, decimals, measure }: Props) { | |||
if (metric.type !== 'RATING') { | |||
const formattedValue = isDiffMetric(metric.key) | |||
? formatLeak(measure.leak, metric, { decimals }) | |||
? formatLeak(measure.leak, metric.key, metric.type, { decimals }) | |||
: formatMeasure(measure.value, metric.type, { decimals }); | |||
return <span className={className}>{formattedValue != null ? formattedValue : '–'}</span>; | |||
} |
@@ -37,7 +37,7 @@ export interface Measure extends MeasureIntern { | |||
} | |||
export interface MeasureEnhanced extends MeasureIntern { | |||
metric: Metric; | |||
metric: { key: string; type: string }; | |||
leak?: string | undefined | undefined; | |||
} | |||
@@ -53,11 +53,16 @@ export function enhanceMeasure( | |||
}; | |||
} | |||
export function formatLeak(value: string | undefined, metric: Metric, options: any): string { | |||
if (isDiffMetric(metric.key)) { | |||
return formatMeasure(value, metric.type, options); | |||
export function formatLeak( | |||
value: string | undefined, | |||
metricKey: string, | |||
metricType: string, | |||
options: any | |||
): string { | |||
if (isDiffMetric(metricKey)) { | |||
return formatMeasure(value, metricType, options); | |||
} else { | |||
return formatMeasureVariation(value, metric.type, options); | |||
return formatMeasureVariation(value, metricType, options); | |||
} | |||
} | |||
@@ -20,8 +20,9 @@ | |||
// @flow | |||
import React from 'react'; | |||
import { minBy } from 'lodash'; | |||
import { AutoSizer } from 'react-virtualized'; | |||
import AdvancedTimeline from '../../../components/charts/AdvancedTimeline'; | |||
import * as PropTypes from 'prop-types'; | |||
import AutoSizer from 'react-virtualized/dist/commonjs/AutoSizer'; | |||
import AdvancedTimeline from '../charts/AdvancedTimeline'; | |||
import PreviewGraphTooltips from './PreviewGraphTooltips'; | |||
import { | |||
DEFAULT_GRAPH, | |||
@@ -30,11 +31,11 @@ import { | |||
getSeriesMetricType, | |||
hasHistoryDataValue, | |||
splitSeriesInGraphs | |||
} from '../../projectActivity/utils'; | |||
import { getCustomGraph, getGraph } from '../../../helpers/storage'; | |||
import { formatMeasure, getShortType } from '../../../helpers/measures'; | |||
/*:: import type { Serie } from '../../../components/charts/AdvancedTimeline'; */ | |||
/*:: import type { History, Metric } from '../types'; */ | |||
} from '../../apps/projectActivity/utils'; | |||
import { getCustomGraph, getGraph } from '../../helpers/storage'; | |||
import { formatMeasure, getShortType } from '../../helpers/measures'; | |||
/*:: import type { Serie } from '../charts/AdvancedTimeline'; */ | |||
/*:: import type { History, Metric } from '../../apps/overview/types'; */ | |||
/*:: | |||
type Props = { | |||
@@ -42,7 +43,7 @@ type Props = { | |||
history: ?History, | |||
metrics: Array<Metric>, | |||
project: string, | |||
router: { push: ({ pathname: string, query?: {} }) => void } | |||
renderWhenEmpty?: () => void | |||
}; | |||
*/ | |||
@@ -65,6 +66,10 @@ export default class PreviewGraph extends React.PureComponent { | |||
/*:: props: Props; */ | |||
/*:: state: State; */ | |||
static contextTypes = { | |||
router: PropTypes.object | |||
}; | |||
constructor(props /*: Props */) { | |||
super(props); | |||
const graph = getGraph(); | |||
@@ -137,7 +142,7 @@ export default class PreviewGraph extends React.PureComponent { | |||
}; | |||
handleClick = () => { | |||
this.props.router.push({ | |||
this.context.router.push({ | |||
pathname: '/project/activity', | |||
query: { id: this.props.project, branch: this.props.branch } | |||
}); | |||
@@ -192,7 +197,7 @@ export default class PreviewGraph extends React.PureComponent { | |||
render() { | |||
const { series } = this.state; | |||
if (!hasHistoryDataValue(series)) { | |||
return null; | |||
return this.props.renderWhenEmpty ? this.props.renderWhenEmpty() : null; | |||
} | |||
return ( |
@@ -18,11 +18,11 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import React from 'react'; | |||
import BubblePopup from '../../../components/common/BubblePopup'; | |||
import DateFormatter from '../../../components/intl/DateFormatter'; | |||
import BubblePopup from '../common/BubblePopup'; | |||
import DateFormatter from '../intl/DateFormatter'; | |||
import PreviewGraphTooltipsContent from './PreviewGraphTooltipsContent'; | |||
/*:: import type { Metric } from '../types'; */ | |||
/*:: import type { Serie } from '../../../components/charts/AdvancedTimeline'; */ | |||
/*:: import type { Serie } from '../charts/AdvancedTimeline'; */ | |||
/*:: | |||
type Props = { |
@@ -19,7 +19,7 @@ | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import ChartLegendIcon from '../../../components/icons-components/ChartLegendIcon'; | |||
import ChartLegendIcon from '../icons-components/ChartLegendIcon'; | |||
/*:: | |||
type Props = { |
@@ -20,8 +20,8 @@ | |||
import React from 'react'; | |||
import { shallow } from 'enzyme'; | |||
import PreviewGraphTooltips from '../PreviewGraphTooltips'; | |||
import { DEFAULT_GRAPH } from '../../../projectActivity/utils'; | |||
import { parseDate } from '../../../../helpers/dates'; | |||
import { DEFAULT_GRAPH } from '../../../apps/projectActivity/utils'; | |||
import { parseDate } from '../../../helpers/dates'; | |||
const SERIES_ISSUES = [ | |||
{ |
@@ -17,7 +17,7 @@ | |||
* 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, ShallowRendererProps, ShallowWrapper } from 'enzyme'; | |||
import { shallow, ShallowRendererProps, ShallowWrapper, ReactWrapper } from 'enzyme'; | |||
import { IntlProvider } from 'react-intl'; | |||
export const mockEvent = { | |||
@@ -27,7 +27,7 @@ export const mockEvent = { | |||
stopPropagation() {} | |||
}; | |||
export function click(element: ShallowWrapper, event = {}): void { | |||
export function click(element: ShallowWrapper | ReactWrapper, event = {}): void { | |||
element.simulate('click', { ...mockEvent, ...event }); | |||
} | |||