From 15705813665581d67d67bff133d31d235dd16535 Mon Sep 17 00:00:00 2001 From: Stas Vilchik Date: Fri, 15 Sep 2017 15:00:45 +0200 Subject: [PATCH] SONAR-9812 display activity page for portfolios (#2510) --- server/sonar-web/src/main/js/api/languages.ts | 7 +- server/sonar-web/src/main/js/api/measures.ts | 5 +- server/sonar-web/src/main/js/api/metrics.ts | 3 +- server/sonar-web/src/main/js/api/report.ts | 55 ++ .../js/app/components/ComponentContainer.tsx | 14 +- .../nav/component/ComponentNavMenu.tsx | 15 +- .../__tests__/ComponentNavMenu-test.tsx | 21 + .../ComponentNavMenu-test.tsx.snap | 646 ++++++++++++++++++ server/sonar-web/src/main/js/app/types.ts | 1 + .../src/main/js/app/utils/exposeLibraries.js | 3 +- .../src/main/js/app/utils/startReactApp.js | 5 +- .../apps/code/components/ComponentMeasure.tsx | 5 +- .../components/MeasureHeader.js | 11 +- .../__tests__/MeasureHeader-test.js | 2 +- .../drilldown/TreeMapView.js | 2 +- .../js/apps/overview/events/AnalysesList.js | 3 +- .../src/main/js/apps/overview/main/enhance.js | 4 +- .../main/js/apps/overview/meta/MetaSize.js | 4 +- .../js/apps/portfolio/components/Activity.tsx | 121 ++++ .../main/js/apps/portfolio/components/App.tsx | 150 ++++ .../js/apps/portfolio/components/Effort.tsx | 59 ++ .../components/HistoryButtonLink.tsx | 38 ++ .../apps/portfolio/components/MainRating.tsx | 37 + .../components/MaintainabilityBox.tsx | 54 ++ .../components/MeasuresButtonLink.tsx | 38 ++ .../portfolio/components/RatingFreshness.tsx | 49 ++ .../portfolio/components/ReleasabilityBox.tsx | 68 ++ .../portfolio/components/ReliabilityBox.tsx | 54 ++ .../js/apps/portfolio/components/Report.tsx | 112 +++ .../apps/portfolio/components/SecurityBox.tsx | 54 ++ .../portfolio/components/Subscription.tsx | 133 ++++ .../components/SubscriptionContainer.tsx | 28 + .../js/apps/portfolio/components/Summary.tsx | 69 ++ .../portfolio/components/WorstProjects.tsx | 140 ++++ .../components/__tests__/Activity-test.tsx | 77 +++ .../components/__tests__/App-test.tsx | 89 +++ .../components/__tests__/Effort-test.tsx | 30 + .../__tests__/HistoryButtonLink-test.tsx | 26 + .../components/__tests__/MainRating-test.tsx | 28 + .../__tests__/MaintainabilityBox-test.tsx | 31 + .../__tests__/MeasuresButtonLink-test.tsx | 28 + .../__tests__/RatingFreshness-test.tsx | 31 + .../__tests__/ReleasabilityBox-test.tsx | 31 + .../__tests__/ReliabilityBox-test.tsx | 31 + .../components/__tests__/Report-test.tsx | 54 ++ .../components/__tests__/SecurityBox-test.tsx | 31 + .../__tests__/Subscription-test.tsx | 84 +++ .../components/__tests__/Summary-test.tsx | 33 + .../__tests__/WorstProjects-test.tsx | 68 ++ .../__snapshots__/Activity-test.tsx.snap | 42 ++ .../__tests__/__snapshots__/App-test.tsx.snap | 65 ++ .../__snapshots__/Effort-test.tsx.snap | 50 ++ .../HistoryButtonLink-test.tsx.snap | 24 + .../__snapshots__/MainRating-test.tsx.snap | 24 + .../MaintainabilityBox-test.tsx.snap | 39 ++ .../MeasuresButtonLink-test.tsx.snap | 23 + .../RatingFreshness-test.tsx.snap | 31 + .../ReleasabilityBox-test.tsx.snap | 75 ++ .../ReliabilityBox-test.tsx.snap | 39 ++ .../__snapshots__/Report-test.tsx.snap | 65 ++ .../__snapshots__/SecurityBox-test.tsx.snap | 39 ++ .../__snapshots__/Subscription-test.tsx.snap | 63 ++ .../__snapshots__/Summary-test.tsx.snap | 96 +++ .../__snapshots__/WorstProjects-test.tsx.snap | 395 +++++++++++ .../src/main/js/apps/portfolio/routes.ts | 30 + .../src/main/js/apps/portfolio/styles.css | 95 +++ .../src/main/js/apps/portfolio/types.ts | 26 + .../src/main/js/apps/portfolio/utils.ts | 92 +++ .../projectActivity/__tests__/utils-test.js | 4 +- .../components/GraphHistory.js | 2 +- .../components/GraphsHistory.js | 2 +- .../projectActivity/components/GraphsZoom.js | 4 +- .../components/ProjectActivityAnalysesList.js | 5 +- .../components/ProjectActivityApp.js | 10 +- .../components/ProjectActivityAppContainer.js | 39 +- .../components/ProjectActivityGraphs.js | 2 +- .../components/ProjectActivityPageHeader.js | 24 +- .../components/ProjectCardLeakMeasures.tsx | 20 +- .../components/ProjectCardOverallMeasures.tsx | 10 +- .../ProjectCardLeakMeasures-test.tsx.snap | 12 - .../ProjectCardOverallMeasures-test.tsx.snap | 6 - .../SourceViewer/views/measures-overlay.js | 31 +- .../components/charts/LanguageDistribution.js | 89 --- .../charts/LanguageDistribution.tsx | 64 ++ .../charts/LanguageDistributionContainer.tsx} | 22 +- .../main/js/components/charts/ZoomTimeLine.js | 3 +- .../{BubblesIcon.js => BubblesIcon.tsx} | 12 +- .../{HistoryIcon.js => HistoryIcon.tsx} | 13 +- .../components/icons-components/LinkIcon.tsx | 1 - .../main/js/components/measure/Measure.tsx | 8 +- .../src/main/js/components/measure/utils.ts | 15 +- .../preview-graph}/PreviewGraph.js | 25 +- .../preview-graph}/PreviewGraphTooltips.js | 6 +- .../PreviewGraphTooltipsContent.js | 2 +- .../__tests__/PreviewGraphTooltips-test.js | 4 +- .../PreviewGraphTooltipsContent-test.js | 0 .../PreviewGraphTooltips-test.js.snap | 0 .../PreviewGraphTooltipsContent-test.js.snap | 0 .../src/main/js/helpers/testUtils.ts | 4 +- server/sonar-web/src/main/js/helpers/urls.ts | 15 +- .../resources/org/sonar/l10n/core.properties | 10 + 101 files changed, 4180 insertions(+), 279 deletions(-) create mode 100644 server/sonar-web/src/main/js/api/report.ts create mode 100644 server/sonar-web/src/main/js/apps/portfolio/components/Activity.tsx create mode 100644 server/sonar-web/src/main/js/apps/portfolio/components/App.tsx create mode 100644 server/sonar-web/src/main/js/apps/portfolio/components/Effort.tsx create mode 100644 server/sonar-web/src/main/js/apps/portfolio/components/HistoryButtonLink.tsx create mode 100644 server/sonar-web/src/main/js/apps/portfolio/components/MainRating.tsx create mode 100644 server/sonar-web/src/main/js/apps/portfolio/components/MaintainabilityBox.tsx create mode 100644 server/sonar-web/src/main/js/apps/portfolio/components/MeasuresButtonLink.tsx create mode 100644 server/sonar-web/src/main/js/apps/portfolio/components/RatingFreshness.tsx create mode 100644 server/sonar-web/src/main/js/apps/portfolio/components/ReleasabilityBox.tsx create mode 100644 server/sonar-web/src/main/js/apps/portfolio/components/ReliabilityBox.tsx create mode 100644 server/sonar-web/src/main/js/apps/portfolio/components/Report.tsx create mode 100644 server/sonar-web/src/main/js/apps/portfolio/components/SecurityBox.tsx create mode 100644 server/sonar-web/src/main/js/apps/portfolio/components/Subscription.tsx create mode 100644 server/sonar-web/src/main/js/apps/portfolio/components/SubscriptionContainer.tsx create mode 100644 server/sonar-web/src/main/js/apps/portfolio/components/Summary.tsx create mode 100644 server/sonar-web/src/main/js/apps/portfolio/components/WorstProjects.tsx create mode 100644 server/sonar-web/src/main/js/apps/portfolio/components/__tests__/Activity-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/portfolio/components/__tests__/App-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/portfolio/components/__tests__/Effort-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/portfolio/components/__tests__/HistoryButtonLink-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/portfolio/components/__tests__/MainRating-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/portfolio/components/__tests__/MaintainabilityBox-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/portfolio/components/__tests__/MeasuresButtonLink-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/portfolio/components/__tests__/RatingFreshness-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/portfolio/components/__tests__/ReleasabilityBox-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/portfolio/components/__tests__/ReliabilityBox-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/portfolio/components/__tests__/Report-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/portfolio/components/__tests__/SecurityBox-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/portfolio/components/__tests__/Subscription-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/portfolio/components/__tests__/Summary-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/portfolio/components/__tests__/WorstProjects-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Activity-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/App-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Effort-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/HistoryButtonLink-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/MainRating-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/MaintainabilityBox-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/MeasuresButtonLink-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/RatingFreshness-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/ReleasabilityBox-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/ReliabilityBox-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Report-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/SecurityBox-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Subscription-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Summary-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/WorstProjects-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/portfolio/routes.ts create mode 100644 server/sonar-web/src/main/js/apps/portfolio/styles.css create mode 100644 server/sonar-web/src/main/js/apps/portfolio/types.ts create mode 100644 server/sonar-web/src/main/js/apps/portfolio/utils.ts delete mode 100644 server/sonar-web/src/main/js/components/charts/LanguageDistribution.js create mode 100644 server/sonar-web/src/main/js/components/charts/LanguageDistribution.tsx rename server/sonar-web/src/main/js/{app/components/extensions/PortfolioDashboard.tsx => components/charts/LanguageDistributionContainer.tsx} (67%) rename server/sonar-web/src/main/js/components/icons-components/{BubblesIcon.js => BubblesIcon.tsx} (88%) rename server/sonar-web/src/main/js/components/icons-components/{HistoryIcon.js => HistoryIcon.tsx} (86%) rename server/sonar-web/src/main/js/{apps/overview/events => components/preview-graph}/PreviewGraph.js (89%) rename server/sonar-web/src/main/js/{apps/overview/events => components/preview-graph}/PreviewGraphTooltips.js (92%) rename server/sonar-web/src/main/js/{apps/overview/events => components/preview-graph}/PreviewGraphTooltipsContent.js (94%) rename server/sonar-web/src/main/js/{apps/overview/events => components/preview-graph}/__tests__/PreviewGraphTooltips-test.js (95%) rename server/sonar-web/src/main/js/{apps/overview/events => components/preview-graph}/__tests__/PreviewGraphTooltipsContent-test.js (100%) rename server/sonar-web/src/main/js/{apps/overview/events => components/preview-graph}/__tests__/__snapshots__/PreviewGraphTooltips-test.js.snap (100%) rename server/sonar-web/src/main/js/{apps/overview/events => components/preview-graph}/__tests__/__snapshots__/PreviewGraphTooltipsContent-test.js.snap (100%) diff --git a/server/sonar-web/src/main/js/api/languages.ts b/server/sonar-web/src/main/js/api/languages.ts index ee5bd130a5e..cb4722540ae 100644 --- a/server/sonar-web/src/main/js/api/languages.ts +++ b/server/sonar-web/src/main/js/api/languages.ts @@ -19,6 +19,11 @@ */ import { getJSON } from '../helpers/request'; -export function getLanguages(): Promise { +export interface Language { + key: string; + name: string; +} + +export function getLanguages(): Promise { return getJSON('/api/languages/list').then(r => r.languages); } diff --git a/server/sonar-web/src/main/js/api/measures.ts b/server/sonar-web/src/main/js/api/measures.ts index df194d04a34..c50933f4b21 100644 --- a/server/sonar-web/src/main/js/api/measures.ts +++ b/server/sonar-web/src/main/js/api/measures.ts @@ -18,15 +18,16 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import { getJSON, RequestData } from '../helpers/request'; +import throwGlobalError from '../app/utils/throwGlobalError'; export function getMeasures( componentKey: string, metrics: string[], branch?: string -): Promise { +): Promise> { const url = '/api/measures/component'; const data = { componentKey, metricKeys: metrics.join(','), branch }; - return getJSON(url, data).then(r => r.component.measures); + return getJSON(url, data).then(r => r.component.measures, throwGlobalError); } export function getMeasuresAndMeta( diff --git a/server/sonar-web/src/main/js/api/metrics.ts b/server/sonar-web/src/main/js/api/metrics.ts index ea2f78d569c..985a8206403 100644 --- a/server/sonar-web/src/main/js/api/metrics.ts +++ b/server/sonar-web/src/main/js/api/metrics.ts @@ -18,7 +18,8 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import { getJSON } from '../helpers/request'; +import { Metric } from '../app/types'; -export function getMetrics(): Promise { +export function getMetrics(): Promise { return getJSON('/api/metrics/search', { ps: 9999 }).then(r => r.metrics); } diff --git a/server/sonar-web/src/main/js/api/report.ts b/server/sonar-web/src/main/js/api/report.ts new file mode 100644 index 00000000000..e7c608ee2d6 --- /dev/null +++ b/server/sonar-web/src/main/js/api/report.ts @@ -0,0 +1,55 @@ +/* +* SonarQube +* Copyright (C) 2009-2016 SonarSource SA +* mailto:contact 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 { getJSON, post } from '../helpers/request'; +import throwGlobalError from '../app/utils/throwGlobalError'; + +export interface ReportStatus { + canDownload?: boolean; + canSubscribe: boolean; + componentFrequency?: string; + globalFrequency: string; + subscribed?: boolean; +} + +export function getReportStatus(component: string): Promise { + return getJSON('/api/governance_reports/status', { componentKey: component }).catch( + throwGlobalError + ); +} + +export function getReportUrl(component: string): string { + return ( + (window as any).baseUrl + + '/api/governance_reports/download?componentKey=' + + encodeURIComponent(component) + ); +} + +export function subscribe(component: string): Promise { + return post('/api/governance_reports/subscribe', { componentKey: component }).catch( + throwGlobalError + ); +} + +export function unsubscribe(component: string): Promise { + return post('/api/governance_reports/unsubscribe', { componentKey: component }).catch( + throwGlobalError + ); +} diff --git a/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx b/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx index 99bd095c9e8..085b4be1979 100644 --- a/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx +++ b/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx @@ -49,15 +49,15 @@ export default class ComponentContainer extends React.PureComponent { diff --git a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.tsx b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.tsx index e3c49a90b9a..625043eb923 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.tsx @@ -65,7 +65,7 @@ export default class ComponentNavMenu extends React.PureComponent { return this.props.component.qualifier === 'DEV'; } - isView() { + isPortfolio() { const { qualifier } = this.props.component; return qualifier === 'VW' || qualifier === 'SVW'; } @@ -79,7 +79,7 @@ export default class ComponentNavMenu extends React.PureComponent { return null; } - const pathname = this.isView() ? '/portfolio' : '/dashboard'; + const pathname = this.isPortfolio() ? '/portfolio' : '/dashboard'; return (
  • { } }} activeClassName="active"> - {this.isView() || this.isApplication() ? ( + {this.isPortfolio() || this.isApplication() ? ( translate('view_projects.page') ) : ( translate('code.page') @@ -124,7 +124,7 @@ export default class ComponentNavMenu extends React.PureComponent { } renderActivityLink() { - if (!this.isProject() && !this.isApplication()) { + if (!this.isProject() && !this.isApplication() && !this.isPortfolio()) { return null; } @@ -252,7 +252,7 @@ export default class ComponentNavMenu extends React.PureComponent { } renderSettingsLink() { - if (!this.props.conf.showSettings || this.isApplication() || this.isView()) { + if (!this.props.conf.showSettings || this.isApplication() || this.isPortfolio()) { return null; } return ( @@ -432,8 +432,7 @@ export default class ComponentNavMenu extends React.PureComponent { renderExtensions() { const extensions = this.props.component.extensions || []; - const withoutGovernance = extensions.filter(ext => ext.name !== 'Governance'); - if (!withoutGovernance.length) { + if (!extensions.length) { return null; } @@ -448,7 +447,7 @@ export default class ComponentNavMenu extends React.PureComponent {
      - {withoutGovernance.map(e => this.renderExtension(e, false))} + {extensions.map(e => this.renderExtension(e, false))}
  • ); diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavMenu-test.tsx b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavMenu-test.tsx index ad86f8f2246..2e4af3fb2ee 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavMenu-test.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavMenu-test.tsx @@ -99,3 +99,24 @@ it('should work for long-living branches', () => { ).toMatchSnapshot() ); }); + +it('should work for all qualifiers', () => { + ['TRK', 'BRC', 'VW', 'SVW', 'APP'].forEach(checkWithQualifier); + expect.assertions(5); + + function checkWithQualifier(qualifier: string) { + const component = { key: 'foo', qualifier } as Component; + expect( + shallow( + , + { + context: { branchesEnabled: true } + } + ) + ).toMatchSnapshot(); + } +}); diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMenu-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMenu-test.tsx.snap index 1cb875eee66..ea4cd792997 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMenu-test.tsx.snap +++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMenu-test.tsx.snap @@ -1,5 +1,651 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`should work for all qualifiers 1`] = ` + +
  • + + overview.page + +
  • +
  • + + issues.page + +
  • +
  • + + layout.measures + +
  • +
  • + + code.page + +
  • +
  • + + project_activity.page + +
  • +
  • + + layout.settings +   + + +
      +
    • + + project_settings.page + +
    • +
    • + + project_branches.page + +
    • +
    • + + deletion.page + +
    • +
    +
  • +
    +`; + +exports[`should work for all qualifiers 2`] = ` + +
  • + + overview.page + +
  • +
  • + + issues.page + +
  • +
  • + + layout.measures + +
  • +
  • + + code.page + +
  • +
  • + + layout.settings +   + + +
      +
    • + + project_settings.page + +
    • +
    +
  • +
    +`; + +exports[`should work for all qualifiers 3`] = ` + +
  • + + overview.page + +
  • +
  • + + issues.page + +
  • +
  • + + layout.measures + +
  • +
  • + + view_projects.page + +
  • +
  • + + project_activity.page + +
  • +
  • + + layout.settings +   + + +
      +
    • + + deletion.page + +
    • +
    +
  • +
    +`; + +exports[`should work for all qualifiers 4`] = ` + +
  • + + overview.page + +
  • +
  • + + issues.page + +
  • +
  • + + layout.measures + +
  • +
  • + + view_projects.page + +
  • +
  • + + project_activity.page + +
  • +
    +`; + +exports[`should work for all qualifiers 5`] = ` + +
  • + + overview.page + +
  • +
  • + + issues.page + +
  • +
  • + + layout.measures + +
  • +
  • + + view_projects.page + +
  • +
  • + + project_activity.page + +
  • +
  • + + layout.settings +   + + +
      +
    • + + deletion.page + +
    • +
    +
  • +
    +`; + exports[`should work for long-living branches 1`] = `
  • diff --git a/server/sonar-web/src/main/js/app/types.ts b/server/sonar-web/src/main/js/app/types.ts index 2d0bc46db36..1db17ec0ffd 100644 --- a/server/sonar-web/src/main/js/app/types.ts +++ b/server/sonar-web/src/main/js/app/types.ts @@ -67,6 +67,7 @@ export interface Component { qualifier: string; }>; configuration?: ComponentConfiguration; + description?: string; extensions?: ComponentExtension[]; isFavorite?: boolean; key: string; diff --git a/server/sonar-web/src/main/js/app/utils/exposeLibraries.js b/server/sonar-web/src/main/js/app/utils/exposeLibraries.js index e69bb3ab64a..b0d2a4fdd9f 100644 --- a/server/sonar-web/src/main/js/app/utils/exposeLibraries.js +++ b/server/sonar-web/src/main/js/app/utils/exposeLibraries.js @@ -21,6 +21,7 @@ import * as ReactRedux from 'react-redux'; import * as ReactRouter from 'react-router'; import Select from 'react-select'; import Modal from 'react-modal'; +import throwGlobalError from './throwGlobalError'; import * as measures from '../../helpers/measures'; import * as request from '../../helpers/request'; import * as icons from '../../components/icons-components/icons'; @@ -41,7 +42,7 @@ const exposeLibraries = () => { window.ReactRouter = ReactRouter; window.SonarIcons = icons; window.SonarMeasures = measures; - window.SonarRequest = request; + window.SonarRequest = { ...request, throwGlobalError }; window.SonarComponents = { DateFromNow, DateFormatter, diff --git a/server/sonar-web/src/main/js/app/utils/startReactApp.js b/server/sonar-web/src/main/js/app/utils/startReactApp.js index 15d742e3730..b6577565efd 100644 --- a/server/sonar-web/src/main/js/app/utils/startReactApp.js +++ b/server/sonar-web/src/main/js/app/utils/startReactApp.js @@ -32,7 +32,6 @@ import Landing from '../components/Landing'; import ProjectAdminContainer from '../components/ProjectAdminContainer'; import ProjectPageExtension from '../components/extensions/ProjectPageExtension'; import ProjectAdminPageExtension from '../components/extensions/ProjectAdminPageExtension'; -import PortfolioDashboard from '../components/extensions/PortfolioDashboard'; import PortfoliosPage from '../components/extensions/PortfoliosPage'; import AdminContainer from '../components/AdminContainer'; import GlobalPageExtension from '../components/extensions/GlobalPageExtension'; @@ -53,6 +52,7 @@ import metricsRoutes from '../../apps/metrics/routes'; import overviewRoutes from '../../apps/overview/routes'; import organizationsRoutes from '../../apps/organizations/routes'; import permissionTemplatesRoutes from '../../apps/permission-templates/routes'; +import portfolioRoutes from '../../apps/portfolio/routes'; import projectActivityRoutes from '../../apps/projectActivity/routes'; import projectAdminRoutes from '../../apps/project-admin/routes'; import projectBranchesRoutes from '../../apps/projectBranches/routes'; @@ -125,7 +125,6 @@ const startReactApp = () => { - @@ -189,7 +188,7 @@ const startReactApp = () => { - + ; } - // TODO - const AnyMeasure = Measure as any; - return ( - + ); } diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureHeader.js b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureHeader.js index b2763cbcdc5..de343e95337 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureHeader.js +++ b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureHeader.js @@ -23,13 +23,13 @@ import { Link } from 'react-router'; import ComplexityDistribution from '../../../components/shared/ComplexityDistribution'; import HistoryIcon from '../../../components/icons-components/HistoryIcon'; import IssueTypeIcon from '../../../components/ui/IssueTypeIcon'; -import LanguageDistribution from '../../../components/charts/LanguageDistribution'; +import LanguageDistributionContainer from '../../../components/charts/LanguageDistributionContainer'; import LeakPeriodLegend from './LeakPeriodLegend'; import Measure from '../../../components/measure/Measure'; import Tooltip from '../../../components/controls/Tooltip'; import { isFileType } from '../utils'; import { getLocalizedMetricName, translate, translateWithParameters } from '../../../helpers/l10n'; -import { getComponentMeasureHistory } from '../../../helpers/urls'; +import { getMeasureHistoryUrl } from '../../../helpers/urls'; import { isDiffMetric } from '../../../helpers/measures'; /*:: import type { Component, Period } from '../types'; */ /*:: import type { MeasureEnhanced } from '../../../components/measure/types'; */ @@ -121,7 +121,7 @@ export default class MeasureHeader extends React.PureComponent { overlay={translate('component_measures.show_metric_history')}> + to={getMeasureHistoryUrl(component.key, metric.key, branch)}> @@ -137,7 +137,10 @@ export default class MeasureHeader extends React.PureComponent { {secondaryMeasure && secondaryMeasure.metric.key === 'ncloc_language_distribution' && (
    - +
    )} {secondaryMeasure && diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureHeader-test.js b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureHeader-test.js index 73619312a76..8164a603717 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureHeader-test.js +++ b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureHeader-test.js @@ -82,7 +82,7 @@ it('should render with branch', () => { it('should display secondary measure too', () => { const wrapper = shallow(); - expect(wrapper.find('LanguageDistribution')).toHaveLength(1); + expect(wrapper.find('Connect(LanguageDistribution)')).toHaveLength(1); }); it('shohuld display correctly for open file', () => { diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/TreeMapView.js b/server/sonar-web/src/main/js/apps/component-measures/drilldown/TreeMapView.js index 4411d4743ec..aa7fa92452a 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/drilldown/TreeMapView.js +++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/TreeMapView.js @@ -19,7 +19,7 @@ */ // @flow import React from 'react'; -import { AutoSizer } from 'react-virtualized'; +import AutoSizer from 'react-virtualized/dist/commonjs/AutoSizer'; import { scaleLinear, scaleOrdinal } from 'd3-scale'; import ColorBoxLegend from '../../../components/charts/ColorBoxLegend'; import ColorGradientLegend from '../../../components/charts/ColorGradientLegend'; diff --git a/server/sonar-web/src/main/js/apps/overview/events/AnalysesList.js b/server/sonar-web/src/main/js/apps/overview/events/AnalysesList.js index 1494366789d..2c181d03d8c 100644 --- a/server/sonar-web/src/main/js/apps/overview/events/AnalysesList.js +++ b/server/sonar-web/src/main/js/apps/overview/events/AnalysesList.js @@ -21,9 +21,9 @@ import React from 'react'; import { Link } from 'react-router'; import Analysis from './Analysis'; -import PreviewGraph from './PreviewGraph'; import { getMetrics } from '../../../api/metrics'; import { getProjectActivity } from '../../../api/projectActivity'; +import PreviewGraph from '../../../components/preview-graph/PreviewGraph'; import { translate } from '../../../helpers/l10n'; /*:: import type { Analysis as AnalysisType } from '../../projectActivity/types'; */ /*:: import type { History, Metric } from '../types'; */ @@ -114,7 +114,6 @@ export default class AnalysesList extends React.PureComponent { history={this.props.history} project={this.props.project} metrics={this.state.metrics} - router={this.props.router} /> {this.renderList(analyses)} diff --git a/server/sonar-web/src/main/js/apps/overview/main/enhance.js b/server/sonar-web/src/main/js/apps/overview/main/enhance.js index 73ab9386a3e..3e3759dfb33 100644 --- a/server/sonar-web/src/main/js/apps/overview/main/enhance.js +++ b/server/sonar-web/src/main/js/apps/overview/main/enhance.js @@ -39,7 +39,7 @@ import { getPeriodDate } from '../../../helpers/periods'; import { getComponentDrilldownUrl, getComponentIssuesUrl, - getComponentMeasureHistory + getMeasureHistoryUrl } from '../../../helpers/urls'; export default function enhance(ComposedComponent) { @@ -175,7 +175,7 @@ export default function enhance(ComposedComponent) { return ( + to={getMeasureHistoryUrl(this.props.component.key, metricKey, this.props.branch)}> ); diff --git a/server/sonar-web/src/main/js/apps/overview/meta/MetaSize.js b/server/sonar-web/src/main/js/apps/overview/meta/MetaSize.js index 686611d5d91..a488f469da6 100644 --- a/server/sonar-web/src/main/js/apps/overview/meta/MetaSize.js +++ b/server/sonar-web/src/main/js/apps/overview/meta/MetaSize.js @@ -21,7 +21,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; import { DrilldownLink } from '../../../components/shared/drilldown-link'; -import LanguageDistribution from '../../../components/charts/LanguageDistribution'; +import LanguageDistributionContainer from '../../../components/charts/LanguageDistributionContainer'; import SizeRating from '../../../components/ui/SizeRating'; import { formatMeasure } from '../../../helpers/measures'; import { getMetricName } from '../helpers/metrics'; @@ -57,7 +57,7 @@ export default class MetaSize extends React.PureComponent { return languageDistribution ? (
    - +
    ) : null; }; diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/Activity.tsx b/server/sonar-web/src/main/js/apps/portfolio/components/Activity.tsx new file mode 100644 index 00000000000..26a66872a01 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/portfolio/components/Activity.tsx @@ -0,0 +1,121 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { getDisplayedHistoryMetrics, DEFAULT_GRAPH } from '../../projectActivity/utils'; +import PreviewGraph from '../../../components/preview-graph/PreviewGraph'; +import { getMetrics } from '../../../api/metrics'; +import { getAllTimeMachineData } from '../../../api/time-machine'; +import { Metric } from '../../../app/types'; +import { parseDate } from '../../../helpers/dates'; +import { translate } from '../../../helpers/l10n'; +import { getCustomGraph, getGraph } from '../../../helpers/storage'; + +const AnyPreviewGraph = PreviewGraph as any; + +interface History { + [metric: string]: Array<{ date: Date; value: string }>; +} + +interface Props { + component: string; +} + +interface State { + history?: History; + loading: boolean; + metrics?: Metric[]; +} + +export default class Activity extends React.PureComponent { + mounted: boolean; + state: State = { loading: true }; + + componentDidMount() { + this.mounted = true; + this.fetchHistory(); + } + + componentDidUpdate(prevProps: Props) { + if (prevProps.component !== this.props.component) { + this.fetchHistory(); + } + } + + componentWillUnmount() { + this.mounted = false; + } + + fetchHistory = () => { + const { component } = this.props; + + let graphMetrics = getDisplayedHistoryMetrics(getGraph(), getCustomGraph()); + if (!graphMetrics || graphMetrics.length <= 0) { + graphMetrics = getDisplayedHistoryMetrics(DEFAULT_GRAPH, []); + } + + this.setState({ loading: true }); + return Promise.all([getAllTimeMachineData(component, graphMetrics), getMetrics()]).then( + ([timeMachine, metrics]) => { + if (this.mounted) { + const history: History = {}; + timeMachine.measures.forEach(measure => { + const measureHistory = measure.history.map(analysis => ({ + date: parseDate(analysis.date), + value: analysis.value + })); + history[measure.metric] = measureHistory; + }); + this.setState({ history, loading: false, metrics }); + } + }, + () => { + if (this.mounted) { + this.setState({ loading: false }); + } + } + ); + }; + + renderWhenEmpty = () =>
    {translate('component_measures.no_history')}
    ; + + render() { + return ( +
    +
    +

    {translate('project_activity.page')}

    +
    + + {this.state.loading ? ( + + ) : ( + this.state.metrics != undefined && + this.state.history != undefined && ( + + ) + )} +
    + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/App.tsx b/server/sonar-web/src/main/js/apps/portfolio/components/App.tsx new file mode 100644 index 00000000000..e0910baa420 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/portfolio/components/App.tsx @@ -0,0 +1,150 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 Summary from './Summary'; +import Report from './Report'; +import WorstProjects from './WorstProjects'; +import ReleasabilityBox from './ReleasabilityBox'; +import ReliabilityBox from './ReliabilityBox'; +import SecurityBox from './SecurityBox'; +import MaintainabilityBox from './MaintainabilityBox'; +import Activity from './Activity'; +import { getMeasures } from '../../../api/measures'; +import { getChildren } from '../../../api/components'; +import { PORTFOLIO_METRICS, SUB_COMPONENTS_METRICS, convertMeasures } from '../utils'; +import { SubComponent } from '../types'; +import '../styles.css'; + +interface Props { + component: { key: string; name: string }; +} + +interface State { + loading: boolean; + measures?: { [key: string]: string | undefined }; + subComponents?: SubComponent[]; + totalSubComponents?: number; +} + +export default class App extends React.PureComponent { + mounted: boolean; + state: State = { loading: true }; + + componentDidMount() { + this.mounted = true; + const html = document.querySelector('html'); + if (html) { + html.classList.add('dashboard-page'); + } + this.fetchData(); + } + + componentDidUpdate(prevProps: Props) { + if (prevProps.component !== this.props.component) { + this.fetchData(); + } + } + + componentWillUnmount() { + this.mounted = false; + const html = document.querySelector('html'); + if (html) { + html.classList.remove('dashboard-page'); + } + } + + fetchData() { + this.setState({ loading: true }); + Promise.all([ + getMeasures(this.props.component.key, PORTFOLIO_METRICS), + getChildren(this.props.component.key, SUB_COMPONENTS_METRICS, { ps: 20 }) + ]).then( + ([measures, subComponents]) => { + if (this.mounted) { + this.setState({ + loading: false, + measures: convertMeasures(measures), + subComponents: subComponents.components.map((component: any) => ({ + ...component, + measures: convertMeasures(component.measures) + })), + totalSubComponents: subComponents.paging.total + }); + } + }, + () => { + if (this.mounted) { + this.setState({ loading: false }); + } + } + ); + } + + renderSpinner() { + return ( +
    +
    + +
    +
    + ); + } + + render() { + const { component } = this.props; + const { loading, measures, subComponents, totalSubComponents } = this.state; + + if (loading) { + return this.renderSpinner(); + } + + return ( +
    +
    +
    + {measures != undefined && ( +
    + + + + +
    + )} + + {subComponents != undefined && + totalSubComponents != undefined && ( + + )} +
    + + +
    +
    + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/Effort.tsx b/server/sonar-web/src/main/js/apps/portfolio/components/Effort.tsx new file mode 100644 index 00000000000..f655af6e4e7 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/portfolio/components/Effort.tsx @@ -0,0 +1,59 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { FormattedMessage } from 'react-intl'; +import Rating from '../../../components/ui/Rating'; +import Measure from '../../../components/measure/Measure'; +import { translate } from '../../../helpers/l10n'; +import { getComponentDrilldownUrl } from '../../../helpers/urls'; + +interface Props { + component: string; + effort: { projects: number; rating: number }; + metricKey: string; +} + +export default function Effort({ component, effort, metricKey }: Props) { + return ( +
    + + + {' '} + {translate('projects_')} + + + ), + rating: + }} + /> +
    + ); +} diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/HistoryButtonLink.tsx b/server/sonar-web/src/main/js/apps/portfolio/components/HistoryButtonLink.tsx new file mode 100644 index 00000000000..fbf281d704d --- /dev/null +++ b/server/sonar-web/src/main/js/apps/portfolio/components/HistoryButtonLink.tsx @@ -0,0 +1,38 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { HistoryIcon } from '../../../components/icons-components/icons'; +import { getMeasureHistoryUrl } from '../../../helpers/urls'; + +interface Props { + component: string; + metric: string; +} + +export default function HistoryButtonLink({ component, metric }: Props) { + return ( + + + + ); +} diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/MainRating.tsx b/server/sonar-web/src/main/js/apps/portfolio/components/MainRating.tsx new file mode 100644 index 00000000000..3a92e0983b3 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/portfolio/components/MainRating.tsx @@ -0,0 +1,37 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 Rating from '../../../components/ui/Rating'; +import { getMeasureTreemapUrl } from '../../../helpers/urls'; + +interface Props { + component: string; + metric: string; + value: string; +} + +export default function MainRating({ component, metric, value }: Props) { + return ( + + + + ); +} diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/MaintainabilityBox.tsx b/server/sonar-web/src/main/js/apps/portfolio/components/MaintainabilityBox.tsx new file mode 100644 index 00000000000..e6c3b50b05a --- /dev/null +++ b/server/sonar-web/src/main/js/apps/portfolio/components/MaintainabilityBox.tsx @@ -0,0 +1,54 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 Effort from './Effort'; +import MainRating from './MainRating'; +import MeasuresButtonLink from './MeasuresButtonLink'; +import HistoryButtonLink from './HistoryButtonLink'; +import RatingFreshness from './RatingFreshness'; +import { translate } from '../../../helpers/l10n'; + +interface Props { + component: string; + measures: { [key: string]: string | undefined }; +} + +export default function MaintainabilityBox({ component, measures }: Props) { + const rating = measures['sqale_rating']; + const lastMaintainabilityChange = measures['last_change_on_maintainability_rating']; + const rawEffort = measures['maintainability_rating_effort']; + const effort = rawEffort ? JSON.parse(rawEffort) : undefined; + + return ( +
    +

    + {translate('metric_domain.Maintainability')} + + +

    + + {rating && } + + + + {effort && } +
    + ); +} diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/MeasuresButtonLink.tsx b/server/sonar-web/src/main/js/apps/portfolio/components/MeasuresButtonLink.tsx new file mode 100644 index 00000000000..a4fc94fcf77 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/portfolio/components/MeasuresButtonLink.tsx @@ -0,0 +1,38 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 BubblesIcon from '../../../components/icons-components/BubblesIcon'; +import { getComponentDrilldownUrl } from '../../../helpers/urls'; + +interface Props { + component: string; + metric: string; +} + +export default function MeasuresButtonLink({ component, metric }: Props) { + return ( + + + + ); +} diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/RatingFreshness.tsx b/server/sonar-web/src/main/js/apps/portfolio/components/RatingFreshness.tsx new file mode 100644 index 00000000000..cd9d28c99e3 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/portfolio/components/RatingFreshness.tsx @@ -0,0 +1,49 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 DateFromNow from '../../../components/intl/DateFromNow'; +import Rating from '../../../components/ui/Rating'; +import { translate } from '../../../helpers/l10n'; + +interface Props { + lastChange?: string; +} + +export default function RatingFreshness({ lastChange }: Props) { + if (!lastChange) { + return
     
    ; + } + + const data = JSON.parse(lastChange); + + return ( +
    + , + date: + }} + /> +
    + ); +} diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/ReleasabilityBox.tsx b/server/sonar-web/src/main/js/apps/portfolio/components/ReleasabilityBox.tsx new file mode 100644 index 00000000000..e71c0aed894 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/portfolio/components/ReleasabilityBox.tsx @@ -0,0 +1,68 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 RatingFreshness from './RatingFreshness'; +import Rating from '../../../components/ui/Rating'; +import Measure from '../../../components/measure/Measure'; +import { translate } from '../../../helpers/l10n'; +import { getComponentDrilldownUrl } from '../../../helpers/urls'; + +interface Props { + component: string; + measures: { [key: string]: string | undefined }; +} + +export default function ReleasabilityBox({ component, measures }: Props) { + const rating = measures['releasability_rating']; + const lastReleasabilityChange = measures['last_change_on_releasability_rating']; + const effort = measures['releasability_effort']; + + return ( +
    +

    {translate('metric_domain.Releasability')}

    + + {rating && ( + + + + )} + + + + {effort && + Number(effort) > 0 && ( +
    + + + {' '} + {Number(effort) === 1 ? 'project' : 'projects'} + + {' '} + {translate('metric.level.ERROR')} +
    + )} +
    + ); +} diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/ReliabilityBox.tsx b/server/sonar-web/src/main/js/apps/portfolio/components/ReliabilityBox.tsx new file mode 100644 index 00000000000..12116d9d5cc --- /dev/null +++ b/server/sonar-web/src/main/js/apps/portfolio/components/ReliabilityBox.tsx @@ -0,0 +1,54 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 Effort from './Effort'; +import MeasuresButtonLink from './MeasuresButtonLink'; +import HistoryButtonLink from './HistoryButtonLink'; +import MainRating from './MainRating'; +import RatingFreshness from './RatingFreshness'; +import { translate } from '../../../helpers/l10n'; + +interface Props { + component: string; + measures: { [key: string]: string | undefined }; +} + +export default function ReliabilityBox({ component, measures }: Props) { + const rating = measures['reliability_rating']; + const lastReliabilityChange = measures['last_change_on_reliability_rating']; + const rawEffort = measures['reliability_rating_effort']; + const effort = rawEffort ? JSON.parse(rawEffort) : undefined; + + return ( +
    +

    + {translate('metric_domain.Reliability')} + + +

    + + {rating && } + + + + {effort && } +
    + ); +} diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/Report.tsx b/server/sonar-web/src/main/js/apps/portfolio/components/Report.tsx new file mode 100644 index 00000000000..596afcba973 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/portfolio/components/Report.tsx @@ -0,0 +1,112 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 SubscriptionContainer from './SubscriptionContainer'; +import { getReportStatus, ReportStatus, getReportUrl } from '../../../api/report'; +import { translate } from '../../../helpers/l10n'; + +interface Props { + component: { key: string; name: string }; +} + +interface State { + loading: boolean; + status?: ReportStatus; +} + +export default class Report extends React.PureComponent { + mounted: boolean; + state: State = { loading: true }; + + componentDidMount() { + this.mounted = true; + this.loadStatus(); + } + + componentWillUnmount() { + this.mounted = false; + } + + loadStatus() { + getReportStatus(this.props.component.key).then( + status => { + if (this.mounted) { + this.setState({ status, loading: false }); + } + }, + () => { + if (this.mounted) { + this.setState({ loading: false }); + } + } + ); + } + + renderHeader = () => ( +
    +

    {translate('report.page')}

    +
    + ); + + render() { + const { component } = this.props; + const { status, loading } = this.state; + + if (loading) { + return ( +
    + {this.renderHeader()} + +
    + ); + } + + if (!status) { + return null; + } + + return ( +
    + {this.renderHeader()} + + {!status.canDownload && ( +
    {translate('report.cant_download')}
    + )} + + {status.canDownload && ( +
    + {translate('report.can_download')} + +
    + )} + + {status.canSubscribe && } +
    + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/SecurityBox.tsx b/server/sonar-web/src/main/js/apps/portfolio/components/SecurityBox.tsx new file mode 100644 index 00000000000..da9076240b4 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/portfolio/components/SecurityBox.tsx @@ -0,0 +1,54 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 Effort from './Effort'; +import MeasuresButtonLink from './MeasuresButtonLink'; +import HistoryButtonLink from './HistoryButtonLink'; +import RatingFreshness from './RatingFreshness'; +import MainRating from './MainRating'; +import { translate } from '../../../helpers/l10n'; + +interface Props { + component: string; + measures: { [key: string]: string | undefined }; +} + +export default function SecurityBox({ component, measures }: Props) { + const rating = measures['security_rating']; + const lastSecurityChange = measures['last_change_on_security_rating']; + const rawEffort = measures['security_rating_effort']; + const effort = rawEffort ? JSON.parse(rawEffort) : undefined; + + return ( +
    +

    + {translate('metric_domain.Security')} + + +

    + + {rating && } + + + + {effort && } +
    + ); +} diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/Subscription.tsx b/server/sonar-web/src/main/js/apps/portfolio/components/Subscription.tsx new file mode 100644 index 00000000000..ab0b1d7cf73 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/portfolio/components/Subscription.tsx @@ -0,0 +1,133 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { ReportStatus, subscribe, unsubscribe } from '../../../api/report'; +import { translate, translateWithParameters } from '../../../helpers/l10n'; + +interface Props { + component: string; + currentUser: { email?: string }; + status: ReportStatus; +} + +interface State { + loading: boolean; + subscribed?: boolean; +} + +export default class Subscription extends React.PureComponent { + mounted: boolean; + + constructor(props: Props) { + super(props); + this.state = { subscribed: props.status.subscribed, loading: false }; + } + + componentDidMount() { + this.mounted = true; + } + + componentWillReceiveProps(nextProps: Props) { + if (nextProps.status.subscribed !== this.props.status.subscribed) { + this.setState({ subscribed: nextProps.status.subscribed }); + } + } + + componentWillUnmount() { + this.mounted = false; + } + + stopLoading = () => { + if (this.mounted) { + this.setState({ loading: false }); + } + }; + + handleSubscription = (subscribed: boolean) => { + if (this.mounted) { + this.setState({ loading: false, subscribed }); + } + }; + + handleSubscribe = (e: React.SyntheticEvent) => { + e.preventDefault(); + e.currentTarget.blur(); + this.setState({ loading: true }); + subscribe(this.props.component) + .then(() => this.handleSubscription(true)) + .catch(this.stopLoading); + }; + + handleUnsubscribe = (e: React.SyntheticEvent) => { + e.preventDefault(); + e.currentTarget.blur(); + this.setState({ loading: true }); + unsubscribe(this.props.component) + .then(() => this.handleSubscription(false)) + .catch(this.stopLoading); + }; + + getEffectiveFrequencyText = () => { + const effectiveFrequency = + this.props.status.componentFrequency || this.props.status.globalFrequency; + return translate('report.frequency', effectiveFrequency, 'effective'); + }; + + renderLoading = () => this.state.loading && ; + + renderWhenSubscribed = () => ( +
    +
    + +
    + {translateWithParameters('report.subscribed', this.getEffectiveFrequencyText())} +
    +
    + + {this.renderLoading()} +
    + ); + + renderWhenNotSubscribed = () => ( +
    +

    + {translateWithParameters('report.unsubscribed', this.getEffectiveFrequencyText())} +

    + + {this.renderLoading()} +
    + ); + + render() { + const hasEmail = !!this.props.currentUser.email; + const { subscribed } = this.state; + + let inner; + if (hasEmail) { + inner = subscribed ? this.renderWhenSubscribed() : this.renderWhenNotSubscribed(); + } else { + inner =

    {translate('report.no_email_to_subscribe')}

    ; + } + + return
    {inner}
    ; + } +} diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/SubscriptionContainer.tsx b/server/sonar-web/src/main/js/apps/portfolio/components/SubscriptionContainer.tsx new file mode 100644 index 00000000000..4f5bd33034c --- /dev/null +++ b/server/sonar-web/src/main/js/apps/portfolio/components/SubscriptionContainer.tsx @@ -0,0 +1,28 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { connect } from 'react-redux'; +import Subscription from './Subscription'; +import { getCurrentUser } from '../../../store/rootReducer'; + +const mapStateToProps = (state: any) => ({ + currentUser: getCurrentUser(state) +}); + +export default connect(mapStateToProps)(Subscription); diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/Summary.tsx b/server/sonar-web/src/main/js/apps/portfolio/components/Summary.tsx new file mode 100644 index 00000000000..ef13cf58cb8 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/portfolio/components/Summary.tsx @@ -0,0 +1,69 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 LanguageDistributionContainer from '../../../components/charts/LanguageDistributionContainer'; +import Measure from '../../../components/measure/Measure'; +import { translate } from '../../../helpers/l10n'; +import { getComponentDrilldownUrl } from '../../../helpers/urls'; + +interface Props { + component: { description?: string; key: string }; + measures: { [key: string]: string | undefined }; +} + +export default function Summary({ component, measures }: Props) { + const projects = measures['projects']; + const ncloc = measures['ncloc']; + const nclocDistribution = measures['ncloc_language_distribution']; + + return ( +
    + {component.description &&
    {component.description}
    } + +
      +
    • +
      + + + +
      + {translate('projects')} +
    • +
    • +
      + + + +
      + {translate('metric.ncloc.name')} +
    • +
    + + {nclocDistribution && ( +
    + +
    + )} +
    + ); +} diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/WorstProjects.tsx b/server/sonar-web/src/main/js/apps/portfolio/components/WorstProjects.tsx new file mode 100644 index 00000000000..421eea14c96 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/portfolio/components/WorstProjects.tsx @@ -0,0 +1,140 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { max } from 'lodash'; +import { SubComponent } from '../types'; +import Measure from '../../../components/measure/Measure'; +import QualifierIcon from '../../../components/shared/QualifierIcon'; +import { translate, translateWithParameters } from '../../../helpers/l10n'; +import { formatMeasure } from '../../../helpers/measures'; +import { getProjectUrl } from '../../../helpers/urls'; + +interface Props { + component: string; + subComponents: SubComponent[]; + total: number; +} + +export default function WorstProjects({ component, subComponents, total }: Props) { + const count = subComponents.length; + + if (!count) { + return null; + } + + const maxLoc = max( + subComponents.map(component => Number(component.measures['ncloc'] || 0)) + ) as number; + + const projectsPageUrl = { pathname: '/code', query: { id: component } }; + + return ( +
    + + + + + + + + + + + + + {subComponents.map(component => ( + + + {component.qualifier === 'TRK' ? ( + renderCell(component.measures, 'alert_status', 'LEVEL') + ) : ( + renderCell(component.measures, 'releasability_rating', 'RATING') + )} + {renderCell(component.measures, 'reliability_rating', 'RATING')} + {renderCell(component.measures, 'security_rating', 'RATING')} + {renderCell(component.measures, 'sqale_rating', 'RATING')} + {renderNcloc(component.measures, maxLoc)} + + ))} + +
      + {translate('metric_domain.Releasability')} + + {translate('metric_domain.Reliability')} + + {translate('metric_domain.Security')} + + {translate('metric_domain.Maintainability')} + + {translate('metric.ncloc.name')} +
    + + {component.name} + +
    + + {total > count && ( +
    + {translateWithParameters( + 'x_of_y_shown', + formatMeasure(count, 'INT'), + formatMeasure(total, 'INT') + )} + + {translate('show_more')} + +
    + )} +
    + ); +} + +function renderCell(measures: { [key: string]: string | undefined }, metric: string, type: string) { + return ( + + + + ); +} + +function renderNcloc(measures: { [key: string]: string | undefined }, maxLoc: number) { + const ncloc = Number(measures['ncloc'] || 0); + const barWidth = maxLoc > 0 ? Math.max(1, Math.round(ncloc / maxLoc * 50)) : 0; + return ( + + + + + {maxLoc > 0 && ( + + + + )} + + ); +} diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/Activity-test.tsx b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/Activity-test.tsx new file mode 100644 index 00000000000..1959dc4daaf --- /dev/null +++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/Activity-test.tsx @@ -0,0 +1,77 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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. + */ +jest.mock('../../../../helpers/storage', () => ({ + getCustomGraph: () => ['coverage'], + getGraph: () => 'custom' +})); + +jest.mock('../../../../api/metrics', () => ({ + getMetrics: jest.fn(() => Promise.resolve([])) +})); + +jest.mock('../../../../api/time-machine', () => ({ + getAllTimeMachineData: jest.fn(() => + Promise.resolve({ + measures: [ + { + metric: 'coverage', + history: [ + { date: '2017-01-01T00:00:00.000Z', value: '73' }, + { date: '2017-01-02T00:00:00.000Z', value: '82' } + ] + } + ] + }) + ) +})); + +import * as React from 'react'; +import { mount, shallow } from 'enzyme'; +import Activity from '../Activity'; + +const getMetrics = require('../../../../api/metrics').getMetrics as jest.Mock; +const getAllTimeMachineData = require('../../../../api/time-machine') + .getAllTimeMachineData as jest.Mock; + +beforeEach(() => { + getMetrics.mockClear(); + getAllTimeMachineData.mockClear(); +}); + +it('renders', () => { + const wrapper = shallow(); + wrapper.setState({ + history: { + coverage: [ + { date: '2017-01-01T00:00:00.000Z', value: '73' }, + { date: '2017-01-02T00:00:00.000Z', value: '82' } + ] + }, + loading: false, + metrics: [{ key: 'coverage' }] + }); + expect(wrapper).toMatchSnapshot(); +}); + +it('fetches history', () => { + mount(); + expect(getMetrics).toBeCalled(); + expect(getAllTimeMachineData).toBeCalledWith('foo', ['coverage']); +}); diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/App-test.tsx new file mode 100644 index 00000000000..fafff182895 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/App-test.tsx @@ -0,0 +1,89 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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. + */ +jest.mock('../../../../api/measures', () => ({ + getMeasures: jest.fn(() => Promise.resolve([])) +})); + +jest.mock('../../../../api/components', () => ({ + getChildren: jest.fn(() => Promise.resolve({ components: [], paging: { total: 0 } })) +})); + +// mock Activity to not deal with localstorage +jest.mock('../Activity', () => ({ + default: function Activity() { + return null; + } +})); + +jest.mock('../Report', () => ({ + default: function Report() { + return null; + } +})); + +import * as React from 'react'; +import { shallow, mount } from 'enzyme'; +import App from '../App'; + +const getMeasures = require('../../../../api/measures').getMeasures as jest.Mock; +const getChildren = require('../../../../api/components').getChildren as jest.Mock; + +const component = { key: 'foo', name: 'Foo' }; + +it('renders', () => { + const wrapper = shallow(); + wrapper.setState({ loading: false, measures: {}, subComponents: [], totalSubComponents: 0 }); + expect(wrapper).toMatchSnapshot(); +}); + +it('fetches measures and children components', () => { + getMeasures.mockClear(); + getChildren.mockClear(); + mount(); + expect(getMeasures).toBeCalledWith('foo', [ + 'projects', + 'ncloc', + 'ncloc_language_distribution', + 'releasability_rating', + 'releasability_effort', + 'sqale_rating', + 'maintainability_rating_effort', + 'reliability_rating', + 'reliability_rating_effort', + 'security_rating', + 'security_rating_effort', + 'last_change_on_releasability_rating', + 'last_change_on_maintainability_rating', + 'last_change_on_security_rating', + 'last_change_on_reliability_rating' + ]); + expect(getChildren).toBeCalledWith( + 'foo', + [ + 'ncloc', + 'releasability_rating', + 'security_rating', + 'reliability_rating', + 'sqale_rating', + 'alert_status' + ], + { ps: 20 } + ); +}); diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/Effort-test.tsx b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/Effort-test.tsx new file mode 100644 index 00000000000..1a0189c5815 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/Effort-test.tsx @@ -0,0 +1,30 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { shallow } from 'enzyme'; +import Effort from '../Effort'; + +it('renders', () => { + expect( + shallow( + + ) + ).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/HistoryButtonLink-test.tsx b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/HistoryButtonLink-test.tsx new file mode 100644 index 00000000000..a2f5cb5c8ba --- /dev/null +++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/HistoryButtonLink-test.tsx @@ -0,0 +1,26 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { shallow } from 'enzyme'; +import HistoryButtonLink from '../HistoryButtonLink'; + +it('renders', () => { + expect(shallow()).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/MainRating-test.tsx b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/MainRating-test.tsx new file mode 100644 index 00000000000..9ff9f372bb1 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/MainRating-test.tsx @@ -0,0 +1,28 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { shallow } from 'enzyme'; +import MainRating from '../MainRating'; + +it('renders', () => { + expect( + shallow() + ).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/MaintainabilityBox-test.tsx b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/MaintainabilityBox-test.tsx new file mode 100644 index 00000000000..11e5b0ff065 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/MaintainabilityBox-test.tsx @@ -0,0 +1,31 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { shallow } from 'enzyme'; +import MaintainabilityBox from '../MaintainabilityBox'; + +it('renders', () => { + const measures = { + sqale_rating: '3', + last_change_on_maintainability_rating: '{"date":"2017-01-02T00:00:00.000Z","value":2}', + maintainability_rating_effort: '{"rating":3,"projects":1}' + }; + expect(shallow()).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/MeasuresButtonLink-test.tsx b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/MeasuresButtonLink-test.tsx new file mode 100644 index 00000000000..6d8a7a59c0a --- /dev/null +++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/MeasuresButtonLink-test.tsx @@ -0,0 +1,28 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { shallow } from 'enzyme'; +import MeasuresButtonLink from '../MeasuresButtonLink'; + +it('renders', () => { + expect( + shallow() + ).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/RatingFreshness-test.tsx b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/RatingFreshness-test.tsx new file mode 100644 index 00000000000..5490ed7ff29 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/RatingFreshness-test.tsx @@ -0,0 +1,31 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { shallow } from 'enzyme'; +import RatingFreshness from '../RatingFreshness'; + +it('renders', () => { + const lastChange = '{"date":"2017-01-02T00:00:00.000Z","value":2}'; + expect(shallow()).toMatchSnapshot(); +}); + +it('renders empty', () => { + expect(shallow()).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/ReleasabilityBox-test.tsx b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/ReleasabilityBox-test.tsx new file mode 100644 index 00000000000..90c900a2d1e --- /dev/null +++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/ReleasabilityBox-test.tsx @@ -0,0 +1,31 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { shallow } from 'enzyme'; +import ReleasabilityBox from '../ReleasabilityBox'; + +it('renders', () => { + const measures = { + releasability_rating: '3', + last_change_on_releasability_rating: '{"date":"2017-01-02T00:00:00.000Z","value":2}', + releasability_effort: '7' + }; + expect(shallow()).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/ReliabilityBox-test.tsx b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/ReliabilityBox-test.tsx new file mode 100644 index 00000000000..f2b65993251 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/ReliabilityBox-test.tsx @@ -0,0 +1,31 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { shallow } from 'enzyme'; +import ReliabilityBox from '../ReliabilityBox'; + +it('renders', () => { + const measures = { + reliability_rating: '3', + last_change_on_reliability_rating: '{"date":"2017-01-02T00:00:00.000Z","value":2}', + reliability_rating_effort: '{"rating":3,"projects":1}' + }; + expect(shallow()).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/Report-test.tsx b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/Report-test.tsx new file mode 100644 index 00000000000..1a21649ae06 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/Report-test.tsx @@ -0,0 +1,54 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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. + */ +jest.mock('../../../../api/report', () => { + const report = require.requireActual('../../../../api/report'); + report.getReportStatus = jest.fn(() => Promise.resolve({})); + return report; +}); + +import * as React from 'react'; +import { mount, shallow } from 'enzyme'; +import Report from '../Report'; + +const getReportStatus = require('../../../../api/report').getReportStatus as jest.Mock; + +const component = { key: 'foo', name: 'Foo' }; + +it('renders', () => { + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); + wrapper.setState({ + loading: false, + status: { + canDownload: true, + canSubscribe: true, + componentFrequency: 'montly', + globalFrequency: 'weekly', + subscribed: true + } + }); + expect(wrapper).toMatchSnapshot(); +}); + +it('fetches status', () => { + getReportStatus.mockClear(); + mount(); + expect(getReportStatus).toBeCalledWith('foo'); +}); diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/SecurityBox-test.tsx b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/SecurityBox-test.tsx new file mode 100644 index 00000000000..b658ce56b8d --- /dev/null +++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/SecurityBox-test.tsx @@ -0,0 +1,31 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { shallow } from 'enzyme'; +import SecurityBox from '../SecurityBox'; + +it('renders', () => { + const measures = { + security_rating: '3', + last_change_on_security_rating: '{"date":"2017-01-02T00:00:00.000Z","value":2}', + security_rating_effort: '{"rating":3,"projects":1}' + }; + expect(shallow()).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/Subscription-test.tsx b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/Subscription-test.tsx new file mode 100644 index 00000000000..4fa146bc940 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/Subscription-test.tsx @@ -0,0 +1,84 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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. + */ +jest.mock('../../../../api/report', () => { + const report = require.requireActual('../../../../api/report'); + report.subscribe = jest.fn(() => Promise.resolve()); + report.unsubscribe = jest.fn(() => Promise.resolve()); + return report; +}); + +import * as React from 'react'; +import { mount, shallow } from 'enzyme'; +import Subscription from '../Subscription'; +import { click } from '../../../../helpers/testUtils'; + +const subscribe = require('../../../../api/report').subscribe as jest.Mock; +const unsubscribe = require('../../../../api/report').unsubscribe as jest.Mock; + +const status = { + canDownload: true, + canSubscribe: true, + componentFrequency: 'montly', + globalFrequency: 'weekly', + subscribed: true +}; + +const currentUser = { email: 'foo@example.com' }; + +beforeEach(() => { + subscribe.mockClear(); + unsubscribe.mockClear(); +}); + +it('renders when subscribed', () => { + expect( + shallow() + ).toMatchSnapshot(); +}); + +it('renders when not subscribed', () => { + expect( + shallow( + + ) + ).toMatchSnapshot(); +}); + +it('renders when no email', () => { + expect( + shallow() + ).toMatchSnapshot(); +}); + +it('changes subscription', async () => { + const wrapper = mount(); + click(wrapper.find('button')); + expect(unsubscribe).toBeCalledWith('foo'); + + await new Promise(setImmediate); + wrapper.update(); + + click(wrapper.find('button')); + expect(subscribe).toBeCalledWith('foo'); +}); diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/Summary-test.tsx b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/Summary-test.tsx new file mode 100644 index 00000000000..0dadfb63e2c --- /dev/null +++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/Summary-test.tsx @@ -0,0 +1,33 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { shallow } from 'enzyme'; +import Summary from '../Summary'; + +it('renders', () => { + expect( + shallow( + + ) + ).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/WorstProjects-test.tsx b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/WorstProjects-test.tsx new file mode 100644 index 00000000000..e4eed115067 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/WorstProjects-test.tsx @@ -0,0 +1,68 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { shallow } from 'enzyme'; +import WorstProjects from '../WorstProjects'; + +it('renders', () => { + const subComponents = [ + { + key: 'foo', + measures: { + releasability_rating: '3', + reliability_rating: '2', + security_rating: '1', + sqale_rating: '4', + ncloc: '200' + }, + name: 'Foo', + qualifier: 'SVW' + }, + { + key: 'bar', + measures: { + alert_status: 'ERROR', + reliability_rating: '2', + security_rating: '1', + sqale_rating: '4', + ncloc: '100' + }, + name: 'Bar', + qualifier: 'TRK', + refKey: 'barbar' + }, + { + key: 'baz', + measures: { + alert_status: 'WARN', + reliability_rating: '2', + security_rating: '1', + sqale_rating: '4', + ncloc: '150' + }, + name: 'Baz', + qualifier: 'TRK', + refKey: 'bazbaz' + } + ]; + expect( + shallow() + ).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Activity-test.tsx.snap b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Activity-test.tsx.snap new file mode 100644 index 00000000000..1df37032691 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Activity-test.tsx.snap @@ -0,0 +1,42 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders 1`] = ` +
    +
    +

    + project_activity.page +

    +
    + +
    +`; diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/App-test.tsx.snap b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/App-test.tsx.snap new file mode 100644 index 00000000000..4a1077741b9 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/App-test.tsx.snap @@ -0,0 +1,65 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders 1`] = ` +
    +
    +
    +
    + + + + +
    + +
    + +
    +
    +`; diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Effort-test.tsx.snap b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Effort-test.tsx.snap new file mode 100644 index 00000000000..79fce89982d --- /dev/null +++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Effort-test.tsx.snap @@ -0,0 +1,50 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders 1`] = ` +
    + + + + + projects_ + + , + "rating": , + } + } + /> +
    +`; diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/HistoryButtonLink-test.tsx.snap b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/HistoryButtonLink-test.tsx.snap new file mode 100644 index 00000000000..d64b7c80c4e --- /dev/null +++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/HistoryButtonLink-test.tsx.snap @@ -0,0 +1,24 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders 1`] = ` + + + +`; diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/MainRating-test.tsx.snap b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/MainRating-test.tsx.snap new file mode 100644 index 00000000000..d8cc0a6fd99 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/MainRating-test.tsx.snap @@ -0,0 +1,24 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders 1`] = ` + + + +`; diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/MaintainabilityBox-test.tsx.snap b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/MaintainabilityBox-test.tsx.snap new file mode 100644 index 00000000000..874b02296e2 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/MaintainabilityBox-test.tsx.snap @@ -0,0 +1,39 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders 1`] = ` +
    +

    + metric_domain.Maintainability + + +

    + + + +
    +`; diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/MeasuresButtonLink-test.tsx.snap b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/MeasuresButtonLink-test.tsx.snap new file mode 100644 index 00000000000..5b7f1c4bb23 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/MeasuresButtonLink-test.tsx.snap @@ -0,0 +1,23 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders 1`] = ` + + + +`; diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/RatingFreshness-test.tsx.snap b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/RatingFreshness-test.tsx.snap new file mode 100644 index 00000000000..ca9124758dc --- /dev/null +++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/RatingFreshness-test.tsx.snap @@ -0,0 +1,31 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders 1`] = ` +
    + , + "rating": , + } + } + /> +
    +`; + +exports[`renders empty 1`] = ` +
    +   +
    +`; diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/ReleasabilityBox-test.tsx.snap b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/ReleasabilityBox-test.tsx.snap new file mode 100644 index 00000000000..3db58b7d539 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/ReleasabilityBox-test.tsx.snap @@ -0,0 +1,75 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders 1`] = ` +
    +

    + metric_domain.Releasability +

    + + + + +
    + + + + + projects + + + + + metric.level.ERROR + +
    +
    +`; diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/ReliabilityBox-test.tsx.snap b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/ReliabilityBox-test.tsx.snap new file mode 100644 index 00000000000..e5b35707fa0 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/ReliabilityBox-test.tsx.snap @@ -0,0 +1,39 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders 1`] = ` +
    +

    + metric_domain.Reliability + + +

    + + + +
    +`; diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Report-test.tsx.snap b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Report-test.tsx.snap new file mode 100644 index 00000000000..76b6a4056ab --- /dev/null +++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Report-test.tsx.snap @@ -0,0 +1,65 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders 1`] = ` +
    +
    +

    + report.page +

    +
    + +
    +`; + +exports[`renders 2`] = ` +
    +
    +

    + report.page +

    +
    +
    + report.can_download + +
    + +
    +`; diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/SecurityBox-test.tsx.snap b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/SecurityBox-test.tsx.snap new file mode 100644 index 00000000000..b2966238d59 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/SecurityBox-test.tsx.snap @@ -0,0 +1,39 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders 1`] = ` +
    +

    + metric_domain.Security + + +

    + + + +
    +`; diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Subscription-test.tsx.snap b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Subscription-test.tsx.snap new file mode 100644 index 00000000000..03ba0e5f118 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Subscription-test.tsx.snap @@ -0,0 +1,63 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders when no email 1`] = ` +
    +

    + report.no_email_to_subscribe +

    +
    +`; + +exports[`renders when not subscribed 1`] = ` +
    +
    +

    + report.unsubscribed.report.frequency.montly.effective +

    + +
    +
    +`; + +exports[`renders when subscribed 1`] = ` +
    +
    +
    + +
    + report.subscribed.report.frequency.montly.effective +
    +
    + +
    +
    +`; diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Summary-test.tsx.snap b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Summary-test.tsx.snap new file mode 100644 index 00000000000..8f934778b8d --- /dev/null +++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Summary-test.tsx.snap @@ -0,0 +1,96 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders 1`] = ` +
    +
    + blabla +
    +
      +
    • +
      + + + +
      + projects +
    • +
    • +
      + + + +
      + metric.ncloc.name +
    • +
    +
    + +
    +
    +`; diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/WorstProjects-test.tsx.snap b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/WorstProjects-test.tsx.snap new file mode 100644 index 00000000000..1eaa2c0bdb9 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/WorstProjects-test.tsx.snap @@ -0,0 +1,395 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders 1`] = ` +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +   + + metric_domain.Releasability + + metric_domain.Reliability + + metric_domain.Security + + metric_domain.Maintainability + + metric.ncloc.name +
    + + + + Foo + + + + + + + + + + + + + + + + +
    + + + + Bar + + + + + + + + + + + + + + + + +
    + + + + Baz + + + + + + + + + + + + + + + + +
    +
    +`; diff --git a/server/sonar-web/src/main/js/apps/portfolio/routes.ts b/server/sonar-web/src/main/js/apps/portfolio/routes.ts new file mode 100644 index 00000000000..520805ebac5 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/portfolio/routes.ts @@ -0,0 +1,30 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { RouterState, IndexRouteProps } from 'react-router'; + +const routes = [ + { + getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) { + import('./components/App').then(i => callback(null, { component: (i as any).default })); + } + } +]; + +export default routes; diff --git a/server/sonar-web/src/main/js/apps/portfolio/styles.css b/server/sonar-web/src/main/js/apps/portfolio/styles.css new file mode 100644 index 00000000000..6214d02e657 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/portfolio/styles.css @@ -0,0 +1,95 @@ +.portfolio-measure-secondary-value { + line-height: 1.4; + margin-bottom: 4px; + font-size: 24px; + font-weight: 300; +} + +.portfolio-grid { + position: relative; + z-index: 10; + display: flex; + height: 80px; + justify-content: space-around; + align-items: center; +} + +.portfolio-grid > li { + vertical-align: top; + width: 50%; + text-align: center; +} + +.portfolio-grid > li.text-middle { + vertical-align: middle; +} + +.portfolio-freshness { + line-height: 24px; + margin-top: 12px; + color: #777; + font-size: 12px; + white-space: nowrap; +} + +.portfolio-effort { + margin-top: 12px; + padding-top: 12px; + border-top: 1px solid #e6e6e6; +} + +.portfolio-boxes { + display: flex; + justify-content: space-between; + align-items: stretch; + margin-bottom: 20px; + padding: 15px 0; + border: 1px solid #e6e6e6; + background-color: #fff; +} + +.portfolio-box { + position: relative; + width: 25%; + padding: 0 5px; + border-radius: 3px; + box-sizing: border-box; + text-align: center; +} + +.portfolio-box-title { + margin-bottom: 25px; + font-size: 16px; +} + +.portfolio-box-title > .button-small > svg { + margin-top: 0; +} + +.portfolio-box-rating, +.portfolio-box-rating .rating { + display: block; + width: 120px; + height: 120px; + line-height: 120px; +} + +.portfolio-box-rating { + margin: 0 auto; + border: none; +} + +.portfolio-box-rating .rating { + border-radius: 120px; + font-size: 60px; + text-align: center; +} + +.portfolio-sub-components table.data > thead > tr > th { + font-size: 13px; + text-transform: none; +} + +.portfolio-sub-components-cell { + width: 90px; +} diff --git a/server/sonar-web/src/main/js/apps/portfolio/types.ts b/server/sonar-web/src/main/js/apps/portfolio/types.ts new file mode 100644 index 00000000000..b9cdc0ba7fe --- /dev/null +++ b/server/sonar-web/src/main/js/apps/portfolio/types.ts @@ -0,0 +1,26 @@ +/* +* SonarQube +* Copyright (C) 2009-2016 SonarSource SA +* mailto:contact 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. + */ +export interface SubComponent { + key: string; + measures: { [key: string]: string | undefined }; + name: string; + refKey?: string; + qualifier: string; +} diff --git a/server/sonar-web/src/main/js/apps/portfolio/utils.ts b/server/sonar-web/src/main/js/apps/portfolio/utils.ts new file mode 100644 index 00000000000..37451bf9e05 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/portfolio/utils.ts @@ -0,0 +1,92 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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. + */ +export function getNextRating(rating: number): number | undefined { + return rating > 1 ? rating - 1 : undefined; +} + +function getWorstSeverity(data: string): { severity: string; count: number } | undefined { + const SEVERITY_ORDER = ['BLOCKER', 'CRITICAL', 'MAJOR', 'MINOR', 'INFO']; + + const severities: { [key: string]: number } = {}; + data.split(';').forEach(equality => { + const [key, count] = equality.split('='); + severities[key] = Number(count); + }); + + for (let i = 0; i < SEVERITY_ORDER.length; i++) { + const count = severities[SEVERITY_ORDER[i]]; + if (count > 0) { + return { severity: SEVERITY_ORDER[i], count }; + } + } + + return undefined; +} + +export function getEffortToNextRating( + measures: Array<{ metric: { key: string }; value: string }>, + metricKey: string +) { + const measure = measures.find(measure => measure.metric.key === metricKey); + if (!measure) { + return undefined; + } + return getWorstSeverity(measure.value); +} + +export const PORTFOLIO_METRICS = [ + 'projects', + 'ncloc', + 'ncloc_language_distribution', + + 'releasability_rating', + 'releasability_effort', + + 'sqale_rating', + 'maintainability_rating_effort', + + 'reliability_rating', + 'reliability_rating_effort', + + 'security_rating', + 'security_rating_effort', + + 'last_change_on_releasability_rating', + 'last_change_on_maintainability_rating', + 'last_change_on_security_rating', + 'last_change_on_reliability_rating' +]; + +export const SUB_COMPONENTS_METRICS = [ + 'ncloc', + 'releasability_rating', + 'security_rating', + 'reliability_rating', + 'sqale_rating', + 'alert_status' +]; + +export function convertMeasures(measures: Array<{ metric: string; value?: string }>) { + const result: { [key: string]: string | undefined } = {}; + measures.forEach(measure => { + result[measure.metric] = measure.value; + }); + return result; +} diff --git a/server/sonar-web/src/main/js/apps/projectActivity/__tests__/utils-test.js b/server/sonar-web/src/main/js/apps/projectActivity/__tests__/utils-test.js index d60213ed43e..960b925b21e 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/__tests__/utils-test.js +++ b/server/sonar-web/src/main/js/apps/projectActivity/__tests__/utils-test.js @@ -221,8 +221,8 @@ describe('parseQuery', () => { expect( utils.parseQuery({ from: '2017-04-27T08:21:32.000Z', - id: 'foo', - custom_metrics: 'foo,bar,baz' + custom_metrics: 'foo,bar,baz', + id: 'foo' }) ).toEqual(QUERY); }); diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphHistory.js b/server/sonar-web/src/main/js/apps/projectActivity/components/GraphHistory.js index 11cbe73667d..29fe837acfa 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphHistory.js +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/GraphHistory.js @@ -18,7 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import React from 'react'; -import { AutoSizer } from 'react-virtualized'; +import AutoSizer from 'react-virtualized/dist/commonjs/AutoSizer'; import AdvancedTimeline from '../../../components/charts/AdvancedTimeline'; import GraphsTooltips from './GraphsTooltips'; import GraphsLegendCustom from './GraphsLegendCustom'; diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsHistory.js b/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsHistory.js index 2b9d710508a..9b861ce2420 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsHistory.js +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsHistory.js @@ -35,7 +35,7 @@ type Props = { graphs: Array>, graphEndDate: ?Date, graphStartDate: ?Date, - leakPeriodDate: Date, + leakPeriodDate?: Date, loading: boolean, measuresHistory: Array, removeCustomMetric: (metric: string) => void, diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsZoom.js b/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsZoom.js index 0160d85a65b..d9b69a1ccf7 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsZoom.js +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsZoom.js @@ -19,7 +19,7 @@ */ // @flow import React from 'react'; -import { AutoSizer } from 'react-virtualized'; +import AutoSizer from 'react-virtualized/dist/commonjs/AutoSizer'; import ZoomTimeLine from '../../../components/charts/ZoomTimeLine'; import { hasHistoryData } from '../utils'; /*:: import type { Serie } from '../../../components/charts/AdvancedTimeline'; */ @@ -28,7 +28,7 @@ import { hasHistoryData } from '../utils'; type Props = { graphEndDate: ?Date, graphStartDate: ?Date, - leakPeriodDate: Date, + leakPeriodDate?: Date, loading: boolean, metricsType: string, series: Array, diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysesList.js b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysesList.js index 8b6b60adb9c..efc78dbe000 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysesList.js +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysesList.js @@ -170,12 +170,13 @@ export default class ProjectActivityAnalysesList extends React.PureComponent { const selectedDate = this.props.query.selectedDate ? this.props.query.selectedDate.valueOf() : null; + return (
      (this.scrollContainer = element)} - style={{ paddingTop: this.props.project.qualifier === 'APP' ? undefined : 52 }}> + style={{ paddingTop: this.props.project.qualifier === 'TRK' ? 52 : undefined }}> {byVersionByDay.map((version, idx) => { const days = Object.keys(version.byDay); if (days.length <= 0) { @@ -205,7 +206,7 @@ export default class ProjectActivityAnalysesList extends React.PureComponent { addVersion={this.props.addVersion} analysis={analysis} canAdmin={this.props.canAdmin} - canCreateVersion={this.props.project.qualifier !== 'APP'} + canCreateVersion={this.props.project.qualifier === 'TRK'} changeEvent={this.props.changeEvent} deleteAnalysis={this.props.deleteAnalysis} deleteEvent={this.props.deleteEvent} diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.js b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.js index e0b70d68e38..cceffeabb0f 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.js +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.js @@ -42,7 +42,7 @@ type Props = { project: { configuration?: { showHistory: boolean }, key: string, - leakPeriodDate: string, + leakPeriodDate?: string, qualifier: string }, metrics: Array, @@ -55,7 +55,9 @@ type Props = { export default function ProjectActivityApp(props /*: Props */) { const { analyses, measuresHistory, query } = props; const { configuration } = props.project; - const canAdmin = configuration ? configuration.showHistory : false; + const canAdmin = + (props.project.qualifier === 'TRK' || props.project.qualifier === 'APP') && + (configuration ? configuration.showHistory : false); return (
      @@ -89,7 +91,9 @@ export default function ProjectActivityApp(props /*: Props */) {
      , + configuration?: { showHistory: boolean }, + key: string, + leakPeriodDate?: string, + qualifier: string +}; + type Props = { branch?: {}, location: { pathname: string, query: RawQuery }, - component: { - configuration?: { showHistory: boolean }, - key: string, - leakPeriodDate: string, - qualifier: string - } + component: Component }; */ @@ -106,7 +109,7 @@ export default class ProjectActivityAppContainer extends React.PureComponent { } }); } else { - this.firstLoadData(this.state.query); + this.firstLoadData(this.state.query, this.props.component); } } @@ -117,7 +120,7 @@ export default class ProjectActivityAppContainer extends React.PureComponent { if (this.state.initialized) { this.updateGraphData(query.graph, query.customMetrics); } else { - this.firstLoadData(query); + this.firstLoadData(query, nextProps.component); } } this.setState({ query }); @@ -177,7 +180,7 @@ export default class ProjectActivityAppContainer extends React.PureComponent { branch: this.props.branch && getBranchName(this.props.branch) }; return api - .getProjectActivity({ ...parameters, ...additional }) + .getProjectActivity({ ...additional, ...parameters }) .then(({ analyses, paging }) => ({ analyses: analyses.map(analysis => ({ ...analysis, date: parseDate(analysis.date) })), paging @@ -227,10 +230,22 @@ export default class ProjectActivityAppContainer extends React.PureComponent { }); }; - firstLoadData(query /*: Query */) { + getTopLevelComponent = (component /*: Component */) => { + let current = component.breadcrumbs.length - 1; + while ( + current > 0 && + !['TRK', 'VW', 'APP'].includes(component.breadcrumbs[current].qualifier) + ) { + current--; + } + return component.breadcrumbs[current].key; + }; + + firstLoadData(query /*: Query */, component /*: Component */) { const graphMetrics = getHistoryMetrics(query.graph, query.customMetrics); + const topLevelComponent = this.getTopLevelComponent(component); Promise.all([ - this.fetchActivity(query.project, 1, 100, serializeQuery(query)), + this.fetchActivity(topLevelComponent, 1, 100, serializeQuery(query)), this.fetchMetrics(), this.fetchMeasuresHistory(graphMetrics) ]).then( @@ -246,7 +261,7 @@ export default class ProjectActivityAppContainer extends React.PureComponent { paging: response[0].paging }); - this.loadAllActivities(query.project).then(({ analyses, paging }) => { + this.loadAllActivities(topLevelComponent).then(({ analyses, paging }) => { if (this.mounted) { this.setState({ analyses, diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityGraphs.js b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityGraphs.js index 51f4df10221..cd1cfd900eb 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityGraphs.js +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityGraphs.js @@ -40,7 +40,7 @@ import { /*:: type Props = { analyses: Array, - leakPeriodDate: Date, + leakPeriodDate?: Date, loading: boolean, measuresHistory: Array, metrics: Array, diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityPageHeader.js b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityPageHeader.js index 57183279074..f4f9a9a7874 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityPageHeader.js +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityPageHeader.js @@ -54,17 +54,19 @@ export default class ProjectActivityPageHeader extends React.PureComponent { return (
      - + )} @@ -57,11 +57,7 @@ export default function ProjectCardLeakMeasures({ measures }: Props) { @@ -80,7 +76,7 @@ export default function ProjectCardLeakMeasures({ measures }: Props) { @@ -98,7 +94,7 @@ export default function ProjectCardLeakMeasures({ measures }: Props) {
      @@ -112,11 +108,7 @@ export default function ProjectCardLeakMeasures({ measures }: Props) {
      @@ -132,7 +124,7 @@ export default function ProjectCardLeakMeasures({ measures }: Props) {
      diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverallMeasures.tsx b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverallMeasures.tsx index 29b76358034..d0ef2f047ce 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverallMeasures.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverallMeasures.tsx @@ -76,7 +76,7 @@ export default function ProjectCardOverallMeasures({ measures }: Props) { )} @@ -95,11 +95,7 @@ export default function ProjectCardOverallMeasures({ measures }: Props) { )} @@ -119,7 +115,7 @@ export default function ProjectCardOverallMeasures({ measures }: Props) { diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLeakMeasures-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLeakMeasures-test.tsx.snap index 1c98e6df82e..9d82c53b036 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLeakMeasures-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLeakMeasures-test.tsx.snap @@ -21,7 +21,6 @@ exports[`should render correctly with all data 1`] = ` "leak": "8", "metric": Object { "key": "new_bugs", - "name": "new_bugs", "type": "SHORT_INT", }, } @@ -58,7 +57,6 @@ exports[`should render correctly with all data 1`] = ` "leak": "2", "metric": Object { "key": "new_vulnerabilities", - "name": "new_vulnerabilities", "type": "SHORT_INT", }, } @@ -95,7 +93,6 @@ exports[`should render correctly with all data 1`] = ` "leak": "0", "metric": Object { "key": "new_code_smells", - "name": "new_code_smells", "type": "SHORT_INT", }, } @@ -131,7 +128,6 @@ exports[`should render correctly with all data 1`] = ` "leak": "26.55", "metric": Object { "key": "new_coverage", - "name": "new_coverage", "type": "PERCENT", }, } @@ -161,7 +157,6 @@ exports[`should render correctly with all data 1`] = ` "leak": "0.55", "metric": Object { "key": "new_duplicated_lines_density", - "name": "new_duplicated_lines_density", "type": "PERCENT", }, } @@ -191,7 +186,6 @@ exports[`should render correctly with all data 1`] = ` "leak": "87", "metric": Object { "key": "new_lines", - "name": "new_lines", "type": "SHORT_INT", }, } @@ -229,7 +223,6 @@ exports[`should render no data style new coverage, new duplications and new line "leak": "8", "metric": Object { "key": "new_bugs", - "name": "new_bugs", "type": "SHORT_INT", }, } @@ -266,7 +259,6 @@ exports[`should render no data style new coverage, new duplications and new line "leak": "2", "metric": Object { "key": "new_vulnerabilities", - "name": "new_vulnerabilities", "type": "SHORT_INT", }, } @@ -303,7 +295,6 @@ exports[`should render no data style new coverage, new duplications and new line "leak": "0", "metric": Object { "key": "new_code_smells", - "name": "new_code_smells", "type": "SHORT_INT", }, } @@ -339,7 +330,6 @@ exports[`should render no data style new coverage, new duplications and new line "leak": undefined, "metric": Object { "key": "new_coverage", - "name": "new_coverage", "type": "PERCENT", }, } @@ -369,7 +359,6 @@ exports[`should render no data style new coverage, new duplications and new line "leak": undefined, "metric": Object { "key": "new_duplicated_lines_density", - "name": "new_duplicated_lines_density", "type": "PERCENT", }, } @@ -399,7 +388,6 @@ exports[`should render no data style new coverage, new duplications and new line "leak": undefined, "metric": Object { "key": "new_lines", - "name": "new_lines", "type": "SHORT_INT", }, } diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardOverallMeasures-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardOverallMeasures-test.tsx.snap index d1c011628f1..341a634b69a 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardOverallMeasures-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardOverallMeasures-test.tsx.snap @@ -16,7 +16,6 @@ exports[`should not render coverage 1`] = ` Object { "metric": Object { "key": "coverage", - "name": "coverage", "type": "PERCENT", }, "value": undefined, @@ -49,7 +48,6 @@ exports[`should not render duplications 1`] = ` Object { "metric": Object { "key": "duplicated_lines_density", - "name": "duplicated_lines_density", "type": "PERCENT", }, "value": undefined, @@ -155,7 +153,6 @@ exports[`should render correctly with all data 1`] = ` Object { "metric": Object { "key": "coverage", - "name": "coverage", "type": "PERCENT", }, "value": "88.3", @@ -192,7 +189,6 @@ exports[`should render correctly with all data 1`] = ` Object { "metric": Object { "key": "duplicated_lines_density", - "name": "duplicated_lines_density", "type": "PERCENT", }, "value": "9.8", @@ -229,7 +225,6 @@ exports[`should render correctly with all data 1`] = ` Object { "metric": Object { "key": "ncloc", - "name": "ncloc", "type": "SHORT_INT", }, "value": "2053", @@ -270,7 +265,6 @@ exports[`should render ncloc correctly 1`] = ` Object { "metric": Object { "key": "ncloc", - "name": "ncloc", "type": "SHORT_INT", }, "value": "16549887", diff --git a/server/sonar-web/src/main/js/components/SourceViewer/views/measures-overlay.js b/server/sonar-web/src/main/js/components/SourceViewer/views/measures-overlay.js index e9db36ae7b7..e2ab2a305cd 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/views/measures-overlay.js +++ b/server/sonar-web/src/main/js/components/SourceViewer/views/measures-overlay.js @@ -152,22 +152,21 @@ export default ModalView.extend({ .filter(metric => metric.type !== 'DATA' && !metric.hidden) .map(metric => metric.key); - return getMeasures( - this.options.component.key, - metricsToRequest, - this.options.branch - ).then(measures => { - let nextMeasures = this.options.component.measures || {}; - measures.forEach(measure => { - const metric = metrics.find(metric => metric.key === measure.metric); - nextMeasures[metric.key] = formatMeasure(measure.value, metric.type); - nextMeasures[metric.key + '_raw'] = measure.value; - metric.value = nextMeasures[metric.key]; - }); - nextMeasures = this.calcAdditionalMeasures(nextMeasures); - this.measures = nextMeasures; - this.measuresToDisplay = this.prepareMetrics(metrics); - }); + return getMeasures(this.options.component.key, metricsToRequest, this.options.branch).then( + measures => { + let nextMeasures = this.options.component.measures || {}; + measures.forEach(measure => { + const metric = metrics.find(metric => metric.key === measure.metric); + nextMeasures[metric.key] = formatMeasure(measure.value, metric.type); + nextMeasures[metric.key + '_raw'] = measure.value; + metric.value = nextMeasures[metric.key]; + }); + nextMeasures = this.calcAdditionalMeasures(nextMeasures); + this.measures = nextMeasures; + this.measuresToDisplay = this.prepareMetrics(metrics); + }, + () => {} + ); }); }, diff --git a/server/sonar-web/src/main/js/components/charts/LanguageDistribution.js b/server/sonar-web/src/main/js/components/charts/LanguageDistribution.js deleted file mode 100644 index 60901a97d98..00000000000 --- a/server/sonar-web/src/main/js/components/charts/LanguageDistribution.js +++ /dev/null @@ -1,89 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2017 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 { find, sortBy } from 'lodash'; -import PropTypes from 'prop-types'; -import React from 'react'; -import { Histogram } from './histogram'; -import { formatMeasure } from '../../helpers/measures'; -import { getLanguages } from '../../api/languages'; -import { translate } from '../../helpers/l10n'; - -export default class LanguageDistribution extends React.PureComponent { - static propTypes = { - alignTicks: PropTypes.bool, - distribution: PropTypes.string.isRequired - }; - - state = {}; - - componentDidMount() { - this.mounted = true; - this.requestLanguages(); - } - - componentWillUnmount() { - this.mounted = false; - } - - requestLanguages() { - getLanguages().then(languages => { - if (this.mounted) { - this.setState({ languages }); - } - }); - } - - getLanguageName(langKey) { - if (this.state.languages) { - const lang = find(this.state.languages, { key: langKey }); - return lang ? lang.name : translate('unknown'); - } else { - return langKey; - } - } - - cutLanguageName(name) { - return name.length > 10 ? `${name.substr(0, 7)}...` : name; - } - - render() { - let data = this.props.distribution.split(';').map((point, index) => { - const tokens = point.split('='); - return { x: parseInt(tokens[1], 10), y: index, value: tokens[0] }; - }); - - data = sortBy(data, d => -d.x); - - const yTicks = data.map(point => this.getLanguageName(point.value)).map(this.cutLanguageName); - const yValues = data.map(point => formatMeasure(point.x, 'SHORT_INT')); - - return ( - - ); - } -} diff --git a/server/sonar-web/src/main/js/components/charts/LanguageDistribution.tsx b/server/sonar-web/src/main/js/components/charts/LanguageDistribution.tsx new file mode 100644 index 00000000000..6766372207f --- /dev/null +++ b/server/sonar-web/src/main/js/components/charts/LanguageDistribution.tsx @@ -0,0 +1,64 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * 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. + * + * 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 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 { find, sortBy } from 'lodash'; +import { Histogram } from './histogram'; +import { formatMeasure } from '../../helpers/measures'; +import { Language } from '../../api/languages'; +import { translate } from '../../helpers/l10n'; + +interface Props { + alignTicks?: boolean; + distribution: string; + languages?: Language[]; +} + +export default function LanguageDistribution(props: Props) { + let data = props.distribution.split(';').map((point, index) => { + const tokens = point.split('='); + return { x: parseInt(tokens[1], 10), y: index, value: tokens[0] }; + }); + + data = sortBy(data, d => -d.x); + + const yTicks = data.map(point => getLanguageName(point.value)).map(cutLanguageName); + const yValues = data.map(point => formatMeasure(point.x, 'SHORT_INT')); + + return ( + + ); + + function getLanguageName(langKey: string) { + const lang = find(props.languages, { key: langKey }); + return lang ? lang.name : translate('unknown'); + } + + function cutLanguageName(name: string) { + return name.length > 10 ? `${name.substr(0, 7)}...` : name; + } +} diff --git a/server/sonar-web/src/main/js/app/components/extensions/PortfolioDashboard.tsx b/server/sonar-web/src/main/js/components/charts/LanguageDistributionContainer.tsx similarity index 67% rename from server/sonar-web/src/main/js/app/components/extensions/PortfolioDashboard.tsx rename to server/sonar-web/src/main/js/components/charts/LanguageDistributionContainer.tsx index 2bb640dae73..1290b077484 100644 --- a/server/sonar-web/src/main/js/app/components/extensions/PortfolioDashboard.tsx +++ b/server/sonar-web/src/main/js/components/charts/LanguageDistributionContainer.tsx @@ -17,20 +17,12 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import * as React from 'react'; -import ProjectPageExtension from './ProjectPageExtension'; -import { Component } from '../../types'; +import { connect } from 'react-redux'; +import { getLanguages } from '../../store/rootReducer'; +import LanguageDistribution from './LanguageDistribution'; -interface Props { - component: Component; - location: { query: { id: string } }; -} +const mapStateToProps = (state: any) => ({ + languages: getLanguages(state) +}); -export default function PortfolioDashboard(props: Props) { - return ( - - ); -} +export default connect(mapStateToProps)(LanguageDistribution); diff --git a/server/sonar-web/src/main/js/components/charts/ZoomTimeLine.js b/server/sonar-web/src/main/js/components/charts/ZoomTimeLine.js index ec1957e2f5b..e7cbd89f6e8 100644 --- a/server/sonar-web/src/main/js/components/charts/ZoomTimeLine.js +++ b/server/sonar-web/src/main/js/components/charts/ZoomTimeLine.js @@ -38,7 +38,7 @@ type Props = { endDate: ?Date, height: number, width: number, - leakPeriodDate: Date, + leakPeriodDate?: Date, padding: Array, series: Array, showAreas?: boolean, @@ -394,6 +394,7 @@ export default class ZoomTimeLine extends React.PureComponent { } const { xScale, yScale } = this.getScales(); + return ( diff --git a/server/sonar-web/src/main/js/components/icons-components/BubblesIcon.js b/server/sonar-web/src/main/js/components/icons-components/BubblesIcon.tsx similarity index 88% rename from server/sonar-web/src/main/js/components/icons-components/BubblesIcon.js rename to server/sonar-web/src/main/js/components/icons-components/BubblesIcon.tsx index 5c0033d036d..2372a606683 100644 --- a/server/sonar-web/src/main/js/components/icons-components/BubblesIcon.js +++ b/server/sonar-web/src/main/js/components/icons-components/BubblesIcon.tsx @@ -17,14 +17,14 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; +import * as React from 'react'; -/*:: -type Props = { className?: string, size?: number }; -*/ +interface Props { + className?: string; + size?: number; +} -export default function BubblesIcon({ className, size = 16 } /*: Props */) { +export default function BubblesIcon({ className, size = 16 }: Props) { return ( {'–'}; + } + const metric = measure.metric; const value = isDiffMetric(metric.key) ? measure.leak : measure.value; @@ -44,7 +48,7 @@ export default function Measure({ className, decimals, measure }: Props) { if (metric.type !== 'RATING') { const formattedValue = isDiffMetric(metric.key) - ? formatLeak(measure.leak, metric, { decimals }) + ? formatLeak(measure.leak, metric.key, metric.type, { decimals }) : formatMeasure(measure.value, metric.type, { decimals }); return {formattedValue != null ? formattedValue : '–'}; } diff --git a/server/sonar-web/src/main/js/components/measure/utils.ts b/server/sonar-web/src/main/js/components/measure/utils.ts index 017e709491d..36494141589 100644 --- a/server/sonar-web/src/main/js/components/measure/utils.ts +++ b/server/sonar-web/src/main/js/components/measure/utils.ts @@ -37,7 +37,7 @@ export interface Measure extends MeasureIntern { } export interface MeasureEnhanced extends MeasureIntern { - metric: Metric; + metric: { key: string; type: string }; leak?: string | undefined | undefined; } @@ -53,11 +53,16 @@ export function enhanceMeasure( }; } -export function formatLeak(value: string | undefined, metric: Metric, options: any): string { - if (isDiffMetric(metric.key)) { - return formatMeasure(value, metric.type, options); +export function formatLeak( + value: string | undefined, + metricKey: string, + metricType: string, + options: any +): string { + if (isDiffMetric(metricKey)) { + return formatMeasure(value, metricType, options); } else { - return formatMeasureVariation(value, metric.type, options); + return formatMeasureVariation(value, metricType, options); } } diff --git a/server/sonar-web/src/main/js/apps/overview/events/PreviewGraph.js b/server/sonar-web/src/main/js/components/preview-graph/PreviewGraph.js similarity index 89% rename from server/sonar-web/src/main/js/apps/overview/events/PreviewGraph.js rename to server/sonar-web/src/main/js/components/preview-graph/PreviewGraph.js index 69a7f843797..c7544325517 100644 --- a/server/sonar-web/src/main/js/apps/overview/events/PreviewGraph.js +++ b/server/sonar-web/src/main/js/components/preview-graph/PreviewGraph.js @@ -20,8 +20,9 @@ // @flow import React from 'react'; import { minBy } from 'lodash'; -import { AutoSizer } from 'react-virtualized'; -import AdvancedTimeline from '../../../components/charts/AdvancedTimeline'; +import * as PropTypes from 'prop-types'; +import AutoSizer from 'react-virtualized/dist/commonjs/AutoSizer'; +import AdvancedTimeline from '../charts/AdvancedTimeline'; import PreviewGraphTooltips from './PreviewGraphTooltips'; import { DEFAULT_GRAPH, @@ -30,11 +31,11 @@ import { getSeriesMetricType, hasHistoryDataValue, splitSeriesInGraphs -} from '../../projectActivity/utils'; -import { getCustomGraph, getGraph } from '../../../helpers/storage'; -import { formatMeasure, getShortType } from '../../../helpers/measures'; -/*:: import type { Serie } from '../../../components/charts/AdvancedTimeline'; */ -/*:: import type { History, Metric } from '../types'; */ +} from '../../apps/projectActivity/utils'; +import { getCustomGraph, getGraph } from '../../helpers/storage'; +import { formatMeasure, getShortType } from '../../helpers/measures'; +/*:: import type { Serie } from '../charts/AdvancedTimeline'; */ +/*:: import type { History, Metric } from '../../apps/overview/types'; */ /*:: type Props = { @@ -42,7 +43,7 @@ type Props = { history: ?History, metrics: Array, project: string, - router: { push: ({ pathname: string, query?: {} }) => void } + renderWhenEmpty?: () => void }; */ @@ -65,6 +66,10 @@ export default class PreviewGraph extends React.PureComponent { /*:: props: Props; */ /*:: state: State; */ + static contextTypes = { + router: PropTypes.object + }; + constructor(props /*: Props */) { super(props); const graph = getGraph(); @@ -137,7 +142,7 @@ export default class PreviewGraph extends React.PureComponent { }; handleClick = () => { - this.props.router.push({ + this.context.router.push({ pathname: '/project/activity', query: { id: this.props.project, branch: this.props.branch } }); @@ -192,7 +197,7 @@ export default class PreviewGraph extends React.PureComponent { render() { const { series } = this.state; if (!hasHistoryDataValue(series)) { - return null; + return this.props.renderWhenEmpty ? this.props.renderWhenEmpty() : null; } return ( diff --git a/server/sonar-web/src/main/js/apps/overview/events/PreviewGraphTooltips.js b/server/sonar-web/src/main/js/components/preview-graph/PreviewGraphTooltips.js similarity index 92% rename from server/sonar-web/src/main/js/apps/overview/events/PreviewGraphTooltips.js rename to server/sonar-web/src/main/js/components/preview-graph/PreviewGraphTooltips.js index c1a898a3bf1..f5e122ac2a1 100644 --- a/server/sonar-web/src/main/js/apps/overview/events/PreviewGraphTooltips.js +++ b/server/sonar-web/src/main/js/components/preview-graph/PreviewGraphTooltips.js @@ -18,11 +18,11 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import React from 'react'; -import BubblePopup from '../../../components/common/BubblePopup'; -import DateFormatter from '../../../components/intl/DateFormatter'; +import BubblePopup from '../common/BubblePopup'; +import DateFormatter from '../intl/DateFormatter'; import PreviewGraphTooltipsContent from './PreviewGraphTooltipsContent'; /*:: import type { Metric } from '../types'; */ -/*:: import type { Serie } from '../../../components/charts/AdvancedTimeline'; */ +/*:: import type { Serie } from '../charts/AdvancedTimeline'; */ /*:: type Props = { diff --git a/server/sonar-web/src/main/js/apps/overview/events/PreviewGraphTooltipsContent.js b/server/sonar-web/src/main/js/components/preview-graph/PreviewGraphTooltipsContent.js similarity index 94% rename from server/sonar-web/src/main/js/apps/overview/events/PreviewGraphTooltipsContent.js rename to server/sonar-web/src/main/js/components/preview-graph/PreviewGraphTooltipsContent.js index 31f38957d36..ccbce540888 100644 --- a/server/sonar-web/src/main/js/apps/overview/events/PreviewGraphTooltipsContent.js +++ b/server/sonar-web/src/main/js/components/preview-graph/PreviewGraphTooltipsContent.js @@ -19,7 +19,7 @@ */ // @flow import React from 'react'; -import ChartLegendIcon from '../../../components/icons-components/ChartLegendIcon'; +import ChartLegendIcon from '../icons-components/ChartLegendIcon'; /*:: type Props = { diff --git a/server/sonar-web/src/main/js/apps/overview/events/__tests__/PreviewGraphTooltips-test.js b/server/sonar-web/src/main/js/components/preview-graph/__tests__/PreviewGraphTooltips-test.js similarity index 95% rename from server/sonar-web/src/main/js/apps/overview/events/__tests__/PreviewGraphTooltips-test.js rename to server/sonar-web/src/main/js/components/preview-graph/__tests__/PreviewGraphTooltips-test.js index 1b8d08d2ef1..4cf95aee328 100644 --- a/server/sonar-web/src/main/js/apps/overview/events/__tests__/PreviewGraphTooltips-test.js +++ b/server/sonar-web/src/main/js/components/preview-graph/__tests__/PreviewGraphTooltips-test.js @@ -20,8 +20,8 @@ import React from 'react'; import { shallow } from 'enzyme'; import PreviewGraphTooltips from '../PreviewGraphTooltips'; -import { DEFAULT_GRAPH } from '../../../projectActivity/utils'; -import { parseDate } from '../../../../helpers/dates'; +import { DEFAULT_GRAPH } from '../../../apps/projectActivity/utils'; +import { parseDate } from '../../../helpers/dates'; const SERIES_ISSUES = [ { diff --git a/server/sonar-web/src/main/js/apps/overview/events/__tests__/PreviewGraphTooltipsContent-test.js b/server/sonar-web/src/main/js/components/preview-graph/__tests__/PreviewGraphTooltipsContent-test.js similarity index 100% rename from server/sonar-web/src/main/js/apps/overview/events/__tests__/PreviewGraphTooltipsContent-test.js rename to server/sonar-web/src/main/js/components/preview-graph/__tests__/PreviewGraphTooltipsContent-test.js diff --git a/server/sonar-web/src/main/js/apps/overview/events/__tests__/__snapshots__/PreviewGraphTooltips-test.js.snap b/server/sonar-web/src/main/js/components/preview-graph/__tests__/__snapshots__/PreviewGraphTooltips-test.js.snap similarity index 100% rename from server/sonar-web/src/main/js/apps/overview/events/__tests__/__snapshots__/PreviewGraphTooltips-test.js.snap rename to server/sonar-web/src/main/js/components/preview-graph/__tests__/__snapshots__/PreviewGraphTooltips-test.js.snap diff --git a/server/sonar-web/src/main/js/apps/overview/events/__tests__/__snapshots__/PreviewGraphTooltipsContent-test.js.snap b/server/sonar-web/src/main/js/components/preview-graph/__tests__/__snapshots__/PreviewGraphTooltipsContent-test.js.snap similarity index 100% rename from server/sonar-web/src/main/js/apps/overview/events/__tests__/__snapshots__/PreviewGraphTooltipsContent-test.js.snap rename to server/sonar-web/src/main/js/components/preview-graph/__tests__/__snapshots__/PreviewGraphTooltipsContent-test.js.snap diff --git a/server/sonar-web/src/main/js/helpers/testUtils.ts b/server/sonar-web/src/main/js/helpers/testUtils.ts index 507fbc44ffb..01b046039dc 100644 --- a/server/sonar-web/src/main/js/helpers/testUtils.ts +++ b/server/sonar-web/src/main/js/helpers/testUtils.ts @@ -17,7 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { shallow, ShallowRendererProps, ShallowWrapper } from 'enzyme'; +import { shallow, ShallowRendererProps, ShallowWrapper, ReactWrapper } from 'enzyme'; import { IntlProvider } from 'react-intl'; export const mockEvent = { @@ -27,7 +27,7 @@ export const mockEvent = { stopPropagation() {} }; -export function click(element: ShallowWrapper, event = {}): void { +export function click(element: ShallowWrapper | ReactWrapper, event = {}): void { element.simulate('click', { ...mockEvent, ...event }); } diff --git a/server/sonar-web/src/main/js/helpers/urls.ts b/server/sonar-web/src/main/js/helpers/urls.ts index 378d89bb0f9..8deba26fcad 100644 --- a/server/sonar-web/src/main/js/helpers/urls.ts +++ b/server/sonar-web/src/main/js/helpers/urls.ts @@ -84,17 +84,20 @@ export function getComponentDrilldownUrl(componentKey: string, metric: string, b return { pathname: '/component_measures', query: { id: componentKey, metric, branch } }; } +export function getMeasureTreemapUrl(component: string, metric: string, branch?: string) { + return { + pathname: '/component_measures', + query: { id: component, metric, branch, view: 'treemap' } + }; +} + /** * Generate URL for a component's measure history */ -export function getComponentMeasureHistory( - componentKey: string, - metric: string, - branch?: string -): Location { +export function getMeasureHistoryUrl(component: string, metric: string, branch?: string) { return { pathname: '/project/activity', - query: { id: componentKey, graph: 'custom', custom_metrics: metric, branch } + query: { id: component, graph: 'custom', custom_metrics: metric, branch } }; } 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 b448c40e3ec..4b4b86ebc49 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -134,6 +134,7 @@ permalinks=Permalinks plugin=Plugin project=Project projects=Projects +projects_=project(s) projects_management=Projects Management quality_profile=Quality Profile raw=Raw @@ -3176,3 +3177,12 @@ branches.orphan_branches.tooltip=When a target branch of a short-living branch w branches.main_branch=Main Branch branches.settings_hint=To administrate your branches, you have to go to your main branch's {link} tab. branches.settings_hint_tab=Administration > Branches + + +#------------------------------------------------------------------------------ +# +# PORTFOLIOS +# +#------------------------------------------------------------------------------ +portfolio.was_x_y=was {rating} {date} +portfolio.x_in_y={projects} in {rating} -- 2.39.5