aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main/js/components
diff options
context:
space:
mode:
Diffstat (limited to 'server/sonar-web/src/main/js/components')
-rw-r--r--server/sonar-web/src/main/js/components/charts/BarChart.tsx169
-rw-r--r--server/sonar-web/src/main/js/components/charts/BubbleChart.tsx41
-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.tsx83
-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.js187
-rw-r--r--server/sonar-web/src/main/js/components/charts/donut-chart.js84
-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.tsx39
-rw-r--r--server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueTitleBar-test.js.snap4
-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.js52
-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.js33
-rw-r--r--server/sonar-web/src/main/js/components/ui/CoverageRating.tsx4
-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.tsx1
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;
> Nextcloud server, a safe home for all your data: https://github.com/nextcloud/serverwww-data
summaryrefslogtreecommitdiffstats
path: root/l10n/es/contacts.po
blob: d3ffeee5d35dbf9dd1b09a91ff4476b4fad0d9b1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# 
# Translators:
#   <davidlopez.david@gmail.com>, 2012.
# Javier Llorente <javier@opensuse.org>, 2012.
#   <juanma@kde.org.ar>, 2011, 2012.
# oSiNaReF  <>, 2012.
#   <rodrigo.calvo@gmail.com>, 2012.
#   <sergioballesterossolanas@gmail.com>, 2011, 2012.
msgid ""
msgstr ""
"Project-Id-Version: ownCloud\n"
"Report-Msgid-Bugs-To: http://bugs.owncloud.org/\n"
"POT-Creation-Date: 2012-08-11 02:02+0200\n"
"PO-Revision-Date: 2012-08-11 00:03+0000\n"
"Last-Translator: owncloud_robot <thomas.mueller@tmit.eu>\n"
"Language-Team: Spanish (http://www.transifex.com/projects/p/owncloud/language/es/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: es\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"

#: ajax/addressbook/activate.php:24 ajax/addressbook/update.php:32
msgid "Error (de)activating addressbook."
msgstr "Error al (des)activar libreta de direcciones."

#: ajax/addressbook/delete.php:31 ajax/addressbook/update.php:20
#: ajax/contact/addproperty.php:42 ajax/contact/delete.php:31
#: ajax/contact/saveproperty.php:39
msgid "id is not set."
msgstr "no se ha puesto ninguna ID."

#: ajax/addressbook/update.php:24
msgid "Cannot update addressbook with an empty name."
msgstr "No se puede actualizar una libreta de direcciones sin nombre."

#: ajax/addressbook/update.php:28
msgid "Error updating addressbook."
msgstr "Error al actualizar la libreta de direcciones."

#: ajax/categories/categoriesfor.php:17
msgid "No ID provided"
msgstr "No se ha proporcionado una ID"

#: ajax/categories/categoriesfor.php:34
msgid "Error setting checksum."
msgstr "Error al establecer la suma de verificación."

#: ajax/categories/delete.php:19
msgid "No categories selected for deletion."
msgstr "No se seleccionaron categorías para borrar."

#: ajax/categories/delete.php:26
msgid "No address books found."
msgstr "No se encontraron libretas de direcciones."

#: ajax/categories/delete.php:34
msgid "No contacts found."
msgstr "No se encontraron contactos."

#: ajax/contact/add.php:47
msgid "There was an error adding the contact."
msgstr "Se ha producido un error al añadir el contacto."

#: ajax/contact/addproperty.php:39 ajax/contact/saveproperty.php:36
msgid "element name is not set."
msgstr "no se ha puesto ningún nombre de elemento."

#: ajax/contact/addproperty.php:46
msgid "Could not parse contact: "
msgstr ""

#: ajax/contact/addproperty.php:56
msgid "Cannot add empty property."
msgstr "No se puede añadir una propiedad vacía."

#: ajax/contact/addproperty.php:67
msgid "At least one of the address fields has to be filled out."
msgstr "Al menos uno de los campos de direcciones se tiene que rellenar."

#: ajax/contact/addproperty.php:76
msgid "Trying to add duplicate property: "
msgstr "Intentando añadir una propiedad duplicada: "

#: ajax/contact/addproperty.php:144
msgid "Error adding contact property: "
msgstr ""

#: ajax/contact/deleteproperty.php:37
msgid "Information about vCard is incorrect. Please reload the page."
msgstr "La información sobre el vCard es incorrecta. Por favor vuelve a cargar la página."

