diff options
21 files changed, 650 insertions, 238 deletions
diff --git a/server/sonar-web/src/main/js/api/measures.ts b/server/sonar-web/src/main/js/api/measures.ts index b5d1e649525..1102c39870c 100644 --- a/server/sonar-web/src/main/js/api/measures.ts +++ b/server/sonar-web/src/main/js/api/measures.ts @@ -19,8 +19,14 @@ */ import { getJSON, RequestData, postJSON, post } from '../helpers/request'; import throwGlobalError from '../app/utils/throwGlobalError'; -import { Measure, MeasurePeriod } from '../helpers/measures'; -import { Metric, CustomMeasure, Paging, BranchParameters } from '../app/types'; +import { + Metric, + CustomMeasure, + Paging, + BranchParameters, + Measure, + MeasurePeriod +} from '../app/types'; import { Period } from '../helpers/periods'; export function getMeasures( diff --git a/server/sonar-web/src/main/js/app/types.ts b/server/sonar-web/src/main/js/app/types.ts index 8a1da4194d6..62ff786b531 100644 --- a/server/sonar-web/src/main/js/app/types.ts +++ b/server/sonar-web/src/main/js/app/types.ts @@ -1,5 +1,3 @@ -import { Measure, MeasureEnhanced } from '../helpers/measures'; - /* * SonarQube * Copyright (C) 2009-2018 SonarSource SA @@ -101,6 +99,28 @@ export interface ComponentQualityProfile { name: string; } +interface ComponentMeasureIntern { + isFavorite?: boolean; + isRecentlyBrowsed?: boolean; + key: string; + match?: string; + name: string; + organization?: string; + project?: string; + qualifier: string; + refKey?: string; +} + +export interface ComponentMeasure extends ComponentMeasureIntern { + measures?: Measure[]; +} + +export interface ComponentMeasureEnhanced extends ComponentMeasureIntern { + value?: string; + leak?: string; + measures: MeasureEnhanced[]; +} + export interface Condition { error: string; id: number; @@ -342,26 +362,25 @@ export interface MainBranch extends Branch { status?: { qualityGateStatus: string }; } -interface ComponentMeasureIntern { - isFavorite?: boolean; - isRecentlyBrowsed?: boolean; - key: string; - match?: string; - name: string; - organization?: string; - project?: string; - qualifier: string; - refKey?: string; +export interface MeasurePeriod { + bestValue?: boolean; + index: number; + value: string; } -export interface ComponentMeasure extends ComponentMeasureIntern { - measures?: Measure[]; +interface MeasureIntern { + bestValue?: boolean; + periods?: MeasurePeriod[]; + value?: string; } -export interface ComponentMeasureEnhanced extends ComponentMeasureIntern { - value?: string; +export interface Measure extends MeasureIntern { + metric: string; +} + +export interface MeasureEnhanced extends MeasureIntern { + metric: Metric; leak?: string; - measures: MeasureEnhanced[]; } export interface Metric { diff --git a/server/sonar-web/src/main/js/apps/code/types.ts b/server/sonar-web/src/main/js/apps/code/types.ts index a0b6459a3d3..f8fbc5af202 100644 --- a/server/sonar-web/src/main/js/apps/code/types.ts +++ b/server/sonar-web/src/main/js/apps/code/types.ts @@ -17,8 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ - -import { Measure } from '../../helpers/measures'; +import { Measure } from '../../app/types'; export interface Component extends Breadcrumb { branch?: string; diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.tsx index 6bdd4d6f190..9472307bc7e 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.tsx @@ -33,15 +33,18 @@ import { getComponentTree } from '../../../api/components'; import { complementary } from '../config/complementary'; import { enhanceComponent, isFileType, isViewType } from '../utils'; import { getProjectUrl } from '../../../helpers/urls'; -import { isDiffMetric, MeasureEnhanced } from '../../../helpers/measures'; +import { isDiffMetric } from '../../../helpers/measures'; import { isSameBranchLike, getBranchLikeQuery } from '../../../helpers/branches'; import DeferredSpinner from '../../../components/common/DeferredSpinner'; import { + BranchLike, ComponentMeasure, ComponentMeasureEnhanced, - BranchLike, + CurrentUser, + isLoggedIn, Metric, - Paging + Paging, + MeasureEnhanced } from '../../../app/types'; import { RequestData } from '../../../helpers/request'; import { Period } from '../../../helpers/periods'; @@ -50,8 +53,9 @@ interface Props { branchLike?: BranchLike; className?: string; component: ComponentMeasure; - currentUser: { isLoggedIn: boolean }; + currentUser: CurrentUser; loading: boolean; + loadingMore: boolean; leakPeriod?: Period; measure?: MeasureEnhanced; metric: Metric; @@ -66,7 +70,6 @@ interface Props { } interface State { - bestValue?: string; components: ComponentMeasureEnhanced[]; metric?: Metric; paging?: Paging; @@ -147,16 +150,15 @@ export default class MeasureContent extends React.PureComponent<Props, State> { if (metric === this.props.metric) { if (this.mounted) { this.setState(({ selected }: State) => ({ - bestValue: r.metrics[0].bestValue, components: r.components.map(component => enhanceComponent(component, metric, metrics) ), - metric, + metric: { ...metric, ...r.metrics.find(m => m.key === metric.key) }, paging: r.paging, selected: - r.components.length > 0 && !r.components.find(c => c.key === selected) - ? r.components[0].key - : selected, + r.components.length > 0 && r.components.find(c => c.key === selected) + ? selected + : undefined, view })); } @@ -176,26 +178,25 @@ export default class MeasureContent extends React.PureComponent<Props, State> { const { metricKeys, opts, strategy } = this.getComponentRequestParams(view, metric, { p: paging.pageIndex + 1 }); - this.props.updateLoading({ components: true }); + this.props.updateLoading({ moreComponents: true }); getComponentTree(strategy, component.key, metricKeys, opts).then( r => { if (metric === this.props.metric) { if (this.mounted) { this.setState(state => ({ - bestValue: r.metrics[0].bestValue, components: [ ...state.components, ...r.components.map(component => enhanceComponent(component, metric, metrics)) ], - metric, + metric: { ...metric, ...r.metrics.find(m => m.key === metric.key) }, paging: r.paging, view })); } - this.props.updateLoading({ components: false }); + this.props.updateLoading({ moreComponents: false }); } }, - () => this.props.updateLoading({ components: false }) + () => this.props.updateLoading({ moreComponents: false }) ); }; @@ -243,12 +244,12 @@ export default class MeasureContent extends React.PureComponent<Props, State> { const selectedIdx = this.getSelectedIndex(); return ( <FilesView - bestValue={this.state.bestValue} branchLike={this.props.branchLike} components={this.state.components} fetchMore={this.fetchMoreComponents} handleOpen={this.onOpenComponent} handleSelect={this.onSelectComponent} + loadingMore={this.props.loadingMore} metric={metric} metrics={this.props.metrics} paging={this.state.paging} @@ -276,7 +277,6 @@ export default class MeasureContent extends React.PureComponent<Props, State> { render() { const { branchLike, component, currentUser, measure, metric, rootComponent, view } = this.props; - const isLoggedIn = currentUser && currentUser.isLoggedIn; const isFile = isFileType(component); const selectedIdx = this.getSelectedIndex(); return ( @@ -295,7 +295,7 @@ export default class MeasureContent extends React.PureComponent<Props, State> { rootComponent={rootComponent} /> {component.key !== rootComponent.key && - isLoggedIn && ( + isLoggedIn(currentUser) && ( <MeasureFavoriteContainer branchLike={branchLike} className="measure-favorite spacer-right" diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContentContainer.js b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContentContainer.tsx index 160bf1d74b6..3c3a636538f 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContentContainer.js +++ b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContentContainer.tsx @@ -17,64 +17,61 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; +import * as React from 'react'; +import { InjectedRouter } from 'react-router'; import MeasureContent from './MeasureContent'; -/*:: import type { Component, Period, Query } from '../types'; */ -/*:: import type { MeasureEnhanced } from '../../../components/measure/types'; */ -/*:: import type { Metric } from '../../../store/metrics/actions'; */ -/*:: import type { RawQuery } from '../../../helpers/query'; */ +import { Query } from '../utils'; +import { + ComponentMeasure, + Metric, + BranchLike, + CurrentUser, + MeasureEnhanced +} from '../../../app/types'; +import { Period } from '../../../helpers/periods'; -/*:: type Props = {| - branchLike?: { id?: string; name: string }, - className?: string, - currentUser: { isLoggedIn: boolean }, - rootComponent: Component, +interface Props { + branchLike?: BranchLike; + className?: string; + currentUser: CurrentUser; + rootComponent: ComponentMeasure; fetchMeasures: ( component: string, - metricsKey: Array<string>, - branchLike?: { id?: string; name: string } - ) => Promise<{ component: Component, measures: Array<MeasureEnhanced> }>, - leakPeriod?: Period, - metric: Metric, - metrics: { [string]: Metric }, - router: { - push: ({ pathname: string, query?: RawQuery }) => void - }, - selected: ?string, - updateQuery: Query => void, - view: string -|}; */ + metricsKey: string[], + branchLike?: BranchLike + ) => Promise<{ component: ComponentMeasure; measures: MeasureEnhanced[] }>; + leakPeriod?: Period; + metric: Metric; + metrics: { [metric: string]: Metric }; + router: InjectedRouter; + selected?: string; + updateQuery: (query: Partial<Query>) => void; + view: string; +} -/*:: type State = { - component: ?Component, - loading: { - measure: boolean, - components: boolean - }, - measure: ?MeasureEnhanced, - secondaryMeasure: ?MeasureEnhanced -}; */ +interface LoadingState { + measure: boolean; + components: boolean; + moreComponents: boolean; +} -export default class MeasureContentContainer extends React.PureComponent { - /*:: mounted: boolean; */ - /*:: props: Props; */ - state /*: State */ = { - component: null, - loading: { - measure: false, - components: false - }, - measure: null, - secondaryMeasure: null - }; +interface State { + component?: ComponentMeasure; + loading: LoadingState; + measure?: MeasureEnhanced; + secondaryMeasure?: MeasureEnhanced; +} + +export default class MeasureContentContainer extends React.PureComponent<Props, State> { + mounted = false; + state: State = { loading: { measure: false, components: false, moreComponents: false } }; componentDidMount() { this.mounted = true; this.fetchMeasure(this.props); } - componentWillReceiveProps(nextProps /*: Props */) { + componentWillReceiveProps(nextProps: Props) { const { component } = this.state; const componentChanged = !component || @@ -89,7 +86,7 @@ export default class MeasureContentContainer extends React.PureComponent { this.mounted = false; } - fetchMeasure = ({ branchLike, rootComponent, fetchMeasures, metric, selected } /*: Props */) => { + fetchMeasure = ({ branchLike, rootComponent, fetchMeasures, metric, selected }: Props) => { this.updateLoading({ measure: true }); const metricKeys = [metric.key]; @@ -110,18 +107,19 @@ export default class MeasureContentContainer extends React.PureComponent { ); }; - updateLoading = (loading /*: { [string]: boolean } */) => { + updateLoading = (loading: Partial<LoadingState>) => { if (this.mounted) { this.setState(state => ({ loading: { ...state.loading, ...loading } })); } }; - updateSelected = (component /*: string */) => + updateSelected = (component: string) => { this.props.updateQuery({ - selected: component !== this.props.rootComponent.key ? component : null + selected: component !== this.props.rootComponent.key ? component : undefined }); + }; - updateView = (view /*: string */) => this.props.updateQuery({ view }); + updateView = (view: string) => this.props.updateQuery({ view }); render() { if (!this.state.component) { @@ -136,6 +134,7 @@ export default class MeasureContentContainer extends React.PureComponent { currentUser={this.props.currentUser} leakPeriod={this.props.leakPeriod} loading={this.state.loading.measure || this.state.loading.components} + loadingMore={this.state.loading.moreComponents} measure={this.state.measure} metric={this.props.metric} metrics={this.props.metrics} diff --git a/server/sonar-web/src/main/js/apps/component-measures/config/bubbles.ts b/server/sonar-web/src/main/js/apps/component-measures/config/bubbles.ts index d59bb1d91e1..99c1b0e8e8c 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/config/bubbles.ts +++ b/server/sonar-web/src/main/js/apps/component-measures/config/bubbles.ts @@ -46,7 +46,7 @@ export const bubbles: { }, Coverage: { x: 'complexity', y: 'coverage', size: 'uncovered_lines', yDomain: [100, 0] }, Duplications: { x: 'ncloc', y: 'duplicated_lines', size: 'duplicated_blocks' }, - // eslint-disable-next-line + // eslint-disable-next-line camelcase project_overview: { x: 'sqale_index', y: 'coverage', diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentsList.tsx b/server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentsList.tsx index ec49ffb6e9c..957fdc7b3e5 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentsList.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentsList.tsx @@ -21,13 +21,10 @@ import * as React from 'react'; import ComponentsListRow from './ComponentsListRow'; import EmptyResult from './EmptyResult'; import { complementary } from '../config/complementary'; -import { getLocalizedMetricName, translate, translateWithParameters } from '../../../helpers/l10n'; -import { formatMeasure, isDiffMetric, isPeriodBestValue } from '../../../helpers/measures'; +import { getLocalizedMetricName } from '../../../helpers/l10n'; import { ComponentMeasure, ComponentMeasureEnhanced, Metric, BranchLike } from '../../../app/types'; -import { Button } from '../../../components/ui/buttons'; interface Props { - bestValue?: string; branchLike?: BranchLike; components: ComponentMeasureEnhanced[]; onClick: (component: string) => void; @@ -37,93 +34,44 @@ interface Props { selectedComponent?: string; } -interface State { - hideBest: boolean; -} - -export default class ComponentsList extends React.PureComponent<Props, State> { - state: State = { hideBest: true }; - - componentWillReceiveProps(nextProps: Props) { - if (nextProps.metric !== this.props.metric) { - this.setState({ hideBest: true }); - } +export default function ComponentsList({ components, metric, metrics, ...props }: Props) { + if (!components.length) { + return <EmptyResult />; } - displayAll = () => { - this.setState({ hideBest: false }); - }; - - hasBestValue = (component: ComponentMeasureEnhanced) => { - const { metric } = this.props; - const focusedMeasure = component.measures.find(measure => measure.metric.key === metric.key); - if (focusedMeasure && isDiffMetric(focusedMeasure.metric.key)) { - return isPeriodBestValue(focusedMeasure, 1); - } - return Boolean(focusedMeasure && focusedMeasure.bestValue); - }; - - renderHiddenLink = (hiddenCount: number) => { - return ( - <div className="alert alert-info spacer-top"> - {translateWithParameters( - 'component_measures.hidden_best_score_metrics', - hiddenCount, - formatMeasure(this.props.bestValue, this.props.metric.type) - )} - <Button className="button-link spacer-left" onClick={this.displayAll}> - {translate('show_all')} - </Button> - </div> - ); - }; - - render() { - const { components, metric, metrics } = this.props; - if (!components.length) { - return <EmptyResult />; - } - - const otherMetrics = (complementary[metric.key] || []).map(key => metrics[key]); - const notBestComponents = components.filter(component => !this.hasBestValue(component)); - const hiddenCount = components.length - notBestComponents.length; - const shouldHideBest = this.state.hideBest && hiddenCount !== components.length; - return ( - <React.Fragment> - <table className="data zebra zebra-hover"> - {otherMetrics.length > 0 && ( - <thead> - <tr> - <th> </th> - <th className="text-right"> + const otherMetrics = (complementary[metric.key] || []).map(key => metrics[key]); + return ( + <React.Fragment> + <table className="data zebra zebra-hover"> + {otherMetrics.length > 0 && ( + <thead> + <tr> + <th> </th> + <th className="text-right"> + <span className="small">{getLocalizedMetricName(metric)}</span> + </th> + {otherMetrics.map(metric => ( + <th className="text-right" key={metric.key}> <span className="small">{getLocalizedMetricName(metric)}</span> </th> - {otherMetrics.map(metric => ( - <th className="text-right" key={metric.key}> - <span className="small">{getLocalizedMetricName(metric)}</span> - </th> - ))} - </tr> - </thead> - )} + ))} + </tr> + </thead> + )} - <tbody> - {(shouldHideBest ? notBestComponents : components).map(component => ( - <ComponentsListRow - branchLike={this.props.branchLike} - component={component} - isSelected={component.key === this.props.selectedComponent} - key={component.key} - metric={metric} - onClick={this.props.onClick} - otherMetrics={otherMetrics} - rootComponent={this.props.rootComponent} - /> - ))} - </tbody> - </table> - {shouldHideBest && hiddenCount > 0 && this.renderHiddenLink(hiddenCount)} - </React.Fragment> - ); - } + <tbody> + {components.map(component => ( + <ComponentsListRow + component={component} + isSelected={component.key === props.selectedComponent} + key={component.key} + metric={metric} + otherMetrics={otherMetrics} + {...props} + /> + ))} + </tbody> + </table> + </React.Fragment> + ); } diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/FilesView.tsx b/server/sonar-web/src/main/js/apps/component-measures/drilldown/FilesView.tsx index a4b0144ba08..ce19d48ae3f 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/drilldown/FilesView.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/FilesView.tsx @@ -22,7 +22,7 @@ import * as key from 'keymaster'; import { throttle } from 'lodash'; import ComponentsList from './ComponentsList'; import ListFooter from '../../../components/controls/ListFooter'; -import { scrollToElement } from '../../../helpers/scrolling'; +import { Button } from '../../../components/ui/buttons'; import { ComponentMeasure, ComponentMeasureEnhanced, @@ -30,14 +30,17 @@ import { Paging, BranchLike } from '../../../app/types'; +import { translate, translateWithParameters } from '../../../helpers/l10n'; +import { isPeriodBestValue, isDiffMetric, formatMeasure } from '../../../helpers/measures'; +import { scrollToElement } from '../../../helpers/scrolling'; interface Props { - bestValue?: string; branchLike?: BranchLike; components: ComponentMeasureEnhanced[]; fetchMore: () => void; handleSelect: (component: string) => void; handleOpen: (component: string) => void; + loadingMore: boolean; metric: Metric; metrics: { [metric: string]: Metric }; paging?: Paging; @@ -46,11 +49,16 @@ interface Props { selectedIdx?: number; } -export default class ListView extends React.PureComponent<Props> { +interface State { + showBestMeasures: boolean; +} + +export default class ListView extends React.PureComponent<Props, State> { listContainer?: HTMLElement | null; constructor(props: Props) { super(props); + this.state = { showBestMeasures: false }; this.selectNext = throttle(this.selectNext, 100); this.selectPrevious = throttle(this.selectPrevious, 100); } @@ -66,6 +74,9 @@ export default class ListView extends React.PureComponent<Props> { if (this.props.selectedKey !== undefined && prevProps.selectedKey !== this.props.selectedKey) { this.scrollToElement(); } + if (prevProps.metric.key !== this.props.metric.key) { + this.setState({ showBestMeasures: false }); + } } componentWillUnmount() { @@ -91,6 +102,30 @@ export default class ListView extends React.PureComponent<Props> { ['up', 'down', 'right'].forEach(action => key.unbind(action, 'measures-files')); } + getVisibleComponents = (components: ComponentMeasureEnhanced[], showBestMeasures: boolean) => { + if (showBestMeasures) { + return components; + } + const filtered = components.filter(component => !this.hasBestValue(component)); + if (filtered.length === 0) { + return components; + } + return filtered; + }; + + handleShowBestMeasures = () => { + this.setState({ showBestMeasures: true }); + }; + + hasBestValue = (component: ComponentMeasureEnhanced) => { + const { metric } = this.props; + const focusedMeasure = component.measures.find(measure => measure.metric.key === metric.key); + if (focusedMeasure && isDiffMetric(metric.key)) { + return isPeriodBestValue(focusedMeasure, 1); + } + return Boolean(focusedMeasure && focusedMeasure.bestValue); + }; + openSelected = () => { if (this.props.selectedKey !== undefined) { this.props.handleOpen(this.props.selectedKey); @@ -98,20 +133,22 @@ export default class ListView extends React.PureComponent<Props> { }; selectPrevious = () => { - const { selectedIdx } = this.props; + const { components, selectedIdx } = this.props; + const visibleComponents = this.getVisibleComponents(components, this.state.showBestMeasures); if (selectedIdx !== undefined && selectedIdx > 0) { - this.props.handleSelect(this.props.components[selectedIdx - 1].key); + this.props.handleSelect(visibleComponents[selectedIdx - 1].key); } else { - this.props.handleSelect(this.props.components[this.props.components.length - 1].key); + this.props.handleSelect(visibleComponents[visibleComponents.length - 1].key); } }; selectNext = () => { - const { selectedIdx } = this.props; - if (selectedIdx !== undefined && selectedIdx < this.props.components.length - 1) { - this.props.handleSelect(this.props.components[selectedIdx + 1].key); + const { components, selectedIdx } = this.props; + const visibleComponents = this.getVisibleComponents(components, this.state.showBestMeasures); + if (selectedIdx !== undefined && selectedIdx < visibleComponents.length - 1) { + this.props.handleSelect(visibleComponents[selectedIdx + 1].key); } else { - this.props.handleSelect(this.props.components[0].key); + this.props.handleSelect(visibleComponents[0].key); } }; @@ -125,23 +162,39 @@ export default class ListView extends React.PureComponent<Props> { }; render() { + const { components } = this.props; + const filteredComponents = this.getVisibleComponents(components, this.state.showBestMeasures); + const hidingBestMeasures = filteredComponents.length < components.length; return ( <div ref={elem => (this.listContainer = elem)}> <ComponentsList - bestValue={this.props.bestValue} branchLike={this.props.branchLike} - components={this.props.components} + components={filteredComponents} metric={this.props.metric} metrics={this.props.metrics} onClick={this.props.handleOpen} rootComponent={this.props.rootComponent} selectedComponent={this.props.selectedKey} /> - {this.props.paging && + {hidingBestMeasures && ( + <div className="alert alert-info spacer-top"> + {translateWithParameters( + 'component_measures.hidden_best_score_metrics', + components.length - filteredComponents.length, + formatMeasure(this.props.metric.bestValue, this.props.metric.type) + )} + <Button className="button-link spacer-left" onClick={this.handleShowBestMeasures}> + {translate('show_all')} + </Button> + </div> + )} + {!hidingBestMeasures && + this.props.paging && this.props.components.length > 0 && ( <ListFooter count={this.props.components.length} loadMore={this.props.fetchMore} + loading={this.props.loadingMore} total={this.props.paging.total} /> )} diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/TreeMapView.tsx b/server/sonar-web/src/main/js/apps/component-measures/drilldown/TreeMapView.tsx index 828cab940d7..61a35f44ace 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/drilldown/TreeMapView.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/TreeMapView.tsx @@ -72,13 +72,14 @@ export default class TreeMapView extends React.PureComponent<Props, State> { } const colorValue = colorMeasure && (isDiffMetric(metric.key) ? colorMeasure.leak : colorMeasure.value); - const sizeValue = Number( - isDiffMetric(sizeMeasure.metric.key) ? sizeMeasure.leak : sizeMeasure.value - ); - if (isNaN(sizeValue)) { + const rawSizeValue = isDiffMetric(sizeMeasure.metric.key) + ? sizeMeasure.leak + : sizeMeasure.value; + if (rawSizeValue === undefined) { return undefined; } + const sizeValue = Number(rawSizeValue); return { color: colorValue !== undefined ? (colorScale as Function)(colorValue) : theme.secondFontColor, diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/ComponentList-test.tsx b/server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/ComponentList-test.tsx new file mode 100644 index 00000000000..38c5bccbcac --- /dev/null +++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/ComponentList-test.tsx @@ -0,0 +1,82 @@ +/* + * 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 ComponentsList from '../ComponentsList'; + +const COMPONENTS = [ + { + key: 'foo', + measures: [], + name: 'Foo', + organization: 'foo', + qualifier: 'TRK' + } +]; + +const METRICS = { + coverage: { id: '1', key: 'coverage', type: 'PERCENT', name: 'Coverage' }, + new_bugs: { id: '2', key: 'new_bugs', type: 'INT', name: 'New Bugs' }, + uncovered_lines: { id: '3', key: 'uncovered_lines', type: 'INT', name: 'Lines' }, + uncovered_conditions: { id: '4', key: 'uncovered_conditions', type: 'INT', name: 'Conditions' } +}; + +it('should renders correctly', () => { + expect( + shallow( + <ComponentsList + components={COMPONENTS} + metric={METRICS.new_bugs} + metrics={METRICS} + onClick={jest.fn()} + rootComponent={COMPONENTS[0]} + /> + ) + ).toMatchSnapshot(); +}); + +it('should renders empty', () => { + expect( + shallow( + <ComponentsList + components={[]} + metric={METRICS.new_bugs} + metrics={METRICS} + onClick={jest.fn()} + rootComponent={COMPONENTS[0]} + /> + ) + ).toMatchSnapshot(); +}); + +it('should renders with multiple measures', () => { + expect( + shallow( + <ComponentsList + components={COMPONENTS} + metric={METRICS.coverage} + metrics={METRICS} + onClick={jest.fn()} + rootComponent={COMPONENTS[0]} + /> + ) + ).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/FilesView-test.tsx b/server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/FilesView-test.tsx new file mode 100644 index 00000000000..b9cce0022db --- /dev/null +++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/FilesView-test.tsx @@ -0,0 +1,79 @@ +/* + * 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 FilesView from '../FilesView'; + +const COMPONENTS = [ + { + key: 'foo', + measures: [], + name: 'Foo', + organization: 'foo', + qualifier: 'TRK' + } +]; + +const METRICS = { coverage: { id: '1', key: 'coverage', type: 'PERCENT', name: 'Coverage' } }; + +it('should renders correctly', () => { + expect(getWrapper()).toMatchSnapshot(); +}); + +it('should render with best values hidden', () => { + expect( + getWrapper({ + components: [ + ...COMPONENTS, + { + key: 'bar', + measures: [{ bestValue: true, metric: { key: 'coverage' } }], + name: 'Bar', + organization: 'foo', + qualifier: 'TRK' + } + ] + }) + ).toMatchSnapshot(); +}); + +function getWrapper(props = {}) { + return shallow( + <FilesView + components={COMPONENTS} + fetchMore={jest.fn()} + handleOpen={jest.fn()} + handleSelect={jest.fn()} + loadingMore={false} + metric={METRICS.coverage} + metrics={METRICS} + paging={{ pageIndex: 0, pageSize: 5, total: 10 }} + rootComponent={{ + key: 'parent', + measures: [], + name: 'Parent', + organization: 'foo', + qualifier: 'TRK' + }} + {...props} + /> + ); +} diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/__snapshots__/ComponentList-test.tsx.snap b/server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/__snapshots__/ComponentList-test.tsx.snap new file mode 100644 index 00000000000..5633f3760fe --- /dev/null +++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/__snapshots__/ComponentList-test.tsx.snap @@ -0,0 +1,140 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should renders correctly 1`] = ` +<React.Fragment> + <table + className="data zebra zebra-hover" + > + <tbody> + <ComponentsListRow + component={ + Object { + "key": "foo", + "measures": Array [], + "name": "Foo", + "organization": "foo", + "qualifier": "TRK", + } + } + isSelected={false} + key="foo" + metric={ + Object { + "id": "2", + "key": "new_bugs", + "name": "New Bugs", + "type": "INT", + } + } + onClick={[MockFunction]} + otherMetrics={Array []} + rootComponent={ + Object { + "key": "foo", + "measures": Array [], + "name": "Foo", + "organization": "foo", + "qualifier": "TRK", + } + } + /> + </tbody> + </table> +</React.Fragment> +`; + +exports[`should renders empty 1`] = `<EmptyResult />`; + +exports[`should renders with multiple measures 1`] = ` +<React.Fragment> + <table + className="data zebra zebra-hover" + > + <thead> + <tr> + <th> + + </th> + <th + className="text-right" + > + <span + className="small" + > + Coverage + </span> + </th> + <th + className="text-right" + key="uncovered_lines" + > + <span + className="small" + > + Lines + </span> + </th> + <th + className="text-right" + key="uncovered_conditions" + > + <span + className="small" + > + Conditions + </span> + </th> + </tr> + </thead> + <tbody> + <ComponentsListRow + component={ + Object { + "key": "foo", + "measures": Array [], + "name": "Foo", + "organization": "foo", + "qualifier": "TRK", + } + } + isSelected={false} + key="foo" + metric={ + Object { + "id": "1", + "key": "coverage", + "name": "Coverage", + "type": "PERCENT", + } + } + onClick={[MockFunction]} + otherMetrics={ + Array [ + Object { + "id": "3", + "key": "uncovered_lines", + "name": "Lines", + "type": "INT", + }, + Object { + "id": "4", + "key": "uncovered_conditions", + "name": "Conditions", + "type": "INT", + }, + ] + } + rootComponent={ + Object { + "key": "foo", + "measures": Array [], + "name": "Foo", + "organization": "foo", + "qualifier": "TRK", + } + } + /> + </tbody> + </table> +</React.Fragment> +`; diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/__snapshots__/FilesView-test.tsx.snap b/server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/__snapshots__/FilesView-test.tsx.snap new file mode 100644 index 00000000000..b63601b1b71 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/__snapshots__/FilesView-test.tsx.snap @@ -0,0 +1,110 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render with best values hidden 1`] = ` +<div> + <ComponentsList + components={ + Array [ + Object { + "key": "foo", + "measures": Array [], + "name": "Foo", + "organization": "foo", + "qualifier": "TRK", + }, + ] + } + metric={ + Object { + "id": "1", + "key": "coverage", + "name": "Coverage", + "type": "PERCENT", + } + } + metrics={ + Object { + "coverage": Object { + "id": "1", + "key": "coverage", + "name": "Coverage", + "type": "PERCENT", + }, + } + } + onClick={[MockFunction]} + rootComponent={ + Object { + "key": "parent", + "measures": Array [], + "name": "Parent", + "organization": "foo", + "qualifier": "TRK", + } + } + /> + <div + className="alert alert-info spacer-top" + > + component_measures.hidden_best_score_metrics.1. + <Button + className="button-link spacer-left" + onClick={[Function]} + > + show_all + </Button> + </div> +</div> +`; + +exports[`should renders correctly 1`] = ` +<div> + <ComponentsList + components={ + Array [ + Object { + "key": "foo", + "measures": Array [], + "name": "Foo", + "organization": "foo", + "qualifier": "TRK", + }, + ] + } + metric={ + Object { + "id": "1", + "key": "coverage", + "name": "Coverage", + "type": "PERCENT", + } + } + metrics={ + Object { + "coverage": Object { + "id": "1", + "key": "coverage", + "name": "Coverage", + "type": "PERCENT", + }, + } + } + onClick={[MockFunction]} + rootComponent={ + Object { + "key": "parent", + "measures": Array [], + "name": "Parent", + "organization": "foo", + "qualifier": "TRK", + } + } + /> + <ListFooter + count={1} + loadMore={[MockFunction]} + loading={false} + total={10} + /> +</div> +`; diff --git a/server/sonar-web/src/main/js/apps/component-measures/utils.ts b/server/sonar-web/src/main/js/apps/component-measures/utils.ts index 553edee41f4..a02181c7e95 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/utils.ts +++ b/server/sonar-web/src/main/js/apps/component-measures/utils.ts @@ -21,10 +21,14 @@ import { groupBy, memoize, sortBy, toPairs } from 'lodash'; import { domains } from './config/domains'; import { bubbles } from './config/bubbles'; import { getLocalizedMetricName } from '../../helpers/l10n'; -import { ComponentMeasure, ComponentMeasureEnhanced, Metric } from '../../app/types'; +import { + ComponentMeasure, + ComponentMeasureEnhanced, + Metric, + MeasureEnhanced +} from '../../app/types'; import { enhanceMeasure } from '../../components/measure/utils'; import { cleanQuery, parseAsString, RawQuery, serializeString } from '../../helpers/query'; -import { MeasureEnhanced } from '../../helpers/measures'; export const PROJECT_OVERVEW = 'project_overview'; export const DEFAULT_VIEW = 'list'; diff --git a/server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx b/server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx index dcf365a6615..63aed5899de 100644 --- a/server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx +++ b/server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx @@ -31,7 +31,7 @@ import throwGlobalError from '../../../app/utils/throwGlobalError'; import { getMeasuresAndMeta } from '../../../api/measures'; import { getAllTimeMachineData, History } from '../../../api/time-machine'; import { parseDate } from '../../../helpers/dates'; -import { enhanceMeasuresWithMetrics, MeasureEnhanced } from '../../../helpers/measures'; +import { enhanceMeasuresWithMetrics } from '../../../helpers/measures'; import { getLeakPeriod, Period } from '../../../helpers/periods'; import { get } from '../../../helpers/storage'; import { METRICS, HISTORY_METRICS_LIST } from '../utils'; @@ -48,7 +48,7 @@ import { } from '../../../helpers/branches'; import { fetchMetrics } from '../../../store/rootActions'; import { getMetrics } from '../../../store/rootReducer'; -import { BranchLike, Component, Metric } from '../../../app/types'; +import { BranchLike, Component, Metric, MeasureEnhanced } from '../../../app/types'; import { translate } from '../../../helpers/l10n'; import '../styles.css'; diff --git a/server/sonar-web/src/main/js/apps/overview/main/enhance.tsx b/server/sonar-web/src/main/js/apps/overview/main/enhance.tsx index 2ff27976bf8..b5509cbd738 100644 --- a/server/sonar-web/src/main/js/apps/overview/main/enhance.tsx +++ b/server/sonar-web/src/main/js/apps/overview/main/enhance.tsx @@ -30,8 +30,7 @@ import { isDiffMetric, getPeriodValue, getShortType, - getRatingTooltip, - MeasureEnhanced + getRatingTooltip } from '../../../helpers/measures'; import { getLocalizedMetricName } from '../../../helpers/l10n'; import { getPeriodDate } from '../../../helpers/periods'; @@ -40,7 +39,7 @@ import { getComponentIssuesUrl, getMeasureHistoryUrl } from '../../../helpers/urls'; -import { Component, BranchLike } from '../../../app/types'; +import { Component, BranchLike, MeasureEnhanced } from '../../../app/types'; import { History } from '../../../api/time-machine'; import { getBranchLikeQuery } from '../../../helpers/branches'; diff --git a/server/sonar-web/src/main/js/apps/overview/meta/MetaContainer.tsx b/server/sonar-web/src/main/js/apps/overview/meta/MetaContainer.tsx index 64a0a9e4ea6..cd93c3a9fbd 100644 --- a/server/sonar-web/src/main/js/apps/overview/meta/MetaContainer.tsx +++ b/server/sonar-web/src/main/js/apps/overview/meta/MetaContainer.tsx @@ -35,11 +35,11 @@ import { Metric, BranchLike, CurrentUser, - Organization + Organization, + MeasureEnhanced } from '../../../app/types'; import { History } from '../../../api/time-machine'; import { translate } from '../../../helpers/l10n'; -import { MeasureEnhanced } from '../../../helpers/measures'; import { hasPrivateAccess } from '../../../helpers/organizations'; import { getCurrentUser, diff --git a/server/sonar-web/src/main/js/apps/overview/meta/MetaSize.tsx b/server/sonar-web/src/main/js/apps/overview/meta/MetaSize.tsx index f1b25609103..4fdc6e23ebf 100644 --- a/server/sonar-web/src/main/js/apps/overview/meta/MetaSize.tsx +++ b/server/sonar-web/src/main/js/apps/overview/meta/MetaSize.tsx @@ -22,10 +22,10 @@ import * as classNames from 'classnames'; import DrilldownLink from '../../../components/shared/DrilldownLink'; import LanguageDistributionContainer from '../../../components/charts/LanguageDistributionContainer'; import SizeRating from '../../../components/ui/SizeRating'; -import { formatMeasure, MeasureEnhanced } from '../../../helpers/measures'; +import { formatMeasure } from '../../../helpers/measures'; import { getMetricName } from '../helpers/metrics'; import { translate } from '../../../helpers/l10n'; -import { LightComponent, BranchLike } from '../../../app/types'; +import { LightComponent, BranchLike, MeasureEnhanced } from '../../../app/types'; interface Props { branchLike?: BranchLike; diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/MeasuresOverlay.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/MeasuresOverlay.tsx index dc71ec4d2aa..cf25dc909b5 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/MeasuresOverlay.tsx +++ b/server/sonar-web/src/main/js/components/SourceViewer/components/MeasuresOverlay.tsx @@ -26,7 +26,7 @@ import { Button } from '../../ui/buttons'; import { getFacets } from '../../../api/issues'; import { getMeasures } from '../../../api/measures'; import { getAllMetrics } from '../../../api/metrics'; -import { FacetValue, SourceViewerFile, BranchLike } from '../../../app/types'; +import { FacetValue, SourceViewerFile, BranchLike, MeasureEnhanced } from '../../../app/types'; import Modal from '../../controls/Modal'; import Measure from '../../measure/Measure'; import QualifierIcon from '../../icons-components/QualifierIcon'; @@ -38,7 +38,6 @@ import { SEVERITIES, TYPES } from '../../../helpers/constants'; import { translate, getLocalizedMetricName } from '../../../helpers/l10n'; import { formatMeasure, - MeasureEnhanced, getDisplayMetrics, enhanceMeasuresWithMetrics } from '../../../helpers/measures'; diff --git a/server/sonar-web/src/main/js/components/measure/utils.ts b/server/sonar-web/src/main/js/components/measure/utils.ts index 626c65f552f..e45496fbd1a 100644 --- a/server/sonar-web/src/main/js/components/measure/utils.ts +++ b/server/sonar-web/src/main/js/components/measure/utils.ts @@ -17,13 +17,8 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { - getRatingTooltip as nextGetRatingTooltip, - isDiffMetric, - Measure, - MeasureEnhanced -} from '../../helpers/measures'; -import { Metric } from '../../app/types'; +import { getRatingTooltip as nextGetRatingTooltip, isDiffMetric } from '../../helpers/measures'; +import { Metric, Measure, MeasureEnhanced } from '../../app/types'; const KNOWN_RATINGS = ['sqale_rating', 'reliability_rating', 'security_rating']; diff --git a/server/sonar-web/src/main/js/helpers/measures.ts b/server/sonar-web/src/main/js/helpers/measures.ts index 171e04121f2..78256f28078 100644 --- a/server/sonar-web/src/main/js/helpers/measures.ts +++ b/server/sonar-web/src/main/js/helpers/measures.ts @@ -18,31 +18,10 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import { translate, translateWithParameters, getCurrentLocale } from './l10n'; -import { Metric } from '../app/types'; +import { Metric, Measure, MeasureEnhanced } from '../app/types'; const HOURS_IN_DAY = 8; -export interface MeasurePeriod { - bestValue?: boolean; - index: number; - value: string; -} - -export interface MeasureIntern { - bestValue?: boolean; - periods?: MeasurePeriod[]; - value?: string; -} - -export interface Measure extends MeasureIntern { - metric: string; -} - -export interface MeasureEnhanced extends MeasureIntern { - metric: Metric; - leak?: string; -} - interface Formatter { (value: string | number, options?: any): string; } |