aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorJeremy Davis <jeremy.davis@sonarsource.com>2021-12-08 17:47:58 +0100
committersonartech <sonartech@sonarsource.com>2021-12-24 20:02:59 +0000
commiteb02c848b5abb29ef43ed4438205af46243faf3d (patch)
tree717876112e597cbb4dd318603ba4bb6e70cd75f1 /server
parentbe00336305414148148f4fc3ff04c58e01de93cb (diff)
downloadsonarqube-eb02c848b5abb29ef43ed4438205af46243faf3d.tar.gz
sonarqube-eb02c848b5abb29ef43ed4438205af46243faf3d.zip
SONAR-15789 New portfolio overview
Diffstat (limited to 'server')
-rw-r--r--server/sonar-web/config/indexHtmlTemplate.js2
-rw-r--r--server/sonar-web/src/main/js/app/components/extensions/PortfolioPage.tsx40
-rw-r--r--server/sonar-web/src/main/js/app/components/extensions/__tests__/PortfolioPage-test.tsx41
-rw-r--r--server/sonar-web/src/main/js/app/components/extensions/__tests__/__snapshots__/PortfolioPage-test.tsx.snap45
-rw-r--r--server/sonar-web/src/main/js/app/components/extensions/exposeLibraries.ts10
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/Menu.tsx26
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/Menu-test.tsx.snap34
-rw-r--r--server/sonar-web/src/main/js/app/utils/startReactApp.tsx6
-rw-r--r--server/sonar-web/src/main/js/components/ui/Rating.tsx9
-rw-r--r--server/sonar-web/src/main/js/components/ui/__tests__/__snapshots__/Rating-test.tsx.snap2
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>