aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
author7PH <benjamin.raymond@sonarsource.com>2023-08-30 09:14:39 +0200
committersonartech <sonartech@sonarsource.com>2023-08-31 20:02:56 +0000
commitf8f36e7876d1d0d20972d17bb66a63ff7e836340 (patch)
tree468b464ebce77f5ea07ff2adc98b1dc6b2ce5fa6
parentee593d4b874d558144e37655cb1e33cd71da1059 (diff)
downloadsonarqube-f8f36e7876d1d0d20972d17bb66a63ff7e836340.tar.gz
sonarqube-f8f36e7876d1d0d20972d17bb66a63ff7e836340.zip
SONAR-19435 Infer component type from URL to make component not found error more explicit
-rw-r--r--server/sonar-web/src/main/js/api/components.ts4
-rw-r--r--server/sonar-web/src/main/js/api/navigation.ts2
-rw-r--r--server/sonar-web/src/main/js/app/components/ComponentContainer.tsx18
-rw-r--r--server/sonar-web/src/main/js/app/components/ComponentContainerNotFound.tsx16
-rw-r--r--server/sonar-web/src/main/js/app/components/__tests__/ComponentContainerNotFound-test.tsx42
-rw-r--r--server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/ComponentContainer-test.tsx.snap6
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/ApplicationCreation.tsx19
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/ProjectRowActions.tsx26
-rw-r--r--sonar-core/src/main/resources/org/sonar/l10n/core.properties6
9 files changed, 98 insertions, 41 deletions
diff --git a/server/sonar-web/src/main/js/api/components.ts b/server/sonar-web/src/main/js/api/components.ts
index e57e665cfa5..50c26818095 100644
--- a/server/sonar-web/src/main/js/api/components.ts
+++ b/server/sonar-web/src/main/js/api/components.ts
@@ -160,10 +160,6 @@ export function getComponentShow(data: { component: string } & BranchParameters)
return getComponentData(data).catch(throwGlobalError);
}
-export function getParents(component: string): Promise<any> {
- return getComponentShow({ component }).then((r) => r.ancestors);
-}
-
export function getBreadcrumbs(
data: { component: string } & BranchParameters
): Promise<Array<Omit<ComponentRaw, 'tags'>>> {
diff --git a/server/sonar-web/src/main/js/api/navigation.ts b/server/sonar-web/src/main/js/api/navigation.ts
index a6a5f87698b..001b6b97fb0 100644
--- a/server/sonar-web/src/main/js/api/navigation.ts
+++ b/server/sonar-web/src/main/js/api/navigation.ts
@@ -26,7 +26,7 @@ import { Extension, NavigationComponent } from '../types/types';
export function getComponentNavigation(
data: { component: string } & BranchParameters
): Promise<NavigationComponent> {
- return getJSON('/api/navigation/component', data).catch(throwGlobalError);
+ return getJSON('/api/navigation/component', data);
}
export function getMarketplaceNavigation(): Promise<{ serverId: string; ncloc: number }> {
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 25e731d7e2e..60d59d84e53 100644
--- a/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx
+++ b/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx
@@ -32,7 +32,7 @@ import { translateWithParameters } from '../../helpers/l10n';
import { HttpStatus } from '../../helpers/request';
import { getPortfolioUrl, getProjectUrl } from '../../helpers/urls';
import { ProjectAlmBindingConfigurationErrors } from '../../types/alm-settings';
-import { ComponentQualifier, isPortfolioLike } from '../../types/component';
+import { ComponentQualifier, isFile, isPortfolioLike } from '../../types/component';
import { Feature } from '../../types/features';
import { Task, TaskStatuses, TaskTypes } from '../../types/tasks';
import { Component } from '../../types/types';
@@ -118,7 +118,7 @@ export class ComponentContainer extends React.PureComponent<Props, State> {
* This is a fail-safe in case there are still some faulty links remaining.
*/
if (
- this.props.location.pathname.match('dashboard') &&
+ this.props.location.pathname.includes('dashboard') &&
isPortfolioLike(componentWithQualifier.qualifier)
) {
this.props.router.replace(getPortfolioUrl(componentWithQualifier.key));
@@ -131,7 +131,7 @@ export class ComponentContainer extends React.PureComponent<Props, State> {
loading: false,
},
() => {
- if (shouldRedirectToDashboard && this.props.location.pathname.match('tutorials')) {
+ if (shouldRedirectToDashboard && this.props.location.pathname.includes('tutorials')) {
this.props.router.replace(getProjectUrl(key));
}
}
@@ -319,7 +319,11 @@ export class ComponentContainer extends React.PureComponent<Props, State> {
const { component, loading } = this.state;
if (!loading && !component) {
- return <ComponentContainerNotFound />;
+ return (
+ <ComponentContainerNotFound
+ isPortfolioLike={this.props.location.pathname.includes('portfolio')}
+ />
+ );
}
const { currentTask, isPending, projectBindingErrors, tasksInProgress } = this.state;
@@ -335,11 +339,8 @@ export class ComponentContainer extends React.PureComponent<Props, State> {
component?.name ?? ''
)}
/>
-
{component &&
- !([ComponentQualifier.File, ComponentQualifier.TestFile] as string[]).includes(
- component.qualifier
- ) &&
+ !isFile(component.qualifier) &&
this.portalAnchor &&
/* Use a portal to fix positioning until we can fully review the layout */
createPortal(
@@ -352,7 +353,6 @@ export class ComponentContainer extends React.PureComponent<Props, State> {
/>,
this.portalAnchor
)}
-
{loading ? (
<div className="page page-limited">
<i className="spinner" />
diff --git a/server/sonar-web/src/main/js/app/components/ComponentContainerNotFound.tsx b/server/sonar-web/src/main/js/app/components/ComponentContainerNotFound.tsx
index 6e783932f97..9c47e9eb6c6 100644
--- a/server/sonar-web/src/main/js/app/components/ComponentContainerNotFound.tsx
+++ b/server/sonar-web/src/main/js/app/components/ComponentContainerNotFound.tsx
@@ -22,14 +22,24 @@ import { Helmet } from 'react-helmet-async';
import Link from '../../components/common/Link';
import { translate } from '../../helpers/l10n';
-export default function ComponentContainerNotFound() {
+export interface ComponentContainerNotFoundProps {
+ isPortfolioLike: boolean;
+}
+
+export default function ComponentContainerNotFound({
+ isPortfolioLike,
+}: ComponentContainerNotFoundProps) {
+ const componentType = isPortfolioLike ? 'portfolio' : 'project';
+
return (
<>
<Helmet defaultTitle={translate('404_not_found')} defer={false} />
<div className="page-wrapper-simple" id="bd">
<div className="page-simple" id="nonav">
- <h2 className="big-spacer-bottom">{translate('dashboard.project_not_found')}</h2>
- <p className="spacer-bottom">{translate('dashboard.project_not_found.2')}</p>
+ <h2 className="big-spacer-bottom">
+ {translate('dashboard', componentType, 'not_found')}
+ </h2>
+ <p className="spacer-bottom">{translate('dashboard', componentType, 'not_found.2')}</p>
<p>
<Link to="/">{translate('go_back_to_homepage')}</Link>
</p>
diff --git a/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainerNotFound-test.tsx b/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainerNotFound-test.tsx
new file mode 100644
index 00000000000..ec1f455c92b
--- /dev/null
+++ b/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainerNotFound-test.tsx
@@ -0,0 +1,42 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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 { screen } from '@testing-library/react';
+import * as React from 'react';
+import { renderComponent } from '../../../helpers/testReactTestingUtils';
+import ComponentContainerNotFound from '../ComponentContainerNotFound';
+
+it('should render portfolio 404 correctly', () => {
+ renderComponentContainerNotFound(true);
+
+ expect(screen.getByText('dashboard.portfolio.not_found')).toBeInTheDocument();
+ expect(screen.getByText('dashboard.portfolio.not_found.2')).toBeInTheDocument();
+});
+
+it('should render project 404 correctly', () => {
+ renderComponentContainerNotFound(false);
+
+ expect(screen.getByText('dashboard.project.not_found')).toBeInTheDocument();
+ expect(screen.getByText('dashboard.project.not_found.2')).toBeInTheDocument();
+});
+
+function renderComponentContainerNotFound(isPortfolioLike: boolean) {
+ return renderComponent(<ComponentContainerNotFound isPortfolioLike={isPortfolioLike} />);
+}
diff --git a/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/ComponentContainer-test.tsx.snap b/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/ComponentContainer-test.tsx.snap
index b71a0038760..a2eba82bd3d 100644
--- a/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/ComponentContainer-test.tsx.snap
+++ b/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/ComponentContainer-test.tsx.snap
@@ -1,3 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`should show component not found if it does not exist 1`] = `<ComponentContainerNotFound />`;
+exports[`should show component not found if it does not exist 1`] = `
+<ComponentContainerNotFound
+ isPortfolioLike={false}
+/>
+`;
diff --git a/server/sonar-web/src/main/js/apps/projects/components/ApplicationCreation.tsx b/server/sonar-web/src/main/js/apps/projects/components/ApplicationCreation.tsx
index cd4cb176e5f..c56f5ca0957 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/ApplicationCreation.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/components/ApplicationCreation.tsx
@@ -24,6 +24,7 @@ import withAppStateContext from '../../../app/components/app-state/withAppStateC
import withCurrentUserContext from '../../../app/components/current-user/withCurrentUserContext';
import CreateApplicationForm from '../../../app/components/extensions/CreateApplicationForm';
import { Router, withRouter } from '../../../components/hoc/withRouter';
+import { throwGlobalError } from '../../../helpers/error';
import { translate } from '../../../helpers/l10n';
import { getComponentAdminUrl, getComponentOverviewUrl } from '../../../helpers/urls';
import { hasGlobalPermission } from '../../../helpers/users';
@@ -59,14 +60,16 @@ export function ApplicationCreation(props: ApplicationCreationProps) {
key: string;
qualifier: ComponentQualifier;
}) => {
- return getComponentNavigation({ component: key }).then(({ configuration }) => {
- if (configuration && configuration.showSettings) {
- router.push(getComponentAdminUrl(key, qualifier));
- } else {
- router.push(getComponentOverviewUrl(key, qualifier));
- }
- setShowForm(false);
- });
+ return getComponentNavigation({ component: key })
+ .then(({ configuration }) => {
+ if (configuration?.showSettings) {
+ router.push(getComponentAdminUrl(key, qualifier));
+ } else {
+ router.push(getComponentOverviewUrl(key, qualifier));
+ }
+ setShowForm(false);
+ })
+ .catch(throwGlobalError);
};
return (
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/ProjectRowActions.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/ProjectRowActions.tsx
index 64a78adfdd5..2f221c82070 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/ProjectRowActions.tsx
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/ProjectRowActions.tsx
@@ -22,6 +22,7 @@ import { getComponentNavigation } from '../../api/navigation';
import { Project } from '../../api/project-management';
import ActionsDropdown, { ActionsDropdownItem } from '../../components/controls/ActionsDropdown';
import Spinner from '../../components/ui/Spinner';
+import { throwGlobalError } from '../../helpers/error';
import { translate, translateWithParameters } from '../../helpers/l10n';
import { getComponentPermissionsUrl } from '../../helpers/urls';
import { useGithubProvisioningEnabledQuery } from '../../queries/identity-provider';
@@ -41,20 +42,19 @@ export default function ProjectRowActions({ currentUser, project }: Props) {
const [restoreAccessModal, setRestoreAccessModal] = useState(false);
const { data: githubProvisioningEnabled } = useGithubProvisioningEnabledQuery();
- const fetchPermissions = () => {
+ const fetchPermissions = async () => {
setLoading(true);
- getComponentNavigation({ component: project.key }).then(
- ({ configuration }) => {
- const hasAccess = Boolean(
- configuration && configuration.showPermissions && configuration.canBrowseProject
- );
- setHasAccess(hasAccess);
- setLoading(false);
- },
- () => {
- setLoading(false);
- }
- );
+
+ try {
+ const { configuration } = await getComponentNavigation({ component: project.key });
+ const hasAccess = Boolean(configuration?.showPermissions && configuration?.canBrowseProject);
+ setHasAccess(hasAccess);
+ setLoading(false);
+ } catch (error) {
+ throwGlobalError(error);
+ } finally {
+ setLoading(false);
+ }
};
const handleDropdownOpen = () => {
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 c923765068a..b1be77cd82c 100644
--- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties
+++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
@@ -1313,8 +1313,10 @@ projects.security_hotspots_reviewed=Hotspots Reviewed
#
#------------------------------------------------------------------------------
-dashboard.project_not_found=The requested project does not exist.
-dashboard.project_not_found.2=Either it has never been analyzed successfully or it has been deleted.
+dashboard.project.not_found=The requested project could not be found.
+dashboard.project.not_found.2=Either it has never been analyzed successfully or it has been deleted.
+dashboard.portfolio.not_found=The requested portfolio could not be found.
+dashboard.portfolio.not_found.2=Either its parent has not been recomputed or it has been deleted.
#------------------------------------------------------------------------------