+++ /dev/null
-/*
- * 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 { 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 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 { 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 { getMeasures } from '../../../api/measures';
import { translate } from '../../../helpers/l10n';
+import { getComponentDrilldownUrl } from '../../../helpers/urls';
import { fetchMetrics } from '../../../store/rootActions';
import { getMetrics, Store } from '../../../store/rootReducer';
import '../styles.css';
-import PrivacyBadgeContainer from '../../../components/common/PrivacyBadgeContainer';
interface OwnProps {
component: T.Component;
);
}
- renderMain() {
+ render() {
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()) {
return this.renderEmpty();
}
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">
- <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>
{subComponents !== undefined && totalSubComponents !== undefined && (
</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 };
metricType="SHORT_INT"
value={String(effort.projects)}
/>
- {translate('projects_')}
+ {effort.projects === 1
+ ? translate('project_singular')
+ : translate('project_plural')}
</span>
</Link>
),
import * as React from 'react';
import { Link } from 'react-router';
import HistoryIcon from '../../../components/icons-components/HistoryIcon';
+import { translate } from '../../../helpers/l10n';
import { getMeasureHistoryUrl } from '../../../helpers/urls';
interface Props {
export default function HistoryButtonLink({ component, metric }: Props) {
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>
);
}
+++ /dev/null
-/*
- * 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 { 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';
interface Props {
export default function MeasuresButtonLink({ component, metric }: Props) {
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>
);
}
--- /dev/null
+/*
+ * 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>
+ );
+}
+++ /dev/null
-/*
- * 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>
- );
-}
+++ /dev/null
-/*
- * 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.
*/
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 { translate } from '../../../helpers/l10n';
this.mounted = false;
}
- loadStatus() {
+ loadStatus = () => {
getReportStatus(this.props.component.key).then(
status => {
if (this.mounted) {
}
}
);
- }
-
- renderHeader = () => <h4>{translate('report.page')}</h4>;
+ };
render() {
const { component } = this.props;
const { status, loading } = this.state;
- if (loading) {
- return (
- <div>
- {this.renderHeader()}
- <i className="spinner" />
- </div>
- );
- }
-
- if (!status) {
+ if (loading || !status) {
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
- className="button js-report-download"
download={component.name + ' - Executive Report.pdf'}
href={getReportUrl(component.key)}
target="_blank">
{translate('report.print')}
</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>
);
}
}
+++ /dev/null
-/*
- * 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.
*/
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 addGlobalSuccessMessage from '../../../app/utils/addGlobalSuccessMessage';
+import throwGlobalError from '../../../app/utils/throwGlobalError';
import { translate, translateWithParameters } from '../../../helpers/l10n';
-import { Button } from '../../../components/ui/buttons';
import { isLoggedIn } from '../../../helpers/users';
+import { getCurrentUser, Store } from '../../../store/rootReducer';
interface Props {
component: string;
currentUser: T.CurrentUser;
+ onSubscribe: () => void;
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) => {
- 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 = () => {
- this.setState({ loading: true });
subscribe(this.props.component)
.then(() => this.handleSubscription(true))
- .catch(this.stopLoading);
+ .catch(throwGlobalError);
};
handleUnsubscribe = () => {
- this.setState({ loading: true });
unsubscribe(this.props.component)
.then(() => this.handleSubscription(false))
- .catch(this.stopLoading);
+ .catch(throwGlobalError);
};
- getEffectiveFrequencyText = () => {
+ getFrequencyText = () => {
const effectiveFrequency =
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() {
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);
+++ /dev/null
-/*
- * 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);
+++ /dev/null
-/*
- * 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')}
</th>
<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 className="text-center portfolio-sub-components-cell">
{translate('metric_domain.Maintainability')}
: renderCell(component.measures, 'releasability_rating', 'RATING')}
{renderCell(component.measures, 'reliability_rating', 'RATING')}
{renderCell(component.measures, 'security_rating', 'RATING')}
+ {renderCell(component.measures, 'security_review_rating', 'RATING')}
{renderCell(component.measures, 'sqale_rating', 'RATING')}
{renderNcloc(component.measures, maxLoc)}
</tr>
+++ /dev/null
-/*
- * 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 } }))
}));
-// 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 { shallow, mount } from 'enzyme';
import { App } from '../App';
expect(getMeasures).toBeCalledWith({
component: 'foo',
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(
'foo',
'ncloc',
'releasability_rating',
'security_rating',
+ 'security_review_rating',
'reliability_rating',
'sqale_rating',
'alert_status'
+++ /dev/null
-/*
- * 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();
-});
--- /dev/null
+/*
+ * 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 MetricBox from '../MetricBox';
+
+it('should render correctly', () => {
+ const measures = {
+ reliability_rating: '3',
+ last_change_on_reliability_rating: '{"date":"2017-01-02T00:00:00.000Z","value":2}',
+ reliability_rating_effort: '{"rating":3,"projects":1}'
+ };
+ expect(
+ shallow(<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();
+});
+++ /dev/null
-/*
- * 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();
-});
+++ /dev/null
-/*
- * 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 ReliabilityBox from '../ReliabilityBox';
-
-it('renders', () => {
- const measures = {
- reliability_rating: '3',
- last_change_on_reliability_rating: '{"date":"2017-01-02T00:00:00.000Z","value":2}',
- reliability_rating_effort: '{"rating":3,"projects":1}'
- };
- expect(shallow(<ReliabilityBox component="foo" measures={measures} />)).toMatchSnapshot();
-});
+++ /dev/null
-/*
- * 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 { mount, shallow } from 'enzyme';
-import Subscription from '../Subscription';
+import { shallow, mount } from 'enzyme';
+import { Subscription } from '../Subscription';
import { click, waitAndUpdate } from '../../../../helpers/testUtils';
+import { ReportStatus } from '../../../../api/report';
const subscribe = require('../../../../api/report').subscribe as jest.Mock<any>;
const unsubscribe = require('../../../../api/report').unsubscribe as jest.Mock<any>;
-const status = {
- canDownload: true,
- canSubscribe: true,
- componentFrequency: 'montly',
- globalFrequency: 'weekly',
- subscribed: true
-};
-
-const currentUser = { isLoggedIn: true, email: 'foo@example.com' };
-
beforeEach(() => {
subscribe.mockClear();
unsubscribe.mockClear();
});
it('renders when subscribed', () => {
- expect(
- shallow(<Subscription component="foo" currentUser={currentUser} status={status} />)
- ).toMatchSnapshot();
+ expect(shallowRender()).toMatchSnapshot();
});
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', () => {
- expect(
- shallow(<Subscription component="foo" currentUser={{ isLoggedIn: false }} status={status} />)
- ).toMatchSnapshot();
+ expect(shallowRender({ currentUser: { isLoggedIn: false } })).toMatchSnapshot();
});
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');
+ wrapper.setProps({ status: { ...status, subscribed: false } });
await waitAndUpdate(wrapper);
- click(wrapper.find('button'));
+ click(wrapper.find('a'));
expect(subscribe).toBeCalledWith('foo');
});
+
+function shallowRender(
+ props: Partial<Subscription['props']> = {},
+ statusOverrides: Partial<ReportStatus> = {}
+) {
+ const status = {
+ canDownload: true,
+ canSubscribe: true,
+ componentFrequency: 'montly',
+ globalFrequency: 'weekly',
+ subscribed: true,
+ ...statusOverrides
+ };
+
+ const currentUser = { isLoggedIn: true, email: 'foo@example.com' };
+
+ return shallow<Subscription>(
+ <Subscription
+ component="foo"
+ currentUser={currentUser}
+ onSubscribe={jest.fn()}
+ status={status}
+ {...props}
+ />
+ );
+}
+++ /dev/null
-/*
- * 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 Summary from '../Summary';
-
-it('renders', () => {
- expect(
- shallow(
- <Summary
- component={{ description: 'blabla', key: 'foo' }}
- measures={{ ncloc: '1234', ncloc_language_distribution: 'java=13;js=17', projects: '15' }}
- />
- )
- ).toMatchSnapshot();
-});
+++ /dev/null
-// 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`] = `
<div
- className="page page-limited"
+ className="page page-limited portfolio-overview"
>
<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
- 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"
- measures={
- Object {
- "ncloc": "173",
- "reliability_rating": "1",
- }
- }
+ metric="projects"
/>
</div>
- <WorstProjects
- component="foo"
- subComponents={Array []}
- total={0}
- />
</div>
</div>
- <aside
- className="page-sidebar-fixed"
+ <div
+ className="portfolio-breakdown-box"
>
+ <h2>
+ portfolio.number_of_lines
+ </h2>
<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
- 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>
- </aside>
+ </div>
</div>
+ <WorstProjects
+ component="foo"
+ subComponents={Array []}
+ total={0}
+ />
</div>
`;
exports[`renders when portfolio is empty 1`] = `
<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>
`;
exports[`renders when portfolio is not computed 1`] = `
<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>
`;
metricType="SHORT_INT"
value="3"
/>
- projects_
+ project_plural
</span>
</Link>,
"rating": <Rating
exports[`renders 1`] = `
<Link
- className="button button-small spacer-left text-text-bottom"
onlyActiveOnIndex={false}
style={Object {}}
to={
}
>
<HistoryIcon
+ className="little-spacer-right"
size={14}
/>
+ <span>
+ portfolio.activity_link
+ </span>
</Link>
`;
+++ /dev/null
-// 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`] = `
<Link
- className="button button-small spacer-left text-text-bottom"
onlyActiveOnIndex={false}
style={Object {}}
to={
}
}
>
- <BubblesIcon
+ <MeasuresIcon
+ className="little-spacer-right"
size={14}
/>
+ <span>
+ portfolio.measures_link
+ </span>
</Link>
`;
--- /dev/null
+// 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>
+`;
+++ /dev/null
-// 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>
-`;
+++ /dev/null
-// 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
-exports[`renders 1`] = `
-<div>
- <h4>
- report.page
- </h4>
- <i
- className="spinner"
- />
-</div>
-`;
+exports[`renders 1`] = `""`;
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>
`;
+++ /dev/null
-// 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
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`] = `
-<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`] = `
-<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>
`;
+++ /dev/null
-// 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
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
className="text-center portfolio-sub-components-cell"
value="1"
/>
</td>
+ <td
+ className="text-center"
+ >
+ <Measure
+ metricKey="security_review_rating"
+ metricType="RATING"
+ />
+ </td>
<td
className="text-center"
>
value="1"
/>
</td>
+ <td
+ className="text-center"
+ >
+ <Measure
+ metricKey="security_review_rating"
+ metricType="RATING"
+ />
+ </td>
<td
className="text-center"
>
value="1"
/>
</td>
+ <td
+ className="text-center"
+ >
+ <Measure
+ metricKey="security_review_rating"
+ metricType="RATING"
+ />
+ </td>
<td
className="text-center"
>
* along with this program; if not, write to the Free Software Foundation,
* 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 {
line-height: var(--controlHeight);
font-size: 18px;
.portfolio-freshness {
line-height: var(--controlHeight);
- margin-top: 12px;
- color: var(--secondFontColor);
font-size: var(--smallFontSize);
white-space: nowrap;
}
-.portfolio-effort {
- margin-top: 12px;
- padding-top: 12px;
- border-top: 1px solid var(--barBorderColor);
-}
-
.portfolio-boxes {
display: flex;
justify-content: space-between;
align-items: stretch;
margin-bottom: 20px;
padding: 15px 0;
- border: 1px solid var(--barBorderColor);
- background-color: #fff;
+ width: 100%;
}
.portfolio-box {
+ flex: 1 0 10%;
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;
- text-align: center;
+}
+
+.portfolio-box:first-child {
+ margin-left: 0;
+}
+
+.portfolio-box:last-child {
+ margin-right: 0;
}
.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);
+ line-height: var(--bigFontSize);
+ border-bottom: 1px solid var(--barBorderColor);
+ white-space: nowrap;
}
.portfolio-box-title > .button-small > svg {
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 .rating {
display: block;
- width: 120px;
- height: 120px;
- line-height: 120px;
+ width: 80px;
+ height: 80px;
+ line-height: 80px;
}
.portfolio-box-rating {
- margin: 0 auto;
+ margin: calc(2 * var(--gridSize)) auto;
border: none;
}
.portfolio-box-rating .rating {
- border-radius: 120px;
- font-size: 60px;
+ border-radius: 80px;
+ font-size: 48px;
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 {
font-size: var(--baseFontSize);
text-transform: none;
+ vertical-align: middle;
}
.portfolio-sub-components-cell {
- width: 90px;
+ width: 110px;
}
.portfolio-meta-header {
'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'
];
+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 = [
'ncloc',
'releasability_rating',
'security_rating',
+ 'security_review_rating',
'reliability_rating',
'sqale_rating',
'alert_status'
--- /dev/null
+/*
+ * 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 Icon, { IconProps } from './Icon';
+
+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
projects=Projects
projects_=project(s)
+project_singular=project
+project_plural=projects
projects_management=Projects Management
quality_profile=Quality Profile
raw=Raw
portfolio.not_computed=This portfolio is not yet computed.
portfolio.app.empty=This application is 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.
#------------------------------------------------------------------------------
#