diff options
Diffstat (limited to 'server/sonar-web/src/main/js/components')
-rw-r--r-- | server/sonar-web/src/main/js/components/charts/BarChart.tsx | 169 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/components/charts/BubbleChart.tsx | 41 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/components/charts/ColorBoxLegend.tsx (renamed from server/sonar-web/src/main/js/components/charts/ColorBoxLegend.js) | 26 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/components/charts/ColorGradientLegend.tsx (renamed from server/sonar-web/src/main/js/components/charts/ColorGradientLegend.js) | 66 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/components/charts/ColorRatingsLegend.tsx (renamed from server/sonar-web/src/main/js/components/charts/ColorRatingsLegend.js) | 11 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/components/charts/DonutChart.tsx | 83 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/components/charts/LineChart.tsx (renamed from server/sonar-web/src/main/js/components/charts/line-chart.js) | 165 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/components/charts/TreeMap.tsx (renamed from server/sonar-web/src/main/js/components/charts/TreeMap.js) | 65 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/components/charts/TreeMapRect.tsx (renamed from server/sonar-web/src/main/js/components/charts/TreeMapRect.js) | 51 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/components/charts/__tests__/BarChart-test.tsx (renamed from server/sonar-web/src/main/js/components/charts/__tests__/bar-chart-test.js) | 12 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/components/charts/__tests__/BubbleChart-test.tsx (renamed from server/sonar-web/src/main/js/components/charts/__tests__/BubbleChart-test.js) | 8 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/components/charts/__tests__/LineChart-test.tsx (renamed from server/sonar-web/src/main/js/components/charts/__tests__/line-chart-test.js) | 10 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/components/charts/__tests__/TreeMap-test.tsx (renamed from server/sonar-web/src/main/js/components/charts/__tests__/TreeMap-test.js) | 4 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/components/charts/__tests__/__snapshots__/BubbleChart-test.tsx.snap (renamed from server/sonar-web/src/main/js/components/charts/__tests__/__snapshots__/BubbleChart-test.js.snap) | 0 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/components/charts/bar-chart.js | 187 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/components/charts/donut-chart.js | 84 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/components/common/LocationIndex.tsx (renamed from server/sonar-web/src/main/js/components/common/LocationIndex.js) | 32 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/components/common/LocationMessage.tsx (renamed from server/sonar-web/src/main/js/components/common/LocationMessage.js) | 17 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/components/common/OrganizationHelmet.tsx (renamed from server/sonar-web/src/main/js/components/common/OrganizationHelmet.js) | 15 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/components/common/UpgradeOrganizationBox.tsx (renamed from server/sonar-web/src/main/js/components/common/UpgradeOrganizationBox.js) | 15 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/components/controls/GlobalMessages.tsx (renamed from server/sonar-web/src/main/js/components/controls/GlobalMessages.js) | 34 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/components/controls/Toggle.tsx (renamed from server/sonar-web/src/main/js/components/controls/Toggle.js) | 34 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/components/controls/__tests__/ListFooter-test.tsx (renamed from server/sonar-web/src/main/js/components/controls/__tests__/ListFooter-test.js) | 6 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/components/controls/__tests__/Toggle-test.tsx (renamed from server/sonar-web/src/main/js/components/controls/__tests__/Toggle-test.js) | 16 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/components/icons-components/BugTrackerIcon.tsx (renamed from server/sonar-web/src/main/js/components/ui/BugTrackerIcon.js) | 32 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/components/icons-components/ClockIcon.tsx (renamed from server/sonar-web/src/main/js/components/common/ClockIcon.js) | 31 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/components/icons-components/PinIcon.tsx | 39 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueTitleBar-test.js.snap | 4 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/components/shared/SeverityIcon.tsx (renamed from server/sonar-web/src/main/js/components/shared/SeverityIcon.js) | 17 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/components/shared/StatusHelper.tsx (renamed from server/sonar-web/src/main/js/components/shared/StatusHelper.js) | 20 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/components/shared/StatusIcon.tsx (renamed from server/sonar-web/src/main/js/components/shared/StatusIcon.js) | 15 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/components/shared/WithStore.js | 52 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/components/shared/__tests__/QualifierIcon-test.tsx (renamed from server/sonar-web/src/main/js/components/shared/__tests__/QualifierIcon-test.js) | 2 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/components/shared/__tests__/__snapshots__/QualifierIcon-test.tsx.snap (renamed from server/sonar-web/src/main/js/components/shared/__tests__/__snapshots__/QualifierIcon-test.js.snap) | 0 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/components/shared/pin-icon.js | 33 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/components/ui/CoverageRating.tsx | 4 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/components/ui/__tests__/Level-test.tsx (renamed from server/sonar-web/src/main/js/components/ui/__tests__/Level-test.js) | 2 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/components/ui/buttons.tsx | 1 |
38 files changed, 669 insertions, 734 deletions
diff --git a/server/sonar-web/src/main/js/components/charts/BarChart.tsx b/server/sonar-web/src/main/js/components/charts/BarChart.tsx new file mode 100644 index 00000000000..7739f1d143d --- /dev/null +++ b/server/sonar-web/src/main/js/components/charts/BarChart.tsx @@ -0,0 +1,169 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { max } from 'd3-array'; +import { scaleLinear, scaleBand, ScaleLinear, ScaleBand } from 'd3-scale'; +import Tooltip from '../controls/Tooltip'; + +interface DataPoint { + tooltip?: React.ReactNode; + x: number; + y: number; +} + +interface Props<T> { + barsWidth: number; + data: Array<DataPoint & T>; + height: number; + onBarClick?: (point: DataPoint & T) => void; + padding?: [number, number, number, number]; + width: number; + xTicks?: string[]; + xValues?: string[]; +} + +export default class BarChart<T> extends React.PureComponent<Props<T>> { + handleClick = (point: DataPoint & T) => { + if (this.props.onBarClick) { + this.props.onBarClick(point); + } + }; + + renderXTicks = (xScale: ScaleBand<number>, yScale: ScaleLinear<number, number>) => { + const { data, xTicks = [] } = this.props; + + if (!xTicks.length) { + return null; + } + + const ticks = xTicks.map((tick, index) => { + const point = data[index]; + const x = Math.round((xScale(point.x) as number) + xScale.bandwidth() / 2); + const y = yScale.range()[0]; + const d = data[index]; + const text = ( + <text + className="bar-chart-tick" + dy="1.5em" + key={index} + onClick={() => this.handleClick(point)} + style={{ cursor: this.props.onBarClick ? 'pointer' : 'default' }} + x={x} + y={y}> + {tick} + </text> + ); + return ( + <Tooltip key={index} overlay={d.tooltip || undefined}> + {text} + </Tooltip> + ); + }); + return <g>{ticks}</g>; + }; + + renderXValues = (xScale: ScaleBand<number>, yScale: ScaleLinear<number, number>) => { + const { data, xValues = [] } = this.props; + + if (!xValues.length) { + return null; + } + + const ticks = xValues.map((value, index) => { + const point = data[index]; + const x = Math.round((xScale(point.x) as number) + xScale.bandwidth() / 2); + const y = yScale(point.y); + const text = ( + <text + className="bar-chart-tick" + dy="-1em" + key={index} + onClick={() => this.handleClick(point)} + style={{ cursor: this.props.onBarClick ? 'pointer' : 'default' }} + x={x} + y={y}> + {value} + </text> + ); + return ( + <Tooltip key={index} overlay={point.tooltip || undefined}> + {text} + </Tooltip> + ); + }); + return <g>{ticks}</g>; + }; + + renderBars = (xScale: ScaleBand<number>, yScale: ScaleLinear<number, number>) => { + const bars = this.props.data.map((point, index) => { + const x = Math.round(xScale(point.x) as number); + const maxY = yScale.range()[0]; + const y = Math.round(yScale(point.y)) - /* minimum bar height */ 1; + const height = maxY - y; + const rect = ( + <rect + className="bar-chart-bar" + height={height} + key={index} + onClick={() => this.handleClick(point)} + style={{ cursor: this.props.onBarClick ? 'pointer' : 'default' }} + width={this.props.barsWidth} + x={x} + y={y} + /> + ); + return ( + <Tooltip key={index} overlay={point.tooltip || undefined}> + {rect} + </Tooltip> + ); + }); + return <g>{bars}</g>; + }; + + render() { + const { barsWidth, data, width, height, padding = [10, 10, 10, 10] } = this.props; + + const availableWidth = width - padding[1] - padding[3]; + const availableHeight = height - padding[0] - padding[2]; + + const innerPadding = (availableWidth - barsWidth * data.length) / (data.length - 1); + const relativeInnerPadding = innerPadding / (innerPadding + barsWidth); + + const maxY = max(data, d => d.y) as number; + const xScale = scaleBand<number>() + .domain(data.map(d => d.x)) + .range([0, availableWidth]) + .paddingInner(relativeInnerPadding); + const yScale = scaleLinear() + .domain([0, maxY]) + .range([availableHeight, 0]); + + return ( + <svg className="bar-chart" height={height} width={width}> + <g transform={`translate(${padding[3]}, ${padding[0]})`}> + {this.renderXTicks(xScale, yScale)} + {this.renderXValues(xScale, yScale)} + {this.renderBars(xScale, yScale)} + </g> + </svg> + ); + } +} diff --git a/server/sonar-web/src/main/js/components/charts/BubbleChart.tsx b/server/sonar-web/src/main/js/components/charts/BubbleChart.tsx index 40be474bb72..d280be68922 100644 --- a/server/sonar-web/src/main/js/components/charts/BubbleChart.tsx +++ b/server/sonar-web/src/main/js/components/charts/BubbleChart.tsx @@ -86,12 +86,12 @@ interface Props { displayXTicks?: boolean; displayYGrid?: boolean; displayYTicks?: boolean; - formatXTick: (tick: number) => string; - formatYTick: (tick: number) => string; + formatXTick?: (tick: number) => string; + formatYTick?: (tick: number) => string; height: number; items: Item[]; onBubbleClick?: (link?: string) => void; - padding: [number, number, number, number]; + padding?: [number, number, number, number]; sizeDomain?: [number, number]; sizeRange?: [number, number]; xDomain?: [number, number]; @@ -115,9 +115,6 @@ export default class BubbleChart extends React.Component<Props, State> { displayXTicks: true, displayYGrid: true, displayYTicks: true, - formatXTick: (d: number) => d, - formatYTick: (d: number) => d, - padding: [10, 10, 10, 10], sizeRange: [5, 45] }; @@ -141,6 +138,18 @@ export default class BubbleChart extends React.Component<Props, State> { document.removeEventListener('mousemove', this.updateZoomCenter); } + get formatXTick() { + return this.props.formatXTick || ((d: number) => String(d)); + } + + get formatYTick() { + return this.props.formatYTick || ((d: number) => String(d)); + } + + get padding() { + return this.props.padding || [10, 10, 10, 10]; + } + startMoving = (event: React.MouseEvent<SVGSVGElement>) => { if (this.node && this.state.zoom > 1) { const rect = this.node.getBoundingClientRect(); @@ -176,8 +185,8 @@ export default class BubbleChart extends React.Component<Props, State> { event.preventDefault(); const rect = this.node.getBoundingClientRect(); - const mouseX = event.clientX - rect.left - this.props.padding[1]; - const mouseY = event.clientY - rect.top - this.props.padding[0]; + const mouseX = event.clientX - rect.left - this.padding[1]; + const mouseY = event.clientY - rect.top - this.padding[0]; let delta = event.deltaY; if ((event as any).webkitDirectionInvertedFromDevice) { @@ -308,7 +317,7 @@ export default class BubbleChart extends React.Component<Props, State> { const ticks = xTicks.map((tick, index) => { const x = xScale(tick); const y = yScale.range()[0]; - const innerText = this.props.formatXTick(tick); + const innerText = this.formatXTick(tick); return ( <text className="bubble-chart-tick" @@ -332,7 +341,7 @@ export default class BubbleChart extends React.Component<Props, State> { const ticks = yTicks.map((tick, index) => { const x = xScale.range()[0]; const y = yScale(tick); - const innerText = this.props.formatYTick(tick); + const innerText = this.formatYTick(tick); return ( <text className="bubble-chart-tick bubble-chart-tick-y" @@ -350,8 +359,8 @@ export default class BubbleChart extends React.Component<Props, State> { }; renderChart = (width: number) => { - const availableWidth = width - this.props.padding[1] - this.props.padding[3]; - const availableHeight = this.props.height - this.props.padding[0] - this.props.padding[2]; + const availableWidth = width - this.padding[1] - this.padding[3]; + const availableHeight = this.props.height - this.padding[0] - this.padding[2]; const xScale = scaleLinear() .domain(this.props.xDomain || [0, max(this.props.items, d => d.x) || 0]) @@ -389,8 +398,8 @@ export default class BubbleChart extends React.Component<Props, State> { ); }); - const xTicks = this.getTicks(xScale, this.props.formatXTick); - const yTicks = this.getTicks(yScale, this.props.formatYTick); + const xTicks = this.getTicks(xScale, this.formatXTick); + const yTicks = this.getTicks(yScale, this.formatYTick); return ( <svg @@ -400,9 +409,9 @@ export default class BubbleChart extends React.Component<Props, State> { onWheel={this.onWheel} ref={node => (this.node = node)} width={width}> - <g transform={`translate(${this.props.padding[3]}, ${this.props.padding[0]})`}> + <g transform={`translate(${this.padding[3]}, ${this.padding[0]})`}> <svg - height={this.props.height - this.props.padding[0] - this.props.padding[2]} + height={this.props.height - this.padding[0] - this.padding[2]} style={{ overflow: 'hidden' }} width={width}> {this.renderXGrid(xTicks, xScale, yScale, centerXDelta)} diff --git a/server/sonar-web/src/main/js/components/charts/ColorBoxLegend.js b/server/sonar-web/src/main/js/components/charts/ColorBoxLegend.tsx index 6d21cca808f..cf2a140d2a2 100644 --- a/server/sonar-web/src/main/js/components/charts/ColorBoxLegend.js +++ b/server/sonar-web/src/main/js/components/charts/ColorBoxLegend.tsx @@ -17,22 +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 classNames from 'classnames'; +import * as React from 'react'; +import * as classNames from 'classnames'; +import { ScaleLinear, ScaleOrdinal } from 'd3-scale'; import { formatMeasure } from '../../helpers/measures'; -/*:: type Props = { - className?: string, - colorScale: Object, - colorNA?: string, - metricType: string -}; */ +interface Props { + className?: string; + colorNA?: string; + colorScale: + | ScaleOrdinal<string, string> // used for LEVEL type + | ScaleLinear<number, string | number>; // used for RATING or PERCENT type + metricType: string; +} -export default function ColorBoxLegend( - { className, colorScale, colorNA, metricType } /*: Props */ -) { - const colorDomain = colorScale.domain(); +export default function ColorBoxLegend({ className, colorScale, colorNA, metricType }: Props) { + const colorDomain: Array<number | string> = colorScale.domain(); const colorRange = colorScale.range(); return ( <div className={classNames('color-box-legend', className)}> diff --git a/server/sonar-web/src/main/js/components/charts/ColorGradientLegend.js b/server/sonar-web/src/main/js/components/charts/ColorGradientLegend.tsx index 06ca596b336..5ea00b6be59 100644 --- a/server/sonar-web/src/main/js/components/charts/ColorGradientLegend.js +++ b/server/sonar-web/src/main/js/components/charts/ColorGradientLegend.tsx @@ -17,60 +17,60 @@ * 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 { ScaleLinear, ScaleOrdinal } from 'd3-scale'; -/*:: type Props = { - className?: string, - colorScale: Object, - colorNA?: string, - direction?: number, - padding?: Array<number>, - height: number, - width: number -}; */ +interface Props { + className?: string; + colorNA?: string; + colorScale: + | ScaleOrdinal<string, string> // used for LEVEL type + | ScaleLinear<number, string | number>; // used for RATING or PERCENT type + direction?: number; + height: number; + padding?: [number, number, number, number]; + width: number; +} const NA_SPACING = 4; -export default function ColorGradientLegend( - { - className, - colorScale, - colorNA, - direction, - padding = [12, 24, 0, 0], - height, - width - } /*: Props */ -) { - const colorRange = colorScale.range(); +export default function ColorGradientLegend({ + className, + colorScale, + colorNA, + direction, + padding = [12, 24, 0, 0], + height, + width +}: Props) { + const colorRange: Array<string | number> = colorScale.range(); if (direction === 1) { colorRange.reverse(); } - const colorDomain = colorScale.domain(); + const colorDomain: Array<string | number> = colorScale.domain(); const lastColorIdx = colorRange.length - 1; const lastDomainIdx = colorDomain.length - 1; const widthNoPadding = width - padding[1]; const rectHeight = height - padding[0]; return ( - <svg className={className} width={width} height={height}> + <svg className={className} height={height} width={width}> <defs> <linearGradient id="gradient-legend"> {colorRange.map((color, idx) => ( - <stop key={idx} offset={idx / lastColorIdx} stopColor={color} /> + <stop key={idx} offset={idx / lastColorIdx} stopColor={String(color)} /> ))} </linearGradient> </defs> <g transform={`translate(${padding[3]}, ${padding[0]})`}> - <rect fill="url(#gradient-legend)" x={0} y={0} height={rectHeight} width={widthNoPadding} /> + <rect fill="url(#gradient-legend)" height={rectHeight} width={widthNoPadding} x={0} y={0} /> {colorDomain.map((d, idx) => ( <text className="gradient-legend-text" + dy="-2px" key={idx} x={widthNoPadding * (idx / lastDomainIdx)} - y={0} - dy="-2px"> + y={0}> {d} </text> ))} @@ -79,16 +79,16 @@ export default function ColorGradientLegend( <g transform={`translate(${widthNoPadding}, ${padding[0]})`}> <rect fill={colorNA} - x={NA_SPACING} - y={0} height={rectHeight} width={padding[1] - NA_SPACING} + x={NA_SPACING} + y={0} /> <text className="gradient-legend-na" + dy="-2px" x={NA_SPACING + (padding[1] - NA_SPACING) / 2} - y={0} - dy="-2px"> + y={0}> N/A </text> </g> diff --git a/server/sonar-web/src/main/js/components/charts/ColorRatingsLegend.js b/server/sonar-web/src/main/js/components/charts/ColorRatingsLegend.tsx index 3d8a07b375c..80e44f7c11e 100644 --- a/server/sonar-web/src/main/js/components/charts/ColorRatingsLegend.js +++ b/server/sonar-web/src/main/js/components/charts/ColorRatingsLegend.tsx @@ -17,13 +17,16 @@ * 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 { formatMeasure } from '../../helpers/measures'; import { RATING_COLORS } from '../../helpers/constants'; -export default function ColorRatingsLegend({ className } /*: { className?: string } */) { +interface Props { + className?: string; +} + +export default function ColorRatingsLegend({ className }: Props) { return ( <div className={classNames('color-box-legend', className)}> {[1, 2, 3, 4, 5].map(rating => ( diff --git a/server/sonar-web/src/main/js/components/charts/DonutChart.tsx b/server/sonar-web/src/main/js/components/charts/DonutChart.tsx new file mode 100644 index 00000000000..4cc5d0318f0 --- /dev/null +++ b/server/sonar-web/src/main/js/components/charts/DonutChart.tsx @@ -0,0 +1,83 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { arc as d3Arc, pie as d3Pie, PieArcDatum } from 'd3-shape'; + +interface DataPoint { + fill: string; + value: number; +} + +interface Props { + data: DataPoint[]; + height: number; + thickness: number; + padding?: [number, number, number, number]; + width: number; +} + +export default function DonutChart(props: Props) { + const { height, padding = [0, 0, 0, 0], width } = props; + + const availableWidth = width - padding[1] - padding[3]; + const availableHeight = height - padding[0] - padding[2]; + + const size = Math.min(availableWidth, availableHeight); + const radius = Math.floor(size / 2); + + const pie = d3Pie<any, DataPoint>() + .sort(null) + .value(d => d.value); + + const sectors = pie(props.data).map((d, i) => { + return ( + <Sector + data={d} + fill={props.data[i].fill} + key={i} + radius={radius} + thickness={props.thickness} + /> + ); + }); + + return ( + <svg className="donut-chart" height={height} width={width}> + <g transform={`translate(${padding[3]}, ${padding[0]})`}> + <g transform={`translate(${radius}, ${radius})`}>{sectors}</g> + </g> + </svg> + ); +} + +interface SectorProps { + data: PieArcDatum<DataPoint>; + fill: string; + radius: number; + thickness: number; +} + +function Sector(props: SectorProps) { + const arc = d3Arc<any, PieArcDatum<DataPoint>>() + .outerRadius(props.radius) + .innerRadius(props.radius - props.thickness); + const d = arc(props.data) as string; + return <path d={d} style={{ fill: props.fill }} />; +} diff --git a/server/sonar-web/src/main/js/components/charts/line-chart.js b/server/sonar-web/src/main/js/components/charts/LineChart.tsx index af99622368b..2d8d5f18058 100644 --- a/server/sonar-web/src/main/js/components/charts/line-chart.js +++ b/server/sonar-web/src/main/js/components/charts/LineChart.tsx @@ -17,160 +17,161 @@ * 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 createReactClass from 'create-react-class'; -import PropTypes from 'prop-types'; +import * as React from 'react'; import { extent, max } from 'd3-array'; -import { scaleLinear } from 'd3-scale'; +import { scaleLinear, ScaleLinear } from 'd3-scale'; import { area as d3Area, line as d3Line, curveBasis } from 'd3-shape'; -import { ResizeMixin } from './../mixins/resize-mixin'; - -export const LineChart = createReactClass({ - displayName: 'LineChart', - - propTypes: { - data: PropTypes.arrayOf(PropTypes.object).isRequired, - xTicks: PropTypes.arrayOf(PropTypes.any), - xValues: PropTypes.arrayOf(PropTypes.any), - padding: PropTypes.arrayOf(PropTypes.number), - backdropConstraints: PropTypes.arrayOf(PropTypes.number), - displayBackdrop: PropTypes.bool, - displayPoints: PropTypes.bool, - displayVerticalGrid: PropTypes.bool, - height: PropTypes.number - }, - - mixins: [ResizeMixin], - - getDefaultProps() { - return { - displayBackdrop: true, - displayPoints: true, - displayVerticalGrid: true, - xTicks: [], - xValues: [], - padding: [10, 10, 10, 10] - }; - }, - - getInitialState() { - return { width: this.props.width, height: this.props.height }; - }, - - renderBackdrop(xScale, yScale) { - if (!this.props.displayBackdrop) { +import { AutoSizer } from 'react-virtualized/dist/commonjs/AutoSizer'; + +interface DataPoint { + x: number; + y: number; +} + +interface Props { + backdropConstraints?: [number, number]; + data: DataPoint[]; + displayBackdrop?: boolean; + displayPoints?: boolean; + displayVerticalGrid?: boolean; + domain?: [number, number]; + height: number; + padding?: [number, number, number, number]; + width?: number; + xTicks?: {}[]; + xValues?: {}[]; +} + +export default class LineChart extends React.PureComponent<Props> { + renderBackdrop(xScale: ScaleLinear<number, number>, yScale: ScaleLinear<number, number>) { + const { displayBackdrop = true } = this.props; + + if (!displayBackdrop) { return null; } - const area = d3Area() + const area = d3Area<DataPoint>() .x(d => xScale(d.x)) .y0(yScale.range()[0]) .y1(d => yScale(d.y)) .defined(d => d.y != null) .curve(curveBasis); - let data = this.props.data; + let { data } = this.props; if (this.props.backdropConstraints) { const c = this.props.backdropConstraints; data = data.filter(d => c[0] <= d.x && d.x <= c[1]); } - return <path className="line-chart-backdrop" d={area(data)} />; - }, - renderPoints(xScale, yScale) { - if (!this.props.displayPoints) { + return <path className="line-chart-backdrop" d={area(data) as string} />; + } + + renderPoints(xScale: ScaleLinear<number, number>, yScale: ScaleLinear<number, number>) { + const { displayPoints = true } = this.props; + + if (!displayPoints) { return null; } + const points = this.props.data.filter(point => point.y != null).map((point, index) => { const x = xScale(point.x); const y = yScale(point.y); - return <circle key={index} className="line-chart-point" r="3" cx={x} cy={y} />; + return <circle className="line-chart-point" cx={x} cy={y} key={index} r="3" />; }); return <g>{points}</g>; - }, + } - renderVerticalGrid(xScale, yScale) { - if (!this.props.displayVerticalGrid) { + renderVerticalGrid(xScale: ScaleLinear<number, number>, yScale: ScaleLinear<number, number>) { + const { displayVerticalGrid = true } = this.props; + + if (!displayVerticalGrid) { return null; } + const lines = this.props.data.map((point, index) => { const x = xScale(point.x); const y1 = yScale.range()[0]; const y2 = yScale(point.y); - return <line key={index} className="line-chart-grid" x1={x} x2={x} y1={y1} y2={y2} />; + return <line className="line-chart-grid" key={index} x1={x} x2={x} y1={y1} y2={y2} />; }); return <g>{lines}</g>; - }, + } - renderXTicks(xScale, yScale) { - if (!this.props.xTicks.length) { + renderXTicks(xScale: ScaleLinear<number, number>, yScale: ScaleLinear<number, number>) { + const { xTicks = [] } = this.props; + + if (!xTicks.length) { return null; } - const ticks = this.props.xTicks.map((tick, index) => { + + const ticks = xTicks.map((tick, index) => { const point = this.props.data[index]; const x = xScale(point.x); const y = yScale.range()[0]; return ( - <text key={index} className="line-chart-tick" x={x} y={y} dy="1.5em"> + <text className="line-chart-tick" dy="1.5em" key={index} x={x} y={y}> {tick} </text> ); }); return <g>{ticks}</g>; - }, + } + + renderXValues(xScale: ScaleLinear<number, number>, yScale: ScaleLinear<number, number>) { + const { xValues = [] } = this.props; - renderXValues(xScale, yScale) { - if (!this.props.xValues.length) { + if (!xValues.length) { return null; } - const ticks = this.props.xValues.map((value, index) => { + + const ticks = xValues.map((value, index) => { const point = this.props.data[index]; const x = xScale(point.x); const y = yScale(point.y); return ( - <text key={index} className="line-chart-tick" x={x} y={y} dy="-1em"> + <text className="line-chart-tick" dy="-1em" key={index} x={x} y={y}> {value} </text> ); }); return <g>{ticks}</g>; - }, + } - renderLine(xScale, yScale) { - const p = d3Line() + renderLine(xScale: ScaleLinear<number, number>, yScale: ScaleLinear<number, number>) { + const p = d3Line<DataPoint>() .x(d => xScale(d.x)) .y(d => yScale(d.y)) .defined(d => d.y != null) .curve(curveBasis); - return <path className="line-chart-path" d={p(this.props.data)} />; - }, + return <path className="line-chart-path" d={p(this.props.data) as string} />; + } - render() { - if (!this.state.width || !this.state.height) { + renderChart = (width: number) => { + const { height, padding = [10, 10, 10, 10] } = this.props; + + if (!width || !height) { return <div />; } - const availableWidth = this.state.width - this.props.padding[1] - this.props.padding[3]; - const availableHeight = this.state.height - this.props.padding[0] - this.props.padding[2]; + const availableWidth = width - padding[1] - padding[3]; + const availableHeight = height - padding[0] - padding[2]; - let maxY; const xScale = scaleLinear() - .domain(extent(this.props.data, d => d.x)) + .domain(extent(this.props.data, d => d.x) as [number, number]) .range([0, availableWidth]); const yScale = scaleLinear().range([availableHeight, 0]); if (this.props.domain) { - maxY = this.props.domain[1]; yScale.domain(this.props.domain); } else { - maxY = max(this.props.data, d => d.y); + const maxY = max(this.props.data, d => d.y) as number; yScale.domain([0, maxY]); } return ( - <svg className="line-chart" width={this.state.width} height={this.state.height}> - <g transform={`translate(${this.props.padding[3]}, ${this.props.padding[0]})`}> - {this.renderVerticalGrid(xScale, yScale, maxY)} + <svg className="line-chart" height={height} width={width}> + <g transform={`translate(${padding[3]}, ${padding[0]})`}> + {this.renderVerticalGrid(xScale, yScale)} {this.renderBackdrop(xScale, yScale)} {this.renderLine(xScale, yScale)} {this.renderPoints(xScale, yScale)} @@ -179,5 +180,13 @@ export const LineChart = createReactClass({ </g> </svg> ); + }; + + render() { + return this.props.width !== undefined ? ( + this.renderChart(this.props.width) + ) : ( + <AutoSizer disableHeight={true}>{size => this.renderChart(size.width)}</AutoSizer> + ); } -}); +} diff --git a/server/sonar-web/src/main/js/components/charts/TreeMap.js b/server/sonar-web/src/main/js/components/charts/TreeMap.tsx index 2b526fd0fe2..9ee5d2320f7 100644 --- a/server/sonar-web/src/main/js/components/charts/TreeMap.js +++ b/server/sonar-web/src/main/js/components/charts/TreeMap.tsx @@ -17,33 +17,34 @@ * 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 { treemap as d3Treemap, hierarchy as d3Hierarchy } from 'd3-hierarchy'; import TreeMapRect from './TreeMapRect'; import { translate } from '../../helpers/l10n'; -/*:: export type TreeMapItem = { - key: string, - size: number, - color: string, - icon?: React.Element<*>, - tooltip?: string | React.Element<*>, - label: string, - link?: string -}; */ +interface TreeMapItem { + color: string; + icon?: React.ReactNode; + key: string; + label: string; + link?: string; + size: number; + tooltip?: React.ReactNode; +} -/*:: type Props = {| - items: Array<TreeMapItem>, - onRectangleClick?: string => void, - height: number, - width: number -|}; */ +interface HierarchicalTreemapItem extends TreeMapItem { + children?: TreeMapItem[]; +} -export default class TreeMap extends React.PureComponent { - /*:: props: Props; */ +interface Props { + height: number; + items: TreeMapItem[]; + onRectangleClick?: (item: string) => void; + width: number; +} - mostCommitPrefix = (labels /*: Array<string> */) => { +export default class TreeMap extends React.PureComponent<Props> { + mostCommitPrefix = (labels: string[]) => { const sortedLabels = labels.slice(0).sort(); const firstLabel = sortedLabels[0]; const firstLabelLength = firstLabel.length; @@ -76,11 +77,11 @@ export default class TreeMap extends React.PureComponent { return this.renderNoData(); } - const hierarchy = d3Hierarchy({ children: items }) + const hierarchy = d3Hierarchy({ children: items } as HierarchicalTreemapItem) .sum(d => d.size) - .sort((a, b) => b.value - a.value); + .sort((a, b) => (b.value || 0) - (a.value || 0)); - const treemap = d3Treemap() + const treemap = d3Treemap<TreeMapItem>() .round(true) .size([width, height]); @@ -92,20 +93,20 @@ export default class TreeMap extends React.PureComponent { <div className="treemap-container" style={{ width, height }}> {nodes.map(node => ( <TreeMapRect - key={node.data.key} - x={node.x0} - y={node.y0} - width={node.x1 - node.x0} - height={node.y1 - node.y0} fill={node.data.color} - label={node.data.label} - prefix={prefix} - itemKey={node.data.key} + height={node.y1 - node.y0} icon={node.data.icon} - tooltip={node.data.tooltip} + itemKey={node.data.key} + key={node.data.key} + label={node.data.label} link={node.data.link} onClick={this.props.onRectangleClick} placement={node.x0 === 0 || node.x1 < halfWidth ? 'right' : 'left'} + prefix={prefix} + tooltip={node.data.tooltip} + width={node.x1 - node.x0} + x={node.x0} + y={node.y0} /> ))} </div> diff --git a/server/sonar-web/src/main/js/components/charts/TreeMapRect.js b/server/sonar-web/src/main/js/components/charts/TreeMapRect.tsx index 9cc3c2e0738..75e8cdc8db5 100644 --- a/server/sonar-web/src/main/js/components/charts/TreeMapRect.js +++ b/server/sonar-web/src/main/js/components/charts/TreeMapRect.tsx @@ -17,42 +17,41 @@ * 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 { Link } from 'react-router'; -import classNames from 'classnames'; +import * as classNames from 'classnames'; import { scaleLinear } from 'd3-scale'; import LinkIcon from '../icons-components/LinkIcon'; -import Tooltip from '../controls/Tooltip'; +import Tooltip, { Placement } from '../controls/Tooltip'; const SIZE_SCALE = scaleLinear() .domain([3, 15]) .range([11, 18]) .clamp(true); -/*:: type Props = {| - x: number, - y: number, - width: number, - height: number, - fill: string, - label: string, - prefix: string, - icon?: React.Element<*>, - tooltip?: string | React.Element<*>, - itemKey: string, - link?: string, - onClick?: string => void, - placement?: string -|}; */ - -export default class TreeMapRect extends React.PureComponent { - /*:: props: Props; */ +interface Props { + fill: string; + height: number; + icon?: React.ReactNode; + itemKey: string; + label: string; + link?: string; + onClick?: (item: string) => void; + placement?: Placement; + prefix: string; + tooltip?: React.ReactNode; + width: number; + x: number; + y: number; +} - handleLinkClick = (e /*: Event */) => e.stopPropagation(); +export default class TreeMapRect extends React.PureComponent<Props> { + handleLinkClick = (event: React.MouseEvent<HTMLAnchorElement>) => { + event.stopPropagation(); + }; handleRectClick = () => { - if (this.props.onClick != null) { + if (this.props.onClick) { this.props.onClick(this.props.itemKey); } }; @@ -64,7 +63,7 @@ export default class TreeMapRect extends React.PureComponent { return null; } return ( - <Link className="treemap-link" to={link} onClick={this.handleLinkClick}> + <Link className="treemap-link" onClick={this.handleLinkClick} to={link}> <LinkIcon /> </Link> ); @@ -91,9 +90,9 @@ export default class TreeMapRect extends React.PureComponent { return ( <div className="treemap-cell" - style={cellStyles} onClick={this.handleRectClick} role="treeitem" + style={cellStyles} tabIndex={0}> <div className="treemap-inner" style={{ maxWidth: this.props.width }}> {isIconVisible && ( diff --git a/server/sonar-web/src/main/js/components/charts/__tests__/bar-chart-test.js b/server/sonar-web/src/main/js/components/charts/__tests__/BarChart-test.tsx index 933ac88ca14..0b109c12b0a 100644 --- a/server/sonar-web/src/main/js/components/charts/__tests__/bar-chart-test.js +++ b/server/sonar-web/src/main/js/components/charts/__tests__/BarChart-test.tsx @@ -17,13 +17,13 @@ * 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 { BarChart } from '../bar-chart'; +import BarChart from '../BarChart'; it('should display bars', () => { const data = [{ x: 1, y: 10 }, { x: 2, y: 30 }, { x: 3, y: 20 }]; - const chart = shallow(<BarChart data={data} width={100} height={100} barsWidth={20} />); + const chart = shallow(<BarChart barsWidth={20} data={data} height={100} width={100} />); expect(chart.find('.bar-chart-bar').length).toBe(3); }); @@ -31,7 +31,7 @@ it('should display ticks', () => { const data = [{ x: 1, y: 10 }, { x: 2, y: 30 }, { x: 3, y: 20 }]; const ticks = ['A', 'B', 'C']; const chart = shallow( - <BarChart data={data} xTicks={ticks} width={100} height={100} barsWidth={20} /> + <BarChart barsWidth={20} data={data} height={100} width={100} xTicks={ticks} /> ); expect(chart.find('.bar-chart-tick').length).toBe(3); }); @@ -40,7 +40,7 @@ it('should display values', () => { const data = [{ x: 1, y: 10 }, { x: 2, y: 30 }, { x: 3, y: 20 }]; const values = ['A', 'B', 'C']; const chart = shallow( - <BarChart data={data} xValues={values} width={100} height={100} barsWidth={20} /> + <BarChart barsWidth={20} data={data} height={100} width={100} xValues={values} /> ); expect(chart.find('.bar-chart-tick').length).toBe(3); }); @@ -50,7 +50,7 @@ it('should display bars, ticks and values', () => { const ticks = ['A', 'B', 'C']; const values = ['A', 'B', 'C']; const chart = shallow( - <BarChart data={data} xTicks={ticks} xValues={values} width={100} height={100} barsWidth={20} /> + <BarChart barsWidth={20} data={data} height={100} width={100} xTicks={ticks} xValues={values} /> ); expect(chart.find('.bar-chart-bar').length).toBe(3); expect(chart.find('.bar-chart-tick').length).toBe(6); diff --git a/server/sonar-web/src/main/js/components/charts/__tests__/BubbleChart-test.js b/server/sonar-web/src/main/js/components/charts/__tests__/BubbleChart-test.tsx index 347d1df976e..07c73da2b87 100644 --- a/server/sonar-web/src/main/js/components/charts/__tests__/BubbleChart-test.js +++ b/server/sonar-web/src/main/js/components/charts/__tests__/BubbleChart-test.tsx @@ -17,25 +17,25 @@ * 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 BubbleChart, { Bubble } from '../BubbleChart'; it('should display bubbles', () => { const items = [{ x: 1, y: 10, size: 7 }, { x: 2, y: 30, size: 5 }]; - const chart = mount(<BubbleChart items={items} height={100} />); + const chart = mount(<BubbleChart height={100} items={items} />); chart.find(Bubble).forEach(bubble => expect(bubble).toMatchSnapshot()); }); it('should render bubble links', () => { const items = [{ x: 1, y: 10, size: 7, link: 'foo' }, { x: 2, y: 30, size: 5, link: 'bar' }]; - const chart = mount(<BubbleChart items={items} height={100} />); + const chart = mount(<BubbleChart height={100} items={items} />); chart.find(Bubble).forEach(bubble => expect(bubble).toMatchSnapshot()); }); it('should render bubbles with click handlers', () => { const onClick = jest.fn(); const items = [{ x: 1, y: 10, size: 7, link: 'foo' }, { x: 2, y: 30, size: 5, link: 'bar' }]; - const chart = mount(<BubbleChart items={items} height={100} onBubbleClick={onClick} />); + const chart = mount(<BubbleChart height={100} items={items} onBubbleClick={onClick} />); chart.find(Bubble).forEach(bubble => expect(bubble).toMatchSnapshot()); }); diff --git a/server/sonar-web/src/main/js/components/charts/__tests__/line-chart-test.js b/server/sonar-web/src/main/js/components/charts/__tests__/LineChart-test.tsx index 9b2b0018c8a..533bd5aeddb 100644 --- a/server/sonar-web/src/main/js/components/charts/__tests__/line-chart-test.js +++ b/server/sonar-web/src/main/js/components/charts/__tests__/LineChart-test.tsx @@ -17,26 +17,26 @@ * 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 { LineChart } from '../line-chart'; +import LineChart from '../LineChart'; it('should display line', () => { const data = [{ x: 1, y: 10 }, { x: 2, y: 30 }, { x: 3, y: 20 }]; - const chart = shallow(<LineChart data={data} width={100} height={100} />); + const chart = shallow(<LineChart data={data} height={100} width={100} />); expect(chart.find('.line-chart-path').length).toBe(1); }); it('should display ticks', () => { const data = [{ x: 1, y: 10 }, { x: 2, y: 30 }, { x: 3, y: 20 }]; const ticks = ['A', 'B', 'C']; - const chart = shallow(<LineChart data={data} xTicks={ticks} width={100} height={100} />); + const chart = shallow(<LineChart data={data} height={100} width={100} xTicks={ticks} />); expect(chart.find('.line-chart-tick').length).toBe(3); }); it('should display values', () => { const data = [{ x: 1, y: 10 }, { x: 2, y: 30 }, { x: 3, y: 20 }]; const values = ['A', 'B', 'C']; - const chart = shallow(<LineChart data={data} xValues={values} width={100} height={100} />); + const chart = shallow(<LineChart data={data} height={100} width={100} xValues={values} />); expect(chart.find('.line-chart-tick').length).toBe(3); }); diff --git a/server/sonar-web/src/main/js/components/charts/__tests__/TreeMap-test.js b/server/sonar-web/src/main/js/components/charts/__tests__/TreeMap-test.tsx index 246f3a840b8..96fbe933b41 100644 --- a/server/sonar-web/src/main/js/components/charts/__tests__/TreeMap-test.js +++ b/server/sonar-web/src/main/js/components/charts/__tests__/TreeMap-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 TreeMap from '../TreeMap'; @@ -28,7 +28,7 @@ it('should display', () => { { key: '3', size: 20, color: '#777', label: 'SonarQube :: Search' } ]; const chart = shallow( - <TreeMap items={items} width={100} height={100} onRectangleClick={() => {}} /> + <TreeMap height={100} items={items} onRectangleClick={() => {}} width={100} /> ); expect(chart.find('TreeMapRect')).toHaveLength(3); }); diff --git a/server/sonar-web/src/main/js/components/charts/__tests__/__snapshots__/BubbleChart-test.js.snap b/server/sonar-web/src/main/js/components/charts/__tests__/__snapshots__/BubbleChart-test.tsx.snap index fbbd76666bd..fbbd76666bd 100644 --- a/server/sonar-web/src/main/js/components/charts/__tests__/__snapshots__/BubbleChart-test.js.snap +++ b/server/sonar-web/src/main/js/components/charts/__tests__/__snapshots__/BubbleChart-test.tsx.snap diff --git a/server/sonar-web/src/main/js/components/charts/bar-chart.js b/server/sonar-web/src/main/js/components/charts/bar-chart.js deleted file mode 100644 index c137b2f74e9..00000000000 --- a/server/sonar-web/src/main/js/components/charts/bar-chart.js +++ /dev/null @@ -1,187 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import React from 'react'; -import createReactClass from 'create-react-class'; -import PropTypes from 'prop-types'; -import { max } from 'd3-array'; -import { scaleLinear, scaleBand } from 'd3-scale'; -import Tooltip from '../controls/Tooltip'; -import { ResizeMixin } from '../mixins/resize-mixin'; - -export const BarChart = createReactClass({ - displayName: 'BarChart', - - propTypes: { - data: PropTypes.arrayOf(PropTypes.object).isRequired, - xTicks: PropTypes.arrayOf(PropTypes.any), - xValues: PropTypes.arrayOf(PropTypes.any), - height: PropTypes.number, - padding: PropTypes.arrayOf(PropTypes.number), - barsWidth: PropTypes.number.isRequired, - onBarClick: PropTypes.func - }, - - mixins: [ResizeMixin], - - getDefaultProps() { - return { - xTicks: [], - xValues: [], - padding: [10, 10, 10, 10] - }; - }, - - getInitialState() { - return { width: this.props.width, height: this.props.height }; - }, - - componentDidUpdate(prevProps) { - if (this.props.width && prevProps.width !== this.props.width) { - this.setState({ width: this.props.width }); - } - if (this.props.height && prevProps.height !== this.props.height) { - this.setState({ height: this.props.height }); - } - }, - - handleClick(point) { - this.props.onBarClick(point); - }, - - renderXTicks(xScale, yScale) { - if (!this.props.xTicks.length) { - return null; - } - const ticks = this.props.xTicks.map((tick, index) => { - const point = this.props.data[index]; - const x = Math.round(xScale(point.x) + xScale.bandwidth() / 2); - const y = yScale.range()[0]; - const d = this.props.data[index]; - const text = ( - <text - className="bar-chart-tick" - dy="1.5em" - key={index} - onClick={this.props.onBarClick && this.handleClick.bind(this, point)} - style={{ cursor: this.props.onBarClick ? 'pointer' : 'default' }} - x={x} - y={y}> - {tick} - </text> - ); - return ( - <Tooltip key={index} overlay={d.tooltip || undefined}> - {text} - </Tooltip> - ); - }); - return <g>{ticks}</g>; - }, - - renderXValues(xScale, yScale) { - if (!this.props.xValues.length) { - return null; - } - const ticks = this.props.xValues.map((value, index) => { - const point = this.props.data[index]; - const x = Math.round(xScale(point.x) + xScale.bandwidth() / 2); - const y = yScale(point.y); - const d = this.props.data[index]; - const text = ( - <text - className="bar-chart-tick" - dy="-1em" - key={index} - onClick={this.props.onBarClick && this.handleClick.bind(this, point)} - style={{ cursor: this.props.onBarClick ? 'pointer' : 'default' }} - x={x} - y={y}> - {value} - </text> - ); - return ( - <Tooltip key={index} overlay={d.tooltip || undefined}> - {text} - </Tooltip> - ); - }); - return <g>{ticks}</g>; - }, - - renderBars(xScale, yScale) { - const bars = this.props.data.map((d, index) => { - const x = Math.round(xScale(d.x)); - const maxY = yScale.range()[0]; - const y = Math.round(yScale(d.y)) - /* minimum bar height */ 1; - const height = maxY - y; - const rect = ( - <rect - className="bar-chart-bar" - height={height} - key={index} - onClick={this.props.onBarClick && this.handleClick.bind(this, d)} - style={{ cursor: this.props.onBarClick ? 'pointer' : 'default' }} - width={this.props.barsWidth} - x={x} - y={y} - /> - ); - return ( - <Tooltip key={index} overlay={d.tooltip || undefined}> - {rect} - </Tooltip> - ); - }); - return <g>{bars}</g>; - }, - - render() { - if (!this.state.width || !this.state.height) { - return <div />; - } - - const availableWidth = this.state.width - this.props.padding[1] - this.props.padding[3]; - const availableHeight = this.state.height - this.props.padding[0] - this.props.padding[2]; - - const innerPadding = - (availableWidth - this.props.barsWidth * this.props.data.length) / - (this.props.data.length - 1); - const relativeInnerPadding = innerPadding / (innerPadding + this.props.barsWidth); - - const maxY = max(this.props.data, d => d.y); - const xScale = scaleBand() - .domain(this.props.data.map(d => d.x)) - .range([0, availableWidth]) - .paddingInner(relativeInnerPadding); - const yScale = scaleLinear() - .domain([0, maxY]) - .range([availableHeight, 0]); - - return ( - <svg className="bar-chart" height={this.state.height} width={this.state.width}> - <g transform={`translate(${this.props.padding[3]}, ${this.props.padding[0]})`}> - {this.renderXTicks(xScale, yScale)} - {this.renderXValues(xScale, yScale)} - {this.renderBars(xScale, yScale)} - </g> - </svg> - ); - } -}); diff --git a/server/sonar-web/src/main/js/components/charts/donut-chart.js b/server/sonar-web/src/main/js/components/charts/donut-chart.js deleted file mode 100644 index 08fd9b0bb24..00000000000 --- a/server/sonar-web/src/main/js/components/charts/donut-chart.js +++ /dev/null @@ -1,84 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import React from 'react'; -import createReactClass from 'create-react-class'; -import PropTypes from 'prop-types'; -import { arc as d3Arc, pie as d3Pie } from 'd3-shape'; -import { ResizeMixin } from './../mixins/resize-mixin'; - -function Sector(props) { - const arc = d3Arc() - .outerRadius(props.radius) - .innerRadius(props.radius - props.thickness); - return <path d={arc(props.data)} style={{ fill: props.fill }} />; -} - -export const DonutChart = createReactClass({ - displayName: 'DonutChart', - - propTypes: { - data: PropTypes.arrayOf(PropTypes.object).isRequired - }, - - mixins: [ResizeMixin], - - getDefaultProps() { - return { thickness: 6, padding: [0, 0, 0, 0] }; - }, - - getInitialState() { - return { width: this.props.width, height: this.props.height }; - }, - - render() { - if (!this.state.width || !this.state.height) { - return <div />; - } - - const availableWidth = this.state.width - this.props.padding[1] - this.props.padding[3]; - const availableHeight = this.state.height - this.props.padding[0] - this.props.padding[2]; - - const size = Math.min(availableWidth, availableHeight); - const radius = Math.floor(size / 2); - - const pie = d3Pie() - .sort(null) - .value(d => d.value); - const sectors = pie(this.props.data).map((d, i) => { - return ( - <Sector - data={d} - fill={this.props.data[i].fill} - key={i} - radius={radius} - thickness={this.props.thickness} - /> - ); - }); - - return ( - <svg className="donut-chart" height={this.state.height} width={this.state.width}> - <g transform={`translate(${this.props.padding[3]}, ${this.props.padding[0]})`}> - <g transform={`translate(${radius}, ${radius})`}>{sectors}</g> - </g> - </svg> - ); - } -}); diff --git a/server/sonar-web/src/main/js/components/common/LocationIndex.js b/server/sonar-web/src/main/js/components/common/LocationIndex.tsx index f3ae1bf7e88..15b143e7876 100644 --- a/server/sonar-web/src/main/js/components/common/LocationIndex.js +++ b/server/sonar-web/src/main/js/components/common/LocationIndex.tsx @@ -17,35 +17,31 @@ * 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 './LocationIndex.css'; -/*:: -type Props = { - children?: React.Element<*>, - leading?: boolean, - onClick?: () => void, - selected?: boolean -}; -*/ +interface Props { + children?: React.ReactNode; + leading?: boolean; + onClick?: () => void; + selected?: boolean; + [x: string]: any; +} -export default function LocationIndex(props /*: Props */) { +export default function LocationIndex(props: Props) { const { children, leading, onClick, selected, ...other } = props; const clickAttributes = onClick ? { onClick, role: 'button', tabIndex: 0 } : {}; - // put {...others} because Tooltip sets some event handlers return ( <div - className={classNames('location-index', { 'is-leading': leading, selected })} + className={classNames('location-index', { + 'is-leading': leading, + selected + })} {...clickAttributes} {...other}> {children} </div> ); } - -LocationIndex.defaultProps = { - selected: false -}; diff --git a/server/sonar-web/src/main/js/components/common/LocationMessage.js b/server/sonar-web/src/main/js/components/common/LocationMessage.tsx index 491ac9e5460..bcee4be2683 100644 --- a/server/sonar-web/src/main/js/components/common/LocationMessage.js +++ b/server/sonar-web/src/main/js/components/common/LocationMessage.tsx @@ -17,19 +17,16 @@ * 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 './LocationMessage.css'; -/*:: -type Props = { - children?: React.Element<*>, - selected: boolean -}; -*/ +interface Props { + children?: React.ReactNode; + selected: boolean; +} -export default function LocationMessage(props /*: Props */) { +export default function LocationMessage(props: Props) { return ( <div className={classNames('location-message', { selected: props.selected })}> {props.children} diff --git a/server/sonar-web/src/main/js/components/common/OrganizationHelmet.js b/server/sonar-web/src/main/js/components/common/OrganizationHelmet.tsx index d4b52885ac0..e842eaae6c7 100644 --- a/server/sonar-web/src/main/js/components/common/OrganizationHelmet.js +++ b/server/sonar-web/src/main/js/components/common/OrganizationHelmet.tsx @@ -17,18 +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 -import React from 'react'; +import * as React from 'react'; import Helmet from 'react-helmet'; -/*:: -type Props = { - title: string, - organization?: ?{ name: string } -}; -*/ +interface Props { + organization?: { name: string }; + title: string; +} -export default function OrganizationHelmet({ title, organization } /*: Props */) { +export default function OrganizationHelmet({ title, organization }: Props) { const defaultTitle = title + (organization ? ' - ' + organization.name : ''); return <Helmet defaultTitle={defaultTitle} titleTemplate={'%s - ' + defaultTitle} />; } diff --git a/server/sonar-web/src/main/js/components/common/UpgradeOrganizationBox.js b/server/sonar-web/src/main/js/components/common/UpgradeOrganizationBox.tsx index b6934cea2c6..baeb9ad0dba 100644 --- a/server/sonar-web/src/main/js/components/common/UpgradeOrganizationBox.js +++ b/server/sonar-web/src/main/js/components/common/UpgradeOrganizationBox.tsx @@ -17,19 +17,16 @@ * 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 { Link } from 'react-router'; import { translate, hasMessage } from '../../helpers/l10n'; import './UpgradeOrganizationBox.css'; -/*:: -type Props = { - organization: string -}; -*/ +interface Props { + organization: string; +} -export default function UpgradeOrganizationBox(props /*: Props */) { +export default function UpgradeOrganizationBox({ organization }: Props) { return ( <div className="boxed-group boxed-group-inner upgrade-organization-box"> <h3 className="spacer-bottom">{translate('billing.upgrade_box.header')}</h3> @@ -41,7 +38,7 @@ export default function UpgradeOrganizationBox(props /*: Props */) { <Link className="button" to={{ - pathname: `organizations/${props.organization}/extension/billing/billing`, + pathname: `organizations/${organization}/extension/billing/billing`, query: { page: 'upgrade' } }}> {translate('billing.upgrade_box.button')} diff --git a/server/sonar-web/src/main/js/components/controls/GlobalMessages.js b/server/sonar-web/src/main/js/components/controls/GlobalMessages.tsx index 0d0782009b7..95ee65b06e2 100644 --- a/server/sonar-web/src/main/js/components/controls/GlobalMessages.js +++ b/server/sonar-web/src/main/js/components/controls/GlobalMessages.tsx @@ -17,28 +17,26 @@ * 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 PropTypes from 'prop-types'; -import classNames from 'classnames'; -import { ERROR, SUCCESS } from '../../store/globalMessages/duck'; +import * as React from 'react'; +import * as classNames from 'classnames'; import { Button } from '../ui/buttons'; -export default class GlobalMessages extends React.PureComponent { - static propTypes = { - messages: PropTypes.arrayOf( - PropTypes.shape({ - id: PropTypes.string.isRequired, - message: PropTypes.string.isRequired, - level: PropTypes.oneOf([ERROR, SUCCESS]) - }) - ), - closeGlobalMessage: PropTypes.func.isRequired - }; +interface Message { + id: string; + level: 'ERROR' | 'SUCCESS'; + message: string; +} + +interface Props { + closeGlobalMessage: (id: string) => void; + messages: Message[]; +} - renderMessage = message => { +export default class GlobalMessages extends React.PureComponent<Props> { + renderMessage = (message: Message) => { const className = classNames('process-spinner', 'shown', { - 'process-spinner-failed': message.level === ERROR, - 'process-spinner-success': message.level === SUCCESS + 'process-spinner-failed': message.level === 'ERROR', + 'process-spinner-success': message.level === 'SUCCESS' }); return ( <div className={className} key={message.id}> diff --git a/server/sonar-web/src/main/js/components/controls/Toggle.js b/server/sonar-web/src/main/js/components/controls/Toggle.tsx index 4cbabfece09..b35d121f9e8 100644 --- a/server/sonar-web/src/main/js/components/controls/Toggle.js +++ b/server/sonar-web/src/main/js/components/controls/Toggle.tsx @@ -17,36 +17,36 @@ * 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 PropTypes from 'prop-types'; -import classNames from 'classnames'; +import * as React from 'react'; +import * as classNames from 'classnames'; import { Button } from '../ui/buttons'; import './styles.css'; -export default class Toggle extends React.PureComponent { - static propTypes = { - value: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]).isRequired, - name: PropTypes.string, - onChange: PropTypes.func +export interface Props { + name?: string; + onChange?: (value: boolean) => void; + value: boolean | string; +} + +export default class Toggle extends React.PureComponent<Props> { + getValue = () => { + const { value } = this.props; + return typeof value === 'string' ? value === 'true' : value; }; - handleClick = value => { + handleClick = () => { if (this.props.onChange) { + const value = this.getValue(); this.props.onChange(!value); } }; render() { - const { value } = this.props; - const booleanValue = typeof value === 'string' ? value === 'true' : value; - - const className = classNames('boolean-toggle', { 'boolean-toggle-on': booleanValue }); + const value = this.getValue(); + const className = classNames('boolean-toggle', { 'boolean-toggle-on': value }); return ( - <Button - className={className} - name={this.props.name} - onClick={() => this.handleClick(booleanValue)}> + <Button className={className} name={this.props.name} onClick={this.handleClick}> <div className="boolean-toggle-handle" /> </Button> ); diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/ListFooter-test.js b/server/sonar-web/src/main/js/components/controls/__tests__/ListFooter-test.tsx index 5c2e7458776..b8de25e41f1 100644 --- a/server/sonar-web/src/main/js/components/controls/__tests__/ListFooter-test.js +++ b/server/sonar-web/src/main/js/components/controls/__tests__/ListFooter-test.tsx @@ -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. */ +import * as React from 'react'; import { shallow } from 'enzyme'; -import React from 'react'; import ListFooter from '../ListFooter'; import { click } from '../../../helpers/testUtils'; @@ -33,13 +33,13 @@ it('should not render "show more"', () => { }); it('should not render "show more"', () => { - const listFooter = shallow(<ListFooter count={5} total={5} loadMore={jest.fn()} />); + const listFooter = shallow(<ListFooter count={5} loadMore={jest.fn()} total={5} />); expect(listFooter.find('a').length).toBe(0); }); it('should "show more"', () => { const loadMore = jest.fn(); - const listFooter = shallow(<ListFooter count={3} total={5} loadMore={loadMore} />); + const listFooter = shallow(<ListFooter count={3} loadMore={loadMore} total={5} />); const link = listFooter.find('a'); expect(link.length).toBe(1); click(link); diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/Toggle-test.js b/server/sonar-web/src/main/js/components/controls/__tests__/Toggle-test.tsx index 3245074388a..7d3e8ae2d0d 100644 --- a/server/sonar-web/src/main/js/components/controls/__tests__/Toggle-test.js +++ b/server/sonar-web/src/main/js/components/controls/__tests__/Toggle-test.tsx @@ -17,23 +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. */ +import * as React from 'react'; import { shallow } from 'enzyme'; -import React from 'react'; -import Toggle from '../Toggle'; +import Toggle, { Props } from '../Toggle'; import { click } from '../../../helpers/testUtils'; -function getSample(props) { - return <Toggle onChange={() => true} value={true} {...props} />; -} - it('should render', () => { - const Toggle = shallow(getSample()); + const Toggle = shallowRender(); expect(Toggle.is('Button')).toBe(true); }); it('should call onChange', () => { const onChange = jest.fn(); - const Toggle = shallow(getSample({ onChange })); + const Toggle = shallowRender({ onChange }); click(Toggle); expect(onChange).toBeCalledWith(false); }); + +function shallowRender(props?: Partial<Props>) { + return shallow(<Toggle onChange={() => true} value={true} {...props} />); +} diff --git a/server/sonar-web/src/main/js/components/ui/BugTrackerIcon.js b/server/sonar-web/src/main/js/components/icons-components/BugTrackerIcon.tsx index 45b233ba806..f42e84aaa6e 100644 --- a/server/sonar-web/src/main/js/components/ui/BugTrackerIcon.js +++ b/server/sonar-web/src/main/js/components/icons-components/BugTrackerIcon.tsx @@ -17,25 +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 * as React from 'react'; +import { IconProps } from './types'; -export default function BugTrackerIcon() { - /* eslint-disable max-len */ +export default function BugTrackerIcon({ className, fill = 'currentColor', size = 16 }: IconProps) { return ( - <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 14" width="14" height="14"> - <g transform="matrix(1,0,0,1,0,1)"> - <path - style={{ - fill: 'none', - stroke: 'currentColor', - strokeWidth: 2, - strokeLinecap: 'round', - strokeMiterlimit: '10' - }} - d="M12 9h-2L8 5 6.5 9.5l-2-6L3 9H1" - /> - </g> + <svg + className={className} + height={size} + version="1.1" + viewBox="0 0 16 16" + width={size} + xmlSpace="preserve" + xmlnsXlink="http://www.w3.org/1999/xlink"> + <path + d="M13.5 9.5c1.003.033 1.466 1.952 0 2h-2.618L9.685 9.107 8 14.162 6.096 8.45l-.832 3.05-2.829-.002c-.984-.097-1.369-1.951.065-1.998h1.236l2.168-7.95L8 7.838l1.315-3.945L12.118 9.5H13.5z" + style={{ fill }} + /> </svg> ); } diff --git a/server/sonar-web/src/main/js/components/common/ClockIcon.js b/server/sonar-web/src/main/js/components/icons-components/ClockIcon.tsx index e0e696128aa..d5da0ec03e9 100644 --- a/server/sonar-web/src/main/js/components/common/ClockIcon.js +++ b/server/sonar-web/src/main/js/components/icons-components/ClockIcon.tsx @@ -17,33 +17,24 @@ * 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 { IconProps } from './types'; -/*:: -type Props = { - className?: string, - size?: number -}; -*/ - -export default function ClockIcon(props /*: Props */) { - /* eslint max-len: 0 */ +export default function ClockIcon({ className, size = 16 }: IconProps) { return ( <svg - className={classNames('icon-clock', props.className)} + className={classNames('icon-clock', className)} + height={size} + version="1.1" viewBox="0 0 16 16" - width={props.size} - height={props.size}> + width={size} + xmlSpace="preserve" + xmlnsXlink="http://www.w3.org/1999/xlink"> <g fill="#fff" stroke="#ADADAD" transform="matrix(1.4 0 0 1.4 .3 .7)"> <circle cx="5.5" cy="5.2" r="5" /> - <path fillRule="nonzero" d="M5.6 2.9v2.7l2-.5" /> + <path d="M5.6 2.9v2.7l2-.5" fillRule="nonzero" /> </g> </svg> ); } - -ClockIcon.defaultProps = { - size: 16 -}; diff --git a/server/sonar-web/src/main/js/components/icons-components/PinIcon.tsx b/server/sonar-web/src/main/js/components/icons-components/PinIcon.tsx new file mode 100644 index 00000000000..3e83487feeb --- /dev/null +++ b/server/sonar-web/src/main/js/components/icons-components/PinIcon.tsx @@ -0,0 +1,39 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { IconProps } from './types'; + +export default function PinIcon({ className, fill = 'currentColor', size = 16 }: IconProps) { + return ( + <svg + className={className} + height={size} + version="1.1" + viewBox="0 0 16 16" + width={size} + xmlSpace="preserve" + xmlnsXlink="http://www.w3.org/1999/xlink"> + <path + d="M7.25 7.25v-3.5a.243.243 0 0 0-.07-.18A.243.243 0 0 0 7 3.5a.243.243 0 0 0-.18.07.243.243 0 0 0-.07.18v3.5c0 .073.023.133.07.18.047.047.107.07.18.07a.243.243 0 0 0 .18-.07.243.243 0 0 0 .07-.18zM12.5 10a.482.482 0 0 1-.148.352.482.482 0 0 1-.352.148H8.648l-.398 3.773a.29.29 0 0 1-.082.161.219.219 0 0 1-.16.066H8c-.141 0-.224-.07-.25-.211L7.156 10.5H4a.482.482 0 0 1-.352-.148A.482.482 0 0 1 3.5 10c0-.641.204-1.217.613-1.73.409-.513.871-.77 1.387-.77v-4a.96.96 0 0 1-.703-.297A.96.96 0 0 1 4.5 2.5a.96.96 0 0 1 .297-.703A.96.96 0 0 1 5.5 1.5h5a.96.96 0 0 1 .703.297.96.96 0 0 1 .297.703.96.96 0 0 1-.297.703.96.96 0 0 1-.703.297v4c.516 0 .978.257 1.387.77.409.513.613 1.089.613 1.73z" + style={{ fill }} + />; + </svg> + ); +} diff --git a/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueTitleBar-test.js.snap b/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueTitleBar-test.js.snap index 8f3694dd8d0..93d4a3f72d2 100644 --- a/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueTitleBar-test.js.snap +++ b/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueTitleBar-test.js.snap @@ -1,9 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`should count all code locations 1`] = ` -<LocationIndex - selected={false} -> +<LocationIndex> 7 </LocationIndex> `; diff --git a/server/sonar-web/src/main/js/components/shared/SeverityIcon.js b/server/sonar-web/src/main/js/components/shared/SeverityIcon.tsx index 4a7575edab2..38ee50884a4 100644 --- a/server/sonar-web/src/main/js/components/shared/SeverityIcon.js +++ b/server/sonar-web/src/main/js/components/shared/SeverityIcon.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. */ -//@flow -import React from 'react'; -import classNames from 'classnames'; +import * as React from 'react'; +import * as classNames from 'classnames'; -export default function SeverityIcon(props /*: { severity: ?string, className?: string } */) { +interface Props { + className?: string; + severity: string | null | undefined; +} + +export default function SeverityIcon(props: Props) { if (!props.severity) { return null; } - const className = classNames('icon-severity-' + props.severity.toLowerCase(), props.className); - return <i className={className} />; + return ( + <i className={classNames('icon-severity-' + props.severity.toLowerCase(), props.className)} /> + ); } diff --git a/server/sonar-web/src/main/js/components/shared/StatusHelper.js b/server/sonar-web/src/main/js/components/shared/StatusHelper.tsx index 2c2532605ad..b6921effdac 100644 --- a/server/sonar-web/src/main/js/components/shared/StatusHelper.js +++ b/server/sonar-web/src/main/js/components/shared/StatusHelper.tsx @@ -17,20 +17,18 @@ * 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 StatusIcon from './StatusIcon'; import { translate } from '../../helpers/l10n'; -export default function StatusHelper( - props /*: { - resolution?: string, - status: string, - className?: string -} */ -) { - const resolution = - props.resolution != null && ` (${translate('issue.resolution', props.resolution)})`; +interface Props { + className?: string; + resolution: string | undefined; + status: string; +} + +export default function StatusHelper(props: Props) { + const resolution = props.resolution && ` (${translate('issue.resolution', props.resolution)})`; return ( <span className={props.className}> <StatusIcon className="little-spacer-right" status={props.status} /> diff --git a/server/sonar-web/src/main/js/components/shared/StatusIcon.js b/server/sonar-web/src/main/js/components/shared/StatusIcon.tsx index ed8c418ee31..d043b034406 100644 --- a/server/sonar-web/src/main/js/components/shared/StatusIcon.js +++ b/server/sonar-web/src/main/js/components/shared/StatusIcon.tsx @@ -17,11 +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 React from 'react'; -import classNames from 'classnames'; +import * as React from 'react'; +import * as classNames from 'classnames'; -export default function StatusIcon(props /*: { status: string, className?: string } */) { - const className = classNames('icon-status-' + props.status.toLowerCase(), props.className); - return <i className={className} />; +interface Props { + className?: string; + status: string; +} + +export default function StatusIcon({ className, status }: Props) { + return <i className={classNames('icon-status-' + status.toLowerCase(), className)} />; } diff --git a/server/sonar-web/src/main/js/components/shared/WithStore.js b/server/sonar-web/src/main/js/components/shared/WithStore.js deleted file mode 100644 index 2cdf9bb3b20..00000000000 --- a/server/sonar-web/src/main/js/components/shared/WithStore.js +++ /dev/null @@ -1,52 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -// @flow -import React from 'react'; -import PropTypes from 'prop-types'; -import getStore from '../../app/utils/getStore'; - -/*:: -type Props = { - children: React.Element<*> -}; -*/ - -export default class WithStore extends React.PureComponent { - /*:: props: Props; */ - /*:: store: {}; -*/ - - static childContextTypes = { - store: PropTypes.object - }; - - constructor(props /*: Props */) { - super(props); - this.store = getStore(); - } - - getChildContext() { - return { store: this.store }; - } - - render() { - return this.props.children; - } -} diff --git a/server/sonar-web/src/main/js/components/shared/__tests__/QualifierIcon-test.js b/server/sonar-web/src/main/js/components/shared/__tests__/QualifierIcon-test.tsx index 32097c900fd..06169277552 100644 --- a/server/sonar-web/src/main/js/components/shared/__tests__/QualifierIcon-test.js +++ b/server/sonar-web/src/main/js/components/shared/__tests__/QualifierIcon-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 QualifierIcon from '../QualifierIcon'; diff --git a/server/sonar-web/src/main/js/components/shared/__tests__/__snapshots__/QualifierIcon-test.js.snap b/server/sonar-web/src/main/js/components/shared/__tests__/__snapshots__/QualifierIcon-test.tsx.snap index 3e1625b3e32..3e1625b3e32 100644 --- a/server/sonar-web/src/main/js/components/shared/__tests__/__snapshots__/QualifierIcon-test.js.snap +++ b/server/sonar-web/src/main/js/components/shared/__tests__/__snapshots__/QualifierIcon-test.tsx.snap diff --git a/server/sonar-web/src/main/js/components/shared/pin-icon.js b/server/sonar-web/src/main/js/components/shared/pin-icon.js deleted file mode 100644 index 9435d00ee8c..00000000000 --- a/server/sonar-web/src/main/js/components/shared/pin-icon.js +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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 max-len: 0 */ -import React from 'react'; -import * as theme from '../../app/theme'; - -export default function PinIcon() { - return ( - <svg width="9" height="14" viewBox="0 0 288 448"> - <path - fill={theme.darkBlue} - d="M120 216v-112q0-3.5-2.25-5.75t-5.75-2.25-5.75 2.25-2.25 5.75v112q0 3.5 2.25 5.75t5.75 2.25 5.75-2.25 2.25-5.75zM288 304q0 6.5-4.75 11.25t-11.25 4.75h-107.25l-12.75 120.75q-0.5 3-2.625 5.125t-5.125 2.125h-0.25q-6.75 0-8-6.75l-19-121.25h-101q-6.5 0-11.25-4.75t-4.75-11.25q0-30.75 19.625-55.375t44.375-24.625v-128q-13 0-22.5-9.5t-9.5-22.5 9.5-22.5 22.5-9.5h160q13 0 22.5 9.5t9.5 22.5-9.5 22.5-22.5 9.5v128q24.75 0 44.375 24.625t19.625 55.375z" - /> - </svg> - ); -} diff --git a/server/sonar-web/src/main/js/components/ui/CoverageRating.tsx b/server/sonar-web/src/main/js/components/ui/CoverageRating.tsx index 7439b850ff3..10ffc9e849b 100644 --- a/server/sonar-web/src/main/js/components/ui/CoverageRating.tsx +++ b/server/sonar-web/src/main/js/components/ui/CoverageRating.tsx @@ -18,7 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import { DonutChart } from '../charts/donut-chart'; +import DonutChart from '../charts/DonutChart'; import * as theme from '../../app/theme'; const SIZE_TO_WIDTH_MAPPING = { small: 16, normal: 24, big: 40, huge: 60 }; @@ -45,5 +45,5 @@ export default function CoverageRating({ muted = false, size = 'normal', value } const width = SIZE_TO_WIDTH_MAPPING[size]; const thickness = SIZE_TO_THICKNESS_MAPPING[size]; - return <DonutChart data={data} width={width} height={width} thickness={thickness} />; + return <DonutChart data={data} height={width} thickness={thickness} width={width} />; } diff --git a/server/sonar-web/src/main/js/components/ui/__tests__/Level-test.js b/server/sonar-web/src/main/js/components/ui/__tests__/Level-test.tsx index 3a315a07599..7706ee2414b 100644 --- a/server/sonar-web/src/main/js/components/ui/__tests__/Level-test.js +++ b/server/sonar-web/src/main/js/components/ui/__tests__/Level-test.tsx @@ -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. */ +import * as React from 'react'; import { shallow } from 'enzyme'; -import React from 'react'; import Level from '../Level'; it('should render', () => { diff --git a/server/sonar-web/src/main/js/components/ui/buttons.tsx b/server/sonar-web/src/main/js/components/ui/buttons.tsx index 82992ba0a64..1ede3bb8ce1 100644 --- a/server/sonar-web/src/main/js/components/ui/buttons.tsx +++ b/server/sonar-web/src/main/js/components/ui/buttons.tsx @@ -32,6 +32,7 @@ interface ButtonProps { disabled?: boolean; id?: string; innerRef?: (node: HTMLElement | null) => void; + name?: string; onClick?: (event: React.MouseEvent<HTMLElement>) => void; preventDefault?: boolean; stopPropagation?: boolean; |