#: ajax/contact/deleteproperty.php:44
msgid "Error deleting contact property."
msgstr "Error al borrar una propiedad del contacto."

#: ajax/contact/details.php:31
msgid "Missing ID"
msgstr "Falta la ID"

#: ajax/contact/details.php:36
msgid "Error parsing VCard for ID: \""
msgstr "Error al analizar el VCard para la ID: \""

#: ajax/contact/saveproperty.php:42
msgid "checksum is not set."
msgstr "no se ha puesto ninguna suma de comprobación."

#: ajax/contact/saveproperty.php:62
msgid "Information about vCard is incorrect. Please reload the page: "
msgstr "La información sobre la vCard es incorrecta. Por favor, recarga la página:"

#: ajax/contact/saveproperty.php:69
msgid "Something went FUBAR. "
msgstr "Plof. Algo ha fallado."

#: ajax/contact/saveproperty.php:144
msgid "Error updating contact property."
msgstr "Error al actualizar una propiedad del contacto."

#: ajax/currentphoto.php:30 ajax/oc_photo.php:28 ajax/uploadphoto.php:36
#: ajax/uploadphoto.php:68
msgid "No contact ID was submitted."
msgstr "No se ha mandado ninguna ID de contacto."

#: ajax/currentphoto.php:36
msgid "Error reading contact photo."
msgstr "Error leyendo fotografía del contacto."

#: ajax/currentphoto.php:48
msgid "Error saving temporary file."
msgstr "Error al guardar archivo temporal."

#: ajax/currentphoto.php:51
msgid "The loading photo is not valid."
msgstr "La foto que se estaba cargando no es válida."

#: ajax/editname.php:31
msgid "Contact ID is missing."
msgstr "Falta la ID del contacto."

#: ajax/oc_photo.php:32
msgid "No photo path was submitted."
msgstr "No se ha introducido la ruta de la foto."

#: ajax/oc_photo.php:39
msgid "File doesn't exist:"
msgstr "Archivo inexistente:"

#: ajax/oc_photo.php:44 ajax/oc_photo.php:47
msgid "Error loading image."
msgstr "Error cargando imagen."

#: ajax/savecrop.php:69
msgid "Error getting contact object."
msgstr "Fallo al coger el contacto."

#: ajax/savecrop.php:79
msgid "Error getting PHOTO property."
msgstr "Fallo al coger las propiedades de la foto ."

#: ajax/savecrop.php:98
msgid "Error saving contact."
msgstr "Fallo al salvar un contacto"

#: ajax/savecrop.php:109
msgid "Error resizing image"
msgstr "Fallo al cambiar de tamaño una foto"

#: ajax/savecrop.php:112
msgid "Error cropping image"
msgstr "Fallo al cortar el tamaño de la foto"

#: ajax/savecrop.php:115
msgid "Error creating temporary image"
msgstr "Fallo al crear la foto temporal"

#: ajax/savecrop.php:118
msgid "Error finding image: "
msgstr "Fallo al encontrar la imagen"

#: ajax/uploadimport.php:44 ajax/uploadimport.php:76
msgid "Error uploading contacts to storage."
msgstr "Error al subir contactos al almacenamiento."

#: ajax/uploadimport.php:61 ajax/uploadphoto.php:77
msgid "There is no error, the file uploaded with success"
msgstr "No hay ningún error, el archivo se ha subido con éxito"

#: ajax/uploadimport.php:62 ajax/uploadphoto.php:78
msgid "The uploaded file exceeds the upload_max_filesize directive in php.ini"
msgstr "El archivo subido sobrepasa la directiva upload_max_filesize de php.ini"

#: ajax/uploadimport.php:63 ajax/uploadphoto.php:79
msgid ""
"The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in "
"the HTML form"
msgstr "El archivo subido sobrepasa la directiva MAX_FILE_SIZE especificada en el formulario HTML"

#: ajax/uploadimport.php:64 ajax/uploadphoto.php:80
msgid "The uploaded file was only partially uploaded"
msgstr "El archivo se ha subido parcialmente"

#: ajax/uploadimport.php:65 ajax/uploadphoto.php:81
msgid "No file was uploaded"
msgstr "No se ha subido ningún archivo"

#: ajax/uploadimport.php:66 ajax/uploadphoto.php:82
msgid "Missing a temporary folder"
msgstr "Falta la carpeta temporal"

