aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main
diff options
context:
space:
mode:
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>2018-08-15 11:03:24 +0200
committerSonarTech <sonartech@sonarsource.com>2018-08-16 20:20:52 +0200
commit81620b85dcbb883166e9d1e13d8cc0207ca61a80 (patch)
treebd6a06a8c4ad808f07e497633d56cbb132b691c2 /server/sonar-web/src/main
parent8d39bc5a3c9a32a3449b529ffec68f28917375c3 (diff)
downloadsonarqube-81620b85dcbb883166e9d1e13d8cc0207ca61a80.tar.gz
sonarqube-81620b85dcbb883166e9d1e13d8cc0207ca61a80.zip
SONAR-11140 Rewrite part of component measures page to TS
Diffstat (limited to 'server/sonar-web/src/main')
-rw-r--r--server/sonar-web/src/main/js/api/components.ts15
-rw-r--r--server/sonar-web/src/main/js/app/types.ts27
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/__tests__/__snapshots__/utils-test.ts.snap (renamed from server/sonar-web/src/main/js/apps/component-measures/__tests__/__snapshots__/utils-test.js.snap)7
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/__tests__/utils-test.ts (renamed from server/sonar-web/src/main/js/apps/component-measures/__tests__/utils-test.js)22
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/Breadcrumb.tsx (renamed from server/sonar-web/src/main/js/apps/component-measures/components/Breadcrumb.js)27
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/Breadcrumbs.tsx (renamed from server/sonar-web/src/main/js/apps/component-measures/components/Breadcrumbs.js)45
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.tsx (renamed from server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.js)134
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/MeasureViewSelect.tsx (renamed from server/sonar-web/src/main/js/apps/component-measures/components/MeasureViewSelect.js)72
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/__tests__/Breadcrumb-test.tsx (renamed from server/sonar-web/src/main/js/apps/component-measures/components/__tests__/Breadcrumb-test.js)4
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/__tests__/Breadcrumbs-test.tsx (renamed from server/sonar-web/src/main/js/apps/component-measures/components/__tests__/Breadcrumbs-test.js)56
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureViewSelect-test.tsx (renamed from server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureViewSelect-test.js)9
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/Breadcrumb-test.tsx.snap (renamed from server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/Breadcrumb-test.js.snap)0
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/Breadcrumbs-test.js.snap93
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/Breadcrumbs-test.tsx.snap37
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureViewSelect-test.tsx.snap (renamed from server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureViewSelect-test.js.snap)22
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/config/bubbles.ts (renamed from server/sonar-web/src/main/js/apps/component-measures/config/bubbles.js)12
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/config/complementary.ts (renamed from server/sonar-web/src/main/js/apps/component-measures/config/complementary.js)4
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/config/domains.ts (renamed from server/sonar-web/src/main/js/apps/component-measures/config/domains.js)3
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/drilldown/CodeView.tsx (renamed from server/sonar-web/src/main/js/apps/component-measures/drilldown/CodeView.js)33
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentsList.tsx (renamed from server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentsList.js)100
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/drilldown/EmptyResult.tsx (renamed from server/sonar-web/src/main/js/apps/component-measures/drilldown/EmptyResult.js)3
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/drilldown/FilesView.tsx (renamed from server/sonar-web/src/main/js/apps/component-measures/drilldown/FilesView.js)63
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/drilldown/TreeMapView.tsx (renamed from server/sonar-web/src/main/js/apps/component-measures/drilldown/TreeMapView.js)112
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/utils.ts (renamed from server/sonar-web/src/main/js/apps/component-measures/utils.js)87
-rw-r--r--server/sonar-web/src/main/js/components/charts/ColorGradientLegend.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/charts/TreeMap.tsx4
-rw-r--r--server/sonar-web/src/main/js/components/charts/TreeMapRect.tsx2
-rw-r--r--server/sonar-web/src/main/js/helpers/measures.ts5
28 files changed, 494 insertions, 506 deletions
diff --git a/server/sonar-web/src/main/js/api/components.ts b/server/sonar-web/src/main/js/api/components.ts
index f6e83e322b9..23b90e37a33 100644
--- a/server/sonar-web/src/main/js/api/components.ts
+++ b/server/sonar-web/src/main/js/api/components.ts
@@ -19,7 +19,14 @@
*/
import throwGlobalError from '../app/utils/throwGlobalError';
import { getJSON, postJSON, post, RequestData } from '../helpers/request';
-import { Paging, Visibility, BranchParameters, MyProject } from '../app/types';
+import {
+ Paging,
+ Visibility,
+ BranchParameters,
+ MyProject,
+ Metric,
+ ComponentMeasure
+} from '../app/types';
export interface BaseSearchProjectsParameters {
analyzedBefore?: string;
@@ -93,7 +100,11 @@ export function getComponentTree(
componentKey: string,
metrics: string[] = [],
additional: RequestData = {}
-): Promise<any> {
+): Promise<{
+ components: ComponentMeasure[];
+ metrics: Metric[];
+ paging: Paging;
+}> {
const url = '/api/measures/component_tree';
const data = Object.assign({}, additional, {
baseComponentKey: componentKey,
diff --git a/server/sonar-web/src/main/js/app/types.ts b/server/sonar-web/src/main/js/app/types.ts
index f081619542f..8a1da4194d6 100644
--- a/server/sonar-web/src/main/js/app/types.ts
+++ b/server/sonar-web/src/main/js/app/types.ts
@@ -1,3 +1,5 @@
+import { Measure, MeasureEnhanced } from '../helpers/measures';
+
/*
* SonarQube
* Copyright (C) 2009-2018 SonarSource SA
@@ -340,18 +342,43 @@ 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 ComponentMeasure extends ComponentMeasureIntern {
+ measures?: Measure[];
+}
+
+export interface ComponentMeasureEnhanced extends ComponentMeasureIntern {
+ value?: string;
+ leak?: string;
+ measures: MeasureEnhanced[];
+}
+
export interface Metric {
+ bestValue?: string;
custom?: boolean;
decimalScale?: number;
description?: string;
direction?: number;
domain?: string;
hidden?: boolean;
+ higherValuesAreBetter?: boolean;
id: string;
key: string;
name: string;
qualitative?: boolean;
type: string;
+ worstValue?: string;
}
export interface MyProject {
diff --git a/server/sonar-web/src/main/js/apps/component-measures/__tests__/__snapshots__/utils-test.js.snap b/server/sonar-web/src/main/js/apps/component-measures/__tests__/__snapshots__/utils-test.ts.snap
index 979e672595d..4493d5a8038 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/__tests__/__snapshots__/utils-test.js.snap
+++ b/server/sonar-web/src/main/js/apps/component-measures/__tests__/__snapshots__/utils-test.ts.snap
@@ -8,6 +8,7 @@ Array [
"leak": "70",
"metric": Object {
"domain": "Coverage",
+ "id": "1",
"key": "lines_to_cover",
"name": "Lines to Cover",
"type": "INT",
@@ -24,6 +25,7 @@ Array [
"leak": "0.0999999999999943",
"metric": Object {
"domain": "Coverage",
+ "id": "2",
"key": "coverage",
"name": "Coverage",
"type": "PERCENT",
@@ -45,6 +47,7 @@ Array [
"leak": "0.0",
"metric": Object {
"domain": "Duplications",
+ "id": "3",
"key": "duplicated_lines_density",
"name": "Duplicated Lines (%)",
"type": "PERCENT",
@@ -67,6 +70,7 @@ exports[`sortMeasures should sort based on the config 1`] = `
Array [
Object {
"metric": Object {
+ "id": "3",
"key": "new_bugs",
"name": "new_bugs",
"type": "INT",
@@ -74,6 +78,7 @@ Array [
},
Object {
"metric": Object {
+ "id": "2",
"key": "new_reliability_remediation_effort",
"name": "bugs",
"type": "INT",
@@ -82,6 +87,7 @@ Array [
"overall_category",
Object {
"metric": Object {
+ "id": "4",
"key": "bugs",
"name": "bugs",
"type": "INT",
@@ -89,6 +95,7 @@ Array [
},
Object {
"metric": Object {
+ "id": "1",
"key": "reliability_remediation_effort",
"name": "new_bugs",
"type": "INT",
diff --git a/server/sonar-web/src/main/js/apps/component-measures/__tests__/utils-test.js b/server/sonar-web/src/main/js/apps/component-measures/__tests__/utils-test.ts
index 767208c79c5..74987c7eaa5 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/__tests__/utils-test.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/__tests__/utils-test.ts
@@ -17,12 +17,12 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-// @flow
import * as utils from '../utils';
const MEASURES = [
{
metric: {
+ id: '1',
key: 'lines_to_cover',
type: 'INT',
name: 'Lines to Cover',
@@ -34,6 +34,7 @@ const MEASURES = [
},
{
metric: {
+ id: '2',
key: 'coverage',
type: 'PERCENT',
name: 'Coverage',
@@ -45,6 +46,7 @@ const MEASURES = [
},
{
metric: {
+ id: '3',
key: 'duplicated_lines_density',
type: 'PERCENT',
name: 'Duplicated Lines (%)',
@@ -60,8 +62,10 @@ describe('filterMeasures', () => {
it('should exclude banned measures', () => {
expect(
utils.filterMeasures([
- { metric: { key: 'bugs', name: 'Bugs', type: 'INT' } },
- { metric: { key: 'critical_violations', name: 'Critical Violations', type: 'INT' } }
+ { metric: { id: '1', key: 'bugs', name: 'Bugs', type: 'INT' } },
+ {
+ metric: { id: '2', key: 'critical_violations', name: 'Critical Violations', type: 'INT' }
+ }
])
).toHaveLength(1);
});
@@ -71,10 +75,14 @@ describe('sortMeasures', () => {
it('should sort based on the config', () => {
expect(
utils.sortMeasures('Reliability', [
- { metric: { key: 'reliability_remediation_effort', name: 'new_bugs', type: 'INT' } },
- { metric: { key: 'new_reliability_remediation_effort', name: 'bugs', type: 'INT' } },
- { metric: { key: 'new_bugs', name: 'new_bugs', type: 'INT' } },
- { metric: { key: 'bugs', name: 'bugs', type: 'INT' } },
+ {
+ metric: { id: '1', key: 'reliability_remediation_effort', name: 'new_bugs', type: 'INT' }
+ },
+ {
+ metric: { id: '2', key: 'new_reliability_remediation_effort', name: 'bugs', type: 'INT' }
+ },
+ { metric: { id: '3', key: 'new_bugs', name: 'new_bugs', type: 'INT' } },
+ { metric: { id: '4', key: 'bugs', name: 'bugs', type: 'INT' } },
'overall_category'
])
).toMatchSnapshot();
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/Breadcrumb.js b/server/sonar-web/src/main/js/apps/component-measures/components/Breadcrumb.tsx
index 78200b351b1..326401a8377 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/components/Breadcrumb.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/Breadcrumb.tsx
@@ -17,25 +17,22 @@
* 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 Tooltip from '../../../components/controls/Tooltip';
import { collapsePath, limitComponentName } from '../../../helpers/path';
-/*:: import type { Component } from '../types'; */
+import { ComponentMeasure } from '../../../app/types';
-/*:: type Props = {
- canBrowse: boolean,
- component: Component,
- isLast: boolean,
- handleSelect: string => void
-}; */
-
-export default class Breadcrumb extends React.PureComponent {
- /*:: props: Props; */
+interface Props {
+ canBrowse: boolean;
+ component: ComponentMeasure;
+ isLast: boolean;
+ handleSelect: (component: string) => void;
+}
- handleClick = (e /*: Event & { target: HTMLElement } */) => {
- e.preventDefault();
- e.target.blur();
+export default class Breadcrumb extends React.PureComponent<Props> {
+ handleClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
+ event.preventDefault();
+ event.currentTarget.blur();
this.props.handleSelect(this.props.component.key);
};
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/Breadcrumbs.js b/server/sonar-web/src/main/js/apps/component-measures/components/Breadcrumbs.tsx
index fbc87441a60..f90d46e7550 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/components/Breadcrumbs.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/Breadcrumbs.tsx
@@ -17,33 +17,29 @@
* 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 key from 'keymaster';
+import * as React from 'react';
+import * as key from 'keymaster';
import Breadcrumb from './Breadcrumb';
import { getBreadcrumbs } from '../../../api/components';
import { getBranchLikeQuery } from '../../../helpers/branches';
-/*:: import type { Component } from '../types'; */
+import { BranchLike, ComponentMeasure } from '../../../app/types';
-/*:: type Props = {|
- backToFirst: boolean,
- branchLike?: { id?: string, name: string },
- className?: string,
- component: Component,
- handleSelect: string => void,
- rootComponent: Component
-|}; */
+interface Props {
+ backToFirst: boolean;
+ branchLike?: BranchLike;
+ className?: string;
+ component: ComponentMeasure;
+ handleSelect: (component: string) => void;
+ rootComponent: ComponentMeasure;
+}
-/*:: type State = {
- breadcrumbs: Array<Component>
-}; */
+interface State {
+ breadcrumbs: ComponentMeasure[];
+}
-export default class Breadcrumbs extends React.PureComponent {
- /*:: mounted: boolean; */
- /*:: props: Props; */
- state /*: State */ = {
- breadcrumbs: []
- };
+export default class Breadcrumbs extends React.PureComponent<Props, State> {
+ mounted = false;
+ state: State = { breadcrumbs: [] };
componentDidMount() {
this.mounted = true;
@@ -51,7 +47,7 @@ export default class Breadcrumbs extends React.PureComponent {
this.attachShortcuts();
}
- componentWillReceiveProps(nextProps /*: Props */) {
+ componentWillReceiveProps(nextProps: Props) {
if (this.props.component !== nextProps.component) {
this.fetchBreadcrumbs(nextProps);
}
@@ -77,7 +73,7 @@ export default class Breadcrumbs extends React.PureComponent {
key.unbind('left', 'measures-files');
}
- fetchBreadcrumbs = ({ branchLike, component, rootComponent } /*: Props */) => {
+ fetchBreadcrumbs = ({ branchLike, component, rootComponent }: Props) => {
const isRoot = component.key === rootComponent.key;
if (isRoot) {
if (this.mounted) {
@@ -90,7 +86,8 @@ export default class Breadcrumbs extends React.PureComponent {
if (this.mounted) {
this.setState({ breadcrumbs });
}
- }
+ },
+ () => {}
);
};
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.js b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.tsx
index 8777d0fca7e..6bdd4d6f190 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.tsx
@@ -17,9 +17,9 @@
* 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 * as React from 'react';
+import * as classNames from 'classnames';
+import { InjectedRouter } from 'react-router';
import Breadcrumbs from './Breadcrumbs';
import MeasureFavoriteContainer from './MeasureFavoriteContainer';
import MeasureHeader from './MeasureHeader';
@@ -33,62 +33,58 @@ import { getComponentTree } from '../../../api/components';
import { complementary } from '../config/complementary';
import { enhanceComponent, isFileType, isViewType } from '../utils';
import { getProjectUrl } from '../../../helpers/urls';
-import { isDiffMetric } from '../../../helpers/measures';
+import { isDiffMetric, MeasureEnhanced } from '../../../helpers/measures';
import { isSameBranchLike, getBranchLikeQuery } from '../../../helpers/branches';
import DeferredSpinner from '../../../components/common/DeferredSpinner';
-/*:: import type { Component, ComponentEnhanced, Paging, Period } from '../types'; */
-/*:: import type { MeasureEnhanced } from '../../../components/measure/types'; */
-/*:: import type { Metric } from '../../../store/metrics/actions'; */
+import {
+ ComponentMeasure,
+ ComponentMeasureEnhanced,
+ BranchLike,
+ Metric,
+ Paging
+} from '../../../app/types';
+import { RequestData } from '../../../helpers/request';
+import { Period } from '../../../helpers/periods';
-// Switching to the following type will make flow crash with :
-// https://github.com/facebook/flow/issues/3147
-// router: { push: ({ pathname: string, query?: RawQuery }) => void }
-/*:: type Props = {|
- branchLike?: { id?: string; name: string },
- className?: string,
- component: Component,
- currentUser: { isLoggedIn: boolean },
- loading: boolean,
- leakPeriod?: Period,
- measure: ?MeasureEnhanced,
- metric: Metric,
- metrics: { [string]: Metric },
- rootComponent: Component,
- router: Object,
- secondaryMeasure: ?MeasureEnhanced,
- updateLoading: ({ [string]: boolean }) => void,
- updateSelected: string => void,
- updateView: string => void,
- view: string
-|}; */
+interface Props {
+ branchLike?: BranchLike;
+ className?: string;
+ component: ComponentMeasure;
+ currentUser: { isLoggedIn: boolean };
+ loading: boolean;
+ leakPeriod?: Period;
+ measure?: MeasureEnhanced;
+ metric: Metric;
+ metrics: { [metric: string]: Metric };
+ rootComponent: ComponentMeasure;
+ router: InjectedRouter;
+ secondaryMeasure?: MeasureEnhanced;
+ updateLoading: (param: { [key: string]: boolean }) => void;
+ updateSelected: (component: string) => void;
+ updateView: (view: string) => void;
+ view: string;
+}
-/*:: type State = {
- bestValue?: string,
- components: Array<ComponentEnhanced>,
- metric: ?Metric,
- paging?: Paging,
- selected: ?string,
- view: ?string
-}; */
+interface State {
+ bestValue?: string;
+ components: ComponentMeasureEnhanced[];
+ metric?: Metric;
+ paging?: Paging;
+ selected?: string;
+ view?: string;
+}
-export default class MeasureContent extends React.PureComponent {
- /*:: container: HTMLElement; */
- /*:: mounted: boolean; */
- /*:: props: Props; */
- state /*: State */ = {
- components: [],
- metric: null,
- paging: null,
- selected: null,
- view: null
- };
+export default class MeasureContent extends React.PureComponent<Props, State> {
+ container?: HTMLElement | null;
+ mounted = false;
+ state: State = { components: [] };
componentDidMount() {
this.mounted = true;
this.fetchComponents(this.props);
}
- componentWillReceiveProps(nextProps /*: Props */) {
+ componentWillReceiveProps(nextProps: Props) {
if (
!isSameBranchLike(nextProps.branchLike, this.props.branchLike) ||
nextProps.component !== this.props.component ||
@@ -107,17 +103,13 @@ export default class MeasureContent extends React.PureComponent {
? this.props.component.key
: this.state.selected;
const index = this.state.components.findIndex(component => component.key === componentKey);
- return index !== -1 ? index : null;
+ return index !== -1 ? index : undefined;
};
- getComponentRequestParams = (
- view /*: string */,
- metric /*: Metric */,
- options /*: Object */ = {}
- ) => {
+ getComponentRequestParams = (view: string, metric: Metric, options: Object = {}) => {
const strategy = view === 'list' ? 'leaves' : 'children';
const metricKeys = [metric.key];
- const opts /*: Object */ = {
+ const opts: RequestData = {
...getBranchLikeQuery(this.props.branchLike),
additionalFields: 'metrics',
metricSortFilter: 'withMeasuresOnly'
@@ -142,9 +134,10 @@ export default class MeasureContent extends React.PureComponent {
return { metricKeys, opts: { ...opts, ...options }, strategy };
};
- fetchComponents = ({ component, metric, metrics, view } /*: Props */) => {
+ fetchComponents = ({ component, metric, metrics, view }: Props) => {
if (isFileType(component)) {
- return this.setState({ metric: null, view: null });
+ this.setState({ metric: undefined, view: undefined });
+ return;
}
const { metricKeys, opts, strategy } = this.getComponentRequestParams(view, metric);
@@ -153,7 +146,7 @@ export default class MeasureContent extends React.PureComponent {
r => {
if (metric === this.props.metric) {
if (this.mounted) {
- this.setState(({ selected } /*: State */) => ({
+ this.setState(({ selected }: State) => ({
bestValue: r.metrics[0].bestValue,
components: r.components.map(component =>
enhanceComponent(component, metric, metrics)
@@ -206,12 +199,12 @@ export default class MeasureContent extends React.PureComponent {
);
};
- onOpenComponent = (componentKey /*: string */) => {
+ onOpenComponent = (componentKey: string) => {
if (isViewType(this.props.rootComponent)) {
const component = this.state.components.find(
component => component.refKey === componentKey || component.key === componentKey
);
- if (component && component.refKey != null) {
+ if (component && component.refKey !== undefined) {
if (this.props.view === 'treemap') {
this.props.router.push(getProjectUrl(componentKey));
}
@@ -225,7 +218,7 @@ export default class MeasureContent extends React.PureComponent {
}
};
- onSelectComponent = (componentKey /*: string */) => this.setState({ selected: componentKey });
+ onSelectComponent = (componentKey: string) => this.setState({ selected: componentKey });
renderCode() {
return (
@@ -245,8 +238,8 @@ export default class MeasureContent extends React.PureComponent {
renderMeasure() {
const { metric, view } = this.state;
- if (metric != null) {
- if (['list', 'tree'].includes(view)) {
+ if (metric !== undefined) {
+ if (!view || ['list', 'tree'].includes(view)) {
const selectedIdx = this.getSelectedIndex();
return (
<FilesView
@@ -261,7 +254,7 @@ export default class MeasureContent extends React.PureComponent {
paging={this.state.paging}
rootComponent={this.props.rootComponent}
selectedIdx={selectedIdx}
- selectedKey={selectedIdx != null ? this.state.selected : null}
+ selectedKey={selectedIdx !== undefined ? this.state.selected : undefined}
/>
);
}
@@ -289,8 +282,7 @@ export default class MeasureContent extends React.PureComponent {
return (
<div
className={classNames('no-outline', this.props.className)}
- ref={container => (this.container = container)}
- tabIndex={0}>
+ ref={container => (this.container = container)}>
<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">
@@ -319,7 +311,9 @@ export default class MeasureContent extends React.PureComponent {
/>
)}
<PageActions
- current={selectedIdx != null && view !== 'treemap' ? selectedIdx + 1 : null}
+ current={
+ selectedIdx !== undefined && view !== 'treemap' ? selectedIdx + 1 : undefined
+ }
isFile={isFile}
paging={this.state.paging}
totalLoadedComponents={this.state.components.length}
@@ -328,10 +322,8 @@ export default class MeasureContent extends React.PureComponent {
</div>
</div>
</div>
- {metric == null && (
- <MetricNotFound className="layout-page-main-inner measure-details-content" />
- )}
- {metric != null && (
+ {!metric && <MetricNotFound className="layout-page-main-inner measure-details-content" />}
+ {metric && (
<div className="layout-page-main-inner measure-details-content">
<MeasureHeader
branchLike={branchLike}
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureViewSelect.js b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureViewSelect.tsx
index e8fb726ef9f..69347353959 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureViewSelect.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureViewSelect.tsx
@@ -17,71 +17,66 @@
* 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 ListIcon from '../../../components/icons-components/ListIcon';
import TreeIcon from '../../../components/icons-components/TreeIcon';
import TreemapIcon from '../../../components/icons-components/TreemapIcon';
import Select from '../../../components/controls/Select';
import { hasList, hasTree, hasTreemap } from '../utils';
import { translate } from '../../../helpers/l10n';
-/*:: import type { Metric } from '../../../store/metrics/actions'; */
+import { Metric } from '../../../app/types';
-/*:: type Props = {
- className?: string,
- metric: Metric,
- handleViewChange: (view: string) => void,
- view: string
-}; */
-
-export default class MeasureViewSelect extends React.PureComponent {
- /*:: props: Props; */
+interface Props {
+ className?: string;
+ metric: Metric;
+ handleViewChange: (view: string) => void;
+ view: string;
+}
+export default class MeasureViewSelect extends React.PureComponent<Props> {
getOptions = () => {
const { metric } = this.props;
const options = [];
if (hasList(metric.key)) {
options.push({
- value: 'list',
- label: (
- <div>
- <ListIcon className="little-spacer-right" />
- {translate('component_measures.tab.list')}
- </div>
- ),
- icon: <ListIcon />
+ icon: <ListIcon />,
+ label: translate('component_measures.tab.list'),
+ value: 'list'
});
}
if (hasTree(metric.key)) {
options.push({
- value: 'tree',
- label: (
- <div>
- <TreeIcon className="little-spacer-right" />
- {translate('component_measures.tab.tree')}
- </div>
- ),
- icon: <TreeIcon />
+ icon: <TreeIcon />,
+ label: translate('component_measures.tab.tree'),
+ value: 'tree'
});
}
if (hasTreemap(metric.key, metric.type)) {
options.push({
- value: 'treemap',
- label: (
- <div>
- <TreemapIcon className="little-spacer-right" />
- {translate('component_measures.tab.treemap')}
- </div>
- ),
- icon: <TreemapIcon />
+ icon: <TreemapIcon />,
+ label: translate('component_measures.tab.treemap'),
+ value: 'treemap'
});
}
return options;
};
- handleChange = (option /*: { value: string } */) => this.props.handleViewChange(option.value);
+ handleChange = (option: { value: string }) => {
+ return this.props.handleViewChange(option.value);
+ };
+
+ renderOption = (option: { icon: JSX.Element; label: string }) => {
+ return (
+ <>
+ {option.icon}
+ <span className="little-spacer-left">{option.label}</span>
+ </>
+ );
+ };
- renderValue = (value /*: { icon: Element<*> } */) => value.icon;
+ renderValue = (value: { icon: JSX.Element }) => {
+ return value.icon;
+ };
render() {
return (
@@ -90,6 +85,7 @@ export default class MeasureViewSelect extends React.PureComponent {
className={this.props.className}
clearable={false}
onChange={this.handleChange}
+ optionRenderer={this.renderOption}
options={this.getOptions()}
searchable={false}
value={this.props.view}
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/Breadcrumb-test.js b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/Breadcrumb-test.tsx
index cd4c0298fb8..8eff874efdd 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/Breadcrumb-test.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/Breadcrumb-test.tsx
@@ -17,7 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import React from 'react';
+import * as React from 'react';
import { shallow } from 'enzyme';
import Breadcrumb from '../Breadcrumb';
@@ -29,6 +29,7 @@ it('should show the last element without clickable link', () => {
component={{
key: 'foo',
name: 'Foo',
+ organization: 'foo',
qualifier: 'TRK'
}}
handleSelect={() => {}}
@@ -46,6 +47,7 @@ it('should correctly show a middle element', () => {
component={{
key: 'foo',
name: 'Foo',
+ organization: 'foo',
qualifier: 'TRK'
}}
handleSelect={() => {}}
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/Breadcrumbs-test.js b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/Breadcrumbs-test.tsx
index a011498c1dc..3511f452b1f 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/Breadcrumbs-test.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/Breadcrumbs-test.tsx
@@ -17,28 +17,47 @@
* 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 * as React from 'react';
import { mount } from 'enzyme';
import Breadcrumbs from '../Breadcrumbs';
-import { doAsync } from '../../../../helpers/testUtils';
+import { waitAndUpdate } from '../../../../helpers/testUtils';
+import { getBreadcrumbs } from '../../../../api/components';
jest.mock('../../../../api/components', () => ({
- getBreadcrumbs: () =>
- Promise.resolve([
+ getBreadcrumbs: jest
+ .fn()
+ .mockResolvedValue([
{ key: 'anc1', name: 'Ancestor1' },
{ key: 'anc2', name: 'Ancestor2' },
{ key: 'bar', name: 'Bar' }
])
}));
+const componentFoo = {
+ key: 'foo',
+ name: 'Foo',
+ organization: 'bar',
+ qualifier: 'TRK'
+};
+
+const componentBar = {
+ key: 'bar',
+ name: 'Bar',
+ organization: 'bar',
+ qualifier: 'TRK'
+};
+
+beforeEach(() => {
+ (getBreadcrumbs as jest.Mock<any>).mockClear();
+});
+
it('should display correctly for the list view', () => {
const wrapper = mount(
<Breadcrumbs
- branch={{ isMain: true }}
- component={{ key: 'bar', name: 'Bar' }}
+ backToFirst={false}
+ component={componentBar}
handleSelect={() => {}}
- rootComponent={{ key: 'foo', name: 'Foo' }}
- view="list"
+ rootComponent={componentFoo}
/>
);
expect(wrapper).toMatchSnapshot();
@@ -47,27 +66,24 @@ it('should display correctly for the list view', () => {
it('should display only the root component', () => {
const wrapper = mount(
<Breadcrumbs
- branch={{ isMain: true }}
- component={{ key: 'foo', name: 'Foo' }}
+ backToFirst={false}
+ component={componentFoo}
handleSelect={() => {}}
- rootComponent={{ key: 'foo', name: 'Foo' }}
- view="tree"
+ rootComponent={componentFoo}
/>
);
expect(wrapper.state()).toMatchSnapshot();
});
-it.only('should load the breadcrumb from the api', () => {
+it('should load the breadcrumb from the api', async () => {
const wrapper = mount(
<Breadcrumbs
- branch={{ isMain: true }}
- component={{ key: 'bar', name: 'Bar' }}
+ backToFirst={false}
+ component={componentBar}
handleSelect={() => {}}
- rootComponent={{ key: 'foo', name: 'Foo' }}
- view="tree"
+ rootComponent={componentFoo}
/>
);
- return doAsync(() => {
- expect(wrapper.state()).toMatchSnapshot();
- });
+ await waitAndUpdate(wrapper);
+ expect(getBreadcrumbs).toHaveBeenCalled();
});
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureViewSelect-test.js b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureViewSelect-test.tsx
index 48b39433226..a54ea14a3a1 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureViewSelect-test.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureViewSelect-test.tsx
@@ -17,14 +17,19 @@
* 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 * as React from 'react';
import { shallow } from 'enzyme';
import MeasureViewSelect from '../MeasureViewSelect';
+import { Metric } from '../../../../app/types';
it('should display correctly with treemap option', () => {
expect(
shallow(
- <MeasureViewSelect handleViewChange={() => {}} metric={{ type: 'PERCENT' }} view="tree" />
+ <MeasureViewSelect
+ handleViewChange={() => {}}
+ metric={{ type: 'PERCENT' } as Metric}
+ view="tree"
+ />
)
).toMatchSnapshot();
});
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/Breadcrumb-test.js.snap b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/Breadcrumb-test.tsx.snap
index e0db087b0a3..e0db087b0a3 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/Breadcrumb-test.js.snap
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/Breadcrumb-test.tsx.snap
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/Breadcrumbs-test.js.snap b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/Breadcrumbs-test.js.snap
deleted file mode 100644
index 63bab85ba77..00000000000
--- a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/Breadcrumbs-test.js.snap
+++ /dev/null
@@ -1,93 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should display correctly for the list view 1`] = `
-<Breadcrumbs
- component={
- Object {
- "key": "bar",
- "name": "Bar",
- }
- }
- handleSelect={[Function]}
- rootComponent={
- Object {
- "key": "foo",
- "name": "Foo",
- }
- }
- view="list"
->
- <div>
- <Breadcrumb
- canBrowse={true}
- component={
- Object {
- "key": "foo",
- "name": "Foo",
- }
- }
- handleSelect={[Function]}
- isLast={false}
- >
- <span>
- <a
- href="#"
- onClick={[Function]}
- >
- Foo
- </a>
- <span
- className="slash-separator"
- />
- </span>
- </Breadcrumb>
- <Breadcrumb
- canBrowse={false}
- component={
- Object {
- "key": "bar",
- "name": "Bar",
- }
- }
- handleSelect={[Function]}
- isLast={true}
- >
- <span>
- <span>
- Bar
- </span>
- </span>
- </Breadcrumb>
- </div>
-</Breadcrumbs>
-`;
-
-exports[`should display only the root component 1`] = `
-Object {
- "breadcrumbs": Array [
- Object {
- "key": "foo",
- "name": "Foo",
- },
- ],
-}
-`;
-
-exports[`should load the breadcrumb from the api 1`] = `
-Object {
- "breadcrumbs": Array [
- Object {
- "key": "anc1",
- "name": "Ancestor1",
- },
- Object {
- "key": "anc2",
- "name": "Ancestor2",
- },
- Object {
- "key": "bar",
- "name": "Bar",
- },
- ],
-}
-`;
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/Breadcrumbs-test.tsx.snap b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/Breadcrumbs-test.tsx.snap
new file mode 100644
index 00000000000..9e4fbb301a8
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/Breadcrumbs-test.tsx.snap
@@ -0,0 +1,37 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should display correctly for the list view 1`] = `
+<Breadcrumbs
+ backToFirst={false}
+ component={
+ Object {
+ "key": "bar",
+ "name": "Bar",
+ "organization": "bar",
+ "qualifier": "TRK",
+ }
+ }
+ handleSelect={[Function]}
+ rootComponent={
+ Object {
+ "key": "foo",
+ "name": "Foo",
+ "organization": "bar",
+ "qualifier": "TRK",
+ }
+ }
+/>
+`;
+
+exports[`should display only the root component 1`] = `
+Object {
+ "breadcrumbs": Array [
+ Object {
+ "key": "foo",
+ "name": "Foo",
+ "organization": "bar",
+ "qualifier": "TRK",
+ },
+ ],
+}
+`;
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureViewSelect-test.js.snap b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureViewSelect-test.tsx.snap
index 6a46b06820b..43cd04e0644 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureViewSelect-test.js.snap
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureViewSelect-test.tsx.snap
@@ -5,36 +5,22 @@ exports[`should display correctly with treemap option 1`] = `
autoBlur={true}
clearable={false}
onChange={[Function]}
+ optionRenderer={[Function]}
options={
Array [
Object {
"icon": <ListIcon />,
- "label": <div>
- <ListIcon
- className="little-spacer-right"
- />
- component_measures.tab.list
- </div>,
+ "label": "component_measures.tab.list",
"value": "list",
},
Object {
"icon": <TreeIcon />,
- "label": <div>
- <TreeIcon
- className="little-spacer-right"
- />
- component_measures.tab.tree
- </div>,
+ "label": "component_measures.tab.tree",
"value": "tree",
},
Object {
"icon": <TreemapIcon />,
- "label": <div>
- <TreemapIcon
- className="little-spacer-right"
- />
- component_measures.tab.treemap
- </div>,
+ "label": "component_measures.tab.treemap",
"value": "treemap",
},
]
diff --git a/server/sonar-web/src/main/js/apps/component-measures/config/bubbles.js b/server/sonar-web/src/main/js/apps/component-measures/config/bubbles.ts
index d00c66bf9f3..d59bb1d91e1 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/config/bubbles.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/config/bubbles.ts
@@ -17,8 +17,15 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-// @flow
-export const bubbles = {
+export const bubbles: {
+ [domain: string]: {
+ x: string;
+ y: string;
+ size: string;
+ colors?: string[];
+ yDomain?: number[];
+ };
+} = {
Reliability: {
x: 'ncloc',
y: 'reliability_remediation_effort',
@@ -39,6 +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
project_overview: {
x: 'sqale_index',
y: 'coverage',
diff --git a/server/sonar-web/src/main/js/apps/component-measures/config/complementary.js b/server/sonar-web/src/main/js/apps/component-measures/config/complementary.ts
index c67a3de7d2d..9c9824c6a4b 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/config/complementary.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/config/complementary.ts
@@ -17,8 +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.
*/
-// @flow
-export const complementary = {
+/* eslint-disable camelcase */
+export const complementary: { [metric: string]: string[] } = {
coverage: ['uncovered_lines', 'uncovered_conditions'],
line_coverage: ['uncovered_lines'],
branch_coverage: ['uncovered_conditions'],
diff --git a/server/sonar-web/src/main/js/apps/component-measures/config/domains.js b/server/sonar-web/src/main/js/apps/component-measures/config/domains.ts
index 8a746deb636..d7022b680b4 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/config/domains.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/config/domains.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.
*/
-// @flow
-export const domains /*: { [string]: { categories?: Array<string>, order: Array<string> } }*/ = {
+export const domains: { [domain: string]: { categories?: string[]; order: string[] } } = {
Reliability: {
categories: ['new_code_category', 'overall_category'],
order: [
diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/CodeView.js b/server/sonar-web/src/main/js/apps/component-measures/drilldown/CodeView.tsx
index 1c0747851f1..6f74390695b 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/drilldown/CodeView.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/CodeView.tsx
@@ -17,26 +17,23 @@
* 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 key from 'keymaster';
+import * as React from 'react';
+import * as key from 'keymaster';
import SourceViewer from '../../../components/SourceViewer/SourceViewer';
-/*:: import type { ComponentEnhanced, Paging, Period } from '../types'; */
-/*:: import type { Metric } from '../../../store/metrics/actions'; */
+import { BranchLike, ComponentMeasure, ComponentMeasureEnhanced, Metric } from '../../../app/types';
+import { Period } from '../../../helpers/periods';
-/*:: type Props = {|
- branchLike?: { id?: string; name: string },
- component: ComponentEnhanced,
- components: Array<ComponentEnhanced>,
- leakPeriod?: Period,
- metric: Metric,
- selectedIdx: ?number,
- updateSelected: string => void,
-|}; */
-
-export default class CodeView extends React.PureComponent {
- /*:: props: Props; */
+interface Props {
+ branchLike?: BranchLike;
+ component: ComponentMeasure;
+ components: Array<ComponentMeasureEnhanced>;
+ leakPeriod?: Period;
+ metric: Metric;
+ selectedIdx?: number;
+ updateSelected: (component: string) => void;
+}
+export default class CodeView extends React.PureComponent<Props> {
componentDidMount() {
this.attachShortcuts();
}
@@ -57,7 +54,7 @@ export default class CodeView extends React.PureComponent {
}
detachShortcuts() {
- ['j', 'k'].map(action => key.unbind(action, 'measures-files'));
+ ['j', 'k'].forEach(action => key.unbind(action, 'measures-files'));
}
selectPrevious = () => {
diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentsList.js b/server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentsList.tsx
index fbd26322b63..ec49ffb6e9c 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentsList.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentsList.tsx
@@ -17,74 +17,53 @@
* 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 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 type { Component, ComponentEnhanced } from '../types'; */
-/*:: import type { Metric } from '../../../store/metrics/actions'; */
+import { ComponentMeasure, ComponentMeasureEnhanced, Metric, BranchLike } from '../../../app/types';
+import { Button } from '../../../components/ui/buttons';
-/*:: type Props = {|
- bestValue?: string,
- branchLike?: { id?: string; name: string },
- components: Array<ComponentEnhanced>,
- onClick: string => void,
- metric: Metric,
- metrics: { [string]: Metric },
- rootComponent: Component,
- selectedComponent?: ?string
-|}; */
+interface Props {
+ bestValue?: string;
+ branchLike?: BranchLike;
+ components: ComponentMeasureEnhanced[];
+ onClick: (component: string) => void;
+ metric: Metric;
+ metrics: { [metric: string]: Metric };
+ rootComponent: ComponentMeasure;
+ selectedComponent?: string;
+}
-/*:: type State = {
- hideBest: boolean
-}; */
+interface State {
+ hideBest: boolean;
+}
-export default class ComponentsList extends React.PureComponent {
- /*:: props: Props; */
- state /*: State */ = {
- hideBest: true
- };
+export default class ComponentsList extends React.PureComponent<Props, State> {
+ state: State = { hideBest: true };
- componentWillReceiveProps(nextProps /*: Props */) {
+ componentWillReceiveProps(nextProps: Props) {
if (nextProps.metric !== this.props.metric) {
this.setState({ hideBest: true });
}
}
- displayAll = (event /*: Event */) => {
- event.preventDefault();
+ displayAll = () => {
this.setState({ hideBest: false });
};
- hasBestValue(component /*: Component*/, otherMetrics /*: Array<Metric> */) {
+ hasBestValue = (component: ComponentMeasureEnhanced) => {
const { metric } = this.props;
const focusedMeasure = component.measures.find(measure => measure.metric.key === metric.key);
- if (isDiffMetric(focusedMeasure.metric.key)) {
+ if (focusedMeasure && isDiffMetric(focusedMeasure.metric.key)) {
return isPeriodBestValue(focusedMeasure, 1);
}
- return focusedMeasure.bestValue;
- }
-
- renderComponent(component /*: Component*/, otherMetrics /*: Array<Metric> */) {
- const { branchLike, metric, selectedComponent, onClick, rootComponent } = this.props;
- return (
- <ComponentsListRow
- branchLike={branchLike}
- component={component}
- isSelected={component.key === selectedComponent}
- key={component.id}
- metric={metric}
- onClick={onClick}
- otherMetrics={otherMetrics}
- rootComponent={rootComponent}
- />
- );
- }
+ return Boolean(focusedMeasure && focusedMeasure.bestValue);
+ };
- renderHiddenLink(hiddenCount /*: number*/, colCount /*: number*/) {
+ renderHiddenLink = (hiddenCount: number) => {
return (
<div className="alert alert-info spacer-top">
{translateWithParameters(
@@ -92,12 +71,12 @@ export default class ComponentsList extends React.PureComponent {
hiddenCount,
formatMeasure(this.props.bestValue, this.props.metric.type)
)}
- <a className="spacer-left" href="#" onClick={this.displayAll}>
+ <Button className="button-link spacer-left" onClick={this.displayAll}>
{translate('show_all')}
- </a>
+ </Button>
</div>
);
- }
+ };
render() {
const { components, metric, metrics } = this.props;
@@ -106,9 +85,7 @@ export default class ComponentsList extends React.PureComponent {
}
const otherMetrics = (complementary[metric.key] || []).map(key => metrics[key]);
- const notBestComponents = components.filter(
- component => !this.hasBestValue(component, otherMetrics)
- );
+ const notBestComponents = components.filter(component => !this.hasBestValue(component));
const hiddenCount = components.length - notBestComponents.length;
const shouldHideBest = this.state.hideBest && hiddenCount !== components.length;
return (
@@ -131,14 +108,21 @@ export default class ComponentsList extends React.PureComponent {
)}
<tbody>
- {(shouldHideBest ? notBestComponents : components).map(component =>
- this.renderComponent(component, otherMetrics)
- )}
+ {(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, otherMetrics.length + 3)}
+ {shouldHideBest && hiddenCount > 0 && this.renderHiddenLink(hiddenCount)}
</React.Fragment>
);
}
diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/EmptyResult.js b/server/sonar-web/src/main/js/apps/component-measures/drilldown/EmptyResult.tsx
index 8f423a92008..9f5d7a8f4df 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/drilldown/EmptyResult.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/EmptyResult.tsx
@@ -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.
*/
-// @flow
-import React from 'react';
+import * as React from 'react';
import { translate } from '../../../helpers/l10n';
export default function EmptyResult() {
diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/FilesView.js b/server/sonar-web/src/main/js/apps/component-measures/drilldown/FilesView.tsx
index 93f464ef47b..a4b0144ba08 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/drilldown/FilesView.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/FilesView.tsx
@@ -17,36 +17,39 @@
* 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 key from 'keymaster';
+import * as React from 'react';
+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 type { Component, ComponentEnhanced, Paging } from '../types'; */
-/*:: import type { Metric } from '../../../store/metrics/actions'; */
+import {
+ ComponentMeasure,
+ ComponentMeasureEnhanced,
+ Metric,
+ Paging,
+ BranchLike
+} from '../../../app/types';
-/*:: type Props = {|
- bestValue?: string,
- branchLike?: { id?: string; name: string },
- components: Array<ComponentEnhanced>,
- fetchMore: () => void,
- handleSelect: string => void,
- handleOpen: string => void,
- metric: Metric,
- metrics: { [string]: Metric },
- paging: ?Paging,
- rootComponent: Component,
- selectedKey: ?string,
- selectedIdx: ?number
-|}; */
+interface Props {
+ bestValue?: string;
+ branchLike?: BranchLike;
+ components: ComponentMeasureEnhanced[];
+ fetchMore: () => void;
+ handleSelect: (component: string) => void;
+ handleOpen: (component: string) => void;
+ metric: Metric;
+ metrics: { [metric: string]: Metric };
+ paging?: Paging;
+ rootComponent: ComponentMeasure;
+ selectedKey?: string;
+ selectedIdx?: number;
+}
-export default class ListView extends React.PureComponent {
- /*:: listContainer: HTMLElement; */
- /*:: props: Props; */
+export default class ListView extends React.PureComponent<Props> {
+ listContainer?: HTMLElement | null;
- constructor(props /*: Props */) {
+ constructor(props: Props) {
super(props);
this.selectNext = throttle(this.selectNext, 100);
this.selectPrevious = throttle(this.selectPrevious, 100);
@@ -54,13 +57,13 @@ export default class ListView extends React.PureComponent {
componentDidMount() {
this.attachShortcuts();
- if (this.props.selectedKey != null) {
+ if (this.props.selectedKey !== undefined) {
this.scrollToElement();
}
}
- componentDidUpdate(prevProps /*: Props */) {
- if (this.props.selectedKey != null && prevProps.selectedKey !== this.props.selectedKey) {
+ componentDidUpdate(prevProps: Props) {
+ if (this.props.selectedKey !== undefined && prevProps.selectedKey !== this.props.selectedKey) {
this.scrollToElement();
}
}
@@ -85,18 +88,18 @@ export default class ListView extends React.PureComponent {
}
detachShortcuts() {
- ['up', 'down', 'right'].map(action => key.unbind(action, 'measures-files'));
+ ['up', 'down', 'right'].forEach(action => key.unbind(action, 'measures-files'));
}
openSelected = () => {
- if (this.props.selectedKey != null) {
+ if (this.props.selectedKey !== undefined) {
this.props.handleOpen(this.props.selectedKey);
}
};
selectPrevious = () => {
const { selectedIdx } = this.props;
- if (selectedIdx != null && selectedIdx > 0) {
+ if (selectedIdx !== undefined && selectedIdx > 0) {
this.props.handleSelect(this.props.components[selectedIdx - 1].key);
} else {
this.props.handleSelect(this.props.components[this.props.components.length - 1].key);
@@ -105,7 +108,7 @@ export default class ListView extends React.PureComponent {
selectNext = () => {
const { selectedIdx } = this.props;
- if (selectedIdx != null && selectedIdx < this.props.components.length - 1) {
+ if (selectedIdx !== undefined && selectedIdx < this.props.components.length - 1) {
this.props.handleSelect(this.props.components[selectedIdx + 1].key);
} else {
this.props.handleSelect(this.props.components[0].key);
diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/TreeMapView.js b/server/sonar-web/src/main/js/apps/component-measures/drilldown/TreeMapView.tsx
index ce1735a02ca..828cab940d7 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/drilldown/TreeMapView.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/TreeMapView.tsx
@@ -17,107 +17,105 @@
* 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';
-// $FlowFixMe
-import AutoSizer from 'react-virtualized/dist/commonjs/AutoSizer';
+
+import * as React from 'react';
+import { AutoSizer } from 'react-virtualized/dist/commonjs/AutoSizer';
import { scaleLinear, scaleOrdinal } from 'd3-scale';
import EmptyResult from './EmptyResult';
import * as theme from '../../../app/theme';
import ColorBoxLegend from '../../../components/charts/ColorBoxLegend';
import ColorGradientLegend from '../../../components/charts/ColorGradientLegend';
import QualifierIcon from '../../../components/icons-components/QualifierIcon';
-import TreeMap from '../../../components/charts/TreeMap';
+import TreeMap, { TreeMapItem } from '../../../components/charts/TreeMap';
import { translate, translateWithParameters, getLocalizedMetricName } from '../../../helpers/l10n';
import { formatMeasure, isDiffMetric } from '../../../helpers/measures';
import { getBranchLikeUrl } from '../../../helpers/urls';
-/*:: import type { Metric } from '../../../store/metrics/actions'; */
-/*:: import type { ComponentEnhanced } from '../types'; */
-/*:: import type { TreeMapItem } from '../../../components/charts/TreeMap'; */
+import { BranchLike, ComponentMeasureEnhanced, Metric } from '../../../app/types';
-/*:: type Props = {|
- branchLike?: { id?: string; name: string },
- components: Array<ComponentEnhanced>,
- handleSelect: string => void,
- metric: Metric
-|}; */
+interface Props {
+ branchLike?: BranchLike;
+ components: ComponentMeasureEnhanced[];
+ handleSelect: (component: string) => void;
+ metric: Metric;
+}
-/*:: type State = {
- treemapItems: Array<TreeMapItem>
-}; */
+interface State {
+ treemapItems: TreeMapItem[];
+}
const HEIGHT = 500;
const COLORS = [theme.green, theme.lightGreen, theme.yellow, theme.orange, theme.red];
const LEVEL_COLORS = [theme.red, theme.orange, theme.green, theme.gray71];
-export default class TreeMapView extends React.PureComponent {
- /*:: props: Props; */
- /*:: state: State; */
+export default class TreeMapView extends React.PureComponent<Props, State> {
+ state: State;
- constructor(props /*: Props */) {
+ constructor(props: Props) {
super(props);
this.state = { treemapItems: this.getTreemapComponents(props) };
}
- componentWillReceiveProps(nextProps /*: Props */) {
+ componentWillReceiveProps(nextProps: Props) {
if (nextProps.components !== this.props.components || nextProps.metric !== this.props.metric) {
this.setState({ treemapItems: this.getTreemapComponents(nextProps) });
}
}
- getTreemapComponents = ({ branchLike, components, metric } /*: Props */) => {
+ getTreemapComponents = ({ branchLike, components, metric }: Props) => {
const colorScale = this.getColorScale(metric);
return components
.map(component => {
const colorMeasure = component.measures.find(measure => measure.metric.key === metric.key);
const sizeMeasure = component.measures.find(measure => measure.metric.key !== metric.key);
- if (sizeMeasure == null) {
- return null;
+ if (!sizeMeasure) {
+ return undefined;
}
const colorValue =
colorMeasure && (isDiffMetric(metric.key) ? colorMeasure.leak : colorMeasure.value);
- const sizeValue = isDiffMetric(sizeMeasure.metric.key)
- ? sizeMeasure.leak
- : sizeMeasure.value;
- if (sizeValue == null) {
- return null;
+ const sizeValue = Number(
+ isDiffMetric(sizeMeasure.metric.key) ? sizeMeasure.leak : sizeMeasure.value
+ );
+ if (isNaN(sizeValue)) {
+ return undefined;
}
+
return {
+ color:
+ colorValue !== undefined ? (colorScale as Function)(colorValue) : theme.secondFontColor,
+ icon: <QualifierIcon fill={theme.baseFontColor} qualifier={component.qualifier} />,
key: component.refKey || component.key,
+ label: component.name,
+ link: getBranchLikeUrl(component.refKey || component.key, branchLike),
size: sizeValue,
- color: colorValue != null ? colorScale(colorValue) : theme.secondFontColor,
- icon: <QualifierIcon color={theme.baseFontColor} qualifier={component.qualifier} />,
- tooltip: this.getTooltip(
- component.name,
- metric,
- sizeMeasure.metric,
+ tooltip: this.getTooltip({
+ colorMetric: metric,
colorValue,
+ componentName: component.name,
+ sizeMetric: sizeMeasure.metric,
sizeValue
- ),
- label: component.name,
- link: getBranchLikeUrl(component.refKey || component.key, branchLike)
+ })
};
})
- .filter(Boolean);
+ .filter(Boolean) as TreeMapItem[];
};
getLevelColorScale = () =>
- scaleOrdinal()
+ scaleOrdinal<string, string>()
.domain(['ERROR', 'WARN', 'OK', 'NONE'])
.range(LEVEL_COLORS);
- getPercentColorScale = (metric /*: Metric */) => {
- const color = scaleLinear().domain([0, 25, 50, 75, 100]);
- color.range(metric.direction === 1 ? COLORS.reverse() : COLORS);
+ getPercentColorScale = (metric: Metric) => {
+ const color = scaleLinear<string, string>().domain([0, 25, 50, 75, 100]);
+ color.range(metric.direction === 1 ? [...COLORS].reverse() : COLORS);
return color;
};
getRatingColorScale = () =>
- scaleLinear()
+ scaleLinear<string, string>()
.domain([1, 2, 3, 4, 5])
.range(COLORS);
- getColorScale = (metric /*: Metric */) => {
+ getColorScale = (metric: Metric) => {
if (metric.type === 'LEVEL') {
return this.getLevelColorScale();
}
@@ -127,15 +125,21 @@ export default class TreeMapView extends React.PureComponent {
return this.getPercentColorScale(metric);
};
- getTooltip = (
- componentName /*: string */,
- colorMetric /*: Metric */,
- sizeMetric /*: Metric */,
- colorValue /*: ?number */,
- sizeValue /*: number */
- ) => {
+ getTooltip = ({
+ colorMetric,
+ colorValue,
+ componentName,
+ sizeMetric,
+ sizeValue
+ }: {
+ colorMetric: Metric;
+ colorValue?: string;
+ componentName: string;
+ sizeMetric: Metric;
+ sizeValue: number;
+ }) => {
const formatted =
- colorMetric != null && colorValue != null ? formatMeasure(colorValue, colorMetric.type) : '—';
+ colorMetric && colorValue !== undefined ? formatMeasure(colorValue, colorMetric.type) : '—';
return (
<div className="text-left">
{componentName}
diff --git a/server/sonar-web/src/main/js/apps/component-measures/utils.js b/server/sonar-web/src/main/js/apps/component-measures/utils.ts
index d822e66e674..553edee41f4 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/utils.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/utils.ts
@@ -17,17 +17,14 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-// @flow
import { groupBy, memoize, sortBy, toPairs } from 'lodash';
import { domains } from './config/domains';
import { bubbles } from './config/bubbles';
import { getLocalizedMetricName } from '../../helpers/l10n';
-import { cleanQuery, parseAsString, serializeString } from '../../helpers/query';
+import { ComponentMeasure, ComponentMeasureEnhanced, Metric } from '../../app/types';
import { enhanceMeasure } from '../../components/measure/utils';
-/*:: import type { Component, ComponentEnhanced, Query } from './types'; */
-/*:: import type { RawQuery } from '../../helpers/query'; */
-/*:: import type { Metric } from '../../store/metrics/actions'; */
-/*:: import type { MeasureEnhanced } from '../../components/measure/types'; */
+import { cleanQuery, parseAsString, RawQuery, serializeString } from '../../helpers/query';
+import { MeasureEnhanced } from '../../helpers/measures';
export const PROJECT_OVERVEW = 'project_overview';
export const DEFAULT_VIEW = 'list';
@@ -55,33 +52,32 @@ const BANNED_MEASURES = [
'new_info_violations'
];
-export function filterMeasures(
- measures /*: Array<MeasureEnhanced> */
-) /*: Array<MeasureEnhanced> */ {
+export function filterMeasures(measures: MeasureEnhanced[]): MeasureEnhanced[] {
return measures.filter(measure => !BANNED_MEASURES.includes(measure.metric.key));
}
export function sortMeasures(
- domainName /*: string */,
- measures /*: Array<MeasureEnhanced | string> */
-) /*: Array<MeasureEnhanced | string> */ {
+ domainName: string,
+ measures: Array<MeasureEnhanced | string>
+): Array<MeasureEnhanced | string> {
const config = domains[domainName] || {};
const configOrder = config.order || [];
return sortBy(measures, [
- item => {
+ (item: MeasureEnhanced | string) => {
if (typeof item === 'string') {
return configOrder.indexOf(item);
}
const idx = configOrder.indexOf(item.metric.key);
return idx >= 0 ? idx : configOrder.length;
},
- item => (typeof item === 'string' ? item : getLocalizedMetricName(item.metric))
+ (item: MeasureEnhanced | string) =>
+ typeof item === 'string' ? item : getLocalizedMetricName(item.metric)
]);
}
export function addMeasureCategories(
- domainName /*: string */,
- measures /*: Array<MeasureEnhanced> */
+ domainName: string,
+ measures: MeasureEnhanced[]
) /*: Array<any> */ {
const categories = domains[domainName] && domains[domainName].categories;
if (categories && categories.length > 0) {
@@ -91,34 +87,37 @@ export function addMeasureCategories(
}
export function enhanceComponent(
- component /*: Component */,
- metric /*: ?Metric */,
- metrics /*: { [string]: Metric } */
-) /*: ComponentEnhanced */ {
+ component: ComponentMeasure,
+ metric: Metric | undefined,
+ metrics: { [key: string]: Metric }
+): ComponentMeasureEnhanced {
+ if (!component.measures) {
+ return { ...component, measures: [] };
+ }
+
const enhancedMeasures = component.measures.map(measure => enhanceMeasure(measure, metrics));
- // $FlowFixMe metric can't be null since there is a guard for it
const measure = metric && enhancedMeasures.find(measure => measure.metric.key === metric.key);
- const value = measure ? measure.value : null;
- const leak = measure ? measure.leak : null;
+ const value = measure && measure.value;
+ const leak = measure && measure.leak;
return { ...component, value, leak, measures: enhancedMeasures };
}
-export function isFileType(component /*: Component */) /*: boolean */ {
+export function isFileType(component: ComponentMeasure): boolean {
return ['FIL', 'UTS'].includes(component.qualifier);
}
-export function isViewType(component /*: Component */) /*: boolean */ {
+export function isViewType(component: ComponentMeasure): boolean {
return ['VW', 'SVW', 'APP'].includes(component.qualifier);
}
-export const groupByDomains = memoize((measures /*: Array<MeasureEnhanced> */) => {
+export const groupByDomains = memoize((measures: MeasureEnhanced[]) => {
const domains = toPairs(groupBy(measures, measure => measure.metric.domain)).map(r => ({
name: r[0],
measures: r[1]
}));
return sortBy(domains, [
- domain => {
+ (domain: { name: string; measure: MeasureEnhanced[] }) => {
const idx = KNOWN_DOMAINS.indexOf(domain.name);
return idx >= 0 ? idx : KNOWN_DOMAINS.length;
},
@@ -126,34 +125,34 @@ export const groupByDomains = memoize((measures /*: Array<MeasureEnhanced> */) =
]);
});
-export function getDefaultView(metric /*: string */) /*: string */ {
+export function getDefaultView(metric: string): string {
if (!hasList(metric)) {
return 'tree';
}
return DEFAULT_VIEW;
}
-export function hasList(metric /*: string */) /*: boolean */ {
+export function hasList(metric: string): boolean {
return !['releasability_rating', 'releasability_effort'].includes(metric);
}
-export function hasTree(metric /*: string */) /*: boolean */ {
+export function hasTree(metric: string): boolean {
return metric !== 'alert_status';
}
-export function hasTreemap(metric /*: string */, type /*: string */) /*: boolean */ {
+export function hasTreemap(metric: string, type: string): boolean {
return ['PERCENT', 'RATING', 'LEVEL'].includes(type) && hasTree(metric);
}
-export function hasBubbleChart(domainName /*: string */) /*: boolean */ {
+export function hasBubbleChart(domainName: string): boolean {
return bubbles[domainName] != null;
}
-export function hasFacetStat(metric /*: string */) /*: boolean */ {
+export function hasFacetStat(metric: string): boolean {
return metric !== 'alert_status';
}
-export function getBubbleMetrics(domain /*: string */, metrics /*: { [string]: Metric } */) {
+export function getBubbleMetrics(domain: string, metrics: { [key: string]: Metric }) {
const conf = bubbles[domain];
return {
x: metrics[conf.x],
@@ -163,15 +162,15 @@ export function getBubbleMetrics(domain /*: string */, metrics /*: { [string]: M
};
}
-export function getBubbleYDomain(domain /*: string */) {
+export function getBubbleYDomain(domain: string) {
return bubbles[domain].yDomain;
}
-export function isProjectOverview(metric /*: string */) {
+export function isProjectOverview(metric: string) {
return metric === PROJECT_OVERVEW;
}
-const parseView = memoize((rawView /*:: ? */ /*: string */, metric /*: string */) => {
+const parseView = (metric: string, rawView?: string) => {
const view = parseAsString(rawView) || DEFAULT_VIEW;
if (!hasTree(metric)) {
return 'list';
@@ -179,18 +178,24 @@ const parseView = memoize((rawView /*:: ? */ /*: string */, metric /*: string */
return 'tree';
}
return view;
-});
+};
+
+export interface Query {
+ metric?: string;
+ selected?: string;
+ view: string;
+}
-export const parseQuery = memoize((urlQuery /*: RawQuery */) => {
+export const parseQuery = memoize((urlQuery: RawQuery) => {
const metric = parseAsString(urlQuery['metric']) || DEFAULT_METRIC;
return {
metric,
selected: parseAsString(urlQuery['selected']),
- view: parseView(urlQuery['view'], metric)
+ view: parseView(metric, urlQuery['view'])
};
});
-export const serializeQuery = memoize((query /*: Query */) => {
+export const serializeQuery = memoize((query: Query) => {
return cleanQuery({
metric: query.metric === DEFAULT_METRIC ? null : serializeString(query.metric),
selected: serializeString(query.selected),
diff --git a/server/sonar-web/src/main/js/components/charts/ColorGradientLegend.tsx b/server/sonar-web/src/main/js/components/charts/ColorGradientLegend.tsx
index ae2eb08bc30..29fe3a07492 100644
--- a/server/sonar-web/src/main/js/components/charts/ColorGradientLegend.tsx
+++ b/server/sonar-web/src/main/js/components/charts/ColorGradientLegend.tsx
@@ -26,7 +26,7 @@ interface Props {
colorNA?: string;
colorScale:
| ScaleOrdinal<string, string> // used for LEVEL type
- | ScaleLinear<number, string | number>; // used for RATING or PERCENT type
+ | ScaleLinear<string, string | number>; // used for RATING or PERCENT type
direction?: number;
height: number;
padding?: [number, number, number, number];
diff --git a/server/sonar-web/src/main/js/components/charts/TreeMap.tsx b/server/sonar-web/src/main/js/components/charts/TreeMap.tsx
index 489617142f3..5116b3b726e 100644
--- a/server/sonar-web/src/main/js/components/charts/TreeMap.tsx
+++ b/server/sonar-web/src/main/js/components/charts/TreeMap.tsx
@@ -23,12 +23,12 @@ import TreeMapRect from './TreeMapRect';
import { translate } from '../../helpers/l10n';
import './TreeMap.css';
-interface TreeMapItem {
+export interface TreeMapItem {
color: string;
icon?: React.ReactNode;
key: string;
label: string;
- link?: string;
+ link?: string | Location;
size: number;
tooltip?: React.ReactNode;
}
diff --git a/server/sonar-web/src/main/js/components/charts/TreeMapRect.tsx b/server/sonar-web/src/main/js/components/charts/TreeMapRect.tsx
index 75e8cdc8db5..3c3341028b5 100644
--- a/server/sonar-web/src/main/js/components/charts/TreeMapRect.tsx
+++ b/server/sonar-web/src/main/js/components/charts/TreeMapRect.tsx
@@ -35,7 +35,7 @@ interface Props {
icon?: React.ReactNode;
itemKey: string;
label: string;
- link?: string;
+ link?: string | Location;
onClick?: (item: string) => void;
placement?: Placement;
prefix: string;
diff --git a/server/sonar-web/src/main/js/helpers/measures.ts b/server/sonar-web/src/main/js/helpers/measures.ts
index 596ea2a3685..171e04121f2 100644
--- a/server/sonar-web/src/main/js/helpers/measures.ts
+++ b/server/sonar-web/src/main/js/helpers/measures.ts
@@ -23,14 +23,15 @@ import { Metric } from '../app/types';
const HOURS_IN_DAY = 8;
export interface MeasurePeriod {
+ bestValue?: boolean;
index: number;
value: string;
- bestValue?: boolean;
}
export interface MeasureIntern {
- value?: string;
+ bestValue?: boolean;
periods?: MeasurePeriod[];
+ value?: string;
}
export interface Measure extends MeasureIntern {