From 822405efd0ddde6b1e123c40cc1255e9b57c05f8 Mon Sep 17 00:00:00 2001 From: Wouter Admiraal Date: Thu, 19 Dec 2019 10:58:37 +0100 Subject: [PATCH] SONAR-12632 SONAR-12678 Implement new Overview layout --- .../sonar-web/public/images/source-code.svg | 1 + .../src/main/js/app/styles/init/tables.css | 2 +- server/sonar-web/src/main/js/app/theme.js | 4 + .../apps/overview/branches/ActivityPanel.tsx | 141 + .../{events => branches}/Analysis.tsx | 6 +- .../branches/ApplicationLeakPeriodInfo.tsx | 21 +- .../apps/overview/branches/BranchOverview.tsx | 410 ++ .../branches/BranchOverviewRenderer.tsx | 115 + .../js/apps/overview/branches/DebtValue.tsx | 66 + .../branches/DrilldownMeasureValue.tsx | 64 + .../overview/{events => branches}/Event.tsx | 4 +- .../apps/overview/branches/MeasuresPanel.tsx | 265 + .../apps/overview/branches/NoCodeWarning.tsx | 90 + .../overview/branches/QualityGatePanel.tsx | 125 + .../branches/QualityGatePanelSection.tsx | 79 + .../branches/__tests__/ActivityPanel-test.tsx | 83 + .../__tests__/Analysis-test.tsx} | 36 +- .../__tests__/BranchOverview-test.tsx | 349 + .../__tests__/BranchOverviewRenderer-test.tsx | 46 + .../__tests__/DebtValue-test.tsx} | 42 +- .../__tests__/DrilldownMeasureValue-test.tsx | 42 + .../__tests__/Event-test.tsx | 16 +- .../branches/__tests__/MeasuresPanel-test.tsx | 82 + .../branches/__tests__/NoCodeWarning-test.tsx | 68 + .../__tests__/QualityGatePanel-test.tsx | 83 + .../QualityGatePanelSection-test.tsx | 58 + .../__snapshots__/ActivityPanel-test.tsx.snap | 349 + .../__snapshots__/Analysis-test.tsx.snap | 81 + .../ApplicationLeakPeriodInfo-test.tsx.snap | 19 + .../BranchOverview-test.tsx.snap | 1702 +++++ .../BranchOverviewRenderer-test.tsx.snap | 386 ++ .../__snapshots__/DebtValue-test.tsx.snap | 65 + .../DrilldownMeasureValue-test.tsx.snap | 47 + .../__snapshots__/Event-test.tsx.snap | 0 .../LeakPeriodInfo-test.tsx.snap | 25 + .../__snapshots__/MeasuresPanel-test.tsx.snap | 5943 +++++++++++++++++ .../ProjectLeakPeriodInfo-test.tsx.snap | 115 + .../QualityGatePanel-test.tsx.snap | 625 ++ .../QualityGatePanelSection-test.tsx.snap | 436 ++ .../main/js/apps/overview/components/App.tsx | 29 +- .../overview/components/EmptyOverview.tsx | 122 +- .../apps/overview/components/OverviewApp.tsx | 341 - .../js/apps/overview/components/Timeline.tsx | 69 - .../components/__tests__/App-test.tsx | 13 +- .../__tests__/EmptyOverview-test.tsx | 85 +- .../components/__tests__/OverviewApp-test.tsx | 179 - .../components/__tests__/Timeline-test.tsx | 72 - .../__snapshots__/EmptyOverview-test.tsx.snap | 337 +- .../__snapshots__/OverviewApp-test.tsx.snap | 790 --- .../__snapshots__/Timeline-test.tsx.snap | 110 - .../js/apps/overview/events/AnalysesList.tsx | 146 - .../__snapshots__/AnalysesList-test.tsx.snap | 18 - .../__snapshots__/Analysis-test.tsx.snap | 41 - .../src/main/js/apps/overview/main/Bugs.tsx | 135 - .../main/js/apps/overview/main/CodeSmells.tsx | 136 - .../main/js/apps/overview/main/Coverage.tsx | 193 - .../js/apps/overview/main/Duplications.tsx | 194 - .../main/VulnerabilitiesAndHotspots.tsx | 128 - .../overview/main/__tests__/Bugs-test.tsx | 80 - .../main/__tests__/CodeSmells-test.tsx | 109 - .../main/__tests__/Duplications-test.tsx | 88 - .../VulnerabilitiesAndHotspots-test.tsx | 115 - .../__snapshots__/Bugs-test.tsx.snap | 229 - .../__snapshots__/CodeSmells-test.tsx.snap | 295 - .../__snapshots__/Coverage-test.tsx.snap | 251 - .../__snapshots__/Duplications-test.tsx.snap | 227 - .../VulnerabilitiesAndHotspots-test.tsx.snap | 307 - .../main/js/apps/overview/main/enhance.tsx | 227 - .../js/apps/overview/meta/MetaContainer.tsx | 17 +- .../main/js/apps/overview/meta/MetaSize.tsx | 2 - .../__snapshots__/MetaContainer-test.tsx.snap | 81 - .../src/main/js/apps/overview/styles.css | 529 +- .../intl/__mocks__/DateFromNow.tsx} | 21 +- .../resources/org/sonar/l10n/core.properties | 42 +- 74 files changed, 12372 insertions(+), 5477 deletions(-) create mode 100644 server/sonar-web/public/images/source-code.svg create mode 100644 server/sonar-web/src/main/js/apps/overview/branches/ActivityPanel.tsx rename server/sonar-web/src/main/js/apps/overview/{events => branches}/Analysis.tsx (93%) create mode 100644 server/sonar-web/src/main/js/apps/overview/branches/BranchOverview.tsx create mode 100644 server/sonar-web/src/main/js/apps/overview/branches/BranchOverviewRenderer.tsx create mode 100644 server/sonar-web/src/main/js/apps/overview/branches/DebtValue.tsx create mode 100644 server/sonar-web/src/main/js/apps/overview/branches/DrilldownMeasureValue.tsx rename server/sonar-web/src/main/js/apps/overview/{events => branches}/Event.tsx (97%) create mode 100644 server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanel.tsx create mode 100644 server/sonar-web/src/main/js/apps/overview/branches/NoCodeWarning.tsx create mode 100644 server/sonar-web/src/main/js/apps/overview/branches/QualityGatePanel.tsx create mode 100644 server/sonar-web/src/main/js/apps/overview/branches/QualityGatePanelSection.tsx create mode 100644 server/sonar-web/src/main/js/apps/overview/branches/__tests__/ActivityPanel-test.tsx rename server/sonar-web/src/main/js/apps/overview/{events/__tests__/AnalysesList-test.tsx => branches/__tests__/Analysis-test.tsx} (60%) create mode 100644 server/sonar-web/src/main/js/apps/overview/branches/__tests__/BranchOverview-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/overview/branches/__tests__/BranchOverviewRenderer-test.tsx rename server/sonar-web/src/main/js/apps/overview/{main/__tests__/Coverage-test.tsx => branches/__tests__/DebtValue-test.tsx} (58%) create mode 100644 server/sonar-web/src/main/js/apps/overview/branches/__tests__/DrilldownMeasureValue-test.tsx rename server/sonar-web/src/main/js/apps/overview/{events => branches}/__tests__/Event-test.tsx (75%) create mode 100644 server/sonar-web/src/main/js/apps/overview/branches/__tests__/MeasuresPanel-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/overview/branches/__tests__/NoCodeWarning-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/overview/branches/__tests__/QualityGatePanel-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/overview/branches/__tests__/QualityGatePanelSection-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/ActivityPanel-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/Analysis-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/ApplicationLeakPeriodInfo-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/BranchOverview-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/BranchOverviewRenderer-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/DebtValue-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/DrilldownMeasureValue-test.tsx.snap rename server/sonar-web/src/main/js/apps/overview/{events => branches}/__tests__/__snapshots__/Event-test.tsx.snap (100%) create mode 100644 server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/LeakPeriodInfo-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/MeasuresPanel-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/ProjectLeakPeriodInfo-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/QualityGatePanel-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/QualityGatePanelSection-test.tsx.snap delete mode 100644 server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx delete mode 100644 server/sonar-web/src/main/js/apps/overview/components/Timeline.tsx delete mode 100644 server/sonar-web/src/main/js/apps/overview/components/__tests__/OverviewApp-test.tsx delete mode 100644 server/sonar-web/src/main/js/apps/overview/components/__tests__/Timeline-test.tsx delete mode 100644 server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/OverviewApp-test.tsx.snap delete mode 100644 server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/Timeline-test.tsx.snap delete mode 100644 server/sonar-web/src/main/js/apps/overview/events/AnalysesList.tsx delete mode 100644 server/sonar-web/src/main/js/apps/overview/events/__tests__/__snapshots__/AnalysesList-test.tsx.snap delete mode 100644 server/sonar-web/src/main/js/apps/overview/events/__tests__/__snapshots__/Analysis-test.tsx.snap delete mode 100644 server/sonar-web/src/main/js/apps/overview/main/Bugs.tsx delete mode 100644 server/sonar-web/src/main/js/apps/overview/main/CodeSmells.tsx delete mode 100644 server/sonar-web/src/main/js/apps/overview/main/Coverage.tsx delete mode 100644 server/sonar-web/src/main/js/apps/overview/main/Duplications.tsx delete mode 100644 server/sonar-web/src/main/js/apps/overview/main/VulnerabilitiesAndHotspots.tsx delete mode 100644 server/sonar-web/src/main/js/apps/overview/main/__tests__/Bugs-test.tsx delete mode 100644 server/sonar-web/src/main/js/apps/overview/main/__tests__/CodeSmells-test.tsx delete mode 100644 server/sonar-web/src/main/js/apps/overview/main/__tests__/Duplications-test.tsx delete mode 100644 server/sonar-web/src/main/js/apps/overview/main/__tests__/VulnerabilitiesAndHotspots-test.tsx delete mode 100644 server/sonar-web/src/main/js/apps/overview/main/__tests__/__snapshots__/Bugs-test.tsx.snap delete mode 100644 server/sonar-web/src/main/js/apps/overview/main/__tests__/__snapshots__/CodeSmells-test.tsx.snap delete mode 100644 server/sonar-web/src/main/js/apps/overview/main/__tests__/__snapshots__/Coverage-test.tsx.snap delete mode 100644 server/sonar-web/src/main/js/apps/overview/main/__tests__/__snapshots__/Duplications-test.tsx.snap delete mode 100644 server/sonar-web/src/main/js/apps/overview/main/__tests__/__snapshots__/VulnerabilitiesAndHotspots-test.tsx.snap delete mode 100644 server/sonar-web/src/main/js/apps/overview/main/enhance.tsx rename server/sonar-web/src/main/js/{apps/overview/events/__tests__/Analysis-test.tsx => components/intl/__mocks__/DateFromNow.tsx} (67%) diff --git a/server/sonar-web/public/images/source-code.svg b/server/sonar-web/public/images/source-code.svg new file mode 100644 index 00000000000..dd597d4f7ee --- /dev/null +++ b/server/sonar-web/public/images/source-code.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/server/sonar-web/src/main/js/app/styles/init/tables.css b/server/sonar-web/src/main/js/app/styles/init/tables.css index dd34a6cc3a8..75431350415 100644 --- a/server/sonar-web/src/main/js/app/styles/init/tables.css +++ b/server/sonar-web/src/main/js/app/styles/init/tables.css @@ -269,7 +269,7 @@ table.data.boxed-padding > thead + tbody > tr:first-child > td { } table.data.zebra-hover > tbody > tr:hover { - background-color: #ecf6fe !important; + background-color: var(--rowHoverHighlight) !important; } table.data.zebra > tbody > tr.selected { diff --git a/server/sonar-web/src/main/js/app/theme.js b/server/sonar-web/src/main/js/app/theme.js index 48b05a12810..7f302abda3b 100644 --- a/server/sonar-web/src/main/js/app/theme.js +++ b/server/sonar-web/src/main/js/app/theme.js @@ -55,6 +55,9 @@ module.exports = { globalNavBarBg: '#262626', + // table + rowHoverHighlight: '#ecf6fe', + // fonts baseFontColor: '#444', secondFontColor: '#777', @@ -145,6 +148,7 @@ module.exports = { mediumFontSize: '14px', bigFontSize: '16px', hugeFontSize: '24px', + giganticFontSize: '36px', hugeControlHeight: `${5 * grid}px`, largeControlHeight: `${4 * grid}px`, diff --git a/server/sonar-web/src/main/js/apps/overview/branches/ActivityPanel.tsx b/server/sonar-web/src/main/js/apps/overview/branches/ActivityPanel.tsx new file mode 100644 index 00000000000..6118f97707d --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/branches/ActivityPanel.tsx @@ -0,0 +1,141 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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 DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner'; +import { parseDate } from 'sonar-ui-common/helpers/dates'; +import { translate } from 'sonar-ui-common/helpers/l10n'; +import GraphsHeader from '../../../components/activity-graph/GraphsHeader'; +import GraphsHistory from '../../../components/activity-graph/GraphsHistory'; +import { + DEFAULT_GRAPH, + generateSeries, + getDisplayedHistoryMetrics, + splitSeriesInGraphs +} from '../../../components/activity-graph/utils'; +import ActivityLink from '../../../components/common/ActivityLink'; +import { BranchLike } from '../../../types/branch-like'; +import { GraphType, MeasureHistory } from '../../../types/project-activity'; +import Analysis from './Analysis'; + +export interface ActivityPanelProps { + analyses?: T.Analysis[]; + branchLike?: BranchLike; + component: Pick; + graph?: GraphType; + leakPeriodDate?: Date; + loading?: boolean; + measuresHistory: MeasureHistory[]; + metrics: T.Metric[]; + onGraphChange: (graph: GraphType) => void; +} + +const MAX_ANALYSES_NB = 5; +const MAX_GRAPH_NB = 2; +const MAX_SERIES_PER_GRAPH = 3; + +export function ActivityPanel(props: ActivityPanelProps) { + const { + analyses = [], + branchLike, + component, + graph = DEFAULT_GRAPH, + leakPeriodDate, + loading, + measuresHistory, + metrics + } = props; + + const series = generateSeries( + measuresHistory, + graph, + metrics, + getDisplayedHistoryMetrics(graph, []) + ); + const graphs = splitSeriesInGraphs(series, MAX_GRAPH_NB, MAX_SERIES_PER_GRAPH); + const parsedAnalyses = analyses.map(a => ({ ...a, date: parseDate(a.date) })); + let shownLeakPeriodDate; + if (leakPeriodDate !== undefined) { + const startDate = measuresHistory.reduce((oldest: Date, { history }) => { + if (history.length > 0) { + const date = parseDate(history[0].date); + return oldest.getTime() > date.getTime() ? date : oldest; + } else { + return oldest; + } + }, new Date()); + shownLeakPeriodDate = + startDate.getTime() > leakPeriodDate.getTime() ? startDate : leakPeriodDate; + } + + const filteredAnalyses = analyses.filter(a => a.events.length > 0).slice(0, MAX_ANALYSES_NB); + + return ( +
+

{translate('overview.activity')}

+ +
+
+
+
+ + +
+ +
+ +
+
+ +
+
+ + {analyses.length === 0 ? ( +

{translate('no_results')}

+ ) : ( +
    + {filteredAnalyses.map(analysis => ( + + ))} +
+ )} +
+
+
+
+
+
+ ); +} + +export default React.memo(ActivityPanel); diff --git a/server/sonar-web/src/main/js/apps/overview/events/Analysis.tsx b/server/sonar-web/src/main/js/apps/overview/branches/Analysis.tsx similarity index 93% rename from server/sonar-web/src/main/js/apps/overview/events/Analysis.tsx rename to server/sonar-web/src/main/js/apps/overview/branches/Analysis.tsx index 78534752de0..0860bfa80c4 100644 --- a/server/sonar-web/src/main/js/apps/overview/events/Analysis.tsx +++ b/server/sonar-web/src/main/js/apps/overview/branches/Analysis.tsx @@ -23,12 +23,12 @@ import { translate } from 'sonar-ui-common/helpers/l10n'; import DateTooltipFormatter from '../../../components/intl/DateTooltipFormatter'; import Event from './Event'; -interface Props { +export interface AnalysisProps { analysis: T.Analysis; qualifier: string; } -export default function Analysis({ analysis, ...props }: Props) { +export function Analysis({ analysis, ...props }: AnalysisProps) { const sortedEvents = sortBy( analysis.events, // versions first @@ -60,3 +60,5 @@ export default function Analysis({ analysis, ...props }: Props) { ); } + +export default React.memo(Analysis); diff --git a/server/sonar-web/src/main/js/apps/overview/branches/ApplicationLeakPeriodInfo.tsx b/server/sonar-web/src/main/js/apps/overview/branches/ApplicationLeakPeriodInfo.tsx index 1d27c582616..3505f3d96a8 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/ApplicationLeakPeriodInfo.tsx +++ b/server/sonar-web/src/main/js/apps/overview/branches/ApplicationLeakPeriodInfo.tsx @@ -18,6 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; +import HelpTooltip from 'sonar-ui-common/components/controls/HelpTooltip'; import { translateWithParameters } from 'sonar-ui-common/helpers/l10n'; import DateFromNow from '../../../components/intl/DateFromNow'; import { ApplicationPeriod } from '../../../types/application'; @@ -28,18 +29,18 @@ export interface ApplicationLeakPeriodInfoProps { export function ApplicationLeakPeriodInfo({ leakPeriod }: ApplicationLeakPeriodInfoProps) { return ( - <> -
- {translateWithParameters('overview.max_new_code_period_from_x', leakPeriod.projectName)} -
+
- {fromNow => ( -
- {translateWithParameters('overview.started_x', fromNow)} -
- )} + {fromNow => translateWithParameters('overview.started_x', fromNow)}
- + +
); } diff --git a/server/sonar-web/src/main/js/apps/overview/branches/BranchOverview.tsx b/server/sonar-web/src/main/js/apps/overview/branches/BranchOverview.tsx new file mode 100644 index 00000000000..6ad66b9295d --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/branches/BranchOverview.tsx @@ -0,0 +1,410 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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 { sortBy, uniq } from 'lodash'; +import * as React from 'react'; +import { parseDate, toNotSoISOString } from 'sonar-ui-common/helpers/dates'; +import { isDefined } from 'sonar-ui-common/helpers/types'; +import { getApplicationLeak } from '../../../api/application'; +import { getMeasuresAndMeta } from '../../../api/measures'; +import { getProjectActivity } from '../../../api/projectActivity'; +import { getApplicationQualityGate, getQualityGateProjectStatus } from '../../../api/quality-gates'; +import { getTimeMachineData } from '../../../api/time-machine'; +import { + getActivityGraph, + getHistoryMetrics, + saveActivityGraph +} from '../../../components/activity-graph/utils'; +import { + getBranchLikeDisplayName, + getBranchLikeQuery, + isSameBranchLike +} from '../../../helpers/branch-like'; +import { enhanceConditionWithMeasure, enhanceMeasuresWithMetrics } from '../../../helpers/measures'; +import { getLeakPeriod } from '../../../helpers/periods'; +import { + extractStatusConditionsFromApplicationStatusChildProject, + extractStatusConditionsFromProjectStatus +} from '../../../helpers/qualityGates'; +import { ApplicationPeriod } from '../../../types/application'; +import { BranchLike } from '../../../types/branch-like'; +import { MetricKey } from '../../../types/metrics'; +import { GraphType, MeasureHistory } from '../../../types/project-activity'; +import { QualityGateStatus, QualityGateStatusCondition } from '../../../types/quality-gates'; +import '../styles.css'; +import { HISTORY_METRICS_LIST, METRICS } from '../utils'; +import BranchOverviewRenderer from './BranchOverviewRenderer'; + +interface Props { + branchLike?: BranchLike; + component: T.Component; +} + +interface State { + analyses?: T.Analysis[]; + appLeak?: ApplicationPeriod; + graph: GraphType; + loadingHistory?: boolean; + loadingStatus?: boolean; + measures?: T.MeasureEnhanced[]; + measuresHistory?: MeasureHistory[]; + metrics?: T.Metric[]; + periods?: T.Period[]; + qgStatuses?: QualityGateStatus[]; +} + +export const BRANCH_OVERVIEW_ACTIVITY_GRAPH = 'sonar_branch_overview.graph'; + +// Get all history data over the past year. +const FROM_DATE = toNotSoISOString(new Date().setFullYear(new Date().getFullYear() - 1)); + +export default class BranchOverview extends React.PureComponent { + mounted = false; + state: State; + + constructor(props: Props) { + super(props); + + const { graph } = getActivityGraph(BRANCH_OVERVIEW_ACTIVITY_GRAPH, props.component.key); + this.state = { graph }; + } + + componentDidMount() { + this.mounted = true; + this.loadStatus(); + this.loadHistory(); + } + + componentDidUpdate(prevProps: Props) { + if ( + this.props.component.key !== prevProps.component.key || + !isSameBranchLike(this.props.branchLike, prevProps.branchLike) + ) { + this.loadStatus(); + this.loadHistory(); + } + } + + componentWillUnmount() { + this.mounted = false; + } + + loadStatus = () => { + if (this.props.component.qualifier === 'APP') { + this.loadApplicationStatus(); + } else { + this.loadProjectStatus(); + } + }; + + loadApplicationStatus = async () => { + const { branchLike, component } = this.props; + this.setState({ loadingStatus: true }); + + // Start by loading the application quality gate info, as well as the meta + // data for the application as a whole. + const appStatus = await getApplicationQualityGate({ + application: component.key, + ...getBranchLikeQuery(branchLike) + }); + const { measures: appMeasures, metrics, periods } = await this.loadMeasuresAndMeta( + component.key + ); + + // We also need to load the application leak periods separately. + getApplicationLeak(component.key, branchLike && getBranchLikeDisplayName(branchLike)).then( + leaks => { + if (this.mounted && leaks && leaks.length) { + const sortedLeaks = sortBy(leaks, leak => { + return new Date(leak.date); + }); + this.setState({ + appLeak: sortedLeaks[0] + }); + } + }, + () => { + if (this.mounted) { + this.setState({ appLeak: undefined }); + } + } + ); + + // We need to load the measures for each project in an application + // individually, in order to display all QG conditions correctly. Loading + // them at the parent application level will not get all the necessary + // information, unfortunately, as they are aggregated. + Promise.all( + appStatus.projects.map(project => { + return this.loadMeasuresAndMeta( + project.key, + // Only load metrics that apply to failing QG conditions; we don't + // need the others anyway. + project.conditions.filter(c => c.status !== 'OK').map(c => c.metric) + ).then(({ measures }) => ({ + measures, + project + })); + }) + ).then( + results => { + if (this.mounted) { + const qgStatuses = results.map(({ measures = [], project }) => { + const { key, name, status } = project; + const conditions = extractStatusConditionsFromApplicationStatusChildProject(project); + const failedConditions = this.getFailedConditions(conditions, measures); + + return { + failedConditions, + key, + name, + status + }; + }); + + this.setState({ + loadingStatus: false, + measures: appMeasures, + metrics, + periods, + qgStatuses + }); + } + }, + () => { + if (this.mounted) { + this.setState({ loadingStatus: false, qgStatuses: undefined }); + } + } + ); + }; + + loadProjectStatus = async () => { + const { + branchLike, + component: { key, name } + } = this.props; + this.setState({ loadingStatus: true }); + + const projectStatus = await getQualityGateProjectStatus({ + projectKey: key, + ...getBranchLikeQuery(branchLike) + }); + + // Get failing condition metric keys. We need measures for them as well to + // render them. + const metricKeys = + projectStatus.conditions !== undefined + ? uniq([...METRICS, ...projectStatus.conditions.map(c => c.metricKey)]) + : METRICS; + + this.loadMeasuresAndMeta(key, metricKeys).then( + ({ measures, metrics, periods }) => { + if (this.mounted && measures) { + const { ignoredConditions, status } = projectStatus; + const conditions = extractStatusConditionsFromProjectStatus(projectStatus); + const failedConditions = this.getFailedConditions(conditions, measures); + + const qgStatus = { + ignoredConditions, + failedConditions, + key, + name, + status + }; + + this.setState({ + loadingStatus: false, + measures, + metrics, + periods, + qgStatuses: [qgStatus] + }); + } else if (this.mounted) { + this.setState({ loadingStatus: false, qgStatuses: undefined }); + } + }, + () => { + if (this.mounted) { + this.setState({ loadingStatus: false, qgStatuses: undefined }); + } + } + ); + }; + + loadMeasuresAndMeta = (componentKey: string, metricKeys: string[] = []) => { + const { branchLike } = this.props; + + return getMeasuresAndMeta(componentKey, metricKeys.length > 0 ? metricKeys : METRICS, { + additionalFields: 'metrics,periods', + ...getBranchLikeQuery(branchLike) + }).then(({ component: { measures }, metrics, periods }) => { + return { + measures: enhanceMeasuresWithMetrics(measures || [], metrics || []), + metrics, + periods + }; + }); + }; + + loadHistory = () => { + this.setState({ loadingHistory: true }); + + return Promise.all([this.loadHistoryMeasures(), this.loadAnalyses()]).then( + this.doneLoadingHistory, + this.doneLoadingHistory + ); + }; + + loadHistoryMeasures = () => { + const { branchLike, component } = this.props; + const { graph } = this.state; + + const graphMetrics = getHistoryMetrics(graph, []); + const metrics = uniq([...HISTORY_METRICS_LIST, ...graphMetrics]); + + return getTimeMachineData({ + ...getBranchLikeQuery(branchLike), + from: FROM_DATE, + component: component.key, + metrics: metrics.join() + }).then( + ({ measures }) => { + if (this.mounted) { + this.setState({ + measuresHistory: measures.map(measure => ({ + metric: measure.metric, + history: measure.history.map(analysis => ({ + date: parseDate(analysis.date), + value: analysis.value + })) + })) + }); + } + }, + () => {} + ); + }; + + loadAnalyses = () => { + const { branchLike } = this.props; + + return getProjectActivity({ + ...getBranchLikeQuery(branchLike), + project: this.getTopLevelComponent(), + from: FROM_DATE + }).then( + ({ analyses }) => { + if (this.mounted) { + this.setState({ + analyses + }); + } + }, + () => {} + ); + }; + + getFailedConditions = ( + conditions: QualityGateStatusCondition[], + measures: T.MeasureEnhanced[] + ) => { + return ( + conditions + .filter(c => c.level !== 'OK') + // Enhance them with Metric information, which will be needed + // to render the conditions properly. + .map(c => enhanceConditionWithMeasure(c, measures)) + // The enhancement will return undefined if it cannot find the + // appropriate measure. Make sure we filter them out. + .filter(isDefined) + ); + }; + + getTopLevelComponent = () => { + const { component } = this.props; + let current = component.breadcrumbs.length - 1; + while ( + current > 0 && + !['TRK', 'VW', 'APP'].includes(component.breadcrumbs[current].qualifier) + ) { + current--; + } + return component.breadcrumbs[current].key; + }; + + doneLoadingHistory = () => { + if (this.mounted) { + this.setState({ + loadingHistory: false + }); + } + }; + + handleGraphChange = (graph: GraphType) => { + const { component } = this.props; + saveActivityGraph(BRANCH_OVERVIEW_ACTIVITY_GRAPH, component.key, graph); + this.setState({ graph, loadingHistory: true }, () => { + this.loadHistoryMeasures().then(this.doneLoadingHistory, this.doneLoadingHistory); + }); + }; + + render() { + const { branchLike, component } = this.props; + const { + analyses, + appLeak, + graph, + loadingStatus, + loadingHistory, + measures, + measuresHistory, + metrics, + periods, + qgStatuses + } = this.state; + + const leakPeriod = component.qualifier === 'APP' ? appLeak : getLeakPeriod(periods); + + const projectIsEmpty = + loadingStatus === false && + (measures === undefined || + measures.find(measure => + ([MetricKey.lines, MetricKey.new_lines] as string[]).includes(measure.metric.key) + ) === undefined); + + return ( + + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/overview/branches/BranchOverviewRenderer.tsx b/server/sonar-web/src/main/js/apps/overview/branches/BranchOverviewRenderer.tsx new file mode 100644 index 00000000000..7db0fe4158e --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/branches/BranchOverviewRenderer.tsx @@ -0,0 +1,115 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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 { parseDate } from 'sonar-ui-common/helpers/dates'; +import A11ySkipTarget from '../../../app/components/a11y/A11ySkipTarget'; +import { ApplicationPeriod } from '../../../types/application'; +import { BranchLike } from '../../../types/branch-like'; +import { GraphType, MeasureHistory } from '../../../types/project-activity'; +import { QualityGateStatus } from '../../../types/quality-gates'; +import ActivityPanel from './ActivityPanel'; +import { MeasuresPanel } from './MeasuresPanel'; +import NoCodeWarning from './NoCodeWarning'; +import QualityGatePanel from './QualityGatePanel'; + +export interface BranchOverviewRendererProps { + analyses?: T.Analysis[]; + branchLike?: BranchLike; + component: T.Component; + graph?: GraphType; + leakPeriod?: T.Period | ApplicationPeriod; + loadingHistory?: boolean; + loadingStatus?: boolean; + measures?: T.MeasureEnhanced[]; + measuresHistory?: MeasureHistory[]; + metrics?: T.Metric[]; + onGraphChange: (graph: GraphType) => void; + projectIsEmpty?: boolean; + qgStatuses?: QualityGateStatus[]; +} + +export function BranchOverviewRenderer(props: BranchOverviewRendererProps) { + const { + analyses, + branchLike, + component, + graph, + leakPeriod, + loadingHistory, + loadingStatus, + measures, + measuresHistory = [], + metrics = [], + onGraphChange, + projectIsEmpty, + qgStatuses + } = props; + + return ( +
+
+ + + {projectIsEmpty ? ( + + ) : ( + <> +
+
+ +
+ +
+
+ + + +
+
+
+ + )} +
+
+ ); +} + +export default React.memo(BranchOverviewRenderer); diff --git a/server/sonar-web/src/main/js/apps/overview/branches/DebtValue.tsx b/server/sonar-web/src/main/js/apps/overview/branches/DebtValue.tsx new file mode 100644 index 00000000000..00c01a5057f --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/branches/DebtValue.tsx @@ -0,0 +1,66 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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 { getLocalizedMetricName, translate } from 'sonar-ui-common/helpers/l10n'; +import { formatMeasure } from 'sonar-ui-common/helpers/measures'; +import { getLeakValue } from '../../../components/measure/utils'; +import DrilldownLink from '../../../components/shared/DrilldownLink'; +import { findMeasure, localizeMetric } from '../../../helpers/measures'; +import { BranchLike } from '../../../types/branch-like'; +import { MetricKey } from '../../../types/metrics'; + +export interface DebtValueProps { + branchLike?: BranchLike; + component: T.Component; + measures: T.MeasureEnhanced[]; + useDiffMetric?: boolean; +} + +export function DebtValue(props: DebtValueProps) { + const { branchLike, component, measures, useDiffMetric = false } = props; + const metric = useDiffMetric ? MetricKey.new_technical_debt : MetricKey.sqale_index; + const measure = findMeasure(measures, metric); + + let value; + if (measure) { + value = useDiffMetric ? getLeakValue(measure) : measure.value; + } + + return ( + <> + {value === undefined ? ( + + ) : ( + + {formatMeasure(value, 'WORK_DUR')} + + )} + + {measure ? getLocalizedMetricName(measure.metric, true) : localizeMetric(metric)} + + + ); +} + +export default React.memo(DebtValue); diff --git a/server/sonar-web/src/main/js/apps/overview/branches/DrilldownMeasureValue.tsx b/server/sonar-web/src/main/js/apps/overview/branches/DrilldownMeasureValue.tsx new file mode 100644 index 00000000000..4db04aef6c0 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/branches/DrilldownMeasureValue.tsx @@ -0,0 +1,64 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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 { getLocalizedMetricName } from 'sonar-ui-common/helpers/l10n'; +import { formatMeasure } from 'sonar-ui-common/helpers/measures'; +import DrilldownLink from '../../../components/shared/DrilldownLink'; +import { findMeasure } from '../../../helpers/measures'; +import { BranchLike } from '../../../types/branch-like'; +import { MetricKey } from '../../../types/metrics'; + +export interface DrilldownMeasureValueProps { + branchLike?: BranchLike; + component: T.Component; + measures: T.MeasureEnhanced[]; + metric: MetricKey; +} + +export function DrilldownMeasureValue(props: DrilldownMeasureValueProps) { + const { branchLike, component, measures, metric } = props; + const measure = findMeasure(measures, metric); + + let content; + if (!measure) { + content = -; + } else { + content = ( + + + {formatMeasure(measure.value, 'SHORT_INT')} + + + ); + } + + return ( +
+ {content} + {getLocalizedMetricName({ key: metric })} +
+ ); +} + +export default React.memo(DrilldownMeasureValue); diff --git a/server/sonar-web/src/main/js/apps/overview/events/Event.tsx b/server/sonar-web/src/main/js/apps/overview/branches/Event.tsx similarity index 97% rename from server/sonar-web/src/main/js/apps/overview/events/Event.tsx rename to server/sonar-web/src/main/js/apps/overview/branches/Event.tsx index bdd90941df2..375380d086b 100644 --- a/server/sonar-web/src/main/js/apps/overview/events/Event.tsx +++ b/server/sonar-web/src/main/js/apps/overview/branches/Event.tsx @@ -28,7 +28,7 @@ interface Props { event: T.AnalysisEvent; } -export default function Event({ event }: Props) { +export function Event({ event }: Props) { if (event.category === 'VERSION') { return ( ); } + +export default React.memo(Event); diff --git a/server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanel.tsx b/server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanel.tsx new file mode 100644 index 00000000000..dfd6f067690 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanel.tsx @@ -0,0 +1,265 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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 { FormattedMessage } from 'react-intl'; +import { Link } from 'react-router'; +import BoxedTabs from 'sonar-ui-common/components/controls/BoxedTabs'; +import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner'; +import { translate } from 'sonar-ui-common/helpers/l10n'; +import { isDiffMetric } from 'sonar-ui-common/helpers/measures'; +import { getBaseUrl } from 'sonar-ui-common/helpers/urls'; +import { rawSizes } from '../../../app/theme'; +import { findMeasure } from '../../../helpers/measures'; +import { ApplicationPeriod } from '../../../types/application'; +import { BranchLike } from '../../../types/branch-like'; +import { ComponentQualifier } from '../../../types/component'; +import { MetricKey } from '../../../types/metrics'; +import IssueLabel from '../components/IssueLabel'; +import IssueRating from '../components/IssueRating'; +import MeasurementLabel from '../components/MeasurementLabel'; +import { IssueType, MeasurementType } from '../utils'; +import DebtValue from './DebtValue'; +import { DrilldownMeasureValue } from './DrilldownMeasureValue'; +import { LeakPeriodInfo } from './LeakPeriodInfo'; + +export interface MeasuresPanelProps { + branchLike?: BranchLike; + component: T.Component; + leakPeriod?: T.Period | ApplicationPeriod; + loading?: boolean; + measures?: T.MeasureEnhanced[]; +} + +export enum MeasuresPanelTabs { + New, + Overall +} + +export function MeasuresPanel(props: MeasuresPanelProps) { + const { branchLike, component, loading, leakPeriod, measures = [] } = props; + + const hasDiffMeasures = measures.some(m => isDiffMetric(m.metric.key)); + const isApp = component.qualifier === ComponentQualifier.Application; + + const [tab, selectTab] = React.useState(MeasuresPanelTabs.New); + + React.useEffect(() => { + // Open Overall tab by default if there are no new measures. + if (loading === false && !hasDiffMeasures && tab === MeasuresPanelTabs.New) { + selectTab(MeasuresPanelTabs.Overall); + } + // In this case, we explicitly do NOT want to mark tab as a dependency, as + // it would prevent the user from selecting it, even if it's empty. + /* eslint-disable-next-line react-hooks/exhaustive-deps */ + }, [loading, hasDiffMeasures]); + + const tabs = [ + { + key: MeasuresPanelTabs.New, + label: ( +
+ {translate('overview.new_code')} + {leakPeriod && } +
+ ) + }, + { + key: MeasuresPanelTabs.Overall, + label: ( +
+ + {translate('overview.overall_code')} + +
+ ) + } + ]; + + return ( +
+

{translate('overview.measures')}

+ + {loading ? ( +
+ +
+ ) : ( + <> + + +
+ {!hasDiffMeasures && tab === MeasuresPanelTabs.New ? ( +
+ +
+

+ {translate('overview.measures.empty_explanation')} +

+

+ + {translate('learn_more')} + + ) + }} + /> +

+
+
+ ) : ( + <> + {[IssueType.Bug, IssueType.Vulnerability, IssueType.CodeSmell].map( + (type: IssueType) => ( +
+ {type === IssueType.CodeSmell ? ( + <> +
+ +
+
+ +
+ + ) : ( + <> +
+ +
+ {type === 'VULNERABILITY' && ( +
+ +
+ )} + + )} + {(!isApp || tab === MeasuresPanelTabs.Overall) && ( +
+ +
+ )} +
+ ) + )} + +
+ {(findMeasure(measures, MetricKey.coverage) || + findMeasure(measures, MetricKey.new_coverage)) && ( +
+ + + {tab === MeasuresPanelTabs.Overall && ( +
+ +
+ )} +
+ )} +
+ + + {tab === MeasuresPanelTabs.Overall && ( +
+ +
+ )} +
+
+ + )} +
+ + )} +
+ ); +} + +export default React.memo(MeasuresPanel); diff --git a/server/sonar-web/src/main/js/apps/overview/branches/NoCodeWarning.tsx b/server/sonar-web/src/main/js/apps/overview/branches/NoCodeWarning.tsx new file mode 100644 index 00000000000..1a81132e2a1 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/branches/NoCodeWarning.tsx @@ -0,0 +1,90 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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 { Alert } from 'sonar-ui-common/components/ui/Alert'; +import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; +import { getBranchLikeDisplayName, isMainBranch } from '../../../helpers/branch-like'; +import { BranchLike } from '../../../types/branch-like'; + +interface Props { + branchLike?: BranchLike; + component: T.Component; + measures?: T.MeasureEnhanced[]; +} + +export function NoCodeWarning({ branchLike, component, measures }: Props) { + const isApp = component.qualifier === 'APP'; + + /* eslint-disable no-lonely-if */ + // - Is App + // - No measures, OR measures, but no projects => empty + // - Else => no lines of code + // - Else + // - No measures => empty + // - Main branch? + // - LLB? + // - No branch info? + // - Measures, but no ncloc (checked in isEmpty()) => no lines of code + // - Main branch? + // - LLB? + // - No branch info? + let title = translate('overview.project.no_lines_of_code'); + if (isApp) { + if ( + measures === undefined || + measures.find(measure => measure.metric.key === 'projects') === undefined + ) { + title = translate('portfolio.app.empty'); + } else { + title = translate('portfolio.app.no_lines_of_code'); + } + } else { + if (measures === undefined || measures.length === 0) { + if (isMainBranch(branchLike)) { + title = translate('overview.project.main_branch_empty'); + } else if (branchLike !== undefined) { + title = translateWithParameters( + 'overview.project.branch_X_empty', + getBranchLikeDisplayName(branchLike) + ); + } else { + title = translate('overview.project.empty'); + } + } else { + if (isMainBranch(branchLike)) { + title = translate('overview.project.main_branch_no_lines_of_code'); + } else if (branchLike !== undefined) { + title = translateWithParameters( + 'overview.project.branch_X_no_lines_of_code', + getBranchLikeDisplayName(branchLike) + ); + } + } + } + /* eslint-enable no-lonely-if */ + + return ( + + {title} + + ); +} + +export default React.memo(NoCodeWarning); diff --git a/server/sonar-web/src/main/js/apps/overview/branches/QualityGatePanel.tsx b/server/sonar-web/src/main/js/apps/overview/branches/QualityGatePanel.tsx new file mode 100644 index 00000000000..fbea9c5716d --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/branches/QualityGatePanel.tsx @@ -0,0 +1,125 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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 classNames from 'classnames'; +import * as React from 'react'; +import HelpTooltip from 'sonar-ui-common/components/controls/HelpTooltip'; +import { Alert } from 'sonar-ui-common/components/ui/Alert'; +import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner'; +import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; +import DocTooltip from '../../../components/docs/DocTooltip'; +import { BranchLike } from '../../../types/branch-like'; +import { QualityGateStatus } from '../../../types/quality-gates'; +import QualityGatePanelSection from './QualityGatePanelSection'; + +export interface QualityGatePanelProps { + branchLike?: BranchLike; + component: Pick; + loading?: boolean; + qgStatuses?: QualityGateStatus[]; +} + +export function QualityGatePanel(props: QualityGatePanelProps) { + const { branchLike, component, loading, qgStatuses = [] } = props; + + if (qgStatuses === undefined) { + return null; + } + + const overallLevel = qgStatuses.map(s => s.status).includes('ERROR') ? 'ERROR' : 'OK'; + const success = overallLevel === 'OK'; + + const overallFailedConditionsCount = qgStatuses.reduce( + (acc, qgStatus) => acc + qgStatus.failedConditions.length, + 0 + ); + + const showIgnoredConditionWarning = + component.qualifier === 'TRK' && + qgStatuses !== undefined && + qgStatuses.some(p => Boolean(p.ignoredConditions)); + + return ( +
+

+ {translate('overview.quality_gate')}{' '} + +

+ + {showIgnoredConditionWarning && ( + + + {translate('overview.quality_gate.ignored_conditions')} + + + + )} + +
+ {loading ? ( +
+ +
+ ) : ( + <> +
+

{translate('metric.level', overallLevel)}

+ + + {overallFailedConditionsCount > 0 + ? translateWithParameters( + 'overview.X_conditions_failed', + overallFailedConditionsCount + ) + : translate('overview.quality_gate_all_conditions_passed')} + +
+ + {overallFailedConditionsCount > 0 && ( +
+ {qgStatuses.map(qgStatus => ( + + ))} +
+ )} + + )} +
+
+ ); +} + +export default React.memo(QualityGatePanel); diff --git a/server/sonar-web/src/main/js/apps/overview/branches/QualityGatePanelSection.tsx b/server/sonar-web/src/main/js/apps/overview/branches/QualityGatePanelSection.tsx new file mode 100644 index 00000000000..a304c5dccd9 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/branches/QualityGatePanelSection.tsx @@ -0,0 +1,79 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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 { translate } from 'sonar-ui-common/helpers/l10n'; +import { isDiffMetric } from '../../../helpers/measures'; +import { BranchLike } from '../../../types/branch-like'; +import { QualityGateStatus } from '../../../types/quality-gates'; +import { QualityGateConditions } from '../components/QualityGateConditions'; + +export interface QualityGatePanelSectionProps { + branchLike?: BranchLike; + component: Pick; + qgStatus: QualityGateStatus; +} + +export function QualityGatePanelSection(props: QualityGatePanelSectionProps) { + const { branchLike, component, qgStatus } = props; + const newCodeFailedConditions = qgStatus.failedConditions.filter(c => isDiffMetric(c.metric)); + const overallFailedConditions = qgStatus.failedConditions.filter(c => !isDiffMetric(c.metric)); + + if (newCodeFailedConditions.length === 0 && overallFailedConditions.length === 0) { + return null; + } + + const showName = component.qualifier === 'APP'; + + return ( +
+ {showName && ( +

{qgStatus.name}

+ )} + + {newCodeFailedConditions.length > 0 && ( + <> +

+ {translate('quality_gates.conditions.new_code')} +

+ + + )} + + {overallFailedConditions.length > 0 && ( + <> +

+ {translate('quality_gates.conditions.overall_code')} +

+ + + )} +
+ ); +} + +export default React.memo(QualityGatePanelSection); diff --git a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/ActivityPanel-test.tsx b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/ActivityPanel-test.tsx new file mode 100644 index 00000000000..21856507dd6 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/ActivityPanel-test.tsx @@ -0,0 +1,83 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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 { shallow } from 'enzyme'; +import * as React from 'react'; +import { parseDate } from 'sonar-ui-common/helpers/dates'; +import GraphsHistory from '../../../../components/activity-graph/GraphsHistory'; +import { mockMainBranch } from '../../../../helpers/mocks/branch-like'; +import { + mockAnalysis, + mockAnalysisEvent, + mockComponent, + mockMeasure, + mockMetric +} from '../../../../helpers/testMocks'; +import { GraphType } from '../../../../types/project-activity'; +import { ActivityPanel, ActivityPanelProps } from '../ActivityPanel'; + +it('should render correctly', () => { + expect(shallowRender()).toMatchSnapshot(); + expect(shallowRender({ loading: true, analyses: undefined })).toMatchSnapshot(); +}); + +it('should correctly pass the leak period start date', () => { + // Leak period start is more recent than the oldest historic measure. + let { leakPeriodDate } = shallowRender({ + leakPeriodDate: parseDate('2017-08-27T16:33:50+0200') + }) + .find(GraphsHistory) + .props(); + + if (leakPeriodDate) { + expect(leakPeriodDate.getTime()).toBe(1503844430000); /* 2017-08-27T16:33:50+0200 */ + } else { + fail('leakPeriodDate should have been set'); + } + + // Leak period start is older than the oldest historic measure. + ({ leakPeriodDate } = shallowRender({ leakPeriodDate: parseDate('2015-08-27T16:33:50+0200') }) + .find(GraphsHistory) + .props()); + + if (leakPeriodDate) { + expect(leakPeriodDate.getTime()).toBe(1477578830000); /* 2016-10-27T16:33:50+0200 */ + } else { + fail('leakPeriodDate should have been set'); + } +}); + +function shallowRender(props: Partial = {}) { + return shallow( + ({ + ...m, + history: [{ date: parseDate('2016-10-27T16:33:50+0200'), value: '20' }] + }))} + metrics={[mockMetric({ key: 'bugs' })]} + onGraphChange={jest.fn()} + {...props} + /> + ); +} diff --git a/server/sonar-web/src/main/js/apps/overview/events/__tests__/AnalysesList-test.tsx b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/Analysis-test.tsx similarity index 60% rename from server/sonar-web/src/main/js/apps/overview/events/__tests__/AnalysesList-test.tsx rename to server/sonar-web/src/main/js/apps/overview/branches/__tests__/Analysis-test.tsx index 52ce01f3aa4..d530f2fff8d 100644 --- a/server/sonar-web/src/main/js/apps/overview/events/__tests__/AnalysesList-test.tsx +++ b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/Analysis-test.tsx @@ -19,21 +19,25 @@ */ import { shallow } from 'enzyme'; import * as React from 'react'; -import { mockMainBranch } from '../../../../helpers/mocks/branch-like'; -import AnalysesList from '../AnalysesList'; +import { mockAnalysis } from '../../../../helpers/testMocks'; +import { Analysis, AnalysisProps } from '../Analysis'; -it('should render show more link', () => { - const branchLike = mockMainBranch(); - const component = { - breadcrumbs: [{ key: 'foo', name: 'foo', qualifier: 'TRK' }], - key: 'foo', - name: 'foo', - organization: 'org', - qualifier: 'TRK' - }; - const wrapper = shallow( - - ); - wrapper.setState({ loading: false }); - expect(wrapper.find('Link')).toMatchSnapshot(); +it('should render correctly', () => { + expect(shallowRender()).toMatchSnapshot(); + expect(shallowRender({ qualifier: 'APP' })).toMatchSnapshot(); }); + +function shallowRender(props: Partial = {}) { + return shallow( + + ); +} diff --git a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/BranchOverview-test.tsx b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/BranchOverview-test.tsx new file mode 100644 index 00000000000..99244d2f41c --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/BranchOverview-test.tsx @@ -0,0 +1,349 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +/* eslint-disable sonarjs/no-duplicate-string */ +import { shallow } from 'enzyme'; +import * as React from 'react'; +import { isDiffMetric } from 'sonar-ui-common/helpers/measures'; +import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; +import { getApplicationLeak } from '../../../../api/application'; +import { getMeasuresAndMeta } from '../../../../api/measures'; +import { getProjectActivity } from '../../../../api/projectActivity'; +import { + getApplicationQualityGate, + getQualityGateProjectStatus +} from '../../../../api/quality-gates'; +import { getTimeMachineData } from '../../../../api/time-machine'; +import { getActivityGraph, saveActivityGraph } from '../../../../components/activity-graph/utils'; +import { mockMainBranch } from '../../../../helpers/mocks/branch-like'; +import { mockComponent } from '../../../../helpers/testMocks'; +import { MetricKey } from '../../../../types/metrics'; +import { GraphType } from '../../../../types/project-activity'; +import BranchOverview, { BRANCH_OVERVIEW_ACTIVITY_GRAPH } from '../BranchOverview'; +import BranchOverviewRenderer from '../BranchOverviewRenderer'; + +jest.mock('sonar-ui-common/helpers/dates', () => ({ + parseDate: jest.fn(date => `PARSED:${date}`), + toNotSoISOString: jest.fn(date => date) +})); + +jest.mock('../../../../api/measures', () => { + const { mockMeasure, mockMetric } = require.requireActual('../../../../helpers/testMocks'); + return { + getMeasuresAndMeta: jest.fn((_, metricKeys: string[]) => { + const metrics: T.Metric[] = []; + const measures: T.Measure[] = []; + metricKeys.forEach(key => { + if (key === 'unknown_metric') { + return; + } + + let type; + if (/(coverage|duplication)$/.test(key)) { + type = 'PERCENT'; + } else if (/_rating$/.test(key)) { + type = 'RATING'; + } else { + type = 'INT'; + } + metrics.push(mockMetric({ key, id: key, name: key, type })); + measures.push( + mockMeasure({ + metric: key, + ...(isDiffMetric(key) ? { leak: '1' } : { periods: undefined }) + }) + ); + }); + return Promise.resolve({ + component: { + measures, + name: 'foo' + }, + metrics + }); + }) + }; +}); + +jest.mock('../../../../api/quality-gates', () => { + const { mockQualityGateProjectStatus, mockQualityGateApplicationStatus } = require.requireActual( + '../../../../helpers/mocks/quality-gates' + ); + const { MetricKey } = require.requireActual('../../../../types/metrics'); + return { + getQualityGateProjectStatus: jest.fn().mockResolvedValue( + mockQualityGateProjectStatus({ + status: 'ERROR', + conditions: [ + { + actualValue: '2', + comparator: 'GT', + errorThreshold: '1.0', + metricKey: MetricKey.new_bugs, + periodIndex: 1, + status: 'ERROR' + }, + { + actualValue: '5', + comparator: 'GT', + errorThreshold: '2.0', + metricKey: MetricKey.bugs, + periodIndex: 0, + status: 'ERROR' + }, + { + actualValue: '2', + comparator: 'GT', + errorThreshold: '1.0', + metricKey: 'unknown_metric', + periodIndex: 0, + status: 'ERROR' + } + ] + }) + ), + getApplicationQualityGate: jest.fn().mockResolvedValue(mockQualityGateApplicationStatus()) + }; +}); + +jest.mock('../../../../api/time-machine', () => { + const { MetricKey } = require.requireActual('../../../../types/metrics'); + return { + getTimeMachineData: jest.fn().mockResolvedValue({ + measures: [ + { metric: MetricKey.bugs, history: [{ date: '2019-01-05', value: '2.0' }] }, + { metric: MetricKey.vulnerabilities, history: [{ date: '2019-01-05', value: '0' }] }, + { metric: MetricKey.sqale_index, history: [{ date: '2019-01-01', value: '1.0' }] }, + { + metric: MetricKey.duplicated_lines_density, + history: [{ date: '2019-01-02', value: '1.0' }] + }, + { metric: MetricKey.ncloc, history: [{ date: '2019-01-03', value: '10000' }] }, + { metric: MetricKey.coverage, history: [{ date: '2019-01-04', value: '95.5' }] } + ] + }) + }; +}); + +jest.mock('../../../../api/projectActivity', () => { + const { mockAnalysis } = require.requireActual('../../../../helpers/testMocks'); + return { + getProjectActivity: jest.fn().mockResolvedValue({ + analyses: [mockAnalysis(), mockAnalysis(), mockAnalysis(), mockAnalysis(), mockAnalysis()] + }) + }; +}); + +jest.mock('../../../../api/application', () => ({ + getApplicationLeak: jest.fn().mockResolvedValue([ + { + date: '2017-01-05', + project: 'foo', + projectName: 'Foo' + } + ]) +})); + +jest.mock('../../../../components/activity-graph/utils', () => { + const { MetricKey } = require.requireActual('../../../../types/metrics'); + const { GraphType } = require.requireActual('../../../../types/project-activity'); + return { + getActivityGraph: jest.fn(() => ({ graph: GraphType.coverage })), + saveActivityGraph: jest.fn(), + getHistoryMetrics: jest.fn(() => [MetricKey.lines_to_cover, MetricKey.uncovered_lines]) + }; +}); + +beforeEach(jest.clearAllMocks); + +describe('project overview', () => { + it('should render correctly', async () => { + const wrapper = shallowRender(); + await waitAndUpdate(wrapper); + expect(wrapper).toMatchSnapshot(); + }); + + it("should correctly load a project's status", async () => { + const wrapper = shallowRender(); + await waitAndUpdate(wrapper); + expect(getQualityGateProjectStatus).toBeCalled(); + expect(getMeasuresAndMeta).toBeCalled(); + + // Check the conditions got correctly enhanced with measure meta data. + const { qgStatuses } = wrapper.state(); + expect(qgStatuses).toHaveLength(1); + const [qgStatus] = qgStatuses!; + + expect(qgStatus).toEqual( + expect.objectContaining({ + name: 'Foo', + key: 'foo', + status: 'ERROR' + }) + ); + + const { failedConditions } = qgStatus; + expect(failedConditions).toHaveLength(2); + expect(failedConditions[0]).toMatchObject({ + actual: '2', + level: 'ERROR', + metric: MetricKey.new_bugs, + measure: expect.objectContaining({ + metric: expect.objectContaining({ key: MetricKey.new_bugs }) + }) + }); + expect(failedConditions[1]).toMatchObject({ + actual: '5', + level: 'ERROR', + metric: MetricKey.bugs, + measure: expect.objectContaining({ + metric: expect.objectContaining({ key: MetricKey.bugs }) + }) + }); + }); + + it('should correctly flag a project as empty', async () => { + (getMeasuresAndMeta as jest.Mock).mockResolvedValueOnce({ component: {} }); + + const wrapper = shallowRender(); + await waitAndUpdate(wrapper); + expect(wrapper.find(BranchOverviewRenderer).props().projectIsEmpty).toBe(true); + }); +}); + +describe('application overview', () => { + const component = mockComponent({ + breadcrumbs: [mockComponent({ key: 'foo', qualifier: 'APP' })], + qualifier: 'APP' + }); + + it('should render correctly', async () => { + const wrapper = shallowRender({ component }); + await waitAndUpdate(wrapper); + expect(wrapper).toMatchSnapshot(); + }); + + it("should correctly load an application's status", async () => { + const wrapper = shallowRender({ component }); + await waitAndUpdate(wrapper); + expect(getApplicationQualityGate).toBeCalled(); + expect(getApplicationLeak).toBeCalled(); + expect(getMeasuresAndMeta).toBeCalled(); + + // Check the conditions got correctly enhanced with measure meta data. + const { qgStatuses } = wrapper.state(); + expect(qgStatuses).toHaveLength(2); + const [qgStatus1, qgStatus2] = qgStatuses!; + + expect(qgStatus1).toEqual( + expect.objectContaining({ + name: 'Foo', + key: 'foo', + status: 'ERROR' + }) + ); + + const { failedConditions: failedConditions1 } = qgStatus1; + expect(failedConditions1).toHaveLength(2); + expect(failedConditions1[0]).toMatchObject({ + actual: '10', + level: 'ERROR', + metric: MetricKey.coverage, + measure: expect.objectContaining({ + metric: expect.objectContaining({ key: MetricKey.coverage }) + }) + }); + expect(failedConditions1[1]).toMatchObject({ + actual: '5', + level: 'ERROR', + metric: MetricKey.new_bugs, + measure: expect.objectContaining({ + metric: expect.objectContaining({ key: MetricKey.new_bugs }) + }) + }); + + expect(qgStatus1).toEqual( + expect.objectContaining({ + name: 'Foo', + key: 'foo', + status: 'ERROR' + }) + ); + + const { failedConditions: failedConditions2 } = qgStatus2; + expect(failedConditions2).toHaveLength(1); + expect(failedConditions2[0]).toMatchObject({ + actual: '15', + level: 'ERROR', + metric: MetricKey.new_bugs, + measure: expect.objectContaining({ + metric: expect.objectContaining({ key: MetricKey.new_bugs }) + }) + }); + }); + + it('should correctly flag an application as empty', async () => { + (getMeasuresAndMeta as jest.Mock).mockResolvedValueOnce({ component: {} }); + + const wrapper = shallowRender({ component }); + await waitAndUpdate(wrapper); + + expect(wrapper.find(BranchOverviewRenderer).props().projectIsEmpty).toBe(true); + }); +}); + +it("should correctly load a component's history", async () => { + const wrapper = shallowRender(); + await waitAndUpdate(wrapper); + expect(getProjectActivity).toBeCalled(); + expect(getTimeMachineData).toBeCalled(); + + const { measuresHistory } = wrapper.state(); + expect(measuresHistory).toHaveLength(6); + expect(measuresHistory![0]).toEqual( + expect.objectContaining({ + metric: MetricKey.bugs, + history: [{ date: 'PARSED:2019-01-05', value: '2.0' }] + }) + ); +}); + +it('should correctly handle graph type storage', () => { + const wrapper = shallowRender(); + expect(getActivityGraph).toBeCalledWith(BRANCH_OVERVIEW_ACTIVITY_GRAPH, 'foo'); + expect(wrapper.state().graph).toBe(GraphType.coverage); + + wrapper.instance().handleGraphChange(GraphType.issues); + expect(saveActivityGraph).toBeCalledWith(BRANCH_OVERVIEW_ACTIVITY_GRAPH, 'foo', GraphType.issues); + expect(wrapper.state().graph).toBe(GraphType.issues); +}); + +function shallowRender(props: Partial = {}) { + return shallow( + + ); +} diff --git a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/BranchOverviewRenderer-test.tsx b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/BranchOverviewRenderer-test.tsx new file mode 100644 index 00000000000..024b32cf1a6 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/BranchOverviewRenderer-test.tsx @@ -0,0 +1,46 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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 { shallow } from 'enzyme'; +import * as React from 'react'; +import { mockMainBranch } from '../../../../helpers/mocks/branch-like'; +import { mockComponent, mockMeasureEnhanced } from '../../../../helpers/testMocks'; +import { GraphType } from '../../../../types/project-activity'; +import { BranchOverviewRenderer, BranchOverviewRendererProps } from '../BranchOverviewRenderer'; + +it('should render correctly', () => { + expect(shallowRender()).toMatchSnapshot(); + expect(shallowRender({ projectIsEmpty: true })).toMatchSnapshot(); + expect(shallowRender({ loadingHistory: true, loadingStatus: true })).toMatchSnapshot(); +}); + +function shallowRender(props: Partial = {}) { + return shallow( + + ); +} diff --git a/server/sonar-web/src/main/js/apps/overview/main/__tests__/Coverage-test.tsx b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/DebtValue-test.tsx similarity index 58% rename from server/sonar-web/src/main/js/apps/overview/main/__tests__/Coverage-test.tsx rename to server/sonar-web/src/main/js/apps/overview/branches/__tests__/DebtValue-test.tsx index 13534354d84..49f1d272fd1 100644 --- a/server/sonar-web/src/main/js/apps/overview/main/__tests__/Coverage-test.tsx +++ b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/DebtValue-test.tsx @@ -21,49 +21,25 @@ import { shallow } from 'enzyme'; import * as React from 'react'; import { mockMainBranch } from '../../../../helpers/mocks/branch-like'; import { mockComponent, mockMeasureEnhanced, mockMetric } from '../../../../helpers/testMocks'; -import Coverage from '../Coverage'; -import { ComposedProps } from '../enhance'; +import { MetricKey } from '../../../../types/metrics'; +import { DebtValue, DebtValueProps } from '../DebtValue'; it('should render correctly', () => { expect(shallowRender()).toMatchSnapshot(); + expect(shallowRender({ useDiffMetric: true })).toMatchSnapshot(); + expect(shallowRender({ measures: [] })).toMatchSnapshot(); }); -function shallowRender(props: Partial = {}) { +function shallowRender(props: Partial = {}) { return shallow( - - ).dive(); + ); } diff --git a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/DrilldownMeasureValue-test.tsx b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/DrilldownMeasureValue-test.tsx new file mode 100644 index 00000000000..73ab71e8bef --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/DrilldownMeasureValue-test.tsx @@ -0,0 +1,42 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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 { shallow } from 'enzyme'; +import * as React from 'react'; +import { mockMainBranch } from '../../../../helpers/mocks/branch-like'; +import { mockComponent, mockMeasureEnhanced, mockMetric } from '../../../../helpers/testMocks'; +import { MetricKey } from '../../../../types/metrics'; +import { DrilldownMeasureValue, DrilldownMeasureValueProps } from '../DrilldownMeasureValue'; + +it('should render correctly', () => { + expect(shallowRender()).toMatchSnapshot('default'); + expect(shallowRender({ metric: MetricKey.bugs })).toMatchSnapshot('measure not found'); +}); + +function shallowRender(props: Partial = {}) { + return shallow( + + ); +} diff --git a/server/sonar-web/src/main/js/apps/overview/events/__tests__/Event-test.tsx b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/Event-test.tsx similarity index 75% rename from server/sonar-web/src/main/js/apps/overview/events/__tests__/Event-test.tsx rename to server/sonar-web/src/main/js/apps/overview/branches/__tests__/Event-test.tsx index 70179114ae4..036705ea099 100644 --- a/server/sonar-web/src/main/js/apps/overview/events/__tests__/Event-test.tsx +++ b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/Event-test.tsx @@ -19,22 +19,22 @@ */ import { shallow } from 'enzyme'; import * as React from 'react'; -import { RichQualityGateEvent } from '../../../projectActivity/components/RichQualityGateEventInner'; -import Event from '../Event'; - -const EVENT = { key: '1', category: 'OTHER', name: 'test' }; -const VERSION = { key: '2', category: 'VERSION', name: '6.5-SNAPSHOT' }; +import { Event } from '../Event'; it('should render an event correctly', () => { - expect(shallow()).toMatchSnapshot(); + expect( + shallow() + ).toMatchSnapshot(); }); it('should render a version correctly', () => { - expect(shallow()).toMatchSnapshot(); + expect( + shallow() + ).toMatchSnapshot(); }); it('should render rich quality gate event', () => { - const event: RichQualityGateEvent = { + const event: T.AnalysisEvent = { category: 'QUALITY_GATE', key: 'foo1234', name: '', diff --git a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/MeasuresPanel-test.tsx b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/MeasuresPanel-test.tsx new file mode 100644 index 00000000000..4f0094bc1b4 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/MeasuresPanel-test.tsx @@ -0,0 +1,82 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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 { shallow } from 'enzyme'; +import * as React from 'react'; +import BoxedTabs from 'sonar-ui-common/components/controls/BoxedTabs'; +import { mockMainBranch } from '../../../../helpers/mocks/branch-like'; +import { mockComponent, mockMeasureEnhanced, mockMetric } from '../../../../helpers/testMocks'; +import { MetricKey } from '../../../../types/metrics'; +import { MeasuresPanel, MeasuresPanelProps, MeasuresPanelTabs } from '../MeasuresPanel'; + +it('should render correctly for projects', () => { + const wrapper = shallowRender(); + expect(wrapper).toMatchSnapshot(); + wrapper.find(BoxedTabs).prop('onSelect')(MeasuresPanelTabs.Overall); + expect(wrapper).toMatchSnapshot(); +}); + +it('should render correctly for applications', () => { + const wrapper = shallowRender({ component: mockComponent({ qualifier: 'APP' }) }); + expect(wrapper).toMatchSnapshot(); + wrapper.find(BoxedTabs).prop('onSelect')(MeasuresPanelTabs.Overall); + expect(wrapper).toMatchSnapshot(); +}); + +it('should render correctly if there is no new code measures', () => { + const wrapper = shallowRender({ + measures: [ + mockMeasureEnhanced({ metric: mockMetric({ key: MetricKey.coverage }) }), + mockMeasureEnhanced({ metric: mockMetric({ key: MetricKey.bugs }) }) + ] + }); + wrapper.find(BoxedTabs).prop('onSelect')(MeasuresPanelTabs.New); + expect(wrapper).toMatchSnapshot(); +}); + +it('should render correctly if there is no coverage', () => { + expect( + shallowRender({ + measures: [ + mockMeasureEnhanced({ metric: mockMetric({ key: MetricKey.bugs }) }), + mockMeasureEnhanced({ metric: mockMetric({ key: MetricKey.new_bugs }) }) + ] + }) + ).toMatchSnapshot(); +}); + +it('should render correctly if the data is still loading', () => { + expect(shallowRender({ loading: true })).toMatchSnapshot(); +}); + +function shallowRender(props: Partial = {}) { + return shallow( + + ); +} diff --git a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/NoCodeWarning-test.tsx b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/NoCodeWarning-test.tsx new file mode 100644 index 00000000000..cfec0025b07 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/NoCodeWarning-test.tsx @@ -0,0 +1,68 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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 { shallow } from 'enzyme'; +import * as React from 'react'; +import { mockBranch, mockMainBranch } from '../../../../helpers/mocks/branch-like'; +import { mockComponent, mockMeasureEnhanced, mockMetric } from '../../../../helpers/testMocks'; +import { NoCodeWarning } from '../NoCodeWarning'; + +it.only('should render correctly if the project has no lines of code', () => { + const wrapper = shallowRender(); + expect(wrapper.children().text()).toBe('overview.project.main_branch_no_lines_of_code'); + + wrapper.setProps({ branchLike: mockBranch({ name: 'branch-foo' }) }); + expect(wrapper.children().text()).toBe('overview.project.branch_X_no_lines_of_code.branch-foo'); + + wrapper.setProps({ branchLike: undefined }); + expect(wrapper.children().text()).toBe('overview.project.no_lines_of_code'); +}); + +it('should correctly if the project is empty', () => { + const wrapper = shallowRender({ measures: [] }); + expect(wrapper.children().text()).toBe('overview.project.main_branch_empty'); + + wrapper.setProps({ branchLike: mockBranch({ name: 'branch-foo' }) }); + expect(wrapper.children().text()).toBe('overview.project.branch_X_empty.branch-foo'); + + wrapper.setProps({ branchLike: undefined }); + expect(wrapper.children().text()).toBe('overview.project.empty'); +}); + +it('should render correctly if the application is empty or has no lines of code', () => { + const wrapper = shallowRender({ + component: mockComponent({ qualifier: 'APP' }), + measures: [mockMeasureEnhanced({ metric: mockMetric({ key: 'projects' }) })] + }); + expect(wrapper.children().text()).toBe('portfolio.app.no_lines_of_code'); + + wrapper.setProps({ measures: [] }); + expect(wrapper.children().text()).toBe('portfolio.app.empty'); +}); + +function shallowRender(props = {}) { + return shallow( + + ); +} diff --git a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/QualityGatePanel-test.tsx b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/QualityGatePanel-test.tsx new file mode 100644 index 00000000000..8769973e216 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/QualityGatePanel-test.tsx @@ -0,0 +1,83 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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 { shallow } from 'enzyme'; +import * as React from 'react'; +import { mockMainBranch } from '../../../../helpers/mocks/branch-like'; +import { + mockQualityGateStatus, + mockQualityGateStatusConditionEnhanced +} from '../../../../helpers/mocks/quality-gates'; +import { mockComponent, mockMeasureEnhanced, mockMetric } from '../../../../helpers/testMocks'; +import { QualityGatePanel, QualityGatePanelProps } from '../QualityGatePanel'; + +it('should render correctly for projects', () => { + expect(shallowRender()).toMatchSnapshot(); + expect( + shallowRender({ qgStatuses: [mockQualityGateStatus({ status: 'OK', failedConditions: [] })] }) + ).toMatchSnapshot(); + + const wrapper = shallowRender({ + qgStatuses: [mockQualityGateStatus({ ignoredConditions: true })] + }); + expect(wrapper).toMatchSnapshot(); +}); + +it('should render correctly for applications', () => { + expect( + shallowRender({ + component: mockComponent({ qualifier: 'APP' }), + qgStatuses: [ + mockQualityGateStatus(), + mockQualityGateStatus({ + failedConditions: [ + mockQualityGateStatusConditionEnhanced(), + mockQualityGateStatusConditionEnhanced({ + measure: mockMeasureEnhanced({ metric: mockMetric({ key: 'new_code_smells' }) }), + metric: 'new_code_smells' + }) + ] + }) + ] + }) + ).toMatchSnapshot(); + + const wrapper = shallowRender({ + component: mockComponent({ qualifier: 'APP' }), + qgStatuses: [ + mockQualityGateStatus(), + mockQualityGateStatus({ + status: 'OK', + failedConditions: [] + }) + ] + }); + expect(wrapper).toMatchSnapshot(); +}); + +function shallowRender(props: Partial = {}) { + return shallow( + + ); +} diff --git a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/QualityGatePanelSection-test.tsx b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/QualityGatePanelSection-test.tsx new file mode 100644 index 00000000000..50169bf8223 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/QualityGatePanelSection-test.tsx @@ -0,0 +1,58 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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 { shallow } from 'enzyme'; +import * as React from 'react'; +import { mockMainBranch } from '../../../../helpers/mocks/branch-like'; +import { + mockQualityGateStatus, + mockQualityGateStatusConditionEnhanced +} from '../../../../helpers/mocks/quality-gates'; +import { mockComponent } from '../../../../helpers/testMocks'; +import { QualityGatePanelSection, QualityGatePanelSectionProps } from '../QualityGatePanelSection'; + +it('should render correctly', () => { + expect(shallowRender()).toMatchSnapshot(); + expect( + shallowRender({ + qgStatus: mockQualityGateStatus({ + failedConditions: [], + status: 'OK' + }) + }).type() + ).toBeNull(); + expect(shallowRender({ component: mockComponent({ qualifier: 'APP' }) })).toMatchSnapshot(); +}); + +function shallowRender(props: Partial = {}) { + return shallow( + + ); +} diff --git a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/ActivityPanel-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/ActivityPanel-test.tsx.snap new file mode 100644 index 00000000000..90b14ae0e81 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/ActivityPanel-test.tsx.snap @@ -0,0 +1,349 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` +
+

+ overview.activity +

+
+
+
+
+ + +
+
+ +
+
+
+
+ +
    + +
+
+
+
+
+
+
+`; + +exports[`should render correctly 2`] = ` +
+

+ overview.activity +

+
+
+
+
+ + +
+
+ +
+
+
+
+ +

+ no_results +

+
+
+
+
+
+
+`; diff --git a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/Analysis-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/Analysis-test.tsx.snap new file mode 100644 index 00000000000..e8a36493871 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/Analysis-test.tsx.snap @@ -0,0 +1,81 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` +
  • +
    + + + +
    +
    + + +
    +
  • +`; + +exports[`should render correctly 2`] = ` +
  • +
    + + + +
    +
    + + +
    +
  • +`; diff --git a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/ApplicationLeakPeriodInfo-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/ApplicationLeakPeriodInfo-test.tsx.snap new file mode 100644 index 00000000000..8a0e03c64df --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/ApplicationLeakPeriodInfo-test.tsx.snap @@ -0,0 +1,19 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly 1`] = ` +
    + + + + +
    +`; + +exports[`renders correctly 2`] = `"overview.started_x.2017-10-01"`; diff --git a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/BranchOverview-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/BranchOverview-test.tsx.snap new file mode 100644 index 00000000000..a7e7dc16f27 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/BranchOverview-test.tsx.snap @@ -0,0 +1,1702 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`application overview should render correctly 1`] = ` + +`; + +exports[`project overview should render correctly 1`] = ` + +`; diff --git a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/BranchOverviewRenderer-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/BranchOverviewRenderer-test.tsx.snap new file mode 100644 index 00000000000..26d005be471 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/BranchOverviewRenderer-test.tsx.snap @@ -0,0 +1,386 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` +
    +
    + +
    +
    + +
    +
    +
    + + +
    +
    +
    +
    +
    +`; + +exports[`should render correctly 2`] = ` +
    +
    + + +
    +
    +`; + +exports[`should render correctly 3`] = ` +
    +
    + +
    +
    + +
    +
    +
    + + +
    +
    +
    +
    +
    +`; diff --git a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/DebtValue-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/DebtValue-test.tsx.snap new file mode 100644 index 00000000000..a96ac567ff1 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/DebtValue-test.tsx.snap @@ -0,0 +1,65 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` + + + work_duration.x_minutes.1 + + + Sqale_index + + +`; + +exports[`should render correctly 2`] = ` + + + work_duration.x_minutes.1 + + + New_technical_debt + + +`; + +exports[`should render correctly 3`] = ` + + + + metric.sqale_index.name + + +`; diff --git a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/DrilldownMeasureValue-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/DrilldownMeasureValue-test.tsx.snap new file mode 100644 index 00000000000..210bb6293e2 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/DrilldownMeasureValue-test.tsx.snap @@ -0,0 +1,47 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly: default 1`] = ` +
    + + + 1 + + + + tests + +
    +`; + +exports[`should render correctly: measure not found 1`] = ` +
    + + - + + + bugs + +
    +`; diff --git a/server/sonar-web/src/main/js/apps/overview/events/__tests__/__snapshots__/Event-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/Event-test.tsx.snap similarity index 100% rename from server/sonar-web/src/main/js/apps/overview/events/__tests__/__snapshots__/Event-test.tsx.snap rename to server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/Event-test.tsx.snap diff --git a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/LeakPeriodInfo-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/LeakPeriodInfo-test.tsx.snap new file mode 100644 index 00000000000..a6a8d910dfa --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/LeakPeriodInfo-test.tsx.snap @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly for applications 1`] = ` + +`; + +exports[`renders correctly for projects 1`] = ` + +`; diff --git a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/MeasuresPanel-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/MeasuresPanel-test.tsx.snap new file mode 100644 index 00000000000..299370e948f --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/MeasuresPanel-test.tsx.snap @@ -0,0 +1,5943 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly for applications 1`] = ` +
    +

    + overview.measures +

    + + + overview.new_code + +
    , + }, + Object { + "key": 1, + "label":
    + + overview.overall_code + +
    , + }, + ] + } + /> +
    +
    +
    + +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    + +
    +
    + +
    +
    +
    + +`; + +exports[`should render correctly for applications 2`] = ` +
    +

    + overview.measures +

    + + + overview.new_code + +
    , + }, + Object { + "key": 1, + "label":
    + + overview.overall_code + +
    , + }, + ] + } + /> +
    +
    +
    + +
    +
    + +
    +
    +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    +
    + +`; + +exports[`should render correctly for projects 1`] = ` +
    +

    + overview.measures +

    + + + overview.new_code + +
    , + }, + Object { + "key": 1, + "label":
    + + overview.overall_code + +
    , + }, + ] + } + /> +
    +
    +
    + +
    +
    + +
    +
    +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    +
    + +
    +
    + +
    +
    +
    + +`; + +exports[`should render correctly for projects 2`] = ` +
    +

    + overview.measures +

    + + + overview.new_code + +
    , + }, + Object { + "key": 1, + "label":
    + + overview.overall_code + +
    , + }, + ] + } + /> +
    +
    +
    + +
    +
    + +
    +
    +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    +
    + +`; + +exports[`should render correctly if the data is still loading 1`] = ` +
    +

    + overview.measures +

    +
    + +
    +
    +`; + +exports[`should render correctly if there is no coverage 1`] = ` +
    +

    + overview.measures +

    + + + overview.new_code + +
    , + }, + Object { + "key": 1, + "label":
    + + overview.overall_code + +
    , + }, + ] + } + /> +
    +
    +
    + +
    +
    + +
    +
    +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    +
    + +
    +
    +
    + +`; + +exports[`should render correctly if there is no new code measures 1`] = ` +
    +

    + overview.measures +

    + + + overview.new_code + +
    , + }, + Object { + "key": 1, + "label":
    + + overview.overall_code + +
    , + }, + ] + } + /> +
    +
    + +
    +

    + overview.measures.empty_explanation +

    +

    + + learn_more + , + } + } + /> +

    +
    +
    +
    + +`; diff --git a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/ProjectLeakPeriodInfo-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/ProjectLeakPeriodInfo-test.tsx.snap new file mode 100644 index 00000000000..dacc2a60a94 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/ProjectLeakPeriodInfo-test.tsx.snap @@ -0,0 +1,115 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render a more precise date 1`] = ` + +
    + overview.period.previous_version_only_date +
    + + + +
    +`; + +exports[`should render correctly for "manual_baseline" 1`] = ` + +
    + overview.period.manual_baseline.formattedTime.2019-04-23T02:12:32+0100 +
    + + + +
    +`; + +exports[`should render correctly for "manual_baseline" 2`] = ` + +
    + overview.period.manual_baseline.1.1.2 +
    + + + +
    +`; + +exports[`should render correctly for "previous_analysis" 1`] = ` + +
    + overview.period.previous_analysis. +
    + + + +
    +`; + +exports[`should render correctly for "previous_version" 1`] = ` + +
    + overview.period.previous_version_only_date +
    + + + +
    +`; + +exports[`should render correctly for 10 days 1`] = ` +
    + overview.period.days.10 + +
    +`; + +exports[`should render correctly for a specific date 1`] = ` + +
    + overview.period.date.formatted.2013-01-01 +
    + + + +
    +`; + +exports[`should render correctly for a specific version 1`] = ` + +
    + overview.period.version.0.1 +
    + + + +
    +`; diff --git a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/QualityGatePanel-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/QualityGatePanel-test.tsx.snap new file mode 100644 index 00000000000..3940bb8d720 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/QualityGatePanel-test.tsx.snap @@ -0,0 +1,625 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly for applications 1`] = ` +
    +

    + overview.quality_gate + + +

    +
    +
    +

    + metric.level.ERROR +

    + + overview.X_conditions_failed.3 + +
    +
    + + +
    +
    +
    +`; + +exports[`should render correctly for applications 2`] = ` +
    +

    + overview.quality_gate + + +

    +
    +
    +

    + metric.level.ERROR +

    + + overview.X_conditions_failed.1 + +
    +
    + + +
    +
    +
    +`; + +exports[`should render correctly for projects 1`] = ` +
    +

    + overview.quality_gate + + +

    +
    +
    +

    + metric.level.ERROR +

    + + overview.X_conditions_failed.1 + +
    +
    + +
    +
    +
    +`; + +exports[`should render correctly for projects 2`] = ` +
    +

    + overview.quality_gate + + +

    +
    +
    +

    + metric.level.OK +

    + + overview.quality_gate_all_conditions_passed + +
    +
    +
    +`; + +exports[`should render correctly for projects 3`] = ` +
    +

    + overview.quality_gate + + +

    + + + overview.quality_gate.ignored_conditions + + + +
    +
    +

    + metric.level.ERROR +

    + + overview.X_conditions_failed.1 + +
    +
    + +
    +
    +
    +`; diff --git a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/QualityGatePanelSection-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/QualityGatePanelSection-test.tsx.snap new file mode 100644 index 00000000000..23dfdaf13b7 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/QualityGatePanelSection-test.tsx.snap @@ -0,0 +1,436 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` +
    +

    + quality_gates.conditions.new_code +

    + +

    + quality_gates.conditions.overall_code +

    + +
    +`; + +exports[`should render correctly 2`] = ` +
    +

    + Foo +

    +

    + quality_gates.conditions.new_code +

    + +

    + quality_gates.conditions.overall_code +

    + +
    +`; 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 index 3a25fa68865..7790272f2e4 100644 --- a/server/sonar-web/src/main/js/apps/overview/components/App.tsx +++ b/server/sonar-web/src/main/js/apps/overview/components/App.tsx @@ -18,19 +18,15 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import { Helmet } from 'react-helmet-async'; import { lazyLoad } from 'sonar-ui-common/components/lazyLoad'; -import { getBaseUrl, getPathUrlAsString } from 'sonar-ui-common/helpers/urls'; import Suggestions from '../../../app/components/embed-docs-modal/Suggestions'; import { Router, withRouter } from '../../../components/hoc/withRouter'; import { isPullRequest } from '../../../helpers/branch-like'; -import { isSonarCloud } from '../../../helpers/system'; -import { getProjectUrl } from '../../../helpers/urls'; import { BranchLike } from '../../../types/branch-like'; -import OverviewApp from './OverviewApp'; +import BranchOverview from '../branches/BranchOverview'; const EmptyOverview = lazyLoad(() => import('./EmptyOverview')); -const ReviewApp = lazyLoad(() => import('../pullRequests/ReviewApp')); +const PullRequestOverview = lazyLoad(() => import('../pullRequests/PullRequestOverview')); interface Props { branchLike?: BranchLike; @@ -38,7 +34,6 @@ interface Props { component: T.Component; isInProgress?: boolean; isPending?: boolean; - onComponentChange: (changes: Partial) => void; router: Pick; } @@ -67,19 +62,10 @@ export class App extends React.PureComponent { return ( <> - {isSonarCloud() && ( - - - - )} - {isPullRequest(branchLike) ? ( <> - + ) : ( <> @@ -91,16 +77,9 @@ export class App extends React.PureComponent { branchLikes={branchLikes} component={component} hasAnalyses={this.props.isPending || this.props.isInProgress} - onComponentChange={this.props.onComponentChange} /> ) : ( - + )} )} diff --git a/server/sonar-web/src/main/js/apps/overview/components/EmptyOverview.tsx b/server/sonar-web/src/main/js/apps/overview/components/EmptyOverview.tsx index eb78fc506ca..970ee8560a6 100644 --- a/server/sonar-web/src/main/js/apps/overview/components/EmptyOverview.tsx +++ b/server/sonar-web/src/main/js/apps/overview/components/EmptyOverview.tsx @@ -22,109 +22,71 @@ import { FormattedMessage } from 'react-intl'; import { connect } from 'react-redux'; import { Alert } from 'sonar-ui-common/components/ui/Alert'; import { translate } from 'sonar-ui-common/helpers/l10n'; -import { isBranch, isMainBranch } from '../../../helpers/branch-like'; -import { isSonarCloud } from '../../../helpers/system'; +import { getBranchLikeDisplayName, isBranch, isMainBranch } from '../../../helpers/branch-like'; import { isLoggedIn } from '../../../helpers/users'; import { getCurrentUser, Store } from '../../../store/rootReducer'; import { BranchLike } from '../../../types/branch-like'; import AnalyzeTutorial from '../../tutorials/analyzeProject/AnalyzeTutorial'; -import AnalyzeTutorialSonarCloud from '../../tutorials/analyzeProject/AnalyzeTutorialSonarCloud'; -import MetaContainer from '../meta/MetaContainer'; -interface OwnProps { +interface Props { branchLike?: BranchLike; branchLikes: BranchLike[]; component: T.Component; + currentUser: T.CurrentUser; hasAnalyses?: boolean; - onComponentChange: (changes: {}) => void; } -interface StateProps { - currentUser: T.CurrentUser; -} +export function EmptyOverview(props: Props) { + const { branchLike, branchLikes, component, currentUser, hasAnalyses } = props; -type Props = OwnProps & StateProps; + if (!isBranch(branchLike)) { + return null; + } -export function EmptyOverview({ - branchLike, - branchLikes, - component, - currentUser, - hasAnalyses, - onComponentChange -}: Props) { const hasBranches = branchLikes.length > 1; const hasBadBranchConfig = branchLikes.length > 2 || - (branchLikes.length === 2 && - branchLikes.some(branch => isBranch(branch) && !isMainBranch(branchLike))); - return ( -
    -
    -
    - {isLoggedIn(currentUser) && isMainBranch(branchLike) ? ( - <> - {hasBranches && ( - - )} - {!hasBranches && - !hasAnalyses && - (isSonarCloud() ? ( - - ) : ( - - ))} - - ) : ( - - )} -
    + (branchLikes.length === 2 && branchLikes.some(branch => isBranch(branch))); - {!isSonarCloud() && ( -
    - -
    - )} -
    -
    - ); -} + const showWarning = isMainBranch(branchLike) && hasBranches; + const showTutorial = + isMainBranch(branchLike) && !hasBranches && !hasAnalyses && component.qualifier !== 'APP'; -export function WarningMessage({ - branchLike, - message -}: { - branchLike?: BranchLike; - message: string; -}) { - if (!isBranch(branchLike)) { - return null; - } - return ( - + let warning; + if (isLoggedIn(currentUser) && showWarning && hasBadBranchConfig) { + warning = ( - + ); + } else { + warning = ( + + ); + } + + return ( +
    + {isLoggedIn(currentUser) ? ( + <> + {showWarning && {warning}} + {showTutorial && } + + ) : ( + {warning} + )} +
    ); } diff --git a/server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx b/server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx deleted file mode 100644 index 4a9ae70d613..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx +++ /dev/null @@ -1,341 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 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 { uniq } from 'lodash'; -import * as React from 'react'; -import { connect } from 'react-redux'; -import { Alert } from 'sonar-ui-common/components/ui/Alert'; -import { parseDate } from 'sonar-ui-common/helpers/dates'; -import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; -import { isDiffMetric } from 'sonar-ui-common/helpers/measures'; -import { getMeasuresAndMeta } from '../../../api/measures'; -import { getAllTimeMachineData } from '../../../api/time-machine'; -import A11ySkipTarget from '../../../app/components/a11y/A11ySkipTarget'; -import { - getBranchLikeDisplayName, - getBranchLikeQuery, - isBranch, - isMainBranch, - isSameBranchLike -} from '../../../helpers/branch-like'; -import { enhanceMeasuresWithMetrics } from '../../../helpers/measures'; -import { getLeakPeriod } from '../../../helpers/periods'; -import { fetchMetrics } from '../../../store/rootActions'; -import { getMetrics, Store } from '../../../store/rootReducer'; -import { BranchLike } from '../../../types/branch-like'; -import { ComponentQualifier } from '../../../types/component'; -import { - DEFAULT_GRAPH, - getDisplayedHistoryMetrics, - getProjectActivityGraph -} from '../../projectActivity/utils'; -import Bugs from '../main/Bugs'; -import CodeSmells from '../main/CodeSmells'; -import Coverage from '../main/Coverage'; -import Duplications from '../main/Duplications'; -import VulnerabilitiesAndHotspots from '../main/VulnerabilitiesAndHotspots'; -import MetaContainer from '../meta/MetaContainer'; -import ApplicationQualityGate from '../qualityGate/ApplicationQualityGate'; -import QualityGate from '../qualityGate/QualityGate'; -import '../styles.css'; -import { HISTORY_METRICS_LIST, METRICS } from '../utils'; - -interface Props { - branchLike?: BranchLike; - component: T.Component; - isInProgress?: boolean; - isPending?: boolean; - fetchMetrics: () => void; - onComponentChange: (changes: {}) => void; - metrics: T.Dict; -} - -interface State { - history?: { - [metric: string]: Array<{ date: Date; value?: string }>; - }; - historyStartDate?: Date; - loading: boolean; - measures: T.MeasureEnhanced[]; - periods?: T.Period[]; -} - -export class OverviewApp extends React.PureComponent { - mounted = false; - state: State = { loading: true, measures: [] }; - - componentDidMount() { - this.mounted = true; - this.props.fetchMetrics(); - this.loadMeasures().then(this.loadHistory, () => {}); - } - - componentDidUpdate(prevProps: Props) { - if ( - this.props.component.key !== prevProps.component.key || - !isSameBranchLike(this.props.branchLike, prevProps.branchLike) - ) { - this.loadMeasures().then(this.loadHistory, () => {}); - } - } - - componentWillUnmount() { - this.mounted = false; - } - - getApplicationLeakPeriod = () => { - return this.state.measures.find(measure => measure.metric.key === 'new_bugs') - ? ({ index: 1 } as T.Period) - : undefined; - }; - - isEmpty = () => { - const { measures } = this.state; - return ( - measures === undefined || - measures.find(measure => ['lines', 'new_lines'].includes(measure.metric.key)) === undefined - ); - }; - - loadHistory = () => { - const { branchLike, component } = this.props; - - const { graph, customGraphs } = getProjectActivityGraph(component.key); - let graphMetrics = getDisplayedHistoryMetrics(graph, customGraphs); - if (!graphMetrics || graphMetrics.length <= 0) { - graphMetrics = getDisplayedHistoryMetrics(DEFAULT_GRAPH, []); - } - - const metrics = uniq(HISTORY_METRICS_LIST.concat(graphMetrics)); - return getAllTimeMachineData({ - ...getBranchLikeQuery(branchLike), - component: component.key, - metrics: metrics.join() - }).then(r => { - if (this.mounted) { - const history: T.Dict> = {}; - 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 }); - } - }); - }; - - loadMeasures = () => { - const { branchLike, component } = this.props; - this.setState({ loading: true }); - - return getMeasuresAndMeta(component.key, METRICS, { - additionalFields: 'metrics,periods', - ...getBranchLikeQuery(branchLike) - }).then( - ({ component, metrics, periods }) => { - if (this.mounted && metrics && component.measures) { - this.setState({ - loading: false, - measures: enhanceMeasuresWithMetrics(component.measures, metrics), - periods - }); - } - }, - () => { - if (this.mounted) { - this.setState({ loading: false }); - } - } - ); - }; - - renderEmpty = () => { - const { branchLike, component } = this.props; - const isApp = component.qualifier === ComponentQualifier.Application; - - /* eslint-disable no-lonely-if */ - // - Is App - // - No measures, OR measures, but no projects => empty - // - Else => no lines of code - // - Else - // - No measures => empty - // - Main branch? - // - LLB? - // - No branch info? - // - Measures, but no ncloc (checked in isEmpty()) => no lines of code - // - Main branch? - // - LLB? - // - No branch info? - let title; - if (isApp) { - if ( - this.state.measures === undefined || - this.state.measures.find(measure => measure.metric.key === 'projects') === undefined - ) { - title = translate('portfolio.app.empty'); - } else { - title = translate('portfolio.app.no_lines_of_code'); - } - } else { - if (this.state.measures === undefined || this.state.measures.length === 0) { - if (isMainBranch(branchLike)) { - title = translate('overview.project.main_branch_empty'); - } else if (branchLike !== undefined) { - title = translateWithParameters( - 'overview.project.branch_X_empty', - getBranchLikeDisplayName(branchLike) - ); - } else { - title = translate('overview.project.empty'); - } - } else { - if (isMainBranch(branchLike)) { - title = translate('overview.project.main_branch_no_lines_of_code'); - } else if (branchLike !== undefined) { - title = translateWithParameters( - 'overview.project.branch_X_no_lines_of_code', - getBranchLikeDisplayName(branchLike) - ); - } else { - title = translate('overview.project.no_lines_of_code'); - } - } - } - /* eslint-enable no-lonely-if */ - return ( -
    - {this.renderNewAnalysisRequired()} -

    {title}

    -
    - ); - }; - - renderNewAnalysisRequired = () => { - const { component, isInProgress, isPending } = this.props; - const { measures, periods } = this.state; - const leakPeriod = getLeakPeriod(periods); - - if ( - !isInProgress && - !isPending && - component.qualifier !== ComponentQualifier.Application && - measures.some(m => isDiffMetric(m.metric.key)) && - leakPeriod === undefined - ) { - return ( - - {translate('overview.project.branch_needs_new_analysis')} - - ); - } else { - return null; - } - }; - - renderLoading = () => { - return ( -
    - -
    - ); - }; - - renderMain = () => { - const { branchLike, component } = this.props; - const { periods, measures, history, historyStartDate } = this.state; - const leakPeriod = - component.qualifier === ComponentQualifier.Application - ? this.getApplicationLeakPeriod() - : getLeakPeriod(periods); - const domainProps = { - branchLike, - component, - measures, - leakPeriod, - history, - historyStartDate - }; - - if (this.isEmpty()) { - return this.renderEmpty(); - } - - return ( -
    - {this.renderNewAnalysisRequired()} - - {component.qualifier === ComponentQualifier.Application ? ( - - ) : ( - - )} - -
    - - - - - -
    -
    - ); - }; - - render() { - const { branchLike, component } = this.props; - const { loading, measures, history } = this.state; - - if (loading) { - return this.renderLoading(); - } - - return ( -
    -
    - - - {this.renderMain()} - -
    - -
    -
    -
    - ); - } -} - -const mapDispatchToProps = { fetchMetrics }; - -const mapStateToProps = (state: Store) => ({ metrics: getMetrics(state) }); - -export default connect(mapStateToProps, mapDispatchToProps)(OverviewApp); diff --git a/server/sonar-web/src/main/js/apps/overview/components/Timeline.tsx b/server/sonar-web/src/main/js/apps/overview/components/Timeline.tsx deleted file mode 100644 index e6cf42920f9..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/components/Timeline.tsx +++ /dev/null @@ -1,69 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 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 { max } from 'd3-array'; -import * as React from 'react'; -import LineChart from 'sonar-ui-common/components/charts/LineChart'; - -const HEIGHT = 80; - -interface Props { - history: Array<{ date: Date; value?: string }>; - before?: Date; - after?: Date; -} - -export default class Timeline extends React.PureComponent { - filterSnapshots() { - const { history, before, after } = this.props; - - return history.filter(s => { - const matchBefore = !before || s.date <= before; - const matchAfter = !after || s.date >= after; - return matchBefore && matchAfter; - }); - } - - render() { - const snapshots = this.filterSnapshots(); - - if (snapshots.length < 2) { - return null; - } - - const data = snapshots.map((snapshot, index) => { - return { x: index, y: snapshot.value !== undefined ? Number(snapshot.value) : undefined }; - }); - const domain = [ - 0, - max(this.props.history, d => (d.value !== undefined ? parseFloat(d.value) : 0)) - ] as [number, number]; - return ( - - ); - } -} 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 index 49f38f54c7a..3d2050e8bb8 100644 --- 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 @@ -20,6 +20,7 @@ import { shallow } from 'enzyme'; import * as React from 'react'; import { isSonarCloud } from '../../../../helpers/system'; +import BranchOverview from '../../branches/BranchOverview'; import { App } from '../App'; jest.mock('../../../../helpers/system', () => ({ isSonarCloud: jest.fn() })); @@ -39,22 +40,16 @@ beforeEach(() => { (isSonarCloud as jest.Mock).mockReturnValue(false); }); -it('should render OverviewApp', () => { +it('should render BranchOverview', () => { expect( getWrapper() - .find('Connect(OverviewApp)') + .find(BranchOverview) .exists() ).toBeTruthy(); }); function getWrapper(props = {}) { return shallow( - + ); } diff --git a/server/sonar-web/src/main/js/apps/overview/components/__tests__/EmptyOverview-test.tsx b/server/sonar-web/src/main/js/apps/overview/components/__tests__/EmptyOverview-test.tsx index 385df2e3933..655ff347aee 100644 --- a/server/sonar-web/src/main/js/apps/overview/components/__tests__/EmptyOverview-test.tsx +++ b/server/sonar-web/src/main/js/apps/overview/components/__tests__/EmptyOverview-test.tsx @@ -19,76 +19,41 @@ */ import { shallow } from 'enzyme'; import * as React from 'react'; -import { mockMainBranch, mockPullRequest } from '../../../../helpers/mocks/branch-like'; -import { mockComponent, mockLoggedInUser } from '../../../../helpers/testMocks'; -import { EmptyOverview, WarningMessage } from '../EmptyOverview'; - -const branch = mockMainBranch(); -const component = mockComponent({ version: '0.0.1' }); -const LoggedInUser = mockLoggedInUser(); +import { mockBranch, mockMainBranch, mockPullRequest } from '../../../../helpers/mocks/branch-like'; +import { mockComponent, mockCurrentUser, mockLoggedInUser } from '../../../../helpers/testMocks'; +import { EmptyOverview } from '../EmptyOverview'; it('renders correctly', () => { - expect( - shallow( - - ) - ).toMatchSnapshot(); + expect(shallowRender()).toMatchSnapshot(); + expect(shallowRender({ hasAnalyses: true })).toMatchSnapshot(); + expect(shallowRender({ currentUser: mockCurrentUser() })).toMatchSnapshot(); }); it('should render another message when there are branches', () => { + expect(shallowRender({ branchLikes: [mockMainBranch(), mockBranch()] })).toMatchSnapshot(); expect( - shallow( - - ) - ).toMatchSnapshot(); - expect( - shallow( - - ) + shallowRender({ + branchLikes: [mockMainBranch(), mockBranch(), mockBranch({ name: 'branch-7.8' })] + }) ).toMatchSnapshot(); }); -it('should not render the tutorial', () => { - expect( - shallow( - - ) - ).toMatchSnapshot(); +it('should not render warning message for pull requests', () => { + expect(shallowRender({ branchLike: mockPullRequest() }).type()).toBeNull(); }); -it('should render warning message', () => { - expect(shallow()).toMatchSnapshot(); +it('should not render the tutorial for applications', () => { + expect(shallowRender({ component: mockComponent({ qualifier: 'APP' }) })).toMatchSnapshot(); }); -it('should not render warning message', () => { - expect( - shallow() - .find('FormattedMessage') - .exists() - ).toBeFalsy(); -}); +function shallowRender(props = {}) { + return shallow( + + ); +} diff --git a/server/sonar-web/src/main/js/apps/overview/components/__tests__/OverviewApp-test.tsx b/server/sonar-web/src/main/js/apps/overview/components/__tests__/OverviewApp-test.tsx deleted file mode 100644 index e12b636ad18..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/components/__tests__/OverviewApp-test.tsx +++ /dev/null @@ -1,179 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 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 { shallow } from 'enzyme'; -import * as React from 'react'; -import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; -import { getMeasuresAndMeta } from '../../../../api/measures'; -import { getAllTimeMachineData } from '../../../../api/time-machine'; -import { mockBranch, mockMainBranch } from '../../../../helpers/mocks/branch-like'; -import { mockComponent, mockMeasure, mockMetric } from '../../../../helpers/testMocks'; -import { OverviewApp } from '../OverviewApp'; - -jest.mock('../../../../api/measures', () => { - const { mockMeasure, mockMetric } = getMockHelpers(); - return { - getMeasuresAndMeta: jest.fn().mockResolvedValue({ - component: { - measures: [mockMeasure({ metric: 'lines' }), mockMeasure({ metric: 'coverage' })], - name: 'foo' - }, - metrics: [mockMetric({ key: 'lines' }), mockMetric()] - }) - }; -}); - -jest.mock('sonar-ui-common/helpers/dates', () => ({ - parseDate: jest.fn(date => date) -})); - -jest.mock('../../../../api/time-machine', () => ({ - getAllTimeMachineData: jest.fn().mockResolvedValue({ - measures: [ - { metric: 'bugs', history: [{ date: '2019-01-05', value: '2.0' }] }, - { metric: 'vulnerabilities', history: [{ date: '2019-01-05', value: '0' }] }, - { metric: 'sqale_index', history: [{ date: '2019-01-01', value: '1.0' }] }, - { metric: 'duplicated_lines_density', history: [{ date: '2019-01-02', value: '1.0' }] }, - { metric: 'lines', history: [{ date: '2019-01-03', value: '10000' }] }, - { metric: 'coverage', history: [{ date: '2019-01-04', value: '95.5' }] } - ] - }) -})); - -it('should render correctly', async () => { - const wrapper = shallowRender(); - expect(wrapper).toMatchSnapshot(); - - await waitAndUpdate(wrapper); - expect(wrapper).toMatchSnapshot(); - expect(getMeasuresAndMeta).toBeCalled(); - expect(getAllTimeMachineData).toBeCalled(); -}); - -it('should show the correct message if the application is empty or has no lines of code', async () => { - (getMeasuresAndMeta as jest.Mock).mockResolvedValue({ - component: { - measures: [mockMeasure({ metric: 'projects' })], - name: 'foo' - }, - metrics: [mockMetric({ key: 'projects' })] - }); - - const wrapper = shallowRender({ - component: mockComponent({ key: 'foo', name: 'foo', qualifier: 'APP' }) - }); - await waitAndUpdate(wrapper); - expect(wrapper.find('h3').text()).toBe('portfolio.app.no_lines_of_code'); - - (getMeasuresAndMeta as jest.Mock).mockResolvedValue({ - component: { - measures: [], - name: 'bar' - }, - metrics: [] - }); - wrapper.setProps({ component: mockComponent({ key: 'bar', name: 'bar', qualifier: 'APP' }) }); - await waitAndUpdate(wrapper); - expect(wrapper.find('h3').text()).toBe('portfolio.app.empty'); -}); - -it('should show the correct message if the project is empty', async () => { - (getMeasuresAndMeta as jest.Mock).mockResolvedValue({ - component: { - measures: [], - name: 'foo' - }, - metrics: [] - }); - const wrapper = shallowRender({ branchLike: mockMainBranch() }); - - await waitAndUpdate(wrapper); - expect(wrapper.find('h3').text()).toBe('overview.project.main_branch_empty'); - - wrapper.setProps({ branchLike: mockBranch({ name: 'branch-foo' }) }); - await waitAndUpdate(wrapper); - expect(wrapper.find('h3').text()).toBe('overview.project.branch_X_empty.branch-foo'); - - wrapper.setProps({ branchLike: undefined }); - await waitAndUpdate(wrapper); - expect(wrapper.find('h3').text()).toBe('overview.project.empty'); -}); - -it('should show the correct message if the project has no lines of code', async () => { - (getMeasuresAndMeta as jest.Mock).mockResolvedValue({ - component: { - measures: [mockMeasure({ metric: 'bugs' })], - name: 'foo' - }, - metrics: [mockMetric({ key: 'bugs' })] - }); - const wrapper = shallowRender({ branchLike: mockMainBranch() }); - - await waitAndUpdate(wrapper); - expect(wrapper.find('h3').text()).toBe('overview.project.main_branch_no_lines_of_code'); - - wrapper.setProps({ branchLike: mockBranch({ name: 'branch-foo' }) }); - await waitAndUpdate(wrapper); - expect(wrapper.find('h3').text()).toBe('overview.project.branch_X_no_lines_of_code.branch-foo'); - - wrapper.setProps({ branchLike: undefined }); - await waitAndUpdate(wrapper); - expect(wrapper.find('h3').text()).toBe('overview.project.no_lines_of_code'); -}); - -it('should show a warning if the project has new measures, but no period info', async () => { - (getMeasuresAndMeta as jest.Mock).mockResolvedValue({ - component: { - measures: [mockMeasure({ metric: 'bugs' }), mockMeasure({ metric: 'new_bugs' })], - name: 'foo' - }, - metrics: [mockMetric({ key: 'bugs' }), mockMetric({ key: 'new_bugs' })] - }); - - const wrapper = shallowRender(); - await waitAndUpdate(wrapper); - expect( - wrapper - .find('Alert') - .dive() - .text() - ).toContain('overview.project.branch_needs_new_analysis'); -}); - -function getMockHelpers() { - // We use this little "force-requiring" instead of an import statement in - // order to prevent a hoisting race condition while mocking. If we want to use - // a mock helper in a Jest mock, we have to require it like this. Otherwise, - // we get errors like: - // ReferenceError: testMocks_1 is not defined - return require.requireActual('../../../../helpers/testMocks'); -} - -function shallowRender(props: Partial = {}) { - return shallow( - - ); -} diff --git a/server/sonar-web/src/main/js/apps/overview/components/__tests__/Timeline-test.tsx b/server/sonar-web/src/main/js/apps/overview/components/__tests__/Timeline-test.tsx deleted file mode 100644 index eadfbdc155d..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/components/__tests__/Timeline-test.tsx +++ /dev/null @@ -1,72 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 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 { shallow } from 'enzyme'; -import * as React from 'react'; -import { parseDate } from 'sonar-ui-common/helpers/dates'; -import Timeline from '../Timeline'; - -const range = parseDate('2017-05-01T00:00:00.000Z'); -const history = [ - { date: parseDate('2017-04-08T00:00:00.000Z'), value: '29.6' }, - { date: parseDate('2017-04-09T00:00:00.000Z'), value: '170.8' }, - { date: parseDate('2017-05-08T00:00:00.000Z'), value: '360' }, - { date: parseDate('2017-05-09T00:00:00.000Z'), value: '39' } -]; - -it('should render correctly with an "after" range', () => { - expect(shallow()).toMatchSnapshot(); -}); - -it('should render correctly with a "before" range', () => { - expect(shallow()).toMatchSnapshot(); -}); - -it('should have a correct domain with strings or numbers', () => { - const date = parseDate('2017-05-08T00:00:00.000Z'); - const wrapper = shallow(); - expect(wrapper.find('LineChart').prop('domain')).toEqual([0, 360]); - - wrapper.setProps({ - history: [ - { date, value: '360.33' }, - { date, value: '39.54' } - ] - }); - expect(wrapper.find('LineChart').prop('domain')).toEqual([0, 360.33]); - - wrapper.setProps({ - history: [ - { date, value: 360 }, - { date, value: 39 } - ] - }); - expect(wrapper.find('LineChart').prop('domain')).toEqual([0, 360]); -}); - -it('should not fail when a value is missing', () => { - expect( - shallow( - - ) - ).toMatchSnapshot(); -}); diff --git a/server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/EmptyOverview-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/EmptyOverview-test.tsx.snap index 99f1d1577a4..204c5941596 100644 --- a/server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/EmptyOverview-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/EmptyOverview-test.tsx.snap @@ -4,206 +4,94 @@ exports[`renders correctly 1`] = `
    -
    -
    - -
    -
    - -
    -
    + "deleted": false, + "key": "my-qp", + "language": "ts", + "name": "Sonar way", + }, + ], + "tags": Array [], + "version": "0.0.1", + } + } + currentUser={ + Object { + "groups": Array [], + "isLoggedIn": true, + "login": "luke", + "name": "Skywalker", + "scmAccounts": Array [], + } + } + />
    `; -exports[`should not render the tutorial 1`] = ` +exports[`renders correctly 2`] = ` +
    +`; + +exports[`renders correctly 3`] = `
    -
    -
    -
    - -
    -
    + } + /> +
    `; +exports[`should not render the tutorial for applications 1`] = ` +
    +`; + exports[`should render another message when there are branches 1`] = `
    -
    -
    - -
    -
    - -
    -
    + } + /> +
    `; @@ -211,80 +99,19 @@ exports[`should render another message when there are branches 2`] = `
    -
    -
    - -
    -
    - -
    -
    -
    -`; - -exports[`should render warning message 1`] = ` - - - + /> + +
    `; diff --git a/server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/OverviewApp-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/OverviewApp-test.tsx.snap deleted file mode 100644 index 17432f3a5e6..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/OverviewApp-test.tsx.snap +++ /dev/null @@ -1,790 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly 1`] = ` -
    - -
    -`; - -exports[`should render correctly 2`] = ` -
    -
    - -
    - -
    - - - - - -
    -
    -
    - -
    -
    -
    -`; diff --git a/server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/Timeline-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/Timeline-test.tsx.snap deleted file mode 100644 index 710cbbf695a..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/Timeline-test.tsx.snap +++ /dev/null @@ -1,110 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should not fail when a value is missing 1`] = ` - -`; - -exports[`should render correctly with a "before" range 1`] = ` - -`; - -exports[`should render correctly with an "after" range 1`] = ` - -`; diff --git a/server/sonar-web/src/main/js/apps/overview/events/AnalysesList.tsx b/server/sonar-web/src/main/js/apps/overview/events/AnalysesList.tsx deleted file mode 100644 index a66a705cda1..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/events/AnalysesList.tsx +++ /dev/null @@ -1,146 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 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 { Link } from 'react-router'; -import { translate } from 'sonar-ui-common/helpers/l10n'; -import { getProjectActivity } from '../../../api/projectActivity'; -import PreviewGraph from '../../../components/preview-graph/PreviewGraph'; -import { getBranchLikeQuery, isSameBranchLike } from '../../../helpers/branch-like'; -import { getActivityUrl } from '../../../helpers/urls'; -import { BranchLike } from '../../../types/branch-like'; -import Analysis from './Analysis'; - -interface Props { - branchLike?: BranchLike; - component: T.Component; - history?: { - [metric: string]: Array<{ date: Date; value?: string }>; - }; - metrics: T.Dict; - qualifier: string; -} - -interface State { - analyses: T.Analysis[]; - loading: boolean; -} - -const PAGE_SIZE = 3; - -export default class AnalysesList extends React.PureComponent { - mounted = false; - state: State = { analyses: [], loading: true }; - - componentDidMount() { - this.mounted = true; - this.fetchData(); - } - - componentDidUpdate(prevProps: Props) { - if ( - prevProps.component.key !== this.props.component.key || - !isSameBranchLike(prevProps.branchLike, this.props.branchLike) - ) { - this.fetchData(); - } - } - - componentWillUnmount() { - this.mounted = false; - } - - getTopLevelComponent = () => { - const { component } = this.props; - let current = component.breadcrumbs.length - 1; - while ( - current > 0 && - !['TRK', 'VW', 'APP'].includes(component.breadcrumbs[current].qualifier) - ) { - current--; - } - return component.breadcrumbs[current].key; - }; - - fetchData = () => { - this.setState({ loading: true }); - - getProjectActivity({ - ...getBranchLikeQuery(this.props.branchLike), - project: this.getTopLevelComponent(), - ps: PAGE_SIZE - }).then( - ({ analyses }) => { - if (this.mounted) { - this.setState({ analyses, loading: false }); - } - }, - () => { - if (this.mounted) { - this.setState({ loading: false }); - } - } - ); - }; - - renderList(analyses: T.Analysis[]) { - if (!analyses.length) { - return

    {translate('no_results')}

    ; - } - - return ( -
      - {analyses.map(analysis => ( - - ))} -
    - ); - } - - render() { - const { analyses, loading } = this.state; - - if (loading) { - return null; - } - - return ( -
    -

    - {translate('overview.project_activity', this.props.component.qualifier)} -

    - - - - {this.renderList(analyses)} - -
    - - {translate('show_more')} - -
    -
    - ); - } -} diff --git a/server/sonar-web/src/main/js/apps/overview/events/__tests__/__snapshots__/AnalysesList-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/events/__tests__/__snapshots__/AnalysesList-test.tsx.snap deleted file mode 100644 index 2320d0d0c4f..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/events/__tests__/__snapshots__/AnalysesList-test.tsx.snap +++ /dev/null @@ -1,18 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render show more link 1`] = ` - - show_more - -`; diff --git a/server/sonar-web/src/main/js/apps/overview/events/__tests__/__snapshots__/Analysis-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/events/__tests__/__snapshots__/Analysis-test.tsx.snap deleted file mode 100644 index f61e89ebd8a..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/events/__tests__/__snapshots__/Analysis-test.tsx.snap +++ /dev/null @@ -1,41 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should sort the events with version first 1`] = ` -
  • -
    - - - -
    -
    - - -
    -
  • -`; diff --git a/server/sonar-web/src/main/js/apps/overview/main/Bugs.tsx b/server/sonar-web/src/main/js/apps/overview/main/Bugs.tsx deleted file mode 100644 index 1ef59518983..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/main/Bugs.tsx +++ /dev/null @@ -1,135 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 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 BugIcon from 'sonar-ui-common/components/icons/BugIcon'; -import { translateWithParameters } from 'sonar-ui-common/helpers/l10n'; -import DocTooltip from '../../../components/docs/DocTooltip'; -import DateFromNow from '../../../components/intl/DateFromNow'; -import { isBranch, isMainBranch } from '../../../helpers/branch-like'; -import ApplicationLeakPeriodLegend from '../components/ApplicationLeakPeriodLegend'; -import LeakPeriodLegend from '../components/LeakPeriodLegend'; -import { getMetricName } from '../utils'; -import enhance, { ComposedProps } from './enhance'; - -export class Bugs extends React.PureComponent { - renderHeader() { - return this.props.renderHeader('Reliability'); - } - - renderTimelineStartDate(historyStartDate?: Date) { - if (!historyStartDate) { - return undefined; - } - return ( - - {fromNow => ( - - {translateWithParameters('overview.started_x', fromNow)} - - )} - - ); - } - - renderTimeline(range: string, historyStartDate?: Date) { - return this.props.renderTimeline('bugs', range, this.renderTimelineStartDate(historyStartDate)); - } - - renderLeak() { - const { branchLike, component, leakPeriod } = this.props; - if (!this.props.hasDiffMetrics()) { - return null; - } - - return ( -
    - {component.qualifier === 'APP' && ( - - )} - - {component.qualifier !== 'APP' && leakPeriod !== undefined && ( - - )} - -
    -
    -
    - {this.props.renderIssues('new_bugs', 'BUG')} - {this.props.renderRating('new_reliability_rating')} -
    -
    - - {getMetricName('new_bugs')} -
    -
    -
    - {this.renderTimeline('after')} -
    - ); - } - - renderNutshell() { - return ( -
    -
    -
    -
    - {this.props.renderIssues('bugs', 'BUG')} - {this.props.renderRating('reliability_rating')} -
    -
    - - {getMetricName('bugs')} - -
    - {this.props.renderHistoryLink('bugs')} -
    -
    - {this.renderTimeline('before', this.props.historyStartDate)} -
    - ); - } - - render() { - const { measures } = this.props; - const bugsMeasure = measures.find(measure => measure.metric.key === 'bugs'); - if (!bugsMeasure) { - return null; - } - return ( -
    - {this.renderHeader()} - -
    - {this.renderNutshell()} - {this.renderLeak()} -
    -
    - ); - } -} - -export default enhance(Bugs); diff --git a/server/sonar-web/src/main/js/apps/overview/main/CodeSmells.tsx b/server/sonar-web/src/main/js/apps/overview/main/CodeSmells.tsx deleted file mode 100644 index 28b8b868883..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/main/CodeSmells.tsx +++ /dev/null @@ -1,136 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 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 CodeSmellIcon from 'sonar-ui-common/components/icons/CodeSmellIcon'; -import { formatMeasure } from 'sonar-ui-common/helpers/measures'; -import DocTooltip from '../../../components/docs/DocTooltip'; -import DrilldownLink from '../../../components/shared/DrilldownLink'; -import { getMetricName } from '../utils'; -import enhance, { ComposedProps } from './enhance'; - -export class CodeSmells extends React.PureComponent { - renderHeader() { - return this.props.renderHeader('Maintainability'); - } - - renderDebt(metric: string) { - const { branchLike, measures, component } = this.props; - const measure = measures.find(measure => measure.metric.key === metric); - const value = measure ? this.props.getValue(measure) : undefined; - - return ( - - {formatMeasure(value, 'SHORT_WORK_DUR')} - - ); - } - - renderTimeline(range: string) { - return this.props.renderTimeline('sqale_index', range); - } - - renderLeak() { - if (!this.props.hasDiffMetrics()) { - return null; - } - - return ( -
    -
    -
    -
    - {this.renderDebt('new_technical_debt')} - {this.props.renderRating('new_maintainability_rating')} -
    -
    {getMetricName('new_effort')}
    -
    -
    -
    - {this.props.renderIssues('new_code_smells', 'CODE_SMELL')} -
    -
    - - {getMetricName('new_code_smells')} -
    -
    -
    - {this.renderTimeline('after')} -
    - ); - } - - renderNutshell() { - return ( -
    -
    -
    -
    - {this.renderDebt('sqale_index')} - {this.props.renderRating('sqale_rating')} -
    -
    - {getMetricName('effort')} - -
    - {this.props.renderHistoryLink('sqale_index')} -
    -
    -
    - {this.props.renderIssues('code_smells', 'CODE_SMELL')} -
    -
    - - {getMetricName('code_smells')} - -
    - {this.props.renderHistoryLink('code_smells')} -
    -
    - {this.renderTimeline('before')} -
    - ); - } - - render() { - const { measures } = this.props; - const codeSmellsMeasure = measures.find(measure => measure.metric.key === 'code_smells'); - if (!codeSmellsMeasure) { - return null; - } - return ( -
    - {this.renderHeader()} - -
    - {this.renderNutshell()} - {this.renderLeak()} -
    -
    - ); - } -} - -export default enhance(CodeSmells); diff --git a/server/sonar-web/src/main/js/apps/overview/main/Coverage.tsx b/server/sonar-web/src/main/js/apps/overview/main/Coverage.tsx deleted file mode 100644 index fe24f269adc..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/main/Coverage.tsx +++ /dev/null @@ -1,193 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 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 { translate } from 'sonar-ui-common/helpers/l10n'; -import { - formatMeasure, - getMinDecimalsCountToBeDistinctFromThreshold -} from 'sonar-ui-common/helpers/measures'; -import DocTooltip from '../../../components/docs/DocTooltip'; -import DrilldownLink from '../../../components/shared/DrilldownLink'; -import CoverageRating from '../../../components/ui/CoverageRating'; -import { getMetricName, getThreshold } from '../utils'; -import enhance, { ComposedProps } from './enhance'; - -export class Coverage extends React.PureComponent { - getCoverage() { - const measure = this.props.measures.find(measure => measure.metric.key === 'coverage'); - return Number(measure ? measure.value : undefined); - } - - renderHeader() { - return this.props.renderHeader('Coverage', translate('metric.coverage.name')); - } - - renderTimeline(range: string) { - return this.props.renderTimeline('coverage', range); - } - - renderTests() { - return this.props.renderMeasure( - 'tests', - - ); - } - - renderCoverage() { - const { branchLike, component } = this.props; - const metric = 'coverage'; - const coverage = this.getCoverage(); - - return ( -
    -
    - -
    - -
    -
    - - - {formatMeasure(coverage, 'PERCENT')} - - -
    - -
    - {getMetricName('coverage')} - -
    - {this.props.renderHistoryLink('coverage')} -
    - {this.props.renderHistoryLink('coverage')} -
    - ); - } - - renderNewCoverage() { - const { branchLike, component, measures } = this.props; - - const newCoverageMeasure = measures.find(measure => measure.metric.key === 'new_coverage'); - const newCoverageValue = newCoverageMeasure && this.props.getValue(newCoverageMeasure); - const formattedValue = - newCoverageMeasure && newCoverageValue !== undefined ? ( -
    - - - {formatMeasure(newCoverageValue, 'PERCENT', { - decimals: getMinDecimalsCountToBeDistinctFromThreshold( - parseFloat(newCoverageValue), - getThreshold(measures, 'new_coverage') - ) - })} - - -
    - ) : ( - — - ); - - const newLinesToCover = measures.find(measure => measure.metric.key === 'new_lines_to_cover'); - const newLinesToCoverValue = newLinesToCover && this.props.getValue(newLinesToCover); - const label = - newLinesToCover && newLinesToCoverValue !== undefined && Number(newLinesToCoverValue) > 0 ? ( -
    - {translate('overview.coverage_on')} -
    - - - {formatMeasure(newLinesToCoverValue, 'SHORT_INT')} - - - {getMetricName('new_lines_to_cover')} -
    - ) : ( -
    - {getMetricName('new_coverage')} -
    - ); - - return ( -
    -
    {formattedValue}
    - {label} -
    - ); - } - - renderNutshell() { - return ( -
    -
    - {this.renderCoverage()} - {this.renderTests()} -
    - - {this.renderTimeline('before')} -
    - ); - } - - renderLeak() { - if (!this.props.hasDiffMetrics()) { - return null; - } - return ( -
    -
    {this.renderNewCoverage()}
    - - {this.renderTimeline('after')} -
    - ); - } - - render() { - const { measures } = this.props; - const coverageMeasure = measures.find(measure => measure.metric.key === 'coverage'); - if (!coverageMeasure) { - return null; - } - return ( -
    - {this.renderHeader()} - -
    - {this.renderNutshell()} - {this.renderLeak()} -
    -
    - ); - } -} - -export default enhance(Coverage); diff --git a/server/sonar-web/src/main/js/apps/overview/main/Duplications.tsx b/server/sonar-web/src/main/js/apps/overview/main/Duplications.tsx deleted file mode 100644 index a22c2f09cbd..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/main/Duplications.tsx +++ /dev/null @@ -1,194 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 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 DuplicationsRating from 'sonar-ui-common/components/ui/DuplicationsRating'; -import { translate } from 'sonar-ui-common/helpers/l10n'; -import { - formatMeasure, - getMinDecimalsCountToBeDistinctFromThreshold -} from 'sonar-ui-common/helpers/measures'; -import DocTooltip from '../../../components/docs/DocTooltip'; -import DrilldownLink from '../../../components/shared/DrilldownLink'; -import { getMetricName, getThreshold } from '../utils'; -import enhance, { ComposedProps } from './enhance'; - -export class Duplications extends React.PureComponent { - renderHeader() { - return this.props.renderHeader('Duplications', translate('overview.domain.duplications')); - } - - renderTimeline(range: string) { - return this.props.renderTimeline('duplicated_lines_density', range); - } - - renderDuplicatedBlocks() { - return this.props.renderMeasure( - 'duplicated_blocks', - - ); - } - - renderDuplications() { - const { branchLike, component, measures } = this.props; - const measure = measures.find(measure => measure.metric.key === 'duplicated_lines_density'); - if (!measure) { - return null; - } - - const duplications = Number(measure.value); - - return ( -
    -
    - -
    - -
    -
    - - {formatMeasure(duplications, 'PERCENT')} - -
    - -
    - {getMetricName('duplications')} - -
    - {this.props.renderHistoryLink('duplicated_lines_density')} -
    -
    - ); - } - - renderNewDuplications() { - const { branchLike, component, measures } = this.props; - const newDuplicationsMeasure = measures.find( - measure => measure.metric.key === 'new_duplicated_lines_density' - ); - const newDuplicationsValue = - newDuplicationsMeasure && this.props.getValue(newDuplicationsMeasure); - const formattedValue = - newDuplicationsMeasure && newDuplicationsValue ? ( -
    - - - {formatMeasure(newDuplicationsValue, 'PERCENT', { - decimals: getMinDecimalsCountToBeDistinctFromThreshold( - parseFloat(newDuplicationsValue), - getThreshold(measures, 'new_duplicated_lines_density') - ) - })} - - -
    - ) : ( - — - ); - - const newLinesMeasure = measures.find(measure => measure.metric.key === 'new_lines'); - const newLinesValue = newLinesMeasure && this.props.getValue(newLinesMeasure); - const label = - newLinesMeasure && newLinesValue !== undefined && Number(newLinesValue) > 0 ? ( -
    - {translate('overview.duplications_on')} -
    - - - {formatMeasure(newLinesValue, 'SHORT_INT')} - - - {getMetricName('new_lines')} -
    - ) : ( -
    {getMetricName('new_duplications')}
    - ); - - return ( -
    -
    {formattedValue}
    - {label} -
    - ); - } - - renderNutshell() { - return ( -
    -
    - {this.renderDuplications()} - {this.renderDuplicatedBlocks()} -
    - - {this.renderTimeline('before')} -
    - ); - } - - renderLeak() { - if (!this.props.hasDiffMetrics()) { - return null; - } - return ( -
    -
    {this.renderNewDuplications()}
    - - {this.renderTimeline('after')} -
    - ); - } - - render() { - const { measures } = this.props; - const duplications = measures.find( - measure => measure.metric.key === 'duplicated_lines_density' - ); - if (duplications == null) { - return null; - } - return ( -
    - {this.renderHeader()} - -
    - {this.renderNutshell()} - {this.renderLeak()} -
    -
    - ); - } -} - -export default enhance(Duplications); diff --git a/server/sonar-web/src/main/js/apps/overview/main/VulnerabilitiesAndHotspots.tsx b/server/sonar-web/src/main/js/apps/overview/main/VulnerabilitiesAndHotspots.tsx deleted file mode 100644 index 513879fed34..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/main/VulnerabilitiesAndHotspots.tsx +++ /dev/null @@ -1,128 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 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 SecurityHotspotIcon from 'sonar-ui-common/components/icons/SecurityHotspotIcon'; -import VulnerabilityIcon from 'sonar-ui-common/components/icons/VulnerabilityIcon'; -import DocTooltip from '../../../components/docs/DocTooltip'; -import { getMetricName } from '../utils'; -import enhance, { ComposedProps } from './enhance'; - -export class VulnerabiltiesAndHotspots extends React.PureComponent { - renderHeader() { - return this.props.renderHeader('Security'); - } - - renderTimeline(range: string) { - return this.props.renderTimeline('vulnerabilities', range); - } - - renderLeak() { - if (!this.props.hasDiffMetrics()) { - return null; - } - - return ( -
    -
    -
    -
    - - {this.props.renderIssues('new_vulnerabilities', 'VULNERABILITY')} - - {this.props.renderRating('new_security_rating')} -
    -
    - - {getMetricName('new_vulnerabilities')} -
    -
    -
    -
    - {this.props.renderIssues('new_security_hotspots', 'SECURITY_HOTSPOT')} -
    -
    - - {getMetricName('new_security_hotspots')} -
    -
    -
    - {this.renderTimeline('after')} -
    - ); - } - - renderNutshell() { - return ( -
    -
    -
    -
    - - {this.props.renderIssues('vulnerabilities', 'VULNERABILITY')} - - {this.props.renderRating('security_rating')} -
    -
    - - {getMetricName('vulnerabilities')} - -
    - {this.props.renderHistoryLink('vulnerabilities')} -
    -
    -
    - {this.props.renderIssues('security_hotspots', 'SECURITY_HOTSPOT')} -
    -
    - - {getMetricName('security_hotspots')} - -
    - {this.props.renderHistoryLink('security_hotspots')} -
    -
    - {this.renderTimeline('before')} -
    - ); - } - - render() { - return ( -
    - {this.renderHeader()} - -
    - {this.renderNutshell()} - {this.renderLeak()} -
    -
    - ); - } -} - -export default enhance(VulnerabiltiesAndHotspots); diff --git a/server/sonar-web/src/main/js/apps/overview/main/__tests__/Bugs-test.tsx b/server/sonar-web/src/main/js/apps/overview/main/__tests__/Bugs-test.tsx deleted file mode 100644 index ae0fdfcc297..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/main/__tests__/Bugs-test.tsx +++ /dev/null @@ -1,80 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 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 { shallow } from 'enzyme'; -import * as React from 'react'; -import { mockMainBranch } from '../../../../helpers/mocks/branch-like'; -import { mockComponent, mockMeasureEnhanced, mockMetric } from '../../../../helpers/testMocks'; -import Bugs from '../Bugs'; -import { ComposedProps } from '../enhance'; - -it('should render correctly', () => { - expect(shallowRender()).toMatchSnapshot(); -}); - -function shallowRender(props: Partial = {}) { - return shallow( - - ).dive(); -} diff --git a/server/sonar-web/src/main/js/apps/overview/main/__tests__/CodeSmells-test.tsx b/server/sonar-web/src/main/js/apps/overview/main/__tests__/CodeSmells-test.tsx deleted file mode 100644 index d87e979a344..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/main/__tests__/CodeSmells-test.tsx +++ /dev/null @@ -1,109 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 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 { shallow } from 'enzyme'; -import * as React from 'react'; -import { mockMainBranch } from '../../../../helpers/mocks/branch-like'; -import { mockComponent, mockMeasureEnhanced, mockMetric } from '../../../../helpers/testMocks'; -import CodeSmells from '../CodeSmells'; -import { ComposedProps } from '../enhance'; - -it('should render correctly', () => { - expect(shallowRender()).toMatchSnapshot(); -}); - -function shallowRender(props: Partial = {}) { - return shallow( - - ).dive(); -} diff --git a/server/sonar-web/src/main/js/apps/overview/main/__tests__/Duplications-test.tsx b/server/sonar-web/src/main/js/apps/overview/main/__tests__/Duplications-test.tsx deleted file mode 100644 index 63d915c677d..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/main/__tests__/Duplications-test.tsx +++ /dev/null @@ -1,88 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 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 { shallow } from 'enzyme'; -import * as React from 'react'; -import { mockMainBranch } from '../../../../helpers/mocks/branch-like'; -import { mockComponent, mockMeasureEnhanced, mockMetric } from '../../../../helpers/testMocks'; -import Duplications from '../Duplications'; -import { ComposedProps } from '../enhance'; - -it('should render correctly', () => { - expect(shallowRender()).toMatchSnapshot(); -}); - -function shallowRender(props: Partial = {}) { - return shallow( - - ).dive(); -} diff --git a/server/sonar-web/src/main/js/apps/overview/main/__tests__/VulnerabilitiesAndHotspots-test.tsx b/server/sonar-web/src/main/js/apps/overview/main/__tests__/VulnerabilitiesAndHotspots-test.tsx deleted file mode 100644 index d25f902c2cf..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/main/__tests__/VulnerabilitiesAndHotspots-test.tsx +++ /dev/null @@ -1,115 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 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 { shallow } from 'enzyme'; -import * as React from 'react'; -import { mockMainBranch } from '../../../../helpers/mocks/branch-like'; -import { mockComponent, mockMeasureEnhanced, mockMetric } from '../../../../helpers/testMocks'; -import { ComposedProps } from '../enhance'; -import VulnerabilitiesAndHotspots from '../VulnerabilitiesAndHotspots'; - -it('should render correctly', () => { - expect(shallowRender()).toMatchSnapshot(); -}); - -function shallowRender(props: Partial = {}) { - return shallow( - - ).dive(); -} diff --git a/server/sonar-web/src/main/js/apps/overview/main/__tests__/__snapshots__/Bugs-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/main/__tests__/__snapshots__/Bugs-test.tsx.snap deleted file mode 100644 index 83f8ef8442a..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/main/__tests__/__snapshots__/Bugs-test.tsx.snap +++ /dev/null @@ -1,229 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly 1`] = ` -
    -
    -
    - - metric_domain.Reliability - - - layout.measures - -
    -
    -
    -
    -
    -
    -
    - - - 5 - - - -
    - - - -
    -
    -
    -
    - - overview.metric.bugs - -
    - - - - project_activity.page - - -
    -
    -
    - - - - -
    -
    -
    - -
    -
    -
    - - - 1 - - - -
    - - - -
    -
    -
    -
    - - overview.metric.new_bugs -
    -
    -
    -
    - -
    -
    -
    -
    -`; diff --git a/server/sonar-web/src/main/js/apps/overview/main/__tests__/__snapshots__/CodeSmells-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/main/__tests__/__snapshots__/CodeSmells-test.tsx.snap deleted file mode 100644 index 88d0a6bac8b..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/main/__tests__/__snapshots__/CodeSmells-test.tsx.snap +++ /dev/null @@ -1,295 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly 1`] = ` -
    -
    -
    - - metric_domain.Maintainability - - - layout.measures - -
    -
    -
    -
    -
    -
    -
    - - - work_duration.x_days.2 - - - -
    - - - -
    -
    -
    -
    - overview.metric.effort - -
    - - - - project_activity.page - - -
    -
    -
    - - 15 - -
    -
    - - overview.metric.code_smells - -
    - - - - project_activity.page - - -
    -
    -
    - -
    -
    -
    -
    -
    -
    - - - work_duration.x_hours.1 - - - -
    - - - -
    -
    -
    -
    - overview.metric.new_effort -
    -
    -
    -
    - - 52 - -
    -
    - - overview.metric.new_code_smells -
    -
    -
    -
    - -
    -
    -
    -
    -`; diff --git a/server/sonar-web/src/main/js/apps/overview/main/__tests__/__snapshots__/Coverage-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/main/__tests__/__snapshots__/Coverage-test.tsx.snap deleted file mode 100644 index 53140c34d90..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/main/__tests__/__snapshots__/Coverage-test.tsx.snap +++ /dev/null @@ -1,251 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly 1`] = ` -
    -
    -
    - - metric.coverage.name - - - layout.measures - -
    -
    -
    -
    -
    -
    -
    - -
    -
    -
    - - - 1.0% - - -
    -
    - overview.metric.coverage - -
    - - - - project_activity.page - - -
    - - - - project_activity.page - - -
    -
    -
    - - - 15 - - -
    -
    - Unit Tests - - - - - project_activity.page - - -
    -
    -
    -
    -
    -
    -
    -
    -
    - - - 1.0% - - -
    -
    -
    - overview.coverage_on -
    - - - 1 - - - overview.metric.new_lines_to_cover -
    -
    -
    -
    -
    -
    -`; diff --git a/server/sonar-web/src/main/js/apps/overview/main/__tests__/__snapshots__/Duplications-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/main/__tests__/__snapshots__/Duplications-test.tsx.snap deleted file mode 100644 index af8b3b669d3..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/main/__tests__/__snapshots__/Duplications-test.tsx.snap +++ /dev/null @@ -1,227 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly 1`] = ` -
    -
    -
    - - overview.domain.duplications - - - layout.measures - -
    -
    -
    -
    -
    -
    -
    - -
    -
    -
    - - 0.5% - -
    -
    - overview.metric.duplications - -
    - - - - project_activity.page - - -
    -
    -
    -
    - - - 1 - - -
    -
    - Duplicated Blocks - - - - - project_activity.page - - -
    -
    -
    -
    -
    -
    -
    -
    -
    - - - 1.5% - - -
    -
    -
    - overview.duplications_on -
    - - - 1 - - - overview.metric.new_lines -
    -
    -
    -
    -
    -
    -`; diff --git a/server/sonar-web/src/main/js/apps/overview/main/__tests__/__snapshots__/VulnerabilitiesAndHotspots-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/main/__tests__/__snapshots__/VulnerabilitiesAndHotspots-test.tsx.snap deleted file mode 100644 index 7aed1c83259..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/main/__tests__/__snapshots__/VulnerabilitiesAndHotspots-test.tsx.snap +++ /dev/null @@ -1,307 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly 1`] = ` -
    -
    -
    - - metric_domain.Security - - - layout.measures - -
    -
    -
    -
    -
    -
    -
    - - - 0 - - - -
    - - - -
    -
    -
    -
    - - overview.metric.vulnerabilities - -
    - - - - project_activity.page - - -
    -
    -
    - - 0 - -
    -
    - - overview.metric.security_hotspots - -
    - - - - project_activity.page - - -
    -
    -
    - -
    -
    -
    -
    -
    -
    - - - 1 - - - -
    - - - -
    -
    -
    -
    - - overview.metric.new_vulnerabilities -
    -
    -
    -
    - - 10 - -
    -
    - - overview.metric.new_security_hotspots -
    -
    -
    -
    - -
    -
    -
    -
    -`; diff --git a/server/sonar-web/src/main/js/apps/overview/main/enhance.tsx b/server/sonar-web/src/main/js/apps/overview/main/enhance.tsx deleted file mode 100644 index 4caf589f566..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/main/enhance.tsx +++ /dev/null @@ -1,227 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 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 { Link } from 'react-router'; -import Tooltip from 'sonar-ui-common/components/controls/Tooltip'; -import HistoryIcon from 'sonar-ui-common/components/icons/HistoryIcon'; -import Rating from 'sonar-ui-common/components/ui/Rating'; -import { getLocalizedMetricName, translate } from 'sonar-ui-common/helpers/l10n'; -import { formatMeasure } from 'sonar-ui-common/helpers/measures'; -import { getWrappedDisplayName } from '../../../components/hoc/utils'; -import { getLeakValue } from '../../../components/measure/utils'; -import DrilldownLink from '../../../components/shared/DrilldownLink'; -import { getBranchLikeQuery } from '../../../helpers/branch-like'; -import { getRatingTooltip, getShortType, isDiffMetric } from '../../../helpers/measures'; -import { getPeriodDate } from '../../../helpers/periods'; -import { - getComponentDrilldownUrl, - getComponentIssuesUrl, - getMeasureHistoryUrl -} from '../../../helpers/urls'; -import { BranchLike } from '../../../types/branch-like'; -import Timeline from '../components/Timeline'; - -export interface EnhanceProps { - branchLike?: BranchLike; - component: T.Component; - measures: T.MeasureEnhanced[]; - leakPeriod?: T.Period; - history?: { - [metric: string]: Array<{ date: Date; value?: string }>; - }; - historyStartDate?: Date; -} - -export interface ComposedProps extends EnhanceProps { - getValue: (measure: T.MeasureEnhanced) => string | undefined; - hasDiffMetrics: () => boolean; - renderHeader: (domain: string, label?: string) => React.ReactNode; - renderMeasure: (metricKey: string, tooltip?: React.ReactNode) => React.ReactNode; - renderRating: (metricKey: string) => React.ReactNode; - renderIssues: (metric: string, type: T.IssueType) => React.ReactNode; - renderHistoryLink: (metricKey: string) => React.ReactNode; - renderTimeline: (metricKey: string, range: string, children?: React.ReactNode) => React.ReactNode; -} - -export default function enhance(ComposedComponent: React.ComponentType) { - return class extends React.PureComponent { - static displayName = getWrappedDisplayName(ComposedComponent, 'enhance'); - - getValue = (measure: T.MeasureEnhanced) => { - if (!measure) { - return '0'; - } - return isDiffMetric(measure.metric.key) ? getLeakValue(measure) : measure.value; - }; - - hasDiffMetrics = () => { - const { measures } = this.props; - return measures.some(m => isDiffMetric(m.metric.key)); - }; - - renderHeader = (domain: string, label?: string) => { - const { branchLike, component } = this.props; - label = label !== undefined ? label : translate('metric_domain', domain); - return ( -
    -
    - {label} - - {translate('layout.measures')} - -
    -
    - ); - }; - - renderMeasure = (metricKey: string, tooltip?: React.ReactNode) => { - const { branchLike, measures, component } = this.props; - const measure = measures.find(measure => measure.metric.key === metricKey); - if (!measure) { - return null; - } - - return ( -
    -
    - - - {formatMeasure(measure.value, getShortType(measure.metric.type))} - - -
    - -
    - {getLocalizedMetricName(measure.metric)} - {tooltip} - {this.renderHistoryLink(measure.metric.key)} -
    -
    - ); - }; - - renderRating = (metricKey: string) => { - const { branchLike, component, measures } = this.props; - const measure = measures.find(measure => measure.metric.key === metricKey); - if (!measure) { - return null; - } - - const value = this.getValue(measure); - const title = value && getRatingTooltip(metricKey, value); - return ( - -
    - - - -
    -
    - ); - }; - - renderIssues = (metric: string, type: string) => { - const { branchLike, measures, component } = this.props; - const measure = measures.find(measure => measure.metric.key === metric); - if (!measure) { - return —; - } - - const value = this.getValue(measure); - const params = { ...getBranchLikeQuery(branchLike), resolved: 'false', types: type }; - if (isDiffMetric(metric)) { - Object.assign(params, { sinceLeakPeriod: 'true' }); - } - - if (metric.endsWith('security_hotspots')) { - return ( - - {formatMeasure(value, 'SHORT_INT')} - - ); - } - - return ( - - {formatMeasure(value, 'SHORT_INT')} - - ); - }; - - renderHistoryLink = (metricKey: string) => { - const linkClass = 'overview-domain-measure-history-link'; - return ( - - - {translate('project_activity.page')} - - ); - }; - - renderTimeline = (metricKey: string, range: 'before' | 'after', children?: React.ReactNode) => { - if (!this.props.history) { - return null; - } - const history = this.props.history[metricKey]; - if (!history) { - return null; - } - const props = { history, [range]: getPeriodDate(this.props.leakPeriod) }; - return ( -
    - - {children} -
    - ); - }; - - render() { - return ( - - ); - } - }; -} diff --git a/server/sonar-web/src/main/js/apps/overview/meta/MetaContainer.tsx b/server/sonar-web/src/main/js/apps/overview/meta/MetaContainer.tsx index 2161ceba139..bd9fbfaca1a 100644 --- a/server/sonar-web/src/main/js/apps/overview/meta/MetaContainer.tsx +++ b/server/sonar-web/src/main/js/apps/overview/meta/MetaContainer.tsx @@ -32,13 +32,11 @@ import { Store } from '../../../store/rootReducer'; import { BranchLike } from '../../../types/branch-like'; -import AnalysesList from '../events/AnalysesList'; import MetaKey from './MetaKey'; import MetaLinks from './MetaLinks'; import MetaOrganizationKey from './MetaOrganizationKey'; import MetaQualityGate from './MetaQualityGate'; import MetaQualityProfiles from './MetaQualityProfiles'; -import MetaSize from './MetaSize'; import MetaTags from './MetaTags'; const ProjectBadges = lazyLoadComponent(() => import('../badges/ProjectBadges'), 'ProjectBadges'); @@ -103,7 +101,7 @@ export class Meta extends React.PureComponent { render() { const { organizationsEnabled } = this.props.appState; - const { branchLike, component, currentUser, measures, metrics, organization } = this.props; + const { branchLike, component, currentUser, metrics, organization } = this.props; const { qualifier, description, visibility } = component; const isProject = qualifier === 'TRK'; @@ -131,21 +129,8 @@ export class Meta extends React.PureComponent { {isProject && ( )} - {measures && ( - - )}
    - {metrics && ( - - )} - {this.renderQualityInfos()} {isProject && } diff --git a/server/sonar-web/src/main/js/apps/overview/meta/MetaSize.tsx b/server/sonar-web/src/main/js/apps/overview/meta/MetaSize.tsx index 3a8b2281593..353ccfed13b 100644 --- a/server/sonar-web/src/main/js/apps/overview/meta/MetaSize.tsx +++ b/server/sonar-web/src/main/js/apps/overview/meta/MetaSize.tsx @@ -25,7 +25,6 @@ import { formatMeasure } from 'sonar-ui-common/helpers/measures'; import LanguageDistributionContainer from '../../../components/charts/LanguageDistributionContainer'; import DrilldownLink from '../../../components/shared/DrilldownLink'; import { BranchLike } from '../../../types/branch-like'; -import { getMetricName } from '../utils'; interface Props { branchLike?: BranchLike; @@ -55,7 +54,6 @@ export default class MetaSize extends React.PureComponent { ) : ( 0 )} -
    {getMetricName('ncloc')}
    ); diff --git a/server/sonar-web/src/main/js/apps/overview/meta/__tests__/__snapshots__/MetaContainer-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/meta/__tests__/__snapshots__/MetaContainer-test.tsx.snap index e7b7c3c489a..0ca8d471138 100644 --- a/server/sonar-web/src/main/js/apps/overview/meta/__tests__/__snapshots__/MetaContainer-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/overview/meta/__tests__/__snapshots__/MetaContainer-test.tsx.snap @@ -39,33 +39,6 @@ exports[`should hide QG and QP links if the organization has a paid plan, and th onComponentChange={[MockFunction]} /> - -
    -
    React.ReactNode; + date: DateSource; +} -it('should sort the events with version first', () => { - expect(shallow()).toMatchSnapshot(); -}); +export default function DateFromNow({ children, date }: Props) { + return children && children(date.toString()); +} diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index 534c4320f2d..67bb752c54f 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -2643,17 +2643,22 @@ system.version_is_availble={version} is available #------------------------------------------------------------------------------ overview.failed_conditions=Failed conditions overview.X_more_failed_conditions={0} more failed conditions -overview.metrics=Metrics -overview.quality_gate=Quality Gate +overview.X_conditions_failed={0} conditions failed +overview.quality_gate=Quality Gate Status overview.quality_gate_x=Quality Gate: {0} overview.quality_gate_failed_with_x=with {0} errors +overview.quality_gate_code_clean=Your code is clean! +overview.quality_gate_all_conditions_passed=All conditions passed. overview.you_should_define_quality_gate=You should define a quality gate on this project. overview.quality_gate.ignored_conditions=Some Quality Gate conditions on New Code were ignored because of the small number of New Lines overview.quality_gate.ignored_conditions.tooltip=At the start of a new code period, if very few lines have been added or modified, it might be difficult to reach the desired level of code coverage or duplications. To prevent Quality Gate failure when there's little that can be done about it, Quality Gate conditions about duplications in new code and coverage on new code are ignored until the number of new lines is at least 20. overview.quality_gate.conditions_on_new_code=Only conditions on new code that are defined in the Quality Gate are checked. See the {link} associated to the project for details. overview.quality_profiles=Quality Profiles -overview.new_code_period_x=New code: {0} -overview.started_x=started {0} +overview.new_code_period_x=New Code: {0} +overview.max_new_code_period_from_x=Max New Code from: {0} +overview.started_x=Started {0} +overview.new_code=New Code +overview.overall_code=Overall Code overview.previous_analysis_x=Previous analysis was {0} overview.started_on_x=Started on {0} overview.previous_analysis_on_x=Previous analysis on {0} @@ -2668,6 +2673,11 @@ overview.project_activity.click_to_see=Click to see project activity overview.external_links=External Links overview.project_key.APP=Application Key overview.project_key.TRK=Project Key +overview.activity=Activity +overview.recent_activity=Recent Activity +overview.measures=Measures +overview.measures.empty_explanation=Measures on New Code will appear after the second analysis of this branch. +overview.measures.empty_link={learn_more_link} about the Clean as You Code approach. overview.project.no_lines_of_code=This project has no lines of code. overview.project.empty=This project is empty. @@ -2677,23 +2687,25 @@ overview.project.main_branch_no_lines_of_code=The main branch has no lines of co overview.project.main_branch_empty=The main branch of this project is empty. overview.project.branch_needs_new_analysis=The branch data is incomplete. Run a new analysis to update it. overview.coverage_on=Coverage on -overview.coverage_on_X_lines=Coverage on {count} New Lines to cover +overview.coverage_on_X_lines=Coverage on {count} Lines to cover +overview.coverage_on_X_new_lines=Coverage on {count} New Lines to cover overview.duplications_on=Duplications on -overview.duplications_on_X=Duplications on {count} New Lines +overview.duplications_on_X_lines=Duplications on {count} Lines +overview.duplications_on_X_new_lines=Duplications on {count} New Lines -overview.period.previous_version=since {0} +overview.period.previous_version=Since {0} # Old periods, necessary to display project that haven't been analyzed with new setting (MMF-1579) -overview.period.previous_version_only_date=since previous version -overview.period.previous_analysis=since previous analysis -overview.period.days=last {0} days -overview.period.version=since {0} -overview.period.date=after {0} -overview.period.manual_baseline=since {0} +overview.period.previous_version_only_date=Since previous version +overview.period.previous_analysis=Since previous analysis +overview.period.days=Last {0} days +overview.period.version=Since {0} +overview.period.date=After {0} +overview.period.manual_baseline=Since {0} # New periods (MMF-1579) -overview.period.number_of_days=from last {0} days -overview.period.specific_analysis=since {0} +overview.period.number_of_days=From last {0} days +overview.period.specific_analysis=Since {0} overview.gate.ERROR=Failed overview.gate.WARN=Warning -- 2.39.5