#: ajax/uploadphoto.php:59 ajax/uploadphoto.php:109
msgid "Couldn't save temporary image: "
msgstr "Fallo no pudo salvar a una imagen temporal"

#: ajax/uploadphoto.php:62 ajax/uploadphoto.php:112
msgid "Couldn't load temporary image: "
msgstr "Fallo no pudo cargara de una imagen temporal"

#: ajax/uploadphoto.php:71
msgid "No file was uploaded. Unknown error"
msgstr "Fallo no se subió el fichero"

#: appinfo/app.php:19
msgid "Contacts"
msgstr "Contactos"

#: js/contacts.js:71
msgid "Sorry, this functionality has not been implemented yet"
msgstr "Perdón esta función no esta aún implementada"

#: js/contacts.js:71
msgid "Not implemented"
msgstr "No esta implementada"

#: js/contacts.js:76
msgid "Couldn't get a valid address."
msgstr "Fallo : no hay dirección valida"

#: js/contacts.js:76 js/contacts.js:365 js/contacts.js:381 js/contacts.js:393
#: js/contacts.js:675 js/contacts.js:715 js/contacts.js:741 js/contacts.js:850
#: js/contacts.js:856 js/contacts.js:868 js/contacts.js:902
#: js/contacts.js:1165 js/contacts.js:1173 js/contacts.js:1182
#: js/contacts.js:1217 js/contacts.js:1249 js/contacts.js:1261
#: js/contacts.js:1284 js/contacts.js:1421 js/contacts.js:1452
#: js/settings.js:25 js/settings.js:42 js/settings.js:67
msgid "Error"
msgstr "Fallo"

#: js/contacts.js:715
msgid "This property has to be non-empty."
msgstr "Este campo no puede estar vacío."

#: js/contacts.js:741
msgid "Couldn't serialize elements."
msgstr "Fallo no podido ordenar los elementos"

#: js/contacts.js:850 js/contacts.js:868
msgid ""
"'deleteProperty' called without type argument. Please report at "
"bugs.owncloud.org"
msgstr "La propiedad de \"borrar\" se llamado sin argumentos envia fallos a\nbugs.owncloud.org"

#: js/contacts.js:884
msgid "Edit name"
msgstr "Edita el Nombre"

#: js/contacts.js:1165
msgid "No files selected for upload."
msgstr "No hay ficheros seleccionados para subir"

#: js/contacts.js:1173
msgid ""
"The file you are trying to upload exceed the maximum size for file uploads "
"on this server."
msgstr "El fichero que quieres subir excede el tamaño máximo permitido en este servidor."

#: js/contacts.js:1236
msgid "Error loading profile picture."
msgstr ""

#: js/contacts.js:1337 js/contacts.js:1371
msgid "Select type"
msgstr "Selecciona el tipo"

#: js/contacts.js:1390
msgid ""
"Some contacts are marked for deletion, but not deleted yet. Please wait for "
"them to be deleted."
msgstr ""

#: js/loader.js:49
msgid "Result: "
msgstr "Resultado :"

#: js/loader.js:49
msgid " imported, "
msgstr "Importado."

#: js/loader.js:49
msgid " failed."
msgstr "Fallo."

#: js/settings.js:67
msgid "Displayname cannot be empty."
msgstr ""

#: lib/app.php:36
msgid "Addressbook not found: "
msgstr ""

#: lib/app.php:49
msgid "This is not your addressbook."
msgstr "Esta no es tu agenda de contactos."

#: lib/app.php:68
msgid "Contact could not be found."
msgstr "No se ha podido encontrar el contacto."

#: lib/app.php:112 templates/part.contact.php:117
msgid "Address"
msgstr "Dirección"

#: lib/app.php:113
msgid "Telephone"
msgstr "Teléfono"

#: lib/app.php:114 templates/part.contact.php:116
msgid "Email"
msgstr "Correo electrónico"

#: lib/app.php:115 templates/part.contact.php:39 templates/part.contact.php:40
#: templates/part.contact.php:112
msgid "Organization"
msgstr "Organización"

#: lib/app.php:127 lib/app.php:134 lib/app.php:144 lib/app.php:197
msgid "Work"
msgstr "Trabajo"

#: lib/app.php:128 lib/app.php:132 lib/app.php:145
msgid "Home"
msgstr "Particular"

#: lib/app.php:133
msgid "Mobile"
msgstr "Móvil"

#: lib/app.php:135
msgid "Text"
msgstr "Texto"

