diff options
author | Jeremy Davis <jeremy.davis@sonarsource.com> | 2021-12-08 17:47:58 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2021-12-24 20:02:59 +0000 |
commit | eb02c848b5abb29ef43ed4438205af46243faf3d (patch) | |
tree | 717876112e597cbb4dd318603ba4bb6e70cd75f1 /server | |
parent | be00336305414148148f4fc3ff04c58e01de93cb (diff) | |
download | sonarqube-eb02c848b5abb29ef43ed4438205af46243faf3d.tar.gz sonarqube-eb02c848b5abb29ef43ed4438205af46243faf3d.zip |
SONAR-15789 New portfolio overview
Diffstat (limited to 'server')
10 files changed, 173 insertions, 42 deletions
diff --git a/server/sonar-web/config/indexHtmlTemplate.js b/server/sonar-web/config/indexHtmlTemplate.js index 0a0695cfc79..3c399c03861 100644 --- a/server/sonar-web/config/indexHtmlTemplate.js +++ b/server/sonar-web/config/indexHtmlTemplate.js @@ -40,7 +40,7 @@ module.exports = (cssHash, jsHash) => ` <meta name="msapplication-TileImage" content="%WEB_CONTEXT%/mstile-512x512.png" /> <title>%INSTANCE%</title> - <link rel="stylesheet" href="%WEB_CONTEXT%/js/out${cssHash}.css"> + <link rel="stylesheet" href="%WEB_CONTEXT%/js/out${cssHash}.css" /> </head> <body> diff --git a/server/sonar-web/src/main/js/app/components/extensions/PortfolioPage.tsx b/server/sonar-web/src/main/js/app/components/extensions/PortfolioPage.tsx new file mode 100644 index 00000000000..24b95c4f8b5 --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/extensions/PortfolioPage.tsx @@ -0,0 +1,40 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { WithRouterProps } from 'react-router'; +import withIndexationGuard from '../../../components/hoc/withIndexationGuard'; +import { PageContext } from '../indexation/PageUnavailableDueToIndexation'; +import ProjectPageExtension from './ProjectPageExtension'; + +export interface PortfolioPageProps extends WithRouterProps { + component: T.Component; +} + +export function PortfolioPage({ component, location }: PortfolioPageProps) { + return ( + <ProjectPageExtension + component={component} + params={{ pluginKey: 'governance', extensionKey: 'portfolio' }} + location={location} + /> + ); +} + +export default withIndexationGuard(PortfolioPage, PageContext.Portfolios); diff --git a/server/sonar-web/src/main/js/app/components/extensions/__tests__/PortfolioPage-test.tsx b/server/sonar-web/src/main/js/app/components/extensions/__tests__/PortfolioPage-test.tsx new file mode 100644 index 00000000000..4b9c66dc1ec --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/extensions/__tests__/PortfolioPage-test.tsx @@ -0,0 +1,41 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import { shallow } from 'enzyme'; +import * as React from 'react'; +import { mockComponent } from '../../../../helpers/mocks/component'; +import { mockLocation, mockRouter } from '../../../../helpers/testMocks'; +import { PortfolioPage, PortfolioPageProps } from '../PortfolioPage'; + +it('should render correctly', () => { + expect(shallowRender()).toMatchSnapshot(); +}); + +function shallowRender(props?: Partial<PortfolioPageProps>) { + return shallow<PortfolioPageProps>( + <PortfolioPage + component={mockComponent()} + location={mockLocation()} + router={mockRouter()} + routes={[]} + params={{}} + {...props} + /> + ); +} diff --git a/server/sonar-web/src/main/js/app/components/extensions/__tests__/__snapshots__/PortfolioPage-test.tsx.snap b/server/sonar-web/src/main/js/app/components/extensions/__tests__/__snapshots__/PortfolioPage-test.tsx.snap new file mode 100644 index 00000000000..553afe6de35 --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/extensions/__tests__/__snapshots__/PortfolioPage-test.tsx.snap @@ -0,0 +1,45 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` +<ProjectPageExtension + component={ + Object { + "breadcrumbs": Array [], + "key": "my-project", + "name": "MyProject", + "qualifier": "TRK", + "qualityGate": Object { + "isDefault": true, + "key": "30", + "name": "Sonar way", + }, + "qualityProfiles": Array [ + Object { + "deleted": false, + "key": "my-qp", + "language": "ts", + "name": "Sonar way", + }, + ], + "tags": Array [], + } + } + location={ + Object { + "action": "PUSH", + "hash": "", + "key": "key", + "pathname": "/path", + "query": Object {}, + "search": "", + "state": Object {}, + } + } + params={ + Object { + "extensionKey": "portfolio", + "pluginKey": "governance", + } + } +/> +`; diff --git a/server/sonar-web/src/main/js/app/components/extensions/exposeLibraries.ts b/server/sonar-web/src/main/js/app/components/extensions/exposeLibraries.ts index b39b7a7b81e..1b2d86945a9 100644 --- a/server/sonar-web/src/main/js/app/components/extensions/exposeLibraries.ts +++ b/server/sonar-web/src/main/js/app/components/extensions/exposeLibraries.ts @@ -56,7 +56,9 @@ import ClearIcon from '../../../components/icons/ClearIcon'; import DetachIcon from '../../../components/icons/DetachIcon'; import DropdownIcon from '../../../components/icons/DropdownIcon'; import HelpIcon from '../../../components/icons/HelpIcon'; +import HistoryIcon from '../../../components/icons/HistoryIcon'; import LockIcon from '../../../components/icons/LockIcon'; +import MeasuresIcon from '../../../components/icons/MeasuresIcon'; import PlusCircleIcon from '../../../components/icons/PlusCircleIcon'; import PullRequestIcon from '../../../components/icons/PullRequestIcon'; import QualifierIcon from '../../../components/icons/QualifierIcon'; @@ -65,6 +67,7 @@ import VulnerabilityIcon from '../../../components/icons/VulnerabilityIcon'; import DateFormatter from '../../../components/intl/DateFormatter'; import DateFromNow from '../../../components/intl/DateFromNow'; import DateTimeFormatter from '../../../components/intl/DateTimeFormatter'; +import Measure from '../../../components/measure/Measure'; import { Alert } from '../../../components/ui/Alert'; import CoverageRating from '../../../components/ui/CoverageRating'; import DeferredSpinner from '../../../components/ui/DeferredSpinner'; @@ -101,8 +104,10 @@ import { renderSonarSourceSecurityCategory } from '../../../helpers/security-standard'; import { + getComponentDrilldownUrl, getComponentIssuesUrl, getComponentSecurityHotspotsUrl, + getMeasureHistoryUrl, getRulesUrl } from '../../../helpers/urls'; import addGlobalSuccessMessage from '../../utils/addGlobalSuccessMessage'; @@ -147,8 +152,10 @@ const exposeLibraries = () => { renderOwaspTop10Category, renderSansTop25Category, renderSonarSourceSecurityCategory, + getComponentDrilldownUrl, getComponentIssuesUrl, getComponentSecurityHotspotsUrl, + getMeasureHistoryUrl, getRulesUrl }; } @@ -203,6 +210,7 @@ const exposeLibraries = () => { FormattedMessage, HelpIcon, HelpTooltip, + HistoryIcon, HomePageSelect, Level, ListFooter, @@ -210,6 +218,8 @@ const exposeLibraries = () => { LongLivingBranchIcon: BranchIcon, MandatoryFieldMarker, MandatoryFieldsExplanation, + Measure, + MeasuresIcon, Modal, NotFound, PlusCircleIcon, diff --git a/server/sonar-web/src/main/js/app/components/nav/component/Menu.tsx b/server/sonar-web/src/main/js/app/components/nav/component/Menu.tsx index 080edc8b4b4..8e25440e799 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/Menu.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/component/Menu.tsx @@ -96,6 +96,14 @@ export class Menu extends React.PureComponent<Props> { return this.isApplication() && !this.isAllChildProjectAccessible(); }; + isGovernanceEnabled = () => { + const { + component: { extensions } + } = this.props; + + return extensions && extensions.some(extension => extension.key.startsWith('governance/')); + }; + getConfiguration = () => { return this.props.component.configuration || {}; }; @@ -153,15 +161,24 @@ export class Menu extends React.PureComponent<Props> { renderDashboardLink = () => { const { id, ...branchLike } = this.getQuery(); + + if (this.isPortfolio()) { + return this.isGovernanceEnabled() ? ( + <li> + <Link activeClassName="active" to={getPortfolioUrl(id)}> + {translate('overview.page')} + </Link> + </li> + ) : null; + } + const isApplicationChildInaccessble = this.isApplicationChildInaccessble(); if (isApplicationChildInaccessble) { return this.renderLinkWhenInaccessibleChild(translate('overview.page')); } return ( <li> - <Link - activeClassName="active" - to={this.isPortfolio() ? getPortfolioUrl(id) : getProjectQueryUrl(id, branchLike)}> + <Link activeClassName="active" to={getProjectQueryUrl(id, branchLike)}> {translate('overview.page')} </Link> </li> @@ -565,7 +582,8 @@ export class Menu extends React.PureComponent<Props> { const query = this.getQuery(); const extensions = this.props.component.extensions || []; const withoutSecurityExtension = extensions.filter( - extension => !extension.key.startsWith('securityreport/') + extension => + !extension.key.startsWith('securityreport/') && !extension.key.startsWith('governance/') ); if (withoutSecurityExtension.length === 0) { diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/Menu-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/Menu-test.tsx.snap index 59c14e2a5b7..52b215373ad 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/Menu-test.tsx.snap +++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/Menu-test.tsx.snap @@ -975,23 +975,6 @@ exports[`should work for all qualifiers 2`] = ` <NavBarTabs> <li> <Link - activeClassName="active" - onlyActiveOnIndex={false} - style={Object {}} - to={ - Object { - "pathname": "/portfolio", - "query": Object { - "id": "foo", - }, - } - } - > - overview.page - </Link> - </li> - <li> - <Link onlyActiveOnIndex={false} style={Object {}} to={ @@ -1114,23 +1097,6 @@ exports[`should work for all qualifiers 3`] = ` <NavBarTabs> <li> <Link - activeClassName="active" - onlyActiveOnIndex={false} - style={Object {}} - to={ - Object { - "pathname": "/portfolio", - "query": Object { - "id": "foo", - }, - } - } - > - overview.page - </Link> - </li> - <li> - <Link onlyActiveOnIndex={false} style={Object {}} to={ diff --git a/server/sonar-web/src/main/js/app/utils/startReactApp.tsx b/server/sonar-web/src/main/js/app/utils/startReactApp.tsx index 1758c81ed31..c9b4d492a54 100644 --- a/server/sonar-web/src/main/js/app/utils/startReactApp.tsx +++ b/server/sonar-web/src/main/js/app/utils/startReactApp.tsx @@ -42,7 +42,6 @@ import marketplaceRoutes from '../../apps/marketplace/routes'; import overviewRoutes from '../../apps/overview/routes'; import permissionTemplatesRoutes from '../../apps/permission-templates/routes'; import { globalPermissionsRoutes, projectPermissionsRoutes } from '../../apps/permissions/routes'; -import portfolioRoutes from '../../apps/portfolio/routes'; import projectActivityRoutes from '../../apps/projectActivity/routes'; import projectBaselineRoutes from '../../apps/projectBaseline/routes'; import projectBranchesRoutes from '../../apps/projectBranches/routes'; @@ -162,7 +161,10 @@ function renderComponentRoutes() { <RouteWithChildRoutes path="code" childRoutes={codeRoutes} /> <RouteWithChildRoutes path="component_measures" childRoutes={componentMeasuresRoutes} /> <RouteWithChildRoutes path="dashboard" childRoutes={overviewRoutes} /> - <RouteWithChildRoutes path="portfolio" childRoutes={portfolioRoutes} /> + <Route + path="portfolio" + component={lazyLoadComponent(() => import('../components/extensions/PortfolioPage'))} + /> <RouteWithChildRoutes path="project/activity" childRoutes={projectActivityRoutes} /> <Route path="project/extension/:pluginKey/:extensionKey" diff --git a/server/sonar-web/src/main/js/components/ui/Rating.tsx b/server/sonar-web/src/main/js/components/ui/Rating.tsx index 4fe2b295f93..a263ea6d2f9 100644 --- a/server/sonar-web/src/main/js/components/ui/Rating.tsx +++ b/server/sonar-web/src/main/js/components/ui/Rating.tsx @@ -39,7 +39,14 @@ export default function Rating({ }: Props) { if (value === undefined) { return ( - <span aria-label={translate('metric.no_rating')} {...ariaAttrs}> + <span + className={classNames( + 'no-rating', + { 'rating-small': small, 'rating-muted': muted }, + className + )} + aria-label={translate('metric.no_rating')} + {...ariaAttrs}> – </span> ); diff --git a/server/sonar-web/src/main/js/components/ui/__tests__/__snapshots__/Rating-test.tsx.snap b/server/sonar-web/src/main/js/components/ui/__tests__/__snapshots__/Rating-test.tsx.snap index 9455ce98965..be2ef45164a 100644 --- a/server/sonar-web/src/main/js/components/ui/__tests__/__snapshots__/Rating-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/ui/__tests__/__snapshots__/Rating-test.tsx.snap @@ -21,6 +21,7 @@ exports[`renders string value 1`] = ` exports[`renders undefined value 1`] = ` <span aria-label="metric.no_rating" + className="no-rating rating-small rating-muted" > – </span> @@ -40,6 +41,7 @@ exports[`renders with a custom aria-label 2`] = ` <span aria-hidden={false} aria-label="custom" + className="no-rating" > – </span> |