metricKeys: metrics.join(','),
strategy
});
- return getJSON(url, data);
+ return getJSON(url, data).catch(throwGlobalError);
}
export function getChildren(
componentKey: string,
metrics: string[] = [],
additional: RequestData = {}
-): Promise<any> {
+) {
return getComponentTree('children', componentKey, metrics, additional);
}
componentKey: string,
metrics: string[] = [],
additional: RequestData = {}
-): Promise<any> {
+) {
return getComponentTree('leaves', componentKey, metrics, additional);
}
export function getComponent(
data: { componentKey: string; metricKeys: string } & BranchParameters
): Promise<any> {
- return getJSON('/api/measures/component', data).then(r => r.component);
+ return getJSON('/api/measures/component', data).then(r => r.component, throwGlobalError);
}
export interface TreeComponent extends LightComponent {
}
export function getComponentShow(data: { component: string } & BranchParameters): Promise<any> {
- return getJSON('/api/components/show', data);
+ return getJSON('/api/components/show', data).catch(throwGlobalError);
}
export function getParents(component: string): Promise<any> {
import { getJSON, RequestData, postJSON, post } from '../helpers/request';
import throwGlobalError from '../app/utils/throwGlobalError';
import {
- Metric,
CustomMeasure,
- Paging,
BranchParameters,
Measure,
- MeasurePeriod
+ Metric,
+ Paging,
+ Period,
+ PeriodMeasure
} from '../app/types';
-import { Period } from '../helpers/periods';
export function getMeasures(
data: { componentKey: string; metricKeys: string } & BranchParameters
interface MeasuresForProjects {
component: string;
metric: string;
- periods?: MeasurePeriod[];
+ periods?: PeriodMeasure[];
value?: string;
}
}
renderComponentMeasuresLink() {
- const { branchLike } = this.props;
-
- if (isShortLivingBranch(branchLike) || isPullRequest(branchLike)) {
- return null;
- }
-
return (
<li>
<Link
issues.page
</Link>
</li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/component_measures",
+ "query": Object {
+ "branch": "feature",
+ "id": "foo",
+ },
+ }
+ }
+ >
+ layout.measures
+ </Link>
+ </li>
<li>
<Link
activeClassName="active"
}
.modal-foot {
- line-height: var(--controlHeight);
padding: 10px;
border-top: 1px solid #ccc;
background-color: var(--gray94);
}
interface ComponentMeasureIntern {
+ branch?: string;
isFavorite?: boolean;
isRecentlyBrowsed?: boolean;
key: string;
match?: string;
name: string;
organization?: string;
+ path?: string;
project?: string;
qualifier: string;
refKey?: string;
status?: { qualityGateStatus: string };
}
-export interface MeasurePeriod {
- bestValue?: boolean;
- index: number;
- value: string;
-}
-
-interface MeasureIntern {
- bestValue?: boolean;
- periods?: MeasurePeriod[];
- value?: string;
-}
-
export interface Measure extends MeasureIntern {
metric: string;
}
leak?: string;
}
+interface MeasureIntern {
+ bestValue?: boolean;
+ periods?: PeriodMeasure[];
+ value?: string;
+}
+
export interface Metric {
bestValue?: string;
custom?: boolean;
total: number;
}
+export interface Period {
+ date: string;
+ index: number;
+ mode: PeriodMode;
+ modeParam?: string;
+ parameter?: string;
+}
+
+export interface PeriodMeasure {
+ bestValue?: boolean;
+ index: number;
+ value: string;
+}
+
+export enum PeriodMode {
+ Days = 'days',
+ Date = 'date',
+ Version = 'version',
+ PreviousAnalysis = 'previous_analysis',
+ PreviousVersion = 'previous_version'
+}
+
export interface PermissionTemplate {
defaultFor: string[];
id: string;
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.
- */
-// @flow
-import React from 'react';
-import Helmet from 'react-helmet';
-import key from 'keymaster';
-import MeasureContentContainer from './MeasureContentContainer';
-import MeasureOverviewContainer from './MeasureOverviewContainer';
-import Sidebar from '../sidebar/Sidebar';
-import ScreenPositionHelper from '../../../components/common/ScreenPositionHelper';
-import { isProjectOverview, hasBubbleChart, parseQuery, serializeQuery } from '../utils';
-import { isSameBranchLike, getBranchLikeQuery } from '../../../helpers/branches';
-import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
-import {
- getLocalizedMetricDomain,
- translateWithParameters,
- translate
-} from '../../../helpers/l10n';
-import { getDisplayMetrics } from '../../../helpers/measures';
-/*:: import type { Component, Query, Period } from '../types'; */
-/*:: import type { RawQuery } from '../../../helpers/query'; */
-/*:: import type { Metric } from '../../../app/flow-types'; */
-/*:: import type { MeasureEnhanced } from '../../../components/measure/types'; */
-import '../../../components/search-navigator.css';
-import '../style.css';
-
-/*:: type Props = {|
- branchLike?: { id?: string; name: string },
- component: Component,
- currentUser: { isLoggedIn: boolean },
- location: { pathname: string, query: RawQuery },
- fetchMeasures: (
- component: string,
- metricsKey: Array<string>,
- branchLike?: { id?: string; name: string }
- ) => Promise<{ component: Component, measures: Array<MeasureEnhanced>, leakPeriod: ?Period }>,
- fetchMetrics: () => void,
- metrics: { [string]: Metric },
- metricsKey: Array<string>,
- router: {
- push: ({ pathname: string, query?: RawQuery }) => void
- }
-|}; */
-
-/*:: type State = {|
- loading: boolean,
- measures: Array<MeasureEnhanced>,
- leakPeriod: ?Period
-|}; */
-
-export default class App extends React.PureComponent {
- /*:: mounted: boolean; */
- /*:: props: Props; */
- /*:: state: State; */
-
- constructor(props /*: Props */) {
- super(props);
- this.state = {
- loading: true,
- measures: [],
- leakPeriod: null
- };
- }
-
- componentDidMount() {
- this.mounted = true;
- // $FlowFixMe
- document.body.classList.add('white-page');
- // $FlowFixMe
- document.documentElement.classList.add('white-page');
- this.props.fetchMetrics();
- this.fetchMeasures(this.props);
- key.setScope('measures-files');
- const footer = document.getElementById('footer');
- if (footer) {
- footer.classList.add('page-footer-with-sidebar');
- }
- }
-
- componentWillReceiveProps(nextProps /*: Props */) {
- if (
- !isSameBranchLike(nextProps.branchLike, this.props.branchLike) ||
- nextProps.component.key !== this.props.component.key ||
- nextProps.metrics !== this.props.metrics
- ) {
- this.fetchMeasures(nextProps);
- }
- }
-
- componentWillUnmount() {
- this.mounted = false;
- // $FlowFixMe
- document.body.classList.remove('white-page');
- // $FlowFixMe
- document.documentElement.classList.remove('white-page');
- key.deleteScope('measures-files');
- const footer = document.getElementById('footer');
- if (footer) {
- footer.classList.remove('page-footer-with-sidebar');
- }
- }
-
- fetchMeasures = ({ branchLike, component, fetchMeasures, metrics } /*: Props */) => {
- this.setState({ loading: true });
- const filteredKeys = getDisplayMetrics(Object.values(metrics)).map(metric => metric.key);
- fetchMeasures(component.key, filteredKeys, branchLike).then(
- ({ measures, leakPeriod }) => {
- if (this.mounted) {
- this.setState({
- loading: false,
- leakPeriod,
- measures: measures.filter(measure => measure.value != null || measure.leak != null)
- });
- }
- },
- () => {
- if (this.mounted) {
- this.setState({ loading: false });
- }
- }
- );
- };
-
- updateQuery = (newQuery /*: Query */) => {
- const query = serializeQuery({
- ...parseQuery(this.props.location.query),
- ...newQuery
- });
- this.props.router.push({
- pathname: this.props.location.pathname,
- query: {
- ...query,
- ...getBranchLikeQuery(this.props.branchLike),
- id: this.props.component.key
- }
- });
- };
-
- getHelmetTitle = (
- metric /*: Metric */,
- query /*: {metric: string, selected: string, view: string }*/
- ) => {
- if (metric == null && hasBubbleChart(query.metric)) {
- return isProjectOverview(query.metric)
- ? translate('component_measures.overview.project_overview.facet')
- : translateWithParameters(
- 'component_measures.domain_x_overview',
- getLocalizedMetricDomain(query.metric)
- );
- }
- return metric != null ? metric.name : translate('layout.measures');
- };
-
- render() {
- const isLoading = this.state.loading || this.props.metricsKey.length <= 0;
- if (isLoading) {
- return <i className="spinner spinner-margin" />;
- }
- const { branchLike, component, fetchMeasures, metrics } = this.props;
- const { leakPeriod } = this.state;
- const query = parseQuery(this.props.location.query);
- const metric = metrics[query.metric];
- return (
- <div className="layout-page" id="component-measures">
- <Suggestions suggestions="component_measures" />
- <Helmet title={this.getHelmetTitle(metric, query)} />
-
- <ScreenPositionHelper className="layout-page-side-outer">
- {({ top }) => (
- <div className="layout-page-side" style={{ top }}>
- <div className="layout-page-side-inner">
- <div className="layout-page-filters">
- <Sidebar
- measures={this.state.measures}
- selectedMetric={query.metric}
- updateQuery={this.updateQuery}
- />
- </div>
- </div>
- </div>
- )}
- </ScreenPositionHelper>
-
- {metric != null && (
- <MeasureContentContainer
- branchLike={branchLike}
- className="layout-page-main"
- currentUser={this.props.currentUser}
- fetchMeasures={fetchMeasures}
- leakPeriod={leakPeriod}
- metric={metric}
- metrics={metrics}
- rootComponent={component}
- router={this.props.router}
- selected={query.selected}
- updateQuery={this.updateQuery}
- view={query.view}
- />
- )}
- {metric == null &&
- hasBubbleChart(query.metric) && (
- <MeasureOverviewContainer
- branchLike={branchLike}
- className="layout-page-main"
- currentUser={this.props.currentUser}
- domain={query.metric}
- leakPeriod={leakPeriod}
- metrics={metrics}
- rootComponent={component}
- router={this.props.router}
- selected={query.selected}
- updateQuery={this.updateQuery}
- />
- )}
- </div>
- );
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 * as key from 'keymaster';
+import { InjectedRouter } from 'react-router';
+import Helmet from 'react-helmet';
+import MeasureContentContainer from './MeasureContentContainer';
+import MeasureOverviewContainer from './MeasureOverviewContainer';
+import Sidebar from '../sidebar/Sidebar';
+import ScreenPositionHelper from '../../../components/common/ScreenPositionHelper';
+import { isProjectOverview, hasBubbleChart, parseQuery, serializeQuery, Query } from '../utils';
+import { isSameBranchLike, getBranchLikeQuery } from '../../../helpers/branches';
+import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
+import {
+ getLocalizedMetricDomain,
+ translateWithParameters,
+ translate
+} from '../../../helpers/l10n';
+import { getDisplayMetrics } from '../../../helpers/measures';
+import { RawQuery } from '../../../helpers/query';
+import {
+ BranchLike,
+ ComponentMeasure,
+ MeasureEnhanced,
+ Metric,
+ CurrentUser,
+ Period
+} from '../../../app/types';
+import '../../../components/search-navigator.css';
+import '../style.css';
+
+interface Props {
+ branchLike?: BranchLike;
+ component: ComponentMeasure;
+ currentUser: CurrentUser;
+ location: { pathname: string; query: RawQuery };
+ fetchMeasures: (
+ component: string,
+ metricsKey: string[],
+ branchLike?: BranchLike
+ ) => Promise<{ component: ComponentMeasure; measures: MeasureEnhanced[]; leakPeriod?: Period }>;
+ fetchMetrics: () => void;
+ metrics: { [metric: string]: Metric };
+ metricsKey: string[];
+ router: InjectedRouter;
+}
+
+interface State {
+ loading: boolean;
+ measures: MeasureEnhanced[];
+ leakPeriod?: Period;
+}
+
+export default class App extends React.PureComponent<Props, State> {
+ mounted = false;
+
+ constructor(props: Props) {
+ super(props);
+ this.state = { loading: true, measures: [] };
+ }
+
+ componentDidMount() {
+ this.mounted = true;
+
+ document.body.classList.add('white-page');
+ document.documentElement.classList.add('white-page');
+ const footer = document.getElementById('footer');
+ if (footer) {
+ footer.classList.add('page-footer-with-sidebar');
+ }
+
+ key.setScope('measures-files');
+ this.props.fetchMetrics();
+ this.fetchMeasures(this.props);
+ }
+
+ componentWillReceiveProps(nextProps: Props) {
+ if (
+ !isSameBranchLike(nextProps.branchLike, this.props.branchLike) ||
+ nextProps.component.key !== this.props.component.key ||
+ nextProps.metrics !== this.props.metrics
+ ) {
+ this.fetchMeasures(nextProps);
+ }
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+
+ document.body.classList.remove('white-page');
+ document.documentElement.classList.remove('white-page');
+
+ const footer = document.getElementById('footer');
+ if (footer) {
+ footer.classList.remove('page-footer-with-sidebar');
+ }
+
+ key.deleteScope('measures-files');
+ }
+
+ fetchMeasures = ({ branchLike, component, fetchMeasures, metrics }: Props) => {
+ this.setState({ loading: true });
+ const filteredKeys = getDisplayMetrics(Object.values(metrics)).map(metric => metric.key);
+
+ fetchMeasures(component.key, filteredKeys, branchLike).then(
+ ({ measures, leakPeriod }) => {
+ if (this.mounted) {
+ this.setState({
+ loading: false,
+ leakPeriod,
+ measures: measures.filter(
+ measure => measure.value !== undefined || measure.leak !== undefined
+ )
+ });
+ }
+ },
+ () => {
+ if (this.mounted) {
+ this.setState({ loading: false });
+ }
+ }
+ );
+ };
+
+ updateQuery = (newQuery: Partial<Query>) => {
+ const query = serializeQuery({
+ ...parseQuery(this.props.location.query),
+ ...newQuery
+ });
+ this.props.router.push({
+ pathname: this.props.location.pathname,
+ query: {
+ ...query,
+ ...getBranchLikeQuery(this.props.branchLike),
+ id: this.props.component.key
+ }
+ });
+ };
+
+ getHelmetTitle = (metric?: Metric) => {
+ if (metric && hasBubbleChart(metric.key)) {
+ return isProjectOverview(metric.key)
+ ? translate('component_measures.overview.project_overview.facet')
+ : translateWithParameters(
+ 'component_measures.domain_x_overview',
+ getLocalizedMetricDomain(metric.key)
+ );
+ }
+ return metric ? metric.name : translate('layout.measures');
+ };
+
+ render() {
+ const isLoading = this.state.loading || this.props.metricsKey.length <= 0;
+ if (isLoading) {
+ return <i className="spinner spinner-margin" />;
+ }
+ const { branchLike, component, fetchMeasures, metrics } = this.props;
+ const { leakPeriod } = this.state;
+ const query = parseQuery(this.props.location.query);
+ const metric = metrics[query.metric];
+ return (
+ <div className="layout-page" id="component-measures">
+ <Suggestions suggestions="component_measures" />
+ <Helmet title={this.getHelmetTitle(metric)} />
+
+ <ScreenPositionHelper className="layout-page-side-outer">
+ {({ top }) => (
+ <div className="layout-page-side" style={{ top }}>
+ <div className="layout-page-side-inner">
+ <div className="layout-page-filters">
+ <Sidebar
+ measures={this.state.measures}
+ selectedMetric={query.metric}
+ updateQuery={this.updateQuery}
+ />
+ </div>
+ </div>
+ </div>
+ )}
+ </ScreenPositionHelper>
+
+ {metric && (
+ <MeasureContentContainer
+ branchLike={branchLike}
+ className="layout-page-main"
+ currentUser={this.props.currentUser}
+ fetchMeasures={fetchMeasures}
+ leakPeriod={leakPeriod}
+ metric={metric}
+ metrics={metrics}
+ rootComponent={component}
+ router={this.props.router}
+ selected={query.selected}
+ updateQuery={this.updateQuery}
+ view={query.view}
+ />
+ )}
+ {!metric &&
+ hasBubbleChart(query.metric) && (
+ <MeasureOverviewContainer
+ branchLike={branchLike}
+ className="layout-page-main"
+ currentUser={this.props.currentUser}
+ domain={query.metric}
+ leakPeriod={leakPeriod}
+ metrics={metrics}
+ rootComponent={component}
+ router={this.props.router}
+ selected={query.selected}
+ updateQuery={this.updateQuery}
+ />
+ )}
+ </div>
+ );
+ }
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.
- */
-// @flow
-import { connect } from 'react-redux';
-import { withRouter } from 'react-router';
-import App from './App';
-import throwGlobalError from '../../../app/utils/throwGlobalError';
-import { getCurrentUser, getMetrics, getMetricsKey } from '../../../store/rootReducer';
-import { fetchMetrics } from '../../../store/rootActions';
-import { getMeasuresAndMeta } from '../../../api/measures';
-import { getLeakPeriod } from '../../../helpers/periods';
-import { enhanceMeasure } from '../../../components/measure/utils';
-import { getBranchLikeQuery } from '../../../helpers/branches';
-/*:: import type { Component, Period } from '../types'; */
-/*:: import type { Measure, MeasureEnhanced } from '../../../components/measure/types'; */
-
-const mapStateToProps = state => ({
- currentUser: getCurrentUser(state),
- metrics: getMetrics(state),
- metricsKey: getMetricsKey(state)
-});
-
-function banQualityGate(component /*: Component */) /*: Array<Measure> */ {
- const bannedMetrics = [];
- if (!['VW', 'SVW'].includes(component.qualifier)) {
- bannedMetrics.push('alert_status');
- }
- if (component.qualifier === 'APP') {
- bannedMetrics.push('releasability_rating', 'releasability_effort');
- }
- return component.measures.filter(measure => !bannedMetrics.includes(measure.metric));
-}
-
-const fetchMeasures = (
- component /*: string */,
- metricsKey /*: Array<string> */,
- branchLike /*: { id?: string; name: string } | void */
-) => (dispatch, getState) => {
- if (metricsKey.length <= 0) {
- return Promise.resolve({ component: {}, measures: [], leakPeriod: null });
- }
-
- return getMeasuresAndMeta(component, metricsKey, {
- additionalFields: 'periods',
- ...getBranchLikeQuery(branchLike)
- }).then(r => {
- const measures = banQualityGate(r.component).map(measure =>
- enhanceMeasure(measure, getMetrics(getState()))
- );
-
- const newBugs = measures.find(measure => measure.metric.key === 'new_bugs');
- const applicationPeriods = newBugs ? [{ index: 1 }] : [];
- const periods = r.component.qualifier === 'APP' ? applicationPeriods : r.periods;
- return { component: r.component, measures, leakPeriod: getLeakPeriod(periods) };
- }, throwGlobalError);
-};
-
-const mapDispatchToProps = { fetchMeasures, fetchMetrics };
-
-export default connect(
- mapStateToProps,
- mapDispatchToProps
-)(withRouter(App));
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 { Dispatch } from 'redux';
+import { connect } from 'react-redux';
+import { withRouter, WithRouterProps } from 'react-router';
+import App from './App';
+import throwGlobalError from '../../../app/utils/throwGlobalError';
+import { getCurrentUser, getMetrics, getMetricsKey } from '../../../store/rootReducer';
+import { fetchMetrics } from '../../../store/rootActions';
+import { getMeasuresAndMeta } from '../../../api/measures';
+import { getLeakPeriod } from '../../../helpers/periods';
+import { enhanceMeasure } from '../../../components/measure/utils';
+import { getBranchLikeQuery } from '../../../helpers/branches';
+import {
+ BranchLike,
+ ComponentMeasure,
+ CurrentUser,
+ Measure,
+ MeasureEnhanced,
+ Metric,
+ Period
+} from '../../../app/types';
+
+interface StateToProps {
+ currentUser: CurrentUser;
+ metrics: { [metric: string]: Metric };
+ metricsKey: string[];
+}
+
+interface DispatchToProps {
+ fetchMeasures: (
+ component: string,
+ metricsKey: string[],
+ branchLike?: BranchLike
+ ) => Promise<{ component: ComponentMeasure; measures: MeasureEnhanced[]; leakPeriod?: Period }>;
+ fetchMetrics: () => void;
+}
+
+interface OwnProps {
+ branchLike?: BranchLike;
+ component: ComponentMeasure;
+}
+
+const mapStateToProps = (state: any): StateToProps => ({
+ currentUser: getCurrentUser(state),
+ metrics: getMetrics(state),
+ metricsKey: getMetricsKey(state)
+});
+
+function banQualityGate({ measures = [], qualifier }: ComponentMeasure): Measure[] {
+ const bannedMetrics: string[] = [];
+ if (!['VW', 'SVW'].includes(qualifier)) {
+ bannedMetrics.push('alert_status');
+ }
+ if (qualifier === 'APP') {
+ bannedMetrics.push('releasability_rating', 'releasability_effort');
+ }
+ return measures.filter(measure => !bannedMetrics.includes(measure.metric));
+}
+
+const fetchMeasures = (component: string, metricsKey: string[], branchLike?: BranchLike) => (
+ _dispatch: Dispatch<any>,
+ getState: () => any
+) => {
+ if (metricsKey.length <= 0) {
+ return Promise.resolve({ component: {}, measures: [], leakPeriod: null });
+ }
+
+ return getMeasuresAndMeta(component, metricsKey, {
+ additionalFields: 'periods',
+ ...getBranchLikeQuery(branchLike)
+ }).then(({ component, periods }) => {
+ const measures = banQualityGate(component).map(measure =>
+ enhanceMeasure(measure, getMetrics(getState()))
+ );
+
+ const newBugs = measures.find(measure => measure.metric.key === 'new_bugs');
+ const applicationPeriods = newBugs ? [{ index: 1 } as Period] : [];
+ const leakPeriod = getLeakPeriod(component.qualifier === 'APP' ? applicationPeriods : periods);
+ return { component, measures, leakPeriod };
+ }, throwGlobalError);
+};
+
+const mapDispatchToProps: DispatchToProps = { fetchMeasures: fetchMeasures as any, fetchMetrics };
+
+export default withRouter<OwnProps>(
+ connect<StateToProps, DispatchToProps, OwnProps & WithRouterProps>(
+ mapStateToProps,
+ mapDispatchToProps
+ )(App)
+);
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.
- */
-// @flow
-import React from 'react';
-import { translate } from '../../../helpers/l10n';
-import { formatMeasure } from '../../../helpers/measures';
-
-/*:: type Props = {
- className?: string,
- current: ?number,
- total: number
-}; */
-
-export default function FilesCounter({ className, current, total } /*: Props */) {
- return (
- <span className={className}>
- <strong>
- {current != null && (
- <span>
- {formatMeasure(current, 'INT')}
- {' / '}
- </span>
- )}
- {formatMeasure(total, 'INT')}
- </strong>{' '}
- {translate('component_measures.files')}
- </span>
- );
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { translate } from '../../../helpers/l10n';
+import { formatMeasure } from '../../../helpers/measures';
+
+interface Props {
+ className?: string;
+ current?: number;
+ total: number;
+}
+
+export default function FilesCounter({ className, current, total }: Props) {
+ return (
+ <span className={className}>
+ <strong>
+ {current !== undefined && (
+ <span>
+ {formatMeasure(current, 'INT')}
+ {' / '}
+ </span>
+ )}
+ {formatMeasure(total, 'INT')}
+ </strong>{' '}
+ {translate('component_measures.files')}
+ </span>
+ );
+}
import DateFormatter, { longFormatterOption } from '../../../components/intl/DateFormatter';
import DateTimeFormatter from '../../../components/intl/DateTimeFormatter';
import Tooltip from '../../../components/controls/Tooltip';
-import { getPeriodLabel, getPeriodDate, Period, PeriodMode } from '../../../helpers/periods';
+import { getPeriodLabel, getPeriodDate } from '../../../helpers/periods';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { differenceInDays } from '../../../helpers/dates';
-import { ComponentMeasure } from '../../../app/types';
+import { ComponentMeasure, Period, PeriodMode } from '../../../app/types';
interface Props {
className?: string;
isLoggedIn,
Metric,
Paging,
- MeasureEnhanced
+ MeasureEnhanced,
+ Period
} from '../../../app/types';
import { RequestData } from '../../../helpers/request';
-import { Period } from '../../../helpers/periods';
interface Props {
branchLike?: BranchLike;
<MeasureHeader
branchLike={branchLike}
component={component}
- components={this.state.components}
leakPeriod={this.props.leakPeriod}
- // fall back to `undefined` to be compatible with typescript files where we compare with `=== undefined`
- measure={measure || undefined}
+ measure={measure}
metric={metric}
secondaryMeasure={this.props.secondaryMeasure}
/>
Metric,
BranchLike,
CurrentUser,
- MeasureEnhanced
+ MeasureEnhanced,
+ Period
} from '../../../app/types';
-import { Period } from '../../../helpers/periods';
interface Props {
branchLike?: BranchLike;
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.
- */
-// @flow
-import React from 'react';
-import { Link } from 'react-router';
-import LeakPeriodLegend from './LeakPeriodLegend';
-import HistoryIcon from '../../../components/icons-components/HistoryIcon';
-import IssueTypeIcon from '../../../components/ui/IssueTypeIcon';
-import LanguageDistributionContainer from '../../../components/charts/LanguageDistributionContainer';
-import Measure from '../../../components/measure/Measure';
-import Tooltip from '../../../components/controls/Tooltip';
-import { getLocalizedMetricName, translate } from '../../../helpers/l10n';
-import { getMeasureHistoryUrl } from '../../../helpers/urls';
-import { isDiffMetric } from '../../../helpers/measures';
-/*:: import type { Component, Period } from '../types'; */
-/*:: import type { MeasureEnhanced } from '../../../components/measure/types'; */
-/*:: import type { Metric } from '../../../app/flow-types'; */
-
-/*:: type Props = {|
- branchLike?: { id?: string; name: string },
- component: Component,
- components: Array<Component>,
- leakPeriod?: Period,
- measure?: MeasureEnhanced,
- metric: Metric,
- secondaryMeasure: ?MeasureEnhanced
-|}; */
-
-export default function MeasureHeader(props /*: Props*/) {
- const { branchLike, component, leakPeriod, measure, metric, secondaryMeasure } = props;
- const isDiff = isDiffMetric(metric.key);
- const hasHistory = component.qualifier !== 'FIL' && component.qualifier !== 'UTS';
- return (
- <div className="measure-details-header big-spacer-bottom">
- <div className="measure-details-primary">
- <div className="measure-details-metric">
- <IssueTypeIcon className="little-spacer-right text-text-bottom" query={metric.key} />
- {getLocalizedMetricName(metric)}
- <span className="measure-details-value spacer-left">
- <strong>
- {isDiff ? (
- <Measure
- className="domain-measures-leak"
- metricKey={metric.key}
- metricType={metric.type}
- value={measure && measure.leak}
- />
- ) : (
- <Measure
- metricKey={metric.key}
- metricType={metric.type}
- value={measure && measure.value}
- />
- )}
- </strong>
- </span>
- {!isDiff &&
- hasHistory && (
- <Tooltip overlay={translate('component_measures.show_metric_history')}>
- <Link
- className="js-show-history spacer-left button button-small"
- to={getMeasureHistoryUrl(component.key, metric.key, branchLike)}>
- <HistoryIcon />
- </Link>
- </Tooltip>
- )}
- </div>
- <div className="measure-details-primary-actions">
- {leakPeriod != null && (
- <LeakPeriodLegend className="spacer-left" component={component} period={leakPeriod} />
- )}
- </div>
- </div>
- {secondaryMeasure &&
- secondaryMeasure.metric.key === 'ncloc_language_distribution' && (
- <div className="measure-details-secondary">
- <LanguageDistributionContainer
- alignTicks={true}
- distribution={secondaryMeasure.value}
- width={260}
- />
- </div>
- )}
- </div>
- );
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 LeakPeriodLegend from './LeakPeriodLegend';
+import HistoryIcon from '../../../components/icons-components/HistoryIcon';
+import IssueTypeIcon from '../../../components/ui/IssueTypeIcon';
+import LanguageDistributionContainer from '../../../components/charts/LanguageDistributionContainer';
+import Measure from '../../../components/measure/Measure';
+import Tooltip from '../../../components/controls/Tooltip';
+import { getLocalizedMetricName, translate } from '../../../helpers/l10n';
+import { getMeasureHistoryUrl } from '../../../helpers/urls';
+import { isDiffMetric } from '../../../helpers/measures';
+import { MeasureEnhanced, Metric, ComponentMeasure, BranchLike, Period } from '../../../app/types';
+
+interface Props {
+ branchLike?: BranchLike;
+ component: ComponentMeasure;
+ leakPeriod?: Period;
+ measure?: MeasureEnhanced;
+ metric: Metric;
+ secondaryMeasure?: MeasureEnhanced;
+}
+
+export default function MeasureHeader(props: Props) {
+ const { branchLike, component, leakPeriod, measure, metric, secondaryMeasure } = props;
+ const isDiff = isDiffMetric(metric.key);
+ const hasHistory = component.qualifier !== 'FIL' && component.qualifier !== 'UTS';
+ return (
+ <div className="measure-details-header big-spacer-bottom">
+ <div className="measure-details-primary">
+ <div className="measure-details-metric">
+ <IssueTypeIcon className="little-spacer-right text-text-bottom" query={metric.key} />
+ {getLocalizedMetricName(metric)}
+ <span className="measure-details-value spacer-left">
+ <strong>
+ {isDiff ? (
+ <Measure
+ className="domain-measures-leak"
+ metricKey={metric.key}
+ metricType={metric.type}
+ value={measure && measure.leak}
+ />
+ ) : (
+ <Measure
+ metricKey={metric.key}
+ metricType={metric.type}
+ value={measure && measure.value}
+ />
+ )}
+ </strong>
+ </span>
+ {!isDiff &&
+ hasHistory && (
+ <Tooltip overlay={translate('component_measures.show_metric_history')}>
+ <Link
+ className="js-show-history spacer-left button button-small"
+ to={getMeasureHistoryUrl(component.key, metric.key, branchLike)}>
+ <HistoryIcon />
+ </Link>
+ </Tooltip>
+ )}
+ </div>
+ <div className="measure-details-primary-actions">
+ {leakPeriod && (
+ <LeakPeriodLegend className="spacer-left" component={component} period={leakPeriod} />
+ )}
+ </div>
+ </div>
+ {secondaryMeasure &&
+ secondaryMeasure.metric.key === 'ncloc_language_distribution' &&
+ secondaryMeasure.value !== undefined && (
+ <div className="measure-details-secondary">
+ <LanguageDistributionContainer
+ alignTicks={true}
+ distribution={secondaryMeasure.value}
+ width={260}
+ />
+ </div>
+ )}
+ </div>
+ );
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.
- */
-// @flow
-import React from 'react';
-import Breadcrumbs from './Breadcrumbs';
-import LeakPeriodLegend from './LeakPeriodLegend';
-import MeasureFavoriteContainer from './MeasureFavoriteContainer';
-import PageActions from './PageActions';
-import BubbleChart from '../drilldown/BubbleChart';
-import SourceViewer from '../../../components/SourceViewer/SourceViewer';
-import { getComponentLeaves } from '../../../api/components';
-import { enhanceComponent, getBubbleMetrics, isFileType } from '../utils';
-import { getBranchLikeQuery } from '../../../helpers/branches';
-import DeferredSpinner from '../../../components/common/DeferredSpinner';
-/*:: import type { Component, ComponentEnhanced, Paging, Period } from '../types'; */
-/*:: import type { Metric } from '../../../app/flow-types'; */
-
-/*:: type Props = {|
- branchLike?: { id?: string; name: string },
- className?: string,
- component: Component,
- currentUser: { isLoggedIn: boolean },
- domain: string,
- leakPeriod: Period,
- loading: boolean,
- metrics: { [string]: Metric },
- rootComponent: Component,
- updateLoading: ({ [string]: boolean }) => void,
- updateSelected: string => void
-|}; */
-
-/*:: type State = {
- components: Array<ComponentEnhanced>,
- paging?: Paging
-}; */
-
-const BUBBLES_LIMIT = 500;
-
-export default class MeasureOverview extends React.PureComponent {
- /*:: mounted: boolean; */
- /*:: props: Props; */
- state /*: State */ = {
- components: [],
- paging: null
- };
-
- componentDidMount() {
- this.mounted = true;
- this.fetchComponents(this.props);
- }
-
- componentWillReceiveProps(nextProps /*: Props */) {
- if (
- nextProps.component !== this.props.component ||
- nextProps.metrics !== this.props.metrics ||
- nextProps.domain !== this.props.domain
- ) {
- this.fetchComponents(nextProps);
- }
- }
-
- componentWillUnmount() {
- this.mounted = false;
- }
-
- fetchComponents = (props /*: Props */) => {
- const { branchLike, component, domain, metrics } = props;
- if (isFileType(component)) {
- this.setState({ components: [], paging: null });
- return;
- }
- const { x, y, size, colors } = getBubbleMetrics(domain, metrics);
- const metricsKey = [x.key, y.key, size.key];
- if (colors) {
- metricsKey.push(colors.map(metric => metric.key));
- }
- const options = {
- ...getBranchLikeQuery(branchLike),
- s: 'metric',
- metricSort: size.key,
- asc: false,
- ps: BUBBLES_LIMIT
- };
-
- this.props.updateLoading({ bubbles: true });
- getComponentLeaves(component.key, metricsKey, options).then(
- r => {
- if (domain === this.props.domain) {
- if (this.mounted) {
- this.setState({
- components: r.components.map(component => enhanceComponent(component, null, metrics)),
- paging: r.paging
- });
- }
- this.props.updateLoading({ bubbles: false });
- }
- },
- () => this.props.updateLoading({ bubbles: false })
- );
- };
-
- renderContent() {
- const { branchLike, component } = this.props;
- if (isFileType(component)) {
- return (
- <div className="measure-details-viewer">
- <SourceViewer branchLike={branchLike} component={component.key} />
- </div>
- );
- }
-
- return (
- <BubbleChart
- component={this.props.component}
- components={this.state.components}
- domain={this.props.domain}
- metrics={this.props.metrics}
- updateSelected={this.props.updateSelected}
- />
- );
- }
-
- render() {
- const { branchLike, component, currentUser, leakPeriod, rootComponent } = this.props;
- const isLoggedIn = currentUser && currentUser.isLoggedIn;
- const isFile = isFileType(component);
- return (
- <div className={this.props.className}>
- <div className="layout-page-header-panel layout-page-main-header">
- <div className="layout-page-header-panel-inner layout-page-main-header-inner">
- <div className="layout-page-main-inner">
- <Breadcrumbs
- backToFirst={true}
- branchLike={branchLike}
- className="measure-breadcrumbs spacer-right text-ellipsis"
- component={component}
- handleSelect={this.props.updateSelected}
- rootComponent={rootComponent}
- />
- {component.key !== rootComponent.key &&
- isLoggedIn && (
- <MeasureFavoriteContainer
- className="measure-favorite spacer-right"
- component={component.key}
- />
- )}
- <PageActions
- current={this.state.components.length}
- isFile={isFile}
- paging={this.state.paging}
- />
- </div>
- </div>
- </div>
- <div className="layout-page-main-inner measure-details-content">
- <div className="clearfix big-spacer-bottom">
- {leakPeriod != null && (
- <LeakPeriodLegend className="pull-right" component={component} period={leakPeriod} />
- )}
- </div>
- <DeferredSpinner loading={this.props.loading} />
- {!this.props.loading && this.renderContent()}
- </div>
- </div>
- );
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 Breadcrumbs from './Breadcrumbs';
+import LeakPeriodLegend from './LeakPeriodLegend';
+import MeasureFavoriteContainer from './MeasureFavoriteContainer';
+import PageActions from './PageActions';
+import BubbleChart from '../drilldown/BubbleChart';
+import SourceViewer from '../../../components/SourceViewer/SourceViewer';
+import { getComponentLeaves } from '../../../api/components';
+import { enhanceComponent, getBubbleMetrics, isFileType } from '../utils';
+import { getBranchLikeQuery } from '../../../helpers/branches';
+import DeferredSpinner from '../../../components/common/DeferredSpinner';
+import {
+ BranchLike,
+ ComponentMeasure,
+ ComponentMeasureEnhanced,
+ CurrentUser,
+ Metric,
+ Paging,
+ Period
+} from '../../../app/types';
+
+interface Props {
+ branchLike?: BranchLike;
+ className?: string;
+ component: ComponentMeasure;
+ currentUser: CurrentUser;
+ domain: string;
+ leakPeriod?: Period;
+ loading: boolean;
+ metrics: { [metric: string]: Metric };
+ rootComponent: ComponentMeasure;
+ updateLoading: (param: { [key: string]: boolean }) => void;
+ updateSelected: (component: string) => void;
+}
+
+interface State {
+ components: ComponentMeasureEnhanced[];
+ paging?: Paging;
+}
+
+const BUBBLES_LIMIT = 500;
+
+export default class MeasureOverview extends React.PureComponent<Props, State> {
+ mounted = false;
+ state: State = { components: [] };
+
+ componentDidMount() {
+ this.mounted = true;
+ this.fetchComponents(this.props);
+ }
+
+ componentWillReceiveProps(nextProps: Props) {
+ if (
+ nextProps.component !== this.props.component ||
+ nextProps.metrics !== this.props.metrics ||
+ nextProps.domain !== this.props.domain
+ ) {
+ this.fetchComponents(nextProps);
+ }
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ fetchComponents = (props: Props) => {
+ const { branchLike, component, domain, metrics } = props;
+ if (isFileType(component)) {
+ this.setState({ components: [], paging: undefined });
+ return;
+ }
+ const { x, y, size, colors } = getBubbleMetrics(domain, metrics);
+ const metricsKey = [x.key, y.key, size.key];
+ if (colors) {
+ metricsKey.push(...colors.map(metric => metric.key));
+ }
+ const options = {
+ ...getBranchLikeQuery(branchLike),
+ s: 'metric',
+ metricSort: size.key,
+ asc: false,
+ ps: BUBBLES_LIMIT
+ };
+
+ this.props.updateLoading({ bubbles: true });
+ getComponentLeaves(component.key, metricsKey, options).then(
+ r => {
+ if (domain === this.props.domain) {
+ if (this.mounted) {
+ this.setState({
+ components: r.components.map(component =>
+ enhanceComponent(component, undefined, metrics)
+ ),
+ paging: r.paging
+ });
+ }
+ this.props.updateLoading({ bubbles: false });
+ }
+ },
+ () => this.props.updateLoading({ bubbles: false })
+ );
+ };
+
+ renderContent() {
+ const { branchLike, component } = this.props;
+ if (isFileType(component)) {
+ return (
+ <div className="measure-details-viewer">
+ <SourceViewer branchLike={branchLike} component={component.key} />
+ </div>
+ );
+ }
+
+ return (
+ <BubbleChart
+ component={this.props.component}
+ components={this.state.components}
+ domain={this.props.domain}
+ metrics={this.props.metrics}
+ updateSelected={this.props.updateSelected}
+ />
+ );
+ }
+
+ render() {
+ const { branchLike, component, currentUser, leakPeriod, rootComponent } = this.props;
+ const isLoggedIn = currentUser && currentUser.isLoggedIn;
+ const isFile = isFileType(component);
+ return (
+ <div className={this.props.className}>
+ <div className="layout-page-header-panel layout-page-main-header">
+ <div className="layout-page-header-panel-inner layout-page-main-header-inner">
+ <div className="layout-page-main-inner">
+ <Breadcrumbs
+ backToFirst={true}
+ branchLike={branchLike}
+ className="measure-breadcrumbs spacer-right text-ellipsis"
+ component={component}
+ handleSelect={this.props.updateSelected}
+ rootComponent={rootComponent}
+ />
+ {component.key !== rootComponent.key &&
+ isLoggedIn && (
+ <MeasureFavoriteContainer
+ className="measure-favorite spacer-right"
+ component={component.key}
+ />
+ )}
+ <PageActions
+ current={this.state.components.length}
+ isFile={isFile}
+ paging={this.state.paging}
+ />
+ </div>
+ </div>
+ </div>
+ <div className="layout-page-main-inner measure-details-content">
+ <div className="clearfix big-spacer-bottom">
+ {leakPeriod && (
+ <LeakPeriodLegend className="pull-right" component={component} period={leakPeriod} />
+ )}
+ </div>
+ <DeferredSpinner loading={this.props.loading} />
+ {!this.props.loading && this.renderContent()}
+ </div>
+ </div>
+ );
+ }
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.
- */
-// @flow
-import React from 'react';
-import MeasureOverview from './MeasureOverview';
-import { getComponentShow } from '../../../api/components';
-import { getProjectUrl } from '../../../helpers/urls';
-import { isViewType } from '../utils';
-import { getBranchLikeQuery } from '../../../helpers/branches';
-/*:: import type { Component, Period, Query } from '../types'; */
-/*:: import type { RawQuery } from '../../../helpers/query'; */
-/*:: import type { Metric } from '../../../app/flow-types'; */
-
-/*:: type Props = {|
- branchLike?: { id?: string; name: string },
- className?: string,
- rootComponent: Component,
- currentUser: { isLoggedIn: boolean },
- domain: string,
- leakPeriod: Period,
- metrics: { [string]: Metric },
- router: {
- push: ({ pathname: string, query?: RawQuery }) => void
- },
- selected: ?string,
- updateQuery: Query => void
-|}; */
-
-/*:: type State = {
- component: ?Component,
- loading: {
- component: boolean,
- bubbles: boolean
- }
-}; */
-
-export default class MeasureOverviewContainer extends React.PureComponent {
- /*:: mounted: boolean; */
- /*:: props: Props; */
- state /*: State */ = {
- component: null,
- loading: {
- component: false,
- bubbles: false
- }
- };
-
- componentDidMount() {
- this.mounted = true;
- this.fetchComponent(this.props);
- }
-
- componentWillReceiveProps(nextProps /*: Props */) {
- const { component } = this.state;
- const componentChanged =
- !component ||
- nextProps.rootComponent.key !== component.key ||
- nextProps.selected !== component.key;
- if (componentChanged || nextProps.domain !== this.props.domain) {
- this.fetchComponent(nextProps);
- }
- }
-
- componentWillUnmount() {
- this.mounted = false;
- }
-
- fetchComponent = ({ branchLike, rootComponent, selected } /*: Props */) => {
- if (!selected || rootComponent.key === selected) {
- this.setState({ component: rootComponent });
- this.updateLoading({ component: false });
- return;
- }
- this.updateLoading({ component: true });
- getComponentShow({ component: selected, ...getBranchLikeQuery(branchLike) }).then(
- ({ component }) => {
- if (this.mounted) {
- this.setState({ component });
- this.updateLoading({ component: false });
- }
- },
- () => this.updateLoading({ component: false })
- );
- };
-
- updateLoading = (loading /*: { [string]: boolean } */) => {
- if (this.mounted) {
- this.setState(state => ({ loading: { ...state.loading, ...loading } }));
- }
- };
-
- updateSelected = (component /*: string */) => {
- if (this.state.component && isViewType(this.state.component)) {
- this.props.router.push(getProjectUrl(component));
- } else {
- this.props.updateQuery({
- selected: component !== this.props.rootComponent.key ? component : null
- });
- }
- };
-
- render() {
- if (!this.state.component) {
- return null;
- }
-
- return (
- <MeasureOverview
- branchLike={this.props.branchLike}
- className={this.props.className}
- component={this.state.component}
- currentUser={this.props.currentUser}
- domain={this.props.domain}
- leakPeriod={this.props.leakPeriod}
- loading={this.state.loading.component || this.state.loading.bubbles}
- metrics={this.props.metrics}
- rootComponent={this.props.rootComponent}
- updateLoading={this.updateLoading}
- updateSelected={this.updateSelected}
- />
- );
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 { InjectedRouter } from 'react-router';
+import MeasureOverview from './MeasureOverview';
+import { getComponentShow } from '../../../api/components';
+import { getProjectUrl } from '../../../helpers/urls';
+import { isViewType, Query } from '../utils';
+import { getBranchLikeQuery } from '../../../helpers/branches';
+import { BranchLike, ComponentMeasure, CurrentUser, Metric, Period } from '../../../app/types';
+
+interface Props {
+ branchLike?: BranchLike;
+ className?: string;
+ currentUser: CurrentUser;
+ domain: string;
+ leakPeriod?: Period;
+ metrics: { [metric: string]: Metric };
+ rootComponent: ComponentMeasure;
+ router: InjectedRouter;
+ selected?: string;
+ updateQuery: (query: Partial<Query>) => void;
+}
+
+interface LoadingState {
+ bubbles: boolean;
+ component: boolean;
+}
+
+interface State {
+ component?: ComponentMeasure;
+ loading: LoadingState;
+}
+
+export default class MeasureOverviewContainer extends React.PureComponent<Props, State> {
+ mounted = false;
+
+ state: State = {
+ loading: { bubbles: false, component: false }
+ };
+
+ componentDidMount() {
+ this.mounted = true;
+ this.fetchComponent(this.props);
+ }
+
+ componentWillReceiveProps(nextProps: Props) {
+ const { component } = this.state;
+ const componentChanged =
+ !component ||
+ nextProps.rootComponent.key !== component.key ||
+ nextProps.selected !== component.key;
+ if (componentChanged || nextProps.domain !== this.props.domain) {
+ this.fetchComponent(nextProps);
+ }
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ fetchComponent = ({ branchLike, rootComponent, selected }: Props) => {
+ if (!selected || rootComponent.key === selected) {
+ this.setState({ component: rootComponent });
+ this.updateLoading({ component: false });
+ return;
+ }
+ this.updateLoading({ component: true });
+ getComponentShow({ component: selected, ...getBranchLikeQuery(branchLike) }).then(
+ ({ component }) => {
+ if (this.mounted) {
+ this.setState({ component });
+ this.updateLoading({ component: false });
+ }
+ },
+ () => this.updateLoading({ component: false })
+ );
+ };
+
+ updateLoading = (loading: Partial<LoadingState>) => {
+ if (this.mounted) {
+ this.setState(state => ({ loading: { ...state.loading, ...loading } }));
+ }
+ };
+
+ updateSelected = (component: string) => {
+ if (this.state.component && isViewType(this.state.component)) {
+ this.props.router.push(getProjectUrl(component));
+ } else {
+ this.props.updateQuery({
+ selected: component !== this.props.rootComponent.key ? component : undefined
+ });
+ }
+ };
+
+ render() {
+ if (!this.state.component) {
+ return null;
+ }
+
+ return (
+ <MeasureOverview
+ branchLike={this.props.branchLike}
+ className={this.props.className}
+ component={this.state.component}
+ currentUser={this.props.currentUser}
+ domain={this.props.domain}
+ leakPeriod={this.props.leakPeriod}
+ loading={this.state.loading.component || this.state.loading.bubbles}
+ metrics={this.props.metrics}
+ rootComponent={this.props.rootComponent}
+ updateLoading={this.updateLoading}
+ updateSelected={this.updateSelected}
+ />
+ );
+ }
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.
- */
-// @flow
-import React from 'react';
-import { translate } from '../../../helpers/l10n';
-
-export default function MetricNotFound({ className } /*: { className?: string } */) {
- return (
- <div className={className}>
- <div className="alert alert-danger">{translate('component_measures.not_found')}</div>
- </div>
- );
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { translate } from '../../../helpers/l10n';
+
+export default function MetricNotFound({ className }: { className?: string }) {
+ return (
+ <div className={className}>
+ <div className="alert alert-danger">{translate('component_measures.not_found')}</div>
+ </div>
+ );
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.
- */
-// @flow
-import React from 'react';
-import FilesCounter from './FilesCounter';
-import { translate } from '../../../helpers/l10n';
-/*:: import type { Paging } from '../types'; */
-
-/*:: type Props = {|
- current: ?number,
- isFile: ?boolean,
- paging: ?Paging,
- totalLoadedComponents?: number,
- view?: string
-|}; */
-
-export default function PageActions(props /*: Props */) {
- const { isFile, paging, totalLoadedComponents } = props;
- const showShortcuts = ['list', 'tree'].includes(props.view);
- return (
- <div className="pull-right">
- {!isFile && showShortcuts && renderShortcuts()}
- {isFile && paging && renderFileShortcuts()}
- <div className="measure-details-page-actions">
- {paging != null && (
- <FilesCounter
- className="spacer-left"
- current={props.current}
- total={isFile && totalLoadedComponents != null ? totalLoadedComponents : paging.total}
- />
- )}
- </div>
- </div>
- );
-}
-
-function renderShortcuts() {
- return (
- <span className="note big-spacer-right">
- <span className="big-spacer-right">
- <span className="shortcut-button little-spacer-right">↑</span>
- <span className="shortcut-button little-spacer-right">↓</span>
- {translate('component_measures.to_select_files')}
- </span>
-
- <span>
- <span className="shortcut-button little-spacer-right">←</span>
- <span className="shortcut-button little-spacer-right">→</span>
- {translate('component_measures.to_navigate')}
- </span>
- </span>
- );
-}
-
-function renderFileShortcuts() {
- return (
- <span className="note spacer-right">
- <span>
- <span className="shortcut-button little-spacer-right">j</span>
- <span className="shortcut-button little-spacer-right">k</span>
- {translate('component_measures.to_navigate_files')}
- </span>
- </span>
- );
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 FilesCounter from './FilesCounter';
+import { translate } from '../../../helpers/l10n';
+import { Paging } from '../../../app/types';
+
+interface Props {
+ current?: number;
+ isFile?: boolean;
+ paging?: Paging;
+ totalLoadedComponents?: number;
+ view?: string;
+}
+
+export default function PageActions(props: Props) {
+ const { isFile, paging, totalLoadedComponents } = props;
+ const showShortcuts = props.view && ['list', 'tree'].includes(props.view);
+ return (
+ <div className="pull-right">
+ {!isFile && showShortcuts && renderShortcuts()}
+ {isFile && paging && renderFileShortcuts()}
+ <div className="measure-details-page-actions">
+ {paging != null && (
+ <FilesCounter
+ className="spacer-left"
+ current={props.current}
+ total={isFile && totalLoadedComponents != null ? totalLoadedComponents : paging.total}
+ />
+ )}
+ </div>
+ </div>
+ );
+}
+
+function renderShortcuts() {
+ return (
+ <span className="note big-spacer-right">
+ <span className="big-spacer-right">
+ <span className="shortcut-button little-spacer-right">↑</span>
+ <span className="shortcut-button little-spacer-right">↓</span>
+ {translate('component_measures.to_select_files')}
+ </span>
+
+ <span>
+ <span className="shortcut-button little-spacer-right">←</span>
+ <span className="shortcut-button little-spacer-right">→</span>
+ {translate('component_measures.to_navigate')}
+ </span>
+ </span>
+ );
+}
+
+function renderFileShortcuts() {
+ return (
+ <span className="note spacer-right">
+ <span>
+ <span className="shortcut-button little-spacer-right">j</span>
+ <span className="shortcut-button little-spacer-right">k</span>
+ {translate('component_measures.to_navigate_files')}
+ </span>
+ </span>
+ );
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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 React from 'react';
-import { shallow } from 'enzyme';
-import App from '../App';
-
-const METRICS = {
- lines_to_cover: {
- key: 'lines_to_cover',
- type: 'INT',
- name: 'Lines to Cover',
- domain: 'Coverage'
- },
- coverage: { key: 'coverage', type: 'PERCENT', name: 'Coverage', domain: 'Coverage' },
- duplicated_lines_density: {
- key: 'duplicated_lines_density',
- type: 'PERCENT',
- name: 'Duplicated Lines (%)',
- domain: 'Duplications'
- },
- new_bugs: { key: 'new_bugs', type: 'INT', name: 'New Bugs', domain: 'Reliability' }
-};
-
-const PROPS = {
- branch: { isMain: true, name: 'master' },
- component: { key: 'foo' },
- location: { pathname: '/component_measures', query: { metric: 'coverage' } },
- fetchMeasures: () => Promise.resolve({ measures: [] }),
- fetchMetrics: () => {},
- metrics: METRICS,
- metricsKey: ['lines_to_cover', 'coverage', 'duplicated_lines_density', 'new_bugs'],
- router: { push: () => {} }
-};
-
-it('should render correctly', () => {
- const wrapper = shallow(<App {...PROPS} />);
- expect(wrapper.find('.spinner')).toHaveLength(1);
- wrapper.setState({ loading: false });
- expect(wrapper).toMatchSnapshot();
-});
-
-it('should render a measure overview', () => {
- const wrapper = shallow(
- <App
- {...PROPS}
- location={{ pathname: '/component_measures', query: { metric: 'Reliability' } }}
- />
- );
- expect(wrapper.find('.spinner')).toHaveLength(1);
- wrapper.setState({ loading: false });
- expect(wrapper.find('MeasureOverviewContainer')).toHaveLength(1);
-});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+/* eslint-disable camelcase */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import App from '../App';
+
+const COMPONENT = { key: 'foo', name: 'Foo', qualifier: 'TRK' };
+
+const METRICS = {
+ lines_to_cover: {
+ id: '1',
+ key: 'lines_to_cover',
+ type: 'INT',
+ name: 'Lines to Cover',
+ domain: 'Coverage'
+ },
+ coverage: { id: '2', key: 'coverage', type: 'PERCENT', name: 'Coverage', domain: 'Coverage' },
+ duplicated_lines_density: {
+ id: '3',
+ key: 'duplicated_lines_density',
+ type: 'PERCENT',
+ name: 'Duplicated Lines (%)',
+ domain: 'Duplications'
+ },
+ new_bugs: { id: '4', key: 'new_bugs', type: 'INT', name: 'New Bugs', domain: 'Reliability' }
+};
+
+const PROPS = {
+ branch: { isMain: true, name: 'master' },
+ component: COMPONENT,
+ currentUser: { isLoggedIn: false },
+ location: { pathname: '/component_measures', query: { metric: 'coverage' } },
+ fetchMeasures: jest.fn().mockResolvedValue({ component: COMPONENT, measures: [] }),
+ fetchMetrics: jest.fn(),
+ metrics: METRICS,
+ metricsKey: ['lines_to_cover', 'coverage', 'duplicated_lines_density', 'new_bugs'],
+ router: { push: jest.fn() } as any
+};
+
+it('should render correctly', () => {
+ const wrapper = shallow(<App {...PROPS} />);
+ expect(wrapper.find('.spinner')).toHaveLength(1);
+ wrapper.setState({ loading: false });
+ expect(wrapper).toMatchSnapshot();
+});
+
+it('should render a measure overview', () => {
+ const wrapper = shallow(
+ <App
+ {...PROPS}
+ location={{ pathname: '/component_measures', query: { metric: 'Reliability' } }}
+ />
+ );
+ expect(wrapper.find('.spinner')).toHaveLength(1);
+ wrapper.setState({ loading: false });
+ expect(wrapper.find('MeasureOverviewContainer')).toHaveLength(1);
+});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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 React from 'react';
-import { shallow } from 'enzyme';
-import FilesCounter from '../FilesCounter';
-
-it('should display x files on y total', () => {
- expect(shallow(<FilesCounter current={12} total={123455} />)).toMatchSnapshot();
-});
-
-it('should display only total of files', () => {
- expect(shallow(<FilesCounter current={null} total={123455} />)).toMatchSnapshot();
-});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 FilesCounter from '../FilesCounter';
+
+it('should display x files on y total', () => {
+ expect(shallow(<FilesCounter current={12} total={123455} />)).toMatchSnapshot();
+});
+
+it('should display only total of files', () => {
+ expect(shallow(<FilesCounter current={undefined} total={123455} />)).toMatchSnapshot();
+});
import * as React from 'react';
import { shallow } from 'enzyme';
import LeakPeriodLegend from '../LeakPeriodLegend';
-import { PeriodMode, Period } from '../../../../helpers/periods';
import { differenceInDays } from '../../../../helpers/dates';
-import { ComponentMeasure } from '../../../../app/types';
+import { ComponentMeasure, Period, PeriodMode } from '../../../../app/types';
jest.mock('../../../../helpers/dates', () => {
const dates = require.requireActual('../../../../helpers/dates');
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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 React from 'react';
-import { shallow } from 'enzyme';
-import MeasureHeader from '../MeasureHeader';
-
-const METRIC = {
- key: 'reliability_rating',
- type: 'RATING',
- name: 'Reliability Rating'
-};
-
-const MEASURE = {
- value: '3.0',
- periods: [{ index: 1, value: '0.0' }],
- metric: METRIC,
- leak: '0.0'
-};
-
-const LEAK_METRIC = {
- key: 'new_reliability_rating',
- type: 'RATING',
- name: 'Reliability Rating on New Code'
-};
-
-const LEAK_MEASURE = {
- periods: [{ index: 1, value: '3.0' }],
- metric: LEAK_METRIC,
- leak: '3.0'
-};
-
-const SECONDARY = {
- value: 'java=175123;js=26382',
- metric: {
- key: 'ncloc_language_distribution',
- type: 'DATA',
- name: 'Lines of Code Per Language'
- },
- leak: null
-};
-
-const PROPS = {
- component: { key: 'foo', qualifier: 'TRK' },
- components: [],
- handleSelect: () => {},
- leakPeriod: {
- date: '2017-05-16T13:50:02+0200',
- index: 1,
- mode: 'previous_version',
- parameter: '6,4'
- },
- measure: MEASURE,
- metric: METRIC,
- paging: null,
- secondaryMeasure: null,
- selectedIdx: null
-};
-
-it('should render correctly', () => {
- expect(shallow(<MeasureHeader {...PROPS} />)).toMatchSnapshot();
-});
-
-it('should render correctly for leak', () => {
- expect(
- shallow(<MeasureHeader {...PROPS} measure={LEAK_MEASURE} metric={LEAK_METRIC} />)
- ).toMatchSnapshot();
-});
-
-it('should render with branch', () => {
- const shortBranch = { isMain: false, name: 'feature', mergeBranch: '', type: 'SHORT' };
- expect(
- shallow(<MeasureHeader branchLike={shortBranch} {...PROPS} />).find('Link')
- ).toMatchSnapshot();
-});
-
-it('should not render link to activity page for files', () => {
- expect(
- shallow(<MeasureHeader {...PROPS} />)
- .find('HistoryIcon')
- .exists()
- ).toBeTruthy();
-
- expect(
- shallow(<MeasureHeader {...PROPS} component={{ ...PROPS.component, qualifier: 'FIL' }} />)
- .find('HistoryIcon')
- .exists()
- ).toBeFalsy();
-});
-
-it('should display secondary measure too', () => {
- const wrapper = shallow(<MeasureHeader {...PROPS} secondaryMeasure={SECONDARY} />);
- expect(wrapper.find('Connect(LanguageDistribution)')).toHaveLength(1);
-});
-
-it('should display correctly for open file', () => {
- const wrapper = shallow(
- <MeasureHeader
- {...PROPS}
- component={{ key: 'bar', qualifier: 'FIL' }}
- components={[{ key: 'foo' }, { key: 'bar' }, { key: 'baz' }]}
- selectedIdx={1}
- />
- );
- expect(wrapper.find('.measure-details-primary-actions')).toMatchSnapshot();
- wrapper.setProps({ components: [{ key: 'foo' }, { key: 'bar' }] });
- expect(wrapper.find('.measure-details-primary-actions')).toMatchSnapshot();
-});
-
-it('should work with measure without value', () => {
- expect(shallow(<MeasureHeader {...PROPS} measure={undefined} />)).toMatchSnapshot();
-});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 MeasureHeader from '../MeasureHeader';
+import { PeriodMode } from '../../../../app/types';
+
+const METRIC = {
+ id: '1',
+ key: 'reliability_rating',
+ type: 'RATING',
+ name: 'Reliability Rating'
+};
+
+const MEASURE = {
+ value: '3.0',
+ periods: [{ index: 1, value: '0.0' }],
+ metric: METRIC,
+ leak: '0.0'
+};
+
+const LEAK_METRIC = {
+ id: '2',
+ key: 'new_reliability_rating',
+ type: 'RATING',
+ name: 'Reliability Rating on New Code'
+};
+
+const LEAK_MEASURE = {
+ periods: [{ index: 1, value: '3.0' }],
+ metric: LEAK_METRIC,
+ leak: '3.0'
+};
+
+const SECONDARY = {
+ value: 'java=175123;js=26382',
+ metric: {
+ id: '3',
+ key: 'ncloc_language_distribution',
+ type: 'DATA',
+ name: 'Lines of Code Per Language'
+ }
+};
+
+const PROPS = {
+ component: { key: 'foo', name: 'Foo', qualifier: 'TRK' },
+ leakPeriod: {
+ date: '2017-05-16T13:50:02+0200',
+ index: 1,
+ mode: PeriodMode.PreviousVersion,
+ parameter: '6,4'
+ },
+ measure: MEASURE,
+ metric: METRIC
+};
+
+it('should render correctly', () => {
+ expect(shallow(<MeasureHeader {...PROPS} />)).toMatchSnapshot();
+});
+
+it('should render correctly for leak', () => {
+ expect(
+ shallow(<MeasureHeader {...PROPS} measure={LEAK_MEASURE} metric={LEAK_METRIC} />)
+ ).toMatchSnapshot();
+});
+
+it('should render with branch', () => {
+ const shortBranch = { isMain: false, name: 'feature', mergeBranch: '', type: 'SHORT' };
+ expect(
+ shallow(<MeasureHeader branchLike={shortBranch} {...PROPS} />).find('Link')
+ ).toMatchSnapshot();
+});
+
+it('should not render link to activity page for files', () => {
+ expect(
+ shallow(<MeasureHeader {...PROPS} />)
+ .find('HistoryIcon')
+ .exists()
+ ).toBeTruthy();
+
+ expect(
+ shallow(<MeasureHeader {...PROPS} component={{ ...PROPS.component, qualifier: 'FIL' }} />)
+ .find('HistoryIcon')
+ .exists()
+ ).toBeFalsy();
+});
+
+it('should display secondary measure too', () => {
+ const wrapper = shallow(<MeasureHeader {...PROPS} secondaryMeasure={SECONDARY} />);
+ expect(wrapper.find('Connect(LanguageDistribution)')).toHaveLength(1);
+});
+
+it('should work with measure without value', () => {
+ expect(shallow(<MeasureHeader {...PROPS} measure={undefined} />)).toMatchSnapshot();
+});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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 React from 'react';
-import { shallow } from 'enzyme';
-import PageActions from '../PageActions';
-
-it('should display correctly for a project', () => {
- expect(
- shallow(<PageActions isFile={false} totalLoadedComponents={20} view="list" />)
- ).toMatchSnapshot();
-});
-
-it('should display correctly for a file', () => {
- const wrapper = shallow(<PageActions isFile={true} totalLoadedComponents={10} view="tree" />);
- expect(wrapper).toMatchSnapshot();
- wrapper.setProps({ paging: { total: 100 } });
- expect(wrapper).toMatchSnapshot();
-});
-
-it('should not display shortcuts for treemap', () => {
- expect(
- shallow(<PageActions isFile={false} totalLoadedComponents={20} view="treemap" />)
- ).toMatchSnapshot();
-});
-
-it('should display the total of files', () => {
- expect(
- shallow(
- <PageActions
- current={12}
- isFile={false}
- paging={{ total: 120 }}
- totalLoadedComponents={20}
- view="treemap"
- />
- )
- ).toMatchSnapshot();
- expect(
- shallow(
- <PageActions
- current={12}
- isFile={true}
- paging={{ total: 120 }}
- totalLoadedComponents={20}
- view="list"
- />
- )
- ).toMatchSnapshot();
-});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 PageActions from '../PageActions';
+
+const PAGING = {
+ pageIndex: 1,
+ pageSize: 100,
+ total: 120
+};
+
+it('should display correctly for a project', () => {
+ expect(
+ shallow(<PageActions isFile={false} totalLoadedComponents={20} view="list" />)
+ ).toMatchSnapshot();
+});
+
+it('should display correctly for a file', () => {
+ const wrapper = shallow(<PageActions isFile={true} totalLoadedComponents={10} view="tree" />);
+ expect(wrapper).toMatchSnapshot();
+ wrapper.setProps({ paging: { total: 100 } });
+ expect(wrapper).toMatchSnapshot();
+});
+
+it('should not display shortcuts for treemap', () => {
+ expect(
+ shallow(<PageActions isFile={false} totalLoadedComponents={20} view="treemap" />)
+ ).toMatchSnapshot();
+});
+
+it('should display the total of files', () => {
+ expect(
+ shallow(
+ <PageActions
+ current={12}
+ isFile={false}
+ paging={PAGING}
+ totalLoadedComponents={20}
+ view="treemap"
+ />
+ )
+ ).toMatchSnapshot();
+ expect(
+ shallow(
+ <PageActions
+ current={12}
+ isFile={true}
+ paging={PAGING}
+ totalLoadedComponents={20}
+ view="list"
+ />
+ )
+ ).toMatchSnapshot();
+});
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly 1`] = `
-<div
- className="layout-page"
- id="component-measures"
->
- <Suggestions
- suggestions="component_measures"
- />
- <HelmetWrapper
- defer={true}
- encodeSpecialCharacters={true}
- title="Coverage"
- />
- <ScreenPositionHelper
- className="layout-page-side-outer"
- />
- <MeasureContentContainer
- className="layout-page-main"
- fetchMeasures={[Function]}
- leakPeriod={null}
- metric={
- Object {
- "domain": "Coverage",
- "key": "coverage",
- "name": "Coverage",
- "type": "PERCENT",
- }
- }
- metrics={
- Object {
- "coverage": Object {
- "domain": "Coverage",
- "key": "coverage",
- "name": "Coverage",
- "type": "PERCENT",
- },
- "duplicated_lines_density": Object {
- "domain": "Duplications",
- "key": "duplicated_lines_density",
- "name": "Duplicated Lines (%)",
- "type": "PERCENT",
- },
- "lines_to_cover": Object {
- "domain": "Coverage",
- "key": "lines_to_cover",
- "name": "Lines to Cover",
- "type": "INT",
- },
- "new_bugs": Object {
- "domain": "Reliability",
- "key": "new_bugs",
- "name": "New Bugs",
- "type": "INT",
- },
- }
- }
- rootComponent={
- Object {
- "key": "foo",
- }
- }
- router={
- Object {
- "push": [Function],
- }
- }
- selected=""
- updateQuery={[Function]}
- view="list"
- />
-</div>
-`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<div
+ className="layout-page"
+ id="component-measures"
+>
+ <Suggestions
+ suggestions="component_measures"
+ />
+ <HelmetWrapper
+ defer={true}
+ encodeSpecialCharacters={true}
+ title="Coverage"
+ />
+ <ScreenPositionHelper
+ className="layout-page-side-outer"
+ />
+ <MeasureContentContainer
+ className="layout-page-main"
+ currentUser={
+ Object {
+ "isLoggedIn": false,
+ }
+ }
+ fetchMeasures={
+ [MockFunction] {
+ "calls": Array [
+ Array [
+ "foo",
+ Array [
+ "lines_to_cover",
+ "coverage",
+ "duplicated_lines_density",
+ "new_bugs",
+ ],
+ undefined,
+ ],
+ ],
+ "results": Array [
+ Object {
+ "isThrow": false,
+ "value": Promise {},
+ },
+ ],
+ }
+ }
+ metric={
+ Object {
+ "domain": "Coverage",
+ "id": "2",
+ "key": "coverage",
+ "name": "Coverage",
+ "type": "PERCENT",
+ }
+ }
+ metrics={
+ Object {
+ "coverage": Object {
+ "domain": "Coverage",
+ "id": "2",
+ "key": "coverage",
+ "name": "Coverage",
+ "type": "PERCENT",
+ },
+ "duplicated_lines_density": Object {
+ "domain": "Duplications",
+ "id": "3",
+ "key": "duplicated_lines_density",
+ "name": "Duplicated Lines (%)",
+ "type": "PERCENT",
+ },
+ "lines_to_cover": Object {
+ "domain": "Coverage",
+ "id": "1",
+ "key": "lines_to_cover",
+ "name": "Lines to Cover",
+ "type": "INT",
+ },
+ "new_bugs": Object {
+ "domain": "Reliability",
+ "id": "4",
+ "key": "new_bugs",
+ "name": "New Bugs",
+ "type": "INT",
+ },
+ }
+ }
+ rootComponent={
+ Object {
+ "key": "foo",
+ "name": "Foo",
+ "qualifier": "TRK",
+ }
+ }
+ router={
+ Object {
+ "push": [MockFunction],
+ }
+ }
+ selected=""
+ updateQuery={[Function]}
+ view="list"
+ />
+</div>
+`;
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should display only total of files 1`] = `
-<span>
- <strong>
- 123,455
- </strong>
-
- component_measures.files
-</span>
-`;
-
-exports[`should display x files on y total 1`] = `
-<span>
- <strong>
- <span>
- 12
- /
- </span>
- 123,455
- </strong>
-
- component_measures.files
-</span>
-`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should display only total of files 1`] = `
+<span>
+ <strong>
+ 123,455
+ </strong>
+
+ component_measures.files
+</span>
+`;
+
+exports[`should display x files on y total 1`] = `
+<span>
+ <strong>
+ <span>
+ 12
+ /
+ </span>
+ 123,455
+ </strong>
+
+ component_measures.files
+</span>
+`;
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should display correctly for open file 1`] = `
-<div
- className="measure-details-primary-actions"
->
- <LeakPeriodLegend
- className="spacer-left"
- component={
- Object {
- "key": "bar",
- "qualifier": "FIL",
- }
- }
- period={
- Object {
- "date": "2017-05-16T13:50:02+0200",
- "index": 1,
- "mode": "previous_version",
- "parameter": "6,4",
- }
- }
- />
-</div>
-`;
-
-exports[`should display correctly for open file 2`] = `
-<div
- className="measure-details-primary-actions"
->
- <LeakPeriodLegend
- className="spacer-left"
- component={
- Object {
- "key": "bar",
- "qualifier": "FIL",
- }
- }
- period={
- Object {
- "date": "2017-05-16T13:50:02+0200",
- "index": 1,
- "mode": "previous_version",
- "parameter": "6,4",
- }
- }
- />
-</div>
-`;
-
-exports[`should render correctly 1`] = `
-<div
- className="measure-details-header big-spacer-bottom"
->
- <div
- className="measure-details-primary"
- >
- <div
- className="measure-details-metric"
- >
- <IssueTypeIcon
- className="little-spacer-right text-text-bottom"
- query="reliability_rating"
- />
- Reliability Rating
- <span
- className="measure-details-value spacer-left"
- >
- <strong>
- <Measure
- metricKey="reliability_rating"
- metricType="RATING"
- value="3.0"
- />
- </strong>
- </span>
- <Tooltip
- overlay="component_measures.show_metric_history"
- >
- <Link
- className="js-show-history spacer-left button button-small"
- onlyActiveOnIndex={false}
- style={Object {}}
- to={
- Object {
- "pathname": "/project/activity",
- "query": Object {
- "custom_metrics": "reliability_rating",
- "graph": "custom",
- "id": "foo",
- },
- }
- }
- >
- <HistoryIcon />
- </Link>
- </Tooltip>
- </div>
- <div
- className="measure-details-primary-actions"
- >
- <LeakPeriodLegend
- className="spacer-left"
- component={
- Object {
- "key": "foo",
- "qualifier": "TRK",
- }
- }
- period={
- Object {
- "date": "2017-05-16T13:50:02+0200",
- "index": 1,
- "mode": "previous_version",
- "parameter": "6,4",
- }
- }
- />
- </div>
- </div>
-</div>
-`;
-
-exports[`should render correctly for leak 1`] = `
-<div
- className="measure-details-header big-spacer-bottom"
->
- <div
- className="measure-details-primary"
- >
- <div
- className="measure-details-metric"
- >
- <IssueTypeIcon
- className="little-spacer-right text-text-bottom"
- query="new_reliability_rating"
- />
- Reliability Rating on New Code
- <span
- className="measure-details-value spacer-left"
- >
- <strong>
- <Measure
- className="domain-measures-leak"
- metricKey="new_reliability_rating"
- metricType="RATING"
- value="3.0"
- />
- </strong>
- </span>
- </div>
- <div
- className="measure-details-primary-actions"
- >
- <LeakPeriodLegend
- className="spacer-left"
- component={
- Object {
- "key": "foo",
- "qualifier": "TRK",
- }
- }
- period={
- Object {
- "date": "2017-05-16T13:50:02+0200",
- "index": 1,
- "mode": "previous_version",
- "parameter": "6,4",
- }
- }
- />
- </div>
- </div>
-</div>
-`;
-
-exports[`should render with branch 1`] = `
-<Link
- className="js-show-history spacer-left button button-small"
- onlyActiveOnIndex={false}
- style={Object {}}
- to={
- Object {
- "pathname": "/project/activity",
- "query": Object {
- "branch": "feature",
- "custom_metrics": "reliability_rating",
- "graph": "custom",
- "id": "foo",
- },
- }
- }
->
- <HistoryIcon />
-</Link>
-`;
-
-exports[`should work with measure without value 1`] = `
-<div
- className="measure-details-header big-spacer-bottom"
->
- <div
- className="measure-details-primary"
- >
- <div
- className="measure-details-metric"
- >
- <IssueTypeIcon
- className="little-spacer-right text-text-bottom"
- query="reliability_rating"
- />
- Reliability Rating
- <span
- className="measure-details-value spacer-left"
- >
- <strong>
- <Measure
- metricKey="reliability_rating"
- metricType="RATING"
- />
- </strong>
- </span>
- <Tooltip
- overlay="component_measures.show_metric_history"
- >
- <Link
- className="js-show-history spacer-left button button-small"
- onlyActiveOnIndex={false}
- style={Object {}}
- to={
- Object {
- "pathname": "/project/activity",
- "query": Object {
- "custom_metrics": "reliability_rating",
- "graph": "custom",
- "id": "foo",
- },
- }
- }
- >
- <HistoryIcon />
- </Link>
- </Tooltip>
- </div>
- <div
- className="measure-details-primary-actions"
- >
- <LeakPeriodLegend
- className="spacer-left"
- component={
- Object {
- "key": "foo",
- "qualifier": "TRK",
- }
- }
- period={
- Object {
- "date": "2017-05-16T13:50:02+0200",
- "index": 1,
- "mode": "previous_version",
- "parameter": "6,4",
- }
- }
- />
- </div>
- </div>
-</div>
-`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<div
+ className="measure-details-header big-spacer-bottom"
+>
+ <div
+ className="measure-details-primary"
+ >
+ <div
+ className="measure-details-metric"
+ >
+ <IssueTypeIcon
+ className="little-spacer-right text-text-bottom"
+ query="reliability_rating"
+ />
+ Reliability Rating
+ <span
+ className="measure-details-value spacer-left"
+ >
+ <strong>
+ <Measure
+ metricKey="reliability_rating"
+ metricType="RATING"
+ value="3.0"
+ />
+ </strong>
+ </span>
+ <Tooltip
+ overlay="component_measures.show_metric_history"
+ >
+ <Link
+ className="js-show-history spacer-left button button-small"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/activity",
+ "query": Object {
+ "custom_metrics": "reliability_rating",
+ "graph": "custom",
+ "id": "foo",
+ },
+ }
+ }
+ >
+ <HistoryIcon />
+ </Link>
+ </Tooltip>
+ </div>
+ <div
+ className="measure-details-primary-actions"
+ >
+ <LeakPeriodLegend
+ className="spacer-left"
+ component={
+ Object {
+ "key": "foo",
+ "name": "Foo",
+ "qualifier": "TRK",
+ }
+ }
+ period={
+ Object {
+ "date": "2017-05-16T13:50:02+0200",
+ "index": 1,
+ "mode": "previous_version",
+ "parameter": "6,4",
+ }
+ }
+ />
+ </div>
+ </div>
+</div>
+`;
+
+exports[`should render correctly for leak 1`] = `
+<div
+ className="measure-details-header big-spacer-bottom"
+>
+ <div
+ className="measure-details-primary"
+ >
+ <div
+ className="measure-details-metric"
+ >
+ <IssueTypeIcon
+ className="little-spacer-right text-text-bottom"
+ query="new_reliability_rating"
+ />
+ Reliability Rating on New Code
+ <span
+ className="measure-details-value spacer-left"
+ >
+ <strong>
+ <Measure
+ className="domain-measures-leak"
+ metricKey="new_reliability_rating"
+ metricType="RATING"
+ value="3.0"
+ />
+ </strong>
+ </span>
+ </div>
+ <div
+ className="measure-details-primary-actions"
+ >
+ <LeakPeriodLegend
+ className="spacer-left"
+ component={
+ Object {
+ "key": "foo",
+ "name": "Foo",
+ "qualifier": "TRK",
+ }
+ }
+ period={
+ Object {
+ "date": "2017-05-16T13:50:02+0200",
+ "index": 1,
+ "mode": "previous_version",
+ "parameter": "6,4",
+ }
+ }
+ />
+ </div>
+ </div>
+</div>
+`;
+
+exports[`should render with branch 1`] = `
+<Link
+ className="js-show-history spacer-left button button-small"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/activity",
+ "query": Object {
+ "branch": "feature",
+ "custom_metrics": "reliability_rating",
+ "graph": "custom",
+ "id": "foo",
+ },
+ }
+ }
+>
+ <HistoryIcon />
+</Link>
+`;
+
+exports[`should work with measure without value 1`] = `
+<div
+ className="measure-details-header big-spacer-bottom"
+>
+ <div
+ className="measure-details-primary"
+ >
+ <div
+ className="measure-details-metric"
+ >
+ <IssueTypeIcon
+ className="little-spacer-right text-text-bottom"
+ query="reliability_rating"
+ />
+ Reliability Rating
+ <span
+ className="measure-details-value spacer-left"
+ >
+ <strong>
+ <Measure
+ metricKey="reliability_rating"
+ metricType="RATING"
+ />
+ </strong>
+ </span>
+ <Tooltip
+ overlay="component_measures.show_metric_history"
+ >
+ <Link
+ className="js-show-history spacer-left button button-small"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/activity",
+ "query": Object {
+ "custom_metrics": "reliability_rating",
+ "graph": "custom",
+ "id": "foo",
+ },
+ }
+ }
+ >
+ <HistoryIcon />
+ </Link>
+ </Tooltip>
+ </div>
+ <div
+ className="measure-details-primary-actions"
+ >
+ <LeakPeriodLegend
+ className="spacer-left"
+ component={
+ Object {
+ "key": "foo",
+ "name": "Foo",
+ "qualifier": "TRK",
+ }
+ }
+ period={
+ Object {
+ "date": "2017-05-16T13:50:02+0200",
+ "index": 1,
+ "mode": "previous_version",
+ "parameter": "6,4",
+ }
+ }
+ />
+ </div>
+ </div>
+</div>
+`;
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should display correctly for a file 1`] = `
-<div
- className="pull-right"
->
- <div
- className="measure-details-page-actions"
- />
-</div>
-`;
-
-exports[`should display correctly for a file 2`] = `
-<div
- className="pull-right"
->
- <span
- className="note spacer-right"
- >
- <span>
- <span
- className="shortcut-button little-spacer-right"
- >
- j
- </span>
- <span
- className="shortcut-button little-spacer-right"
- >
- k
- </span>
- component_measures.to_navigate_files
- </span>
- </span>
- <div
- className="measure-details-page-actions"
- >
- <FilesCounter
- className="spacer-left"
- total={10}
- />
- </div>
-</div>
-`;
-
-exports[`should display correctly for a project 1`] = `
-<div
- className="pull-right"
->
- <span
- className="note big-spacer-right"
- >
- <span
- className="big-spacer-right"
- >
- <span
- className="shortcut-button little-spacer-right"
- >
- ↑
- </span>
- <span
- className="shortcut-button little-spacer-right"
- >
- ↓
- </span>
- component_measures.to_select_files
- </span>
- <span>
- <span
- className="shortcut-button little-spacer-right"
- >
- ←
- </span>
- <span
- className="shortcut-button little-spacer-right"
- >
- →
- </span>
- component_measures.to_navigate
- </span>
- </span>
- <div
- className="measure-details-page-actions"
- />
-</div>
-`;
-
-exports[`should display the total of files 1`] = `
-<div
- className="pull-right"
->
- <div
- className="measure-details-page-actions"
- >
- <FilesCounter
- className="spacer-left"
- current={12}
- total={120}
- />
- </div>
-</div>
-`;
-
-exports[`should display the total of files 2`] = `
-<div
- className="pull-right"
->
- <span
- className="note spacer-right"
- >
- <span>
- <span
- className="shortcut-button little-spacer-right"
- >
- j
- </span>
- <span
- className="shortcut-button little-spacer-right"
- >
- k
- </span>
- component_measures.to_navigate_files
- </span>
- </span>
- <div
- className="measure-details-page-actions"
- >
- <FilesCounter
- className="spacer-left"
- current={12}
- total={20}
- />
- </div>
-</div>
-`;
-
-exports[`should not display shortcuts for treemap 1`] = `
-<div
- className="pull-right"
->
- <div
- className="measure-details-page-actions"
- />
-</div>
-`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should display correctly for a file 1`] = `
+<div
+ className="pull-right"
+>
+ <div
+ className="measure-details-page-actions"
+ />
+</div>
+`;
+
+exports[`should display correctly for a file 2`] = `
+<div
+ className="pull-right"
+>
+ <span
+ className="note spacer-right"
+ >
+ <span>
+ <span
+ className="shortcut-button little-spacer-right"
+ >
+ j
+ </span>
+ <span
+ className="shortcut-button little-spacer-right"
+ >
+ k
+ </span>
+ component_measures.to_navigate_files
+ </span>
+ </span>
+ <div
+ className="measure-details-page-actions"
+ >
+ <FilesCounter
+ className="spacer-left"
+ total={10}
+ />
+ </div>
+</div>
+`;
+
+exports[`should display correctly for a project 1`] = `
+<div
+ className="pull-right"
+>
+ <span
+ className="note big-spacer-right"
+ >
+ <span
+ className="big-spacer-right"
+ >
+ <span
+ className="shortcut-button little-spacer-right"
+ >
+ ↑
+ </span>
+ <span
+ className="shortcut-button little-spacer-right"
+ >
+ ↓
+ </span>
+ component_measures.to_select_files
+ </span>
+ <span>
+ <span
+ className="shortcut-button little-spacer-right"
+ >
+ ←
+ </span>
+ <span
+ className="shortcut-button little-spacer-right"
+ >
+ →
+ </span>
+ component_measures.to_navigate
+ </span>
+ </span>
+ <div
+ className="measure-details-page-actions"
+ />
+</div>
+`;
+
+exports[`should display the total of files 1`] = `
+<div
+ className="pull-right"
+>
+ <div
+ className="measure-details-page-actions"
+ >
+ <FilesCounter
+ className="spacer-left"
+ current={12}
+ total={120}
+ />
+ </div>
+</div>
+`;
+
+exports[`should display the total of files 2`] = `
+<div
+ className="pull-right"
+>
+ <span
+ className="note spacer-right"
+ >
+ <span>
+ <span
+ className="shortcut-button little-spacer-right"
+ >
+ j
+ </span>
+ <span
+ className="shortcut-button little-spacer-right"
+ >
+ k
+ </span>
+ component_measures.to_navigate_files
+ </span>
+ </span>
+ <div
+ className="measure-details-page-actions"
+ >
+ <FilesCounter
+ className="spacer-left"
+ current={12}
+ total={20}
+ />
+ </div>
+</div>
+`;
+
+exports[`should not display shortcuts for treemap 1`] = `
+<div
+ className="pull-right"
+>
+ <div
+ className="measure-details-page-actions"
+ />
+</div>
+`;
y: string;
size: string;
colors?: string[];
- yDomain?: number[];
+ yDomain?: [number, number];
};
} = {
Reliability: {
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-export const domains: { [domain: string]: { categories?: string[]; order: string[] } } = {
+
+interface Domains {
+ [domain: string]: { categories?: string[]; order: string[] };
+}
+
+export const domains: Domains = {
Reliability: {
categories: ['new_code_category', 'overall_category'],
order: [
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.
- */
-// @flow
-import React from 'react';
-import EmptyResult from './EmptyResult';
-import OriginalBubbleChart from '../../../components/charts/BubbleChart';
-import ColorRatingsLegend from '../../../components/charts/ColorRatingsLegend';
-import HelpTooltip from '../../../components/controls/HelpTooltip';
-import { formatMeasure, isDiffMetric } from '../../../helpers/measures';
-import {
- getLocalizedMetricDomain,
- getLocalizedMetricName,
- translate,
- translateWithParameters
-} from '../../../helpers/l10n';
-import { getBubbleMetrics, getBubbleYDomain, isProjectOverview } from '../utils';
-import { RATING_COLORS } from '../../../helpers/constants';
-/*:: import type { Component, ComponentEnhanced } from '../types'; */
-/*:: import type { Metric } from '../../../app/flow-types'; */
-
-const HEIGHT = 500;
-
-/*:: type Props = {|
- component: Component,
- components: Array<ComponentEnhanced>,
- domain: string,
- metrics: { [string]: Metric },
- updateSelected: string => void
-|}; */
-
-export default class BubbleChart extends React.PureComponent {
- /*:: props: Props; */
-
- getMeasureVal = (component /*: ComponentEnhanced */, metric /*: Metric */) => {
- const measure = component.measures.find(measure => measure.metric.key === metric.key);
- if (measure) {
- return Number(isDiffMetric(metric.key) ? measure.leak : measure.value);
- }
- };
-
- getTooltip(
- componentName /*: string */,
- values /*: {
- x: number,
- y: number,
- size: number,
- colors: ?Array<?number>
- }*/,
- metrics /*: {
- x: Metric ,
- y: Metric ,
- size: Metric ,
- colors: ?Array<Metric>
- }*/
- ) {
- const inner = [
- componentName,
- `${metrics.x.name}: ${formatMeasure(values.x, metrics.x.type)}`,
- `${metrics.y.name}: ${formatMeasure(values.y, metrics.y.type)}`,
- `${metrics.size.name}: ${formatMeasure(values.size, metrics.size.type)}`
- ];
- if (values.colors && metrics.colors) {
- metrics.colors.forEach((metric, idx) => {
- // $FlowFixMe colors is always defined at this point
- const colorValue = values.colors[idx];
- if (colorValue || colorValue === 0) {
- inner.push(`${metric.name}: ${formatMeasure(colorValue, metric.type)}`);
- }
- });
- }
- return (
- <div className="text-left">
- {inner.map((line, index) => (
- <React.Fragment key={index}>
- {line}
- {index < inner.length - 1 && <br />}
- </React.Fragment>
- ))}
- </div>
- );
- }
-
- handleBubbleClick = (component /*: ComponentEnhanced */) =>
- this.props.updateSelected(component.refKey || component.key);
-
- getDescription(domain /*: string */) {
- const description = `component_measures.overview.${domain}.description`;
- const translatedDescription = translate(description);
- if (description === translatedDescription) {
- return null;
- }
- return translatedDescription;
- }
-
- renderBubbleChart(
- metrics /*: {
- x: Metric ,
- y: Metric ,
- size: Metric ,
- colors: ?Array<Metric>
- }*/
- ) {
- const items = this.props.components
- .map(component => {
- const x = this.getMeasureVal(component, metrics.x);
- const y = this.getMeasureVal(component, metrics.y);
- const size = this.getMeasureVal(component, metrics.size);
- const colors =
- metrics.colors && metrics.colors.map(metric => this.getMeasureVal(component, metric));
- if ((!x && x !== 0) || (!y && y !== 0) || (!size && size !== 0)) {
- return null;
- }
- return {
- x,
- y,
- size,
- color:
- colors != null ? RATING_COLORS[Math.max(...colors.filter(Boolean)) - 1] : undefined,
- link: component,
- tooltip: this.getTooltip(component.name, { x, y, size, colors }, metrics)
- };
- })
- .filter(Boolean);
-
- const formatXTick = tick => formatMeasure(tick, metrics.x.type);
- const formatYTick = tick => formatMeasure(tick, metrics.y.type);
-
- return (
- <OriginalBubbleChart
- formatXTick={formatXTick}
- formatYTick={formatYTick}
- height={HEIGHT}
- items={items}
- onBubbleClick={this.handleBubbleClick}
- padding={[25, 60, 50, 60]}
- yDomain={getBubbleYDomain(this.props.domain)}
- />
- );
- }
-
- renderChartHeader(
- domain /*: string */,
- sizeMetric /*: Metric */,
- colorsMetric /*: ?Array<Metric> */
- ) {
- const title = isProjectOverview(domain)
- ? translate('component_measures.overview', domain, 'title')
- : translateWithParameters(
- 'component_measures.domain_x_overview',
- getLocalizedMetricDomain(domain)
- );
- return (
- <div className="measure-overview-bubble-chart-header">
- <span className="measure-overview-bubble-chart-title">
- <span className="text-middle">{title}</span>
- <HelpTooltip className="spacer-left" overlay={this.getDescription(domain)} />
- </span>
- <span className="measure-overview-bubble-chart-legend">
- <span className="note">
- {colorsMetric && (
- <span className="spacer-right">
- {translateWithParameters(
- 'component_measures.legend.color_x',
- colorsMetric.length > 1
- ? translateWithParameters(
- 'component_measures.legend.worse_of_x_y',
- ...colorsMetric.map(metric => getLocalizedMetricName(metric))
- )
- : getLocalizedMetricName(colorsMetric[0])
- )}
- </span>
- )}
- {translateWithParameters(
- 'component_measures.legend.size_x',
- getLocalizedMetricName(sizeMetric)
- )}
- </span>
- {colorsMetric && <ColorRatingsLegend className="spacer-top" />}
- </span>
- </div>
- );
- }
-
- render() {
- if (this.props.components.length <= 0) {
- return <EmptyResult />;
- }
- const { domain } = this.props;
- const metrics = getBubbleMetrics(domain, this.props.metrics);
-
- return (
- <div className="measure-overview-bubble-chart">
- {this.renderChartHeader(domain, metrics.size, metrics.colors)}
- <div className="measure-overview-bubble-chart-content">
- {this.renderBubbleChart(metrics)}
- </div>
- <div className="measure-overview-bubble-chart-axis x">
- {getLocalizedMetricName(metrics.x)}
- </div>
- <div className="measure-overview-bubble-chart-axis y">
- {getLocalizedMetricName(metrics.y)}
- </div>
- </div>
- );
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 EmptyResult from './EmptyResult';
+import OriginalBubbleChart, { BubbleItem } from '../../../components/charts/BubbleChart';
+import ColorRatingsLegend from '../../../components/charts/ColorRatingsLegend';
+import HelpTooltip from '../../../components/controls/HelpTooltip';
+import { formatMeasure, isDiffMetric } from '../../../helpers/measures';
+import {
+ getLocalizedMetricDomain,
+ getLocalizedMetricName,
+ translate,
+ translateWithParameters
+} from '../../../helpers/l10n';
+import { getBubbleMetrics, getBubbleYDomain, isProjectOverview } from '../utils';
+import { RATING_COLORS } from '../../../helpers/constants';
+import { ComponentMeasure, ComponentMeasureEnhanced, Metric } from '../../../app/types';
+
+const HEIGHT = 500;
+
+interface Props {
+ component: ComponentMeasure;
+ components: ComponentMeasureEnhanced[];
+ domain: string;
+ metrics: { [metric: string]: Metric };
+ updateSelected: (component: string) => void;
+}
+
+export default class BubbleChart extends React.PureComponent<Props> {
+ getMeasureVal = (component: ComponentMeasureEnhanced, metric: Metric) => {
+ const measure = component.measures.find(measure => measure.metric.key === metric.key);
+ if (!measure) {
+ return undefined;
+ }
+ return Number(isDiffMetric(metric.key) ? measure.leak : measure.value);
+ };
+
+ getTooltip(
+ componentName: string,
+ values: { x: number; y: number; size: number; colors?: Array<number | undefined> },
+ metrics: { x: Metric; y: Metric; size: Metric; colors?: Array<Metric> }
+ ) {
+ const inner = [
+ componentName,
+ `${metrics.x.name}: ${formatMeasure(values.x, metrics.x.type)}`,
+ `${metrics.y.name}: ${formatMeasure(values.y, metrics.y.type)}`,
+ `${metrics.size.name}: ${formatMeasure(values.size, metrics.size.type)}`
+ ];
+ const { colors: valuesColors } = values;
+ const { colors: metricColors } = metrics;
+ if (valuesColors && metricColors) {
+ metricColors.forEach((metric, idx) => {
+ const colorValue = valuesColors[idx];
+ if (colorValue || colorValue === 0) {
+ inner.push(`${metric.name}: ${formatMeasure(colorValue, metric.type)}`);
+ }
+ });
+ }
+ return (
+ <div className="text-left">
+ {inner.map((line, index) => (
+ <React.Fragment key={index}>
+ {line}
+ {index < inner.length - 1 && <br />}
+ </React.Fragment>
+ ))}
+ </div>
+ );
+ }
+
+ handleBubbleClick = (component: ComponentMeasureEnhanced) =>
+ this.props.updateSelected(component.refKey || component.key);
+
+ getDescription(domain: string) {
+ const description = `component_measures.overview.${domain}.description`;
+ const translatedDescription = translate(description);
+ if (description === translatedDescription) {
+ return null;
+ }
+ return translatedDescription;
+ }
+
+ renderBubbleChart(metrics: { x: Metric; y: Metric; size: Metric; colors?: Metric[] }) {
+ const items = this.props.components
+ .map(component => {
+ const x = this.getMeasureVal(component, metrics.x);
+ const y = this.getMeasureVal(component, metrics.y);
+ const size = this.getMeasureVal(component, metrics.size);
+ const colors =
+ metrics.colors && metrics.colors.map(metric => this.getMeasureVal(component, metric));
+ if ((!x && x !== 0) || (!y && y !== 0) || (!size && size !== 0)) {
+ return undefined;
+ }
+ return {
+ x,
+ y,
+ size,
+ color:
+ colors !== undefined
+ ? RATING_COLORS[Math.max(...colors.filter(Boolean) as number[]) - 1]
+ : undefined,
+ data: component,
+ tooltip: this.getTooltip(component.name, { x, y, size, colors }, metrics)
+ };
+ })
+ .filter(Boolean) as BubbleItem<ComponentMeasureEnhanced>[];
+
+ const formatXTick = (tick: string | number | undefined) => formatMeasure(tick, metrics.x.type);
+ const formatYTick = (tick: string | number | undefined) => formatMeasure(tick, metrics.y.type);
+
+ return (
+ <OriginalBubbleChart<ComponentMeasureEnhanced>
+ formatXTick={formatXTick}
+ formatYTick={formatYTick}
+ height={HEIGHT}
+ items={items}
+ onBubbleClick={this.handleBubbleClick}
+ padding={[25, 60, 50, 60]}
+ yDomain={getBubbleYDomain(this.props.domain)}
+ />
+ );
+ }
+
+ renderChartHeader(domain: string, sizeMetric: Metric, colorsMetric?: Metric[]) {
+ const title = isProjectOverview(domain)
+ ? translate('component_measures.overview', domain, 'title')
+ : translateWithParameters(
+ 'component_measures.domain_x_overview',
+ getLocalizedMetricDomain(domain)
+ );
+ return (
+ <div className="measure-overview-bubble-chart-header">
+ <span className="measure-overview-bubble-chart-title">
+ <span className="text-middle">{title}</span>
+ <HelpTooltip className="spacer-left" overlay={this.getDescription(domain)} />
+ </span>
+ <span className="measure-overview-bubble-chart-legend">
+ <span className="note">
+ {colorsMetric && (
+ <span className="spacer-right">
+ {translateWithParameters(
+ 'component_measures.legend.color_x',
+ colorsMetric.length > 1
+ ? translateWithParameters(
+ 'component_measures.legend.worse_of_x_y',
+ ...colorsMetric.map(metric => getLocalizedMetricName(metric))
+ )
+ : getLocalizedMetricName(colorsMetric[0])
+ )}
+ </span>
+ )}
+ {translateWithParameters(
+ 'component_measures.legend.size_x',
+ getLocalizedMetricName(sizeMetric)
+ )}
+ </span>
+ {colorsMetric && <ColorRatingsLegend className="spacer-top" />}
+ </span>
+ </div>
+ );
+ }
+
+ render() {
+ if (this.props.components.length <= 0) {
+ return <EmptyResult />;
+ }
+ const { domain } = this.props;
+ const metrics = getBubbleMetrics(domain, this.props.metrics);
+
+ return (
+ <div className="measure-overview-bubble-chart">
+ {this.renderChartHeader(domain, metrics.size, metrics.colors)}
+ <div className="measure-overview-bubble-chart-content">
+ {this.renderBubbleChart(metrics)}
+ </div>
+ <div className="measure-overview-bubble-chart-axis x">
+ {getLocalizedMetricName(metrics.x)}
+ </div>
+ <div className="measure-overview-bubble-chart-axis y">
+ {getLocalizedMetricName(metrics.y)}
+ </div>
+ </div>
+ );
+ }
+}
import * as React from 'react';
import * as key from 'keymaster';
import SourceViewer from '../../../components/SourceViewer/SourceViewer';
-import { BranchLike, ComponentMeasure, ComponentMeasureEnhanced, Metric } from '../../../app/types';
-import { Period } from '../../../helpers/periods';
+import {
+ BranchLike,
+ ComponentMeasure,
+ ComponentMeasureEnhanced,
+ Metric,
+ Period
+} from '../../../app/types';
interface Props {
branchLike?: BranchLike;
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.
- */
-// @flow
-import React from 'react';
-import { Link } from 'react-router';
-import LinkIcon from '../../../components/icons-components/LinkIcon';
-import QualifierIcon from '../../../components/icons-components/QualifierIcon';
-import LongLivingBranchIcon from '../../../components/icons-components/LongLivingBranchIcon';
-import { splitPath } from '../../../helpers/path';
-import {
- getPathUrlAsString,
- getBranchLikeUrl,
- getLongLivingBranchUrl,
- getComponentDrilldownUrlWithSelection
-} from '../../../helpers/urls';
-import { translate } from '../../../helpers/l10n';
-/*:: import type { Component, ComponentEnhanced } from '../types'; */
-/*:: import type { Metric } from '../../../app/flow-types'; */
-
-/*:: type Props = {
- branchLike?: { id?: string; name: string },
- component: ComponentEnhanced,
- onClick: string => void,
- metric: Metric,
- rootComponent: Component
-}; */
-
-export default class ComponentCell extends React.PureComponent {
- /*:: props: Props; */
-
- handleClick = (e /*: MouseEvent */) => {
- const isLeftClickEvent = e.button === 0;
- const isModifiedEvent = !!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey);
-
- if (isLeftClickEvent && !isModifiedEvent) {
- e.preventDefault();
- this.props.onClick(this.props.component.key);
- }
- };
-
- renderInner() {
- const { component } = this.props;
- let head = '';
- let tail = component.name;
- let branch = null;
-
- if (['DIR', 'FIL', 'UTS'].includes(component.qualifier)) {
- const parts = splitPath(component.path);
- ({ head, tail } = parts);
- }
-
- if (this.props.rootComponent.qualifier === 'APP') {
- branch = (
- <React.Fragment>
- {component.branch ? (
- <React.Fragment>
- <LongLivingBranchIcon className="spacer-left little-spacer-right" />
- <span className="note">{component.branch}</span>
- </React.Fragment>
- ) : (
- <span className="spacer-left outline-badge">{translate('branches.main_branch')}</span>
- )}
- </React.Fragment>
- );
- }
- return (
- <span title={component.refKey || component.key}>
- <QualifierIcon qualifier={component.qualifier} />
-
- {head.length > 0 && <span className="note">{head}/</span>}
- <span>{tail}</span>
- {branch}
- </span>
- );
- }
-
- render() {
- const { branchLike, component, metric, rootComponent } = this.props;
- const to =
- this.props.rootComponent.qualifier === 'APP'
- ? getLongLivingBranchUrl(component.refKey, component.branch)
- : getBranchLikeUrl(component.refKey, branchLike);
- return (
- <td className="measure-details-component-cell">
- <div className="text-ellipsis">
- {component.refKey == null ? (
- <a
- className="link-no-underline"
- href={getPathUrlAsString(
- getComponentDrilldownUrlWithSelection(
- rootComponent.key,
- component.key,
- metric.key,
- branchLike
- )
- )}
- id={'component-measures-component-link-' + component.key}
- onClick={this.handleClick}>
- {this.renderInner()}
- </a>
- ) : (
- <Link
- className="link-no-underline"
- id={'component-measures-component-link-' + component.key}
- to={to}>
- <span className="big-spacer-right">
- <LinkIcon />
- </span>
- {this.renderInner()}
- </Link>
- )}
- </div>
- </td>
- );
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 LinkIcon from '../../../components/icons-components/LinkIcon';
+import QualifierIcon from '../../../components/icons-components/QualifierIcon';
+import LongLivingBranchIcon from '../../../components/icons-components/LongLivingBranchIcon';
+import { splitPath } from '../../../helpers/path';
+import {
+ getPathUrlAsString,
+ getBranchLikeUrl,
+ getComponentDrilldownUrlWithSelection,
+ getProjectUrl
+} from '../../../helpers/urls';
+import { translate } from '../../../helpers/l10n';
+import { BranchLike, ComponentMeasure, ComponentMeasureEnhanced, Metric } from '../../../app/types';
+
+interface Props {
+ branchLike?: BranchLike;
+ component: ComponentMeasureEnhanced;
+ onClick: (component: string) => void;
+ metric: Metric;
+ rootComponent: ComponentMeasure;
+}
+
+export default class ComponentCell extends React.PureComponent<Props> {
+ handleClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
+ const isLeftClickEvent = event.button === 0;
+ const isModifiedEvent = !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey);
+
+ if (isLeftClickEvent && !isModifiedEvent) {
+ event.preventDefault();
+ this.props.onClick(this.props.component.key);
+ }
+ };
+
+ renderInner(componentKey: string) {
+ const { component } = this.props;
+ let head = '';
+ let tail = component.name;
+ let branchComponent = null;
+
+ if (['DIR', 'FIL', 'UTS'].includes(component.qualifier) && component.path) {
+ ({ head, tail } = splitPath(component.path));
+ }
+
+ if (this.props.rootComponent.qualifier === 'APP') {
+ branchComponent = (
+ <>
+ {component.branch ? (
+ <>
+ <LongLivingBranchIcon className="spacer-left little-spacer-right" />
+ <span className="note">{component.branch}</span>
+ </>
+ ) : (
+ <span className="spacer-left outline-badge">{translate('branches.main_branch')}</span>
+ )}
+ </>
+ );
+ }
+ return (
+ <span title={componentKey}>
+ <QualifierIcon qualifier={component.qualifier} />
+
+ {head.length > 0 && <span className="note">{head}/</span>}
+ <span>{tail}</span>
+ {branchComponent}
+ </span>
+ );
+ }
+
+ render() {
+ const { branchLike, component, metric, rootComponent } = this.props;
+ return (
+ <td className="measure-details-component-cell">
+ <div className="text-ellipsis">
+ {!component.refKey ? (
+ <a
+ className="link-no-underline"
+ href={getPathUrlAsString(
+ getComponentDrilldownUrlWithSelection(
+ rootComponent.key,
+ component.key,
+ metric.key,
+ branchLike
+ )
+ )}
+ id={'component-measures-component-link-' + component.key}
+ onClick={this.handleClick}>
+ {this.renderInner(component.key)}
+ </a>
+ ) : (
+ <Link
+ className="link-no-underline"
+ id={'component-measures-component-link-' + component.refKey}
+ to={
+ this.props.rootComponent.qualifier === 'APP'
+ ? getProjectUrl(component.refKey, component.branch)
+ : getBranchLikeUrl(component.refKey, branchLike)
+ }>
+ <span className="big-spacer-right">
+ <LinkIcon />
+ </span>
+ {this.renderInner(component.refKey)}
+ </Link>
+ )}
+ </div>
+ </td>
+ );
+ }
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.
- */
-// @flow
-import React from 'react';
-import classNames from 'classnames';
-import ComponentCell from './ComponentCell';
-import MeasureCell from './MeasureCell';
-/*:: import type { Component, ComponentEnhanced } from '../types'; */
-/*:: import type { Metric } from '../../../app/flow-types'; */
-
-/*:: type Props = {|
- branchLike?: { id?: string; name: string },
- component: ComponentEnhanced,
- isSelected: boolean,
- onClick: string => void,
- otherMetrics: Array<Metric>,
- metric: Metric,
- rootComponent: Component
-|}; */
-
-export default function ComponentsListRow(props /*: Props */) {
- const { branchLike, component, rootComponent } = props;
- const otherMeasures = props.otherMetrics.map(metric => {
- const measure = component.measures.find(measure => measure.metric.key === metric.key);
- return { ...measure, metric };
- });
- const rowClass = classNames('measure-details-component-row', {
- selected: props.isSelected
- });
- return (
- <tr className={rowClass}>
- <ComponentCell
- branchLike={branchLike}
- component={component}
- metric={props.metric}
- onClick={props.onClick}
- rootComponent={rootComponent}
- />
-
- <MeasureCell component={component} metric={props.metric} />
-
- {otherMeasures.map(measure => (
- <MeasureCell
- component={component}
- key={measure.metric.key}
- measure={measure}
- metric={measure.metric}
- />
- ))}
- </tr>
- );
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 * as classNames from 'classnames';
+import ComponentCell from './ComponentCell';
+import MeasureCell from './MeasureCell';
+import { ComponentMeasure, Metric, ComponentMeasureEnhanced, BranchLike } from '../../../app/types';
+
+interface Props {
+ branchLike?: BranchLike;
+ component: ComponentMeasureEnhanced;
+ isSelected: boolean;
+ onClick: (component: string) => void;
+ otherMetrics: Metric[];
+ metric: Metric;
+ rootComponent: ComponentMeasure;
+}
+
+export default function ComponentsListRow(props: Props) {
+ const { branchLike, component, rootComponent } = props;
+ const otherMeasures = props.otherMetrics.map(metric => {
+ const measure = component.measures.find(measure => measure.metric.key === metric.key);
+ return { ...measure, metric };
+ });
+ const rowClass = classNames('measure-details-component-row', {
+ selected: props.isSelected
+ });
+ return (
+ <tr className={rowClass}>
+ <ComponentCell
+ branchLike={branchLike}
+ component={component}
+ metric={props.metric}
+ onClick={props.onClick}
+ rootComponent={rootComponent}
+ />
+
+ <MeasureCell component={component} metric={props.metric} />
+
+ {otherMeasures.map(measure => (
+ <MeasureCell
+ component={component}
+ key={measure.metric.key}
+ measure={measure}
+ metric={measure.metric}
+ />
+ ))}
+ </tr>
+ );
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.
- */
-// @flow
-import React from 'react';
-import Measure from '../../../components/measure/Measure';
-import { isDiffMetric } from '../../../helpers/measures';
-/*:: import type { ComponentEnhanced } from '../types'; */
-/*:: import type { MeasureEnhanced } from '../../../components/measure/types'; */
-/*:: import type { Metric } from '../../../app/flow-types'; */
-
-/*:: type Props = {
- component: ComponentEnhanced,
- measure?: MeasureEnhanced,
- metric: Metric
-}; */
-
-export default function MeasureCell({ component, measure, metric } /*: Props */) {
- const getValue = (item /*: { leak?: ?string; value?: string } */) =>
- isDiffMetric(metric.key) ? item.leak : item.value;
-
- const value = getValue(measure || component);
-
- return (
- <td className="thin nowrap text-right">
- <span id={`component-measures-component-measure-${component.key}-${metric.key}`}>
- <Measure metricKey={metric.key} metricType={metric.type} value={value} />
- </span>
- </td>
- );
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 Measure from '../../../components/measure/Measure';
+import { isDiffMetric } from '../../../helpers/measures';
+import { Metric, MeasureEnhanced, ComponentMeasureEnhanced } from '../../../app/types';
+
+interface Props {
+ component: ComponentMeasureEnhanced;
+ measure?: MeasureEnhanced;
+ metric: Metric;
+}
+
+export default function MeasureCell({ component, measure, metric }: Props) {
+ const getValue = (item: { leak?: string; value?: string }) =>
+ isDiffMetric(metric.key) ? item.leak : item.value;
+
+ const value = getValue(measure || component);
+
+ return (
+ <td className="thin nowrap text-right">
+ <span id={`component-measures-component-measure-${component.key}-${metric.key}`}>
+ <Measure metricKey={metric.key} metricType={metric.type} value={value} />
+ </span>
+ </td>
+ );
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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 React from 'react';
-import { shallow } from 'enzyme';
-import MeasureCell from '../MeasureCell';
-
-describe('should correctly take the value', () => {
- const renderAndTakeValue = props =>
- shallow(<MeasureCell {...props} />)
- .find('Measure')
- .prop('value');
-
- it('absolute value', () => {
- const component = { value: '123' };
- const metric = { key: 'coverage' };
- const measure = { value: '567' };
-
- expect(renderAndTakeValue({ component, metric })).toEqual('123');
- expect(renderAndTakeValue({ component, metric, measure })).toEqual('567');
- });
-
- it('leak value', () => {
- const component = { leak: '234' };
- const metric = { key: 'new_coverage' };
- const measure = { leak: '678' };
-
- expect(renderAndTakeValue({ component, metric })).toEqual('234');
- expect(renderAndTakeValue({ component, metric, measure })).toEqual('678');
- });
-});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 MeasureCell from '../MeasureCell';
+
+describe('should correctly take the value', () => {
+ const renderAndTakeValue = (props: any) =>
+ shallow(<MeasureCell {...props} />)
+ .find('Measure')
+ .prop('value');
+
+ it('absolute value', () => {
+ const component = { value: '123' };
+ const metric = { id: '1', key: 'coverage' };
+ const measure = { value: '567' };
+
+ expect(renderAndTakeValue({ component, metric })).toEqual('123');
+ expect(renderAndTakeValue({ component, metric, measure })).toEqual('567');
+ });
+
+ it('leak value', () => {
+ const component = { leak: '234' };
+ const metric = { id: '1', key: 'new_coverage' };
+ const measure = { leak: '678' };
+
+ expect(renderAndTakeValue({ component, metric })).toEqual('234');
+ expect(renderAndTakeValue({ component, metric, measure })).toEqual('678');
+ });
+});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.
- */
-// @flow
-import React from 'react';
-import FacetMeasureValue from './FacetMeasureValue';
-import BubblesIcon from '../../../components/icons-components/BubblesIcon';
-import FacetBox from '../../../components/facet/FacetBox';
-import FacetHeader from '../../../components/facet/FacetHeader';
-import FacetItem from '../../../components/facet/FacetItem';
-import FacetItemsList from '../../../components/facet/FacetItemsList';
-import {
- addMeasureCategories,
- filterMeasures,
- hasBubbleChart,
- hasFacetStat,
- sortMeasures
-} from '../utils';
-import {
- getLocalizedCategoryMetricName,
- getLocalizedMetricDomain,
- getLocalizedMetricName,
- translate
-} from '../../../helpers/l10n';
-/*:: import type { MeasureEnhanced } from '../../../components/measure/types'; */
-
-/*:: type Props = {|
- onChange: (metric: string) => void,
- onToggle: (property: string) => void,
- open: boolean,
- domain: { name: string, measures: Array<MeasureEnhanced> },
- selected: string
-|}; */
-
-export default class DomainFacet extends React.PureComponent {
- /*:: props: Props; */
-
- handleHeaderClick = () => this.props.onToggle(this.props.domain.name);
-
- hasFacetSelected = (
- domain /*: { name: string } */,
- measures /*: Array<MeasureEnhanced> */,
- selected /*: string */
- ) => {
- const measureSelected = measures.find(measure => measure.metric.key === selected);
- const overviewSelected = domain.name === selected && hasBubbleChart(domain.name);
- return measureSelected || overviewSelected;
- };
-
- getValues = () => {
- const { domain, selected } = this.props;
- const measureSelected = domain.measures.find(measure => measure.metric.key === selected);
- const overviewSelected = domain.name === selected && hasBubbleChart(domain.name);
- if (measureSelected) {
- return [getLocalizedMetricName(measureSelected.metric)];
- }
- return overviewSelected ? [translate('component_measures.domain_overview')] : [];
- };
-
- renderItemFacetStat = (item /*: MeasureEnhanced */) =>
- hasFacetStat(item.metric.key) ? <FacetMeasureValue measure={item} /> : null;
-
- renderItemsFacet = () => {
- const { domain, selected } = this.props;
- const items = addMeasureCategories(domain.name, filterMeasures(domain.measures));
- const hasCategories = items.some(item => typeof item === 'string');
- const translateMetric = hasCategories ? getLocalizedCategoryMetricName : getLocalizedMetricName;
- let sortedItems = sortMeasures(domain.name, items);
-
- sortedItems = sortedItems.filter((item, index) => {
- return (
- typeof item !== 'string' ||
- (index + 1 !== sortedItems.length && typeof sortedItems[index + 1] !== 'string')
- );
- });
-
- return sortedItems.map(
- item =>
- typeof item === 'string' ? (
- <span className="facet search-navigator-facet facet-category" key={item}>
- <span className="facet-name">
- {translate('component_measures.facet_category', item)}
- </span>
- </span>
- ) : (
- <FacetItem
- active={item.metric.key === selected}
- disabled={false}
- key={item.metric.key}
- name={
- <span className="big-spacer-left" id={`measure-${item.metric.key}-name`}>
- {translateMetric(item.metric)}
- </span>
- }
- onClick={this.props.onChange}
- stat={this.renderItemFacetStat(item)}
- value={item.metric.key}
- />
- )
- );
- };
-
- renderOverviewFacet = () => {
- const { domain, selected } = this.props;
- if (!hasBubbleChart(domain.name)) {
- return null;
- }
- return (
- <FacetItem
- active={domain.name === selected}
- disabled={false}
- key={domain.name}
- name={
- <span id={`measure-overview-${domain.name}-name`}>
- {translate('component_measures.domain_overview')}
- </span>
- }
- onClick={this.props.onChange}
- stat={<BubblesIcon size={14} />}
- value={domain.name}
- />
- );
- };
-
- render() {
- const { domain } = this.props;
- const helper = `component_measures.domain_facets.${domain.name}.help`;
- const translatedHelper = translate(helper);
- return (
- <FacetBox property={domain.name}>
- <FacetHeader
- helper={helper !== translatedHelper ? translatedHelper : undefined}
- name={getLocalizedMetricDomain(domain.name)}
- onClick={this.handleHeaderClick}
- open={this.props.open}
- values={this.getValues()}
- />
-
- {this.props.open && (
- <FacetItemsList>
- {this.renderOverviewFacet()}
- {this.renderItemsFacet()}
- </FacetItemsList>
- )}
- </FacetBox>
- );
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 FacetMeasureValue from './FacetMeasureValue';
+import BubblesIcon from '../../../components/icons-components/BubblesIcon';
+import FacetBox from '../../../components/facet/FacetBox';
+import FacetHeader from '../../../components/facet/FacetHeader';
+import FacetItem from '../../../components/facet/FacetItem';
+import FacetItemsList from '../../../components/facet/FacetItemsList';
+import {
+ addMeasureCategories,
+ filterMeasures,
+ hasBubbleChart,
+ hasFacetStat,
+ sortMeasures
+} from '../utils';
+import {
+ getLocalizedCategoryMetricName,
+ getLocalizedMetricDomain,
+ getLocalizedMetricName,
+ translate
+} from '../../../helpers/l10n';
+import { MeasureEnhanced } from '../../../app/types';
+
+interface Props {
+ domain: { name: string; measures: MeasureEnhanced[] };
+ onChange: (metric: string) => void;
+ onToggle: (property: string) => void;
+ open: boolean;
+ selected: string;
+}
+
+export default class DomainFacet extends React.PureComponent<Props> {
+ handleHeaderClick = () => {
+ this.props.onToggle(this.props.domain.name);
+ };
+
+ hasFacetSelected = (domain: { name: string }, measures: MeasureEnhanced[], selected: string) => {
+ const measureSelected = measures.find(measure => measure.metric.key === selected);
+ const overviewSelected = domain.name === selected && hasBubbleChart(domain.name);
+ return measureSelected || overviewSelected;
+ };
+
+ getValues = () => {
+ const { domain, selected } = this.props;
+ const measureSelected = domain.measures.find(measure => measure.metric.key === selected);
+ const overviewSelected = domain.name === selected && hasBubbleChart(domain.name);
+ if (measureSelected) {
+ return [getLocalizedMetricName(measureSelected.metric)];
+ }
+ return overviewSelected ? [translate('component_measures.domain_overview')] : [];
+ };
+
+ renderItemFacetStat = (item: MeasureEnhanced) => {
+ return hasFacetStat(item.metric.key) ? <FacetMeasureValue measure={item} /> : null;
+ };
+
+ renderItemsFacet = () => {
+ const { domain, selected } = this.props;
+ const items = addMeasureCategories(domain.name, filterMeasures(domain.measures));
+ const hasCategories = items.some(item => typeof item === 'string');
+ const translateMetric = hasCategories ? getLocalizedCategoryMetricName : getLocalizedMetricName;
+ let sortedItems = sortMeasures(domain.name, items);
+
+ sortedItems = sortedItems.filter((item, index) => {
+ return (
+ typeof item !== 'string' ||
+ (index + 1 !== sortedItems.length && typeof sortedItems[index + 1] !== 'string')
+ );
+ });
+
+ return sortedItems.map(
+ item =>
+ typeof item === 'string' ? (
+ <span className="facet search-navigator-facet facet-category" key={item}>
+ <span className="facet-name">
+ {translate('component_measures.facet_category', item)}
+ </span>
+ </span>
+ ) : (
+ <FacetItem
+ active={item.metric.key === selected}
+ disabled={false}
+ key={item.metric.key}
+ name={
+ <span className="big-spacer-left" id={`measure-${item.metric.key}-name`}>
+ {translateMetric(item.metric)}
+ </span>
+ }
+ onClick={this.props.onChange}
+ stat={this.renderItemFacetStat(item)}
+ tooltip={translateMetric(item.metric)}
+ value={item.metric.key}
+ />
+ )
+ );
+ };
+
+ renderOverviewFacet = () => {
+ const { domain, selected } = this.props;
+ if (!hasBubbleChart(domain.name)) {
+ return null;
+ }
+ return (
+ <FacetItem
+ active={domain.name === selected}
+ disabled={false}
+ key={domain.name}
+ name={
+ <span id={`measure-overview-${domain.name}-name`}>
+ {translate('component_measures.domain_overview')}
+ </span>
+ }
+ onClick={this.props.onChange}
+ stat={<BubblesIcon size={14} />}
+ tooltip={translate('component_measures.domain_overview')}
+ value={domain.name}
+ />
+ );
+ };
+
+ render() {
+ const { domain } = this.props;
+ const helper = `component_measures.domain_facets.${domain.name}.help`;
+ const translatedHelper = translate(helper);
+ return (
+ <FacetBox property={domain.name}>
+ <FacetHeader
+ helper={helper !== translatedHelper ? translatedHelper : undefined}
+ name={getLocalizedMetricDomain(domain.name)}
+ onClick={this.handleHeaderClick}
+ open={this.props.open}
+ values={this.getValues()}
+ />
+
+ {this.props.open && (
+ <FacetItemsList>
+ {this.renderOverviewFacet()}
+ {this.renderItemsFacet()}
+ </FacetItemsList>
+ )}
+ </FacetBox>
+ );
+ }
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.
- */
-// @flow
-import React from 'react';
-import Measure from '../../../components/measure/Measure';
-import { isDiffMetric } from '../../../helpers/measures';
-/*:: import type { MeasureEnhanced } from '../../../components/measure/types'; */
-
-export default function FacetMeasureValue({ measure } /*: { measure: MeasureEnhanced } */) {
- if (isDiffMetric(measure.metric.key)) {
- return (
- <div
- className="domain-measures-value domain-measures-leak"
- id={`measure-${measure.metric.key}-leak`}>
- <Measure
- metricKey={measure.metric.key}
- metricType={measure.metric.type}
- value={measure.leak}
- />
- </div>
- );
- }
-
- return (
- <div className="domain-measures-value" id={`measure-${measure.metric.key}-value`}>
- <Measure
- metricKey={measure.metric.key}
- metricType={measure.metric.type}
- value={measure.value}
- />
- </div>
- );
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 Measure from '../../../components/measure/Measure';
+import { isDiffMetric } from '../../../helpers/measures';
+import { MeasureEnhanced } from '../../../app/types';
+
+interface Props {
+ measure: MeasureEnhanced;
+}
+
+export default function FacetMeasureValue({ measure }: Props) {
+ if (isDiffMetric(measure.metric.key)) {
+ return (
+ <div
+ className="domain-measures-value domain-measures-leak"
+ id={`measure-${measure.metric.key}-leak`}>
+ <Measure
+ metricKey={measure.metric.key}
+ metricType={measure.metric.type}
+ value={measure.leak}
+ />
+ </div>
+ );
+ }
+
+ return (
+ <div className="domain-measures-value" id={`measure-${measure.metric.key}-value`}>
+ <Measure
+ metricKey={measure.metric.key}
+ metricType={measure.metric.type}
+ value={measure.value}
+ />
+ </div>
+ );
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.
- */
-// @flow
-import React from 'react';
-import FacetBox from '../../../components/facet/FacetBox';
-import FacetItem from '../../../components/facet/FacetItem';
-import FacetItemsList from '../../../components/facet/FacetItemsList';
-import { translate } from '../../../helpers/l10n';
-
-/*:: type Props = {|
- onChange: (metric: string) => void,
- selected: string,
- value: string
-|}; */
-
-export default function ProjectOverviewFacet({ value, selected, onChange } /*: Props */) {
- const facetName = translate('component_measures.overview', value, 'facet');
- return (
- <FacetBox property={value}>
- <FacetItemsList>
- <FacetItem
- active={value === selected}
- disabled={false}
- key={value}
- name={
- <strong id={`measure-overview-${value}-name`} title={facetName}>
- {facetName}
- </strong>
- }
- onClick={onChange}
- value={value}
- />
- </FacetItemsList>
- </FacetBox>
- );
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 FacetBox from '../../../components/facet/FacetBox';
+import FacetItem from '../../../components/facet/FacetItem';
+import FacetItemsList from '../../../components/facet/FacetItemsList';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+ onChange: (metric: string) => void;
+ selected: string;
+ value: string;
+}
+
+export default function ProjectOverviewFacet({ value, selected, onChange }: Props) {
+ const facetName = translate('component_measures.overview', value, 'facet');
+ return (
+ <FacetBox property={value}>
+ <FacetItemsList>
+ <FacetItem
+ active={value === selected}
+ disabled={false}
+ key={value}
+ name={<strong id={`measure-overview-${value}-name`}>{facetName}</strong>}
+ onClick={onChange}
+ tooltip={facetName}
+ value={value}
+ />
+ </FacetItemsList>
+ </FacetBox>
+ );
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.
- */
-// @flow
-import React from 'react';
-import ProjectOverviewFacet from './ProjectOverviewFacet';
-import DomainFacet from './DomainFacet';
-import { getDefaultView, groupByDomains, KNOWN_DOMAINS, PROJECT_OVERVEW } from '../utils';
-/*:: import type { MeasureEnhanced } from '../../../components/measure/types'; */
-/*:: import type { Query } from '../types'; */
-
-/*:: type Props = {|
- measures: Array<MeasureEnhanced>,
- selectedMetric: string,
- updateQuery: Query => void
-|}; */
-
-/*:: type State = {|
- openFacets: { [string]: boolean }
-|}; */
-
-export default class Sidebar extends React.PureComponent {
- /*:: props: Props; */
- /*:: state: State; */
-
- constructor(props /*: Props */) {
- super(props);
- this.state = { openFacets: this.getOpenFacets({}, props) };
- }
-
- componentWillReceiveProps(nextProps /*: Props */) {
- if (nextProps.selectedMetric !== this.props.selectedMetric) {
- this.setState(state => this.getOpenFacets(state.openFacets, nextProps));
- }
- }
-
- getOpenFacets = (
- openFacets /*: { [string]: boolean } */,
- { measures, selectedMetric } /*: Props */
- ) => {
- const newOpenFacets = { ...openFacets };
- const measure = measures.find(measure => measure.metric.key === selectedMetric);
- if (measure && measure.metric && measure.metric.domain) {
- newOpenFacets[measure.metric.domain] = true;
- } else if (KNOWN_DOMAINS.includes(selectedMetric)) {
- newOpenFacets[selectedMetric] = true;
- }
- return newOpenFacets;
- };
-
- toggleFacet = (name /*: string */) => {
- this.setState(({ openFacets } /*: State */) => ({
- openFacets: { ...openFacets, [name]: !openFacets[name] }
- }));
- };
-
- resetSelection = (metric /*: string */) => ({ selected: null, view: getDefaultView(metric) });
-
- changeMetric = (metric /*: string */) =>
- this.props.updateQuery({ metric, ...this.resetSelection(metric) });
-
- render() {
- return (
- <div>
- <ProjectOverviewFacet
- onChange={this.changeMetric}
- selected={this.props.selectedMetric}
- value={PROJECT_OVERVEW}
- />
- {groupByDomains(this.props.measures).map(domain => (
- <DomainFacet
- domain={domain}
- key={domain.name}
- onChange={this.changeMetric}
- onToggle={this.toggleFacet}
- open={this.state.openFacets[domain.name] === true}
- selected={this.props.selectedMetric}
- />
- ))}
- </div>
- );
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 ProjectOverviewFacet from './ProjectOverviewFacet';
+import DomainFacet from './DomainFacet';
+import { getDefaultView, groupByDomains, KNOWN_DOMAINS, PROJECT_OVERVEW, Query } from '../utils';
+import { MeasureEnhanced } from '../../../app/types';
+
+interface Props {
+ measures: MeasureEnhanced[];
+ selectedMetric: string;
+ updateQuery: (query: Query) => void;
+}
+
+interface State {
+ openFacets: { [metric: string]: boolean };
+}
+
+export default class Sidebar extends React.PureComponent<Props, State> {
+ constructor(props: Props) {
+ super(props);
+ this.state = { openFacets: this.getOpenFacets({}, props) };
+ }
+
+ componentWillReceiveProps(nextProps: Props) {
+ if (nextProps.selectedMetric !== this.props.selectedMetric) {
+ this.setState(({ openFacets }) => ({
+ openFacets: this.getOpenFacets(openFacets, nextProps)
+ }));
+ }
+ }
+
+ getOpenFacets = (
+ openFacets: { [metric: string]: boolean },
+ { measures, selectedMetric }: Props
+ ) => {
+ const newOpenFacets = { ...openFacets };
+ const measure = measures.find(measure => measure.metric.key === selectedMetric);
+ if (measure && measure.metric && measure.metric.domain) {
+ newOpenFacets[measure.metric.domain] = true;
+ } else if (KNOWN_DOMAINS.includes(selectedMetric)) {
+ newOpenFacets[selectedMetric] = true;
+ }
+ return newOpenFacets;
+ };
+
+ toggleFacet = (name: string) => {
+ this.setState(({ openFacets }) => ({
+ openFacets: { ...openFacets, [name]: !openFacets[name] }
+ }));
+ };
+
+ resetSelection = (metric: string) => ({ selected: undefined, view: getDefaultView(metric) });
+
+ changeMetric = (metric: string) =>
+ this.props.updateQuery({ metric, ...this.resetSelection(metric) });
+
+ render() {
+ return (
+ <div>
+ <ProjectOverviewFacet
+ onChange={this.changeMetric}
+ selected={this.props.selectedMetric}
+ value={PROJECT_OVERVEW}
+ />
+ {groupByDomains(this.props.measures).map(domain => (
+ <DomainFacet
+ domain={domain}
+ key={domain.name}
+ onChange={this.changeMetric}
+ onToggle={this.toggleFacet}
+ open={this.state.openFacets[domain.name] === true}
+ selected={this.props.selectedMetric}
+ />
+ ))}
+ </div>
+ );
+ }
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.
- */
-// @flow
-import React from 'react';
-import { shallow } from 'enzyme';
-import DomainFacet from '../DomainFacet';
-
-const DOMAIN = {
- name: 'Reliability',
- measures: [
- {
- metric: {
- key: 'bugs',
- type: 'INT',
- name: 'Bugs',
- domain: 'Reliability'
- },
- value: '5',
- periods: [{ index: 1, value: '5' }],
- leak: '5'
- },
- {
- metric: {
- key: 'new_bugs',
- type: 'INT',
- name: 'New Bugs',
- domain: 'Reliability'
- },
- periods: [{ index: 1, value: '5' }],
- leak: '5'
- }
- ]
-};
-
-const PROPS = {
- onChange: () => {},
- onToggle: () => {},
- open: true,
- domain: DOMAIN,
- selected: 'foo'
-};
-
-it('should display facet item list', () => {
- expect(shallow(<DomainFacet {...PROPS} />)).toMatchSnapshot();
-});
-
-it('should display facet item list with bugs selected', () => {
- expect(shallow(<DomainFacet {...PROPS} selected="bugs" />)).toMatchSnapshot();
-});
-
-it('should render closed', () => {
- const wrapper = shallow(<DomainFacet {...PROPS} open={false} />);
- expect(wrapper.find('FacetItemsList')).toHaveLength(0);
-});
-
-it('should not display subtitles of new measures if there is none', () => {
- const domain = {
- name: 'Reliability',
- measures: [
- {
- metric: { key: 'bugs', type: 'INT', name: 'Bugs', domain: 'Reliability' },
- value: '5'
- }
- ]
- };
-
- expect(
- shallow(
- <DomainFacet
- domain={domain}
- onChange={() => {}}
- onToggle={() => {}}
- open={true}
- selected={'foo'}
- />
- )
- ).toMatchSnapshot();
-});
-
-it('should not display subtitles of new measures if there is none, even on last line', () => {
- const domain = {
- name: 'Reliability',
- measures: [
- {
- metric: { key: 'new_bugs', type: 'INT', name: 'New Bugs', domain: 'Reliability' },
- value: '5'
- }
- ]
- };
-
- expect(
- shallow(
- <DomainFacet
- domain={domain}
- onChange={() => {}}
- onToggle={() => {}}
- open={true}
- selected={'foo'}
- />
- )
- ).toMatchSnapshot();
-});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 DomainFacet from '../DomainFacet';
+
+const DOMAIN = {
+ name: 'Reliability',
+ measures: [
+ {
+ metric: {
+ id: '1',
+ key: 'bugs',
+ type: 'INT',
+ name: 'Bugs',
+ domain: 'Reliability'
+ },
+ value: '5',
+ periods: [{ index: 1, value: '5' }],
+ leak: '5'
+ },
+ {
+ metric: {
+ id: '2',
+ key: 'new_bugs',
+ type: 'INT',
+ name: 'New Bugs',
+ domain: 'Reliability'
+ },
+ periods: [{ index: 1, value: '5' }],
+ leak: '5'
+ }
+ ]
+};
+
+const PROPS = {
+ onChange: () => {},
+ onToggle: () => {},
+ open: true,
+ domain: DOMAIN,
+ selected: 'foo'
+};
+
+it('should display facet item list', () => {
+ expect(shallow(<DomainFacet {...PROPS} />)).toMatchSnapshot();
+});
+
+it('should display facet item list with bugs selected', () => {
+ expect(shallow(<DomainFacet {...PROPS} selected="bugs" />)).toMatchSnapshot();
+});
+
+it('should render closed', () => {
+ const wrapper = shallow(<DomainFacet {...PROPS} open={false} />);
+ expect(wrapper.find('FacetItemsList')).toHaveLength(0);
+});
+
+it('should not display subtitles of new measures if there is none', () => {
+ const domain = {
+ name: 'Reliability',
+ measures: [
+ {
+ metric: { id: '1', key: 'bugs', type: 'INT', name: 'Bugs', domain: 'Reliability' },
+ value: '5'
+ }
+ ]
+ };
+
+ expect(
+ shallow(
+ <DomainFacet
+ domain={domain}
+ onChange={() => {}}
+ onToggle={() => {}}
+ open={true}
+ selected={'foo'}
+ />
+ )
+ ).toMatchSnapshot();
+});
+
+it('should not display subtitles of new measures if there is none, even on last line', () => {
+ const domain = {
+ name: 'Reliability',
+ measures: [
+ {
+ metric: { id: '2', key: 'new_bugs', type: 'INT', name: 'New Bugs', domain: 'Reliability' },
+ value: '5'
+ }
+ ]
+ };
+
+ expect(
+ shallow(
+ <DomainFacet
+ domain={domain}
+ onChange={() => {}}
+ onToggle={() => {}}
+ open={true}
+ selected={'foo'}
+ />
+ )
+ ).toMatchSnapshot();
+});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.
- */
-// @flow
-import React from 'react';
-import { shallow } from 'enzyme';
-import FacetMeasureValue from '../FacetMeasureValue';
-
-const MEASURE = {
- metric: {
- key: 'bugs',
- type: 'INT',
- name: 'Bugs',
- domain: 'Reliability'
- },
- value: '5',
- periods: [{ index: 1, value: '5' }],
- leak: '5'
-};
-const LEAK_MEASURE = {
- metric: {
- key: 'new_bugs',
- type: 'INT',
- name: 'New Bugs',
- domain: 'Reliability'
- },
- periods: [{ index: 1, value: '5' }],
- leak: '5'
-};
-
-it('should display measure value', () => {
- expect(shallow(<FacetMeasureValue measure={MEASURE} />)).toMatchSnapshot();
-});
-
-it('should display leak measure value', () => {
- expect(shallow(<FacetMeasureValue measure={LEAK_MEASURE} />)).toMatchSnapshot();
-});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 FacetMeasureValue from '../FacetMeasureValue';
+
+const MEASURE = {
+ metric: {
+ id: '1',
+ key: 'bugs',
+ type: 'INT',
+ name: 'Bugs',
+ domain: 'Reliability'
+ },
+ value: '5',
+ periods: [{ index: 1, value: '5' }],
+ leak: '5'
+};
+const LEAK_MEASURE = {
+ metric: {
+ id: '2',
+ key: 'new_bugs',
+ type: 'INT',
+ name: 'New Bugs',
+ domain: 'Reliability'
+ },
+ periods: [{ index: 1, value: '5' }],
+ leak: '5'
+};
+
+it('should display measure value', () => {
+ expect(shallow(<FacetMeasureValue measure={MEASURE} />)).toMatchSnapshot();
+});
+
+it('should display leak measure value', () => {
+ expect(shallow(<FacetMeasureValue measure={LEAK_MEASURE} />)).toMatchSnapshot();
+});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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 React from 'react';
-import { shallow } from 'enzyme';
-import Sidebar from '../Sidebar';
-
-const MEASURES = [
- {
- metric: {
- key: 'lines_to_cover',
- type: 'INT',
- name: 'Lines to Cover',
- domain: 'Coverage'
- },
- value: '431',
- periods: [{ index: 1, value: '70' }],
- leak: '70'
- },
- {
- metric: {
- key: 'coverage',
- type: 'PERCENT',
- name: 'Coverage',
- domain: 'Coverage'
- },
- value: '99.3',
- periods: [{ index: 1, value: '0.0999999999999943' }],
- leak: '0.0999999999999943'
- },
- {
- metric: {
- key: 'duplicated_lines_density',
- type: 'PERCENT',
- name: 'Duplicated Lines (%)',
- domain: 'Duplications'
- },
- value: '3.2',
- periods: [{ index: 1, value: '0.0' }],
- leak: '0.0'
- }
-];
-
-const PROPS = {
- measures: MEASURES,
- selectedMetric: 'duplicated_lines_density',
- updateQuery: () => {}
-};
-
-it('should display two facets', () => {
- expect(shallow(<Sidebar {...PROPS} />)).toMatchSnapshot();
-});
-
-it('should correctly toggle facets', () => {
- const wrapper = shallow(<Sidebar {...PROPS} />);
- expect(wrapper.state('openFacets').bugs).toBeUndefined();
- wrapper.instance().toggleFacet('bugs');
- expect(wrapper.state('openFacets').bugs).toBeTruthy();
- wrapper.instance().toggleFacet('bugs');
- expect(wrapper.state('openFacets').bugs).toBeFalsy();
-});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 Sidebar from '../Sidebar';
+
+const MEASURES = [
+ {
+ metric: {
+ id: '1',
+ key: 'lines_to_cover',
+ type: 'INT',
+ name: 'Lines to Cover',
+ domain: 'Coverage'
+ },
+ value: '431',
+ periods: [{ index: 1, value: '70' }],
+ leak: '70'
+ },
+ {
+ metric: {
+ id: '2',
+ key: 'coverage',
+ type: 'PERCENT',
+ name: 'Coverage',
+ domain: 'Coverage'
+ },
+ value: '99.3',
+ periods: [{ index: 1, value: '0.0999999999999943' }],
+ leak: '0.0999999999999943'
+ },
+ {
+ metric: {
+ id: '3',
+ key: 'duplicated_lines_density',
+ type: 'PERCENT',
+ name: 'Duplicated Lines (%)',
+ domain: 'Duplications'
+ },
+ value: '3.2',
+ periods: [{ index: 1, value: '0.0' }],
+ leak: '0.0'
+ }
+];
+
+const PROPS = {
+ measures: MEASURES,
+ selectedMetric: 'duplicated_lines_density',
+ updateQuery: () => {}
+};
+
+it('should display two facets', () => {
+ expect(shallow(<Sidebar {...PROPS} />)).toMatchSnapshot();
+});
+
+it('should correctly toggle facets', () => {
+ const wrapper = shallow(<Sidebar {...PROPS} />);
+ expect(wrapper.state('openFacets').bugs).toBeUndefined();
+ (wrapper.instance() as Sidebar).toggleFacet('bugs');
+ expect(wrapper.state('openFacets').bugs).toBeTruthy();
+ (wrapper.instance() as Sidebar).toggleFacet('bugs');
+ expect(wrapper.state('openFacets').bugs).toBeFalsy();
+});
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should display facet item list 1`] = `
-<FacetBox
- property="Reliability"
->
- <FacetHeader
- name="Reliability"
- onClick={[Function]}
- open={true}
- values={Array []}
- />
- <FacetItemsList>
- <FacetItem
- active={false}
- disabled={false}
- halfWidth={false}
- key="Reliability"
- loading={false}
- name={
- <span
- id="measure-overview-Reliability-name"
- >
- component_measures.domain_overview
- </span>
- }
- onClick={[Function]}
- stat={
- <BubblesIcon
- size={14}
- />
- }
- value="Reliability"
- />
- <span
- className="facet search-navigator-facet facet-category"
- key="new_code_category"
- >
- <span
- className="facet-name"
- >
- component_measures.facet_category.new_code_category
- </span>
- </span>
- <FacetItem
- active={false}
- disabled={false}
- halfWidth={false}
- key="new_bugs"
- loading={false}
- name={
- <span
- className="big-spacer-left"
- id="measure-new_bugs-name"
- >
- New Bugs
- </span>
- }
- onClick={[Function]}
- stat={
- <FacetMeasureValue
- measure={
- Object {
- "leak": "5",
- "metric": Object {
- "domain": "Reliability",
- "key": "new_bugs",
- "name": "New Bugs",
- "type": "INT",
- },
- "periods": Array [
- Object {
- "index": 1,
- "value": "5",
- },
- ],
- }
- }
- />
- }
- value="new_bugs"
- />
- <span
- className="facet search-navigator-facet facet-category"
- key="overall_category"
- >
- <span
- className="facet-name"
- >
- component_measures.facet_category.overall_category
- </span>
- </span>
- <FacetItem
- active={false}
- disabled={false}
- halfWidth={false}
- key="bugs"
- loading={false}
- name={
- <span
- className="big-spacer-left"
- id="measure-bugs-name"
- >
- Bugs
- </span>
- }
- onClick={[Function]}
- stat={
- <FacetMeasureValue
- measure={
- Object {
- "leak": "5",
- "metric": Object {
- "domain": "Reliability",
- "key": "bugs",
- "name": "Bugs",
- "type": "INT",
- },
- "periods": Array [
- Object {
- "index": 1,
- "value": "5",
- },
- ],
- "value": "5",
- }
- }
- />
- }
- value="bugs"
- />
- </FacetItemsList>
-</FacetBox>
-`;
-
-exports[`should display facet item list with bugs selected 1`] = `
-<FacetBox
- property="Reliability"
->
- <FacetHeader
- name="Reliability"
- onClick={[Function]}
- open={true}
- values={
- Array [
- "Bugs",
- ]
- }
- />
- <FacetItemsList>
- <FacetItem
- active={false}
- disabled={false}
- halfWidth={false}
- key="Reliability"
- loading={false}
- name={
- <span
- id="measure-overview-Reliability-name"
- >
- component_measures.domain_overview
- </span>
- }
- onClick={[Function]}
- stat={
- <BubblesIcon
- size={14}
- />
- }
- value="Reliability"
- />
- <span
- className="facet search-navigator-facet facet-category"
- key="new_code_category"
- >
- <span
- className="facet-name"
- >
- component_measures.facet_category.new_code_category
- </span>
- </span>
- <FacetItem
- active={false}
- disabled={false}
- halfWidth={false}
- key="new_bugs"
- loading={false}
- name={
- <span
- className="big-spacer-left"
- id="measure-new_bugs-name"
- >
- New Bugs
- </span>
- }
- onClick={[Function]}
- stat={
- <FacetMeasureValue
- measure={
- Object {
- "leak": "5",
- "metric": Object {
- "domain": "Reliability",
- "key": "new_bugs",
- "name": "New Bugs",
- "type": "INT",
- },
- "periods": Array [
- Object {
- "index": 1,
- "value": "5",
- },
- ],
- }
- }
- />
- }
- value="new_bugs"
- />
- <span
- className="facet search-navigator-facet facet-category"
- key="overall_category"
- >
- <span
- className="facet-name"
- >
- component_measures.facet_category.overall_category
- </span>
- </span>
- <FacetItem
- active={true}
- disabled={false}
- halfWidth={false}
- key="bugs"
- loading={false}
- name={
- <span
- className="big-spacer-left"
- id="measure-bugs-name"
- >
- Bugs
- </span>
- }
- onClick={[Function]}
- stat={
- <FacetMeasureValue
- measure={
- Object {
- "leak": "5",
- "metric": Object {
- "domain": "Reliability",
- "key": "bugs",
- "name": "Bugs",
- "type": "INT",
- },
- "periods": Array [
- Object {
- "index": 1,
- "value": "5",
- },
- ],
- "value": "5",
- }
- }
- />
- }
- value="bugs"
- />
- </FacetItemsList>
-</FacetBox>
-`;
-
-exports[`should not display subtitles of new measures if there is none 1`] = `
-<FacetBox
- property="Reliability"
->
- <FacetHeader
- name="Reliability"
- onClick={[Function]}
- open={true}
- values={Array []}
- />
- <FacetItemsList>
- <FacetItem
- active={false}
- disabled={false}
- halfWidth={false}
- key="Reliability"
- loading={false}
- name={
- <span
- id="measure-overview-Reliability-name"
- >
- component_measures.domain_overview
- </span>
- }
- onClick={[Function]}
- stat={
- <BubblesIcon
- size={14}
- />
- }
- value="Reliability"
- />
- <span
- className="facet search-navigator-facet facet-category"
- key="overall_category"
- >
- <span
- className="facet-name"
- >
- component_measures.facet_category.overall_category
- </span>
- </span>
- <FacetItem
- active={false}
- disabled={false}
- halfWidth={false}
- key="bugs"
- loading={false}
- name={
- <span
- className="big-spacer-left"
- id="measure-bugs-name"
- >
- Bugs
- </span>
- }
- onClick={[Function]}
- stat={
- <FacetMeasureValue
- measure={
- Object {
- "metric": Object {
- "domain": "Reliability",
- "key": "bugs",
- "name": "Bugs",
- "type": "INT",
- },
- "value": "5",
- }
- }
- />
- }
- value="bugs"
- />
- </FacetItemsList>
-</FacetBox>
-`;
-
-exports[`should not display subtitles of new measures if there is none, even on last line 1`] = `
-<FacetBox
- property="Reliability"
->
- <FacetHeader
- name="Reliability"
- onClick={[Function]}
- open={true}
- values={Array []}
- />
- <FacetItemsList>
- <FacetItem
- active={false}
- disabled={false}
- halfWidth={false}
- key="Reliability"
- loading={false}
- name={
- <span
- id="measure-overview-Reliability-name"
- >
- component_measures.domain_overview
- </span>
- }
- onClick={[Function]}
- stat={
- <BubblesIcon
- size={14}
- />
- }
- value="Reliability"
- />
- <span
- className="facet search-navigator-facet facet-category"
- key="new_code_category"
- >
- <span
- className="facet-name"
- >
- component_measures.facet_category.new_code_category
- </span>
- </span>
- <FacetItem
- active={false}
- disabled={false}
- halfWidth={false}
- key="new_bugs"
- loading={false}
- name={
- <span
- className="big-spacer-left"
- id="measure-new_bugs-name"
- >
- New Bugs
- </span>
- }
- onClick={[Function]}
- stat={
- <FacetMeasureValue
- measure={
- Object {
- "metric": Object {
- "domain": "Reliability",
- "key": "new_bugs",
- "name": "New Bugs",
- "type": "INT",
- },
- "value": "5",
- }
- }
- />
- }
- value="new_bugs"
- />
- </FacetItemsList>
-</FacetBox>
-`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should display facet item list 1`] = `
+<FacetBox
+ property="Reliability"
+>
+ <FacetHeader
+ name="Reliability"
+ onClick={[Function]}
+ open={true}
+ values={Array []}
+ />
+ <FacetItemsList>
+ <FacetItem
+ active={false}
+ disabled={false}
+ halfWidth={false}
+ key="Reliability"
+ loading={false}
+ name={
+ <span
+ id="measure-overview-Reliability-name"
+ >
+ component_measures.domain_overview
+ </span>
+ }
+ onClick={[Function]}
+ stat={
+ <BubblesIcon
+ size={14}
+ />
+ }
+ tooltip="component_measures.domain_overview"
+ value="Reliability"
+ />
+ <span
+ className="facet search-navigator-facet facet-category"
+ key="new_code_category"
+ >
+ <span
+ className="facet-name"
+ >
+ component_measures.facet_category.new_code_category
+ </span>
+ </span>
+ <FacetItem
+ active={false}
+ disabled={false}
+ halfWidth={false}
+ key="new_bugs"
+ loading={false}
+ name={
+ <span
+ className="big-spacer-left"
+ id="measure-new_bugs-name"
+ >
+ New Bugs
+ </span>
+ }
+ onClick={[Function]}
+ stat={
+ <FacetMeasureValue
+ measure={
+ Object {
+ "leak": "5",
+ "metric": Object {
+ "domain": "Reliability",
+ "id": "2",
+ "key": "new_bugs",
+ "name": "New Bugs",
+ "type": "INT",
+ },
+ "periods": Array [
+ Object {
+ "index": 1,
+ "value": "5",
+ },
+ ],
+ }
+ }
+ />
+ }
+ tooltip="New Bugs"
+ value="new_bugs"
+ />
+ <span
+ className="facet search-navigator-facet facet-category"
+ key="overall_category"
+ >
+ <span
+ className="facet-name"
+ >
+ component_measures.facet_category.overall_category
+ </span>
+ </span>
+ <FacetItem
+ active={false}
+ disabled={false}
+ halfWidth={false}
+ key="bugs"
+ loading={false}
+ name={
+ <span
+ className="big-spacer-left"
+ id="measure-bugs-name"
+ >
+ Bugs
+ </span>
+ }
+ onClick={[Function]}
+ stat={
+ <FacetMeasureValue
+ measure={
+ Object {
+ "leak": "5",
+ "metric": Object {
+ "domain": "Reliability",
+ "id": "1",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "INT",
+ },
+ "periods": Array [
+ Object {
+ "index": 1,
+ "value": "5",
+ },
+ ],
+ "value": "5",
+ }
+ }
+ />
+ }
+ tooltip="Bugs"
+ value="bugs"
+ />
+ </FacetItemsList>
+</FacetBox>
+`;
+
+exports[`should display facet item list with bugs selected 1`] = `
+<FacetBox
+ property="Reliability"
+>
+ <FacetHeader
+ name="Reliability"
+ onClick={[Function]}
+ open={true}
+ values={
+ Array [
+ "Bugs",
+ ]
+ }
+ />
+ <FacetItemsList>
+ <FacetItem
+ active={false}
+ disabled={false}
+ halfWidth={false}
+ key="Reliability"
+ loading={false}
+ name={
+ <span
+ id="measure-overview-Reliability-name"
+ >
+ component_measures.domain_overview
+ </span>
+ }
+ onClick={[Function]}
+ stat={
+ <BubblesIcon
+ size={14}
+ />
+ }
+ tooltip="component_measures.domain_overview"
+ value="Reliability"
+ />
+ <span
+ className="facet search-navigator-facet facet-category"
+ key="new_code_category"
+ >
+ <span
+ className="facet-name"
+ >
+ component_measures.facet_category.new_code_category
+ </span>
+ </span>
+ <FacetItem
+ active={false}
+ disabled={false}
+ halfWidth={false}
+ key="new_bugs"
+ loading={false}
+ name={
+ <span
+ className="big-spacer-left"
+ id="measure-new_bugs-name"
+ >
+ New Bugs
+ </span>
+ }
+ onClick={[Function]}
+ stat={
+ <FacetMeasureValue
+ measure={
+ Object {
+ "leak": "5",
+ "metric": Object {
+ "domain": "Reliability",
+ "id": "2",
+ "key": "new_bugs",
+ "name": "New Bugs",
+ "type": "INT",
+ },
+ "periods": Array [
+ Object {
+ "index": 1,
+ "value": "5",
+ },
+ ],
+ }
+ }
+ />
+ }
+ tooltip="New Bugs"
+ value="new_bugs"
+ />
+ <span
+ className="facet search-navigator-facet facet-category"
+ key="overall_category"
+ >
+ <span
+ className="facet-name"
+ >
+ component_measures.facet_category.overall_category
+ </span>
+ </span>
+ <FacetItem
+ active={true}
+ disabled={false}
+ halfWidth={false}
+ key="bugs"
+ loading={false}
+ name={
+ <span
+ className="big-spacer-left"
+ id="measure-bugs-name"
+ >
+ Bugs
+ </span>
+ }
+ onClick={[Function]}
+ stat={
+ <FacetMeasureValue
+ measure={
+ Object {
+ "leak": "5",
+ "metric": Object {
+ "domain": "Reliability",
+ "id": "1",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "INT",
+ },
+ "periods": Array [
+ Object {
+ "index": 1,
+ "value": "5",
+ },
+ ],
+ "value": "5",
+ }
+ }
+ />
+ }
+ tooltip="Bugs"
+ value="bugs"
+ />
+ </FacetItemsList>
+</FacetBox>
+`;
+
+exports[`should not display subtitles of new measures if there is none 1`] = `
+<FacetBox
+ property="Reliability"
+>
+ <FacetHeader
+ name="Reliability"
+ onClick={[Function]}
+ open={true}
+ values={Array []}
+ />
+ <FacetItemsList>
+ <FacetItem
+ active={false}
+ disabled={false}
+ halfWidth={false}
+ key="Reliability"
+ loading={false}
+ name={
+ <span
+ id="measure-overview-Reliability-name"
+ >
+ component_measures.domain_overview
+ </span>
+ }
+ onClick={[Function]}
+ stat={
+ <BubblesIcon
+ size={14}
+ />
+ }
+ tooltip="component_measures.domain_overview"
+ value="Reliability"
+ />
+ <span
+ className="facet search-navigator-facet facet-category"
+ key="overall_category"
+ >
+ <span
+ className="facet-name"
+ >
+ component_measures.facet_category.overall_category
+ </span>
+ </span>
+ <FacetItem
+ active={false}
+ disabled={false}
+ halfWidth={false}
+ key="bugs"
+ loading={false}
+ name={
+ <span
+ className="big-spacer-left"
+ id="measure-bugs-name"
+ >
+ Bugs
+ </span>
+ }
+ onClick={[Function]}
+ stat={
+ <FacetMeasureValue
+ measure={
+ Object {
+ "metric": Object {
+ "domain": "Reliability",
+ "id": "1",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "INT",
+ },
+ "value": "5",
+ }
+ }
+ />
+ }
+ tooltip="Bugs"
+ value="bugs"
+ />
+ </FacetItemsList>
+</FacetBox>
+`;
+
+exports[`should not display subtitles of new measures if there is none, even on last line 1`] = `
+<FacetBox
+ property="Reliability"
+>
+ <FacetHeader
+ name="Reliability"
+ onClick={[Function]}
+ open={true}
+ values={Array []}
+ />
+ <FacetItemsList>
+ <FacetItem
+ active={false}
+ disabled={false}
+ halfWidth={false}
+ key="Reliability"
+ loading={false}
+ name={
+ <span
+ id="measure-overview-Reliability-name"
+ >
+ component_measures.domain_overview
+ </span>
+ }
+ onClick={[Function]}
+ stat={
+ <BubblesIcon
+ size={14}
+ />
+ }
+ tooltip="component_measures.domain_overview"
+ value="Reliability"
+ />
+ <span
+ className="facet search-navigator-facet facet-category"
+ key="new_code_category"
+ >
+ <span
+ className="facet-name"
+ >
+ component_measures.facet_category.new_code_category
+ </span>
+ </span>
+ <FacetItem
+ active={false}
+ disabled={false}
+ halfWidth={false}
+ key="new_bugs"
+ loading={false}
+ name={
+ <span
+ className="big-spacer-left"
+ id="measure-new_bugs-name"
+ >
+ New Bugs
+ </span>
+ }
+ onClick={[Function]}
+ stat={
+ <FacetMeasureValue
+ measure={
+ Object {
+ "metric": Object {
+ "domain": "Reliability",
+ "id": "2",
+ "key": "new_bugs",
+ "name": "New Bugs",
+ "type": "INT",
+ },
+ "value": "5",
+ }
+ }
+ />
+ }
+ tooltip="New Bugs"
+ value="new_bugs"
+ />
+ </FacetItemsList>
+</FacetBox>
+`;
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should display leak measure value 1`] = `
-<div
- className="domain-measures-value domain-measures-leak"
- id="measure-new_bugs-leak"
->
- <Measure
- metricKey="new_bugs"
- metricType="INT"
- value="5"
- />
-</div>
-`;
-
-exports[`should display measure value 1`] = `
-<div
- className="domain-measures-value"
- id="measure-bugs-value"
->
- <Measure
- metricKey="bugs"
- metricType="INT"
- value="5"
- />
-</div>
-`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should display leak measure value 1`] = `
+<div
+ className="domain-measures-value domain-measures-leak"
+ id="measure-new_bugs-leak"
+>
+ <Measure
+ metricKey="new_bugs"
+ metricType="INT"
+ value="5"
+ />
+</div>
+`;
+
+exports[`should display measure value 1`] = `
+<div
+ className="domain-measures-value"
+ id="measure-bugs-value"
+>
+ <Measure
+ metricKey="bugs"
+ metricType="INT"
+ value="5"
+ />
+</div>
+`;
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should display two facets 1`] = `
-<div>
- <ProjectOverviewFacet
- onChange={[Function]}
- selected="duplicated_lines_density"
- value="project_overview"
- />
- <DomainFacet
- domain={
- Object {
- "measures": Array [
- Object {
- "leak": "70",
- "metric": Object {
- "domain": "Coverage",
- "key": "lines_to_cover",
- "name": "Lines to Cover",
- "type": "INT",
- },
- "periods": Array [
- Object {
- "index": 1,
- "value": "70",
- },
- ],
- "value": "431",
- },
- Object {
- "leak": "0.0999999999999943",
- "metric": Object {
- "domain": "Coverage",
- "key": "coverage",
- "name": "Coverage",
- "type": "PERCENT",
- },
- "periods": Array [
- Object {
- "index": 1,
- "value": "0.0999999999999943",
- },
- ],
- "value": "99.3",
- },
- ],
- "name": "Coverage",
- }
- }
- key="Coverage"
- onChange={[Function]}
- onToggle={[Function]}
- open={false}
- selected="duplicated_lines_density"
- />
- <DomainFacet
- domain={
- Object {
- "measures": Array [
- Object {
- "leak": "0.0",
- "metric": Object {
- "domain": "Duplications",
- "key": "duplicated_lines_density",
- "name": "Duplicated Lines (%)",
- "type": "PERCENT",
- },
- "periods": Array [
- Object {
- "index": 1,
- "value": "0.0",
- },
- ],
- "value": "3.2",
- },
- ],
- "name": "Duplications",
- }
- }
- key="Duplications"
- onChange={[Function]}
- onToggle={[Function]}
- open={true}
- selected="duplicated_lines_density"
- />
-</div>
-`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should display two facets 1`] = `
+<div>
+ <ProjectOverviewFacet
+ onChange={[Function]}
+ selected="duplicated_lines_density"
+ value="project_overview"
+ />
+ <DomainFacet
+ domain={
+ Object {
+ "measures": Array [
+ Object {
+ "leak": "70",
+ "metric": Object {
+ "domain": "Coverage",
+ "id": "1",
+ "key": "lines_to_cover",
+ "name": "Lines to Cover",
+ "type": "INT",
+ },
+ "periods": Array [
+ Object {
+ "index": 1,
+ "value": "70",
+ },
+ ],
+ "value": "431",
+ },
+ Object {
+ "leak": "0.0999999999999943",
+ "metric": Object {
+ "domain": "Coverage",
+ "id": "2",
+ "key": "coverage",
+ "name": "Coverage",
+ "type": "PERCENT",
+ },
+ "periods": Array [
+ Object {
+ "index": 1,
+ "value": "0.0999999999999943",
+ },
+ ],
+ "value": "99.3",
+ },
+ ],
+ "name": "Coverage",
+ }
+ }
+ key="Coverage"
+ onChange={[Function]}
+ onToggle={[Function]}
+ open={false}
+ selected="duplicated_lines_density"
+ />
+ <DomainFacet
+ domain={
+ Object {
+ "measures": Array [
+ Object {
+ "leak": "0.0",
+ "metric": Object {
+ "domain": "Duplications",
+ "id": "3",
+ "key": "duplicated_lines_density",
+ "name": "Duplicated Lines (%)",
+ "type": "PERCENT",
+ },
+ "periods": Array [
+ Object {
+ "index": 1,
+ "value": "0.0",
+ },
+ ],
+ "value": "3.2",
+ },
+ ],
+ "name": "Duplications",
+ }
+ }
+ key="Duplications"
+ onChange={[Function]}
+ onToggle={[Function]}
+ open={true}
+ selected="duplicated_lines_density"
+ />
+</div>
+`;
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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 type { Measure, MeasureEnhanced } from '../../components/measure/types'; */
-
-/*:: type ComponentIntern = {
- isFavorite?: boolean,
- isRecentlyBrowsed?: boolean,
- key: string,
- match?: string,
- name: string,
- organization?: string,
- project?: string,
- qualifier: string
-}; */
-
-/*:: export type Component = ComponentIntern & { measures?: Array<Measure> }; */
-
-/*:: export type ComponentEnhanced = ComponentIntern & {
- value?: ?string,
- leak?: ?string,
- measures: Array<MeasureEnhanced>
-}; */
-
-/*:: export type Paging = {
- pageIndex: number,
- pageSize: number,
- total: number
-}; */
-
-/*:: export type Period = {
- index: number,
- date: string,
- mode: string,
- parameter?: string
-}; */
-
-/*:: export type Query = {
- metric: ?string,
- selected: ?string,
- view: string
-}; */
]);
}
-export function addMeasureCategories(
- domainName: string,
- measures: MeasureEnhanced[]
-) /*: Array<any> */ {
+export function addMeasureCategories(domainName: string, measures: MeasureEnhanced[]) {
const categories = domains[domainName] && domains[domainName].categories;
if (categories && categories.length > 0) {
return [...categories, ...measures];
}));
return sortBy(domains, [
- (domain: { name: string; measure: MeasureEnhanced[] }) => {
+ (domain: { name: string; measures: MeasureEnhanced[] }) => {
const idx = KNOWN_DOMAINS.indexOf(domain.name);
return idx >= 0 ? idx : KNOWN_DOMAINS.length;
},
x: metrics[conf.x],
y: metrics[conf.y],
size: metrics[conf.size],
- colors: conf.colors ? conf.colors.map(color => metrics[color]) : null
+ colors: conf.colors && conf.colors.map(color => metrics[color])
};
}
import DateFormatter, { longFormatterOption } from '../../../components/intl/DateFormatter';
import DateTimeFormatter from '../../../components/intl/DateTimeFormatter';
import Tooltip from '../../../components/controls/Tooltip';
-import { getPeriodDate, getPeriodLabel, Period, PeriodMode } from '../../../helpers/periods';
+import { getPeriodDate, getPeriodLabel } from '../../../helpers/periods';
import { translateWithParameters } from '../../../helpers/l10n';
import { differenceInDays } from '../../../helpers/dates';
+import { Period, PeriodMode } from '../../../app/types';
interface Props {
period: Period;
import { getAllTimeMachineData, History } from '../../../api/time-machine';
import { parseDate } from '../../../helpers/dates';
import { enhanceMeasuresWithMetrics } from '../../../helpers/measures';
-import { getLeakPeriod, Period } from '../../../helpers/periods';
+import { getLeakPeriod } from '../../../helpers/periods';
import { get } from '../../../helpers/storage';
import { METRICS, HISTORY_METRICS_LIST } from '../utils';
import {
} from '../../../helpers/branches';
import { fetchMetrics } from '../../../store/rootActions';
import { getMetrics, Store } from '../../../store/rootReducer';
-import { BranchLike, Component, Metric, MeasureEnhanced } from '../../../app/types';
+import { BranchLike, Component, Metric, MeasureEnhanced, Period } from '../../../app/types';
import { translate } from '../../../helpers/l10n';
import '../styles.css';
import * as React from 'react';
import { shallow } from 'enzyme';
import LeakPeriodLegend from '../LeakPeriodLegend';
-import { PeriodMode, Period } from '../../../../helpers/periods';
import { differenceInDays } from '../../../../helpers/dates';
+import { Period, PeriodMode } from '../../../../app/types';
jest.mock('../../../../helpers/dates', () => {
const dates = require.requireActual('../../../../helpers/dates');
getRatingTooltip
} from '../../../helpers/measures';
import { getLocalizedMetricName } from '../../../helpers/l10n';
-import { getPeriodDate, Period } from '../../../helpers/periods';
+import { getPeriodDate } from '../../../helpers/periods';
import {
getComponentDrilldownUrl,
getComponentIssuesUrl,
getMeasureHistoryUrl
} from '../../../helpers/urls';
-import { Component, BranchLike, MeasureEnhanced } from '../../../app/types';
+import { Component, BranchLike, MeasureEnhanced, Period } from '../../../app/types';
import { History } from '../../../api/time-machine';
import { getBranchLikeQuery } from '../../../helpers/branches';
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+/* eslint-disable camelcase */
import * as React from 'react';
import { shallow } from 'enzyme';
import ProjectCardLeak from '../ProjectCardLeak';
SourceViewerFile
} from '../../app/types';
import { isSameBranchLike, getBranchLikeQuery } from '../../helpers/branches';
-import { parseDate } from '../../helpers/dates';
import { translate } from '../../helpers/l10n';
import './styles.css';
import { sortBy, uniq } from 'lodash';
import Tooltip from '../controls/Tooltip';
import { translate } from '../../helpers/l10n';
+import { Location } from '../../helpers/urls';
import './BubbleChart.css';
const TICKS_COUNT = 5;
-interface BubbleProps {
+interface BubbleProps<T> {
color?: string;
- link?: string;
- onClick?: (link?: string) => void;
+ link?: string | Location;
+ onClick?: (ref?: T) => void;
+ data?: T;
r: number;
scale: number;
tooltip?: string | React.ReactNode;
y: number;
}
-export class Bubble extends React.PureComponent<BubbleProps> {
+export class Bubble<T> extends React.PureComponent<BubbleProps<T>> {
handleClick = (event: React.MouseEvent<SVGCircleElement>) => {
if (this.props.onClick) {
event.stopPropagation();
event.preventDefault();
- this.props.onClick(this.props.link);
+ this.props.onClick(this.props.data);
}
};
}
}
-interface Item {
+export interface BubbleItem<T> {
color?: string;
key?: string;
- link?: any;
+ link?: string | Location;
+ data?: T;
size: number;
tooltip?: React.ReactNode;
x: number;
y: number;
}
-interface Props {
+interface Props<T> {
displayXGrid?: boolean;
displayXTicks?: boolean;
displayYGrid?: boolean;
formatXTick?: (tick: number) => string;
formatYTick?: (tick: number) => string;
height: number;
- items: Item[];
- onBubbleClick?: (link?: string) => void;
+ items: BubbleItem<T>[];
+ onBubbleClick?: (ref?: T) => void;
padding?: [number, number, number, number];
sizeDomain?: [number, number];
sizeRange?: [number, number];
type Scale = ScaleLinear<number, number>;
-export default class BubbleChart extends React.Component<Props, State> {
+export default class BubbleChart<T> extends React.Component<Props<T>, State> {
node: SVGSVGElement | null = null;
selection: any = null;
transform: any = null;
sizeRange: [5, 45]
};
- constructor(props: Props) {
+ constructor(props: Props<T>) {
super(props);
this.state = { transform: { x: 0, y: 0, k: 1 } };
}
key={item.key || index}
link={item.link}
onClick={this.props.onBubbleClick}
+ data={item.data}
r={sizeScale(item.size)}
scale={1 / transform.k}
tooltip={item.tooltip}
it('should render bubbles with click handlers', () => {
const onClick = jest.fn();
- const items = [{ x: 1, y: 10, size: 7, link: 'foo' }, { x: 2, y: 30, size: 5, link: 'bar' }];
+ const items = [{ x: 1, y: 10, size: 7, data: 'foo' }, { x: 2, y: 30, size: 5, data: 'bar' }];
const chart = mount(<BubbleChart height={100} items={items} onBubbleClick={onClick} />);
chart.find(Bubble).forEach(bubble => expect(bubble).toMatchSnapshot());
});
exports[`should render bubbles with click handlers 1`] = `
<Bubble
+ data="foo"
key="0"
- link="foo"
onClick={[MockFunction]}
r={45}
scale={1}
exports[`should render bubbles with click handlers 2`] = `
<Bubble
+ data="bar"
key="1"
- link="bar"
onClick={[MockFunction]}
r={33.57142857142857}
scale={1}
*/
import { getRatingTooltip as nextGetRatingTooltip, isDiffMetric } from '../../helpers/measures';
import { Metric, Measure, MeasureEnhanced } from '../../app/types';
+import { getLeakPeriod } from '../../helpers/periods';
const KNOWN_RATINGS = ['sqale_rating', 'reliability_rating', 'security_rating'];
if (!measure || !measure.periods) {
return undefined;
}
- const period = measure.periods.find(period => period.index === 1);
+ const period = getLeakPeriod(measure.periods);
return period && period.value;
}
}
}
-export function splitPath(path: string): { head: string; tail: string } | null {
- if (typeof path === 'string') {
- const tokens = path.split('/');
- return {
- head: tokens.slice(0, -1).join('/'),
- tail: tokens[tokens.length - 1]
- };
- } else {
- return null;
- }
+export function splitPath(path: string) {
+ const tokens = path.split('/');
+ return {
+ head: tokens.slice(0, -1).join('/'),
+ tail: tokens[tokens.length - 1]
+ };
}
export function limitComponentName(str: string, limit = 30): string {
*/
import { translate, translateWithParameters } from './l10n';
import { parseDate } from './dates';
+import { Period, PeriodMode, PeriodMeasure } from '../app/types';
-export enum PeriodMode {
- Days = 'days',
- Date = 'date',
- Version = 'version',
- PreviousAnalysis = 'previous_analysis',
- PreviousVersion = 'previous_version'
-}
-
-export interface Period {
- date: string;
- index: number;
- mode: PeriodMode;
- modeParam?: string;
- parameter?: string;
-}
-
-function getPeriod(periods: Period[] | undefined, index: number) {
+function getPeriod<T extends Period | PeriodMeasure>(periods: T[] | undefined, index: number) {
if (!Array.isArray(periods)) {
return undefined;
}
-
return periods.find(period => period.index === index);
}
-export function getLeakPeriod(periods: Period[] | undefined) {
+export function getLeakPeriod<T extends Period | PeriodMeasure>(periods: T[] | undefined) {
return getPeriod(periods, 1);
}