/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2019 SonarSource SA | |||||
* mailto:info AT sonarsource DOT com | |||||
* | |||||
* This program is free software; you can redistribute it and/or | |||||
* modify it under the terms of the GNU Lesser General Public | |||||
* License as published by the Free Software Foundation; either | |||||
* version 3 of the License, or (at your option) any later version. | |||||
* | |||||
* This program is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||||
* Lesser General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU Lesser General Public License | |||||
* along with this program; if not, write to the Free Software Foundation, | |||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||||
*/ | |||||
import * as React from 'react'; | |||||
import { | |||||
getDisplayedHistoryMetrics, | |||||
DEFAULT_GRAPH, | |||||
getProjectActivityGraph | |||||
} from '../../projectActivity/utils'; | |||||
import PreviewGraph from '../../../components/preview-graph/PreviewGraph'; | |||||
import { getAllTimeMachineData } from '../../../api/time-machine'; | |||||
import { parseDate } from '../../../helpers/dates'; | |||||
import { translate } from '../../../helpers/l10n'; | |||||
interface Props { | |||||
component: string; | |||||
metrics: T.Dict<T.Metric>; | |||||
} | |||||
interface State { | |||||
history?: { | |||||
[metric: string]: Array<{ date: Date; value?: string }>; | |||||
}; | |||||
loading: boolean; | |||||
} | |||||
export default class Activity extends React.PureComponent<Props> { | |||||
mounted = false; | |||||
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; | |||||
const { graph, customGraphs } = getProjectActivityGraph(component); | |||||
let graphMetrics = getDisplayedHistoryMetrics(graph, customGraphs); | |||||
if (!graphMetrics || graphMetrics.length <= 0) { | |||||
graphMetrics = getDisplayedHistoryMetrics(DEFAULT_GRAPH, []); | |||||
} | |||||
this.setState({ loading: true }); | |||||
return getAllTimeMachineData({ component, metrics: graphMetrics.join() }).then( | |||||
timeMachine => { | |||||
if (this.mounted) { | |||||
const history: T.Dict<Array<{ date: Date; value?: string }>> = {}; | |||||
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 }); | |||||
} | |||||
}, | |||||
() => { | |||||
if (this.mounted) { | |||||
this.setState({ loading: false }); | |||||
} | |||||
} | |||||
); | |||||
}; | |||||
renderWhenEmpty = () => <div className="note">{translate('component_measures.no_history')}</div>; | |||||
render() { | |||||
return ( | |||||
<div className="big-spacer-bottom"> | |||||
<h4>{translate('project_activity.page')}</h4> | |||||
{this.state.loading ? ( | |||||
<i className="spinner" /> | |||||
) : ( | |||||
this.state.history !== undefined && ( | |||||
<PreviewGraph | |||||
history={this.state.history} | |||||
metrics={this.props.metrics} | |||||
project={this.props.component} | |||||
renderWhenEmpty={this.renderWhenEmpty} | |||||
/> | |||||
) | |||||
)} | |||||
</div> | |||||
); | |||||
} | |||||
} |
*/ | */ | ||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import { connect } from 'react-redux'; | import { connect } from 'react-redux'; | ||||
import Summary from './Summary'; | |||||
import { Link } from 'react-router'; | |||||
import MeasuresButtonLink from './MeasuresButtonLink'; | |||||
import MetricBox from './MetricBox'; | |||||
import Report from './Report'; | import Report from './Report'; | ||||
import WorstProjects from './WorstProjects'; | 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 { SubComponent } from '../types'; | import { SubComponent } from '../types'; | ||||
import { PORTFOLIO_METRICS, SUB_COMPONENTS_METRICS, convertMeasures } from '../utils'; | import { PORTFOLIO_METRICS, SUB_COMPONENTS_METRICS, convertMeasures } from '../utils'; | ||||
import { getMeasures } from '../../../api/measures'; | |||||
import Measure from '../../../components/measure/Measure'; | |||||
import { getChildren } from '../../../api/components'; | import { getChildren } from '../../../api/components'; | ||||
import { getMeasures } from '../../../api/measures'; | |||||
import { translate } from '../../../helpers/l10n'; | import { translate } from '../../../helpers/l10n'; | ||||
import { getComponentDrilldownUrl } from '../../../helpers/urls'; | |||||
import { fetchMetrics } from '../../../store/rootActions'; | import { fetchMetrics } from '../../../store/rootActions'; | ||||
import { getMetrics, Store } from '../../../store/rootReducer'; | import { getMetrics, Store } from '../../../store/rootReducer'; | ||||
import '../styles.css'; | import '../styles.css'; | ||||
import PrivacyBadgeContainer from '../../../components/common/PrivacyBadgeContainer'; | |||||
interface OwnProps { | interface OwnProps { | ||||
component: T.Component; | component: T.Component; | ||||
); | ); | ||||
} | } | ||||
renderMain() { | |||||
render() { | |||||
const { component } = this.props; | const { component } = this.props; | ||||
const { measures, subComponents, totalSubComponents } = this.state; | |||||
const { loading, measures, subComponents, totalSubComponents } = this.state; | |||||
if (loading) { | |||||
return this.renderSpinner(); | |||||
} | |||||
if (this.isEmpty()) { | if (this.isEmpty()) { | ||||
return this.renderEmpty(); | return this.renderEmpty(); | ||||
} | } | ||||
return ( | return ( | ||||
<div> | |||||
<div className="page page-limited portfolio-overview"> | |||||
<div className="page-actions"> | |||||
<Report component={component} /> | |||||
</div> | |||||
<h1>{translate('portfolio.health_factors')}</h1> | |||||
<div className="portfolio-boxes"> | <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!} /> | |||||
<MetricBox component={component.key} measures={measures!} metricKey="releasability" /> | |||||
<MetricBox component={component.key} measures={measures!} metricKey="reliability" /> | |||||
<MetricBox component={component.key} measures={measures!} metricKey="vulnerabilities" /> | |||||
<MetricBox component={component.key} measures={measures!} metricKey="security_hotspots" /> | |||||
<MetricBox component={component.key} measures={measures!} metricKey="maintainability" /> | |||||
</div> | |||||
<h1>{translate('portfolio.breakdown')}</h1> | |||||
<div className="portfolio-breakdown"> | |||||
<div className="portfolio-breakdown-box"> | |||||
<h2>{translate('portfolio.number_of_projects')}</h2> | |||||
<div className="portfolio-breakdown-metric"> | |||||
<Measure | |||||
metricKey="projects" | |||||
metricType="SHORT_INT" | |||||
value={(measures && measures.projects) || '0'} | |||||
/> | |||||
</div> | |||||
<div className="portfolio-breakdown-box-link"> | |||||
<div> | |||||
<MeasuresButtonLink component={component.key} metric="projects" /> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
<div className="portfolio-breakdown-box"> | |||||
<h2>{translate('portfolio.number_of_lines')}</h2> | |||||
<div className="portfolio-breakdown-metric"> | |||||
<Measure | |||||
metricKey="ncloc" | |||||
metricType="SHORT_INT" | |||||
value={(measures && measures.ncloc) || '0'} | |||||
/> | |||||
</div> | |||||
<div className="portfolio-breakdown-box-link"> | |||||
<div> | |||||
<Link | |||||
to={getComponentDrilldownUrl({ componentKey: component.key, metric: 'ncloc' })}> | |||||
<span>{translate('portfolio.language_breakdown_link')}</span> | |||||
</Link> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
</div> | </div> | ||||
{subComponents !== undefined && totalSubComponents !== undefined && ( | {subComponents !== undefined && totalSubComponents !== undefined && ( | ||||
</div> | </div> | ||||
); | ); | ||||
} | } | ||||
render() { | |||||
const { component } = this.props; | |||||
const { loading, measures } = this.state; | |||||
if (loading) { | |||||
return this.renderSpinner(); | |||||
} | |||||
return ( | |||||
<div className="page page-limited"> | |||||
<div className="page-with-sidebar"> | |||||
<div className="page-main">{this.renderMain()}</div> | |||||
<aside className="page-sidebar-fixed"> | |||||
<div className="portfolio-meta-card"> | |||||
<h4 className="portfolio-meta-header"> | |||||
{translate('overview.about_this_portfolio')} | |||||
{component.visibility && ( | |||||
<PrivacyBadgeContainer | |||||
className="spacer-left pull-right" | |||||
organization={component.organization} | |||||
qualifier={component.qualifier} | |||||
tooltipProps={{ projectKey: component.key }} | |||||
visibility={component.visibility} | |||||
/> | |||||
)} | |||||
</h4> | |||||
<Summary component={component} measures={measures || {}} /> | |||||
</div> | |||||
<div className="portfolio-meta-card"> | |||||
<Activity component={component.key} metrics={this.props.metrics} /> | |||||
</div> | |||||
<div className="portfolio-meta-card"> | |||||
<Report component={component} /> | |||||
</div> | |||||
</aside> | |||||
</div> | |||||
</div> | |||||
); | |||||
} | |||||
} | } | ||||
const mapDispatchToProps: DispatchToProps = { fetchMetrics }; | const mapDispatchToProps: DispatchToProps = { fetchMetrics }; |
metricType="SHORT_INT" | metricType="SHORT_INT" | ||||
value={String(effort.projects)} | value={String(effort.projects)} | ||||
/> | /> | ||||
{translate('projects_')} | |||||
{effort.projects === 1 | |||||
? translate('project_singular') | |||||
: translate('project_plural')} | |||||
</span> | </span> | ||||
</Link> | </Link> | ||||
), | ), |
import * as React from 'react'; | import * as React from 'react'; | ||||
import { Link } from 'react-router'; | import { Link } from 'react-router'; | ||||
import HistoryIcon from '../../../components/icons-components/HistoryIcon'; | import HistoryIcon from '../../../components/icons-components/HistoryIcon'; | ||||
import { translate } from '../../../helpers/l10n'; | |||||
import { getMeasureHistoryUrl } from '../../../helpers/urls'; | import { getMeasureHistoryUrl } from '../../../helpers/urls'; | ||||
interface Props { | interface Props { | ||||
export default function HistoryButtonLink({ component, metric }: Props) { | export default function HistoryButtonLink({ component, metric }: Props) { | ||||
return ( | return ( | ||||
<Link | |||||
className="button button-small spacer-left text-text-bottom" | |||||
to={getMeasureHistoryUrl(component, metric)}> | |||||
<HistoryIcon size={14} /> | |||||
<Link to={getMeasureHistoryUrl(component, metric)}> | |||||
<HistoryIcon className="little-spacer-right" size={14} /> | |||||
<span>{translate('portfolio.activity_link')}</span> | |||||
</Link> | </Link> | ||||
); | ); | ||||
} | } |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2019 SonarSource SA | |||||
* mailto:info AT sonarsource DOT com | |||||
* | |||||
* This program is free software; you can redistribute it and/or | |||||
* modify it under the terms of the GNU Lesser General Public | |||||
* License as published by the Free Software Foundation; either | |||||
* version 3 of the License, or (at your option) any later version. | |||||
* | |||||
* This program is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||||
* Lesser General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU Lesser General Public License | |||||
* along with this program; if not, write to the Free Software Foundation, | |||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||||
*/ | |||||
import * as React from 'react'; | |||||
import 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: T.Dict<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} rating={rating} /> | |||||
{effort && <Effort component={component} effort={effort} metricKey={'sqale_rating'} />} | |||||
</div> | |||||
); | |||||
} |
*/ | */ | ||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import { Link } from 'react-router'; | import { Link } from 'react-router'; | ||||
import BubblesIcon from '../../../components/icons-components/BubblesIcon'; | |||||
import MeasuresIcon from '../../../components/icons-components/MeasuresIcon'; | |||||
import { translate } from '../../../helpers/l10n'; | |||||
import { getComponentDrilldownUrl } from '../../../helpers/urls'; | import { getComponentDrilldownUrl } from '../../../helpers/urls'; | ||||
interface Props { | interface Props { | ||||
export default function MeasuresButtonLink({ component, metric }: Props) { | export default function MeasuresButtonLink({ component, metric }: Props) { | ||||
return ( | return ( | ||||
<Link | |||||
className="button button-small spacer-left text-text-bottom" | |||||
to={getComponentDrilldownUrl({ componentKey: component, metric })}> | |||||
<BubblesIcon size={14} /> | |||||
<Link to={getComponentDrilldownUrl({ componentKey: component, metric })}> | |||||
<MeasuresIcon className="little-spacer-right" size={14} /> | |||||
<span>{translate('portfolio.measures_link')}</span> | |||||
</Link> | </Link> | ||||
); | ); | ||||
} | } |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2019 SonarSource SA | |||||
* mailto:info AT sonarsource DOT com | |||||
* | |||||
* This program is free software; you can redistribute it and/or | |||||
* modify it under the terms of the GNU Lesser General Public | |||||
* License as published by the Free Software Foundation; either | |||||
* version 3 of the License, or (at your option) any later version. | |||||
* | |||||
* This program is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||||
* Lesser General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU Lesser General Public License | |||||
* along with this program; if not, write to the Free Software Foundation, | |||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||||
*/ | |||||
import * as React from 'react'; | |||||
import { Link } from 'react-router'; | |||||
import Effort from './Effort'; | |||||
import HistoryButtonLink from './HistoryButtonLink'; | |||||
import MainRating from './MainRating'; | |||||
import MeasuresButtonLink from './MeasuresButtonLink'; | |||||
import RatingFreshness from './RatingFreshness'; | |||||
import { METRICS_PER_TYPE } from '../utils'; | |||||
import HelpTooltip from '../../../components/controls/HelpTooltip'; | |||||
import Level from '../../../components/ui/Level'; | |||||
import Measure from '../../../components/measure/Measure'; | |||||
import { translate } from '../../../helpers/l10n'; | |||||
import { getComponentDrilldownUrl } from '../../../helpers/urls'; | |||||
interface Props { | |||||
component: string; | |||||
measures: T.Dict<string | undefined>; | |||||
metricKey: string; | |||||
} | |||||
export default function MetricBox({ component, measures, metricKey }: Props) { | |||||
const keys = METRICS_PER_TYPE[metricKey]; | |||||
const rating = measures[keys.rating]; | |||||
const lastReliabilityChange = measures[keys.last_change]; | |||||
const rawEffort = measures[keys.effort]; | |||||
const effort = rawEffort ? JSON.parse(rawEffort) : undefined; | |||||
return ( | |||||
<div className="portfolio-box"> | |||||
<h2 className="portfolio-box-title"> | |||||
{translate(keys.label)} | |||||
<HelpTooltip | |||||
className="little-spacer-left" | |||||
overlay={translate('portfolio.metric_domain', metricKey, 'help')} | |||||
/> | |||||
</h2> | |||||
{rating ? ( | |||||
<MainRating component={component} metric={keys.rating} value={rating} /> | |||||
) : ( | |||||
<div className="portfolio-box-rating"> | |||||
<span className="rating no-rating">—</span> | |||||
</div> | |||||
)} | |||||
{rating && ( | |||||
<> | |||||
<h3>{translate('portfolio.metric_trend')}</h3> | |||||
<RatingFreshness lastChange={lastReliabilityChange} rating={rating} /> | |||||
</> | |||||
)} | |||||
{metricKey === 'releasability' | |||||
? Number(effort) > 0 && ( | |||||
<> | |||||
<h3>{translate('portfolio.lowest_rated_projects')}</h3> | |||||
<div className="portfolio-effort"> | |||||
<Link | |||||
to={getComponentDrilldownUrl({ | |||||
componentKey: component, | |||||
metric: 'alert_status' | |||||
})}> | |||||
<span> | |||||
<Measure | |||||
className="little-spacer-right" | |||||
metricKey="projects" | |||||
metricType="SHORT_INT" | |||||
value={effort} | |||||
/> | |||||
{Number(effort) === 1 | |||||
? translate('project_singular') | |||||
: translate('project_plural')} | |||||
</span> | |||||
</Link>{' '} | |||||
<Level level="ERROR" small={true} /> | |||||
</div> | |||||
</> | |||||
) | |||||
: effort && ( | |||||
<> | |||||
<h3>{translate('portfolio.lowest_rated_projects')}</h3> | |||||
<Effort component={component} effort={effort} metricKey={keys.rating} /> | |||||
</> | |||||
)} | |||||
<div className="portfolio-box-links"> | |||||
<div> | |||||
<MeasuresButtonLink component={component} metric={keys.measuresMetric} /> | |||||
</div> | |||||
<div> | |||||
<HistoryButtonLink component={component} metric={keys.activity || keys.rating} /> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
); | |||||
} |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2019 SonarSource SA | |||||
* mailto:info AT sonarsource DOT com | |||||
* | |||||
* This program is free software; you can redistribute it and/or | |||||
* modify it under the terms of the GNU Lesser General Public | |||||
* License as published by the Free Software Foundation; either | |||||
* version 3 of the License, or (at your option) any later version. | |||||
* | |||||
* This program is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||||
* Lesser General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU Lesser General Public License | |||||
* along with this program; if not, write to the Free Software Foundation, | |||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||||
*/ | |||||
import * as React from 'react'; | |||||
import { Link } from 'react-router'; | |||||
import RatingFreshness from './RatingFreshness'; | |||||
import Rating from '../../../components/ui/Rating'; | |||||
import Measure from '../../../components/measure/Measure'; | |||||
import Level from '../../../components/ui/Level'; | |||||
import { translate } from '../../../helpers/l10n'; | |||||
import { getComponentDrilldownUrl } from '../../../helpers/urls'; | |||||
interface Props { | |||||
component: string; | |||||
measures: T.Dict<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 | |||||
className="portfolio-box-rating" | |||||
to={getComponentDrilldownUrl({ componentKey: component, metric: 'alert_status' })}> | |||||
<Rating value={rating} /> | |||||
</Link> | |||||
)} | |||||
<RatingFreshness lastChange={lastReleasabilityChange} rating={rating} /> | |||||
{effort && Number(effort) > 0 && ( | |||||
<div className="portfolio-effort"> | |||||
<Link to={getComponentDrilldownUrl({ componentKey: component, metric: 'alert_status' })}> | |||||
<span> | |||||
<Measure | |||||
className="little-spacer-right" | |||||
metricKey="projects" | |||||
metricType="SHORT_INT" | |||||
value={effort} | |||||
/> | |||||
{Number(effort) === 1 ? 'project' : 'projects'} | |||||
</span> | |||||
</Link>{' '} | |||||
<Level level="ERROR" small={true} /> | |||||
</div> | |||||
)} | |||||
</div> | |||||
); | |||||
} |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2019 SonarSource SA | |||||
* mailto:info AT sonarsource DOT com | |||||
* | |||||
* This program is free software; you can redistribute it and/or | |||||
* modify it under the terms of the GNU Lesser General Public | |||||
* License as published by the Free Software Foundation; either | |||||
* version 3 of the License, or (at your option) any later version. | |||||
* | |||||
* This program is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||||
* Lesser General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU Lesser General Public License | |||||
* along with this program; if not, write to the Free Software Foundation, | |||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||||
*/ | |||||
import * as React from 'react'; | |||||
import 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: T.Dict<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} rating={rating} /> | |||||
{effort && <Effort component={component} effort={effort} metricKey="reliability_rating" />} | |||||
</div> | |||||
); | |||||
} |
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import SubscriptionContainer from './SubscriptionContainer'; | |||||
import Subscription from './Subscription'; | |||||
import { Button } from '../../../components/ui/buttons'; | |||||
import DropdownIcon from '../../../components/icons-components/DropdownIcon'; | |||||
import Dropdown from '../../../components/controls/Dropdown'; | |||||
import { getReportStatus, ReportStatus, getReportUrl } from '../../../api/report'; | import { getReportStatus, ReportStatus, getReportUrl } from '../../../api/report'; | ||||
import { translate } from '../../../helpers/l10n'; | import { translate } from '../../../helpers/l10n'; | ||||
this.mounted = false; | this.mounted = false; | ||||
} | } | ||||
loadStatus() { | |||||
loadStatus = () => { | |||||
getReportStatus(this.props.component.key).then( | getReportStatus(this.props.component.key).then( | ||||
status => { | status => { | ||||
if (this.mounted) { | if (this.mounted) { | ||||
} | } | ||||
} | } | ||||
); | ); | ||||
} | |||||
renderHeader = () => <h4>{translate('report.page')}</h4>; | |||||
}; | |||||
render() { | render() { | ||||
const { component } = this.props; | const { component } = this.props; | ||||
const { status, loading } = this.state; | const { status, loading } = this.state; | ||||
if (loading) { | |||||
return ( | |||||
<div> | |||||
{this.renderHeader()} | |||||
<i className="spinner" /> | |||||
</div> | |||||
); | |||||
} | |||||
if (!status) { | |||||
if (loading || !status) { | |||||
return null; | return null; | ||||
} | } | ||||
return ( | |||||
<div> | |||||
{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"> | |||||
return status.canSubscribe ? ( | |||||
<Dropdown | |||||
overlay={ | |||||
<ul className="menu"> | |||||
<li> | |||||
<a | <a | ||||
className="button js-report-download" | |||||
download={component.name + ' - Executive Report.pdf'} | download={component.name + ' - Executive Report.pdf'} | ||||
href={getReportUrl(component.key)} | href={getReportUrl(component.key)} | ||||
target="_blank"> | target="_blank"> | ||||
{translate('report.print')} | {translate('report.print')} | ||||
</a> | </a> | ||||
</div> | |||||
</div> | |||||
)} | |||||
{status.canSubscribe && <SubscriptionContainer component={component.key} status={status} />} | |||||
</div> | |||||
</li> | |||||
<li> | |||||
<Subscription | |||||
component={component.key} | |||||
onSubscribe={this.loadStatus} | |||||
status={status} | |||||
/> | |||||
</li> | |||||
</ul> | |||||
} | |||||
tagName="li"> | |||||
<Button className="dropdown-toggle"> | |||||
{translate('portfolio.pdf_report')} | |||||
<DropdownIcon className="spacer-left icon-half-transparent" /> | |||||
</Button> | |||||
</Dropdown> | |||||
) : ( | |||||
<a | |||||
className="button" | |||||
download={component.name + ' - Executive Report.pdf'} | |||||
href={getReportUrl(component.key)} | |||||
target="_blank"> | |||||
{translate('report.print')} | |||||
</a> | |||||
); | ); | ||||
} | } | ||||
} | } |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2019 SonarSource SA | |||||
* mailto:info AT sonarsource DOT com | |||||
* | |||||
* This program is free software; you can redistribute it and/or | |||||
* modify it under the terms of the GNU Lesser General Public | |||||
* License as published by the Free Software Foundation; either | |||||
* version 3 of the License, or (at your option) any later version. | |||||
* | |||||
* This program is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||||
* Lesser General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU Lesser General Public License | |||||
* along with this program; if not, write to the Free Software Foundation, | |||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||||
*/ | |||||
import * as React from 'react'; | |||||
import 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: T.Dict<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} rating={rating} /> | |||||
{effort && <Effort component={component} effort={effort} metricKey="security_rating" />} | |||||
</div> | |||||
); | |||||
} |
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import AlertSuccessIcon from '../../../components/icons-components/AlertSuccessIcon'; | |||||
import { connect } from 'react-redux'; | |||||
import { ReportStatus, subscribe, unsubscribe } from '../../../api/report'; | import { ReportStatus, subscribe, unsubscribe } from '../../../api/report'; | ||||
import addGlobalSuccessMessage from '../../../app/utils/addGlobalSuccessMessage'; | |||||
import throwGlobalError from '../../../app/utils/throwGlobalError'; | |||||
import { translate, translateWithParameters } from '../../../helpers/l10n'; | import { translate, translateWithParameters } from '../../../helpers/l10n'; | ||||
import { Button } from '../../../components/ui/buttons'; | |||||
import { isLoggedIn } from '../../../helpers/users'; | import { isLoggedIn } from '../../../helpers/users'; | ||||
import { getCurrentUser, Store } from '../../../store/rootReducer'; | |||||
interface Props { | interface Props { | ||||
component: string; | component: string; | ||||
currentUser: T.CurrentUser; | currentUser: T.CurrentUser; | ||||
onSubscribe: () => void; | |||||
status: ReportStatus; | status: ReportStatus; | ||||
} | } | ||||
interface State { | |||||
loading: boolean; | |||||
subscribed?: boolean; | |||||
} | |||||
export default class Subscription extends React.PureComponent<Props, State> { | |||||
mounted = false; | |||||
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 }); | |||||
} | |||||
}; | |||||
export class Subscription extends React.PureComponent<Props> { | |||||
handleSubscription = (subscribed: boolean) => { | handleSubscription = (subscribed: boolean) => { | ||||
if (this.mounted) { | |||||
this.setState({ loading: false, subscribed }); | |||||
} | |||||
addGlobalSuccessMessage( | |||||
subscribed | |||||
? translateWithParameters('report.subscribe_x_success', this.getFrequencyText()) | |||||
: translateWithParameters('report.unsubscribe_x_success', this.getFrequencyText()) | |||||
); | |||||
this.props.onSubscribe(); | |||||
}; | }; | ||||
handleSubscribe = () => { | handleSubscribe = () => { | ||||
this.setState({ loading: true }); | |||||
subscribe(this.props.component) | subscribe(this.props.component) | ||||
.then(() => this.handleSubscription(true)) | .then(() => this.handleSubscription(true)) | ||||
.catch(this.stopLoading); | |||||
.catch(throwGlobalError); | |||||
}; | }; | ||||
handleUnsubscribe = () => { | handleUnsubscribe = () => { | ||||
this.setState({ loading: true }); | |||||
unsubscribe(this.props.component) | unsubscribe(this.props.component) | ||||
.then(() => this.handleSubscription(false)) | .then(() => this.handleSubscription(false)) | ||||
.catch(this.stopLoading); | |||||
.catch(throwGlobalError); | |||||
}; | }; | ||||
getEffectiveFrequencyText = () => { | |||||
getFrequencyText = () => { | |||||
const effectiveFrequency = | const effectiveFrequency = | ||||
this.props.status.componentFrequency || this.props.status.globalFrequency; | this.props.status.componentFrequency || this.props.status.globalFrequency; | ||||
return translate('report.frequency', effectiveFrequency, 'effective'); | |||||
return translate('report.frequency', effectiveFrequency); | |||||
}; | }; | ||||
renderLoading = () => this.state.loading && <i className="spacer-left spinner" />; | |||||
renderWhenSubscribed = () => ( | |||||
<div className="js-subscribed"> | |||||
<div className="spacer-bottom"> | |||||
<AlertSuccessIcon className="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() { | render() { | ||||
const hasEmail = isLoggedIn(this.props.currentUser) && !!this.props.currentUser.email; | const hasEmail = isLoggedIn(this.props.currentUser) && !!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>; | |||||
const { status } = this.props; | |||||
if (!hasEmail) { | |||||
return <span className="text-muted-2">{translate('report.no_email_to_subscribe')}</span>; | |||||
} | } | ||||
return <div className="big-spacer-top js-report-subscription">{inner}</div>; | |||||
return status.subscribed ? ( | |||||
<a href="#" onClick={this.handleUnsubscribe}> | |||||
{translateWithParameters('report.unsubscribe_x', this.getFrequencyText())} | |||||
</a> | |||||
) : ( | |||||
<a href="#" onClick={this.handleSubscribe}> | |||||
{translateWithParameters('report.subscribe_x', this.getFrequencyText())} | |||||
</a> | |||||
); | |||||
} | } | ||||
} | } | ||||
const mapStateToProps = (state: Store) => ({ | |||||
currentUser: getCurrentUser(state) | |||||
}); | |||||
export default connect(mapStateToProps)(Subscription); |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2019 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 { connect } from 'react-redux'; | |||||
import Subscription from './Subscription'; | |||||
import { getCurrentUser, Store } from '../../../store/rootReducer'; | |||||
const mapStateToProps = (state: Store) => ({ | |||||
currentUser: getCurrentUser(state) | |||||
}); | |||||
export default connect(mapStateToProps)(Subscription); |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2019 SonarSource SA | |||||
* mailto:info AT sonarsource DOT com | |||||
* | |||||
* This program is free software; you can redistribute it and/or | |||||
* modify it under the terms of the GNU Lesser General Public | |||||
* License as published by the Free Software Foundation; either | |||||
* version 3 of the License, or (at your option) any later version. | |||||
* | |||||
* This program is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||||
* Lesser General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU Lesser General Public License | |||||
* along with this program; if not, write to the Free Software Foundation, | |||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||||
*/ | |||||
import * as React from 'react'; | |||||
import { 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: T.Dict<string | undefined>; | |||||
} | |||||
export default function Summary({ component, measures }: Props) { | |||||
const { projects, ncloc } = measures; | |||||
const nclocDistribution = measures['ncloc_language_distribution']; | |||||
return ( | |||||
<section className="big-spacer-bottom" id="portfolio-summary"> | |||||
{component.description && <div className="big-spacer-bottom">{component.description}</div>} | |||||
<ul className="portfolio-grid"> | |||||
<li> | |||||
<div className="portfolio-measure-secondary-value"> | |||||
{projects ? ( | |||||
<Link | |||||
to={getComponentDrilldownUrl({ componentKey: component.key, metric: 'projects' })}> | |||||
<Measure metricKey="projects" metricType="SHORT_INT" value={projects} /> | |||||
</Link> | |||||
) : ( | |||||
'0' | |||||
)} | |||||
</div> | |||||
<div className="spacer-top text-muted">{translate('projects')}</div> | |||||
</li> | |||||
<li> | |||||
<div className="portfolio-measure-secondary-value"> | |||||
{ncloc ? ( | |||||
<Link to={getComponentDrilldownUrl({ componentKey: component.key, metric: 'ncloc' })}> | |||||
<Measure metricKey="ncloc" metricType="SHORT_INT" value={ncloc} /> | |||||
</Link> | |||||
) : ( | |||||
'0' | |||||
)} | |||||
</div> | |||||
<div className="spacer-top text-muted">{translate('metric.ncloc.name')}</div> | |||||
</li> | |||||
</ul> | |||||
{nclocDistribution && ( | |||||
<div className="big-spacer-top"> | |||||
<LanguageDistributionContainer distribution={nclocDistribution} width={260} /> | |||||
</div> | |||||
)} | |||||
</section> | |||||
); | |||||
} |
{translate('metric_domain.Reliability')} | {translate('metric_domain.Reliability')} | ||||
</th> | </th> | ||||
<th className="text-center portfolio-sub-components-cell"> | <th className="text-center portfolio-sub-components-cell"> | ||||
{translate('metric_domain.Security')} | |||||
{translate('portfolio.metric_domain.vulnerabilities')} | |||||
</th> | |||||
<th className="text-center portfolio-sub-components-cell"> | |||||
{translate('portfolio.metric_domain.security_hotspots')} | |||||
</th> | </th> | ||||
<th className="text-center portfolio-sub-components-cell"> | <th className="text-center portfolio-sub-components-cell"> | ||||
{translate('metric_domain.Maintainability')} | {translate('metric_domain.Maintainability')} | ||||
: renderCell(component.measures, 'releasability_rating', 'RATING')} | : renderCell(component.measures, 'releasability_rating', 'RATING')} | ||||
{renderCell(component.measures, 'reliability_rating', 'RATING')} | {renderCell(component.measures, 'reliability_rating', 'RATING')} | ||||
{renderCell(component.measures, 'security_rating', 'RATING')} | {renderCell(component.measures, 'security_rating', 'RATING')} | ||||
{renderCell(component.measures, 'security_review_rating', 'RATING')} | |||||
{renderCell(component.measures, 'sqale_rating', 'RATING')} | {renderCell(component.measures, 'sqale_rating', 'RATING')} | ||||
{renderNcloc(component.measures, maxLoc)} | {renderNcloc(component.measures, maxLoc)} | ||||
</tr> | </tr> |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2019 SonarSource SA | |||||
* mailto:info AT sonarsource DOT com | |||||
* | |||||
* This program is free software; you can redistribute it and/or | |||||
* modify it under the terms of the GNU Lesser General Public | |||||
* License as published by the Free Software Foundation; either | |||||
* version 3 of the License, or (at your option) any later version. | |||||
* | |||||
* This program is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||||
* Lesser General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU Lesser General Public License | |||||
* along with this program; if not, write to the Free Software Foundation, | |||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||||
*/ | |||||
import * as React from 'react'; | |||||
import { mount, shallow } from 'enzyme'; | |||||
import Activity from '../Activity'; | |||||
import { getAllTimeMachineData } from '../../../../api/time-machine'; | |||||
import { getProjectActivityGraph } from '../../../projectActivity/utils'; | |||||
jest.mock('../../../projectActivity/utils', () => { | |||||
const utils = require.requireActual('../../../projectActivity/utils'); | |||||
utils.getProjectActivityGraph = jest | |||||
.fn() | |||||
.mockReturnValue({ graph: 'custom', customGraphs: ['coverage'] }); | |||||
return utils; | |||||
}); | |||||
jest.mock('../../../../api/time-machine', () => ({ | |||||
getAllTimeMachineData: jest.fn().mockResolvedValue({ | |||||
measures: [ | |||||
{ | |||||
metric: 'coverage', | |||||
history: [ | |||||
{ date: '2017-01-01T00:00:00.000Z', value: '73' }, | |||||
{ date: '2017-01-02T00:00:00.000Z', value: '82' } | |||||
] | |||||
} | |||||
] | |||||
}) | |||||
})); | |||||
beforeEach(() => { | |||||
(getAllTimeMachineData as jest.Mock).mockClear(); | |||||
(getProjectActivityGraph as jest.Mock).mockClear(); | |||||
}); | |||||
it('renders', () => { | |||||
const wrapper = shallow(<Activity component="foo" metrics={{}} />); | |||||
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(); | |||||
expect(getProjectActivityGraph).toBeCalledWith('foo'); | |||||
}); | |||||
it('fetches history', () => { | |||||
mount(<Activity component="foo" metrics={{}} />); | |||||
expect(getAllTimeMachineData).toBeCalledWith({ component: 'foo', metrics: 'coverage' }); | |||||
}); |
getChildren: jest.fn(() => Promise.resolve({ components: [], paging: { total: 0 } })) | getChildren: jest.fn(() => Promise.resolve({ components: [], paging: { total: 0 } })) | ||||
})); | })); | ||||
// mock Activity to not deal with localstorage | |||||
jest.mock('../Activity', () => ({ | |||||
// eslint-disable-next-line | |||||
default: function Activity() { | |||||
return null; | |||||
} | |||||
})); | |||||
jest.mock('../Report', () => ({ | |||||
// eslint-disable-next-line | |||||
default: function Report() { | |||||
return null; | |||||
} | |||||
})); | |||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import { shallow, mount } from 'enzyme'; | import { shallow, mount } from 'enzyme'; | ||||
import { App } from '../App'; | import { App } from '../App'; | ||||
expect(getMeasures).toBeCalledWith({ | expect(getMeasures).toBeCalledWith({ | ||||
component: 'foo', | component: 'foo', | ||||
metricKeys: | metricKeys: | ||||
'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' | |||||
'projects,ncloc,ncloc_language_distribution,releasability_rating,releasability_effort,sqale_rating,maintainability_rating_effort,reliability_rating,reliability_rating_effort,security_rating,security_rating_effort,security_review_rating,security_review_rating_effort,last_change_on_releasability_rating,last_change_on_maintainability_rating,last_change_on_security_rating,last_change_on_security_review_rating,last_change_on_reliability_rating' | |||||
}); | }); | ||||
expect(getChildren).toBeCalledWith( | expect(getChildren).toBeCalledWith( | ||||
'foo', | 'foo', | ||||
'ncloc', | 'ncloc', | ||||
'releasability_rating', | 'releasability_rating', | ||||
'security_rating', | 'security_rating', | ||||
'security_review_rating', | |||||
'reliability_rating', | 'reliability_rating', | ||||
'sqale_rating', | 'sqale_rating', | ||||
'alert_status' | 'alert_status' |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2019 SonarSource SA | |||||
* mailto:info AT sonarsource DOT com | |||||
* | |||||
* This program is free software; you can redistribute it and/or | |||||
* modify it under the terms of the GNU Lesser General Public | |||||
* License as published by the Free Software Foundation; either | |||||
* version 3 of the License, or (at your option) any later version. | |||||
* | |||||
* This program is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||||
* Lesser General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU Lesser General Public License | |||||
* along with this program; if not, write to the Free Software Foundation, | |||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||||
*/ | |||||
import * as React from 'react'; | |||||
import { 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(); | |||||
}); |
*/ | */ | ||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import { shallow } from 'enzyme'; | import { shallow } from 'enzyme'; | ||||
import ReliabilityBox from '../ReliabilityBox'; | |||||
import MetricBox from '../MetricBox'; | |||||
it('renders', () => { | |||||
it('should render correctly', () => { | |||||
const measures = { | const measures = { | ||||
reliability_rating: '3', | reliability_rating: '3', | ||||
last_change_on_reliability_rating: '{"date":"2017-01-02T00:00:00.000Z","value":2}', | last_change_on_reliability_rating: '{"date":"2017-01-02T00:00:00.000Z","value":2}', | ||||
reliability_rating_effort: '{"rating":3,"projects":1}' | reliability_rating_effort: '{"rating":3,"projects":1}' | ||||
}; | }; | ||||
expect(shallow(<ReliabilityBox component="foo" measures={measures} />)).toMatchSnapshot(); | |||||
expect( | |||||
shallow(<MetricBox component="foo" measures={measures} metricKey="reliability" />) | |||||
).toMatchSnapshot(); | |||||
}); | |||||
it('should render correctly for releasability', () => { | |||||
const measures = { | |||||
releasability_rating: '2', | |||||
last_change_on_releasability_rating: '{"date":"2017-01-02T00:00:00.000Z","value":2}', | |||||
releasability_effort: '5' | |||||
}; | |||||
expect( | |||||
shallow(<MetricBox component="foo" measures={measures} metricKey="releasability" />) | |||||
).toMatchSnapshot(); | |||||
}); | |||||
it('should render correctly when no effort', () => { | |||||
const measures = { | |||||
releasability_rating: '2', | |||||
last_change_on_releasability_rating: '{"date":"2017-01-02T00:00:00.000Z","value":2}', | |||||
releasability_effort: '0' | |||||
}; | |||||
expect( | |||||
shallow(<MetricBox component="foo" measures={measures} metricKey="releasability" />) | |||||
).toMatchSnapshot(); | |||||
}); | }); |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2019 SonarSource SA | |||||
* mailto:info AT sonarsource DOT com | |||||
* | |||||
* This program is free software; you can redistribute it and/or | |||||
* modify it under the terms of the GNU Lesser General Public | |||||
* License as published by the Free Software Foundation; either | |||||
* version 3 of the License, or (at your option) any later version. | |||||
* | |||||
* This program is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||||
* Lesser General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU Lesser General Public License | |||||
* along with this program; if not, write to the Free Software Foundation, | |||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||||
*/ | |||||
import * as React from 'react'; | |||||
import { 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(); | |||||
}); |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2019 SonarSource SA | |||||
* mailto:info AT sonarsource DOT com | |||||
* | |||||
* This program is free software; you can redistribute it and/or | |||||
* modify it under the terms of the GNU Lesser General Public | |||||
* License as published by the Free Software Foundation; either | |||||
* version 3 of the License, or (at your option) any later version. | |||||
* | |||||
* This program is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||||
* Lesser General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU Lesser General Public License | |||||
* along with this program; if not, write to the Free Software Foundation, | |||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||||
*/ | |||||
import * as React from 'react'; | |||||
import { 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(); | |||||
}); |
}); | }); | ||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import { mount, shallow } from 'enzyme'; | |||||
import Subscription from '../Subscription'; | |||||
import { shallow, mount } from 'enzyme'; | |||||
import { Subscription } from '../Subscription'; | |||||
import { click, waitAndUpdate } from '../../../../helpers/testUtils'; | import { click, waitAndUpdate } from '../../../../helpers/testUtils'; | ||||
import { ReportStatus } from '../../../../api/report'; | |||||
const subscribe = require('../../../../api/report').subscribe as jest.Mock<any>; | const subscribe = require('../../../../api/report').subscribe as jest.Mock<any>; | ||||
const unsubscribe = require('../../../../api/report').unsubscribe 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 = { isLoggedIn: true, email: 'foo@example.com' }; | |||||
beforeEach(() => { | beforeEach(() => { | ||||
subscribe.mockClear(); | subscribe.mockClear(); | ||||
unsubscribe.mockClear(); | unsubscribe.mockClear(); | ||||
}); | }); | ||||
it('renders when subscribed', () => { | it('renders when subscribed', () => { | ||||
expect( | |||||
shallow(<Subscription component="foo" currentUser={currentUser} status={status} />) | |||||
).toMatchSnapshot(); | |||||
expect(shallowRender()).toMatchSnapshot(); | |||||
}); | }); | ||||
it('renders when not subscribed', () => { | it('renders when not subscribed', () => { | ||||
expect( | |||||
shallow( | |||||
<Subscription | |||||
component="foo" | |||||
currentUser={currentUser} | |||||
status={{ ...status, subscribed: false }} | |||||
/> | |||||
) | |||||
).toMatchSnapshot(); | |||||
expect(shallowRender({}, { subscribed: false })).toMatchSnapshot(); | |||||
}); | }); | ||||
it('renders when no email', () => { | it('renders when no email', () => { | ||||
expect( | |||||
shallow(<Subscription component="foo" currentUser={{ isLoggedIn: false }} status={status} />) | |||||
).toMatchSnapshot(); | |||||
expect(shallowRender({ currentUser: { isLoggedIn: false } })).toMatchSnapshot(); | |||||
}); | }); | ||||
it('changes subscription', async () => { | it('changes subscription', async () => { | ||||
const wrapper = mount(<Subscription component="foo" currentUser={currentUser} status={status} />); | |||||
click(wrapper.find('button')); | |||||
const status = { | |||||
canDownload: true, | |||||
canSubscribe: true, | |||||
componentFrequency: 'montly', | |||||
globalFrequency: 'weekly', | |||||
subscribed: true | |||||
}; | |||||
const currentUser = { isLoggedIn: true, email: 'foo@example.com' }; | |||||
const wrapper = mount( | |||||
<Subscription | |||||
component="foo" | |||||
currentUser={currentUser} | |||||
onSubscribe={jest.fn()} | |||||
status={status} | |||||
/> | |||||
); | |||||
click(wrapper.find('a')); | |||||
expect(unsubscribe).toBeCalledWith('foo'); | expect(unsubscribe).toBeCalledWith('foo'); | ||||
wrapper.setProps({ status: { ...status, subscribed: false } }); | |||||
await waitAndUpdate(wrapper); | await waitAndUpdate(wrapper); | ||||
click(wrapper.find('button')); | |||||
click(wrapper.find('a')); | |||||
expect(subscribe).toBeCalledWith('foo'); | expect(subscribe).toBeCalledWith('foo'); | ||||
}); | }); | ||||
function shallowRender( | |||||
props: Partial<Subscription['props']> = {}, | |||||
statusOverrides: Partial<ReportStatus> = {} | |||||
) { | |||||
const status = { | |||||
canDownload: true, | |||||
canSubscribe: true, | |||||
componentFrequency: 'montly', | |||||
globalFrequency: 'weekly', | |||||
subscribed: true, | |||||
...statusOverrides | |||||
}; | |||||
const currentUser = { isLoggedIn: true, email: 'foo@example.com' }; | |||||
return shallow<Subscription>( | |||||
<Subscription | |||||
component="foo" | |||||
currentUser={currentUser} | |||||
onSubscribe={jest.fn()} | |||||
status={status} | |||||
{...props} | |||||
/> | |||||
); | |||||
} |
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||||
exports[`renders 1`] = ` | |||||
<div | |||||
className="big-spacer-bottom" | |||||
> | |||||
<h4> | |||||
project_activity.page | |||||
</h4> | |||||
<withRouter(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={Object {}} | |||||
project="foo" | |||||
renderWhenEmpty={[Function]} | |||||
/> | |||||
</div> | |||||
`; |
exports[`renders 1`] = ` | exports[`renders 1`] = ` | ||||
<div | <div | ||||
className="page page-limited" | |||||
className="page page-limited portfolio-overview" | |||||
> | > | ||||
<div | <div | ||||
className="page-with-sidebar" | |||||
className="page-actions" | |||||
> | |||||
<Report | |||||
component={ | |||||
Object { | |||||
"key": "foo", | |||||
"name": "Foo", | |||||
"qualifier": "TRK", | |||||
} | |||||
} | |||||
/> | |||||
</div> | |||||
<h1> | |||||
portfolio.health_factors | |||||
</h1> | |||||
<div | |||||
className="portfolio-boxes" | |||||
> | |||||
<MetricBox | |||||
component="foo" | |||||
measures={ | |||||
Object { | |||||
"ncloc": "173", | |||||
"reliability_rating": "1", | |||||
} | |||||
} | |||||
metricKey="releasability" | |||||
/> | |||||
<MetricBox | |||||
component="foo" | |||||
measures={ | |||||
Object { | |||||
"ncloc": "173", | |||||
"reliability_rating": "1", | |||||
} | |||||
} | |||||
metricKey="reliability" | |||||
/> | |||||
<MetricBox | |||||
component="foo" | |||||
measures={ | |||||
Object { | |||||
"ncloc": "173", | |||||
"reliability_rating": "1", | |||||
} | |||||
} | |||||
metricKey="vulnerabilities" | |||||
/> | |||||
<MetricBox | |||||
component="foo" | |||||
measures={ | |||||
Object { | |||||
"ncloc": "173", | |||||
"reliability_rating": "1", | |||||
} | |||||
} | |||||
metricKey="security_hotspots" | |||||
/> | |||||
<MetricBox | |||||
component="foo" | |||||
measures={ | |||||
Object { | |||||
"ncloc": "173", | |||||
"reliability_rating": "1", | |||||
} | |||||
} | |||||
metricKey="maintainability" | |||||
/> | |||||
</div> | |||||
<h1> | |||||
portfolio.breakdown | |||||
</h1> | |||||
<div | |||||
className="portfolio-breakdown" | |||||
> | > | ||||
<div | <div | ||||
className="page-main" | |||||
className="portfolio-breakdown-box" | |||||
> | > | ||||
<div> | |||||
<div | |||||
className="portfolio-boxes" | |||||
> | |||||
<ReleasabilityBox | |||||
component="foo" | |||||
measures={ | |||||
Object { | |||||
"ncloc": "173", | |||||
"reliability_rating": "1", | |||||
} | |||||
} | |||||
/> | |||||
<ReliabilityBox | |||||
component="foo" | |||||
measures={ | |||||
Object { | |||||
"ncloc": "173", | |||||
"reliability_rating": "1", | |||||
} | |||||
} | |||||
/> | |||||
<SecurityBox | |||||
component="foo" | |||||
measures={ | |||||
Object { | |||||
"ncloc": "173", | |||||
"reliability_rating": "1", | |||||
} | |||||
} | |||||
/> | |||||
<MaintainabilityBox | |||||
<h2> | |||||
portfolio.number_of_projects | |||||
</h2> | |||||
<div | |||||
className="portfolio-breakdown-metric" | |||||
> | |||||
<Measure | |||||
metricKey="projects" | |||||
metricType="SHORT_INT" | |||||
value="0" | |||||
/> | |||||
</div> | |||||
<div | |||||
className="portfolio-breakdown-box-link" | |||||
> | |||||
<div> | |||||
<MeasuresButtonLink | |||||
component="foo" | component="foo" | ||||
measures={ | |||||
Object { | |||||
"ncloc": "173", | |||||
"reliability_rating": "1", | |||||
} | |||||
} | |||||
metric="projects" | |||||
/> | /> | ||||
</div> | </div> | ||||
<WorstProjects | |||||
component="foo" | |||||
subComponents={Array []} | |||||
total={0} | |||||
/> | |||||
</div> | </div> | ||||
</div> | </div> | ||||
<aside | |||||
className="page-sidebar-fixed" | |||||
<div | |||||
className="portfolio-breakdown-box" | |||||
> | > | ||||
<h2> | |||||
portfolio.number_of_lines | |||||
</h2> | |||||
<div | <div | ||||
className="portfolio-meta-card" | |||||
className="portfolio-breakdown-metric" | |||||
> | > | ||||
<h4 | |||||
className="portfolio-meta-header" | |||||
> | |||||
overview.about_this_portfolio | |||||
</h4> | |||||
<Summary | |||||
component={ | |||||
Object { | |||||
"key": "foo", | |||||
"name": "Foo", | |||||
"qualifier": "TRK", | |||||
} | |||||
} | |||||
measures={ | |||||
Object { | |||||
"ncloc": "173", | |||||
"reliability_rating": "1", | |||||
} | |||||
} | |||||
<Measure | |||||
metricKey="ncloc" | |||||
metricType="SHORT_INT" | |||||
value="173" | |||||
/> | /> | ||||
</div> | </div> | ||||
<div | <div | ||||
className="portfolio-meta-card" | |||||
className="portfolio-breakdown-box-link" | |||||
> | > | ||||
<Activity | |||||
component="foo" | |||||
metrics={Object {}} | |||||
/> | |||||
</div> | |||||
<div | |||||
className="portfolio-meta-card" | |||||
> | |||||
<Report | |||||
component={ | |||||
Object { | |||||
"key": "foo", | |||||
"name": "Foo", | |||||
"qualifier": "TRK", | |||||
<div> | |||||
<Link | |||||
onlyActiveOnIndex={false} | |||||
style={Object {}} | |||||
to={ | |||||
Object { | |||||
"pathname": "/component_measures", | |||||
"query": Object { | |||||
"id": "foo", | |||||
"metric": "ncloc", | |||||
}, | |||||
} | |||||
} | } | ||||
} | |||||
/> | |||||
> | |||||
<span> | |||||
portfolio.language_breakdown_link | |||||
</span> | |||||
</Link> | |||||
</div> | |||||
</div> | </div> | ||||
</aside> | |||||
</div> | |||||
</div> | </div> | ||||
<WorstProjects | |||||
component="foo" | |||||
subComponents={Array []} | |||||
total={0} | |||||
/> | |||||
</div> | </div> | ||||
`; | `; | ||||
exports[`renders when portfolio is empty 1`] = ` | exports[`renders when portfolio is empty 1`] = ` | ||||
<div | <div | ||||
className="page page-limited" | |||||
className="empty-search" | |||||
> | > | ||||
<div | |||||
className="page-with-sidebar" | |||||
> | |||||
<div | |||||
className="page-main" | |||||
> | |||||
<div | |||||
className="empty-search" | |||||
> | |||||
<h3> | |||||
portfolio.empty | |||||
</h3> | |||||
</div> | |||||
</div> | |||||
<aside | |||||
className="page-sidebar-fixed" | |||||
> | |||||
<div | |||||
className="portfolio-meta-card" | |||||
> | |||||
<h4 | |||||
className="portfolio-meta-header" | |||||
> | |||||
overview.about_this_portfolio | |||||
</h4> | |||||
<Summary | |||||
component={ | |||||
Object { | |||||
"key": "foo", | |||||
"name": "Foo", | |||||
"qualifier": "TRK", | |||||
} | |||||
} | |||||
measures={ | |||||
Object { | |||||
"reliability_rating": "1", | |||||
} | |||||
} | |||||
/> | |||||
</div> | |||||
<div | |||||
className="portfolio-meta-card" | |||||
> | |||||
<Activity | |||||
component="foo" | |||||
metrics={Object {}} | |||||
/> | |||||
</div> | |||||
<div | |||||
className="portfolio-meta-card" | |||||
> | |||||
<Report | |||||
component={ | |||||
Object { | |||||
"key": "foo", | |||||
"name": "Foo", | |||||
"qualifier": "TRK", | |||||
} | |||||
} | |||||
/> | |||||
</div> | |||||
</aside> | |||||
</div> | |||||
<h3> | |||||
portfolio.empty | |||||
</h3> | |||||
</div> | </div> | ||||
`; | `; | ||||
exports[`renders when portfolio is not computed 1`] = ` | exports[`renders when portfolio is not computed 1`] = ` | ||||
<div | <div | ||||
className="page page-limited" | |||||
className="empty-search" | |||||
> | > | ||||
<div | |||||
className="page-with-sidebar" | |||||
> | |||||
<div | |||||
className="page-main" | |||||
> | |||||
<div | |||||
className="empty-search" | |||||
> | |||||
<h3> | |||||
portfolio.not_computed | |||||
</h3> | |||||
</div> | |||||
</div> | |||||
<aside | |||||
className="page-sidebar-fixed" | |||||
> | |||||
<div | |||||
className="portfolio-meta-card" | |||||
> | |||||
<h4 | |||||
className="portfolio-meta-header" | |||||
> | |||||
overview.about_this_portfolio | |||||
</h4> | |||||
<Summary | |||||
component={ | |||||
Object { | |||||
"key": "foo", | |||||
"name": "Foo", | |||||
"qualifier": "TRK", | |||||
} | |||||
} | |||||
measures={ | |||||
Object { | |||||
"ncloc": "173", | |||||
} | |||||
} | |||||
/> | |||||
</div> | |||||
<div | |||||
className="portfolio-meta-card" | |||||
> | |||||
<Activity | |||||
component="foo" | |||||
metrics={Object {}} | |||||
/> | |||||
</div> | |||||
<div | |||||
className="portfolio-meta-card" | |||||
> | |||||
<Report | |||||
component={ | |||||
Object { | |||||
"key": "foo", | |||||
"name": "Foo", | |||||
"qualifier": "TRK", | |||||
} | |||||
} | |||||
/> | |||||
</div> | |||||
</aside> | |||||
</div> | |||||
<h3> | |||||
portfolio.not_computed | |||||
</h3> | |||||
</div> | </div> | ||||
`; | `; |
metricType="SHORT_INT" | metricType="SHORT_INT" | ||||
value="3" | value="3" | ||||
/> | /> | ||||
projects_ | |||||
project_plural | |||||
</span> | </span> | ||||
</Link>, | </Link>, | ||||
"rating": <Rating | "rating": <Rating |
exports[`renders 1`] = ` | exports[`renders 1`] = ` | ||||
<Link | <Link | ||||
className="button button-small spacer-left text-text-bottom" | |||||
onlyActiveOnIndex={false} | onlyActiveOnIndex={false} | ||||
style={Object {}} | style={Object {}} | ||||
to={ | to={ | ||||
} | } | ||||
> | > | ||||
<HistoryIcon | <HistoryIcon | ||||
className="little-spacer-right" | |||||
size={14} | size={14} | ||||
/> | /> | ||||
<span> | |||||
portfolio.activity_link | |||||
</span> | |||||
</Link> | </Link> | ||||
`; | `; |
// 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}" | |||||
rating="3" | |||||
/> | |||||
<Effort | |||||
component="foo" | |||||
effort={ | |||||
Object { | |||||
"projects": 1, | |||||
"rating": 3, | |||||
} | |||||
} | |||||
metricKey="sqale_rating" | |||||
/> | |||||
</div> | |||||
`; |
exports[`renders 1`] = ` | exports[`renders 1`] = ` | ||||
<Link | <Link | ||||
className="button button-small spacer-left text-text-bottom" | |||||
onlyActiveOnIndex={false} | onlyActiveOnIndex={false} | ||||
style={Object {}} | style={Object {}} | ||||
to={ | to={ | ||||
} | } | ||||
} | } | ||||
> | > | ||||
<BubblesIcon | |||||
<MeasuresIcon | |||||
className="little-spacer-right" | |||||
size={14} | size={14} | ||||
/> | /> | ||||
<span> | |||||
portfolio.measures_link | |||||
</span> | |||||
</Link> | </Link> | ||||
`; | `; |
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||||
exports[`should render correctly 1`] = ` | |||||
<div | |||||
className="portfolio-box" | |||||
> | |||||
<h2 | |||||
className="portfolio-box-title" | |||||
> | |||||
metric_domain.Reliability | |||||
<HelpTooltip | |||||
className="little-spacer-left" | |||||
overlay="portfolio.metric_domain.reliability.help" | |||||
/> | |||||
</h2> | |||||
<MainRating | |||||
component="foo" | |||||
metric="reliability_rating" | |||||
value="3" | |||||
/> | |||||
<h3> | |||||
portfolio.metric_trend | |||||
</h3> | |||||
<RatingFreshness | |||||
lastChange="{\\"date\\":\\"2017-01-02T00:00:00.000Z\\",\\"value\\":2}" | |||||
rating="3" | |||||
/> | |||||
<h3> | |||||
portfolio.lowest_rated_projects | |||||
</h3> | |||||
<Effort | |||||
component="foo" | |||||
effort={ | |||||
Object { | |||||
"projects": 1, | |||||
"rating": 3, | |||||
} | |||||
} | |||||
metricKey="reliability_rating" | |||||
/> | |||||
<div | |||||
className="portfolio-box-links" | |||||
> | |||||
<div> | |||||
<MeasuresButtonLink | |||||
component="foo" | |||||
metric="Reliability" | |||||
/> | |||||
</div> | |||||
<div> | |||||
<HistoryButtonLink | |||||
component="foo" | |||||
metric="reliability_rating" | |||||
/> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
`; | |||||
exports[`should render correctly for releasability 1`] = ` | |||||
<div | |||||
className="portfolio-box" | |||||
> | |||||
<h2 | |||||
className="portfolio-box-title" | |||||
> | |||||
metric_domain.Releasability | |||||
<HelpTooltip | |||||
className="little-spacer-left" | |||||
overlay="portfolio.metric_domain.releasability.help" | |||||
/> | |||||
</h2> | |||||
<MainRating | |||||
component="foo" | |||||
metric="releasability_rating" | |||||
value="2" | |||||
/> | |||||
<h3> | |||||
portfolio.metric_trend | |||||
</h3> | |||||
<RatingFreshness | |||||
lastChange="{\\"date\\":\\"2017-01-02T00:00:00.000Z\\",\\"value\\":2}" | |||||
rating="2" | |||||
/> | |||||
<h3> | |||||
portfolio.lowest_rated_projects | |||||
</h3> | |||||
<div | |||||
className="portfolio-effort" | |||||
> | |||||
<Link | |||||
onlyActiveOnIndex={false} | |||||
style={Object {}} | |||||
to={ | |||||
Object { | |||||
"pathname": "/component_measures", | |||||
"query": Object { | |||||
"id": "foo", | |||||
"metric": "alert_status", | |||||
}, | |||||
} | |||||
} | |||||
> | |||||
<span> | |||||
<Measure | |||||
className="little-spacer-right" | |||||
metricKey="projects" | |||||
metricType="SHORT_INT" | |||||
value={5} | |||||
/> | |||||
project_plural | |||||
</span> | |||||
</Link> | |||||
<Level | |||||
level="ERROR" | |||||
small={true} | |||||
/> | |||||
</div> | |||||
<div | |||||
className="portfolio-box-links" | |||||
> | |||||
<div> | |||||
<MeasuresButtonLink | |||||
component="foo" | |||||
metric="Releasability" | |||||
/> | |||||
</div> | |||||
<div> | |||||
<HistoryButtonLink | |||||
component="foo" | |||||
metric="releasability_rating" | |||||
/> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
`; | |||||
exports[`should render correctly when no effort 1`] = ` | |||||
<div | |||||
className="portfolio-box" | |||||
> | |||||
<h2 | |||||
className="portfolio-box-title" | |||||
> | |||||
metric_domain.Releasability | |||||
<HelpTooltip | |||||
className="little-spacer-left" | |||||
overlay="portfolio.metric_domain.releasability.help" | |||||
/> | |||||
</h2> | |||||
<MainRating | |||||
component="foo" | |||||
metric="releasability_rating" | |||||
value="2" | |||||
/> | |||||
<h3> | |||||
portfolio.metric_trend | |||||
</h3> | |||||
<RatingFreshness | |||||
lastChange="{\\"date\\":\\"2017-01-02T00:00:00.000Z\\",\\"value\\":2}" | |||||
rating="2" | |||||
/> | |||||
<div | |||||
className="portfolio-box-links" | |||||
> | |||||
<div> | |||||
<MeasuresButtonLink | |||||
component="foo" | |||||
metric="Releasability" | |||||
/> | |||||
</div> | |||||
<div> | |||||
<HistoryButtonLink | |||||
component="foo" | |||||
metric="releasability_rating" | |||||
/> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
`; |
// 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 { | |||||
"id": "foo", | |||||
"metric": "alert_status", | |||||
}, | |||||
} | |||||
} | |||||
> | |||||
<Rating | |||||
value="3" | |||||
/> | |||||
</Link> | |||||
<RatingFreshness | |||||
lastChange="{\\"date\\":\\"2017-01-02T00:00:00.000Z\\",\\"value\\":2}" | |||||
rating="3" | |||||
/> | |||||
<div | |||||
className="portfolio-effort" | |||||
> | |||||
<Link | |||||
onlyActiveOnIndex={false} | |||||
style={Object {}} | |||||
to={ | |||||
Object { | |||||
"pathname": "/component_measures", | |||||
"query": Object { | |||||
"id": "foo", | |||||
"metric": "alert_status", | |||||
}, | |||||
} | |||||
} | |||||
> | |||||
<span> | |||||
<Measure | |||||
className="little-spacer-right" | |||||
metricKey="projects" | |||||
metricType="SHORT_INT" | |||||
value="7" | |||||
/> | |||||
projects | |||||
</span> | |||||
</Link> | |||||
<Level | |||||
level="ERROR" | |||||
small={true} | |||||
/> | |||||
</div> | |||||
</div> | |||||
`; |
// 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}" | |||||
rating="3" | |||||
/> | |||||
<Effort | |||||
component="foo" | |||||
effort={ | |||||
Object { | |||||
"projects": 1, | |||||
"rating": 3, | |||||
} | |||||
} | |||||
metricKey="reliability_rating" | |||||
/> | |||||
</div> | |||||
`; |
// Jest Snapshot v1, https://goo.gl/fbAQLP | // Jest Snapshot v1, https://goo.gl/fbAQLP | ||||
exports[`renders 1`] = ` | |||||
<div> | |||||
<h4> | |||||
report.page | |||||
</h4> | |||||
<i | |||||
className="spinner" | |||||
/> | |||||
</div> | |||||
`; | |||||
exports[`renders 1`] = `""`; | |||||
exports[`renders 2`] = ` | exports[`renders 2`] = ` | ||||
<div> | |||||
<h4> | |||||
report.page | |||||
</h4> | |||||
<div | |||||
className="js-report-can-download" | |||||
> | |||||
report.can_download | |||||
<div | |||||
className="spacer-top" | |||||
<Dropdown | |||||
overlay={ | |||||
<ul | |||||
className="menu" | |||||
> | > | ||||
<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> | |||||
<li> | |||||
<a | |||||
download="Foo - Executive Report.pdf" | |||||
href="/api/governance_reports/download?componentKey=foo" | |||||
target="_blank" | |||||
> | |||||
report.print | |||||
</a> | |||||
</li> | |||||
<li> | |||||
<Connect(Subscription) | |||||
component="foo" | |||||
onSubscribe={[Function]} | |||||
status={ | |||||
Object { | |||||
"canDownload": true, | |||||
"canSubscribe": true, | |||||
"componentFrequency": "montly", | |||||
"globalFrequency": "weekly", | |||||
"subscribed": true, | |||||
} | |||||
} | |||||
/> | |||||
</li> | |||||
</ul> | |||||
} | |||||
tagName="li" | |||||
> | |||||
<Button | |||||
className="dropdown-toggle" | |||||
> | |||||
portfolio.pdf_report | |||||
<DropdownIcon | |||||
className="spacer-left icon-half-transparent" | |||||
/> | |||||
</Button> | |||||
</Dropdown> | |||||
`; | `; |
// 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}" | |||||
rating="3" | |||||
/> | |||||
<Effort | |||||
component="foo" | |||||
effort={ | |||||
Object { | |||||
"projects": 1, | |||||
"rating": 3, | |||||
} | |||||
} | |||||
metricKey="security_rating" | |||||
/> | |||||
</div> | |||||
`; |
// Jest Snapshot v1, https://goo.gl/fbAQLP | // Jest Snapshot v1, https://goo.gl/fbAQLP | ||||
exports[`renders when no email 1`] = ` | exports[`renders when no email 1`] = ` | ||||
<div | |||||
className="big-spacer-top js-report-subscription" | |||||
<span | |||||
className="text-muted-2" | |||||
> | > | ||||
<p | |||||
className="note js-no-email" | |||||
> | |||||
report.no_email_to_subscribe | |||||
</p> | |||||
</div> | |||||
report.no_email_to_subscribe | |||||
</span> | |||||
`; | `; | ||||
exports[`renders when not subscribed 1`] = ` | exports[`renders when not subscribed 1`] = ` | ||||
<div | |||||
className="big-spacer-top js-report-subscription" | |||||
<a | |||||
href="#" | |||||
onClick={[Function]} | |||||
> | > | ||||
<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> | |||||
report.subscribe_x.report.frequency.montly | |||||
</a> | |||||
`; | `; | ||||
exports[`renders when subscribed 1`] = ` | exports[`renders when subscribed 1`] = ` | ||||
<div | |||||
className="big-spacer-top js-report-subscription" | |||||
<a | |||||
href="#" | |||||
onClick={[Function]} | |||||
> | > | ||||
<div | |||||
className="js-subscribed" | |||||
> | |||||
<div | |||||
className="spacer-bottom" | |||||
> | |||||
<AlertSuccessIcon | |||||
className="pull-left spacer-right" | |||||
/> | |||||
<div | |||||
className="overflow-hidden" | |||||
> | |||||
report.subscribed.report.frequency.montly.effective | |||||
</div> | |||||
</div> | |||||
<Button | |||||
onClick={[Function]} | |||||
> | |||||
report.unsubscribe | |||||
</Button> | |||||
</div> | |||||
</div> | |||||
report.unsubscribe_x.report.frequency.montly | |||||
</a> | |||||
`; | `; |
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||||
exports[`renders 1`] = ` | |||||
<section | |||||
className="big-spacer-bottom" | |||||
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 { | |||||
"id": "foo", | |||||
"metric": "projects", | |||||
}, | |||||
} | |||||
} | |||||
> | |||||
<Measure | |||||
metricKey="projects" | |||||
metricType="SHORT_INT" | |||||
value="15" | |||||
/> | |||||
</Link> | |||||
</div> | |||||
<div | |||||
className="spacer-top text-muted" | |||||
> | |||||
projects | |||||
</div> | |||||
</li> | |||||
<li> | |||||
<div | |||||
className="portfolio-measure-secondary-value" | |||||
> | |||||
<Link | |||||
onlyActiveOnIndex={false} | |||||
style={Object {}} | |||||
to={ | |||||
Object { | |||||
"pathname": "/component_measures", | |||||
"query": Object { | |||||
"id": "foo", | |||||
"metric": "ncloc", | |||||
}, | |||||
} | |||||
} | |||||
> | |||||
<Measure | |||||
metricKey="ncloc" | |||||
metricType="SHORT_INT" | |||||
value="1234" | |||||
/> | |||||
</Link> | |||||
</div> | |||||
<div | |||||
className="spacer-top text-muted" | |||||
> | |||||
metric.ncloc.name | |||||
</div> | |||||
</li> | |||||
</ul> | |||||
<div | |||||
className="big-spacer-top" | |||||
> | |||||
<Connect(LanguageDistribution) | |||||
distribution="java=13;js=17" | |||||
width={260} | |||||
/> | |||||
</div> | |||||
</section> | |||||
`; |
<th | <th | ||||
className="text-center portfolio-sub-components-cell" | className="text-center portfolio-sub-components-cell" | ||||
> | > | ||||
metric_domain.Security | |||||
portfolio.metric_domain.vulnerabilities | |||||
</th> | |||||
<th | |||||
className="text-center portfolio-sub-components-cell" | |||||
> | |||||
portfolio.metric_domain.security_hotspots | |||||
</th> | </th> | ||||
<th | <th | ||||
className="text-center portfolio-sub-components-cell" | className="text-center portfolio-sub-components-cell" | ||||
value="1" | value="1" | ||||
/> | /> | ||||
</td> | </td> | ||||
<td | |||||
className="text-center" | |||||
> | |||||
<Measure | |||||
metricKey="security_review_rating" | |||||
metricType="RATING" | |||||
/> | |||||
</td> | |||||
<td | <td | ||||
className="text-center" | className="text-center" | ||||
> | > | ||||
value="1" | value="1" | ||||
/> | /> | ||||
</td> | </td> | ||||
<td | |||||
className="text-center" | |||||
> | |||||
<Measure | |||||
metricKey="security_review_rating" | |||||
metricType="RATING" | |||||
/> | |||||
</td> | |||||
<td | <td | ||||
className="text-center" | className="text-center" | ||||
> | > | ||||
value="1" | value="1" | ||||
/> | /> | ||||
</td> | </td> | ||||
<td | |||||
className="text-center" | |||||
> | |||||
<Measure | |||||
metricKey="security_review_rating" | |||||
metricType="RATING" | |||||
/> | |||||
</td> | |||||
<td | <td | ||||
className="text-center" | className="text-center" | ||||
> | > |
* along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
.portfolio-overview > h1 { | |||||
font-weight: normal; | |||||
} | |||||
.portfolio-overview > .page-actions { | |||||
margin-bottom: 0; | |||||
} | |||||
.portfolio-measure-secondary-value { | .portfolio-measure-secondary-value { | ||||
line-height: var(--controlHeight); | line-height: var(--controlHeight); | ||||
font-size: 18px; | font-size: 18px; | ||||
.portfolio-freshness { | .portfolio-freshness { | ||||
line-height: var(--controlHeight); | line-height: var(--controlHeight); | ||||
margin-top: 12px; | |||||
color: var(--secondFontColor); | |||||
font-size: var(--smallFontSize); | font-size: var(--smallFontSize); | ||||
white-space: nowrap; | white-space: nowrap; | ||||
} | } | ||||
.portfolio-effort { | |||||
margin-top: 12px; | |||||
padding-top: 12px; | |||||
border-top: 1px solid var(--barBorderColor); | |||||
} | |||||
.portfolio-boxes { | .portfolio-boxes { | ||||
display: flex; | display: flex; | ||||
justify-content: space-between; | justify-content: space-between; | ||||
align-items: stretch; | align-items: stretch; | ||||
margin-bottom: 20px; | margin-bottom: 20px; | ||||
padding: 15px 0; | padding: 15px 0; | ||||
border: 1px solid var(--barBorderColor); | |||||
background-color: #fff; | |||||
width: 100%; | |||||
} | } | ||||
.portfolio-box { | .portfolio-box { | ||||
flex: 1 0 10%; | |||||
position: relative; | position: relative; | ||||
width: 25%; | |||||
padding: 0 5px; | |||||
border-radius: 3px; | |||||
padding: 0 calc(2 * var(--gridSize)) 66px; | |||||
margin: 0 var(--gridSize); | |||||
border: 1px solid var(--barBorderColor); | |||||
background-color: #fff; | |||||
box-sizing: border-box; | box-sizing: border-box; | ||||
text-align: center; | |||||
} | |||||
.portfolio-box:first-child { | |||||
margin-left: 0; | |||||
} | |||||
.portfolio-box:last-child { | |||||
margin-right: 0; | |||||
} | } | ||||
.portfolio-box-title { | .portfolio-box-title { | ||||
margin-bottom: 25px; | |||||
padding: var(--gridSize) 0 calc(2 * var(--gridSize)); | |||||
margin: var(--gridSize) 0 calc(2 * var(--gridSize)); | |||||
font-size: var(--bigFontSize); | font-size: var(--bigFontSize); | ||||
line-height: var(--bigFontSize); | |||||
border-bottom: 1px solid var(--barBorderColor); | |||||
white-space: nowrap; | |||||
} | } | ||||
.portfolio-box-title > .button-small > svg { | .portfolio-box-title > .button-small > svg { | ||||
margin-top: 0; | margin-top: 0; | ||||
} | } | ||||
.portfolio-box > h3 { | |||||
color: var(--secondFontColor); | |||||
font-size: 12px; | |||||
font-weight: normal; | |||||
margin-top: var(--gridSize); | |||||
} | |||||
.portfolio-box-rating, | .portfolio-box-rating, | ||||
.portfolio-box-rating .rating { | .portfolio-box-rating .rating { | ||||
display: block; | display: block; | ||||
width: 120px; | |||||
height: 120px; | |||||
line-height: 120px; | |||||
width: 80px; | |||||
height: 80px; | |||||
line-height: 80px; | |||||
} | } | ||||
.portfolio-box-rating { | .portfolio-box-rating { | ||||
margin: 0 auto; | |||||
margin: calc(2 * var(--gridSize)) auto; | |||||
border: none; | border: none; | ||||
} | } | ||||
.portfolio-box-rating .rating { | .portfolio-box-rating .rating { | ||||
border-radius: 120px; | |||||
font-size: 60px; | |||||
border-radius: 80px; | |||||
font-size: 48px; | |||||
text-align: center; | text-align: center; | ||||
} | } | ||||
.portfolio-box-rating .rating.no-rating { | |||||
color: var(--secondFontColor); | |||||
} | |||||
.portfolio-box-links { | |||||
border-top: 1px solid var(--barBorderColor); | |||||
text-align: center; | |||||
position: absolute; | |||||
bottom: 0; | |||||
left: 0; | |||||
right: 0; | |||||
} | |||||
.portfolio-box-links > div { | |||||
display: inline-block; | |||||
padding: calc(1.5 * var(--gridSize)) 0; | |||||
width: 50%; | |||||
box-sizing: border-box; | |||||
} | |||||
.portfolio-box-links > div:first-child { | |||||
border-right: 1px solid var(--barBorderColor); | |||||
} | |||||
.portfolio-box-links a, | |||||
.portfolio-breakdown-box-link a { | |||||
border: none; | |||||
} | |||||
.portfolio-box-links svg, | |||||
.portfolio-breakdown-box-link svg { | |||||
vertical-align: middle; | |||||
} | |||||
.portfolio-box-links a > span, | |||||
.portfolio-breakdown-box-link a > span { | |||||
border-bottom: 1px solid #cae3f2; | |||||
} | |||||
.portfolio-breakdown { | |||||
display: flex; | |||||
flex-direction: row; | |||||
align-items: flex-start; | |||||
} | |||||
.portfolio-breakdown-box { | |||||
flex: 0 0 auto; | |||||
background-color: white; | |||||
border: 1px solid var(--barBorderColor); | |||||
margin: var(--gridSize) var(--gridSize) calc(2 * var(--gridSize)); | |||||
padding: 0 var(--gridSize) 66px; | |||||
position: relative; | |||||
} | |||||
.portfolio-breakdown-box:first-child { | |||||
margin-left: 0; | |||||
} | |||||
.portfolio-breakdown-box:last-child { | |||||
margin-right: 0; | |||||
} | |||||
.portfolio-breakdown-box > h2 { | |||||
color: var(--secondFontColor); | |||||
margin: var(--gridSize); | |||||
font-size: 12px; | |||||
} | |||||
.portfolio-breakdown-box > .portfolio-breakdown-metric { | |||||
font-size: var(--hugeFontSize); | |||||
margin-left: var(--gridSize); | |||||
} | |||||
.portfolio-breakdown-box-link { | |||||
border-top: 1px solid var(--barBorderColor); | |||||
padding: calc(2 * var(--gridSize)); | |||||
position: absolute; | |||||
bottom: 0; | |||||
left: 0; | |||||
right: 0; | |||||
} | |||||
.portfolio-sub-components table.data > thead > tr > th { | .portfolio-sub-components table.data > thead > tr > th { | ||||
font-size: var(--baseFontSize); | font-size: var(--baseFontSize); | ||||
text-transform: none; | text-transform: none; | ||||
vertical-align: middle; | |||||
} | } | ||||
.portfolio-sub-components-cell { | .portfolio-sub-components-cell { | ||||
width: 90px; | |||||
width: 110px; | |||||
} | } | ||||
.portfolio-meta-header { | .portfolio-meta-header { |
'security_rating', | 'security_rating', | ||||
'security_rating_effort', | 'security_rating_effort', | ||||
'security_review_rating', | |||||
'security_review_rating_effort', | |||||
'last_change_on_releasability_rating', | 'last_change_on_releasability_rating', | ||||
'last_change_on_maintainability_rating', | 'last_change_on_maintainability_rating', | ||||
'last_change_on_security_rating', | 'last_change_on_security_rating', | ||||
'last_change_on_security_review_rating', | |||||
'last_change_on_reliability_rating' | 'last_change_on_reliability_rating' | ||||
]; | ]; | ||||
export interface MetricKeys { | |||||
activity?: string; | |||||
effort: string; | |||||
measuresMetric: string; | |||||
label: string; | |||||
last_change: string; | |||||
rating: string; | |||||
} | |||||
export const METRICS_PER_TYPE: T.Dict<MetricKeys> = { | |||||
releasability: { | |||||
measuresMetric: 'Releasability', | |||||
label: 'metric_domain.Releasability', | |||||
rating: 'releasability_rating', | |||||
effort: 'releasability_effort', | |||||
last_change: 'last_change_on_releasability_rating' | |||||
}, | |||||
reliability: { | |||||
measuresMetric: 'Reliability', | |||||
label: 'metric_domain.Reliability', | |||||
rating: 'reliability_rating', | |||||
effort: 'reliability_rating_effort', | |||||
last_change: 'last_change_on_reliability_rating' | |||||
}, | |||||
vulnerabilities: { | |||||
measuresMetric: 'Security', | |||||
label: 'portfolio.metric_domain.vulnerabilities', | |||||
rating: 'security_rating', | |||||
effort: 'security_rating_effort', | |||||
last_change: 'last_change_on_security_rating', | |||||
activity: 'security_rating,vulnerabilities' | |||||
}, | |||||
security_hotspots: { | |||||
measuresMetric: 'security_review_rating', | |||||
label: 'portfolio.metric_domain.security_hotspots', | |||||
rating: 'security_review_rating', | |||||
effort: 'security_review_rating_effort', | |||||
last_change: 'last_change_on_security_review_rating' | |||||
}, | |||||
maintainability: { | |||||
measuresMetric: 'Maintainability', | |||||
label: 'metric_domain.Maintainability', | |||||
rating: 'sqale_rating', | |||||
effort: 'maintainability_rating_effort', | |||||
last_change: 'last_change_on_maintainability_rating' | |||||
} | |||||
}; | |||||
export const SUB_COMPONENTS_METRICS = [ | export const SUB_COMPONENTS_METRICS = [ | ||||
'ncloc', | 'ncloc', | ||||
'releasability_rating', | 'releasability_rating', | ||||
'security_rating', | 'security_rating', | ||||
'security_review_rating', | |||||
'reliability_rating', | 'reliability_rating', | ||||
'sqale_rating', | 'sqale_rating', | ||||
'alert_status' | 'alert_status' |
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import { shallow } from 'enzyme'; | |||||
import Summary from '../Summary'; | |||||
import Icon, { IconProps } from './Icon'; | |||||
it('renders', () => { | |||||
expect( | |||||
shallow( | |||||
<Summary | |||||
component={{ description: 'blabla', key: 'foo' }} | |||||
measures={{ ncloc: '1234', ncloc_language_distribution: 'java=13;js=17', projects: '15' }} | |||||
/> | |||||
) | |||||
).toMatchSnapshot(); | |||||
}); | |||||
export default function MeasuresIcon({ className, fill = 'currentColor', size }: IconProps) { | |||||
return ( | |||||
<Icon className={className} size={size} style={{ fillRule: 'nonzero' }}> | |||||
<path d="M3.33 6.13h2v6.54h-2zm3.74-2.8h1.86v9.34H7.07zm3.73 5.34h1.87v4H10.8z" fill={fill} /> | |||||
</Icon> | |||||
); | |||||
} |
project=Project | project=Project | ||||
projects=Projects | projects=Projects | ||||
projects_=project(s) | projects_=project(s) | ||||
project_singular=project | |||||
project_plural=projects | |||||
projects_management=Projects Management | projects_management=Projects Management | ||||
quality_profile=Quality Profile | quality_profile=Quality Profile | ||||
raw=Raw | raw=Raw | ||||
portfolio.not_computed=This portfolio is not yet computed. | portfolio.not_computed=This portfolio is not yet computed. | ||||
portfolio.app.empty=This application is empty. | portfolio.app.empty=This application is empty. | ||||
portfolio.app.no_lines_of_code=All projects in this application are empty | portfolio.app.no_lines_of_code=All projects in this application are empty | ||||
portfolio.metric_trend=Metric trend | |||||
portfolio.lowest_rated_projects=Lowest rated projects | |||||
portfolio.health_factors=Portfolio health factors | |||||
portfolio.activity_link=Activity | |||||
portfolio.measures_link=Measures | |||||
portfolio.language_breakdown_link=Language breakdown | |||||
portfolio.breakdown=Portfolio breakdown | |||||
portfolio.pdf_report=Portfolio PDF Report | |||||
portfolio.number_of_projects=Number of projects | |||||
portfolio.number_of_lines=Number of lines of code | |||||
portfolio.metric_domain.vulnerabilities=Security Vulnerabilities | |||||
portfolio.metric_domain.security_hotspots=Security Hotspots Review | |||||
#------------------------------------------------------------------------------ | |||||
# | |||||
# METRIC DOMAINS HELP TEXT | |||||
# | |||||
#------------------------------------------------------------------------------ | |||||
portfolio.metric_domain.releasability.help=Ratio of projects in the Portfolio that have passed the Quality Gate. | |||||
portfolio.metric_domain.reliability.help=Average Reliability rating for all projects in the portfolio. | |||||
portfolio.metric_domain.vulnerabilities.help=Average security rating for all projects in the portfolio. | |||||
portfolio.metric_domain.security_hotspots.help=Ratio of To Review or In Review Security Hotspots per 1k lines of code. | |||||
portfolio.metric_domain.maintainability.help=Average maintainability rating for all projects in the portfolio. | |||||
#------------------------------------------------------------------------------ | #------------------------------------------------------------------------------ | ||||
# | # |