From 0ba8c56c9a7cddf225720f224e66b267f9bbf528 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Gr=C3=A9goire=20Aubert?= Date: Mon, 20 Aug 2018 17:28:52 +0200 Subject: [PATCH] SONAR-11165 Migrate rest of component measures page to TS --- .../sonar-web/src/main/js/api/components.ts | 10 +- server/sonar-web/src/main/js/api/measures.ts | 10 +- .../nav/component/ComponentNavMenu.tsx | 6 - .../ComponentNavMenu-test.tsx.snap | 18 +++ .../main/js/app/styles/components/modals.css | 1 - server/sonar-web/src/main/js/app/types.ts | 42 ++++-- .../components/{App.js => App.tsx} | 120 +++++++++--------- .../{AppContainer.js => AppContainer.tsx} | 78 ++++++++---- .../{FilesCounter.js => FilesCounter.tsx} | 17 ++- .../components/LeakPeriodLegend.tsx | 4 +- .../components/MeasureContent.tsx | 8 +- .../components/MeasureContentContainer.tsx | 4 +- .../{MeasureHeader.js => MeasureHeader.tsx} | 31 ++--- ...MeasureOverview.js => MeasureOverview.tsx} | 72 ++++++----- ...tainer.js => MeasureOverviewContainer.tsx} | 76 +++++------ .../{MetricNotFound.js => MetricNotFound.tsx} | 5 +- .../{PageActions.js => PageActions.tsx} | 23 ++-- .../__tests__/{App-test.js => App-test.tsx} | 20 ++- ...sCounter-test.js => FilesCounter-test.tsx} | 4 +- .../__tests__/LeakPeriodLegend-test.tsx | 3 +- ...eHeader-test.js => MeasureHeader-test.tsx} | 34 ++--- ...geActions-test.js => PageActions-test.tsx} | 12 +- .../{App-test.js.snap => App-test.tsx.snap} | 38 +++++- ...est.js.snap => FilesCounter-test.tsx.snap} | 0 ...st.js.snap => MeasureHeader-test.tsx.snap} | 51 +------- ...test.js.snap => PageActions-test.tsx.snap} | 0 .../apps/component-measures/config/bubbles.ts | 2 +- .../apps/component-measures/config/domains.ts | 7 +- .../{BubbleChart.js => BubbleChart.tsx} | 93 ++++++-------- .../component-measures/drilldown/CodeView.tsx | 9 +- .../{ComponentCell.js => ComponentCell.tsx} | 77 ++++++----- ...onentsListRow.js => ComponentsListRow.tsx} | 28 ++-- .../{MeasureCell.js => MeasureCell.tsx} | 21 ++- ...asureCell-test.js => MeasureCell-test.tsx} | 8 +- .../{DomainFacet.js => DomainFacet.tsx} | 40 +++--- ...tMeasureValue.js => FacetMeasureValue.tsx} | 11 +- ...rviewFacet.js => ProjectOverviewFacet.tsx} | 22 ++-- .../sidebar/{Sidebar.js => Sidebar.tsx} | 49 ++++--- ...mainFacet-test.js => DomainFacet-test.tsx} | 9 +- ...lue-test.js => FacetMeasureValue-test.tsx} | 5 +- .../{Sidebar-test.js => Sidebar-test.tsx} | 9 +- ...test.js.snap => DomainFacet-test.tsx.snap} | 16 +++ ...s.snap => FacetMeasureValue-test.tsx.snap} | 0 ...bar-test.js.snap => Sidebar-test.tsx.snap} | 3 + .../main/js/apps/component-measures/types.js | 58 --------- .../main/js/apps/component-measures/utils.ts | 9 +- .../overview/components/LeakPeriodLegend.tsx | 3 +- .../apps/overview/components/OverviewApp.tsx | 4 +- .../__tests__/LeakPeriodLegend-test.tsx | 2 +- .../main/js/apps/overview/main/enhance.tsx | 4 +- .../__tests__/ProjectCardLeak-test.tsx | 1 + .../SourceViewer/SourceViewerBase.tsx | 1 - .../main/js/components/charts/BubbleChart.tsx | 28 ++-- .../charts/__tests__/BubbleChart-test.tsx | 2 +- .../__snapshots__/BubbleChart-test.tsx.snap | 4 +- .../src/main/js/components/measure/utils.ts | 3 +- server/sonar-web/src/main/js/helpers/path.ts | 16 +-- .../sonar-web/src/main/js/helpers/periods.ts | 22 +--- 58 files changed, 601 insertions(+), 652 deletions(-) rename server/sonar-web/src/main/js/apps/component-measures/components/{App.js => App.tsx} (74%) rename server/sonar-web/src/main/js/apps/component-measures/components/{AppContainer.js => AppContainer.tsx} (53%) rename server/sonar-web/src/main/js/apps/component-measures/components/{FilesCounter.js => FilesCounter.tsx} (88%) rename server/sonar-web/src/main/js/apps/component-measures/components/{MeasureHeader.js => MeasureHeader.tsx} (86%) rename server/sonar-web/src/main/js/apps/component-measures/components/{MeasureOverview.js => MeasureOverview.tsx} (81%) rename server/sonar-web/src/main/js/apps/component-measures/components/{MeasureOverviewContainer.js => MeasureOverviewContainer.tsx} (72%) rename server/sonar-web/src/main/js/apps/component-measures/components/{MetricNotFound.js => MetricNotFound.tsx} (89%) rename server/sonar-web/src/main/js/apps/component-measures/components/{PageActions.js => PageActions.tsx} (87%) rename server/sonar-web/src/main/js/apps/component-measures/components/__tests__/{App-test.js => App-test.tsx} (78%) rename server/sonar-web/src/main/js/apps/component-measures/components/__tests__/{FilesCounter-test.js => FilesCounter-test.tsx} (90%) rename server/sonar-web/src/main/js/apps/component-measures/components/__tests__/{MeasureHeader-test.js => MeasureHeader-test.tsx} (79%) rename server/sonar-web/src/main/js/apps/component-measures/components/__tests__/{PageActions-test.js => PageActions-test.tsx} (93%) rename server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/{App-test.js.snap => App-test.tsx.snap} (67%) rename server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/{FilesCounter-test.js.snap => FilesCounter-test.tsx.snap} (100%) rename server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/{MeasureHeader-test.js.snap => MeasureHeader-test.tsx.snap} (85%) rename server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/{PageActions-test.js.snap => PageActions-test.tsx.snap} (100%) rename server/sonar-web/src/main/js/apps/component-measures/drilldown/{BubbleChart.js => BubbleChart.tsx} (75%) rename server/sonar-web/src/main/js/apps/component-measures/drilldown/{ComponentCell.js => ComponentCell.tsx} (68%) rename server/sonar-web/src/main/js/apps/component-measures/drilldown/{ComponentsListRow.js => ComponentsListRow.tsx} (78%) rename server/sonar-web/src/main/js/apps/component-measures/drilldown/{MeasureCell.js => MeasureCell.tsx} (76%) rename server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/{MeasureCell-test.js => MeasureCell-test.tsx} (89%) rename server/sonar-web/src/main/js/apps/component-measures/sidebar/{DomainFacet.js => DomainFacet.tsx} (85%) rename server/sonar-web/src/main/js/apps/component-measures/sidebar/{FacetMeasureValue.js => FacetMeasureValue.tsx} (88%) rename server/sonar-web/src/main/js/apps/component-measures/sidebar/{ProjectOverviewFacet.js => ProjectOverviewFacet.tsx} (82%) rename server/sonar-web/src/main/js/apps/component-measures/sidebar/{Sidebar.js => Sidebar.tsx} (70%) rename server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/{DomainFacet-test.js => DomainFacet-test.tsx} (91%) rename server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/{FacetMeasureValue-test.js => FacetMeasureValue-test.tsx} (96%) rename server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/{Sidebar-test.js => Sidebar-test.tsx} (91%) rename server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/__snapshots__/{DomainFacet-test.js.snap => DomainFacet-test.tsx.snap} (94%) rename server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/__snapshots__/{FacetMeasureValue-test.js.snap => FacetMeasureValue-test.tsx.snap} (100%) rename server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/__snapshots__/{Sidebar-test.js.snap => Sidebar-test.tsx.snap} (96%) delete mode 100644 server/sonar-web/src/main/js/apps/component-measures/types.js diff --git a/server/sonar-web/src/main/js/api/components.ts b/server/sonar-web/src/main/js/api/components.ts index 39805e25649..df793ee8a87 100644 --- a/server/sonar-web/src/main/js/api/components.ts +++ b/server/sonar-web/src/main/js/api/components.ts @@ -114,14 +114,14 @@ export function getComponentTree( metricKeys: metrics.join(','), strategy }); - return getJSON(url, data); + return getJSON(url, data).catch(throwGlobalError); } export function getChildren( componentKey: string, metrics: string[] = [], additional: RequestData = {} -): Promise { +) { return getComponentTree('children', componentKey, metrics, additional); } @@ -129,14 +129,14 @@ export function getComponentLeaves( componentKey: string, metrics: string[] = [], additional: RequestData = {} -): Promise { +) { return getComponentTree('leaves', componentKey, metrics, additional); } export function getComponent( data: { componentKey: string; metricKeys: string } & BranchParameters ): Promise { - return getJSON('/api/measures/component', data).then(r => r.component); + return getJSON('/api/measures/component', data).then(r => r.component, throwGlobalError); } export interface TreeComponent extends LightComponent { @@ -165,7 +165,7 @@ export function getTree(data: { } export function getComponentShow(data: { component: string } & BranchParameters): Promise { - return getJSON('/api/components/show', data); + return getJSON('/api/components/show', data).catch(throwGlobalError); } export function getParents(component: string): Promise { diff --git a/server/sonar-web/src/main/js/api/measures.ts b/server/sonar-web/src/main/js/api/measures.ts index 1102c39870c..378c5079242 100644 --- a/server/sonar-web/src/main/js/api/measures.ts +++ b/server/sonar-web/src/main/js/api/measures.ts @@ -20,14 +20,14 @@ import { getJSON, RequestData, postJSON, post } from '../helpers/request'; import throwGlobalError from '../app/utils/throwGlobalError'; import { - Metric, CustomMeasure, - Paging, BranchParameters, Measure, - MeasurePeriod + Metric, + Paging, + Period, + PeriodMeasure } from '../app/types'; -import { Period } from '../helpers/periods'; export function getMeasures( data: { componentKey: string; metricKeys: string } & BranchParameters @@ -55,7 +55,7 @@ export function getMeasuresAndMeta( interface MeasuresForProjects { component: string; metric: string; - periods?: MeasurePeriod[]; + periods?: PeriodMeasure[]; value?: string; } diff --git a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.tsx b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.tsx index 31983bc3913..267ec368cb1 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.tsx @@ -152,12 +152,6 @@ export default class ComponentNavMenu extends React.PureComponent { } renderComponentMeasuresLink() { - const { branchLike } = this.props; - - if (isShortLivingBranch(branchLike) || isPullRequest(branchLike)) { - return null; - } - return (
  • +
  • + + layout.measures + +
  • , - branchLike?: { id?: string; name: string } - ) => Promise<{ component: Component, measures: Array, leakPeriod: ?Period }>, - fetchMetrics: () => void, - metrics: { [string]: Metric }, - metricsKey: Array, - router: { - push: ({ pathname: string, query?: RawQuery }) => void - } -|}; */ + metricsKey: string[], + branchLike?: BranchLike + ) => Promise<{ component: ComponentMeasure; measures: MeasureEnhanced[]; leakPeriod?: Period }>; + fetchMetrics: () => void; + metrics: { [metric: string]: Metric }; + metricsKey: string[]; + router: InjectedRouter; +} -/*:: type State = {| - loading: boolean, - measures: Array, - leakPeriod: ?Period -|}; */ +interface State { + loading: boolean; + measures: MeasureEnhanced[]; + leakPeriod?: Period; +} -export default class App extends React.PureComponent { - /*:: mounted: boolean; */ - /*:: props: Props; */ - /*:: state: State; */ +export default class App extends React.PureComponent { + mounted = false; - constructor(props /*: Props */) { + constructor(props: Props) { super(props); - this.state = { - loading: true, - measures: [], - leakPeriod: null - }; + this.state = { loading: true, measures: [] }; } componentDidMount() { this.mounted = true; - // $FlowFixMe + document.body.classList.add('white-page'); - // $FlowFixMe document.documentElement.classList.add('white-page'); - this.props.fetchMetrics(); - this.fetchMeasures(this.props); - key.setScope('measures-files'); const footer = document.getElementById('footer'); if (footer) { footer.classList.add('page-footer-with-sidebar'); } + + key.setScope('measures-files'); + this.props.fetchMetrics(); + this.fetchMeasures(this.props); } - componentWillReceiveProps(nextProps /*: Props */) { + componentWillReceiveProps(nextProps: Props) { if ( !isSameBranchLike(nextProps.branchLike, this.props.branchLike) || nextProps.component.key !== this.props.component.key || @@ -106,27 +103,31 @@ export default class App extends React.PureComponent { componentWillUnmount() { this.mounted = false; - // $FlowFixMe + document.body.classList.remove('white-page'); - // $FlowFixMe document.documentElement.classList.remove('white-page'); - key.deleteScope('measures-files'); + const footer = document.getElementById('footer'); if (footer) { footer.classList.remove('page-footer-with-sidebar'); } + + key.deleteScope('measures-files'); } - fetchMeasures = ({ branchLike, component, fetchMeasures, metrics } /*: Props */) => { + fetchMeasures = ({ branchLike, component, fetchMeasures, metrics }: Props) => { this.setState({ loading: true }); const filteredKeys = getDisplayMetrics(Object.values(metrics)).map(metric => metric.key); + fetchMeasures(component.key, filteredKeys, branchLike).then( ({ measures, leakPeriod }) => { if (this.mounted) { this.setState({ loading: false, leakPeriod, - measures: measures.filter(measure => measure.value != null || measure.leak != null) + measures: measures.filter( + measure => measure.value !== undefined || measure.leak !== undefined + ) }); } }, @@ -138,7 +139,7 @@ export default class App extends React.PureComponent { ); }; - updateQuery = (newQuery /*: Query */) => { + updateQuery = (newQuery: Partial) => { const query = serializeQuery({ ...parseQuery(this.props.location.query), ...newQuery @@ -153,19 +154,16 @@ export default class App extends React.PureComponent { }); }; - getHelmetTitle = ( - metric /*: Metric */, - query /*: {metric: string, selected: string, view: string }*/ - ) => { - if (metric == null && hasBubbleChart(query.metric)) { - return isProjectOverview(query.metric) + getHelmetTitle = (metric?: Metric) => { + if (metric && hasBubbleChart(metric.key)) { + return isProjectOverview(metric.key) ? translate('component_measures.overview.project_overview.facet') : translateWithParameters( 'component_measures.domain_x_overview', - getLocalizedMetricDomain(query.metric) + getLocalizedMetricDomain(metric.key) ); } - return metric != null ? metric.name : translate('layout.measures'); + return metric ? metric.name : translate('layout.measures'); }; render() { @@ -180,7 +178,7 @@ export default class App extends React.PureComponent { return (
    - + {({ top }) => ( @@ -198,7 +196,7 @@ export default class App extends React.PureComponent { )} - {metric != null && ( + {metric && ( )} - {metric == null && + {!metric && hasBubbleChart(query.metric) && ( ({ +interface StateToProps { + currentUser: CurrentUser; + metrics: { [metric: string]: Metric }; + metricsKey: string[]; +} + +interface DispatchToProps { + fetchMeasures: ( + component: string, + metricsKey: string[], + branchLike?: BranchLike + ) => Promise<{ component: ComponentMeasure; measures: MeasureEnhanced[]; leakPeriod?: Period }>; + fetchMetrics: () => void; +} + +interface OwnProps { + branchLike?: BranchLike; + component: ComponentMeasure; +} + +const mapStateToProps = (state: any): StateToProps => ({ currentUser: getCurrentUser(state), metrics: getMetrics(state), metricsKey: getMetricsKey(state) }); -function banQualityGate(component /*: Component */) /*: Array */ { - const bannedMetrics = []; - if (!['VW', 'SVW'].includes(component.qualifier)) { +function banQualityGate({ measures = [], qualifier }: ComponentMeasure): Measure[] { + const bannedMetrics: string[] = []; + if (!['VW', 'SVW'].includes(qualifier)) { bannedMetrics.push('alert_status'); } - if (component.qualifier === 'APP') { + if (qualifier === 'APP') { bannedMetrics.push('releasability_rating', 'releasability_effort'); } - return component.measures.filter(measure => !bannedMetrics.includes(measure.metric)); + return measures.filter(measure => !bannedMetrics.includes(measure.metric)); } -const fetchMeasures = ( - component /*: string */, - metricsKey /*: Array */, - branchLike /*: { id?: string; name: string } | void */ -) => (dispatch, getState) => { +const fetchMeasures = (component: string, metricsKey: string[], branchLike?: BranchLike) => ( + _dispatch: Dispatch, + getState: () => any +) => { if (metricsKey.length <= 0) { return Promise.resolve({ component: {}, measures: [], leakPeriod: null }); } @@ -60,21 +86,23 @@ const fetchMeasures = ( return getMeasuresAndMeta(component, metricsKey, { additionalFields: 'periods', ...getBranchLikeQuery(branchLike) - }).then(r => { - const measures = banQualityGate(r.component).map(measure => + }).then(({ component, periods }) => { + const measures = banQualityGate(component).map(measure => enhanceMeasure(measure, getMetrics(getState())) ); const newBugs = measures.find(measure => measure.metric.key === 'new_bugs'); - const applicationPeriods = newBugs ? [{ index: 1 }] : []; - const periods = r.component.qualifier === 'APP' ? applicationPeriods : r.periods; - return { component: r.component, measures, leakPeriod: getLeakPeriod(periods) }; + const applicationPeriods = newBugs ? [{ index: 1 } as Period] : []; + const leakPeriod = getLeakPeriod(component.qualifier === 'APP' ? applicationPeriods : periods); + return { component, measures, leakPeriod }; }, throwGlobalError); }; -const mapDispatchToProps = { fetchMeasures, fetchMetrics }; +const mapDispatchToProps: DispatchToProps = { fetchMeasures: fetchMeasures as any, fetchMetrics }; -export default connect( - mapStateToProps, - mapDispatchToProps -)(withRouter(App)); +export default withRouter( + connect( + mapStateToProps, + mapDispatchToProps + )(App) +); diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/FilesCounter.js b/server/sonar-web/src/main/js/apps/component-measures/components/FilesCounter.tsx similarity index 88% rename from server/sonar-web/src/main/js/apps/component-measures/components/FilesCounter.js rename to server/sonar-web/src/main/js/apps/component-measures/components/FilesCounter.tsx index 94ab8b016f9..37e794a6ee8 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/FilesCounter.js +++ b/server/sonar-web/src/main/js/apps/component-measures/components/FilesCounter.tsx @@ -17,22 +17,21 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; +import * as React from 'react'; import { translate } from '../../../helpers/l10n'; import { formatMeasure } from '../../../helpers/measures'; -/*:: type Props = { - className?: string, - current: ?number, - total: number -}; */ +interface Props { + className?: string; + current?: number; + total: number; +} -export default function FilesCounter({ className, current, total } /*: Props */) { +export default function FilesCounter({ className, current, total }: Props) { return ( - {current != null && ( + {current !== undefined && ( {formatMeasure(current, 'INT')} {' / '} diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/LeakPeriodLegend.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/LeakPeriodLegend.tsx index 8c61f4e87b7..f1d8747a2c8 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/LeakPeriodLegend.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/components/LeakPeriodLegend.tsx @@ -24,10 +24,10 @@ import DateFromNow from '../../../components/intl/DateFromNow'; import DateFormatter, { longFormatterOption } from '../../../components/intl/DateFormatter'; import DateTimeFormatter from '../../../components/intl/DateTimeFormatter'; import Tooltip from '../../../components/controls/Tooltip'; -import { getPeriodLabel, getPeriodDate, Period, PeriodMode } from '../../../helpers/periods'; +import { getPeriodLabel, getPeriodDate } from '../../../helpers/periods'; import { translate, translateWithParameters } from '../../../helpers/l10n'; import { differenceInDays } from '../../../helpers/dates'; -import { ComponentMeasure } from '../../../app/types'; +import { ComponentMeasure, Period, PeriodMode } from '../../../app/types'; interface Props { className?: string; diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.tsx index 9472307bc7e..7eee54217b6 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.tsx @@ -44,10 +44,10 @@ import { isLoggedIn, Metric, Paging, - MeasureEnhanced + MeasureEnhanced, + Period } from '../../../app/types'; import { RequestData } from '../../../helpers/request'; -import { Period } from '../../../helpers/periods'; interface Props { branchLike?: BranchLike; @@ -328,10 +328,8 @@ export default class MeasureContent extends React.PureComponent { diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContentContainer.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContentContainer.tsx index 3c3a636538f..c8a1301038d 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContentContainer.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContentContainer.tsx @@ -26,9 +26,9 @@ import { Metric, BranchLike, CurrentUser, - MeasureEnhanced + MeasureEnhanced, + Period } from '../../../app/types'; -import { Period } from '../../../helpers/periods'; interface Props { branchLike?: BranchLike; 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.tsx similarity index 86% rename from server/sonar-web/src/main/js/apps/component-measures/components/MeasureHeader.js rename to server/sonar-web/src/main/js/apps/component-measures/components/MeasureHeader.tsx index c39214fba0b..61840e12d62 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.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 { Link } from 'react-router'; import LeakPeriodLegend from './LeakPeriodLegend'; import HistoryIcon from '../../../components/icons-components/HistoryIcon'; @@ -29,21 +28,18 @@ import Tooltip from '../../../components/controls/Tooltip'; import { getLocalizedMetricName, translate } from '../../../helpers/l10n'; import { getMeasureHistoryUrl } from '../../../helpers/urls'; import { isDiffMetric } from '../../../helpers/measures'; -/*:: import type { Component, Period } from '../types'; */ -/*:: import type { MeasureEnhanced } from '../../../components/measure/types'; */ -/*:: import type { Metric } from '../../../app/flow-types'; */ +import { MeasureEnhanced, Metric, ComponentMeasure, BranchLike, Period } from '../../../app/types'; -/*:: type Props = {| - branchLike?: { id?: string; name: string }, - component: Component, - components: Array, - leakPeriod?: Period, - measure?: MeasureEnhanced, - metric: Metric, - secondaryMeasure: ?MeasureEnhanced -|}; */ +interface Props { + branchLike?: BranchLike; + component: ComponentMeasure; + leakPeriod?: Period; + measure?: MeasureEnhanced; + metric: Metric; + secondaryMeasure?: MeasureEnhanced; +} -export default function MeasureHeader(props /*: Props*/) { +export default function MeasureHeader(props: Props) { const { branchLike, component, leakPeriod, measure, metric, secondaryMeasure } = props; const isDiff = isDiffMetric(metric.key); const hasHistory = component.qualifier !== 'FIL' && component.qualifier !== 'UTS'; @@ -83,13 +79,14 @@ export default function MeasureHeader(props /*: Props*/) { )}
    - {leakPeriod != null && ( + {leakPeriod && ( )}
    {secondaryMeasure && - secondaryMeasure.metric.key === 'ncloc_language_distribution' && ( + secondaryMeasure.metric.key === 'ncloc_language_distribution' && + secondaryMeasure.value !== undefined && (
    void, - updateSelected: string => void -|}; */ +interface Props { + branchLike?: BranchLike; + className?: string; + component: ComponentMeasure; + currentUser: CurrentUser; + domain: string; + leakPeriod?: Period; + loading: boolean; + metrics: { [metric: string]: Metric }; + rootComponent: ComponentMeasure; + updateLoading: (param: { [key: string]: boolean }) => void; + updateSelected: (component: string) => void; +} -/*:: type State = { - components: Array, - paging?: Paging -}; */ +interface State { + components: ComponentMeasureEnhanced[]; + paging?: Paging; +} const BUBBLES_LIMIT = 500; -export default class MeasureOverview extends React.PureComponent { - /*:: mounted: boolean; */ - /*:: props: Props; */ - state /*: State */ = { - components: [], - paging: null - }; +export default class MeasureOverview extends React.PureComponent { + mounted = false; + state: State = { components: [] }; componentDidMount() { this.mounted = true; this.fetchComponents(this.props); } - componentWillReceiveProps(nextProps /*: Props */) { + componentWillReceiveProps(nextProps: Props) { if ( nextProps.component !== this.props.component || nextProps.metrics !== this.props.metrics || @@ -80,16 +82,16 @@ export default class MeasureOverview extends React.PureComponent { this.mounted = false; } - fetchComponents = (props /*: Props */) => { + fetchComponents = (props: Props) => { const { branchLike, component, domain, metrics } = props; if (isFileType(component)) { - this.setState({ components: [], paging: null }); + this.setState({ components: [], paging: undefined }); return; } const { x, y, size, colors } = getBubbleMetrics(domain, metrics); const metricsKey = [x.key, y.key, size.key]; if (colors) { - metricsKey.push(colors.map(metric => metric.key)); + metricsKey.push(...colors.map(metric => metric.key)); } const options = { ...getBranchLikeQuery(branchLike), @@ -105,7 +107,9 @@ export default class MeasureOverview extends React.PureComponent { if (domain === this.props.domain) { if (this.mounted) { this.setState({ - components: r.components.map(component => enhanceComponent(component, null, metrics)), + components: r.components.map(component => + enhanceComponent(component, undefined, metrics) + ), paging: r.paging }); } @@ -171,7 +175,7 @@ export default class MeasureOverview extends React.PureComponent {
    - {leakPeriod != null && ( + {leakPeriod && ( )}
    diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverviewContainer.js b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverviewContainer.tsx similarity index 72% rename from server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverviewContainer.js rename to server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverviewContainer.tsx index db4caed4947..642cde71e20 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverviewContainer.js +++ b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverviewContainer.tsx @@ -17,49 +17,43 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; +import * as React from 'react'; +import { InjectedRouter } from 'react-router'; import MeasureOverview from './MeasureOverview'; import { getComponentShow } from '../../../api/components'; import { getProjectUrl } from '../../../helpers/urls'; -import { isViewType } from '../utils'; +import { isViewType, Query } from '../utils'; import { getBranchLikeQuery } from '../../../helpers/branches'; -/*:: import type { Component, Period, Query } from '../types'; */ -/*:: import type { RawQuery } from '../../../helpers/query'; */ -/*:: import type { Metric } from '../../../app/flow-types'; */ +import { BranchLike, ComponentMeasure, CurrentUser, Metric, Period } from '../../../app/types'; -/*:: type Props = {| - branchLike?: { id?: string; name: string }, - className?: string, - rootComponent: Component, - currentUser: { isLoggedIn: boolean }, - domain: string, - leakPeriod: Period, - metrics: { [string]: Metric }, - router: { - push: ({ pathname: string, query?: RawQuery }) => void - }, - selected: ?string, - updateQuery: Query => void -|}; */ +interface Props { + branchLike?: BranchLike; + className?: string; + currentUser: CurrentUser; + domain: string; + leakPeriod?: Period; + metrics: { [metric: string]: Metric }; + rootComponent: ComponentMeasure; + router: InjectedRouter; + selected?: string; + updateQuery: (query: Partial) => void; +} -/*:: type State = { - component: ?Component, - loading: { - component: boolean, - bubbles: boolean - } -}; */ +interface LoadingState { + bubbles: boolean; + component: boolean; +} -export default class MeasureOverviewContainer extends React.PureComponent { - /*:: mounted: boolean; */ - /*:: props: Props; */ - state /*: State */ = { - component: null, - loading: { - component: false, - bubbles: false - } +interface State { + component?: ComponentMeasure; + loading: LoadingState; +} + +export default class MeasureOverviewContainer extends React.PureComponent { + mounted = false; + + state: State = { + loading: { bubbles: false, component: false } }; componentDidMount() { @@ -67,7 +61,7 @@ export default class MeasureOverviewContainer extends React.PureComponent { this.fetchComponent(this.props); } - componentWillReceiveProps(nextProps /*: Props */) { + componentWillReceiveProps(nextProps: Props) { const { component } = this.state; const componentChanged = !component || @@ -82,7 +76,7 @@ export default class MeasureOverviewContainer extends React.PureComponent { this.mounted = false; } - fetchComponent = ({ branchLike, rootComponent, selected } /*: Props */) => { + fetchComponent = ({ branchLike, rootComponent, selected }: Props) => { if (!selected || rootComponent.key === selected) { this.setState({ component: rootComponent }); this.updateLoading({ component: false }); @@ -100,18 +94,18 @@ export default class MeasureOverviewContainer extends React.PureComponent { ); }; - updateLoading = (loading /*: { [string]: boolean } */) => { + updateLoading = (loading: Partial) => { if (this.mounted) { this.setState(state => ({ loading: { ...state.loading, ...loading } })); } }; - updateSelected = (component /*: string */) => { + updateSelected = (component: string) => { if (this.state.component && isViewType(this.state.component)) { this.props.router.push(getProjectUrl(component)); } else { this.props.updateQuery({ - selected: component !== this.props.rootComponent.key ? component : null + selected: component !== this.props.rootComponent.key ? component : undefined }); } }; diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MetricNotFound.js b/server/sonar-web/src/main/js/apps/component-measures/components/MetricNotFound.tsx similarity index 89% rename from server/sonar-web/src/main/js/apps/component-measures/components/MetricNotFound.js rename to server/sonar-web/src/main/js/apps/component-measures/components/MetricNotFound.tsx index e51270786d1..6009d74a6c9 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/MetricNotFound.js +++ b/server/sonar-web/src/main/js/apps/component-measures/components/MetricNotFound.tsx @@ -17,11 +17,10 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; +import * as React from 'react'; import { translate } from '../../../helpers/l10n'; -export default function MetricNotFound({ className } /*: { className?: string } */) { +export default function MetricNotFound({ className }: { className?: string }) { return (
    {translate('component_measures.not_found')}
    diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/PageActions.js b/server/sonar-web/src/main/js/apps/component-measures/components/PageActions.tsx similarity index 87% rename from server/sonar-web/src/main/js/apps/component-measures/components/PageActions.js rename to server/sonar-web/src/main/js/apps/component-measures/components/PageActions.tsx index 4b26994fa81..75078edb21a 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/PageActions.js +++ b/server/sonar-web/src/main/js/apps/component-measures/components/PageActions.tsx @@ -17,23 +17,22 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; +import * as React from 'react'; import FilesCounter from './FilesCounter'; import { translate } from '../../../helpers/l10n'; -/*:: import type { Paging } from '../types'; */ +import { Paging } from '../../../app/types'; -/*:: type Props = {| - current: ?number, - isFile: ?boolean, - paging: ?Paging, - totalLoadedComponents?: number, - view?: string -|}; */ +interface Props { + current?: number; + isFile?: boolean; + paging?: Paging; + totalLoadedComponents?: number; + view?: string; +} -export default function PageActions(props /*: Props */) { +export default function PageActions(props: Props) { const { isFile, paging, totalLoadedComponents } = props; - const showShortcuts = ['list', 'tree'].includes(props.view); + const showShortcuts = props.view && ['list', 'tree'].includes(props.view); return (
    {!isFile && showShortcuts && renderShortcuts()} diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/App-test.js b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/App-test.tsx similarity index 78% rename from server/sonar-web/src/main/js/apps/component-measures/components/__tests__/App-test.js rename to server/sonar-web/src/main/js/apps/component-measures/components/__tests__/App-test.tsx index 5b19497408a..f0202595073 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/App-test.js +++ b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/App-test.tsx @@ -17,36 +17,42 @@ * 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'; +/* eslint-disable camelcase */ +import * as React from 'react'; import { shallow } from 'enzyme'; import App from '../App'; +const COMPONENT = { key: 'foo', name: 'Foo', qualifier: 'TRK' }; + const METRICS = { lines_to_cover: { + id: '1', key: 'lines_to_cover', type: 'INT', name: 'Lines to Cover', domain: 'Coverage' }, - coverage: { key: 'coverage', type: 'PERCENT', name: 'Coverage', domain: 'Coverage' }, + coverage: { id: '2', key: 'coverage', type: 'PERCENT', name: 'Coverage', domain: 'Coverage' }, duplicated_lines_density: { + id: '3', key: 'duplicated_lines_density', type: 'PERCENT', name: 'Duplicated Lines (%)', domain: 'Duplications' }, - new_bugs: { key: 'new_bugs', type: 'INT', name: 'New Bugs', domain: 'Reliability' } + new_bugs: { id: '4', key: 'new_bugs', type: 'INT', name: 'New Bugs', domain: 'Reliability' } }; const PROPS = { branch: { isMain: true, name: 'master' }, - component: { key: 'foo' }, + component: COMPONENT, + currentUser: { isLoggedIn: false }, location: { pathname: '/component_measures', query: { metric: 'coverage' } }, - fetchMeasures: () => Promise.resolve({ measures: [] }), - fetchMetrics: () => {}, + fetchMeasures: jest.fn().mockResolvedValue({ component: COMPONENT, measures: [] }), + fetchMetrics: jest.fn(), metrics: METRICS, metricsKey: ['lines_to_cover', 'coverage', 'duplicated_lines_density', 'new_bugs'], - router: { push: () => {} } + router: { push: jest.fn() } as any }; it('should render correctly', () => { diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/FilesCounter-test.js b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/FilesCounter-test.tsx similarity index 90% rename from server/sonar-web/src/main/js/apps/component-measures/components/__tests__/FilesCounter-test.js rename to server/sonar-web/src/main/js/apps/component-measures/components/__tests__/FilesCounter-test.tsx index 1cedf4a4238..8d02bc65a1c 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/FilesCounter-test.js +++ b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/FilesCounter-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 FilesCounter from '../FilesCounter'; @@ -26,5 +26,5 @@ it('should display x files on y total', () => { }); it('should display only total of files', () => { - expect(shallow()).toMatchSnapshot(); + expect(shallow()).toMatchSnapshot(); }); diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/LeakPeriodLegend-test.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/LeakPeriodLegend-test.tsx index 475b6e17bc6..be616b62017 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/LeakPeriodLegend-test.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/LeakPeriodLegend-test.tsx @@ -20,9 +20,8 @@ import * as React from 'react'; import { shallow } from 'enzyme'; import LeakPeriodLegend from '../LeakPeriodLegend'; -import { PeriodMode, Period } from '../../../../helpers/periods'; import { differenceInDays } from '../../../../helpers/dates'; -import { ComponentMeasure } from '../../../../app/types'; +import { ComponentMeasure, Period, PeriodMode } from '../../../../app/types'; jest.mock('../../../../helpers/dates', () => { const dates = require.requireActual('../../../../helpers/dates'); diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureHeader-test.js b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureHeader-test.tsx similarity index 79% rename from server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureHeader-test.js rename to server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureHeader-test.tsx index 5582f01e151..965af1b5474 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureHeader-test.js +++ b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureHeader-test.tsx @@ -17,11 +17,13 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import React from 'react'; +import * as React from 'react'; import { shallow } from 'enzyme'; import MeasureHeader from '../MeasureHeader'; +import { PeriodMode } from '../../../../app/types'; const METRIC = { + id: '1', key: 'reliability_rating', type: 'RATING', name: 'Reliability Rating' @@ -35,6 +37,7 @@ const MEASURE = { }; const LEAK_METRIC = { + id: '2', key: 'new_reliability_rating', type: 'RATING', name: 'Reliability Rating on New Code' @@ -49,28 +52,23 @@ const LEAK_MEASURE = { const SECONDARY = { value: 'java=175123;js=26382', metric: { + id: '3', key: 'ncloc_language_distribution', type: 'DATA', name: 'Lines of Code Per Language' - }, - leak: null + } }; const PROPS = { - component: { key: 'foo', qualifier: 'TRK' }, - components: [], - handleSelect: () => {}, + component: { key: 'foo', name: 'Foo', qualifier: 'TRK' }, leakPeriod: { date: '2017-05-16T13:50:02+0200', index: 1, - mode: 'previous_version', + mode: PeriodMode.PreviousVersion, parameter: '6,4' }, measure: MEASURE, - metric: METRIC, - paging: null, - secondaryMeasure: null, - selectedIdx: null + metric: METRIC }; it('should render correctly', () => { @@ -109,20 +107,6 @@ it('should display secondary measure too', () => { expect(wrapper.find('Connect(LanguageDistribution)')).toHaveLength(1); }); -it('should display correctly for open file', () => { - const wrapper = shallow( - - ); - expect(wrapper.find('.measure-details-primary-actions')).toMatchSnapshot(); - wrapper.setProps({ components: [{ key: 'foo' }, { key: 'bar' }] }); - expect(wrapper.find('.measure-details-primary-actions')).toMatchSnapshot(); -}); - it('should work with measure without value', () => { expect(shallow()).toMatchSnapshot(); }); diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/PageActions-test.js b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/PageActions-test.tsx similarity index 93% rename from server/sonar-web/src/main/js/apps/component-measures/components/__tests__/PageActions-test.js rename to server/sonar-web/src/main/js/apps/component-measures/components/__tests__/PageActions-test.tsx index 067ecb4d78d..016e36de105 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/PageActions-test.js +++ b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/PageActions-test.tsx @@ -17,10 +17,16 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import React from 'react'; +import * as React from 'react'; import { shallow } from 'enzyme'; import PageActions from '../PageActions'; +const PAGING = { + pageIndex: 1, + pageSize: 100, + total: 120 +}; + it('should display correctly for a project', () => { expect( shallow() @@ -46,7 +52,7 @@ it('should display the total of files', () => { @@ -57,7 +63,7 @@ it('should display the total of files', () => { diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/App-test.js.snap b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/App-test.tsx.snap similarity index 67% rename from server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/App-test.js.snap rename to server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/App-test.tsx.snap index 70583de970f..b0436ae2851 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/App-test.js.snap +++ b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/App-test.tsx.snap @@ -18,11 +18,37 @@ exports[`should render correctly 1`] = ` /> - -
    -`; - -exports[`should display correctly for open file 2`] = ` -
    - -
    -`; - exports[`should render correctly 1`] = `
    , - domain: string, - metrics: { [string]: Metric }, - updateSelected: string => void -|}; */ - -export default class BubbleChart extends React.PureComponent { - /*:: props: Props; */ +interface Props { + component: ComponentMeasure; + components: ComponentMeasureEnhanced[]; + domain: string; + metrics: { [metric: string]: Metric }; + updateSelected: (component: string) => void; +} - getMeasureVal = (component /*: ComponentEnhanced */, metric /*: Metric */) => { +export default class BubbleChart extends React.PureComponent { + getMeasureVal = (component: ComponentMeasureEnhanced, metric: Metric) => { const measure = component.measures.find(measure => measure.metric.key === metric.key); - if (measure) { - return Number(isDiffMetric(metric.key) ? measure.leak : measure.value); + if (!measure) { + return undefined; } + return Number(isDiffMetric(metric.key) ? measure.leak : measure.value); }; getTooltip( - componentName /*: string */, - values /*: { - x: number, - y: number, - size: number, - colors: ?Array - }*/, - metrics /*: { - x: Metric , - y: Metric , - size: Metric , - colors: ?Array - }*/ + componentName: string, + values: { x: number; y: number; size: number; colors?: Array }, + metrics: { x: Metric; y: Metric; size: Metric; colors?: Array } ) { const inner = [ componentName, @@ -76,10 +63,11 @@ export default class BubbleChart extends React.PureComponent { `${metrics.y.name}: ${formatMeasure(values.y, metrics.y.type)}`, `${metrics.size.name}: ${formatMeasure(values.size, metrics.size.type)}` ]; - if (values.colors && metrics.colors) { - metrics.colors.forEach((metric, idx) => { - // $FlowFixMe colors is always defined at this point - const colorValue = values.colors[idx]; + const { colors: valuesColors } = values; + const { colors: metricColors } = metrics; + if (valuesColors && metricColors) { + metricColors.forEach((metric, idx) => { + const colorValue = valuesColors[idx]; if (colorValue || colorValue === 0) { inner.push(`${metric.name}: ${formatMeasure(colorValue, metric.type)}`); } @@ -97,10 +85,10 @@ export default class BubbleChart extends React.PureComponent { ); } - handleBubbleClick = (component /*: ComponentEnhanced */) => + handleBubbleClick = (component: ComponentMeasureEnhanced) => this.props.updateSelected(component.refKey || component.key); - getDescription(domain /*: string */) { + getDescription(domain: string) { const description = `component_measures.overview.${domain}.description`; const translatedDescription = translate(description); if (description === translatedDescription) { @@ -109,14 +97,7 @@ export default class BubbleChart extends React.PureComponent { return translatedDescription; } - renderBubbleChart( - metrics /*: { - x: Metric , - y: Metric , - size: Metric , - colors: ?Array - }*/ - ) { + renderBubbleChart(metrics: { x: Metric; y: Metric; size: Metric; colors?: Metric[] }) { const items = this.props.components .map(component => { const x = this.getMeasureVal(component, metrics.x); @@ -125,25 +106,27 @@ export default class BubbleChart extends React.PureComponent { const colors = metrics.colors && metrics.colors.map(metric => this.getMeasureVal(component, metric)); if ((!x && x !== 0) || (!y && y !== 0) || (!size && size !== 0)) { - return null; + return undefined; } return { x, y, size, color: - colors != null ? RATING_COLORS[Math.max(...colors.filter(Boolean)) - 1] : undefined, - link: component, + colors !== undefined + ? RATING_COLORS[Math.max(...colors.filter(Boolean) as number[]) - 1] + : undefined, + data: component, tooltip: this.getTooltip(component.name, { x, y, size, colors }, metrics) }; }) - .filter(Boolean); + .filter(Boolean) as BubbleItem[]; - const formatXTick = tick => formatMeasure(tick, metrics.x.type); - const formatYTick = tick => formatMeasure(tick, metrics.y.type); + const formatXTick = (tick: string | number | undefined) => formatMeasure(tick, metrics.x.type); + const formatYTick = (tick: string | number | undefined) => formatMeasure(tick, metrics.y.type); return ( - formatXTick={formatXTick} formatYTick={formatYTick} height={HEIGHT} @@ -155,11 +138,7 @@ export default class BubbleChart extends React.PureComponent { ); } - renderChartHeader( - domain /*: string */, - sizeMetric /*: Metric */, - colorsMetric /*: ?Array */ - ) { + renderChartHeader(domain: string, sizeMetric: Metric, colorsMetric?: Metric[]) { const title = isProjectOverview(domain) ? translate('component_measures.overview', domain, 'title') : translateWithParameters( diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/CodeView.tsx b/server/sonar-web/src/main/js/apps/component-measures/drilldown/CodeView.tsx index 6f74390695b..a07eb4710c2 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/drilldown/CodeView.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/CodeView.tsx @@ -20,8 +20,13 @@ import * as React from 'react'; import * as key from 'keymaster'; import SourceViewer from '../../../components/SourceViewer/SourceViewer'; -import { BranchLike, ComponentMeasure, ComponentMeasureEnhanced, Metric } from '../../../app/types'; -import { Period } from '../../../helpers/periods'; +import { + BranchLike, + ComponentMeasure, + ComponentMeasureEnhanced, + Metric, + Period +} from '../../../app/types'; interface Props { branchLike?: BranchLike; diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentCell.js b/server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentCell.tsx similarity index 68% rename from server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentCell.js rename to server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentCell.tsx index 6ec4749855e..4ddcd665e7b 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentCell.js +++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentCell.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 { Link } from 'react-router'; import LinkIcon from '../../../components/icons-components/LinkIcon'; import QualifierIcon from '../../../components/icons-components/QualifierIcon'; @@ -27,80 +26,72 @@ import { splitPath } from '../../../helpers/path'; import { getPathUrlAsString, getBranchLikeUrl, - getLongLivingBranchUrl, - getComponentDrilldownUrlWithSelection + getComponentDrilldownUrlWithSelection, + getProjectUrl } from '../../../helpers/urls'; import { translate } from '../../../helpers/l10n'; -/*:: import type { Component, ComponentEnhanced } from '../types'; */ -/*:: import type { Metric } from '../../../app/flow-types'; */ +import { BranchLike, ComponentMeasure, ComponentMeasureEnhanced, Metric } from '../../../app/types'; -/*:: type Props = { - branchLike?: { id?: string; name: string }, - component: ComponentEnhanced, - onClick: string => void, - metric: Metric, - rootComponent: Component -}; */ - -export default class ComponentCell extends React.PureComponent { - /*:: props: Props; */ +interface Props { + branchLike?: BranchLike; + component: ComponentMeasureEnhanced; + onClick: (component: string) => void; + metric: Metric; + rootComponent: ComponentMeasure; +} - handleClick = (e /*: MouseEvent */) => { - const isLeftClickEvent = e.button === 0; - const isModifiedEvent = !!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey); +export default class ComponentCell extends React.PureComponent { + handleClick = (event: React.MouseEvent) => { + const isLeftClickEvent = event.button === 0; + const isModifiedEvent = !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey); if (isLeftClickEvent && !isModifiedEvent) { - e.preventDefault(); + event.preventDefault(); this.props.onClick(this.props.component.key); } }; - renderInner() { + renderInner(componentKey: string) { const { component } = this.props; let head = ''; let tail = component.name; - let branch = null; + let branchComponent = null; - if (['DIR', 'FIL', 'UTS'].includes(component.qualifier)) { - const parts = splitPath(component.path); - ({ head, tail } = parts); + if (['DIR', 'FIL', 'UTS'].includes(component.qualifier) && component.path) { + ({ head, tail } = splitPath(component.path)); } if (this.props.rootComponent.qualifier === 'APP') { - branch = ( - + branchComponent = ( + <> {component.branch ? ( - + <> {component.branch} - + ) : ( {translate('branches.main_branch')} )} - + ); } return ( - +   {head.length > 0 && {head}/} {tail} - {branch} + {branchComponent} ); } render() { const { branchLike, component, metric, rootComponent } = this.props; - const to = - this.props.rootComponent.qualifier === 'APP' - ? getLongLivingBranchUrl(component.refKey, component.branch) - : getBranchLikeUrl(component.refKey, branchLike); return (
    - {component.refKey == null ? ( + {!component.refKey ? ( - {this.renderInner()} + {this.renderInner(component.key)} ) : ( + id={'component-measures-component-link-' + component.refKey} + to={ + this.props.rootComponent.qualifier === 'APP' + ? getProjectUrl(component.refKey, component.branch) + : getBranchLikeUrl(component.refKey, branchLike) + }> - {this.renderInner()} + {this.renderInner(component.refKey)} )}
    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.tsx similarity index 78% rename from server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentsListRow.js rename to server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentsListRow.tsx index 49024fe6f07..6ae4c80b619 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.tsx @@ -17,25 +17,23 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; -import classNames from 'classnames'; +import * as React from 'react'; +import * as classNames from 'classnames'; import ComponentCell from './ComponentCell'; import MeasureCell from './MeasureCell'; -/*:: import type { Component, ComponentEnhanced } from '../types'; */ -/*:: import type { Metric } from '../../../app/flow-types'; */ +import { ComponentMeasure, Metric, ComponentMeasureEnhanced, BranchLike } from '../../../app/types'; -/*:: type Props = {| - branchLike?: { id?: string; name: string }, - component: ComponentEnhanced, - isSelected: boolean, - onClick: string => void, - otherMetrics: Array, - metric: Metric, - rootComponent: Component -|}; */ +interface Props { + branchLike?: BranchLike; + component: ComponentMeasureEnhanced; + isSelected: boolean; + onClick: (component: string) => void; + otherMetrics: Metric[]; + metric: Metric; + rootComponent: ComponentMeasure; +} -export default function ComponentsListRow(props /*: Props */) { +export default function ComponentsListRow(props: Props) { const { branchLike, component, rootComponent } = props; const otherMeasures = props.otherMetrics.map(metric => { const measure = component.measures.find(measure => measure.metric.key === metric.key); 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.tsx similarity index 76% rename from server/sonar-web/src/main/js/apps/component-measures/drilldown/MeasureCell.js rename to server/sonar-web/src/main/js/apps/component-measures/drilldown/MeasureCell.tsx index 8a984c5001f..951bb590f62 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.tsx @@ -17,22 +17,19 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; +import * as React from 'react'; import Measure from '../../../components/measure/Measure'; import { isDiffMetric } from '../../../helpers/measures'; -/*:: import type { ComponentEnhanced } from '../types'; */ -/*:: import type { MeasureEnhanced } from '../../../components/measure/types'; */ -/*:: import type { Metric } from '../../../app/flow-types'; */ +import { Metric, MeasureEnhanced, ComponentMeasureEnhanced } from '../../../app/types'; -/*:: type Props = { - component: ComponentEnhanced, - measure?: MeasureEnhanced, - metric: Metric -}; */ +interface Props { + component: ComponentMeasureEnhanced; + measure?: MeasureEnhanced; + metric: Metric; +} -export default function MeasureCell({ component, measure, metric } /*: Props */) { - const getValue = (item /*: { leak?: ?string; value?: string } */) => +export default function MeasureCell({ component, measure, metric }: Props) { + const getValue = (item: { leak?: string; value?: string }) => isDiffMetric(metric.key) ? item.leak : item.value; const value = getValue(measure || component); diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/MeasureCell-test.js b/server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/MeasureCell-test.tsx similarity index 89% rename from server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/MeasureCell-test.js rename to server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/MeasureCell-test.tsx index 6897ed8a623..cd0772fd03b 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/MeasureCell-test.js +++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/MeasureCell-test.tsx @@ -17,19 +17,19 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import React from 'react'; +import * as React from 'react'; import { shallow } from 'enzyme'; import MeasureCell from '../MeasureCell'; describe('should correctly take the value', () => { - const renderAndTakeValue = props => + const renderAndTakeValue = (props: any) => shallow() .find('Measure') .prop('value'); it('absolute value', () => { const component = { value: '123' }; - const metric = { key: 'coverage' }; + const metric = { id: '1', key: 'coverage' }; const measure = { value: '567' }; expect(renderAndTakeValue({ component, metric })).toEqual('123'); @@ -38,7 +38,7 @@ describe('should correctly take the value', () => { it('leak value', () => { const component = { leak: '234' }; - const metric = { key: 'new_coverage' }; + const metric = { id: '1', key: 'new_coverage' }; const measure = { leak: '678' }; expect(renderAndTakeValue({ component, metric })).toEqual('234'); diff --git a/server/sonar-web/src/main/js/apps/component-measures/sidebar/DomainFacet.js b/server/sonar-web/src/main/js/apps/component-measures/sidebar/DomainFacet.tsx similarity index 85% rename from server/sonar-web/src/main/js/apps/component-measures/sidebar/DomainFacet.js rename to server/sonar-web/src/main/js/apps/component-measures/sidebar/DomainFacet.tsx index 47338d7ac0d..3257089235c 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/sidebar/DomainFacet.js +++ b/server/sonar-web/src/main/js/apps/component-measures/sidebar/DomainFacet.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 FacetMeasureValue from './FacetMeasureValue'; import BubblesIcon from '../../../components/icons-components/BubblesIcon'; import FacetBox from '../../../components/facet/FacetBox'; @@ -38,26 +37,22 @@ import { getLocalizedMetricName, translate } from '../../../helpers/l10n'; -/*:: import type { MeasureEnhanced } from '../../../components/measure/types'; */ +import { MeasureEnhanced } from '../../../app/types'; -/*:: type Props = {| - onChange: (metric: string) => void, - onToggle: (property: string) => void, - open: boolean, - domain: { name: string, measures: Array }, - selected: string -|}; */ - -export default class DomainFacet extends React.PureComponent { - /*:: props: Props; */ +interface Props { + domain: { name: string; measures: MeasureEnhanced[] }; + onChange: (metric: string) => void; + onToggle: (property: string) => void; + open: boolean; + selected: string; +} - handleHeaderClick = () => this.props.onToggle(this.props.domain.name); +export default class DomainFacet extends React.PureComponent { + handleHeaderClick = () => { + this.props.onToggle(this.props.domain.name); + }; - hasFacetSelected = ( - domain /*: { name: string } */, - measures /*: Array */, - selected /*: string */ - ) => { + hasFacetSelected = (domain: { name: string }, measures: MeasureEnhanced[], selected: string) => { const measureSelected = measures.find(measure => measure.metric.key === selected); const overviewSelected = domain.name === selected && hasBubbleChart(domain.name); return measureSelected || overviewSelected; @@ -73,8 +68,9 @@ export default class DomainFacet extends React.PureComponent { return overviewSelected ? [translate('component_measures.domain_overview')] : []; }; - renderItemFacetStat = (item /*: MeasureEnhanced */) => - hasFacetStat(item.metric.key) ? : null; + renderItemFacetStat = (item: MeasureEnhanced) => { + return hasFacetStat(item.metric.key) ? : null; + }; renderItemsFacet = () => { const { domain, selected } = this.props; @@ -110,6 +106,7 @@ export default class DomainFacet extends React.PureComponent { } onClick={this.props.onChange} stat={this.renderItemFacetStat(item)} + tooltip={translateMetric(item.metric)} value={item.metric.key} /> ) @@ -133,6 +130,7 @@ export default class DomainFacet extends React.PureComponent { } onClick={this.props.onChange} stat={} + tooltip={translate('component_measures.domain_overview')} value={domain.name} /> ); 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.tsx similarity index 88% rename from server/sonar-web/src/main/js/apps/component-measures/sidebar/FacetMeasureValue.js rename to server/sonar-web/src/main/js/apps/component-measures/sidebar/FacetMeasureValue.tsx index 70953357687..1829547f860 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.tsx @@ -17,13 +17,16 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; +import * as React from 'react'; import Measure from '../../../components/measure/Measure'; import { isDiffMetric } from '../../../helpers/measures'; -/*:: import type { MeasureEnhanced } from '../../../components/measure/types'; */ +import { MeasureEnhanced } from '../../../app/types'; -export default function FacetMeasureValue({ measure } /*: { measure: MeasureEnhanced } */) { +interface Props { + measure: MeasureEnhanced; +} + +export default function FacetMeasureValue({ measure }: Props) { if (isDiffMetric(measure.metric.key)) { return (
    void, - selected: string, - value: string -|}; */ +interface Props { + onChange: (metric: string) => void; + selected: string; + value: string; +} -export default function ProjectOverviewFacet({ value, selected, onChange } /*: Props */) { +export default function ProjectOverviewFacet({ value, selected, onChange }: Props) { const facetName = translate('component_measures.overview', value, 'facet'); return ( @@ -39,12 +38,9 @@ export default function ProjectOverviewFacet({ value, selected, onChange } /*: P active={value === selected} disabled={false} key={value} - name={ - - {facetName} - - } + name={{facetName}} onClick={onChange} + tooltip={facetName} value={value} /> diff --git a/server/sonar-web/src/main/js/apps/component-measures/sidebar/Sidebar.js b/server/sonar-web/src/main/js/apps/component-measures/sidebar/Sidebar.tsx similarity index 70% rename from server/sonar-web/src/main/js/apps/component-measures/sidebar/Sidebar.js rename to server/sonar-web/src/main/js/apps/component-measures/sidebar/Sidebar.tsx index 183875dc425..027d3593f4f 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/sidebar/Sidebar.js +++ b/server/sonar-web/src/main/js/apps/component-measures/sidebar/Sidebar.tsx @@ -17,42 +17,39 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; +import * as React from 'react'; import ProjectOverviewFacet from './ProjectOverviewFacet'; import DomainFacet from './DomainFacet'; -import { getDefaultView, groupByDomains, KNOWN_DOMAINS, PROJECT_OVERVEW } from '../utils'; -/*:: import type { MeasureEnhanced } from '../../../components/measure/types'; */ -/*:: import type { Query } from '../types'; */ +import { getDefaultView, groupByDomains, KNOWN_DOMAINS, PROJECT_OVERVEW, Query } from '../utils'; +import { MeasureEnhanced } from '../../../app/types'; -/*:: type Props = {| - measures: Array, - selectedMetric: string, - updateQuery: Query => void -|}; */ - -/*:: type State = {| - openFacets: { [string]: boolean } -|}; */ +interface Props { + measures: MeasureEnhanced[]; + selectedMetric: string; + updateQuery: (query: Query) => void; +} -export default class Sidebar extends React.PureComponent { - /*:: props: Props; */ - /*:: state: State; */ +interface State { + openFacets: { [metric: string]: boolean }; +} - constructor(props /*: Props */) { +export default class Sidebar extends React.PureComponent { + constructor(props: Props) { super(props); this.state = { openFacets: this.getOpenFacets({}, props) }; } - componentWillReceiveProps(nextProps /*: Props */) { + componentWillReceiveProps(nextProps: Props) { if (nextProps.selectedMetric !== this.props.selectedMetric) { - this.setState(state => this.getOpenFacets(state.openFacets, nextProps)); + this.setState(({ openFacets }) => ({ + openFacets: this.getOpenFacets(openFacets, nextProps) + })); } } getOpenFacets = ( - openFacets /*: { [string]: boolean } */, - { measures, selectedMetric } /*: Props */ + openFacets: { [metric: string]: boolean }, + { measures, selectedMetric }: Props ) => { const newOpenFacets = { ...openFacets }; const measure = measures.find(measure => measure.metric.key === selectedMetric); @@ -64,15 +61,15 @@ export default class Sidebar extends React.PureComponent { return newOpenFacets; }; - toggleFacet = (name /*: string */) => { - this.setState(({ openFacets } /*: State */) => ({ + toggleFacet = (name: string) => { + this.setState(({ openFacets }) => ({ openFacets: { ...openFacets, [name]: !openFacets[name] } })); }; - resetSelection = (metric /*: string */) => ({ selected: null, view: getDefaultView(metric) }); + resetSelection = (metric: string) => ({ selected: undefined, view: getDefaultView(metric) }); - changeMetric = (metric /*: string */) => + changeMetric = (metric: string) => this.props.updateQuery({ metric, ...this.resetSelection(metric) }); render() { diff --git a/server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/DomainFacet-test.js b/server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/DomainFacet-test.tsx similarity index 91% rename from server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/DomainFacet-test.js rename to server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/DomainFacet-test.tsx index 551d11831e8..590536b454c 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/DomainFacet-test.js +++ b/server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/DomainFacet-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 DomainFacet from '../DomainFacet'; @@ -27,6 +26,7 @@ const DOMAIN = { measures: [ { metric: { + id: '1', key: 'bugs', type: 'INT', name: 'Bugs', @@ -38,6 +38,7 @@ const DOMAIN = { }, { metric: { + id: '2', key: 'new_bugs', type: 'INT', name: 'New Bugs', @@ -75,7 +76,7 @@ it('should not display subtitles of new measures if there is none', () => { name: 'Reliability', measures: [ { - metric: { key: 'bugs', type: 'INT', name: 'Bugs', domain: 'Reliability' }, + metric: { id: '1', key: 'bugs', type: 'INT', name: 'Bugs', domain: 'Reliability' }, value: '5' } ] @@ -99,7 +100,7 @@ it('should not display subtitles of new measures if there is none, even on last name: 'Reliability', measures: [ { - metric: { key: 'new_bugs', type: 'INT', name: 'New Bugs', domain: 'Reliability' }, + metric: { id: '2', key: 'new_bugs', type: 'INT', name: 'New Bugs', domain: 'Reliability' }, value: '5' } ] diff --git a/server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/FacetMeasureValue-test.js b/server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/FacetMeasureValue-test.tsx similarity index 96% rename from server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/FacetMeasureValue-test.js rename to server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/FacetMeasureValue-test.tsx index 542fc424aa7..971be2bbf3d 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/FacetMeasureValue-test.js +++ b/server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/FacetMeasureValue-test.tsx @@ -17,13 +17,13 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; +import * as React from 'react'; import { shallow } from 'enzyme'; import FacetMeasureValue from '../FacetMeasureValue'; const MEASURE = { metric: { + id: '1', key: 'bugs', type: 'INT', name: 'Bugs', @@ -35,6 +35,7 @@ const MEASURE = { }; const LEAK_MEASURE = { metric: { + id: '2', key: 'new_bugs', type: 'INT', name: 'New Bugs', diff --git a/server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/Sidebar-test.js b/server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/Sidebar-test.tsx similarity index 91% rename from server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/Sidebar-test.js rename to server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/Sidebar-test.tsx index e19a46431cf..9b629994ac3 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/Sidebar-test.js +++ b/server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/Sidebar-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. */ -import React from 'react'; +import * as React from 'react'; import { shallow } from 'enzyme'; import Sidebar from '../Sidebar'; const MEASURES = [ { metric: { + id: '1', key: 'lines_to_cover', type: 'INT', name: 'Lines to Cover', @@ -35,6 +36,7 @@ const MEASURES = [ }, { metric: { + id: '2', key: 'coverage', type: 'PERCENT', name: 'Coverage', @@ -46,6 +48,7 @@ const MEASURES = [ }, { metric: { + id: '3', key: 'duplicated_lines_density', type: 'PERCENT', name: 'Duplicated Lines (%)', @@ -70,8 +73,8 @@ it('should display two facets', () => { it('should correctly toggle facets', () => { const wrapper = shallow(); expect(wrapper.state('openFacets').bugs).toBeUndefined(); - wrapper.instance().toggleFacet('bugs'); + (wrapper.instance() as Sidebar).toggleFacet('bugs'); expect(wrapper.state('openFacets').bugs).toBeTruthy(); - wrapper.instance().toggleFacet('bugs'); + (wrapper.instance() as Sidebar).toggleFacet('bugs'); expect(wrapper.state('openFacets').bugs).toBeFalsy(); }); diff --git a/server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/__snapshots__/DomainFacet-test.js.snap b/server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/__snapshots__/DomainFacet-test.tsx.snap similarity index 94% rename from server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/__snapshots__/DomainFacet-test.js.snap rename to server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/__snapshots__/DomainFacet-test.tsx.snap index 64c9678aaef..0a4c85967f1 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/__snapshots__/DomainFacet-test.js.snap +++ b/server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/__snapshots__/DomainFacet-test.tsx.snap @@ -30,6 +30,7 @@ exports[`should display facet item list 1`] = ` size={14} /> } + tooltip="component_measures.domain_overview" value="Reliability" /> } + tooltip="New Bugs" value="new_bugs" /> } + tooltip="Bugs" value="bugs" /> @@ -167,6 +172,7 @@ exports[`should display facet item list with bugs selected 1`] = ` size={14} /> } + tooltip="component_measures.domain_overview" value="Reliability" /> } + tooltip="New Bugs" value="new_bugs" /> } + tooltip="Bugs" value="bugs" /> @@ -300,6 +310,7 @@ exports[`should not display subtitles of new measures if there is none 1`] = ` size={14} /> } + tooltip="component_measures.domain_overview" value="Reliability" /> } + tooltip="Bugs" value="bugs" /> @@ -378,6 +391,7 @@ exports[`should not display subtitles of new measures if there is none, even on size={14} /> } + tooltip="component_measures.domain_overview" value="Reliability" /> } + tooltip="New Bugs" value="new_bugs" /> 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.tsx.snap similarity index 100% rename from server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/__snapshots__/FacetMeasureValue-test.js.snap rename to server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/__snapshots__/FacetMeasureValue-test.tsx.snap diff --git a/server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/__snapshots__/Sidebar-test.js.snap b/server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/__snapshots__/Sidebar-test.tsx.snap similarity index 96% rename from server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/__snapshots__/Sidebar-test.js.snap rename to server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/__snapshots__/Sidebar-test.tsx.snap index f526bbb3a16..09f7788831c 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/__snapshots__/Sidebar-test.js.snap +++ b/server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/__snapshots__/Sidebar-test.tsx.snap @@ -15,6 +15,7 @@ exports[`should display two facets 1`] = ` "leak": "70", "metric": Object { "domain": "Coverage", + "id": "1", "key": "lines_to_cover", "name": "Lines to Cover", "type": "INT", @@ -31,6 +32,7 @@ exports[`should display two facets 1`] = ` "leak": "0.0999999999999943", "metric": Object { "domain": "Coverage", + "id": "2", "key": "coverage", "name": "Coverage", "type": "PERCENT", @@ -61,6 +63,7 @@ exports[`should display two facets 1`] = ` "leak": "0.0", "metric": Object { "domain": "Duplications", + "id": "3", "key": "duplicated_lines_density", "name": "Duplicated Lines (%)", "type": "PERCENT", diff --git a/server/sonar-web/src/main/js/apps/component-measures/types.js b/server/sonar-web/src/main/js/apps/component-measures/types.js deleted file mode 100644 index 4ce7d76cf75..00000000000 --- a/server/sonar-web/src/main/js/apps/component-measures/types.js +++ /dev/null @@ -1,58 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -/*:: import type { Measure, MeasureEnhanced } from '../../components/measure/types'; */ - -/*:: type ComponentIntern = { - isFavorite?: boolean, - isRecentlyBrowsed?: boolean, - key: string, - match?: string, - name: string, - organization?: string, - project?: string, - qualifier: string -}; */ - -/*:: export type Component = ComponentIntern & { measures?: Array }; */ - -/*:: export type ComponentEnhanced = ComponentIntern & { - value?: ?string, - leak?: ?string, - measures: Array -}; */ - -/*:: export type Paging = { - pageIndex: number, - pageSize: number, - total: number -}; */ - -/*:: export type Period = { - index: number, - date: string, - mode: string, - parameter?: string -}; */ - -/*:: export type Query = { - metric: ?string, - selected: ?string, - view: string -}; */ diff --git a/server/sonar-web/src/main/js/apps/component-measures/utils.ts b/server/sonar-web/src/main/js/apps/component-measures/utils.ts index a02181c7e95..74befd184f0 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/utils.ts +++ b/server/sonar-web/src/main/js/apps/component-measures/utils.ts @@ -79,10 +79,7 @@ export function sortMeasures( ]); } -export function addMeasureCategories( - domainName: string, - measures: MeasureEnhanced[] -) /*: Array */ { +export function addMeasureCategories(domainName: string, measures: MeasureEnhanced[]) { const categories = domains[domainName] && domains[domainName].categories; if (categories && categories.length > 0) { return [...categories, ...measures]; @@ -121,7 +118,7 @@ export const groupByDomains = memoize((measures: MeasureEnhanced[]) => { })); return sortBy(domains, [ - (domain: { name: string; measure: MeasureEnhanced[] }) => { + (domain: { name: string; measures: MeasureEnhanced[] }) => { const idx = KNOWN_DOMAINS.indexOf(domain.name); return idx >= 0 ? idx : KNOWN_DOMAINS.length; }, @@ -162,7 +159,7 @@ export function getBubbleMetrics(domain: string, metrics: { [key: string]: Metri x: metrics[conf.x], y: metrics[conf.y], size: metrics[conf.size], - colors: conf.colors ? conf.colors.map(color => metrics[color]) : null + colors: conf.colors && conf.colors.map(color => metrics[color]) }; } diff --git a/server/sonar-web/src/main/js/apps/overview/components/LeakPeriodLegend.tsx b/server/sonar-web/src/main/js/apps/overview/components/LeakPeriodLegend.tsx index 90b9b569311..7e1c17777dd 100644 --- a/server/sonar-web/src/main/js/apps/overview/components/LeakPeriodLegend.tsx +++ b/server/sonar-web/src/main/js/apps/overview/components/LeakPeriodLegend.tsx @@ -23,9 +23,10 @@ import DateFromNow from '../../../components/intl/DateFromNow'; import DateFormatter, { longFormatterOption } from '../../../components/intl/DateFormatter'; import DateTimeFormatter from '../../../components/intl/DateTimeFormatter'; import Tooltip from '../../../components/controls/Tooltip'; -import { getPeriodDate, getPeriodLabel, Period, PeriodMode } from '../../../helpers/periods'; +import { getPeriodDate, getPeriodLabel } from '../../../helpers/periods'; import { translateWithParameters } from '../../../helpers/l10n'; import { differenceInDays } from '../../../helpers/dates'; +import { Period, PeriodMode } from '../../../app/types'; interface Props { period: Period; diff --git a/server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx b/server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx index 3f6982b4696..f6e58af8c0b 100644 --- a/server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx +++ b/server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx @@ -32,7 +32,7 @@ import { getMeasuresAndMeta } from '../../../api/measures'; import { getAllTimeMachineData, History } from '../../../api/time-machine'; import { parseDate } from '../../../helpers/dates'; import { enhanceMeasuresWithMetrics } from '../../../helpers/measures'; -import { getLeakPeriod, Period } from '../../../helpers/periods'; +import { getLeakPeriod } from '../../../helpers/periods'; import { get } from '../../../helpers/storage'; import { METRICS, HISTORY_METRICS_LIST } from '../utils'; import { @@ -48,7 +48,7 @@ import { } from '../../../helpers/branches'; import { fetchMetrics } from '../../../store/rootActions'; import { getMetrics, Store } from '../../../store/rootReducer'; -import { BranchLike, Component, Metric, MeasureEnhanced } from '../../../app/types'; +import { BranchLike, Component, Metric, MeasureEnhanced, Period } from '../../../app/types'; import { translate } from '../../../helpers/l10n'; import '../styles.css'; diff --git a/server/sonar-web/src/main/js/apps/overview/components/__tests__/LeakPeriodLegend-test.tsx b/server/sonar-web/src/main/js/apps/overview/components/__tests__/LeakPeriodLegend-test.tsx index fa6f4c952fe..77a2ddc8cca 100644 --- a/server/sonar-web/src/main/js/apps/overview/components/__tests__/LeakPeriodLegend-test.tsx +++ b/server/sonar-web/src/main/js/apps/overview/components/__tests__/LeakPeriodLegend-test.tsx @@ -20,8 +20,8 @@ import * as React from 'react'; import { shallow } from 'enzyme'; import LeakPeriodLegend from '../LeakPeriodLegend'; -import { PeriodMode, Period } from '../../../../helpers/periods'; import { differenceInDays } from '../../../../helpers/dates'; +import { Period, PeriodMode } from '../../../../app/types'; jest.mock('../../../../helpers/dates', () => { const dates = require.requireActual('../../../../helpers/dates'); diff --git a/server/sonar-web/src/main/js/apps/overview/main/enhance.tsx b/server/sonar-web/src/main/js/apps/overview/main/enhance.tsx index 44aecab78af..e0306ea0a7e 100644 --- a/server/sonar-web/src/main/js/apps/overview/main/enhance.tsx +++ b/server/sonar-web/src/main/js/apps/overview/main/enhance.tsx @@ -33,13 +33,13 @@ import { getRatingTooltip } from '../../../helpers/measures'; import { getLocalizedMetricName } from '../../../helpers/l10n'; -import { getPeriodDate, Period } from '../../../helpers/periods'; +import { getPeriodDate } from '../../../helpers/periods'; import { getComponentDrilldownUrl, getComponentIssuesUrl, getMeasureHistoryUrl } from '../../../helpers/urls'; -import { Component, BranchLike, MeasureEnhanced } from '../../../app/types'; +import { Component, BranchLike, MeasureEnhanced, Period } from '../../../app/types'; import { History } from '../../../api/time-machine'; import { getBranchLikeQuery } from '../../../helpers/branches'; diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardLeak-test.tsx b/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardLeak-test.tsx index 67d4b26c17a..62c153454d7 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardLeak-test.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardLeak-test.tsx @@ -17,6 +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. */ +/* eslint-disable camelcase */ import * as React from 'react'; import { shallow } from 'enzyme'; import ProjectCardLeak from '../ProjectCardLeak'; diff --git a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerBase.tsx b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerBase.tsx index 85b04e98266..021f46232bf 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerBase.tsx +++ b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerBase.tsx @@ -48,7 +48,6 @@ import { SourceViewerFile } from '../../app/types'; import { isSameBranchLike, getBranchLikeQuery } from '../../helpers/branches'; -import { parseDate } from '../../helpers/dates'; import { translate } from '../../helpers/l10n'; import './styles.css'; diff --git a/server/sonar-web/src/main/js/components/charts/BubbleChart.tsx b/server/sonar-web/src/main/js/components/charts/BubbleChart.tsx index f636a57ee14..7810f4503d8 100644 --- a/server/sonar-web/src/main/js/components/charts/BubbleChart.tsx +++ b/server/sonar-web/src/main/js/components/charts/BubbleChart.tsx @@ -28,14 +28,16 @@ import { event, select } from 'd3-selection'; import { sortBy, uniq } from 'lodash'; import Tooltip from '../controls/Tooltip'; import { translate } from '../../helpers/l10n'; +import { Location } from '../../helpers/urls'; import './BubbleChart.css'; const TICKS_COUNT = 5; -interface BubbleProps { +interface BubbleProps { color?: string; - link?: string; - onClick?: (link?: string) => void; + link?: string | Location; + onClick?: (ref?: T) => void; + data?: T; r: number; scale: number; tooltip?: string | React.ReactNode; @@ -43,12 +45,12 @@ interface BubbleProps { y: number; } -export class Bubble extends React.PureComponent { +export class Bubble extends React.PureComponent> { handleClick = (event: React.MouseEvent) => { if (this.props.onClick) { event.stopPropagation(); event.preventDefault(); - this.props.onClick(this.props.link); + this.props.onClick(this.props.data); } }; @@ -75,17 +77,18 @@ export class Bubble extends React.PureComponent { } } -interface Item { +export interface BubbleItem { color?: string; key?: string; - link?: any; + link?: string | Location; + data?: T; size: number; tooltip?: React.ReactNode; x: number; y: number; } -interface Props { +interface Props { displayXGrid?: boolean; displayXTicks?: boolean; displayYGrid?: boolean; @@ -93,8 +96,8 @@ interface Props { formatXTick?: (tick: number) => string; formatYTick?: (tick: number) => string; height: number; - items: Item[]; - onBubbleClick?: (link?: string) => void; + items: BubbleItem[]; + onBubbleClick?: (ref?: T) => void; padding?: [number, number, number, number]; sizeDomain?: [number, number]; sizeRange?: [number, number]; @@ -108,7 +111,7 @@ interface State { type Scale = ScaleLinear; -export default class BubbleChart extends React.Component { +export default class BubbleChart extends React.Component, State> { node: SVGSVGElement | null = null; selection: any = null; transform: any = null; @@ -122,7 +125,7 @@ export default class BubbleChart extends React.Component { sizeRange: [5, 45] }; - constructor(props: Props) { + constructor(props: Props) { super(props); this.state = { transform: { x: 0, y: 0, k: 1 } }; } @@ -317,6 +320,7 @@ export default class BubbleChart extends React.Component { key={item.key || index} link={item.link} onClick={this.props.onBubbleClick} + data={item.data} r={sizeScale(item.size)} scale={1 / transform.k} tooltip={item.tooltip} diff --git a/server/sonar-web/src/main/js/components/charts/__tests__/BubbleChart-test.tsx b/server/sonar-web/src/main/js/components/charts/__tests__/BubbleChart-test.tsx index 07c73da2b87..d98be168b61 100644 --- a/server/sonar-web/src/main/js/components/charts/__tests__/BubbleChart-test.tsx +++ b/server/sonar-web/src/main/js/components/charts/__tests__/BubbleChart-test.tsx @@ -35,7 +35,7 @@ it('should render bubble links', () => { it('should render bubbles with click handlers', () => { const onClick = jest.fn(); - const items = [{ x: 1, y: 10, size: 7, link: 'foo' }, { x: 2, y: 30, size: 5, link: 'bar' }]; + const items = [{ x: 1, y: 10, size: 7, data: 'foo' }, { x: 2, y: 30, size: 5, data: 'bar' }]; const chart = mount(); chart.find(Bubble).forEach(bubble => expect(bubble).toMatchSnapshot()); }); diff --git a/server/sonar-web/src/main/js/components/charts/__tests__/__snapshots__/BubbleChart-test.tsx.snap b/server/sonar-web/src/main/js/components/charts/__tests__/__snapshots__/BubbleChart-test.tsx.snap index 89b36a4c5ea..9d69488fbd0 100644 --- a/server/sonar-web/src/main/js/components/charts/__tests__/__snapshots__/BubbleChart-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/charts/__tests__/__snapshots__/BubbleChart-test.tsx.snap @@ -130,8 +130,8 @@ exports[`should render bubble links 2`] = ` exports[`should render bubbles with click handlers 1`] = ` period.index === 1); + const period = getLeakPeriod(measure.periods); return period && period.value; } diff --git a/server/sonar-web/src/main/js/helpers/path.ts b/server/sonar-web/src/main/js/helpers/path.ts index 1c4138e75a5..c9a257f90fb 100644 --- a/server/sonar-web/src/main/js/helpers/path.ts +++ b/server/sonar-web/src/main/js/helpers/path.ts @@ -87,16 +87,12 @@ export function fileFromPath(path: string | null): string | null { } } -export function splitPath(path: string): { head: string; tail: string } | null { - if (typeof path === 'string') { - const tokens = path.split('/'); - return { - head: tokens.slice(0, -1).join('/'), - tail: tokens[tokens.length - 1] - }; - } else { - return null; - } +export function splitPath(path: string) { + const tokens = path.split('/'); + return { + head: tokens.slice(0, -1).join('/'), + tail: tokens[tokens.length - 1] + }; } export function limitComponentName(str: string, limit = 30): string { diff --git a/server/sonar-web/src/main/js/helpers/periods.ts b/server/sonar-web/src/main/js/helpers/periods.ts index d278a904b9d..baa0651439d 100644 --- a/server/sonar-web/src/main/js/helpers/periods.ts +++ b/server/sonar-web/src/main/js/helpers/periods.ts @@ -19,32 +19,16 @@ */ import { translate, translateWithParameters } from './l10n'; import { parseDate } from './dates'; +import { Period, PeriodMode, PeriodMeasure } from '../app/types'; -export enum PeriodMode { - Days = 'days', - Date = 'date', - Version = 'version', - PreviousAnalysis = 'previous_analysis', - PreviousVersion = 'previous_version' -} - -export interface Period { - date: string; - index: number; - mode: PeriodMode; - modeParam?: string; - parameter?: string; -} - -function getPeriod(periods: Period[] | undefined, index: number) { +function getPeriod(periods: T[] | undefined, index: number) { if (!Array.isArray(periods)) { return undefined; } - return periods.find(period => period.index === index); } -export function getLeakPeriod(periods: Period[] | undefined) { +export function getLeakPeriod(periods: T[] | undefined) { return getPeriod(periods, 1); } -- 2.39.5