Browse Source

SONAR-20463 Do not display permission warning in case the project has not been analyzed

tags/10.5.0.89998
David Cho-Lerat 3 months ago
parent
commit
c6cb86cf12

+ 31
- 15
server/sonar-web/src/main/js/apps/overview/components/EmptyOverview.tsx View File

@@ -17,16 +17,20 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

import { FlagMessage, LargeCenteredLayout, PageContentFontWrapper, Spinner } from 'design-system';
import * as React from 'react';
import { Navigate } from 'react-router-dom';
import { getScannableProjects } from '../../../api/components';
import withCurrentUserContext from '../../../app/components/current-user/withCurrentUserContext';
import { getBranchLikeDisplayName, isBranch, isMainBranch } from '../../../helpers/branch-like';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { getProjectTutorialLocation } from '../../../helpers/urls';
import { hasGlobalPermission } from '../../../helpers/users';
import { useTaskForComponentQuery } from '../../../queries/component';
import { BranchLike } from '../../../types/branch-like';
import { ComponentQualifier } from '../../../types/component';
import { Permissions } from '../../../types/permissions';
import { TaskTypes } from '../../../types/tasks';
import { Component } from '../../../types/types';
import { CurrentUser, isLoggedIn } from '../../../types/users';
@@ -38,16 +42,35 @@ export interface EmptyOverviewProps {
currentUser: CurrentUser;
}