#: lib/app.php:136
msgid "Voice"
msgstr "Voz"

#: lib/app.php:137
msgid "Message"
msgstr "Mensaje"

#: lib/app.php:138
msgid "Fax"
msgstr "Fax"

#: lib/app.php:139
msgid "Video"
msgstr "Vídeo"

#: lib/app.php:140
msgid "Pager"
msgstr "Localizador"

#: lib/app.php:146
msgid "Internet"
msgstr "Internet"

#: lib/app.php:183 templates/part.contact.php:45
#: templates/part.contact.php:114
msgid "Birthday"
msgstr "Cumpleaños"

#: lib/app.php:184
msgid "Business"
msgstr "Negocio"

#: lib/app.php:185
msgid "Call"
msgstr "Llamada"

#: lib/app.php:186
msgid "Clients"
msgstr "Clientes"

#: lib/app.php:187
msgid "Deliverer"
msgstr ""

#: lib/app.php:188
msgid "Holidays"
msgstr "Vacaciones"

#: lib/app.php:189
msgid "Ideas"
msgstr "Ideas"

#: lib/app.php:190
msgid "Journey"
msgstr "Jornada"

#: lib/app.php:191
msgid "Jubilee"
msgstr ""

#: lib/app.php:192
msgid "Meeting"
msgstr "Reunión"

#: lib/app.php:193
msgid "Other"
msgstr "Otro"

#: lib/app.php:194
msgid "Personal"
msgstr "Personal"

#: lib/app.php:195
msgid "Projects"
msgstr "Proyectos"

#: lib/app.php:196
msgid "Questions"
msgstr "Preguntas"

#: lib/hooks.php:102
msgid "{name}'s Birthday"
msgstr "Cumpleaños de {name}"

#: lib/search.php:15
msgid "Contact"
msgstr "Contacto"

#: templates/index.php:14
msgid "Add Contact"
msgstr "Añadir contacto"

#: templates/index.php:15 templates/index.php:16 templates/part.import.php:17
msgid "Import"
msgstr "Importar"

#: templates/index.php:18
msgid "Settings"
msgstr "Configuración"

#: templates/index.php:18 templates/settings.php:9
msgid "Addressbooks"
msgstr "Libretas de direcciones"

#: templates/index.php:36 templates/part.import.php:24
msgid "Close"
msgstr "Cierra."

#: templates/index.php:37
msgid "Keyboard shortcuts"
msgstr "Atajos de teclado"

#: templates/index.php:39
msgid "Navigation"
msgstr "Navegación"

#: templates/index.php:42
msgid "Next contact in list"
msgstr "Siguiente contacto en la lista"

#: templates/index.php:44
msgid "Previous contact in list"
msgstr "Anterior contacto en la lista"

#: templates/index.php:46
msgid "Expand/collapse current addressbook"
msgstr ""

#: templates/index.php:48
msgid "Next addressbook"
msgstr ""

#: templates/index.php:50
msgid "Previous addressbook"
msgstr ""

#: templates/index.php:54
msgid "Actions"
msgstr "Acciones"

#: templates/index.php:57
msgid "Refresh contacts list"
msgstr "Refrescar la lista de contactos"

#: templates/index.php:59
msgid "Add new contact"
msgstr "Añadir un nuevo contacto"

#: templates/index.php:61
msgid "Add new addressbook"
msgstr ""

#: templates/index.php:63
msgid "Delete current contact"
msgstr "Eliminar contacto actual"

#: templates/part.contact.php:17
msgid "Drop photo to upload"
msgstr "Suelta una foto para subirla"

#: templates/part.contact.php:19
msgid "Delete current photo"
msgstr "Eliminar fotografía actual"

#: templates/part.contact.php:20
msgid "Edit current photo"
msgstr "Editar fotografía actual"

#: templates/part.contact.php:21
msgid "Upload new photo"
msgstr "Subir nueva fotografía"

#: templates/part.contact.php:22
msgid "Select photo from ownCloud"
msgstr "Seleccionar fotografía desde ownCloud"

#: templates/part.contact.php:35
msgid "Format custom, Short name, Full name, Reverse or Reverse with comma"
msgstr "Formato personalizado, nombre abreviado, nombre completo, al revés o al revés con coma"

#: templates/part.contact.php:36
msgid "Edit name details"
msgstr "Editar los detalles del nombre"

