From df96a05cc325b2946c25ee8277f64638ed72288c Mon Sep 17 00:00:00 2001 From: =?utf8?q?Gr=C3=A9goire=20Aubert?= Date: Wed, 17 Jan 2018 11:29:05 +0100 Subject: [PATCH] Migrate parts of overview app to TS --- .../sonar-web/src/main/js/api/components.ts | 2 +- .../src/main/js/api/projectActivity.ts | 21 +- .../sonar-web/src/main/js/api/projectLinks.ts | 12 +- .../src/main/js/api/quality-gates.ts | 27 ++- .../sonar-web/src/main/js/api/time-machine.ts | 3 +- .../js/app/components/ComponentContainer.tsx | 4 +- server/sonar-web/src/main/js/app/types.ts | 16 +- .../apps/code/components/ComponentMeasure.tsx | 4 +- .../components/MeasureHeader.js | 11 +- .../__snapshots__/MeasureHeader-test.js.snap | 53 +----- .../drilldown/ComponentsListRow.js | 7 +- .../drilldown/MeasureCell.js | 7 +- .../sidebar/FacetMeasureValue.js | 12 +- .../FacetMeasureValue-test.js.snap | 41 +--- .../js/apps/overview/badges/BadgesModal.tsx | 2 +- .../main/js/apps/overview/components/App.tsx | 86 +++++++++ .../{OverviewApp.js => OverviewApp.tsx} | 95 +++++---- .../components/{Timeline.js => Timeline.tsx} | 16 +- .../components/__tests__/App-test.tsx | 68 +++++++ .../{AnalysesList.js => AnalysesList.tsx} | 62 +++--- .../events/{Analysis.js => Analysis.tsx} | 22 +-- .../overview/events/{Event.js => Event.tsx} | 15 +- .../{Analysis-test.js => Analysis-test.tsx} | 4 +- .../{Event-test.js => Event-test.tsx} | 2 +- ...is-test.js.snap => Analysis-test.tsx.snap} | 0 ...Event-test.js.snap => Event-test.tsx.snap} | 0 .../main/js/apps/overview/main/Coverage.js | 2 +- .../js/apps/overview/main/Duplications.js | 2 +- .../overview/main/{enhance.js => enhance.tsx} | 89 ++++----- .../apps/overview/meta/{Meta.js => Meta.tsx} | 46 +++-- .../meta/{MetaLink.js => MetaLink.tsx} | 48 ++--- .../meta/{MetaLinks.js => MetaLinks.tsx} | 41 ++-- .../meta/{MetaSize.js => MetaSize.tsx} | 26 +-- .../meta/{MetaTags.js => MetaTags.tsx} | 80 +++----- ...taTagsSelector.js => MetaTagsSelector.tsx} | 41 ++-- .../{MetaLink-test.js => MetaLink-test.tsx} | 2 +- .../{MetaTags-test.js => MetaTags-test.tsx} | 31 ++- ...ctor-test.js => MetaTagsSelector-test.tsx} | 20 +- ...nk-test.js.snap => MetaLink-test.tsx.snap} | 0 ...gs-test.js.snap => MetaTags-test.tsx.snap} | 0 ...lityGate.js => ApplicationQualityGate.tsx} | 71 +++---- ...t.js => ApplicationQualityGateProject.tsx} | 46 +---- .../qualityGate/QualityGateCondition.js | 9 +- ...est.js => ApplicationQualityGate-test.tsx} | 7 +- ...=> ApplicationQualityGateProject-test.tsx} | 3 +- ...p => ApplicationQualityGate-test.tsx.snap} | 0 ...plicationQualityGateProject-test.tsx.snap} | 0 .../QualityGateCondition-test.js.snap | 150 +++------------ .../js/apps/portfolio/components/Effort.tsx | 10 +- .../portfolio/components/ReleasabilityBox.tsx | 7 +- .../js/apps/portfolio/components/Summary.tsx | 6 +- .../portfolio/components/WorstProjects.tsx | 9 +- .../__snapshots__/Effort-test.tsx.snap | 14 +- .../ReleasabilityBox-test.tsx.snap | 14 +- .../__snapshots__/Summary-test.tsx.snap | 24 +-- .../__snapshots__/WorstProjects-test.tsx.snap | 180 +++++------------- .../components/ProjectActivityAppContainer.js | 2 +- .../components/ProjectCardLeakMeasures.tsx | 42 ++-- .../components/ProjectCardOverallMeasures.tsx | 42 ++-- .../ProjectCardLeakMeasures-test.tsx.snap | 141 ++++---------- .../ProjectCardOverallMeasures-test.tsx.snap | 106 +++-------- .../js/apps/projects/filters/TagsFilter.tsx | 13 +- .../main/js/components/common/BubblePopup.tsx | 7 +- .../{MultiSelect.js => MultiSelect.tsx} | 128 ++++++------- ...iSelectOption.js => MultiSelectOption.tsx} | 41 ++-- ...ltiSelect-test.js => MultiSelect-test.tsx} | 2 +- ...ion-test.js => MultiSelectOption-test.tsx} | 5 +- ...test.js.snap => MultiSelect-test.tsx.snap} | 17 -- ...s.snap => MultiSelectOption-test.tsx.snap} | 0 .../issue/popups/SetIssueTagsPopup.js | 1 - .../main/js/components/measure/Measure.tsx | 27 +-- .../measure/__tests__/Measure-test.tsx | 49 ++--- .../src/main/js/components/measure/utils.ts | 15 -- .../preview-graph/PreviewGraph.d.ts | 32 ++++ .../{drilldown-link.js => DrilldownLink.tsx} | 24 +-- .../{TagsSelector.js => TagsSelector.tsx} | 29 ++- ...Selector-test.js => TagsSelector-test.tsx} | 5 +- ...est.js.snap => TagsSelector-test.tsx.snap} | 4 +- .../js/helpers/__tests__/measures-test.ts | 91 +-------- .../sonar-web/src/main/js/helpers/measures.ts | 88 +-------- .../sonar-web/src/main/js/helpers/periods.ts | 4 +- 81 files changed, 1007 insertions(+), 1478 deletions(-) create mode 100644 server/sonar-web/src/main/js/apps/overview/components/App.tsx rename server/sonar-web/src/main/js/apps/overview/components/{OverviewApp.js => OverviewApp.tsx} (74%) rename server/sonar-web/src/main/js/apps/overview/components/{Timeline.js => Timeline.tsx} (87%) create mode 100644 server/sonar-web/src/main/js/apps/overview/components/__tests__/App-test.tsx rename server/sonar-web/src/main/js/apps/overview/events/{AnalysesList.js => AnalysesList.tsx} (77%) rename server/sonar-web/src/main/js/apps/overview/events/{Analysis.js => Analysis.tsx} (80%) rename server/sonar-web/src/main/js/apps/overview/events/{Event.js => Event.tsx} (78%) rename server/sonar-web/src/main/js/apps/overview/events/__tests__/{Analysis-test.js => Analysis-test.tsx} (90%) rename server/sonar-web/src/main/js/apps/overview/events/__tests__/{Event-test.js => Event-test.tsx} (97%) rename server/sonar-web/src/main/js/apps/overview/events/__tests__/__snapshots__/{Analysis-test.js.snap => Analysis-test.tsx.snap} (100%) rename server/sonar-web/src/main/js/apps/overview/events/__tests__/__snapshots__/{Event-test.js.snap => Event-test.tsx.snap} (100%) rename server/sonar-web/src/main/js/apps/overview/main/{enhance.js => enhance.tsx} (75%) rename server/sonar-web/src/main/js/apps/overview/meta/{Meta.js => Meta.tsx} (79%) rename server/sonar-web/src/main/js/apps/overview/meta/{MetaLink.js => MetaLink.tsx} (75%) rename server/sonar-web/src/main/js/apps/overview/meta/{MetaLinks.js => MetaLinks.tsx} (70%) rename server/sonar-web/src/main/js/apps/overview/meta/{MetaSize.js => MetaSize.tsx} (85%) rename server/sonar-web/src/main/js/apps/overview/meta/{MetaTags.js => MetaTags.tsx} (74%) rename server/sonar-web/src/main/js/apps/overview/meta/{MetaTagsSelector.js => MetaTagsSelector.tsx} (76%) rename server/sonar-web/src/main/js/apps/overview/meta/__tests__/{MetaLink-test.js => MetaLink-test.tsx} (98%) rename server/sonar-web/src/main/js/apps/overview/meta/__tests__/{MetaTags-test.js => MetaTags-test.tsx} (72%) rename server/sonar-web/src/main/js/apps/overview/meta/__tests__/{MetaTagsSelector-test.js => MetaTagsSelector-test.tsx} (80%) rename server/sonar-web/src/main/js/apps/overview/meta/__tests__/__snapshots__/{MetaLink-test.js.snap => MetaLink-test.tsx.snap} (100%) rename server/sonar-web/src/main/js/apps/overview/meta/__tests__/__snapshots__/{MetaTags-test.js.snap => MetaTags-test.tsx.snap} (100%) rename server/sonar-web/src/main/js/apps/overview/qualityGate/{ApplicationQualityGate.js => ApplicationQualityGate.tsx} (69%) rename server/sonar-web/src/main/js/apps/overview/qualityGate/{ApplicationQualityGateProject.js => ApplicationQualityGateProject.tsx} (82%) rename server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/{ApplicationQualityGate-test.js => ApplicationQualityGate-test.tsx} (89%) rename server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/{ApplicationQualityGateProject-test.js => ApplicationQualityGateProject-test.tsx} (98%) rename server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/{ApplicationQualityGate-test.js.snap => ApplicationQualityGate-test.tsx.snap} (100%) rename server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/{ApplicationQualityGateProject-test.js.snap => ApplicationQualityGateProject-test.tsx.snap} (100%) rename server/sonar-web/src/main/js/components/common/{MultiSelect.js => MultiSelect.tsx} (73%) rename server/sonar-web/src/main/js/components/common/{MultiSelectOption.js => MultiSelectOption.tsx} (75%) rename server/sonar-web/src/main/js/components/common/__tests__/{MultiSelect-test.js => MultiSelect-test.tsx} (98%) rename server/sonar-web/src/main/js/components/common/__tests__/{MultiSelectOption-test.js => MultiSelectOption-test.tsx} (95%) rename server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/{MultiSelect-test.js.snap => MultiSelect-test.tsx.snap} (89%) rename server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/{MultiSelectOption-test.js.snap => MultiSelectOption-test.tsx.snap} (100%) create mode 100644 server/sonar-web/src/main/js/components/preview-graph/PreviewGraph.d.ts rename server/sonar-web/src/main/js/components/shared/{drilldown-link.js => DrilldownLink.tsx} (90%) rename server/sonar-web/src/main/js/components/tags/{TagsSelector.js => TagsSelector.tsx} (78%) rename server/sonar-web/src/main/js/components/tags/__tests__/{TagsSelector-test.js => TagsSelector-test.tsx} (95%) rename server/sonar-web/src/main/js/components/tags/__tests__/__snapshots__/{TagsSelector-test.js.snap => TagsSelector-test.tsx.snap} (96%) 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 { - 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 { 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 { +}): 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 { +export interface ProjectLink { + id: string; + name: string; + type: string; + url: string; +} + +export function getProjectLinks(projectKey: string): Promise { 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 { 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 { +}): Promise { 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 { 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 ; } - return ( - - ); + return ; } 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 (
@@ -55,9 +55,14 @@ export default function MeasureHeader(props /*: Props*/) { {isDiff ? ( - + ) : ( - + )} 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`] = ` > @@ -165,29 +144,9 @@ exports[`should render correctly for leak 1`] = ` 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 => ( ))} 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 ( - + ); 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
- +
); } return (
- +
); } 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" >
`; @@ -33,24 +19,9 @@ exports[`should display measure value 1`] = ` id="measure-bugs-value" > `; 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) => void; +} + +export default class App extends React.PureComponent { + 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 ( + + ); + } + + return ( + + ); + } +} 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 similarity index 74% rename from server/sonar-web/src/main/js/apps/overview/components/OverviewApp.js rename to 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 -}; -*/ - -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 { + 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 similarity index 87% rename from server/sonar-web/src/main/js/apps/overview/components/Timeline.js rename to 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 { 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(, { + context: { router: { replace } } + }); + expect(replace).toBeCalledWith({ + pathname: '/code', + query: { branch: 'b', id: 'project', selected: 'foo' } + }); +}); + +function getWrapper(props = {}) { + return shallow(); +} 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 similarity index 77% rename from server/sonar-web/src/main/js/apps/overview/events/AnalysesList.js rename to 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, - loading: boolean, - metrics: Array -}; -*/ +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 { + 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 */) { + renderList(analyses: IAnalysis[]) { if (!analyses.length) { return

{translate('no_results')}

; } 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 similarity index 80% rename from server/sonar-web/src/main/js/apps/overview/events/Analysis.js rename to 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 */ = sortBy( +export default function Analysis({ analysis, ...props }: Props) { + const sortedEvents: Array = 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 similarity index 78% rename from server/sonar-web/src/main/js/apps/overview/events/Event.js rename to 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 ( - - {props.event.name} + + {event.name} ); } 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 similarity index 90% rename from server/sonar-web/src/main/js/apps/overview/events/__tests__/Analysis-test.js rename to 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()).toMatchSnapshot(); + expect(shallow()).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 similarity index 97% rename from server/sonar-web/src/main/js/apps/overview/events/__tests__/Event-test.js rename to 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 similarity index 100% rename from server/sonar-web/src/main/js/apps/overview/events/__tests__/__snapshots__/Analysis-test.js.snap rename to 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 similarity index 100% rename from server/sonar-web/src/main/js/apps/overview/events/__tests__/__snapshots__/Event-test.js.snap rename to 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 similarity index 75% rename from server/sonar-web/src/main/js/apps/overview/main/enhance.js rename to 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) { + return class extends React.PureComponent { 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 (
@@ -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 ( -
-
{formatted}
- -
{customLabel || measure.metric.name}
-
- ); - }; - - 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 (
@@ -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 && ( {formattedAnalysisDate => ( @@ -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 ( { + 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 (
@@ -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 similarity index 79% rename from server/sonar-web/src/main/js/apps/overview/meta/Meta.js rename to 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 = ({
{description}
)} - + - {isProject && } + {isProject && } {shouldShowQualityGate && ( @@ -91,14 +99,14 @@ const Meta = ({ {hasOrganization && } - {onSonarCloud && + {props.onSonarCloud && isProject && !isPrivate && }
); -}; +} -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(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 similarity index 75% rename from server/sonar-web/src/main/js/apps/overview/meta/MetaLink.js rename to 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 { + state: State = { expanded: false }; - state /*: State */ = { - expanded: false + handleClick = (e: React.SyntheticEvent) => { + 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) => { + e.currentTarget.select(); }; - renderLinkIcon(link /*: Link */) { + renderLinkIcon = (link: ProjectLink) => { if (link.type === 'issue') { return ; } @@ -63,7 +53,7 @@ export default class MetaLink extends React.PureComponent { ) : ( ); - } + }; 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} />
)} 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 similarity index 70% rename from server/sonar-web/src/main/js/apps/overview/meta/MetaLinks.js rename to 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 { + 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 similarity index 85% rename from server/sonar-web/src/main/js/apps/overview/meta/MetaSize.js rename to 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 { + renderLoC = (ncloc: MeasureEnhanced) => (
- + {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 similarity index 74% rename from server/sonar-web/src/main/js/apps/overview/meta/MetaTags.js rename to 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, - 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 { + 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) => { 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 */) => { + 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 similarity index 76% rename from server/sonar-web/src/main/js/apps/overview/meta/MetaTagsSelector.js rename to 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, - setProjectTags: (Array) => void -}; -*/ +interface Props { + position: BubblePopupPosition; + project: string; + selectedTags: string[]; + setProjectTags: (tags: string[]) => void; +} -/*:: -type State = { - searchResult: Array -}; -*/ +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 { + 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 similarity index 98% rename from server/sonar-web/src/main/js/apps/overview/meta/__tests__/MetaLink-test.js rename to 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 similarity index 72% rename from server/sonar-web/src/main/js/apps/overview/meta/__tests__/MetaTags-test.js rename to 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(, { disableLifecycleMethods: true }) + shallow(, { + disableLifecycleMethods: true + }) ).toMatchSnapshot(); }); it('should render with tags and admin rights', () => { expect( - shallow(, { disableLifecycleMethods: true }) + shallow(, { + disableLifecycleMethods: true + }) ).toMatchSnapshot(); }); it('should open the tag selector on click', () => { - const wrapper = shallow(, { - disableLifecycleMethods: true - }); + const wrapper = shallow( + , + { + 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 similarity index 80% rename from server/sonar-web/src/main/js/apps/overview/meta/__tests__/MetaTagsSelector-test.js rename to 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( - + ); expect(searchProjectTags).toBeCalledWith({ ps: 9, q: '' }); }); @@ -46,17 +53,18 @@ it('selects and deselects tags', () => { const setProjectTags = jest.fn(); const wrapper = shallow( ); - 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 similarity index 100% rename from server/sonar-web/src/main/js/apps/overview/meta/__tests__/__snapshots__/MetaLink-test.js.snap rename to 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 similarity index 100% rename from server/sonar-web/src/main/js/apps/overview/meta/__tests__/__snapshots__/MetaTags-test.js.snap rename to 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 similarity index 69% rename from server/sonar-web/src/main/js/apps/overview/qualityGate/ApplicationQualityGate.js rename to 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, - 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 { + 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 && } - {projects != null && ( -
- {projects - .filter(project => project.status !== 'OK') - .map(project => ( - - ))} -
- )} + {projects && + metrics && ( +
+ {projects + .filter(project => project.status !== 'OK') + .map(project => ( + + ))} +
+ )} ); } 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 similarity index 82% rename from server/sonar-web/src/main/js/apps/overview/qualityGate/ApplicationQualityGateProject.js rename to 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, - 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 { + 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(
- +
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 similarity index 89% rename from server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/ApplicationQualityGate-test.js rename to 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(); + const wrapper = shallow( + + ); 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 similarity index 98% rename from server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/ApplicationQualityGateProject-test.js rename to 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 similarity index 100% rename from server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/ApplicationQualityGate-test.js.snap rename to 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 similarity index 100% rename from server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/ApplicationQualityGateProject-test.js.snap rename to 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`] = ` >
@@ -82,23 +68,9 @@ exports[`new_open_issues 1`] = ` >
@@ -150,23 +122,9 @@ exports[`new_reliability_rating 1`] = ` >
@@ -218,23 +176,9 @@ exports[`new_security_rating 1`] = ` >
@@ -274,17 +218,9 @@ exports[`open_issues 1`] = ` >
@@ -335,17 +271,9 @@ exports[`reliability_rating 1`] = ` >
@@ -396,17 +324,9 @@ exports[`security_rating 1`] = ` >
@@ -457,17 +377,9 @@ exports[`should work with branch 1`] = ` >
@@ -517,17 +429,9 @@ exports[`sqale_rating 1`] = ` >
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) { {' '} + className="little-spacer-right" + metricKey="projects" + metricType="SHORT_INT" + value={String(effort.projects)} + /> {translate('projects_')} 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) { {' '} + className="little-spacer-right" + metricKey="projects" + metricType="SHORT_INT" + value={effort} + /> {Number(effort) === 1 ? 'project' : 'projects'} {' '} 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) {
  • - +
    {translate('projects')}
    @@ -51,7 +49,7 @@ export default function Summary({ component, measures }: Props) {
  • - +
    {translate('metric.ncloc.name')}
    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 ( - + ); } @@ -121,12 +121,7 @@ function renderNcloc(measures: { [key: string]: string | undefined }, maxLoc: nu return ( - + {maxLoc > 0 && ( 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`] = ` > - projects_ , 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`] = ` > - projects 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`] = ` } >
  • @@ -69,15 +63,9 @@ exports[`renders 1`] = ` } >
    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" > {} ); }; 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) {
    @@ -56,10 +55,9 @@ export default function ProjectCardLeakMeasures({ measures }: Props) {
    @@ -75,10 +73,9 @@ export default function ProjectCardLeakMeasures({ measures }: Props) {
    @@ -93,10 +90,9 @@ export default function ProjectCardLeakMeasures({ measures }: Props) {
    {translate('metric.coverage.name')}
    @@ -107,10 +103,9 @@ export default function ProjectCardLeakMeasures({ measures }: Props) {
    @@ -122,12 +117,7 @@ export default function ProjectCardLeakMeasures({ measures }: Props) {
    - +
    {translate('metric.lines.name')}
    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) {
    @@ -64,10 +63,9 @@ export default function ProjectCardOverallMeasures({ measures }: Props) {
    @@ -83,10 +81,9 @@ export default function ProjectCardOverallMeasures({ measures }: Props) {
    @@ -105,12 +102,7 @@ export default function ProjectCardOverallMeasures({ measures }: Props) { )} - +
    {translate('metric.coverage.name')}
    @@ -125,10 +117,9 @@ export default function ProjectCardOverallMeasures({ measures }: Props) { )}
    @@ -141,12 +132,7 @@ export default function ProjectCardOverallMeasures({ measures }: Props) {
    - + 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`] = ` >
    { 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 similarity index 73% rename from server/sonar-web/src/main/js/components/common/MultiSelect.js rename to 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, - elements: Array, - listSize: number, - onSearch: string => void, - onSelect: string => void, - onUnselect: string => void, - validateSearchInput: string => string, - placeholder: string -}; -*/ +interface Props { + selectedElements: Array; + elements: Array; + 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; + unselectedElements: Array; + activeIdx: number; +} -/*:: -type State = { - query: string, - selectedElements: Array, - unselectedElements: Array, - 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 { + 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 similarity index 75% rename from server/sonar-web/src/main/js/components/common/MultiSelectOption.js rename to 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 { + handleSelect = (evt: React.SyntheticEvent) => { 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 similarity index 98% rename from server/sonar-web/src/main/js/components/common/__tests__/MultiSelect-test.js rename to 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 similarity index 95% rename from server/sonar-web/src/main/js/components/common/__tests__/MultiSelectOption-test.js rename to 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 similarity index 89% rename from server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/MultiSelect-test.js.snap rename to 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`] = ` >
    @@ -97,7 +91,6 @@ exports[`should render multiselect with selected elements 3`] = ` >
    @@ -145,7 +134,6 @@ exports[`should render multiselect with selected elements 4`] = ` >
    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 similarity index 100% rename from server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/MultiSelectOption-test.js.snap rename to 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` {'–'}; - } - - 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 {'–'}; } - if (metric.type === 'LEVEL') { + if (metricType === 'LEVEL') { return ; } - 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 {formattedValue != null ? formattedValue : '–'}; } - const tooltip = getRatingTooltip(metric.key, Number(value)); + const tooltip = getRatingTooltip(metricKey, Number(value)); const rating = ; 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()).toMatchSnapshot(); + expect( + shallow() + ).toMatchSnapshot(); }); it('renders leak measure', () => { - const measure = { - metric: { key: 'new_coverage', name: 'Coverage on New Code', type: 'PERCENT' }, - leak: '36.0' - }; - expect(shallow()).toMatchSnapshot(); + expect( + shallow() + ).toMatchSnapshot(); }); it('renders LEVEL', () => { - const measure = { - metric: { key: 'quality_gate_status', name: 'Quality Gate', type: 'LEVEL' }, - value: 'ERROR' - }; - expect(shallow()).toMatchSnapshot(); + expect( + shallow() + ).toMatchSnapshot(); }); it('renders known RATING', () => { - const measure = { - metric: { key: 'sqale_rating', name: 'Maintainability Rating', type: 'RATING' }, - value: '3' - }; - expect(shallow()).toMatchSnapshot(); + expect( + shallow() + ).toMatchSnapshot(); }); it('renders unknown RATING', () => { - const measure = { - metric: { key: 'foo_rating', name: 'Foo Rating', type: 'RATING' }, - value: '4' - }; - expect(shallow()).toMatchSnapshot(); + expect( + shallow() + ).toMatchSnapshot(); }); it('renders undefined measure', () => { - const measure = { metric: { key: 'foo', name: 'Foo', type: 'PERCENT' } }; - expect(shallow()).toMatchSnapshot(); + expect(shallow()).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 {} 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 similarity index 90% rename from server/sonar-web/src/main/js/components/shared/drilldown-link.js rename to 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 { 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 similarity index 78% rename from server/sonar-web/src/main/js/components/tags/TagsSelector.js rename to 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, - selectedTags: Array, - 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 ( {}, 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 similarity index 96% rename from server/sonar-web/src/main/js/components/tags/__tests__/__snapshots__/TagsSelector-test.js.snap rename to 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 { -- 2.39.5