export function EmptyOverview(props: EmptyOverviewProps) {
export function EmptyOverview(props: Readonly<EmptyOverviewProps>) {
const { branchLike, branchLikes, component, currentUser } = props;

const [currentUserCanScanProject, setCurrentUserCanScanProject] = React.useState(
hasGlobalPermission(currentUser, Permissions.Scan),
);

const { data, isLoading } = useTaskForComponentQuery(component);

const hasQueuedAnalyses =
data && data.queue.filter((task) => task.type === TaskTypes.Report).length > 0;

const hasPermissionSyncInProgess =
data &&
data.queue.filter((task) => task.type === TaskTypes.GithubProjectPermissionsProvisioning)
.length > 0;

React.useEffect(() => {
if (currentUserCanScanProject || !isLoggedIn(currentUser)) {
return;
}

getScannableProjects()
.then(({ projects }) => {
setCurrentUserCanScanProject(projects.find((p) => p.key === component.key) !== undefined);
})
.catch(() => {});
}, [component.key, currentUser, currentUserCanScanProject]);

if (isLoading) {
return <Spinner loading />;
}
@@ -65,6 +88,7 @@ export function EmptyOverview(props: EmptyOverviewProps) {
}

const hasBranches = branchLikes.length > 1;

const hasBadBranchConfig =
branchLikes.length > 2 ||
(branchLikes.length === 2 && branchLikes.some((branch) => isBranch(branch)));
@@ -82,13 +106,15 @@ export function EmptyOverview(props: EmptyOverviewProps) {
);
}

const showTutorial = isMainBranch(branchLike) && !hasBranches && !hasQueuedAnalyses;
const showTutorial =
currentUserCanScanProject && isMainBranch(branchLike) && !hasBranches && !hasQueuedAnalyses;

if (showTutorial && isLoggedIn(currentUser)) {
return <Navigate replace to={getProjectTutorialLocation(component.key)} />;
}

let warning;

if (isLoggedIn(currentUser) && isMainBranch(branchLike) && hasBranches && hasBadBranchConfig) {
warning = translateWithParameters(
'provisioning.no_analysis_on_main_branch.bad_configuration',
@@ -105,19 +131,9 @@ export function EmptyOverview(props: EmptyOverviewProps) {
return (
<LargeCenteredLayout className="sw-pt-8">
<PageContentFontWrapper>
{isLoggedIn(currentUser) ? (
<>
{hasBranches && (
<FlagMessage className="sw-w-full" variant="warning">
{warning}
</FlagMessage>
)}
</>
) : (
<FlagMessage className="sw-w-full" variant="warning">
{warning}
</FlagMessage>
)}
<FlagMessage className="sw-w-full" variant="warning">
{warning}
</FlagMessage>
</PageContentFontWrapper>
</LargeCenteredLayout>
);

+ 31
- 2
server/sonar-web/src/main/js/apps/overview/components/__tests__/App-test.tsx View File

@@ -19,18 +19,30 @@
*/
import { screen, waitFor } from '@testing-library/react';
import * as React from 'react';
import { getScannableProjects } from '../../../../api/components';
import BranchesServiceMock from '../../../../api/mocks/BranchesServiceMock';
import ComputeEngineServiceMock from '../../../../api/mocks/ComputeEngineServiceMock';
import CurrentUserContextProvider from '../../../../app/components/current-user/CurrentUserContextProvider';
import { mockBranch } from '../../../../helpers/mocks/branch-like';
import { mockBranch, mockMainBranch } from '../../../../helpers/mocks/branch-like';
import { mockComponent } from '../../../../helpers/mocks/component';
import { mockTask } from '../../../../helpers/mocks/tasks';
import { mockCurrentUser } from '../../../../helpers/testMocks';
import { mockCurrentUser, mockLoggedInUser } from '../../../../helpers/testMocks';
import { renderComponent } from '../../../../helpers/testReactTestingUtils';
import { getProjectTutorialLocation } from '../../../../helpers/urls';
import { ComponentQualifier } from '../../../../types/component';
import { TaskStatuses, TaskTypes } from '../../../../types/tasks';
import { App } from '../App';

jest.mock('../../../../api/components', () => ({
...jest.requireActual('../../../../api/components'),
getScannableProjects: jest.fn().mockResolvedValue({ projects: [] }),
}));

jest.mock('../../../../helpers/urls', () => ({
...jest.requireActual('../../../../helpers/urls'),
getProjectTutorialLocation: jest.fn().mockResolvedValue({ pathname: '/tutorial' }),
}));

const handlerBranches = new BranchesServiceMock();
const handlerCe = new ComputeEngineServiceMock();

@@ -57,6 +69,23 @@ it('should render Empty Overview on main branch with no analysis', async () => {
).toBeInTheDocument();
});

it('should redirect to tutorial when the user can scan a project that has no analysis yet', async () => {
handlerBranches.emptyBranchesAndPullRequest();
handlerBranches.addBranch(mockMainBranch());

jest
.mocked(getScannableProjects)
.mockResolvedValueOnce({ projects: [{ key: 'my-project', name: 'MyProject' }] });

renderApp({}, mockLoggedInUser());

await appLoaded();

await waitFor(() => {
expect(getProjectTutorialLocation).toHaveBeenCalled();
});
});

it('should render Empty Overview on main branch with multiple branches with bad configuration', async () => {
renderApp({ branchLikes: [mockBranch(), mockBranch()] });


+ 12
- 2
server/sonar-web/src/main/js/components/tutorials/TutorialSelectionRenderer.tsx View File

@@ -17,8 +17,10 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

import {
Breadcrumbs,
FlagMessage,
GreyCard,
HoverLink,
LightLabel,
@@ -38,7 +40,6 @@ import { AlmKeys, AlmSettingsInstance, ProjectAlmBindingResponse } from '../../t
import { MainBranch } from '../../types/branch-like';
import { Component } from '../../types/types';
import { LoggedInUser } from '../../types/users';
import { Alert } from '../ui/Alert';
import AzurePipelinesTutorial from './azure-pipelines/AzurePipelinesTutorial';
import BitbucketPipelinesTutorial from './bitbucket-pipelines/BitbucketPipelinesTutorial';
import GitHubActionTutorial from './github-action/GitHubActionTutorial';
@@ -73,6 +74,7 @@ function renderAlm(mode: TutorialModes, project: string, icon?: React.ReactNode)
{translate('onboarding.mode.help.manual')}
</LightLabel>
)}

{mode === TutorialModes.OtherCI && (
<LightLabel as="p" className="sw-mt-3">
{translate('onboarding.mode.help.otherci')}
@@ -106,7 +108,11 @@ export default function TutorialSelectionRenderer(props: TutorialSelectionRender
}

if (!currentUserCanScanProject) {
return <Alert variant="warning">{translate('onboarding.tutorial.no_scan_rights')}</Alert>;
return (
<FlagMessage className="sw-w-full" variant="warning">
{translate('onboarding.tutorial.no_scan_rights')}
</FlagMessage>
);
}

let showGitHubActions = true;
@@ -120,6 +126,7 @@ export default function TutorialSelectionRenderer(props: TutorialSelectionRender
showGitLabCICD = projectBinding.alm === AlmKeys.GitLab;
showBitbucketPipelines = projectBinding.alm === AlmKeys.BitbucketCloud;
showAzurePipelines = [AlmKeys.Azure, AlmKeys.GitHub].includes(projectBinding.alm);

showJenkins = [
AlmKeys.BitbucketCloud,
AlmKeys.BitbucketServer,
@@ -137,6 +144,7 @@ export default function TutorialSelectionRenderer(props: TutorialSelectionRender
<Title className="sw-mb-6 sw-heading-lg">
{translate('onboarding.tutorial.page.title')}
</Title>

<LightPrimary>{translate('onboarding.tutorial.page.description')}</LightPrimary>

<SubTitle className="sw-mt-12 sw-mb-4 sw-heading-md">
@@ -200,6 +208,7 @@ export default function TutorialSelectionRenderer(props: TutorialSelectionRender
)}

{renderAlm(TutorialModes.OtherCI, component.key)}

{renderAlm(TutorialModes.Local, component.key)}
</div>
</div>
@@ -210,6 +219,7 @@ export default function TutorialSelectionRenderer(props: TutorialSelectionRender
<HoverLink to={getProjectTutorialLocation(component.key)}>
{translate('onboarding.tutorial.breadcrumbs.home')}
</HoverLink>

<HoverLink to={getProjectTutorialLocation(component.key, selectedTutorial)}>
{translate('onboarding.tutorial.breadcrumbs', selectedTutorial)}
</HoverLink>

Loading…
Cancel
Save