#: templates/part.contact.php:40 templates/part.contact.php:42
#: templates/part.contact.php:44 templates/part.contact.php:46
#: templates/part.contact.php:50 templates/settings.php:33
msgid "Delete"
msgstr "Borrar"

#: templates/part.contact.php:41 templates/part.contact.php:113
msgid "Nickname"
msgstr "Alias"

#: templates/part.contact.php:42
msgid "Enter nickname"
msgstr "Introduce un alias"

#: templates/part.contact.php:43 templates/part.contact.php:119
msgid "Web site"
msgstr "Sitio Web"

#: templates/part.contact.php:44
msgid "http://www.somesite.com"
msgstr ""

#: templates/part.contact.php:44
msgid "Go to web site"
msgstr ""

#: templates/part.contact.php:46
msgid "dd-mm-yyyy"
msgstr "dd-mm-yyyy"

#: templates/part.contact.php:47 templates/part.contact.php:120
msgid "Groups"
msgstr "Grupos"

#: templates/part.contact.php:49
msgid "Separate groups with commas"
msgstr "Separa los grupos con comas"

#: templates/part.contact.php:50
msgid "Edit groups"
msgstr "Editar grupos"

#: templates/part.contact.php:63 templates/part.contact.php:77
msgid "Preferred"
msgstr "Preferido"

#: templates/part.contact.php:64
msgid "Please specify a valid email address."
msgstr "Por favor especifica una dirección de correo electrónico válida."

#: templates/part.contact.php:64
msgid "Enter email address"
msgstr "Introduce una dirección de correo electrónico"

#: templates/part.contact.php:68
msgid "Mail to address"
msgstr "Enviar por correo a la dirección"

#: templates/part.contact.php:69
msgid "Delete email address"
msgstr "Eliminar dirección de correo electrónico"

#: templates/part.contact.php:78
msgid "Enter phone number"
msgstr "Introduce un número de teléfono"

#: templates/part.contact.php:82
msgid "Delete phone number"
msgstr "Eliminar número de teléfono"

#: templates/part.contact.php:92
msgid "View on map"
msgstr "Ver en el mapa"

#: templates/part.contact.php:92
msgid "Edit address details"
msgstr "Editar detalles de la dirección"

#: templates/part.contact.php:103
msgid "Add notes here."
msgstr "Añade notas aquí."

#: templates/part.contact.php:110
msgid "Add field"
msgstr "Añadir campo"

#: templates/part.contact.php:115
msgid "Phone"
msgstr "Teléfono"

#: templates/part.contact.php:118
msgid "Note"
msgstr "Nota"

#: templates/part.contact.php:123
msgid "Download contact"
msgstr "Descargar contacto"

#: templates/part.contact.php:124
msgid "Delete contact"
msgstr "Eliminar contacto"

#: templates/part.cropphoto.php:65
msgid "The temporary image has been removed from cache."
msgstr "La foto temporal se ha borrado del cache."

#: templates/part.edit_address_dialog.php:6
msgid "Edit address"
msgstr "Editar dirección"

#: templates/part.edit_address_dialog.php:10
msgid "Type"
msgstr "Tipo"

#: templates/part.edit_address_dialog.php:18
#: templates/part.edit_address_dialog.php:21
msgid "PO Box"
msgstr "Código postal"

#: templates/part.edit_address_dialog.php:24
msgid "Street address"
msgstr ""

#: templates/part.edit_address_dialog.php:27
msgid "Street and number"
msgstr ""

#: templates/part.edit_address_dialog.php:30
msgid "Extended"
msgstr "Extendido"

#: templates/part.edit_address_dialog.php:33
msgid "Apartment number etc."
msgstr ""

#: templates/part.edit_address_dialog.php:36
#: templates/part.edit_address_dialog.php:39
msgid "City"
msgstr "Ciudad"

#: templates/part.edit_address_dialog.php:42
msgid "Region"
msgstr "Región"

#: templates/part.edit_address_dialog.php:45
msgid "E.g. state or province"
msgstr ""

#: templates/part.edit_address_dialog.php:48
msgid "Zipcode"
msgstr "Código postal"

#: templates/part.edit_address_dialog.php:51
msgid "Postal code"
msgstr "Código postal"

#: templates/part.edit_address_dialog.php:54
#: templates/part.edit_address_dialog.php:57
msgid "Country"
msgstr "País"

#: templates/part.edit_name_dialog.php:16
msgid "Addressbook"
msgstr "Libreta de direcciones"

