diff options
author | Grégoire Aubert <gregoire.aubert@sonarsource.com> | 2018-01-17 11:29:05 +0100 |
---|---|---|
committer | Grégoire Aubert <gregoire.aubert@sonarsource.com> | 2018-01-25 15:16:50 +0100 |
commit | df96a05cc325b2946c25ee8277f64638ed72288c (patch) | |
tree | ca28960f1a7fc514e0b1d6e5b90d1f46a6c45ffd /server/sonar-web/src | |
parent | 49391d2eff65209068acc31e41393b9617fb2458 (diff) | |
download | sonarqube-df96a05cc325b2946c25ee8277f64638ed72288c.tar.gz sonarqube-df96a05cc325b2946c25ee8277f64638ed72288c.zip |
Migrate parts of overview app to TS
Diffstat (limited to 'server/sonar-web/src')
81 files changed, 1007 insertions, 1478 deletions
diff --git a/server/sonar-web/src/main/js/api/components.ts b/server/sonar-web/src/main/js/api/components.ts index 5ed1cf5a258..db1e32cc643 100644 --- a/server/sonar-web/src/main/js/api/components.ts +++ b/server/sonar-web/src/main/js/api/components.ts @@ -79,7 +79,7 @@ export function createProject(data: { } export function searchProjectTags(data?: { ps?: number; q?: string }): Promise<any> { - return getJSON('/api/project_tags/search', data); + return getJSON('/api/project_tags/search', data).catch(throwGlobalError); } export function setProjectTags(data: { project: string; tags: string }): Promise<void> { diff --git a/server/sonar-web/src/main/js/api/projectActivity.ts b/server/sonar-web/src/main/js/api/projectActivity.ts index d78ff17681e..b931ea5a6f6 100644 --- a/server/sonar-web/src/main/js/api/projectActivity.ts +++ b/server/sonar-web/src/main/js/api/projectActivity.ts @@ -19,14 +19,19 @@ */ import { getJSON, postJSON, post, RequestData } from '../helpers/request'; import throwGlobalError from '../app/utils/throwGlobalError'; +import { Paging } from '../app/types'; -interface GetProjectActivityResponse { - analyses: any[]; - paging: { - total: number; - pageIndex: number; - pageSize: number; - }; +export interface Event { + key: string; + name: string; + category: string; + description?: string; +} + +export interface Analysis { + key: string; + date: string; + events: Event[]; } export function getProjectActivity(data: { @@ -35,7 +40,7 @@ export function getProjectActivity(data: { category?: string; p?: number; ps?: number; -}): Promise<GetProjectActivityResponse> { +}): Promise<{ analyses: Analysis[]; paging: Paging }> { return getJSON('/api/project_analyses/search', data).catch(throwGlobalError); } diff --git a/server/sonar-web/src/main/js/api/projectLinks.ts b/server/sonar-web/src/main/js/api/projectLinks.ts index 16a492bca9f..e91b200c4fe 100644 --- a/server/sonar-web/src/main/js/api/projectLinks.ts +++ b/server/sonar-web/src/main/js/api/projectLinks.ts @@ -18,11 +18,19 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import { getJSON, post, postJSON } from '../helpers/request'; +import throwGlobalError from '../app/utils/throwGlobalError'; -export function getProjectLinks(projectKey: string): Promise<any> { +export interface ProjectLink { + id: string; + name: string; + type: string; + url: string; +} + +export function getProjectLinks(projectKey: string): Promise<ProjectLink[]> { const url = '/api/project_links/search'; const data = { projectKey }; - return getJSON(url, data).then(r => r.links); + return getJSON(url, data).then(r => r.links, throwGlobalError); } export function deleteLink(linkId: string): Promise<void> { diff --git a/server/sonar-web/src/main/js/api/quality-gates.ts b/server/sonar-web/src/main/js/api/quality-gates.ts index 5a50564b36f..b40e6130b28 100644 --- a/server/sonar-web/src/main/js/api/quality-gates.ts +++ b/server/sonar-web/src/main/js/api/quality-gates.ts @@ -19,6 +19,7 @@ */ import { getJSON, post, postJSON } from '../helpers/request'; import throwGlobalError from '../app/utils/throwGlobalError'; +import { Metric } from '../app/types'; export interface ConditionBase { error: string; @@ -148,9 +149,33 @@ export function dissociateGateWithProject(data: { return post('/api/qualitygates/deselect', data).catch(throwGlobalError); } +export interface ConditionAnalysis { + comparator: string; + errorThreshold?: string; + metric: string; + periodIndex?: number; + onLeak?: boolean; + status: string; + value: string; + warningThreshold?: string; +} + +export interface ApplicationProject { + key: string; + name: string; + status: string; + conditions: ConditionAnalysis[]; +} + +export interface ApplicationQualityGate { + metrics: Metric[]; + projects: ApplicationProject[]; + status: string; +} + export function getApplicationQualityGate(data: { application: string; organization?: string; -}): Promise<void | Response> { +}): Promise<ApplicationQualityGate> { return getJSON('/api/qualitygates/application_status', data).catch(throwGlobalError); } diff --git a/server/sonar-web/src/main/js/api/time-machine.ts b/server/sonar-web/src/main/js/api/time-machine.ts index e6c01eba852..2f25912b191 100644 --- a/server/sonar-web/src/main/js/api/time-machine.ts +++ b/server/sonar-web/src/main/js/api/time-machine.ts @@ -19,6 +19,7 @@ */ import { getJSON } from '../helpers/request'; import { Paging } from '../app/types'; +import throwGlobalError from '../app/utils/throwGlobalError'; export interface HistoryItem { date: Date; @@ -47,7 +48,7 @@ export function getTimeMachineData( metrics: metrics.join(), ps: 1000, ...other - }); + }).catch(throwGlobalError); } export function getAllTimeMachineData( diff --git a/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx b/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx index e109a142861..de3102a3957 100644 --- a/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx +++ b/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx @@ -43,7 +43,7 @@ interface Props { interface State { branches: Branch[]; loading: boolean; - component: Component | null; + component?: Component; currentTask?: Task; isInProgress?: boolean; isPending?: boolean; @@ -54,7 +54,7 @@ export class ComponentContainer extends React.PureComponent<Props, State> { constructor(props: Props) { super(props); - this.state = { branches: [], loading: true, component: null }; + this.state = { branches: [], loading: true }; } componentDidMount() { diff --git a/server/sonar-web/src/main/js/app/types.ts b/server/sonar-web/src/main/js/app/types.ts index 7a2a64d4e26..dedfabe68c2 100644 --- a/server/sonar-web/src/main/js/app/types.ts +++ b/server/sonar-web/src/main/js/app/types.ts @@ -68,19 +68,25 @@ export interface Breadcrumb { qualifier: string; } -export interface Component { +export interface LightComponent { + key: string; + organization: string; + qualifier: string; +} + +export interface Component extends LightComponent { analysisDate?: string; breadcrumbs: Breadcrumb[]; configuration?: ComponentConfiguration; description?: string; extensions?: Extension[]; isFavorite?: boolean; - key: string; name: string; - organization: string; path?: string; - qualifier: string; refKey?: string; + qualityProfiles?: { key: string; language: string; name: string }[]; + qualityGate?: { isDefault?: boolean; key: string; name: string }; + tags?: string[]; version?: string; visibility?: string; } @@ -105,7 +111,7 @@ export interface Metric { domain?: string; hidden?: boolean; key: string; - name?: string; + name: string; qualitative?: boolean; type: string; } diff --git a/server/sonar-web/src/main/js/apps/code/components/ComponentMeasure.tsx b/server/sonar-web/src/main/js/apps/code/components/ComponentMeasure.tsx index d0bdd18e303..bc900211a0e 100644 --- a/server/sonar-web/src/main/js/apps/code/components/ComponentMeasure.tsx +++ b/server/sonar-web/src/main/js/apps/code/components/ComponentMeasure.tsx @@ -42,7 +42,5 @@ export default function ComponentMeasure({ component, metricKey, metricType }: P return <span />; } - return ( - <Measure measure={{ ...measure, metric: { key: finalMetricKey, type: finalMetricType } }} /> - ); + return <Measure value={measure.value} metricKey={finalMetricKey} metricType={finalMetricType} />; } diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureHeader.js b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureHeader.js index f59b58ee56f..c7964eb56a1 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureHeader.js +++ b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureHeader.js @@ -44,7 +44,7 @@ import { isDiffMetric } from '../../../helpers/measures'; export default function MeasureHeader(props /*: Props*/) { const { branch, component, leakPeriod, measure, secondaryMeasure } = props; - const metric = measure.metric; + const { metric } = measure; const isDiff = isDiffMetric(metric.key); return ( <div className="measure-details-header big-spacer-bottom"> @@ -55,9 +55,14 @@ export default function MeasureHeader(props /*: Props*/) { <span className="measure-details-value spacer-left"> <strong> {isDiff ? ( - <Measure className="domain-measures-leak" measure={measure} metric={metric} /> + <Measure + className="domain-measures-leak" + value={measure.leak} + metricKey={metric.key} + metricType={metric.type} + /> ) : ( - <Measure measure={measure} metric={metric} /> + <Measure value={measure.value} metricKey={metric.key} metricType={metric.type} /> )} </strong> </span> diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureHeader-test.js.snap b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureHeader-test.js.snap index e58ba900cd7..d6c9bceb55a 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureHeader-test.js.snap +++ b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureHeader-test.js.snap @@ -68,30 +68,9 @@ exports[`should render correctly 1`] = ` > <strong> <Measure - measure={ - Object { - "leak": "0.0", - "metric": Object { - "key": "reliability_rating", - "name": "Reliability Rating", - "type": "RATING", - }, - "periods": Array [ - Object { - "index": 1, - "value": "0.0", - }, - ], - "value": "3.0", - } - } - metric={ - Object { - "key": "reliability_rating", - "name": "Reliability Rating", - "type": "RATING", - } - } + metricKey="reliability_rating" + metricType="RATING" + value="3.0" /> </strong> </span> @@ -165,29 +144,9 @@ exports[`should render correctly for leak 1`] = ` <strong> <Measure className="domain-measures-leak" - measure={ - Object { - "leak": "3.0", - "metric": Object { - "key": "new_reliability_rating", - "name": "Reliability Rating on New Code", - "type": "RATING", - }, - "periods": Array [ - Object { - "index": 1, - "value": "3.0", - }, - ], - } - } - metric={ - Object { - "key": "new_reliability_rating", - "name": "Reliability Rating on New Code", - "type": "RATING", - } - } + metricKey="new_reliability_rating" + metricType="RATING" + value="3.0" /> </strong> </span> diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentsListRow.js b/server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentsListRow.js index 30b74e4aab4..e87b224967f 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentsListRow.js +++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentsListRow.js @@ -52,11 +52,8 @@ export default function ComponentsListRow(props /*: Props */) { {otherMeasures.map(measure => ( <MeasureCell key={measure.metric.key} - component={{ - ...component, - value: measure.value, - leak: measure.leak - }} + component={component} + measure={measure} metric={measure.metric} /> ))} diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/MeasureCell.js b/server/sonar-web/src/main/js/apps/component-measures/drilldown/MeasureCell.js index d5109091646..0934587ccf2 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/drilldown/MeasureCell.js +++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/MeasureCell.js @@ -20,6 +20,7 @@ // @flow import React from 'react'; import Measure from '../../../components/measure/Measure'; +import { isDiffMetric } from '../../../helpers/measures'; /*:: import type { Component } from '../types'; */ /*:: import type { Metric } from '../../../store/metrics/actions'; */ @@ -32,7 +33,11 @@ export default function MeasureCell({ component, metric } /*: Props */) { return ( <td className="thin nowrap text-right"> <span id={`component-measures-component-measure-${component.key}-${metric.key}`}> - <Measure measure={{ metric, value: component.value, leak: component.leak }} /> + <Measure + value={isDiffMetric(metric.key) ? component.leak : component.value} + metricKey={metric.key} + metricType={metric.type} + /> </span> </td> ); diff --git a/server/sonar-web/src/main/js/apps/component-measures/sidebar/FacetMeasureValue.js b/server/sonar-web/src/main/js/apps/component-measures/sidebar/FacetMeasureValue.js index d4060ac29c7..93d7ff58493 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/sidebar/FacetMeasureValue.js +++ b/server/sonar-web/src/main/js/apps/component-measures/sidebar/FacetMeasureValue.js @@ -29,14 +29,22 @@ export default function FacetMeasureValue({ measure } /*: { measure: MeasureEnha <div id={`measure-${measure.metric.key}-leak`} className="domain-measures-value domain-measures-leak"> - <Measure measure={measure} /> + <Measure + value={measure.leak} + metricKey={measure.metric.key} + metricType={measure.metric.type} + /> </div> ); } return ( <div id={`measure-${measure.metric.key}-value`} className="domain-measures-value"> - <Measure measure={measure} /> + <Measure + value={measure.value} + metricKey={measure.metric.key} + metricType={measure.metric.type} + /> </div> ); } diff --git a/server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/__snapshots__/FacetMeasureValue-test.js.snap b/server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/__snapshots__/FacetMeasureValue-test.js.snap index 9bd6a3e7e00..228f8a3e6d9 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/__snapshots__/FacetMeasureValue-test.js.snap +++ b/server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/__snapshots__/FacetMeasureValue-test.js.snap @@ -6,23 +6,9 @@ exports[`should display leak measure value 1`] = ` id="measure-new_bugs-leak" > <Measure - measure={ - Object { - "leak": "5", - "metric": Object { - "domain": "Reliability", - "key": "new_bugs", - "name": "New Bugs", - "type": "INT", - }, - "periods": Array [ - Object { - "index": 1, - "value": "5", - }, - ], - } - } + metricKey="new_bugs" + metricType="INT" + value="5" /> </div> `; @@ -33,24 +19,9 @@ exports[`should display measure value 1`] = ` id="measure-bugs-value" > <Measure - measure={ - Object { - "leak": "5", - "metric": Object { - "domain": "Reliability", - "key": "bugs", - "name": "Bugs", - "type": "INT", - }, - "periods": Array [ - Object { - "index": 1, - "value": "5", - }, - ], - "value": "5", - } - } + metricKey="bugs" + metricType="INT" + value="5" /> </div> `; diff --git a/server/sonar-web/src/main/js/apps/overview/badges/BadgesModal.tsx b/server/sonar-web/src/main/js/apps/overview/badges/BadgesModal.tsx index d8d23b2182c..7e4e0daf249 100644 --- a/server/sonar-web/src/main/js/apps/overview/badges/BadgesModal.tsx +++ b/server/sonar-web/src/main/js/apps/overview/badges/BadgesModal.tsx @@ -27,7 +27,7 @@ import { translate } from '../../../helpers/l10n'; import './styles.css'; interface Props { - branch: string; + branch?: string; project: string; } diff --git a/server/sonar-web/src/main/js/apps/overview/components/App.tsx b/server/sonar-web/src/main/js/apps/overview/components/App.tsx new file mode 100644 index 00000000000..03d898d4e9e --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/components/App.tsx @@ -0,0 +1,86 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import * as PropTypes from 'prop-types'; +import OverviewApp from './OverviewApp'; +import EmptyOverview from './EmptyOverview'; +import { getBranchName, isShortLivingBranch } from '../../../helpers/branches'; +import { getProjectBranchUrl, getCodeUrl } from '../../../helpers/urls'; +import { Branch, Component } from '../../../app/types'; + +interface Props { + branch?: Branch; + component: Component; + isInProgress?: boolean; + isPending?: boolean; + onComponentChange: (changes: Partial<Component>) => void; +} + +export default class App extends React.PureComponent<Props> { + static contextTypes = { + router: PropTypes.object + }; + + componentDidMount() { + const { branch, component } = this.props; + + if (this.isPortfolio()) { + this.context.router.replace({ + pathname: '/portfolio', + query: { id: component.key } + }); + } else if (this.isFile()) { + this.context.router.replace( + getCodeUrl(component.breadcrumbs[0].key, getBranchName(branch), component.key) + ); + } else if (isShortLivingBranch(branch)) { + this.context.router.replace(getProjectBranchUrl(component.key, branch)); + } + } + + isPortfolio = () => ['VW', 'SVW'].includes(this.props.component.qualifier); + + isFile = () => ['FIL', 'UTS'].includes(this.props.component.qualifier); + + render() { + const { branch, component } = this.props; + + if (this.isPortfolio() || this.isFile() || isShortLivingBranch(branch)) { + return null; + } + + if (!component.analysisDate) { + return ( + <EmptyOverview + component={component.key} + showWarning={!this.props.isPending && !this.props.isInProgress} + /> + ); + } + + return ( + <OverviewApp + branch={branch} + component={component} + onComponentChange={this.props.onComponentChange} + /> + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/overview/components/OverviewApp.js b/server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx index 0271e1ecdc1..07d22017edf 100644 --- a/server/sonar-web/src/main/js/apps/overview/components/OverviewApp.js +++ b/server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx @@ -17,8 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; +import * as React from 'react'; import { uniq } from 'lodash'; import QualityGate from '../qualityGate/QualityGate'; import ApplicationQualityGate from '../qualityGate/ApplicationQualityGate'; @@ -29,54 +28,46 @@ import Duplications from '../main/Duplications'; import Meta from '../meta/Meta'; import throwGlobalError from '../../../app/utils/throwGlobalError'; import { getMeasuresAndMeta } from '../../../api/measures'; -import { getAllTimeMachineData } from '../../../api/time-machine'; +import { getAllTimeMachineData, History } from '../../../api/time-machine'; import { parseDate } from '../../../helpers/dates'; -import { enhanceMeasuresWithMetrics } from '../../../helpers/measures'; -import { getLeakPeriod } from '../../../helpers/periods'; +import { enhanceMeasuresWithMetrics, MeasureEnhanced } from '../../../helpers/measures'; +import { getLeakPeriod, Period } from '../../../helpers/periods'; import { getCustomGraph, getGraph } from '../../../helpers/storage'; import { METRICS, HISTORY_METRICS_LIST } from '../utils'; import { DEFAULT_GRAPH, getDisplayedHistoryMetrics } from '../../projectActivity/utils'; import { getBranchName } from '../../../helpers/branches'; -/*:: import type { Component, History, MeasuresList, Period } from '../types'; */ +import { Branch, Component } from '../../../app/types'; import '../styles.css'; -/*:: -type Props = { - branch?: { name: string }, - component: Component, - onComponentChange: {} => void -}; -*/ - -/*:: -type State = { - history?: History, - historyStartDate?: Date, - loading: boolean, - measures: MeasuresList, - periods?: Array<Period> -}; -*/ - -export default class OverviewApp extends React.PureComponent { - /*:: mounted: boolean; */ - /*:: props: Props; */ - state /*: State */ = { - loading: true, - measures: [] - }; +interface Props { + branch?: Branch; + component: Component; + onComponentChange: (changes: {}) => void; +} + +interface State { + history?: History; + historyStartDate?: Date; + loading: boolean; + measures: MeasureEnhanced[]; + periods?: Period[]; +} + +export default class OverviewApp extends React.PureComponent<Props, State> { + mounted: boolean; + state: State = { loading: true, measures: [] }; componentDidMount() { this.mounted = true; - this.loadMeasures().then(this.loadHistory); + this.loadMeasures().then(this.loadHistory, () => {}); } - componentDidUpdate(prevProps /*: Props */) { + componentDidUpdate(prevProps: Props) { if ( this.props.component.key !== prevProps.component.key || this.props.branch !== prevProps.branch ) { - this.loadMeasures().then(this.loadHistory); + this.loadMeasures().then(this.loadHistory, () => {}); } } @@ -90,10 +81,10 @@ export default class OverviewApp extends React.PureComponent { return getMeasuresAndMeta(component.key, METRICS, { additionalFields: 'metrics,periods', - branch: branch && getBranchName(branch) + branch: getBranchName(branch) }).then( r => { - if (this.mounted) { + if (this.mounted && r.metrics) { this.setState({ loading: false, measures: enhanceMeasuresWithMetrics(r.component.measures, r.metrics), @@ -119,22 +110,22 @@ export default class OverviewApp extends React.PureComponent { } const metrics = uniq(HISTORY_METRICS_LIST.concat(graphMetrics)); - return getAllTimeMachineData(component.key, metrics, { - branch: branch && getBranchName(branch) - }).then(r => { - if (this.mounted) { - const history /*: History */ = {}; - r.measures.forEach(measure => { - const measureHistory = measure.history.map(analysis => ({ - date: parseDate(analysis.date), - value: analysis.value - })); - history[measure.metric] = measureHistory; - }); - const historyStartDate = history[HISTORY_METRICS_LIST[0]][0].date; - this.setState({ history, historyStartDate }); + return getAllTimeMachineData(component.key, metrics, { branch: getBranchName(branch) }).then( + r => { + if (this.mounted) { + const history: History = {}; + r.measures.forEach(measure => { + const measureHistory = measure.history.map(analysis => ({ + date: parseDate(analysis.date), + value: analysis.value + })); + history[measure.metric] = measureHistory; + }); + const historyStartDate = history[HISTORY_METRICS_LIST[0]][0].date; + this.setState({ history, historyStartDate }); + } } - }, throwGlobalError); + ); }; getApplicationLeakPeriod = () => @@ -158,7 +149,7 @@ export default class OverviewApp extends React.PureComponent { const leakPeriod = component.qualifier === 'APP' ? this.getApplicationLeakPeriod() : getLeakPeriod(periods); - const branchName = branch && getBranchName(branch); + const branchName = getBranchName(branch); const domainProps = { branch: branchName, component, diff --git a/server/sonar-web/src/main/js/apps/overview/components/Timeline.js b/server/sonar-web/src/main/js/apps/overview/components/Timeline.tsx index 15dde5221d3..1b2bd3d04ec 100644 --- a/server/sonar-web/src/main/js/apps/overview/components/Timeline.js +++ b/server/sonar-web/src/main/js/apps/overview/components/Timeline.tsx @@ -17,20 +17,20 @@ * 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 * as React from 'react'; import { max } from 'd3-array'; import { LineChart } from '../../../components/charts/line-chart'; +import { HistoryItem } from '../../../api/time-machine'; const HEIGHT = 80; -export default class Timeline extends React.PureComponent { - static propTypes = { - history: PropTypes.arrayOf(PropTypes.object).isRequired, - before: PropTypes.object, - after: PropTypes.object - }; +interface Props { + history: HistoryItem[]; + before?: Date; + after?: Date; +} +export default class Timeline extends React.PureComponent<Props> { filterSnapshots() { const { history, before, after } = this.props; diff --git a/server/sonar-web/src/main/js/apps/overview/components/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/overview/components/__tests__/App-test.tsx new file mode 100644 index 00000000000..4edd63ffba8 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/components/__tests__/App-test.tsx @@ -0,0 +1,68 @@ +/* + * 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 { mount, shallow } from 'enzyme'; +import App from '../App'; +import OverviewApp from '../OverviewApp'; +import EmptyOverview from '../EmptyOverview'; +import { BranchType, LongLivingBranch } from '../../../../app/types'; + +const component = { + key: 'foo', + analysisDate: '2016-01-01', + breadcrumbs: [], + name: 'Foo', + organization: 'org', + qualifier: 'TRK', + version: '0.0.1' +}; + +it('should render OverviewApp', () => { + expect(getWrapper().type()).toBe(OverviewApp); +}); + +it('should render EmptyOverview', () => { + const output = getWrapper({ component: { key: 'foo' } }); + expect(output.type()).toBe(EmptyOverview); +}); + +it('redirects on Code page for files', () => { + const branch: LongLivingBranch = { isMain: false, name: 'b', type: BranchType.LONG }; + const newComponent = { + ...component, + breadcrumbs: [ + { key: 'project', name: 'Project', qualifier: 'TRK' }, + { key: 'foo', name: 'Foo', qualifier: 'DIR' } + ], + qualifier: 'FIL' + }; + const replace = jest.fn(); + mount(<App branch={branch} component={newComponent} onComponentChange={jest.fn()} />, { + context: { router: { replace } } + }); + expect(replace).toBeCalledWith({ + pathname: '/code', + query: { branch: 'b', id: 'project', selected: 'foo' } + }); +}); + +function getWrapper(props = {}) { + return shallow(<App component={component} onComponentChange={jest.fn()} {...props} />); +} diff --git a/server/sonar-web/src/main/js/apps/overview/events/AnalysesList.js b/server/sonar-web/src/main/js/apps/overview/events/AnalysesList.tsx index bff7894acb7..30a3d98e7b1 100644 --- a/server/sonar-web/src/main/js/apps/overview/events/AnalysesList.js +++ b/server/sonar-web/src/main/js/apps/overview/events/AnalysesList.tsx @@ -17,47 +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 Analysis from './Analysis'; import { getAllMetrics } from '../../../api/metrics'; -import { getProjectActivity } from '../../../api/projectActivity'; +import { getProjectActivity, Analysis as IAnalysis } from '../../../api/projectActivity'; import PreviewGraph from '../../../components/preview-graph/PreviewGraph'; import { translate } from '../../../helpers/l10n'; -/*:: import type { Analysis as AnalysisType } from '../../projectActivity/types'; */ -/*:: import type { History, Metric } from '../types'; */ - -/*:: -type Props = { - branch?: string, - component: Object, - history: ?History, - qualifier: string -}; -*/ - -/*:: -type State = { - analyses: Array<AnalysisType>, - loading: boolean, - metrics: Array<Metric> -}; -*/ +import { Metric, Component } from '../../../app/types'; +import { History } from '../../../api/time-machine'; + +interface Props { + branch?: string; + component: Component; + history?: History; + qualifier: string; +} + +interface State { + analyses: IAnalysis[]; + loading: boolean; + metrics: Metric[]; +} const PAGE_SIZE = 3; -export default class AnalysesList extends React.PureComponent { - /*:: mounted: boolean; */ - /*:: props: Props; */ - state /*: State */ = { analyses: [], loading: true, metrics: [] }; +export default class AnalysesList extends React.PureComponent<Props, State> { + mounted: boolean; + state: State = { analyses: [], loading: true, metrics: [] }; componentDidMount() { this.mounted = true; this.fetchData(); } - componentDidUpdate(prevProps /*: Props */) { + componentDidUpdate(prevProps: Props) { if (prevProps.component !== this.props.component) { this.fetchData(); } @@ -79,7 +73,7 @@ export default class AnalysesList extends React.PureComponent { return component.breadcrumbs[current].key; }; - fetchData() { + fetchData = () => { this.setState({ loading: true }); Promise.all([ getProjectActivity({ @@ -89,13 +83,9 @@ export default class AnalysesList extends React.PureComponent { }), getAllMetrics() ]).then( - response => { + ([{ analyses }, metrics]) => { if (this.mounted) { - this.setState({ - analyses: response[0].analyses, - metrics: response[1], - loading: false - }); + this.setState({ analyses, metrics, loading: false }); } }, () => { @@ -104,9 +94,9 @@ export default class AnalysesList extends React.PureComponent { } } ); - } + }; - renderList(analyses /*: Array<AnalysisType> */) { + renderList(analyses: IAnalysis[]) { if (!analyses.length) { return <p className="spacer-top note">{translate('no_results')}</p>; } diff --git a/server/sonar-web/src/main/js/apps/overview/events/Analysis.js b/server/sonar-web/src/main/js/apps/overview/events/Analysis.tsx index 3addf419677..f80ce5646b8 100644 --- a/server/sonar-web/src/main/js/apps/overview/events/Analysis.js +++ b/server/sonar-web/src/main/js/apps/overview/events/Analysis.tsx @@ -17,27 +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 { sortBy } from 'lodash'; import Event from './Event'; import DateTooltipFormatter from '../../../components/intl/DateTooltipFormatter'; +import { Analysis as IAnalysis, Event as IEvent } from '../../../api/projectActivity'; import { translate } from '../../../helpers/l10n'; -/*:: import type { Analysis as AnalysisType, Event as EventType } from '../../projectActivity/types'; */ -/*:: -type Props = { - analysis: AnalysisType, - qualifier: string -}; -*/ +interface Props { + analysis: IAnalysis; + qualifier: string; +} -export default function Analysis(props /*: Props */) { - const { analysis } = props; - const sortedEvents /*: Array<EventType> */ = sortBy( +export default function Analysis({ analysis, ...props }: Props) { + const sortedEvents: Array<IEvent> = sortBy( analysis.events, // versions first - (event /*: EventType */) => (event.category === 'VERSION' ? 0 : 1), + (event: IEvent) => (event.category === 'VERSION' ? 0 : 1), // then the rest sorted by category 'category' ); diff --git a/server/sonar-web/src/main/js/apps/overview/events/Event.js b/server/sonar-web/src/main/js/apps/overview/events/Event.tsx index ca2982c0d0b..f90d318845a 100644 --- a/server/sonar-web/src/main/js/apps/overview/events/Event.js +++ b/server/sonar-web/src/main/js/apps/overview/events/Event.tsx @@ -17,19 +17,20 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; +import * as React from 'react'; import Tooltip from '../../../components/controls/Tooltip'; +import { Event as IEvent } from '../../../api/projectActivity'; import { translate } from '../../../helpers/l10n'; -/*:: import type { Event as EventType } from '../../projectActivity/types'; */ -export default function Event(props /*: { event: EventType } */) { - const { event } = props; +interface Props { + event: IEvent; +} +export default function Event({ event }: Props) { if (event.category === 'VERSION') { return ( - <Tooltip overlay={`${translate('version')} ${props.event.name}`} mouseEnterDelay={0.5}> - <span className="overview-analysis-event badge">{props.event.name}</span> + <Tooltip overlay={`${translate('version')} ${event.name}`} mouseEnterDelay={0.5}> + <span className="overview-analysis-event badge">{event.name}</span> </Tooltip> ); } diff --git a/server/sonar-web/src/main/js/apps/overview/events/__tests__/Analysis-test.js b/server/sonar-web/src/main/js/apps/overview/events/__tests__/Analysis-test.tsx index 7c6fdb452ad..16c481b42db 100644 --- a/server/sonar-web/src/main/js/apps/overview/events/__tests__/Analysis-test.js +++ b/server/sonar-web/src/main/js/apps/overview/events/__tests__/Analysis-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 Analysis from '../Analysis'; @@ -31,5 +31,5 @@ const ANALYSIS = { }; it('should sort the events with version first', () => { - expect(shallow(<Analysis analysis={ANALYSIS} />)).toMatchSnapshot(); + expect(shallow(<Analysis analysis={ANALYSIS} qualifier="TRK" />)).toMatchSnapshot(); }); diff --git a/server/sonar-web/src/main/js/apps/overview/events/__tests__/Event-test.js b/server/sonar-web/src/main/js/apps/overview/events/__tests__/Event-test.tsx index 496bf250aee..e15be764817 100644 --- a/server/sonar-web/src/main/js/apps/overview/events/__tests__/Event-test.js +++ b/server/sonar-web/src/main/js/apps/overview/events/__tests__/Event-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 Event from '../Event'; diff --git a/server/sonar-web/src/main/js/apps/overview/events/__tests__/__snapshots__/Analysis-test.js.snap b/server/sonar-web/src/main/js/apps/overview/events/__tests__/__snapshots__/Analysis-test.tsx.snap index a9296187266..a9296187266 100644 --- a/server/sonar-web/src/main/js/apps/overview/events/__tests__/__snapshots__/Analysis-test.js.snap +++ b/server/sonar-web/src/main/js/apps/overview/events/__tests__/__snapshots__/Analysis-test.tsx.snap diff --git a/server/sonar-web/src/main/js/apps/overview/events/__tests__/__snapshots__/Event-test.js.snap b/server/sonar-web/src/main/js/apps/overview/events/__tests__/__snapshots__/Event-test.tsx.snap index f6b5bdfc0e6..f6b5bdfc0e6 100644 --- a/server/sonar-web/src/main/js/apps/overview/events/__tests__/__snapshots__/Event-test.js.snap +++ b/server/sonar-web/src/main/js/apps/overview/events/__tests__/__snapshots__/Event-test.tsx.snap diff --git a/server/sonar-web/src/main/js/apps/overview/main/Coverage.js b/server/sonar-web/src/main/js/apps/overview/main/Coverage.js index d8f4a118c43..ecc08743527 100644 --- a/server/sonar-web/src/main/js/apps/overview/main/Coverage.js +++ b/server/sonar-web/src/main/js/apps/overview/main/Coverage.js @@ -19,7 +19,7 @@ */ import React from 'react'; import enhance from './enhance'; -import { DrilldownLink } from '../../../components/shared/drilldown-link'; +import DrilldownLink from '../../../components/shared/DrilldownLink'; import { getMetricName } from '../helpers/metrics'; import { formatMeasure, getPeriodValue } from '../../../helpers/measures'; import { translate } from '../../../helpers/l10n'; diff --git a/server/sonar-web/src/main/js/apps/overview/main/Duplications.js b/server/sonar-web/src/main/js/apps/overview/main/Duplications.js index 3a04c3cc306..e668ff8efc2 100644 --- a/server/sonar-web/src/main/js/apps/overview/main/Duplications.js +++ b/server/sonar-web/src/main/js/apps/overview/main/Duplications.js @@ -19,7 +19,7 @@ */ import React from 'react'; import enhance from './enhance'; -import { DrilldownLink } from '../../../components/shared/drilldown-link'; +import DrilldownLink from '../../../components/shared/DrilldownLink'; import { getMetricName } from '../helpers/metrics'; import { formatMeasure, getPeriodValue } from '../../../helpers/measures'; import { translate } from '../../../helpers/l10n'; diff --git a/server/sonar-web/src/main/js/apps/overview/main/enhance.js b/server/sonar-web/src/main/js/apps/overview/main/enhance.tsx index 8aac3e3985b..142cdf3eb90 100644 --- a/server/sonar-web/src/main/js/apps/overview/main/enhance.js +++ b/server/sonar-web/src/main/js/apps/overview/main/enhance.tsx @@ -17,9 +17,9 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import React from 'react'; +import * as React from 'react'; import { Link } from 'react-router'; -import { DrilldownLink } from '../../../components/shared/drilldown-link'; +import DrilldownLink from '../../../components/shared/DrilldownLink'; import BubblesIcon from '../../../components/icons-components/BubblesIcon'; import DateTimeFormatter from '../../../components/intl/DateTimeFormatter'; import HistoryIcon from '../../../components/icons-components/HistoryIcon'; @@ -28,11 +28,11 @@ import Timeline from '../components/Timeline'; import Tooltip from '../../../components/controls/Tooltip'; import { formatMeasure, - formatMeasureVariation, isDiffMetric, getPeriodValue, getShortType, - getRatingTooltip + getRatingTooltip, + MeasureEnhanced } from '../../../helpers/measures'; import { translateWithParameters, getLocalizedMetricName } from '../../../helpers/l10n'; import { getPeriodDate } from '../../../helpers/periods'; @@ -41,24 +41,43 @@ import { getComponentIssuesUrl, getMeasureHistoryUrl } from '../../../helpers/urls'; +import { Component } from '../../../app/types'; +import { History } from '../../../api/time-machine'; + +export interface EnhanceProps { + branch?: string; + component: Component; + measures: MeasureEnhanced[]; + leakPeriod?: { index: number; date?: string }; + history?: History; + historyStartDate?: Date; +} + +export interface ComposedProps extends EnhanceProps { + getValue: (measure: MeasureEnhanced) => string | undefined; + renderHeader: (domain: string, label: string) => React.ReactNode; + renderMeasure: (metricKey: string) => React.ReactNode; + renderRating: (metricKey: string) => React.ReactNode; + renderIssues: (metric: string, type: string) => React.ReactNode; + renderHistoryLink: (metricKey: string) => React.ReactNode; + renderTimeline: (metricKey: string, range: string, children?: React.ReactNode) => React.ReactNode; +} -export default function enhance(ComposedComponent) { - return class extends React.PureComponent { +export default function enhance(ComposedComponent: React.ComponentType<ComposedProps>) { + return class extends React.PureComponent<EnhanceProps> { static displayName = `enhance(${ComposedComponent.displayName})}`; - getValue = measure => { + getValue = (measure: MeasureEnhanced) => { const { leakPeriod } = this.props; - if (!measure) { - return 0; + return '0'; } - return isDiffMetric(measure.metric.key) - ? getPeriodValue(measure, leakPeriod.index) + ? getPeriodValue(measure, leakPeriod ? leakPeriod.index : 0) : measure.value; }; - renderHeader = (domain, label) => { + renderHeader = (domain: string, label: string) => { const { branch, component } = this.props; return ( <div className="overview-card-header"> @@ -74,11 +93,10 @@ export default function enhance(ComposedComponent) { ); }; - renderMeasure = metricKey => { + renderMeasure = (metricKey: string) => { const { branch, measures, component } = this.props; const measure = measures.find(measure => measure.metric.key === metricKey); - - if (measure == null) { + if (!measure) { return null; } @@ -100,32 +118,15 @@ export default function enhance(ComposedComponent) { ); }; - renderMeasureVariation = (metricKey, customLabel) => { - const NO_VALUE = '—'; - const { measures, leakPeriod } = this.props; - const measure = measures.find(measure => measure.metric.key === metricKey); - const periodValue = getPeriodValue(measure, leakPeriod.index); - const formatted = - periodValue != null - ? formatMeasureVariation(periodValue, getShortType(measure.metric.type)) - : NO_VALUE; - return ( - <div className="overview-domain-measure"> - <div className="overview-domain-measure-value">{formatted}</div> - - <div className="overview-domain-measure-label">{customLabel || measure.metric.name}</div> - </div> - ); - }; - - renderRating = metricKey => { + renderRating = (metricKey: string) => { const { branch, component, measures } = this.props; const measure = measures.find(measure => measure.metric.key === metricKey); if (!measure) { return null; } + const value = this.getValue(measure); - const title = getRatingTooltip(metricKey, value); + const title = value && getRatingTooltip(metricKey, value); return ( <Tooltip overlay={title} placement="top"> <div className="overview-domain-measure-sup"> @@ -141,16 +142,20 @@ export default function enhance(ComposedComponent) { ); }; - renderIssues = (metric, type) => { + renderIssues = (metric: string, type: string) => { const { branch, measures, component } = this.props; const measure = measures.find(measure => measure.metric.key === metric); + if (!measure) { + return null; + } + const value = this.getValue(measure); const params = { branch, resolved: 'false', types: type }; if (isDiffMetric(metric)) { Object.assign(params, { sinceLeakPeriod: 'true' }); } - const tooltip = ( + const tooltip = component.analysisDate && ( <DateTimeFormatter date={component.analysisDate}> {formattedAnalysisDate => ( <span> @@ -169,7 +174,7 @@ export default function enhance(ComposedComponent) { ); }; - renderHistoryLink = metricKey => { + renderHistoryLink = (metricKey: string) => { const linkClass = 'button button-small spacer-left overview-domain-measure-history-link'; return ( <Link @@ -180,7 +185,7 @@ export default function enhance(ComposedComponent) { ); }; - renderTimeline = (metricKey, range, children) => { + renderTimeline = (metricKey: string, range: 'before' | 'after', children?: React.ReactNode) => { if (!this.props.history) { return null; } @@ -188,10 +193,7 @@ export default function enhance(ComposedComponent) { if (!history) { return null; } - const props = { - history, - [range]: getPeriodDate(this.props.leakPeriod) - }; + const props = { history, [range]: getPeriodDate(this.props.leakPeriod) }; return ( <div className="overview-domain-timeline"> <Timeline {...props} /> @@ -208,7 +210,6 @@ export default function enhance(ComposedComponent) { renderHeader={this.renderHeader} renderHistoryLink={this.renderHistoryLink} renderMeasure={this.renderMeasure} - renderMeasureVariation={this.renderMeasureVariation} renderRating={this.renderRating} renderIssues={this.renderIssues} renderTimeline={this.renderTimeline} diff --git a/server/sonar-web/src/main/js/apps/overview/meta/Meta.js b/server/sonar-web/src/main/js/apps/overview/meta/Meta.tsx index 0422440aa99..bfffecd1f7c 100644 --- a/server/sonar-web/src/main/js/apps/overview/meta/Meta.js +++ b/server/sonar-web/src/main/js/apps/overview/meta/Meta.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 { connect } from 'react-redux'; import MetaKey from './MetaKey'; import MetaOrganizationKey from './MetaOrganizationKey'; @@ -29,17 +29,25 @@ import MetaSize from './MetaSize'; import MetaTags from './MetaTags'; import BadgesModal from '../badges/BadgesModal'; import { areThereCustomOrganizations, getGlobalSettingValue } from '../../../store/rootReducer'; -import { Visibility } from '../../../app/types'; - -const Meta = ({ - branch, - component, - history, - measures, - areThereCustomOrganizations, - onComponentChange, - onSonarCloud -}) => { +import { Visibility, Component } from '../../../app/types'; +import { History } from '../../../api/time-machine'; +import { MeasureEnhanced } from '../../../helpers/measures'; + +interface OwnProps { + branch?: string; + component: Component; + history?: History; + measures: MeasureEnhanced[]; + onComponentChange: (changes: {}) => void; +} + +interface StateToProps { + areThereCustomOrganizations: boolean; + onSonarCloud: boolean; +} + +export function Meta(props: OwnProps & StateToProps) { + const { branch, component, areThereCustomOrganizations } = props; const { qualifier, description, qualityProfiles, qualityGate, visibility } = component; const isProject = qualifier === 'TRK'; @@ -59,15 +67,15 @@ const Meta = ({ <div className="overview-meta-card overview-meta-description">{description}</div> )} - <MetaSize branch={branch} component={component} measures={measures} /> + <MetaSize branch={branch} component={component} measures={props.measures} /> - {isProject && <MetaTags component={component} onComponentChange={onComponentChange} />} + {isProject && <MetaTags component={component} onComponentChange={props.onComponentChange} />} <AnalysesList branch={branch} component={component} qualifier={component.qualifier} - history={history} + history={props.history} /> {shouldShowQualityGate && ( @@ -91,14 +99,14 @@ const Meta = ({ {hasOrganization && <MetaOrganizationKey component={component} />} - {onSonarCloud && + {props.onSonarCloud && isProject && !isPrivate && <BadgesModal branch={branch} project={component.key} />} </div> ); -}; +} -const mapStateToProps = state => { +const mapStateToProps = (state: any): StateToProps => { const sonarCloudSetting = getGlobalSettingValue(state, 'sonar.sonarcloud.enabled'); return { areThereCustomOrganizations: areThereCustomOrganizations(state), @@ -106,4 +114,4 @@ const mapStateToProps = state => { }; }; -export default connect(mapStateToProps)(Meta); +export default connect<StateToProps, {}, OwnProps>(mapStateToProps)(Meta); diff --git a/server/sonar-web/src/main/js/apps/overview/meta/MetaLink.js b/server/sonar-web/src/main/js/apps/overview/meta/MetaLink.tsx index a59e818bc16..b704420b5ed 100644 --- a/server/sonar-web/src/main/js/apps/overview/meta/MetaLink.js +++ b/server/sonar-web/src/main/js/apps/overview/meta/MetaLink.tsx @@ -17,43 +17,33 @@ * 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 { isProvided, isClickable } from '../../project-admin/links/utils'; import BugTrackerIcon from '../../../components/ui/BugTrackerIcon'; +import { ProjectLink } from '../../../api/projectLinks'; -/*:: -type Link = { - id: string, - name: string, - url: string, - type: string -}; -*/ +interface Props { + link: ProjectLink; +} -/*:: -type State = {| - expanded: boolean -|}; -*/ +interface State { + expanded: boolean; +} -export default class MetaLink extends React.PureComponent { - /*:: props: { - link: Link - }; -*/ +export default class MetaLink extends React.PureComponent<Props, State> { + state: State = { expanded: false }; - state /*: State */ = { - expanded: false + handleClick = (e: React.SyntheticEvent<HTMLAnchorElement>) => { + e.preventDefault(); + e.currentTarget.blur(); + this.setState((s: State) => ({ expanded: !s.expanded })); }; - handleClick = (e /*: Object */) => { - e.preventDefault(); - e.target.blur(); - this.setState((s /*: State */) => ({ expanded: !s.expanded })); + handleInputClick = (e: React.SyntheticEvent<HTMLInputElement>) => { + e.currentTarget.select(); }; - renderLinkIcon(link /*: Link */) { + renderLinkIcon = (link: ProjectLink) => { if (link.type === 'issue') { return <BugTrackerIcon />; } @@ -63,7 +53,7 @@ export default class MetaLink extends React.PureComponent { ) : ( <i className="icon-color-link icon-detach" /> ); - } + }; render() { const { link } = this.props; @@ -86,7 +76,7 @@ export default class MetaLink extends React.PureComponent { className="overview-key" value={link.url} readOnly={true} - onClick={e => e.target.select()} + onClick={this.handleInputClick} /> </div> )} diff --git a/server/sonar-web/src/main/js/apps/overview/meta/MetaLinks.js b/server/sonar-web/src/main/js/apps/overview/meta/MetaLinks.tsx index 7e2ca4c1761..66980dda926 100644 --- a/server/sonar-web/src/main/js/apps/overview/meta/MetaLinks.js +++ b/server/sonar-web/src/main/js/apps/overview/meta/MetaLinks.tsx @@ -17,25 +17,30 @@ * 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 * as React from 'react'; import MetaLink from './MetaLink'; -import { getProjectLinks } from '../../../api/projectLinks'; +import { getProjectLinks, ProjectLink } from '../../../api/projectLinks'; import { orderLinks } from '../../project-admin/links/utils'; +import { LightComponent } from '../../../app/types'; -export default class MetaLinks extends React.PureComponent { - static propTypes = { - component: PropTypes.object.isRequired - }; +interface Props { + component: LightComponent; +} + +interface State { + links?: ProjectLink[]; +} - state = {}; +export default class MetaLinks extends React.PureComponent<Props, State> { + mounted: boolean; + state: State = {}; componentDidMount() { this.mounted = true; this.loadLinks(); } - componentDidUpdate(prevProps) { + componentDidUpdate(prevProps: Props) { if (prevProps.component.key !== this.props.component.key) { this.loadLinks(); } @@ -45,18 +50,20 @@ export default class MetaLinks extends React.PureComponent { this.mounted = false; } - loadLinks() { - getProjectLinks(this.props.component.key).then(links => { - if (this.mounted) { - this.setState({ links }); - } - }); - } + loadLinks = () => + getProjectLinks(this.props.component.key).then( + links => { + if (this.mounted) { + this.setState({ links }); + } + }, + () => {} + ); render() { const { links } = this.state; - if (links == null || links.length === 0) { + if (!links || links.length === 0) { return null; } diff --git a/server/sonar-web/src/main/js/apps/overview/meta/MetaSize.js b/server/sonar-web/src/main/js/apps/overview/meta/MetaSize.tsx index 9d34ec90c2c..6465c861817 100644 --- a/server/sonar-web/src/main/js/apps/overview/meta/MetaSize.js +++ b/server/sonar-web/src/main/js/apps/overview/meta/MetaSize.tsx @@ -17,31 +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. */ -import React from 'react'; -import PropTypes from 'prop-types'; -import classNames from 'classnames'; -import { DrilldownLink } from '../../../components/shared/drilldown-link'; +import * as React from 'react'; +import * as classNames from 'classnames'; +import DrilldownLink from '../../../components/shared/DrilldownLink'; import LanguageDistributionContainer from '../../../components/charts/LanguageDistributionContainer'; import SizeRating from '../../../components/ui/SizeRating'; -import { formatMeasure } from '../../../helpers/measures'; +import { formatMeasure, MeasureEnhanced } from '../../../helpers/measures'; import { getMetricName } from '../helpers/metrics'; import { translate } from '../../../helpers/l10n'; +import { LightComponent } from '../../../app/types'; -export default class MetaSize extends React.PureComponent { - static propTypes = { - branch: PropTypes.string, - component: PropTypes.object.isRequired, - measures: PropTypes.array.isRequired - }; +interface Props { + branch?: string; + component: LightComponent; + measures: MeasureEnhanced[]; +} - renderLoC = ncloc => ( +export default class MetaSize extends React.PureComponent<Props> { + renderLoC = (ncloc: MeasureEnhanced) => ( <div id="overview-ncloc" className={classNames('overview-meta-size-ncloc', { 'is-half-width': this.props.component.qualifier === 'APP' })}> <span className="spacer-right"> - <SizeRating value={ncloc.value} /> + <SizeRating value={Number(ncloc.value)} /> </span> <DrilldownLink branch={this.props.branch} component={this.props.component.key} metric="ncloc"> {formatMeasure(ncloc.value, 'SHORT_INT')} diff --git a/server/sonar-web/src/main/js/apps/overview/meta/MetaTags.js b/server/sonar-web/src/main/js/apps/overview/meta/MetaTags.tsx index 409f3049e9a..f08c08252fe 100644 --- a/server/sonar-web/src/main/js/apps/overview/meta/MetaTags.js +++ b/server/sonar-web/src/main/js/apps/overview/meta/MetaTags.tsx @@ -17,48 +17,32 @@ * 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 { setProjectTags } from '../../../api/components'; import { translate } from '../../../helpers/l10n'; import TagsList from '../../../components/tags/TagsList'; import MetaTagsSelector from './MetaTagsSelector'; +import { BubblePopupPosition } from '../../../components/common/BubblePopup'; +import { Component } from '../../../app/types'; -/*:: -type Props = { - component: { - key: string, - tags: Array<string>, - configuration?: { - showSettings?: boolean - } - }, - onComponentChange: {} => void -}; -*/ +interface Props { + component: Component; + onComponentChange: (changes: {}) => void; +} -/*:: -type State = { - popupOpen: boolean, - popupPosition: { top: number, right: number } -}; -*/ +interface State { + popupOpen: boolean; + popupPosition: BubblePopupPosition; +} -export default class MetaTags extends React.PureComponent { - /*:: card: HTMLElement; */ - /*:: tagsList: HTMLElement; */ - /*:: tagsSelector: HTMLElement; */ - /*:: props: Props; */ - state /*: State */ = { - popupOpen: false, - popupPosition: { - top: 0, - right: 0 - } - }; +export default class MetaTags extends React.PureComponent<Props, State> { + card: HTMLDivElement | null; + tagsList: HTMLButtonElement | null; + tagsSelector: HTMLDivElement | null; + state: State = { popupOpen: false, popupPosition: { top: 0, right: 0 } }; componentDidMount() { - if (this.canUpdateTags()) { + if (this.canUpdateTags() && this.tagsList && this.card) { const buttonPos = this.tagsList.getBoundingClientRect(); const cardPos = this.card.getBoundingClientRect(); this.setState({ popupPosition: this.getPopupPos(buttonPos, cardPos) }); @@ -73,40 +57,35 @@ export default class MetaTags extends React.PureComponent { window.removeEventListener('click', this.handleOutsideClick); } - handleKey = (evt /*: KeyboardEvent */) => { + handleKey = (evt: KeyboardEvent) => { // Escape key if (evt.keyCode === 27) { this.setState({ popupOpen: false }); } }; - handleOutsideClick = (evt /*: SyntheticInputEvent */) => { - if (!this.tagsSelector || !this.tagsSelector.contains(evt.target)) { + handleOutsideClick = (evt: Event) => { + if (!this.tagsSelector || !this.tagsSelector.contains(evt.target as Node)) { this.setState({ popupOpen: false }); } }; - handleClick = (evt /*: MouseEvent */) => { + handleClick = (evt: React.SyntheticEvent<HTMLButtonElement>) => { evt.stopPropagation(); this.setState(state => ({ popupOpen: !state.popupOpen })); }; - canUpdateTags() { + canUpdateTags = () => { const { configuration } = this.props.component; return configuration && configuration.showSettings; - } + }; - getPopupPos( - eltPos /*: { height: number, width: number } */, - containerPos /*: { width: number } */ - ) { - return { - top: eltPos.height, - right: containerPos.width - eltPos.width - }; - } + getPopupPos = (eltPos: ClientRect, containerPos: ClientRect) => ({ + top: eltPos.height, + right: containerPos.width - eltPos.width + }); - handleSetProjectTags = (tags /*: Array<string> */) => { + handleSetProjectTags = (tags: string[]) => { setProjectTags({ project: this.props.component.key, tags: tags.join(',') }).then( () => this.props.onComponentChange({ tags }), () => {} @@ -114,8 +93,9 @@ export default class MetaTags extends React.PureComponent { }; render() { - const { tags, key } = this.props.component; + const { key } = this.props.component; const { popupOpen, popupPosition } = this.state; + const tags = this.props.component.tags || []; if (this.canUpdateTags()) { return ( diff --git a/server/sonar-web/src/main/js/apps/overview/meta/MetaTagsSelector.js b/server/sonar-web/src/main/js/apps/overview/meta/MetaTagsSelector.tsx index f728a15225d..2e2bb774a80 100644 --- a/server/sonar-web/src/main/js/apps/overview/meta/MetaTagsSelector.js +++ b/server/sonar-web/src/main/js/apps/overview/meta/MetaTagsSelector.tsx @@ -17,51 +17,44 @@ * 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 { without } from 'lodash'; import TagsSelector from '../../../components/tags/TagsSelector'; +import { BubblePopupPosition } from '../../../components/common/BubblePopup'; import { searchProjectTags } from '../../../api/components'; -/*:: -type Props = { - position: {}, - project: string, - selectedTags: Array<string>, - setProjectTags: (Array<string>) => void -}; -*/ +interface Props { + position: BubblePopupPosition; + project: string; + selectedTags: string[]; + setProjectTags: (tags: string[]) => void; +} -/*:: -type State = { - searchResult: Array<string> -}; -*/ +interface State { + searchResult: string[]; +} const LIST_SIZE = 10; -export default class MetaTagsSelector extends React.PureComponent { - /*:: props: Props; */ - state /*: State */ = { searchResult: [] }; +export default class MetaTagsSelector extends React.PureComponent<Props, State> { + state: State = { searchResult: [] }; componentDidMount() { this.onSearch(''); } - onSearch = (query /*: string */) => { + onSearch = (query: string) => { searchProjectTags({ q: query, ps: Math.min(this.props.selectedTags.length - 1 + LIST_SIZE, 100) - }).then(result => { - this.setState({ searchResult: result.tags }); - }); + }).then(result => this.setState({ searchResult: result.tags }), () => {}); }; - onSelect = (tag /*: string */) => { + onSelect = (tag: string) => { this.props.setProjectTags([...this.props.selectedTags, tag]); }; - onUnselect = (tag /*: string */) => { + onUnselect = (tag: string) => { this.props.setProjectTags(without(this.props.selectedTags, tag)); }; diff --git a/server/sonar-web/src/main/js/apps/overview/meta/__tests__/MetaLink-test.js b/server/sonar-web/src/main/js/apps/overview/meta/__tests__/MetaLink-test.tsx index 2499c2e3c36..0bea70841d8 100644 --- a/server/sonar-web/src/main/js/apps/overview/meta/__tests__/MetaLink-test.js +++ b/server/sonar-web/src/main/js/apps/overview/meta/__tests__/MetaLink-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 MetaLink from '../MetaLink'; import { click } from '../../../../helpers/testUtils'; diff --git a/server/sonar-web/src/main/js/apps/overview/meta/__tests__/MetaTags-test.js b/server/sonar-web/src/main/js/apps/overview/meta/__tests__/MetaTags-test.tsx index 9a621ce7aa9..1479225a022 100644 --- a/server/sonar-web/src/main/js/apps/overview/meta/__tests__/MetaTags-test.js +++ b/server/sonar-web/src/main/js/apps/overview/meta/__tests__/MetaTags-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 { click } from '../../../../helpers/testUtils'; import MetaTags from '../MetaTags'; @@ -27,7 +27,11 @@ const component = { tags: [], configuration: { showSettings: false - } + }, + organization: 'foo', + qualifier: 'TRK', + name: 'MyProject', + breadcrumbs: [] }; const componentWithTags = { @@ -35,25 +39,36 @@ const componentWithTags = { tags: ['foo', 'bar'], configuration: { showSettings: true - } + }, + organization: 'foo', + qualifier: 'TRK', + name: 'MySecondProject', + breadcrumbs: [] }; it('should render without tags and admin rights', () => { expect( - shallow(<MetaTags component={component} />, { disableLifecycleMethods: true }) + shallow(<MetaTags component={component} onComponentChange={jest.fn()} />, { + disableLifecycleMethods: true + }) ).toMatchSnapshot(); }); it('should render with tags and admin rights', () => { expect( - shallow(<MetaTags component={componentWithTags} />, { disableLifecycleMethods: true }) + shallow(<MetaTags component={componentWithTags} onComponentChange={jest.fn()} />, { + disableLifecycleMethods: true + }) ).toMatchSnapshot(); }); it('should open the tag selector on click', () => { - const wrapper = shallow(<MetaTags component={componentWithTags} />, { - disableLifecycleMethods: true - }); + const wrapper = shallow( + <MetaTags component={componentWithTags} onComponentChange={jest.fn()} />, + { + disableLifecycleMethods: true + } + ); expect(wrapper).toMatchSnapshot(); // open diff --git a/server/sonar-web/src/main/js/apps/overview/meta/__tests__/MetaTagsSelector-test.js b/server/sonar-web/src/main/js/apps/overview/meta/__tests__/MetaTagsSelector-test.tsx index 2f7a364d101..aefeb8d6fb6 100644 --- a/server/sonar-web/src/main/js/apps/overview/meta/__tests__/MetaTagsSelector-test.js +++ b/server/sonar-web/src/main/js/apps/overview/meta/__tests__/MetaTagsSelector-test.tsx @@ -18,7 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /* eslint-disable import/order, import/first */ -import React from 'react'; +import * as React from 'react'; import { mount, shallow } from 'enzyme'; import MetaTagsSelector from '../MetaTagsSelector'; @@ -35,9 +35,16 @@ jest.mock('lodash', () => { import { searchProjectTags } from '../../../../api/components'; it('searches tags on mount', () => { - searchProjectTags.mockImplementation(() => Promise.resolve({ tags: ['foo', 'bar'] })); + (searchProjectTags as jest.Mock).mockImplementation(() => + Promise.resolve({ tags: ['foo', 'bar'] }) + ); mount( - <MetaTagsSelector position={{}} project="foo" selectedTags={[]} setProjectTags={jest.fn()} /> + <MetaTagsSelector + position={{ top: 0, right: 0 }} + project="foo" + selectedTags={[]} + setProjectTags={jest.fn()} + /> ); expect(searchProjectTags).toBeCalledWith({ ps: 9, q: '' }); }); @@ -46,17 +53,18 @@ it('selects and deselects tags', () => { const setProjectTags = jest.fn(); const wrapper = shallow( <MetaTagsSelector - position={{}} + position={{ top: 0, right: 0 }} project="foo" selectedTags={['foo', 'bar']} setProjectTags={setProjectTags} /> ); - wrapper.find('TagsSelector').prop('onSelect')('baz'); + const tagSelect: any = wrapper.find('TagsSelector'); + tagSelect.prop('onSelect')('baz'); expect(setProjectTags).toHaveBeenLastCalledWith(['foo', 'bar', 'baz']); // note that the `selectedTags` is a prop and so it wasn't changed - wrapper.find('TagsSelector').prop('onUnselect')('bar'); + tagSelect.prop('onUnselect')('bar'); expect(setProjectTags).toHaveBeenLastCalledWith(['foo']); }); diff --git a/server/sonar-web/src/main/js/apps/overview/meta/__tests__/__snapshots__/MetaLink-test.js.snap b/server/sonar-web/src/main/js/apps/overview/meta/__tests__/__snapshots__/MetaLink-test.tsx.snap index 20c6a30481a..20c6a30481a 100644 --- a/server/sonar-web/src/main/js/apps/overview/meta/__tests__/__snapshots__/MetaLink-test.js.snap +++ b/server/sonar-web/src/main/js/apps/overview/meta/__tests__/__snapshots__/MetaLink-test.tsx.snap diff --git a/server/sonar-web/src/main/js/apps/overview/meta/__tests__/__snapshots__/MetaTags-test.js.snap b/server/sonar-web/src/main/js/apps/overview/meta/__tests__/__snapshots__/MetaTags-test.tsx.snap index 875302462d5..875302462d5 100644 --- a/server/sonar-web/src/main/js/apps/overview/meta/__tests__/__snapshots__/MetaTags-test.js.snap +++ b/server/sonar-web/src/main/js/apps/overview/meta/__tests__/__snapshots__/MetaTags-test.tsx.snap diff --git a/server/sonar-web/src/main/js/apps/overview/qualityGate/ApplicationQualityGate.js b/server/sonar-web/src/main/js/apps/overview/qualityGate/ApplicationQualityGate.tsx index 6528d5a44e8..3d8202f0d9d 100644 --- a/server/sonar-web/src/main/js/apps/overview/qualityGate/ApplicationQualityGate.js +++ b/server/sonar-web/src/main/js/apps/overview/qualityGate/ApplicationQualityGate.tsx @@ -17,47 +17,35 @@ * 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 { keyBy } from 'lodash'; import ApplicationQualityGateProject from './ApplicationQualityGateProject'; import Level from '../../../components/ui/Level'; -import { getApplicationQualityGate } from '../../../api/quality-gates'; +import { getApplicationQualityGate, ApplicationProject } from '../../../api/quality-gates'; import { translate } from '../../../helpers/l10n'; +import { LightComponent, Metric } from '../../../app/types'; -/*:: -type Props = { - component: { key: string, organization?: string } -}; -*/ +interface Props { + component: LightComponent; +} -/*:: type State = { - loading: boolean, - metrics?: { [string]: Object }, - projects?: Array<{ - conditions: Array<Object>, - key: string, - name: string, - status: string - }>, - status?: string + loading: boolean; + metrics?: { [key: string]: Metric }; + projects?: ApplicationProject[]; + status?: string; }; -*/ -export default class ApplicationQualityGate extends React.PureComponent { - /*:: mounted: boolean; */ - /*:: props: Props; */ - state /*: State */ = { - loading: true - }; +export default class ApplicationQualityGate extends React.PureComponent<Props, State> { + mounted: boolean; + state: State = { loading: true }; componentDidMount() { this.mounted = true; this.fetchDetails(); } - componentDidUpdate(prevProps /*: Props */) { + componentDidUpdate(prevProps: Props) { if (prevProps.component.key !== this.props.component.key) { this.fetchDetails(); } @@ -103,21 +91,22 @@ export default class ApplicationQualityGate extends React.PureComponent { {status != null && <Level level={status} />} </h2> - {projects != null && ( - <div - id="overview-quality-gate-conditions-list" - className="overview-quality-gate-conditions-list clearfix"> - {projects - .filter(project => project.status !== 'OK') - .map(project => ( - <ApplicationQualityGateProject - key={project.key} - metrics={metrics} - project={project} - /> - ))} - </div> - )} + {projects && + metrics && ( + <div + id="overview-quality-gate-conditions-list" + className="overview-quality-gate-conditions-list clearfix"> + {projects + .filter(project => project.status !== 'OK') + .map(project => ( + <ApplicationQualityGateProject + key={project.key} + metrics={metrics} + project={project} + /> + ))} + </div> + )} </div> ); } diff --git a/server/sonar-web/src/main/js/apps/overview/qualityGate/ApplicationQualityGateProject.js b/server/sonar-web/src/main/js/apps/overview/qualityGate/ApplicationQualityGateProject.tsx index 451c808abc9..388b77005ee 100644 --- a/server/sonar-web/src/main/js/apps/overview/qualityGate/ApplicationQualityGateProject.js +++ b/server/sonar-web/src/main/js/apps/overview/qualityGate/ApplicationQualityGateProject.tsx @@ -17,49 +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 * as classNames from 'classnames'; import { Link } from 'react-router'; -import classNames from 'classnames'; import { getLocalizedMetricName, translate } from '../../../helpers/l10n'; import { formatMeasure, isDiffMetric } from '../../../helpers/measures'; import { getProjectUrl } from '../../../helpers/urls'; import './ApplicationQualityGateProject.css'; +import { Metric } from '../../../app/types'; +import { ApplicationProject, ConditionAnalysis } from '../../../api/quality-gates'; -/*:: -type Condition = { - comparator: string, - errorThreshold?: string, - metric: string, - onLeak: boolean, - status: string, - value: string, - warningThreshold?: string -}; -*/ - -/*:: -type Props = { - metrics: { - [string]: { - key: string, - name: string, - type: string - } - }, - project: { - conditions: Array<Condition>, - key: string, - name: string, - status: string - } -}; -*/ - -export default class ApplicationQualityGateProject extends React.PureComponent { - /*:: props: Props; */ +interface Props { + metrics: { [key: string]: Metric }; + project: ApplicationProject; +} - renderCondition = (condition /*: Condition */) => { +export default class ApplicationQualityGateProject extends React.PureComponent<Props> { + renderCondition = (condition: ConditionAnalysis) => { const metric = this.props.metrics[condition.metric]; const metricName = getLocalizedMetricName(metric); const threshold = condition.errorThreshold || condition.warningThreshold; diff --git a/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGateCondition.js b/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGateCondition.js index 71eeccc2932..3301c33ef6f 100644 --- a/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGateCondition.js +++ b/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGateCondition.js @@ -21,7 +21,7 @@ import React from 'react'; import classNames from 'classnames'; import { Link } from 'react-router'; -import { DrilldownLink } from '../../../components/shared/drilldown-link'; +import DrilldownLink from '../../../components/shared/DrilldownLink'; import Measure from '../../../components/measure/Measure'; import IssueTypeIcon from '../../../components/ui/IssueTypeIcon'; import { getPeriodValue, isDiffMetric, formatMeasure } from '../../../helpers/measures'; @@ -146,7 +146,12 @@ export default class QualityGateCondition extends React.PureComponent { return this.wrapWithLink( <div className="overview-quality-gate-condition-container"> <div className="overview-quality-gate-condition-value"> - <Measure measure={{ ...measure, value: actual, leak: actual }} decimals={decimals} /> + <Measure + decimals={decimals} + value={actual} + metricKey={measure.metric.key} + metricType={measure.metric.type} + /> </div> <div> diff --git a/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/ApplicationQualityGate-test.js b/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/ApplicationQualityGate-test.tsx index 5d6170c69ea..fc15b30e10b 100644 --- a/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/ApplicationQualityGate-test.js +++ b/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/ApplicationQualityGate-test.tsx @@ -17,13 +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 * as React from 'react'; import { shallow } from 'enzyme'; import ApplicationQualityGate from '../ApplicationQualityGate'; it('renders', () => { - const wrapper = shallow(<ApplicationQualityGate component={{ key: 'foo' }} />); + const wrapper = shallow( + <ApplicationQualityGate component={{ key: 'foo', organization: 'foo', qualifier: 'TRK' }} /> + ); expect(wrapper).toMatchSnapshot(); wrapper.setState({ loading: false, diff --git a/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/ApplicationQualityGateProject-test.js b/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/ApplicationQualityGateProject-test.tsx index 87d7f69861d..3f7904c67e6 100644 --- a/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/ApplicationQualityGateProject-test.js +++ b/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/ApplicationQualityGateProject-test.tsx @@ -17,8 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; +import * as React from 'react'; import { shallow } from 'enzyme'; import ApplicationQualityGateProject from '../ApplicationQualityGateProject'; diff --git a/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/ApplicationQualityGate-test.js.snap b/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/ApplicationQualityGate-test.tsx.snap index 5a7f39cf4d9..5a7f39cf4d9 100644 --- a/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/ApplicationQualityGate-test.js.snap +++ b/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/ApplicationQualityGate-test.tsx.snap diff --git a/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/ApplicationQualityGateProject-test.js.snap b/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/ApplicationQualityGateProject-test.tsx.snap index 64d2bf86cfb..64d2bf86cfb 100644 --- a/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/ApplicationQualityGateProject-test.js.snap +++ b/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/ApplicationQualityGateProject-test.tsx.snap diff --git a/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/QualityGateCondition-test.js.snap b/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/QualityGateCondition-test.js.snap index 2c31f8e7d5b..e286b3da99d 100644 --- a/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/QualityGateCondition-test.js.snap +++ b/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/QualityGateCondition-test.js.snap @@ -26,23 +26,9 @@ exports[`new_maintainability_rating 1`] = ` > <Measure decimals={null} - measure={ - Object { - "leak": "3", - "metric": Object { - "key": "new_maintainability_rating", - "name": "new_maintainability_rating", - "type": "RATING", - }, - "periods": Array [ - Object { - "index": 1, - "value": "3", - }, - ], - "value": "3", - } - } + metricKey="new_maintainability_rating" + metricType="RATING" + value="3" /> </div> <div> @@ -82,23 +68,9 @@ exports[`new_open_issues 1`] = ` > <Measure decimals={null} - measure={ - Object { - "leak": "10", - "metric": Object { - "key": "new_open_issues", - "name": "new_open_issues", - "type": "INT", - }, - "periods": Array [ - Object { - "index": 1, - "value": "10", - }, - ], - "value": "10", - } - } + metricKey="new_open_issues" + metricType="INT" + value="10" /> </div> <div> @@ -150,23 +122,9 @@ exports[`new_reliability_rating 1`] = ` > <Measure decimals={null} - measure={ - Object { - "leak": "3", - "metric": Object { - "key": "new_reliability_rating", - "name": "new_reliability_rating", - "type": "RATING", - }, - "periods": Array [ - Object { - "index": 1, - "value": "3", - }, - ], - "value": "3", - } - } + metricKey="new_reliability_rating" + metricType="RATING" + value="3" /> </div> <div> @@ -218,23 +176,9 @@ exports[`new_security_rating 1`] = ` > <Measure decimals={null} - measure={ - Object { - "leak": "3", - "metric": Object { - "key": "new_security_rating", - "name": "new_security_rating", - "type": "RATING", - }, - "periods": Array [ - Object { - "index": 1, - "value": "3", - }, - ], - "value": "3", - } - } + metricKey="new_security_rating" + metricType="RATING" + value="3" /> </div> <div> @@ -274,17 +218,9 @@ exports[`open_issues 1`] = ` > <Measure decimals={null} - measure={ - Object { - "leak": "10", - "metric": Object { - "key": "open_issues", - "name": "Open open_issues", - "type": "INT", - }, - "value": "10", - } - } + metricKey="open_issues" + metricType="INT" + value="10" /> </div> <div> @@ -335,17 +271,9 @@ exports[`reliability_rating 1`] = ` > <Measure decimals={null} - measure={ - Object { - "leak": "3", - "metric": Object { - "key": "reliability_rating", - "name": "reliability_rating", - "type": "RATING", - }, - "value": "3", - } - } + metricKey="reliability_rating" + metricType="RATING" + value="3" /> </div> <div> @@ -396,17 +324,9 @@ exports[`security_rating 1`] = ` > <Measure decimals={null} - measure={ - Object { - "leak": "3", - "metric": Object { - "key": "security_rating", - "name": "security_rating", - "type": "RATING", - }, - "value": "3", - } - } + metricKey="security_rating" + metricType="RATING" + value="3" /> </div> <div> @@ -457,17 +377,9 @@ exports[`should work with branch 1`] = ` > <Measure decimals={null} - measure={ - Object { - "leak": "3", - "metric": Object { - "key": "new_maintainability_rating", - "name": "new_maintainability_rating", - "type": "RATING", - }, - "value": "3", - } - } + metricKey="new_maintainability_rating" + metricType="RATING" + value="3" /> </div> <div> @@ -517,17 +429,9 @@ exports[`sqale_rating 1`] = ` > <Measure decimals={null} - measure={ - Object { - "leak": "3", - "metric": Object { - "key": "sqale_rating", - "name": "sqale_rating", - "type": "RATING", - }, - "value": "3", - } - } + metricKey="sqale_rating" + metricType="RATING" + value="3" /> </div> <div> diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/Effort.tsx b/server/sonar-web/src/main/js/apps/portfolio/components/Effort.tsx index 69bd9c4b254..4029dbe7a38 100644 --- a/server/sonar-web/src/main/js/apps/portfolio/components/Effort.tsx +++ b/server/sonar-web/src/main/js/apps/portfolio/components/Effort.tsx @@ -42,11 +42,11 @@ export default function Effort({ component, effort, metricKey }: Props) { <Link to={getComponentDrilldownUrl(component, metricKey)}> <span> <Measure - measure={{ - metric: { key: 'projects', type: 'SHORT_INT' }, - value: String(effort.projects) - }} - />{' '} + className="little-spacer-right" + metricKey="projects" + metricType="SHORT_INT" + value={String(effort.projects)} + /> {translate('projects_')} </span> </Link> diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/ReleasabilityBox.tsx b/server/sonar-web/src/main/js/apps/portfolio/components/ReleasabilityBox.tsx index 5a57b31cd9d..ff7a2dd2f15 100644 --- a/server/sonar-web/src/main/js/apps/portfolio/components/ReleasabilityBox.tsx +++ b/server/sonar-web/src/main/js/apps/portfolio/components/ReleasabilityBox.tsx @@ -55,8 +55,11 @@ export default function ReleasabilityBox({ component, measures }: Props) { <Link to={getComponentDrilldownUrl(component, 'alert_status')}> <span> <Measure - measure={{ metric: { key: 'projects', type: 'SHORT_INT' }, value: effort }} - />{' '} + className="little-spacer-right" + metricKey="projects" + metricType="SHORT_INT" + value={effort} + /> {Number(effort) === 1 ? 'project' : 'projects'} </span> </Link>{' '} diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/Summary.tsx b/server/sonar-web/src/main/js/apps/portfolio/components/Summary.tsx index 3279b530d70..75ef3f32723 100644 --- a/server/sonar-web/src/main/js/apps/portfolio/components/Summary.tsx +++ b/server/sonar-web/src/main/js/apps/portfolio/components/Summary.tsx @@ -41,9 +41,7 @@ export default function Summary({ component, measures }: Props) { <li> <div className="portfolio-measure-secondary-value"> <Link to={getComponentDrilldownUrl(component.key, 'projects')}> - <Measure - measure={{ metric: { key: 'projects', type: 'SHORT_INT' }, value: projects }} - /> + <Measure metricKey="projects" metricType="SHORT_INT" value={projects} /> </Link> </div> <div className="spacer-top text-muted">{translate('projects')}</div> @@ -51,7 +49,7 @@ export default function Summary({ component, measures }: Props) { <li> <div className="portfolio-measure-secondary-value"> <Link to={getComponentDrilldownUrl(component.key, 'ncloc')}> - <Measure measure={{ metric: { key: 'ncloc', type: 'SHORT_INT' }, value: ncloc }} /> + <Measure metricKey="ncloc" metricType="SHORT_INT" value={ncloc} /> </Link> </div> <div className="spacer-top text-muted">{translate('metric.ncloc.name')}</div> diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/WorstProjects.tsx b/server/sonar-web/src/main/js/apps/portfolio/components/WorstProjects.tsx index 3e297104882..2dc04f3278b 100644 --- a/server/sonar-web/src/main/js/apps/portfolio/components/WorstProjects.tsx +++ b/server/sonar-web/src/main/js/apps/portfolio/components/WorstProjects.tsx @@ -110,7 +110,7 @@ export default function WorstProjects({ component, subComponents, total }: Props function renderCell(measures: { [key: string]: string | undefined }, metric: string, type: string) { return ( <td className="text-center"> - <Measure measure={{ metric: { key: metric, type }, value: measures[metric] }} /> + <Measure metricKey={metric} metricType={type} value={measures[metric]} /> </td> ); } @@ -121,12 +121,7 @@ function renderNcloc(measures: { [key: string]: string | undefined }, maxLoc: nu return ( <td className="text-right"> <span className="note"> - <Measure - measure={{ - metric: { key: 'ncloc', type: 'SHORT_INT' }, - value: measures['ncloc'] - }} - /> + <Measure metricKey="ncloc" metricType="SHORT_INT" value={measures['ncloc']} /> </span> {maxLoc > 0 && ( <svg width="50" height="16" className="spacer-left"> diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Effort-test.tsx.snap b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Effort-test.tsx.snap index 989531cad1e..8918c9e4b99 100644 --- a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Effort-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Effort-test.tsx.snap @@ -25,17 +25,11 @@ exports[`renders 1`] = ` > <span> <Measure - measure={ - Object { - "metric": Object { - "key": "projects", - "type": "SHORT_INT", - }, - "value": "3", - } - } + className="little-spacer-right" + metricKey="projects" + metricType="SHORT_INT" + value="3" /> - projects_ </span> </Link>, diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/ReleasabilityBox-test.tsx.snap b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/ReleasabilityBox-test.tsx.snap index 3db58b7d539..c2120360b56 100644 --- a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/ReleasabilityBox-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/ReleasabilityBox-test.tsx.snap @@ -50,17 +50,11 @@ exports[`renders 1`] = ` > <span> <Measure - measure={ - Object { - "metric": Object { - "key": "projects", - "type": "SHORT_INT", - }, - "value": "7", - } - } + className="little-spacer-right" + metricKey="projects" + metricType="SHORT_INT" + value="7" /> - projects </span> </Link> diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Summary-test.tsx.snap b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Summary-test.tsx.snap index f8f2706777d..149c818b286 100644 --- a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Summary-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Summary-test.tsx.snap @@ -32,15 +32,9 @@ exports[`renders 1`] = ` } > <Measure - measure={ - Object { - "metric": Object { - "key": "projects", - "type": "SHORT_INT", - }, - "value": "15", - } - } + metricKey="projects" + metricType="SHORT_INT" + value="15" /> </Link> </div> @@ -69,15 +63,9 @@ exports[`renders 1`] = ` } > <Measure - measure={ - Object { - "metric": Object { - "key": "ncloc", - "type": "SHORT_INT", - }, - "value": "1234", - } - } + metricKey="ncloc" + metricType="SHORT_INT" + value="1234" /> </Link> </div> diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/WorstProjects-test.tsx.snap b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/WorstProjects-test.tsx.snap index c36279d6709..736ab248060 100644 --- a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/WorstProjects-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/WorstProjects-test.tsx.snap @@ -70,60 +70,36 @@ exports[`renders 1`] = ` className="text-center" > <Measure - measure={ - Object { - "metric": Object { - "key": "releasability_rating", - "type": "RATING", - }, - "value": "3", - } - } + metricKey="releasability_rating" + metricType="RATING" + value="3" /> </td> <td className="text-center" > <Measure - measure={ - Object { - "metric": Object { - "key": "reliability_rating", - "type": "RATING", - }, - "value": "2", - } - } + metricKey="reliability_rating" + metricType="RATING" + value="2" /> </td> <td className="text-center" > <Measure - measure={ - Object { - "metric": Object { - "key": "security_rating", - "type": "RATING", - }, - "value": "1", - } - } + metricKey="security_rating" + metricType="RATING" + value="1" /> </td> <td className="text-center" > <Measure - measure={ - Object { - "metric": Object { - "key": "sqale_rating", - "type": "RATING", - }, - "value": "4", - } - } + metricKey="sqale_rating" + metricType="RATING" + value="4" /> </td> <td @@ -133,15 +109,9 @@ exports[`renders 1`] = ` className="note" > <Measure - measure={ - Object { - "metric": Object { - "key": "ncloc", - "type": "SHORT_INT", - }, - "value": "200", - } - } + metricKey="ncloc" + metricType="SHORT_INT" + value="200" /> </span> <svg @@ -188,60 +158,36 @@ exports[`renders 1`] = ` className="text-center" > <Measure - measure={ - Object { - "metric": Object { - "key": "alert_status", - "type": "LEVEL", - }, - "value": "ERROR", - } - } + metricKey="alert_status" + metricType="LEVEL" + value="ERROR" /> </td> <td className="text-center" > <Measure - measure={ - Object { - "metric": Object { - "key": "reliability_rating", - "type": "RATING", - }, - "value": "2", - } - } + metricKey="reliability_rating" + metricType="RATING" + value="2" /> </td> <td className="text-center" > <Measure - measure={ - Object { - "metric": Object { - "key": "security_rating", - "type": "RATING", - }, - "value": "1", - } - } + metricKey="security_rating" + metricType="RATING" + value="1" /> </td> <td className="text-center" > <Measure - measure={ - Object { - "metric": Object { - "key": "sqale_rating", - "type": "RATING", - }, - "value": "4", - } - } + metricKey="sqale_rating" + metricType="RATING" + value="4" /> </td> <td @@ -251,15 +197,9 @@ exports[`renders 1`] = ` className="note" > <Measure - measure={ - Object { - "metric": Object { - "key": "ncloc", - "type": "SHORT_INT", - }, - "value": "100", - } - } + metricKey="ncloc" + metricType="SHORT_INT" + value="100" /> </span> <svg @@ -306,60 +246,36 @@ exports[`renders 1`] = ` className="text-center" > <Measure - measure={ - Object { - "metric": Object { - "key": "alert_status", - "type": "LEVEL", - }, - "value": "WARN", - } - } + metricKey="alert_status" + metricType="LEVEL" + value="WARN" /> </td> <td className="text-center" > <Measure - measure={ - Object { - "metric": Object { - "key": "reliability_rating", - "type": "RATING", - }, - "value": "2", - } - } + metricKey="reliability_rating" + metricType="RATING" + value="2" /> </td> <td className="text-center" > <Measure - measure={ - Object { - "metric": Object { - "key": "security_rating", - "type": "RATING", - }, - "value": "1", - } - } + metricKey="security_rating" + metricType="RATING" + value="1" /> </td> <td className="text-center" > <Measure - measure={ - Object { - "metric": Object { - "key": "sqale_rating", - "type": "RATING", - }, - "value": "4", - } - } + metricKey="sqale_rating" + metricType="RATING" + value="4" /> </td> <td @@ -369,15 +285,9 @@ exports[`renders 1`] = ` className="note" > <Measure - measure={ - Object { - "metric": Object { - "key": "ncloc", - "type": "SHORT_INT", - }, - "value": "150", - } - } + metricKey="ncloc" + metricType="SHORT_INT" + value="150" /> </span> <svg diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppContainer.js b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppContainer.js index 25157da7d59..51d28646604 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppContainer.js +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppContainer.js @@ -198,7 +198,7 @@ export default class ProjectActivityAppContainer extends React.PureComponent { value: analysis.value })) })), - throwGlobalError + () => {} ); }; diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeakMeasures.tsx b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeakMeasures.tsx index 4efd5855aff..50c440bda9c 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeakMeasures.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeakMeasures.tsx @@ -37,10 +37,9 @@ export default function ProjectCardLeakMeasures({ measures }: Props) { <div className="project-card-measure-number"> <Measure className="spacer-right" - measure={{ - metric: { key: 'new_bugs', type: 'SHORT_INT' }, - leak: measures['new_bugs'] - }} + metricKey="new_bugs" + metricType="SHORT_INT" + value={measures['new_bugs']} /> <Rating value={measures['new_reliability_rating']} /> </div> @@ -56,10 +55,9 @@ export default function ProjectCardLeakMeasures({ measures }: Props) { <div className="project-card-measure-number"> <Measure className="spacer-right" - measure={{ - metric: { key: 'new_vulnerabilities', type: 'SHORT_INT' }, - leak: measures['new_vulnerabilities'] - }} + metricKey="new_vulnerabilities" + metricType="SHORT_INT" + value={measures['new_vulnerabilities']} /> <Rating value={measures['new_security_rating']} /> </div> @@ -75,10 +73,9 @@ export default function ProjectCardLeakMeasures({ measures }: Props) { <div className="project-card-measure-number"> <Measure className="spacer-right" - measure={{ - metric: { key: 'new_code_smells', type: 'SHORT_INT' }, - leak: measures['new_code_smells'] - }} + metricKey="new_code_smells" + metricType="SHORT_INT" + value={measures['new_code_smells']} /> <Rating value={measures['new_maintainability_rating']} /> </div> @@ -93,10 +90,9 @@ export default function ProjectCardLeakMeasures({ measures }: Props) { <div className="project-card-measure-inner"> <div className="project-card-measure-number"> <Measure - measure={{ - metric: { key: 'new_coverage', type: 'PERCENT' }, - leak: measures['new_coverage'] - }} + metricKey="new_coverage" + metricType="PERCENT" + value={measures['new_coverage']} /> </div> <div className="project-card-measure-label">{translate('metric.coverage.name')}</div> @@ -107,10 +103,9 @@ export default function ProjectCardLeakMeasures({ measures }: Props) { <div className="project-card-measure-inner"> <div className="project-card-measure-number"> <Measure - measure={{ - metric: { key: 'new_duplicated_lines_density', type: 'PERCENT' }, - leak: measures['new_duplicated_lines_density'] - }} + metricKey="new_duplicated_lines_density" + metricType="PERCENT" + value={measures['new_duplicated_lines_density']} /> </div> <div className="project-card-measure-label"> @@ -122,12 +117,7 @@ export default function ProjectCardLeakMeasures({ measures }: Props) { <div className="project-card-measure smaller-card project-card-ncloc" data-key="new_lines"> <div className="project-card-measure-inner"> <div className="project-card-measure-number"> - <Measure - measure={{ - metric: { key: 'new_lines', type: 'SHORT_INT' }, - leak: measures['new_lines'] - }} - /> + <Measure metricKey="new_lines" metricType="SHORT_INT" value={measures['new_lines']} /> </div> <div className="project-card-measure-label">{translate('metric.lines.name')}</div> </div> diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverallMeasures.tsx b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverallMeasures.tsx index 63dba2d25f4..e11c8e5b80d 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverallMeasures.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverallMeasures.tsx @@ -45,10 +45,9 @@ export default function ProjectCardOverallMeasures({ measures }: Props) { <div className="project-card-measure-number"> <Measure className="spacer-right" - measure={{ - metric: { key: 'bugs', type: 'SHORT_INT' }, - value: measures['bugs'] - }} + metricKey="bugs" + metricType="SHORT_INT" + value={measures['bugs']} /> <Rating value={measures['reliability_rating']} /> </div> @@ -64,10 +63,9 @@ export default function ProjectCardOverallMeasures({ measures }: Props) { <div className="project-card-measure-number"> <Measure className="spacer-right" - measure={{ - metric: { key: 'vulnerabilities', type: 'SHORT_INT' }, - value: measures['vulnerabilities'] - }} + metricKey="vulnerabilities" + metricType="SHORT_INT" + value={measures['vulnerabilities']} /> <Rating value={measures['security_rating']} /> </div> @@ -83,10 +81,9 @@ export default function ProjectCardOverallMeasures({ measures }: Props) { <div className="project-card-measure-number"> <Measure className="spacer-right" - measure={{ - metric: { key: 'code_smells', type: 'SHORT_INT' }, - value: measures['code_smells'] - }} + metricKey="code_smells" + metricType="SHORT_INT" + value={measures['code_smells']} /> <Rating value={measures['sqale_rating']} /> </div> @@ -105,12 +102,7 @@ export default function ProjectCardOverallMeasures({ measures }: Props) { <CoverageRating value={measures['coverage']} /> </span> )} - <Measure - measure={{ - metric: { key: 'coverage', type: 'PERCENT' }, - value: measures['coverage'] - }} - /> + <Measure metricKey="coverage" metricType="PERCENT" value={measures['coverage']} /> </div> <div className="project-card-measure-label">{translate('metric.coverage.name')}</div> </div> @@ -125,10 +117,9 @@ export default function ProjectCardOverallMeasures({ measures }: Props) { </span> )} <Measure - measure={{ - metric: { key: 'duplicated_lines_density', type: 'PERCENT' }, - value: measures['duplicated_lines_density'] - }} + metricKey="duplicated_lines_density" + metricType="PERCENT" + value={measures['duplicated_lines_density']} /> </div> <div className="project-card-measure-label"> @@ -141,12 +132,7 @@ export default function ProjectCardOverallMeasures({ measures }: Props) { <div className="project-card-measure project-card-ncloc" data-key="ncloc"> <div className="project-card-measure-inner pull-right"> <div className="project-card-measure-number"> - <Measure - measure={{ - metric: { key: 'ncloc', type: 'SHORT_INT' }, - value: measures['ncloc'] - }} - /> + <Measure metricKey="ncloc" metricType="SHORT_INT" value={measures['ncloc']} /> <span className="spacer-left"> <SizeRating value={Number(measures['ncloc'])} /> </span> diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLeakMeasures-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLeakMeasures-test.tsx.snap index 0ccdace41b1..5dbdbd4a074 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLeakMeasures-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLeakMeasures-test.tsx.snap @@ -16,15 +16,9 @@ exports[`should render correctly with all data 1`] = ` > <Measure className="spacer-right" - measure={ - Object { - "leak": "8", - "metric": Object { - "key": "new_bugs", - "type": "SHORT_INT", - }, - } - } + metricKey="new_bugs" + metricType="SHORT_INT" + value="8" /> <Rating value="1.0" @@ -52,15 +46,9 @@ exports[`should render correctly with all data 1`] = ` > <Measure className="spacer-right" - measure={ - Object { - "leak": "2", - "metric": Object { - "key": "new_vulnerabilities", - "type": "SHORT_INT", - }, - } - } + metricKey="new_vulnerabilities" + metricType="SHORT_INT" + value="2" /> <Rating value="2.0" @@ -88,15 +76,9 @@ exports[`should render correctly with all data 1`] = ` > <Measure className="spacer-right" - measure={ - Object { - "leak": "0", - "metric": Object { - "key": "new_code_smells", - "type": "SHORT_INT", - }, - } - } + metricKey="new_code_smells" + metricType="SHORT_INT" + value="0" /> <Rating value="1.0" @@ -123,15 +105,9 @@ exports[`should render correctly with all data 1`] = ` className="project-card-measure-number" > <Measure - measure={ - Object { - "leak": "26.55", - "metric": Object { - "key": "new_coverage", - "type": "PERCENT", - }, - } - } + metricKey="new_coverage" + metricType="PERCENT" + value="26.55" /> </div> <div @@ -152,15 +128,9 @@ exports[`should render correctly with all data 1`] = ` className="project-card-measure-number" > <Measure - measure={ - Object { - "leak": "0.55", - "metric": Object { - "key": "new_duplicated_lines_density", - "type": "PERCENT", - }, - } - } + metricKey="new_duplicated_lines_density" + metricType="PERCENT" + value="0.55" /> </div> <div @@ -181,15 +151,9 @@ exports[`should render correctly with all data 1`] = ` className="project-card-measure-number" > <Measure - measure={ - Object { - "leak": "87", - "metric": Object { - "key": "new_lines", - "type": "SHORT_INT", - }, - } - } + metricKey="new_lines" + metricType="SHORT_INT" + value="87" /> </div> <div @@ -218,15 +182,9 @@ exports[`should render no data style new coverage, new duplications and new line > <Measure className="spacer-right" - measure={ - Object { - "leak": "8", - "metric": Object { - "key": "new_bugs", - "type": "SHORT_INT", - }, - } - } + metricKey="new_bugs" + metricType="SHORT_INT" + value="8" /> <Rating value="1.0" @@ -254,15 +212,9 @@ exports[`should render no data style new coverage, new duplications and new line > <Measure className="spacer-right" - measure={ - Object { - "leak": "2", - "metric": Object { - "key": "new_vulnerabilities", - "type": "SHORT_INT", - }, - } - } + metricKey="new_vulnerabilities" + metricType="SHORT_INT" + value="2" /> <Rating value="2.0" @@ -290,15 +242,9 @@ exports[`should render no data style new coverage, new duplications and new line > <Measure className="spacer-right" - measure={ - Object { - "leak": "0", - "metric": Object { - "key": "new_code_smells", - "type": "SHORT_INT", - }, - } - } + metricKey="new_code_smells" + metricType="SHORT_INT" + value="0" /> <Rating value="1.0" @@ -325,15 +271,8 @@ exports[`should render no data style new coverage, new duplications and new line className="project-card-measure-number" > <Measure - measure={ - Object { - "leak": undefined, - "metric": Object { - "key": "new_coverage", - "type": "PERCENT", - }, - } - } + metricKey="new_coverage" + metricType="PERCENT" /> </div> <div @@ -354,15 +293,8 @@ exports[`should render no data style new coverage, new duplications and new line className="project-card-measure-number" > <Measure - measure={ - Object { - "leak": undefined, - "metric": Object { - "key": "new_duplicated_lines_density", - "type": "PERCENT", - }, - } - } + metricKey="new_duplicated_lines_density" + metricType="PERCENT" /> </div> <div @@ -383,15 +315,8 @@ exports[`should render no data style new coverage, new duplications and new line className="project-card-measure-number" > <Measure - measure={ - Object { - "leak": undefined, - "metric": Object { - "key": "new_lines", - "type": "SHORT_INT", - }, - } - } + metricKey="new_lines" + metricType="SHORT_INT" /> </div> <div diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardOverallMeasures-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardOverallMeasures-test.tsx.snap index 74ef1f08cfe..2c300ec6258 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardOverallMeasures-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardOverallMeasures-test.tsx.snap @@ -12,15 +12,8 @@ exports[`should not render coverage 1`] = ` className="project-card-measure-number" > <Measure - measure={ - Object { - "metric": Object { - "key": "coverage", - "type": "PERCENT", - }, - "value": undefined, - } - } + metricKey="coverage" + metricType="PERCENT" /> </div> <div @@ -44,15 +37,8 @@ exports[`should not render duplications 1`] = ` className="project-card-measure-number" > <Measure - measure={ - Object { - "metric": Object { - "key": "duplicated_lines_density", - "type": "PERCENT", - }, - "value": undefined, - } - } + metricKey="duplicated_lines_density" + metricType="PERCENT" /> </div> <div @@ -80,15 +66,9 @@ exports[`should render correctly with all data 1`] = ` > <Measure className="spacer-right" - measure={ - Object { - "metric": Object { - "key": "bugs", - "type": "SHORT_INT", - }, - "value": "17", - } - } + metricKey="bugs" + metricType="SHORT_INT" + value="17" /> <Rating value="1.0" @@ -116,15 +96,9 @@ exports[`should render correctly with all data 1`] = ` > <Measure className="spacer-right" - measure={ - Object { - "metric": Object { - "key": "vulnerabilities", - "type": "SHORT_INT", - }, - "value": "0", - } - } + metricKey="vulnerabilities" + metricType="SHORT_INT" + value="0" /> <Rating value="1.0" @@ -152,15 +126,9 @@ exports[`should render correctly with all data 1`] = ` > <Measure className="spacer-right" - measure={ - Object { - "metric": Object { - "key": "code_smells", - "type": "SHORT_INT", - }, - "value": "132", - } - } + metricKey="code_smells" + metricType="SHORT_INT" + value="132" /> <Rating value="1.0" @@ -194,15 +162,9 @@ exports[`should render correctly with all data 1`] = ` /> </span> <Measure - measure={ - Object { - "metric": Object { - "key": "coverage", - "type": "PERCENT", - }, - "value": "88.3", - } - } + metricKey="coverage" + metricType="PERCENT" + value="88.3" /> </div> <div @@ -230,15 +192,9 @@ exports[`should render correctly with all data 1`] = ` /> </span> <Measure - measure={ - Object { - "metric": Object { - "key": "duplicated_lines_density", - "type": "PERCENT", - }, - "value": "9.8", - } - } + metricKey="duplicated_lines_density" + metricType="PERCENT" + value="9.8" /> </div> <div @@ -259,15 +215,9 @@ exports[`should render correctly with all data 1`] = ` className="project-card-measure-number" > <Measure - measure={ - Object { - "metric": Object { - "key": "ncloc", - "type": "SHORT_INT", - }, - "value": "2053", - } - } + metricKey="ncloc" + metricType="SHORT_INT" + value="2053" /> <span className="spacer-left" @@ -299,15 +249,9 @@ exports[`should render ncloc correctly 1`] = ` className="project-card-measure-number" > <Measure - measure={ - Object { - "metric": Object { - "key": "ncloc", - "type": "SHORT_INT", - }, - "value": "16549887", - } - } + metricKey="ncloc" + metricType="SHORT_INT" + value="16549887" /> <span className="spacer-left" diff --git a/server/sonar-web/src/main/js/apps/projects/filters/TagsFilter.tsx b/server/sonar-web/src/main/js/apps/projects/filters/TagsFilter.tsx index 091bd3c95b5..963333ba5c1 100644 --- a/server/sonar-web/src/main/js/apps/projects/filters/TagsFilter.tsx +++ b/server/sonar-web/src/main/js/apps/projects/filters/TagsFilter.tsx @@ -82,11 +82,14 @@ export default class TagsFilter extends React.PureComponent<Props, State> { searchProjectTags({ q: search, ps: size(this.props.facet || {}) + LIST_SIZE - }).then(result => { - if (this.mounted) { - this.setState({ isLoading: false, tags: result.tags }); - } - }); + }).then( + result => { + if (this.mounted) { + this.setState({ isLoading: false, tags: result.tags }); + } + }, + () => {} + ); } }; diff --git a/server/sonar-web/src/main/js/components/common/BubblePopup.tsx b/server/sonar-web/src/main/js/components/common/BubblePopup.tsx index deb5f3d7882..18b7dbd6f95 100644 --- a/server/sonar-web/src/main/js/components/common/BubblePopup.tsx +++ b/server/sonar-web/src/main/js/components/common/BubblePopup.tsx @@ -20,10 +20,15 @@ import * as React from 'react'; import * as classNames from 'classnames'; +export interface BubblePopupPosition { + top: number; + right: number; +} + interface Props { customClass?: string; children: React.ReactNode; - position: { top: number; right: number }; + position: BubblePopupPosition; } export default function BubblePopup(props: Props) { diff --git a/server/sonar-web/src/main/js/components/common/MultiSelect.js b/server/sonar-web/src/main/js/components/common/MultiSelect.tsx index 2921ab1ad76..b88ccd430cf 100644 --- a/server/sonar-web/src/main/js/components/common/MultiSelect.js +++ b/server/sonar-web/src/main/js/components/common/MultiSelect.tsx @@ -17,47 +17,46 @@ * 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 { difference } from 'lodash'; import MultiSelectOption from './MultiSelectOption'; import SearchBox from '../controls/SearchBox'; -import { translate } from '../../helpers/l10n'; -/*:: -type Props = { - selectedElements: Array<string>, - elements: Array<string>, - listSize: number, - onSearch: string => void, - onSelect: string => void, - onUnselect: string => void, - validateSearchInput: string => string, - placeholder: string -}; -*/ +interface Props { + selectedElements: Array<string>; + elements: Array<string>; + listSize?: number; + onSearch: (query: string) => void; + onSelect: (item: string) => void; + onUnselect: (item: string) => void; + validateSearchInput?: (value: string) => string; + placeholder: string; +} + +interface State { + query: string; + selectedElements: Array<string>; + unselectedElements: Array<string>; + activeIdx: number; +} -/*:: -type State = { - query: string, - selectedElements: Array<string>, - unselectedElements: Array<string>, - activeIdx: number -}; -*/ +interface DefaultProps { + listSize: number; + validateSearchInput: (value: string) => string; +} + +type PropsWithDefault = Props & DefaultProps; -export default class MultiSelect extends React.PureComponent { - /*:: container: HTMLElement; */ - /*:: searchInput: HTMLInputElement; */ - /*:: props: Props; */ - /*:: state: State; */ +export default class MultiSelect extends React.PureComponent<Props, State> { + container: HTMLDivElement | null; + searchInput: HTMLInputElement | null; - static defaultProps = { + static defaultProps: DefaultProps = { listSize: 10, - validateSearchInput: (value /*: string */) => value + validateSearchInput: (value: string) => value }; - constructor(props /*: Props */) { + constructor(props: Props) { super(props); this.state = { query: '', @@ -69,13 +68,13 @@ export default class MultiSelect extends React.PureComponent { componentDidMount() { this.updateSelectedElements(this.props); - this.updateUnselectedElements(this.props); + this.updateUnselectedElements(this.props as PropsWithDefault); if (this.container) { this.container.addEventListener('keydown', this.handleKeyboard, true); } } - componentWillReceiveProps(nextProps /*: Props */) { + componentWillReceiveProps(nextProps: PropsWithDefault) { if ( this.props.elements !== nextProps.elements || this.props.selectedElements !== nextProps.selectedElements @@ -91,14 +90,18 @@ export default class MultiSelect extends React.PureComponent { } componentDidUpdate() { - this.searchInput && this.searchInput.focus(); + if (this.searchInput) { + this.searchInput.focus(); + } } componentWillUnmount() { - this.container.removeEventListener('keydown', this.handleKeyboard); + if (this.container) { + this.container.removeEventListener('keydown', this.handleKeyboard); + } } - handleSelectChange = (item /*: string */, selected /*: boolean */) => { + handleSelectChange = (item: string, selected: boolean) => { if (selected) { this.onSelectItem(item); } else { @@ -106,17 +109,17 @@ export default class MultiSelect extends React.PureComponent { } }; - handleSearchChange = (value /*: string */) => { - this.onSearchQuery(this.props.validateSearchInput(value)); + handleSearchChange = (value: string) => { + this.onSearchQuery((this.props as PropsWithDefault).validateSearchInput(value)); }; - handleElementHover = (element /*: string */) => { + handleElementHover = (element: string) => { this.setState((prevState, props) => { return { activeIdx: this.getAllElements(props, prevState).indexOf(element) }; }); }; - handleKeyboard = (evt /*: KeyboardEvent */) => { + handleKeyboard = (evt: KeyboardEvent) => { switch (evt.keyCode) { case 40: // down this.setState(this.selectNextElement); @@ -140,28 +143,25 @@ export default class MultiSelect extends React.PureComponent { } }; - onSearchQuery(query /*: string */) { + onSearchQuery = (query: string) => { this.setState({ query, activeIdx: 0 }); this.props.onSearch(query); - } + }; - onSelectItem(item /*: string */) { + onSelectItem = (item: string) => { if (this.isNewElement(item, this.props)) { this.onSearchQuery(''); } this.props.onSelect(item); - } + }; - onUnselectItem(item /*: string */) { - this.props.onUnselect(item); - } + onUnselectItem = (item: string) => this.props.onUnselect(item); - isNewElement(elem /*: string */, { selectedElements, elements } /*: Props */) { - return elem && selectedElements.indexOf(elem) === -1 && elements.indexOf(elem) === -1; - } + isNewElement = (elem: string, { selectedElements, elements }: Props) => + elem && selectedElements.indexOf(elem) === -1 && elements.indexOf(elem) === -1; - updateSelectedElements(props /*: Props */) { - this.setState((state /*: State */) => { + updateSelectedElements = (props: Props) => { + this.setState((state: State) => { if (state.query) { return { selectedElements: [...props.selectedElements.filter(elem => elem.includes(state.query))] @@ -170,10 +170,10 @@ export default class MultiSelect extends React.PureComponent { return { selectedElements: [...props.selectedElements] }; } }); - } + }; - updateUnselectedElements(props /*: Props */) { - this.setState((state /*: State */) => { + updateUnselectedElements = (props: PropsWithDefault) => { + this.setState((state: State) => { if (props.listSize < state.selectedElements.length) { return { unselectedElements: [] }; } else { @@ -185,21 +185,19 @@ export default class MultiSelect extends React.PureComponent { }; } }); - } + }; - getAllElements(props /*: Props */, state /*: State */) { + getAllElements = (props: Props, state: State) => { if (this.isNewElement(state.query, props)) { return [...state.selectedElements, ...state.unselectedElements, state.query]; } else { return [...state.selectedElements, ...state.unselectedElements]; } - } + }; - setElementActive(idx /*: number */) { - this.setState({ activeIdx: idx }); - } + setElementActive = (idx: number) => this.setState({ activeIdx: idx }); - selectNextElement = (state /*: State */, props /*: Props */) => { + selectNextElement = (state: State, props: Props) => { const { activeIdx } = state; const allElements = this.getAllElements(props, state); if (activeIdx < 0 || activeIdx >= allElements.length - 1) { @@ -209,7 +207,7 @@ export default class MultiSelect extends React.PureComponent { } }; - selectPreviousElement = (state /*: State */, props /*: Props */) => { + selectPreviousElement = (state: State, props: Props) => { const { activeIdx } = state; const allElements = this.getAllElements(props, state); if (activeIdx <= 0) { @@ -220,13 +218,13 @@ export default class MultiSelect extends React.PureComponent { } }; - toggleSelect(item /*: string */) { + toggleSelect = (item: string) => { if (this.props.selectedElements.indexOf(item) === -1) { this.onSelectItem(item); } else { this.onUnselectItem(item); } - } + }; render() { const { query, activeIdx, selectedElements, unselectedElements } = this.state; diff --git a/server/sonar-web/src/main/js/components/common/MultiSelectOption.js b/server/sonar-web/src/main/js/components/common/MultiSelectOption.tsx index ad144f5cd9d..89429426d7e 100644 --- a/server/sonar-web/src/main/js/components/common/MultiSelectOption.js +++ b/server/sonar-web/src/main/js/components/common/MultiSelectOption.tsx @@ -17,40 +17,27 @@ * 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'; -/*:: -type Props = { - element: string, - selected: boolean, - custom: boolean, - active: boolean, - onSelectChange: (string, boolean) => void, - onHover: string => void -}; -*/ - -export default class MultiSelectOption extends React.PureComponent { - /*:: props: Props; */ - - static defaultProps = { - selected: false, - custom: false, - active: false - }; +interface Props { + element: string; + selected?: boolean; + custom?: boolean; + active?: boolean; + onSelectChange: (elem: string, selected: boolean) => void; + onHover: (elem: string) => void; +} - handleSelect = (evt /*: SyntheticInputEvent */) => { +export default class MultiSelectOption extends React.PureComponent<Props> { + handleSelect = (evt: React.SyntheticEvent<HTMLAnchorElement>) => { evt.stopPropagation(); evt.preventDefault(); - evt.target.blur(); + evt.currentTarget.blur(); this.props.onSelectChange(this.props.element, !this.props.selected); }; - handleHover = () => { - this.props.onHover(this.props.element); - }; + handleHover = () => this.props.onHover(this.props.element); render() { const className = classNames('icon-checkbox', { diff --git a/server/sonar-web/src/main/js/components/common/__tests__/MultiSelect-test.js b/server/sonar-web/src/main/js/components/common/__tests__/MultiSelect-test.tsx index c843a7952bb..13bb1651866 100644 --- a/server/sonar-web/src/main/js/components/common/__tests__/MultiSelect-test.js +++ b/server/sonar-web/src/main/js/components/common/__tests__/MultiSelect-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, mount } from 'enzyme'; -import React from 'react'; import MultiSelect from '../MultiSelect'; const props = { diff --git a/server/sonar-web/src/main/js/components/common/__tests__/MultiSelectOption-test.js b/server/sonar-web/src/main/js/components/common/__tests__/MultiSelectOption-test.tsx index a15475bc149..5b72d18b21e 100644 --- a/server/sonar-web/src/main/js/components/common/__tests__/MultiSelectOption-test.js +++ b/server/sonar-web/src/main/js/components/common/__tests__/MultiSelectOption-test.tsx @@ -17,15 +17,12 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import * as React from 'react'; import { shallow } from 'enzyme'; -import React from 'react'; import MultiSelectOption from '../MultiSelectOption'; const props = { element: 'mytag', - selected: false, - custom: false, - active: false, onSelectChange: () => {}, onHover: () => {} }; diff --git a/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/MultiSelect-test.js.snap b/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/MultiSelect-test.tsx.snap index 5af710f8bb1..32dac980bbe 100644 --- a/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/MultiSelect-test.js.snap +++ b/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/MultiSelect-test.tsx.snap @@ -19,7 +19,6 @@ exports[`should render multiselect with selected elements 1`] = ` > <MultiSelectOption active={true} - custom={false} element="bar" key="bar" onHover={[Function]} @@ -49,7 +48,6 @@ exports[`should render multiselect with selected elements 2`] = ` > <MultiSelectOption active={true} - custom={false} element="bar" key="bar" onHover={[Function]} @@ -58,21 +56,17 @@ exports[`should render multiselect with selected elements 2`] = ` /> <MultiSelectOption active={false} - custom={false} element="foo" key="foo" onHover={[Function]} onSelectChange={[Function]} - selected={false} /> <MultiSelectOption active={false} - custom={false} element="baz" key="baz" onHover={[Function]} onSelectChange={[Function]} - selected={false} /> </ul> </div> @@ -97,7 +91,6 @@ exports[`should render multiselect with selected elements 3`] = ` > <MultiSelectOption active={false} - custom={false} element="bar" key="bar" onHover={[Function]} @@ -106,21 +99,17 @@ exports[`should render multiselect with selected elements 3`] = ` /> <MultiSelectOption active={false} - custom={false} element="foo" key="foo" onHover={[Function]} onSelectChange={[Function]} - selected={false} /> <MultiSelectOption active={true} - custom={false} element="baz" key="baz" onHover={[Function]} onSelectChange={[Function]} - selected={false} /> </ul> </div> @@ -145,7 +134,6 @@ exports[`should render multiselect with selected elements 4`] = ` > <MultiSelectOption active={false} - custom={false} element="bar" key="bar" onHover={[Function]} @@ -154,21 +142,17 @@ exports[`should render multiselect with selected elements 4`] = ` /> <MultiSelectOption active={false} - custom={false} element="foo" key="foo" onHover={[Function]} onSelectChange={[Function]} - selected={false} /> <MultiSelectOption active={true} - custom={false} element="baz" key="baz" onHover={[Function]} onSelectChange={[Function]} - selected={false} /> <MultiSelectOption active={false} @@ -177,7 +161,6 @@ exports[`should render multiselect with selected elements 4`] = ` key="test" onHover={[Function]} onSelectChange={[Function]} - selected={false} /> </ul> </div> diff --git a/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/MultiSelectOption-test.js.snap b/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/MultiSelectOption-test.tsx.snap index b7b9f2a61c0..b7b9f2a61c0 100644 --- a/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/MultiSelectOption-test.js.snap +++ b/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/MultiSelectOption-test.tsx.snap diff --git a/server/sonar-web/src/main/js/components/issue/popups/SetIssueTagsPopup.js b/server/sonar-web/src/main/js/components/issue/popups/SetIssueTagsPopup.js index 30c93818fff..c334412d5c0 100644 --- a/server/sonar-web/src/main/js/components/issue/popups/SetIssueTagsPopup.js +++ b/server/sonar-web/src/main/js/components/issue/popups/SetIssueTagsPopup.js @@ -77,7 +77,6 @@ export default class SetIssueTagsPopup extends React.PureComponent { render() { return ( - // $FlowFixMe `this.props.popupPosition` is passed from `BabelPopupHelper` <TagsSelector position={this.props.popupPosition} tags={this.state.searchResult} diff --git a/server/sonar-web/src/main/js/components/measure/Measure.tsx b/server/sonar-web/src/main/js/components/measure/Measure.tsx index 200035f8016..ae12c4c480f 100644 --- a/server/sonar-web/src/main/js/components/measure/Measure.tsx +++ b/server/sonar-web/src/main/js/components/measure/Measure.tsx @@ -21,39 +21,32 @@ import * as React from 'react'; import Rating from '../ui/Rating'; import Level from '../ui/Level'; import Tooltips from '../controls/Tooltip'; -import { formatMeasure, isDiffMetric, MeasureEnhanced } from '../../helpers/measures'; -import { formatLeak, getRatingTooltip } from './utils'; +import { formatMeasure } from '../../helpers/measures'; +import { getRatingTooltip } from './utils'; interface Props { className?: string; decimals?: number | null; - measure?: MeasureEnhanced; + value?: string; + metricKey: string; + metricType: string; } -export default function Measure({ className, decimals, measure }: Props) { - if (measure === undefined) { - return <span>{'–'}</span>; - } - - const { metric } = measure; - const value = isDiffMetric(metric.key) ? measure.leak : measure.value; - +export default function Measure({ className, decimals, metricKey, metricType, value }: Props) { if (value === undefined) { return <span>{'–'}</span>; } - if (metric.type === 'LEVEL') { + if (metricType === 'LEVEL') { return <Level className={className} level={value} />; } - if (metric.type !== 'RATING') { - const formattedValue = isDiffMetric(metric.key) - ? formatLeak(measure.leak, metric.key, metric.type, { decimals }) - : formatMeasure(measure.value, metric.type, { decimals }); + if (metricType !== 'RATING') { + const formattedValue = formatMeasure(value, metricType, { decimals }); return <span className={className}>{formattedValue != null ? formattedValue : '–'}</span>; } - const tooltip = getRatingTooltip(metric.key, Number(value)); + const tooltip = getRatingTooltip(metricKey, Number(value)); const rating = <Rating value={value} />; if (tooltip) { return ( diff --git a/server/sonar-web/src/main/js/components/measure/__tests__/Measure-test.tsx b/server/sonar-web/src/main/js/components/measure/__tests__/Measure-test.tsx index a62f16c513d..84aaf15fdb3 100644 --- a/server/sonar-web/src/main/js/components/measure/__tests__/Measure-test.tsx +++ b/server/sonar-web/src/main/js/components/measure/__tests__/Measure-test.tsx @@ -17,55 +17,46 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -/* eslint-disable import/first */ +import * as React from 'react'; +import { shallow } from 'enzyme'; +import Measure from '../Measure'; + jest.mock('../../../helpers/measures', () => { const measures = require.requireActual('../../../helpers/measures'); measures.getRatingTooltip = jest.fn(() => 'tooltip'); return measures; }); -import * as React from 'react'; -import { shallow } from 'enzyme'; -import Measure from '../Measure'; - it('renders trivial measure', () => { - const measure = { metric: { key: 'coverage', name: 'Coverage', type: 'PERCENT' }, value: '73.0' }; - expect(shallow(<Measure measure={measure} />)).toMatchSnapshot(); + expect( + shallow(<Measure metricKey="coverage" metricType="PERCENT" value="73.0" />) + ).toMatchSnapshot(); }); it('renders leak measure', () => { - const measure = { - metric: { key: 'new_coverage', name: 'Coverage on New Code', type: 'PERCENT' }, - leak: '36.0' - }; - expect(shallow(<Measure measure={measure} />)).toMatchSnapshot(); + expect( + shallow(<Measure metricKey="new_coverage" metricType="PERCENT" value="36.0" />) + ).toMatchSnapshot(); }); it('renders LEVEL', () => { - const measure = { - metric: { key: 'quality_gate_status', name: 'Quality Gate', type: 'LEVEL' }, - value: 'ERROR' - }; - expect(shallow(<Measure measure={measure} />)).toMatchSnapshot(); + expect( + shallow(<Measure metricKey="quality_gate_status" metricType="LEVEL" value="ERROR" />) + ).toMatchSnapshot(); }); it('renders known RATING', () => { - const measure = { - metric: { key: 'sqale_rating', name: 'Maintainability Rating', type: 'RATING' }, - value: '3' - }; - expect(shallow(<Measure measure={measure} />)).toMatchSnapshot(); + expect( + shallow(<Measure metricKey="sqale_rating" metricType="RATING" value="3" />) + ).toMatchSnapshot(); }); it('renders unknown RATING', () => { - const measure = { - metric: { key: 'foo_rating', name: 'Foo Rating', type: 'RATING' }, - value: '4' - }; - expect(shallow(<Measure measure={measure} />)).toMatchSnapshot(); + expect( + shallow(<Measure metricKey="foo_rating" metricType="RATING" value="4" />) + ).toMatchSnapshot(); }); it('renders undefined measure', () => { - const measure = { metric: { key: 'foo', name: 'Foo', type: 'PERCENT' } }; - expect(shallow(<Measure measure={measure} />)).toMatchSnapshot(); + expect(shallow(<Measure metricKey="foo" metricType="PERCENT" />)).toMatchSnapshot(); }); diff --git a/server/sonar-web/src/main/js/components/measure/utils.ts b/server/sonar-web/src/main/js/components/measure/utils.ts index 8e932892152..626c65f552f 100644 --- a/server/sonar-web/src/main/js/components/measure/utils.ts +++ b/server/sonar-web/src/main/js/components/measure/utils.ts @@ -18,8 +18,6 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import { - formatMeasure, - formatMeasureVariation, getRatingTooltip as nextGetRatingTooltip, isDiffMetric, Measure, @@ -40,19 +38,6 @@ export function enhanceMeasure( }; } -export function formatLeak( - value: string | undefined, - metricKey: string, - metricType: string, - options: any -): string { - if (isDiffMetric(metricKey)) { - return formatMeasure(value, metricType, options); - } else { - return formatMeasureVariation(value, metricType, options); - } -} - export function getLeakValue(measure: Measure | undefined): string | undefined { if (!measure || !measure.periods) { return undefined; diff --git a/server/sonar-web/src/main/js/components/preview-graph/PreviewGraph.d.ts b/server/sonar-web/src/main/js/components/preview-graph/PreviewGraph.d.ts new file mode 100644 index 00000000000..744eed0493d --- /dev/null +++ b/server/sonar-web/src/main/js/components/preview-graph/PreviewGraph.d.ts @@ -0,0 +1,32 @@ +/* + * 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 { History } from '../../api/time-machine'; +import { Metric } from '../../app/types'; + +interface Props { + branch?: string; + history?: History; + metrics: Metric[]; + project: string; + renderWhenEmpty?: () => void; +} + +export default class PreviewGraph extends React.Component<Props> {} diff --git a/server/sonar-web/src/main/js/components/shared/drilldown-link.js b/server/sonar-web/src/main/js/components/shared/DrilldownLink.tsx index cdff767a2d9..1065e91e9ef 100644 --- a/server/sonar-web/src/main/js/components/shared/drilldown-link.js +++ b/server/sonar-web/src/main/js/components/shared/DrilldownLink.tsx @@ -17,8 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import React from 'react'; -import PropTypes from 'prop-types'; +import * as React from 'react'; import { Link } from 'react-router'; import { getComponentDrilldownUrl, getComponentIssuesUrl } from '../../helpers/urls'; @@ -47,21 +46,22 @@ const ISSUE_MEASURES = [ 'new_vulnerabilities' ]; -export class DrilldownLink extends React.PureComponent { - static propTypes = { - branch: PropTypes.string, - children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf(PropTypes.node)]), - className: PropTypes.string, - component: PropTypes.string.isRequired, - metric: PropTypes.string.isRequired, - sinceLeakPeriod: PropTypes.bool - }; +interface Props { + branch?: string; + children?: React.ReactNode; + className?: string; + component: string; + metric: string; + sinceLeakPeriod?: boolean; +} + +export default class DrilldownLink extends React.PureComponent<Props> { isIssueMeasure = () => { return ISSUE_MEASURES.indexOf(this.props.metric) !== -1; }; propsToIssueParams = () => { - const params = {}; + const params: { [key: string]: string | boolean } = {}; if (this.props.sinceLeakPeriod) { params.sinceLeakPeriod = true; diff --git a/server/sonar-web/src/main/js/components/tags/TagsSelector.js b/server/sonar-web/src/main/js/components/tags/TagsSelector.tsx index c257c31a5ed..f9ed202989c 100644 --- a/server/sonar-web/src/main/js/components/tags/TagsSelector.js +++ b/server/sonar-web/src/main/js/components/tags/TagsSelector.tsx @@ -17,26 +17,23 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; -import BubblePopup from '../common/BubblePopup'; +import * as React from 'react'; +import BubblePopup, { BubblePopupPosition } from '../common/BubblePopup'; import MultiSelect from '../common/MultiSelect'; import { translate } from '../../helpers/l10n'; import './TagsList.css'; -/*:: -type Props = { - position: {}, - tags: Array<string>, - selectedTags: Array<string>, - listSize: number, - onSearch: string => void, - onSelect: string => void, - onUnselect: string => void -}; -*/ +interface Props { + position: BubblePopupPosition; + tags: string[]; + selectedTags: string[]; + listSize: number; + onSearch: (query: string) => void; + onSelect: (item: string) => void; + onUnselect: (item: string) => void; +} -export default function TagsSelector(props /*: Props */) { +export default function TagsSelector(props: Props) { return ( <BubblePopup position={props.position} @@ -55,7 +52,7 @@ export default function TagsSelector(props /*: Props */) { ); } -export function validateTag(value /*: string */) { +export function validateTag(value: string) { // Allow only a-z, 0-9, '+', '-', '#', '.' return value.toLowerCase().replace(/[^a-z0-9\+\-#.]/gi, ''); } diff --git a/server/sonar-web/src/main/js/components/tags/__tests__/TagsSelector-test.js b/server/sonar-web/src/main/js/components/tags/__tests__/TagsSelector-test.tsx index 98186daca4a..1b5169a2940 100644 --- a/server/sonar-web/src/main/js/components/tags/__tests__/TagsSelector-test.js +++ b/server/sonar-web/src/main/js/components/tags/__tests__/TagsSelector-test.tsx @@ -17,12 +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 * as React from 'react'; import { shallow } from 'enzyme'; -import React from 'react'; import TagsSelector, { validateTag } from '../TagsSelector'; const props = { - position: { left: 0, top: 0 }, + position: { right: 0, top: 0 }, + listSize: 10, tags: ['foo', 'bar', 'baz'], selectedTags: ['bar'], onSearch: () => {}, diff --git a/server/sonar-web/src/main/js/components/tags/__tests__/__snapshots__/TagsSelector-test.js.snap b/server/sonar-web/src/main/js/components/tags/__tests__/__snapshots__/TagsSelector-test.tsx.snap index e2d57569b31..c2d4623b8c8 100644 --- a/server/sonar-web/src/main/js/components/tags/__tests__/__snapshots__/TagsSelector-test.js.snap +++ b/server/sonar-web/src/main/js/components/tags/__tests__/__snapshots__/TagsSelector-test.tsx.snap @@ -5,7 +5,7 @@ exports[`should render with selected tags 1`] = ` customClass="bubble-popup-bottom-right bubble-popup-menu abs-width-300" position={ Object { - "left": 0, + "right": 0, "top": 0, } } @@ -38,7 +38,7 @@ exports[`should render without tags at all 1`] = ` customClass="bubble-popup-bottom-right bubble-popup-menu abs-width-300" position={ Object { - "left": 0, + "right": 0, "top": 0, } } diff --git a/server/sonar-web/src/main/js/helpers/__tests__/measures-test.ts b/server/sonar-web/src/main/js/helpers/__tests__/measures-test.ts index 68ae35138a7..6cf02992461 100644 --- a/server/sonar-web/src/main/js/helpers/__tests__/measures-test.ts +++ b/server/sonar-web/src/main/js/helpers/__tests__/measures-test.ts @@ -18,7 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import { resetBundle } from '../l10n'; -import { formatMeasure, formatMeasureVariation } from '../measures'; +import { formatMeasure } from '../measures'; const HOURS_IN_DAY = 8; const ONE_MINUTE = 1; @@ -170,92 +170,3 @@ describe('#formatMeasure()', () => { expect(formatMeasure(undefined, 'INT')).toBe(''); }); }); - -describe('#formatMeasureVariation()', () => { - it('should format INT', () => { - expect(formatMeasureVariation(0, 'INT')).toBe('+0'); - expect(formatMeasureVariation(1, 'INT')).toBe('+1'); - expect(formatMeasureVariation(-1, 'INT')).toBe('-1'); - expect(formatMeasureVariation(1529, 'INT')).toBe('+1,529'); - expect(formatMeasureVariation(-1529, 'INT')).toBe('-1,529'); - }); - - it('should format SHORT_INT', () => { - expect(formatMeasureVariation(0, 'SHORT_INT')).toBe('+0'); - expect(formatMeasureVariation(1, 'SHORT_INT')).toBe('+1'); - expect(formatMeasureVariation(-1, 'SHORT_INT')).toBe('-1'); - expect(formatMeasureVariation(1529, 'SHORT_INT')).toBe('+1.5k'); - expect(formatMeasureVariation(-1529, 'SHORT_INT')).toBe('-1.5k'); - expect(formatMeasureVariation(10678, 'SHORT_INT')).toBe('+11k'); - expect(formatMeasureVariation(-10678, 'SHORT_INT')).toBe('-11k'); - }); - - it('should format FLOAT', () => { - expect(formatMeasureVariation(0.0, 'FLOAT')).toBe('+0.0'); - expect(formatMeasureVariation(1.0, 'FLOAT')).toBe('+1.0'); - expect(formatMeasureVariation(-1.0, 'FLOAT')).toBe('-1.0'); - expect(formatMeasureVariation(50.89, 'FLOAT')).toBe('+50.89'); - expect(formatMeasureVariation(-50.89, 'FLOAT')).toBe('-50.89'); - }); - - it('should respect FLOAT precision', () => { - expect(formatMeasureVariation(0.1, 'FLOAT')).toBe('+0.1'); - expect(formatMeasureVariation(0.12, 'FLOAT')).toBe('+0.12'); - expect(formatMeasureVariation(0.12345, 'FLOAT')).toBe('+0.12345'); - expect(formatMeasureVariation(0.123456, 'FLOAT')).toBe('+0.12346'); - }); - - it('should format PERCENT', () => { - expect(formatMeasureVariation(0.0, 'PERCENT')).toBe('+0.0%'); - expect(formatMeasureVariation(1.0, 'PERCENT')).toBe('+1.0%'); - expect(formatMeasureVariation(-1.0, 'PERCENT')).toBe('-1.0%'); - expect(formatMeasureVariation(50.89, 'PERCENT')).toBe('+50.9%'); - expect(formatMeasureVariation(-50.89, 'PERCENT')).toBe('-50.9%'); - }); - - it('should format WORK_DUR', () => { - expect(formatMeasureVariation(0, 'WORK_DUR')).toBe('+0'); - expect(formatMeasureVariation(5 * ONE_DAY, 'WORK_DUR')).toBe('+5d'); - expect(formatMeasureVariation(2 * ONE_HOUR, 'WORK_DUR')).toBe('+2h'); - expect(formatMeasureVariation(ONE_MINUTE, 'WORK_DUR')).toBe('+1min'); - expect(formatMeasureVariation(-5 * ONE_DAY, 'WORK_DUR')).toBe('-5d'); - expect(formatMeasureVariation(-2 * ONE_HOUR, 'WORK_DUR')).toBe('-2h'); - expect(formatMeasureVariation(-1 * ONE_MINUTE, 'WORK_DUR')).toBe('-1min'); - }); - - it('should format SHORT_WORK_DUR', () => { - expect(formatMeasureVariation(0, 'SHORT_WORK_DUR')).toBe('+0'); - expect(formatMeasureVariation(5 * ONE_DAY, 'SHORT_WORK_DUR')).toBe('+5d'); - expect(formatMeasureVariation(2 * ONE_HOUR, 'SHORT_WORK_DUR')).toBe('+2h'); - expect(formatMeasureVariation(ONE_MINUTE, 'SHORT_WORK_DUR')).toBe('+1min'); - expect(formatMeasureVariation(30 * ONE_MINUTE, 'SHORT_WORK_DUR')).toBe('+30min'); - expect(formatMeasureVariation(58 * ONE_MINUTE, 'SHORT_WORK_DUR')).toBe('+1h'); - expect(formatMeasureVariation(5 * ONE_DAY + 2 * ONE_HOUR, 'SHORT_WORK_DUR')).toBe('+5d'); - expect(formatMeasureVariation(2 * ONE_HOUR + ONE_MINUTE, 'SHORT_WORK_DUR')).toBe('+2h'); - expect(formatMeasureVariation(ONE_HOUR + 55 * ONE_MINUTE, 'SHORT_WORK_DUR')).toBe('+2h'); - expect(formatMeasureVariation(3 * ONE_DAY + 6 * ONE_HOUR, 'SHORT_WORK_DUR')).toBe('+4d'); - expect(formatMeasureVariation(7 * ONE_HOUR + 59 * ONE_MINUTE, 'SHORT_WORK_DUR')).toBe('+1d'); - expect(formatMeasureVariation(5 * ONE_DAY + 2 * ONE_HOUR + ONE_MINUTE, 'SHORT_WORK_DUR')).toBe( - '+5d' - ); - expect(formatMeasureVariation(15 * ONE_DAY + 2 * ONE_HOUR + ONE_MINUTE, 'SHORT_WORK_DUR')).toBe( - '+15d' - ); - expect(formatMeasureVariation(7 * ONE_MINUTE, 'SHORT_WORK_DUR')).toBe('+7min'); - expect(formatMeasureVariation(-5 * ONE_DAY, 'SHORT_WORK_DUR')).toBe('-5d'); - expect(formatMeasureVariation(-2 * ONE_HOUR, 'SHORT_WORK_DUR')).toBe('-2h'); - expect(formatMeasureVariation(-1 * ONE_MINUTE, 'SHORT_WORK_DUR')).toBe('-1min'); - - expect(formatMeasureVariation(1529 * ONE_DAY, 'SHORT_WORK_DUR')).toBe('+1.5kd'); - expect(formatMeasureVariation(1234567 * ONE_DAY, 'SHORT_WORK_DUR')).toBe('+1Md'); - expect(formatMeasureVariation(1234567 * ONE_DAY + 2 * ONE_HOUR, 'SHORT_WORK_DUR')).toBe('+1Md'); - }); - - it('should not format unknown type', () => { - expect(formatMeasureVariation('random value', 'RANDOM_TYPE')).toBe('random value'); - }); - - it('should not fail with undefined', () => { - expect(formatMeasureVariation(undefined, 'INT')).toBe(''); - }); -}); diff --git a/server/sonar-web/src/main/js/helpers/measures.ts b/server/sonar-web/src/main/js/helpers/measures.ts index edb05d8ce85..b6354557ad1 100644 --- a/server/sonar-web/src/main/js/helpers/measures.ts +++ b/server/sonar-web/src/main/js/helpers/measures.ts @@ -55,16 +55,6 @@ export function formatMeasure( return useFormatter(value, formatter, options); } -/** Format a measure variation for a given type */ -export function formatMeasureVariation( - value: string | number | undefined, - type: string, - options?: any -): string { - const formatter = getVariationFormatter(type); - return useFormatter(value, formatter, options); -} - /** Return a localized metric name */ export function localizeMetric(metricKey: string): string { return translate('metric', metricKey, 'name'); @@ -91,7 +81,10 @@ export function enhanceMeasuresWithMetrics( } /** Get period value of a measure */ -export function getPeriodValue(measure: Measure, periodIndex: number): string | undefined { +export function getPeriodValue( + measure: Measure | MeasureEnhanced, + periodIndex: number +): string | undefined { const { periods } = measure; const period = periods && periods.find(period => period.index === periodIndex); return period ? period.value : undefined; @@ -125,21 +118,6 @@ function getFormatter(type: string): Formatter { return FORMATTERS[type] || noFormatter; } -function getVariationFormatter(type: string): Formatter { - const FORMATTERS: { [type: string]: Formatter } = { - INT: intVariationFormatter, - SHORT_INT: shortIntVariationFormatter, - FLOAT: floatVariationFormatter, - PERCENT: percentVariationFormatter, - WORK_DUR: durationVariationFormatter, - SHORT_WORK_DUR: shortDurationVariationFormatter, - RATING: emptyFormatter, - LEVEL: emptyFormatter, - MILLISEC: millisecondsVariationFormatter - }; - return FORMATTERS[type] || noFormatter; -} - function numberFormatter( value: number, minimumFractionDigits = 0, @@ -156,19 +134,10 @@ function noFormatter(value: string | number): string | number { return value; } -function emptyFormatter(): string { - return ''; -} - function intFormatter(value: number): string { return numberFormatter(value); } -function intVariationFormatter(value: number): string { - const prefix = value < 0 ? '-' : '+'; - return prefix + intFormatter(Math.abs(value)); -} - function shortIntFormatter(value: number): string { if (value >= 1e9) { return numberFormatter(value / 1e9) + translate('short_number_suffix.g'); @@ -183,20 +152,10 @@ function shortIntFormatter(value: number): string { } } -function shortIntVariationFormatter(value: number): string { - const formatted = shortIntFormatter(Math.abs(value)); - return value < 0 ? `-${formatted}` : `+${formatted}`; -} - function floatFormatter(value: number): string { return numberFormatter(value, 1, 5); } -function floatVariationFormatter(value: number): string { - const prefix = value < 0 ? '-' : '+'; - return prefix + floatFormatter(Math.abs(value)); -} - function percentFormatter(value: string | number, options: { decimals?: number } = {}): string { if (typeof value === 'string') { value = parseFloat(value); @@ -207,17 +166,6 @@ function percentFormatter(value: string | number, options: { decimals?: number } return value === 100 ? '100%' : numberFormatter(value, 1) + '%'; } -function percentVariationFormatter( - value: string | number, - options: { decimals?: number } = {} -): string { - if (typeof value === 'string') { - value = parseFloat(value); - } - const prefix = value < 0 ? '-' : '+'; - return prefix + percentFormatter(Math.abs(value), options); -} - function ratingFormatter(value: string | number): string { if (typeof value === 'string') { value = parseInt(value, 10); @@ -247,12 +195,6 @@ function millisecondsFormatter(value: number): string { } } -function millisecondsVariationFormatter(value: number): string { - const absValue = Math.abs(value); - const formattedValue = millisecondsFormatter(absValue); - return value < 0 ? `-${formattedValue}` : `+${formattedValue}`; -} - /* * Debt Formatters */ @@ -362,22 +304,6 @@ function shortDurationFormatter(value: string | number): string { return formatDurationShort(isNegative, days, hours, remainingValue); } -function durationVariationFormatter(value: string | number): string { - if (value === 0 || value === '0') { - return '+0'; - } - const formatted = durationFormatter(value); - return formatted[0] !== '-' ? '+' + formatted : formatted; -} - -function shortDurationVariationFormatter(value: string | number): string { - if (value === 0 || value === '0') { - return '+0'; - } - const formatted = shortDurationFormatter(value); - return formatted[0] !== '-' ? '+' + formatted : formatted; -} - function getRatingGrid(): string { // workaround cyclic dependencies const getStore = require('../app/utils/getStore').default; @@ -429,12 +355,12 @@ function getMaintainabilityRatingTooltip(rating: number): string { ); } -export function getRatingTooltip(metricKey: string, value: number): string { +export function getRatingTooltip(metricKey: string, value: number | string): string { const ratingLetter = formatMeasure(value, 'RATING'); - const finalMetricKey = metricKey.startsWith('new_') ? metricKey.substr(4) : metricKey; + const finalMetricKey = isDiffMetric(metricKey) ? metricKey.substr(4) : metricKey; return finalMetricKey === 'sqale_rating' || finalMetricKey === 'maintainability_rating' - ? getMaintainabilityRatingTooltip(value) + ? getMaintainabilityRatingTooltip(Number(value)) : translate('metric', finalMetricKey, 'tooltip', ratingLetter); } diff --git a/server/sonar-web/src/main/js/helpers/periods.ts b/server/sonar-web/src/main/js/helpers/periods.ts index 110eb67cf2a..afc0edf1c49 100644 --- a/server/sonar-web/src/main/js/helpers/periods.ts +++ b/server/sonar-web/src/main/js/helpers/periods.ts @@ -54,8 +54,8 @@ export function getPeriodLabel(period: Period | undefined): string | undefined { return translateWithParameters(`overview.period.${period.mode}`, parameter); } -export function getPeriodDate(period: Period | undefined): Date | undefined { - return period ? parseDate(period.date) : undefined; +export function getPeriodDate(period?: { date?: string }): Date | undefined { + return period && period.date ? parseDate(period.date) : undefined; } export function getLeakPeriodLabel(periods: Period[]): string | undefined { |