Browse Source

SONAR-19435 Infer component type from URL to make component not found error more explicit

tags/10.2.0.77647
7PH 9 months ago
parent
commit
f8f36e7876

+ 0
- 4
server/sonar-web/src/main/js/api/components.ts View File

@@ -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'>>> {

+ 1
- 1
server/sonar-web/src/main/js/api/navigation.ts View File

@@ -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 }> {

+ 9
- 9
server/sonar-web/src/main/js/app/components/ComponentContainer.tsx View File

@@ -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" />

+ 13
- 3
server/sonar-web/src/main/js/app/components/ComponentContainerNotFound.tsx View File

@@ -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>

+ 42
- 0
server/sonar-web/src/main/js/app/components/__tests__/ComponentContainerNotFound-test.tsx View File

@@ -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} />);
}

+ 5
- 1
server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/ComponentContainer-test.tsx.snap View File

@@ -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}
/>
`;

+ 11
- 8
server/sonar-web/src/main/js/apps/projects/components/ApplicationCreation.tsx View File

@@ -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 (

+ 13
- 13
server/sonar-web/src/main/js/apps/projectsManagement/ProjectRowActions.tsx View File

@@ -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 = () => {

+ 4
- 2
sonar-core/src/main/resources/org/sonar/l10n/core.properties View File

@@ -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.


#------------------------------------------------------------------------------

Loading…
Cancel
Save