#: templates/part.edit_name_dialog.php:23
msgid "Hon. prefixes"
msgstr "Prefijos honoríficos"

#: templates/part.edit_name_dialog.php:27
msgid "Miss"
msgstr "Srta"

#: templates/part.edit_name_dialog.php:28
msgid "Ms"
msgstr "Sra."

#: templates/part.edit_name_dialog.php:29
msgid "Mr"
msgstr "Sr."

#: templates/part.edit_name_dialog.php:30
msgid "Sir"
msgstr "Señor"

#: templates/part.edit_name_dialog.php:31
msgid "Mrs"
msgstr "Sra"

#: templates/part.edit_name_dialog.php:32
msgid "Dr"
msgstr "Dr"

#: templates/part.edit_name_dialog.php:35
msgid "Given name"
msgstr "Nombre"

#: templates/part.edit_name_dialog.php:37
msgid "Additional names"
msgstr "Nombres adicionales"

#: templates/part.edit_name_dialog.php:39
msgid "Family name"
msgstr "Apellido"

#: templates/part.edit_name_dialog.php:41
msgid "Hon. suffixes"
msgstr "Sufijos honoríficos"

#: templates/part.edit_name_dialog.php:45
msgid "J.D."
msgstr "J.D."

#: templates/part.edit_name_dialog.php:46
msgid "M.D."
msgstr "M.D."

#: templates/part.edit_name_dialog.php:47
msgid "D.O."
msgstr "D.O."

#: templates/part.edit_name_dialog.php:48
msgid "D.C."
msgstr "D.C."

#: templates/part.edit_name_dialog.php:49
msgid "Ph.D."
msgstr "Dr"

#: templates/part.edit_name_dialog.php:50
msgid "Esq."
msgstr "Don"

#: templates/part.edit_name_dialog.php:51
msgid "Jr."
msgstr "Jr."

#: templates/part.edit_name_dialog.php:52
msgid "Sn."
msgstr "Sn."

#: templates/part.import.php:1
msgid "Import a contacts file"
msgstr "Importar archivo de contactos"

#: templates/part.import.php:6
msgid "Please choose the addressbook"
msgstr "Por favor escoge la agenda"

#: templates/part.import.php:10
msgid "create a new addressbook"
msgstr "crear una nueva agenda"

#: templates/part.import.php:15
msgid "Name of new addressbook"
msgstr "Nombre de la nueva agenda"

#: templates/part.import.php:20
msgid "Importing contacts"
msgstr "Importando contactos"

#: templates/part.no_contacts.php:3
msgid "You have no contacts in your addressbook."
msgstr "No hay contactos en tu agenda."

#: templates/part.no_contacts.php:5
msgid "Add contact"
msgstr "Añadir contacto"

#: templates/part.no_contacts.php:6
msgid "Configure addressbooks"
msgstr "Configurar agenda"

#: templates/part.selectaddressbook.php:1
msgid "Select Address Books"
msgstr ""

#: templates/part.selectaddressbook.php:20
msgid "Enter name"
msgstr "Introducir nombre"

#: templates/part.selectaddressbook.php:22
msgid "Enter description"
msgstr "Introducir descripción"

#: templates/settings.php:3
msgid "CardDAV syncing addresses"
msgstr "Sincronizando direcciones"

#: templates/settings.php:3
msgid "more info"
msgstr "más información"

#: templates/settings.php:5
msgid "Primary address (Kontact et al)"
msgstr "Dirección primaria (Kontact et al)"

#: templates/settings.php:7
msgid "iOS/OS X"
msgstr "iOS/OS X"

#: templates/settings.php:20
msgid "Show CardDav link"
msgstr ""

#: templates/settings.php:23
msgid "Show read-only VCF link"
msgstr ""

#: templates/settings.php:26
msgid "Download"
msgstr "Descargar"

#: templates/settings.php:30
msgid "Edit"
msgstr "Editar"

#: templates/settings.php:40
msgid "New Address Book"
msgstr "Nueva libreta de direcciones"

#: templates/settings.php:41
msgid "Name"
msgstr "Nombre"

#: templates/settings.php:42
msgid "Description"
msgstr "Descripción"

#: templates/settings.php:43
msgid "Save"
msgstr "Guardar"

#: templates/settings.php:44
msgid "Cancel"
msgstr "Cancelar"

#: templates/settings.php:49
msgid "More..."
msgstr "Más..."