Browse Source

SONAR-14440 Better message for Branch Analysis tooltip

tags/8.9.0.43852
Wouter Admiraal 3 years ago
parent
commit
407f46439c
18 changed files with 357 additions and 64 deletions
  1. 21
    4
      server/sonar-web/src/main/js/app/components/ComponentContainer.tsx
  2. 22
    0
      server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx
  3. 4
    0
      server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.tsx
  4. 4
    1
      server/sonar-web/src/main/js/app/components/nav/component/Header.tsx
  5. 5
    1
      server/sonar-web/src/main/js/app/components/nav/component/branch-like/BranchLikeNavigation.tsx
  6. 22
    4
      server/sonar-web/src/main/js/app/components/nav/component/branch-like/CurrentBranchLike.tsx
  7. 37
    14
      server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/CurrentBranchLike-test.tsx
  8. 88
    6
      server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/CurrentBranchLike-test.tsx.snap
  9. 4
    1
      server/sonar-web/src/main/js/apps/overview/components/App.tsx
  10. 12
    4
      server/sonar-web/src/main/js/apps/overview/components/EmptyOverview.tsx
  11. 5
    3
      server/sonar-web/src/main/js/apps/overview/components/__tests__/EmptyOverview-test.tsx
  12. 49
    0
      server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/EmptyOverview-test.tsx.snap
  13. 8
    2
      server/sonar-web/src/main/js/apps/tutorials/components/TutorialsApp.tsx
  14. 2
    0
      server/sonar-web/src/main/js/apps/tutorials/components/__tests__/TutorialsApp-test.tsx
  15. 50
    0
      server/sonar-web/src/main/js/apps/tutorials/components/__tests__/__snapshots__/TutorialsApp-test.tsx.snap
  16. 11
    15
      server/sonar-web/src/main/js/components/tutorials/TutorialSelection.tsx
  17. 7
    7
      server/sonar-web/src/main/js/components/tutorials/__tests__/TutorialSelection-test.tsx
  18. 6
    2
      sonar-core/src/main/resources/org/sonar/l10n/core.properties

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

@@ -20,6 +20,7 @@
import { differenceBy } from 'lodash';
import * as React from 'react';
import { connect } from 'react-redux';
import { getProjectAlmBinding } from '../../api/alm-settings';
import { getBranches, getPullRequests } from '../../api/branches';
import { getAnalysisStatus, getTasksForComponent } from '../../api/ce';
import { getComponentData } from '../../api/components';
@@ -33,6 +34,7 @@ import {
} from '../../helpers/branch-like';
import { getPortfolioUrl } from '../../helpers/urls';
import { registerBranchStatus, requireAuthorization } from '../../store/rootActions';
import { ProjectAlmBindingResponse } from '../../types/alm-settings';
import { BranchLike } from '../../types/branch-like';
import { isPortfolioLike } from '../../types/component';
import { Task, TaskStatuses, TaskWarning } from '../../types/tasks';
@@ -56,6 +58,7 @@ interface State {
currentTask?: Task;
isPending: boolean;
loading: boolean;
projectBinding?: ProjectAlmBindingResponse;
tasksInProgress?: Task[];
warnings: TaskWarning[];
}
@@ -108,9 +111,10 @@ export class ComponentContainer extends React.PureComponent<Props, State> {

Promise.all([
getComponentNavigation({ component: key, branch, pullRequest }),
getComponentData({ component: key, branch, pullRequest })
getComponentData({ component: key, branch, pullRequest }),
getProjectAlmBinding(key).catch(() => undefined)
])
.then(([nav, { component }]) => {
.then(([nav, { component }, projectBinding]) => {
const componentWithQualifier = this.addQualifier({ ...nav, ...component });

/*
@@ -125,6 +129,10 @@ export class ComponentContainer extends React.PureComponent<Props, State> {
this.props.router.replace(getPortfolioUrl(component.key));
}

if (this.mounted) {
this.setState({ projectBinding });
}

return componentWithQualifier;
}, onError)
.then(this.fetchBranches)
@@ -333,7 +341,14 @@ export class ComponentContainer extends React.PureComponent<Props, State> {
return <PageUnavailableDueToIndexation component={component} />;
}

const { branchLike, branchLikes, currentTask, isPending, tasksInProgress } = this.state;
const {
branchLike,
branchLikes,
currentTask,
isPending,
projectBinding,
tasksInProgress
} = this.state;
const isInProgress = tasksInProgress && tasksInProgress.length > 0;

return (
@@ -349,6 +364,7 @@ export class ComponentContainer extends React.PureComponent<Props, State> {
isPending={isPending}
onComponentChange={this.handleComponentChange}
onWarningDismiss={this.handleWarningDismiss}
projectBinding={projectBinding}
warnings={this.state.warnings}
/>
)}
@@ -365,7 +381,8 @@ export class ComponentContainer extends React.PureComponent<Props, State> {
isInProgress,
isPending,
onBranchesChange: this.handleBranchesChange,
onComponentChange: this.handleComponentChange
onComponentChange: this.handleComponentChange,
projectBinding
})}
</ComponentContext.Provider>
)}

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

@@ -20,6 +20,7 @@
import { shallow } from 'enzyme';
import * as React from 'react';
import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils';
import { getProjectAlmBinding } from '../../../api/alm-settings';
import { getBranches, getPullRequests } from '../../../api/branches';
import { getAnalysisStatus, getTasksForComponent } from '../../../api/ce';
import { getComponentData } from '../../../api/components';
@@ -27,6 +28,7 @@ import { getComponentNavigation } from '../../../api/nav';
import { mockBranch, mockMainBranch, mockPullRequest } from '../../../helpers/mocks/branch-like';
import { mockTask } from '../../../helpers/mocks/tasks';
import { mockComponent, mockLocation, mockRouter } from '../../../helpers/testMocks';
import { AlmKeys } from '../../../types/alm-settings';
import { ComponentQualifier } from '../../../types/component';
import { TaskStatuses } from '../../../types/tasks';
import { ComponentContainer } from '../ComponentContainer';
@@ -65,6 +67,10 @@ jest.mock('../../../api/nav', () => ({
})
}));

jest.mock('../../../api/alm-settings', () => ({
getProjectAlmBinding: jest.fn().mockResolvedValue(undefined)
}));

// mock this, because some of its children are using redux store
jest.mock('../nav/component/ComponentNav', () => ({
default: () => null
@@ -88,6 +94,22 @@ it('changes component', () => {
expect(wrapper.state().component).toEqual({ qualifier: 'TRK', visibility: 'private' });
});

it('loads the project binding, if any', async () => {
(getProjectAlmBinding as jest.Mock).mockResolvedValueOnce(undefined).mockResolvedValueOnce({
alm: AlmKeys.GitHub,
key: 'foo'
});

const wrapper = shallowRender();
await waitAndUpdate(wrapper);
expect(getProjectAlmBinding).toBeCalled();
expect(wrapper.state().projectBinding).toBeUndefined();

wrapper.setProps({ location: mockLocation({ query: { id: 'bar' } }) });
await waitAndUpdate(wrapper);
expect(wrapper.state().projectBinding).toEqual({ alm: AlmKeys.GitHub, key: 'foo' });
});

it("doesn't load branches portfolio", async () => {
const wrapper = shallowRender({ location: mockLocation({ query: { id: 'portfolioKey' } }) });
await new Promise(setImmediate);

+ 4
- 0
server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.tsx View File

@@ -20,6 +20,7 @@
import * as classNames from 'classnames';
import * as React from 'react';
import ContextNavBar from 'sonar-ui-common/components/ui/ContextNavBar';
import { ProjectAlmBindingResponse } from '../../../../types/alm-settings';
import { BranchLike } from '../../../../types/branch-like';
import { ComponentQualifier } from '../../../../types/component';
import { Task, TaskStatuses, TaskWarning } from '../../../../types/tasks';
@@ -42,6 +43,7 @@ export interface ComponentNavProps {
isPending?: boolean;
onComponentChange: (changes: Partial<T.Component>) => void;
onWarningDismiss: () => void;
projectBinding?: ProjectAlmBindingResponse;
warnings: TaskWarning[];
}

@@ -54,6 +56,7 @@ export default function ComponentNav(props: ComponentNavProps) {
currentTaskOnSameBranch,
isInProgress,
isPending,
projectBinding,
warnings
} = props;
const { contextNavHeightRaw, globalNavHeightRaw } = rawSizes;
@@ -100,6 +103,7 @@ export default function ComponentNav(props: ComponentNavProps) {
branchLikes={branchLikes}
component={component}
currentBranchLike={currentBranchLike}
projectBinding={projectBinding}
/>
<HeaderMeta
branchLike={currentBranchLike}

+ 4
- 1
server/sonar-web/src/main/js/app/components/nav/component/Header.tsx View File

@@ -23,6 +23,7 @@ import { connect } from 'react-redux';
import Favorite from '../../../../components/controls/Favorite';
import { isLoggedIn } from '../../../../helpers/users';
import { getCurrentUser, Store } from '../../../../store/rootReducer';
import { ProjectAlmBindingResponse } from '../../../../types/alm-settings';
import { BranchLike } from '../../../../types/branch-like';
import BranchLikeNavigation from './branch-like/BranchLikeNavigation';
import CurrentBranchLikeMergeInformation from './branch-like/CurrentBranchLikeMergeInformation';
@@ -33,10 +34,11 @@ export interface HeaderProps {
component: T.Component;
currentBranchLike: BranchLike | undefined;
currentUser: T.CurrentUser;
projectBinding?: ProjectAlmBindingResponse;
}

export function Header(props: HeaderProps) {
const { branchLikes, component, currentBranchLike, currentUser } = props;
const { branchLikes, component, currentBranchLike, currentUser, projectBinding } = props;

return (
<>
@@ -57,6 +59,7 @@ export function Header(props: HeaderProps) {
branchLikes={branchLikes}
component={component}
currentBranchLike={currentBranchLike}
projectBinding={projectBinding}
/>
<CurrentBranchLikeMergeInformation currentBranchLike={currentBranchLike} />
</>

+ 5
- 1
server/sonar-web/src/main/js/app/components/nav/component/branch-like/BranchLikeNavigation.tsx View File

@@ -21,6 +21,7 @@ import * as classNames from 'classnames';
import * as React from 'react';
import Toggler from 'sonar-ui-common/components/controls/Toggler';
import { withAppState } from '../../../../../components/hoc/withAppState';
import { ProjectAlmBindingResponse } from '../../../../../types/alm-settings';
import { BranchLike } from '../../../../../types/branch-like';
import './BranchLikeNavigation.css';
import CurrentBranchLike from './CurrentBranchLike';
@@ -31,6 +32,7 @@ export interface BranchLikeNavigationProps {
branchLikes: BranchLike[];
component: T.Component;
currentBranchLike: BranchLike;
projectBinding?: ProjectAlmBindingResponse;
}

export function BranchLikeNavigation(props: BranchLikeNavigationProps) {
@@ -39,7 +41,8 @@ export function BranchLikeNavigation(props: BranchLikeNavigationProps) {
branchLikes,
component,
component: { configuration },
currentBranchLike
currentBranchLike,
projectBinding
} = props;

const [isMenuOpen, setIsMenuOpen] = React.useState(false);
@@ -54,6 +57,7 @@ export function BranchLikeNavigation(props: BranchLikeNavigationProps) {
component={component}
currentBranchLike={currentBranchLike}
hasManyBranches={hasManyBranches}
projectBinding={projectBinding}
/>
);


+ 22
- 4
server/sonar-web/src/main/js/app/components/nav/component/branch-like/CurrentBranchLike.tsx View File

@@ -22,11 +22,12 @@ import { Link } from 'react-router';
import HelpTooltip from 'sonar-ui-common/components/controls/HelpTooltip';
import DropdownIcon from 'sonar-ui-common/components/icons/DropdownIcon';
import PlusCircleIcon from 'sonar-ui-common/components/icons/PlusCircleIcon';
import { translate } from 'sonar-ui-common/helpers/l10n';
import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n';
import DocumentationTooltip from '../../../../../components/common/DocumentationTooltip';
import BranchLikeIcon from '../../../../../components/icons/BranchLikeIcon';
import { getBranchLikeDisplayName } from '../../../../../helpers/branch-like';
import { getApplicationAdminUrl } from '../../../../../helpers/urls';
import { AlmKeys, ProjectAlmBindingResponse } from '../../../../../types/alm-settings';
import { BranchLike } from '../../../../../types/branch-like';
import { ComponentQualifier } from '../../../../../types/component';
import { colors } from '../../../../theme';
@@ -36,6 +37,7 @@ export interface CurrentBranchLikeProps {
component: T.Component;
currentBranchLike: BranchLike;
hasManyBranches: boolean;
projectBinding?: ProjectAlmBindingResponse;
}

export function CurrentBranchLike(props: CurrentBranchLikeProps) {
@@ -44,12 +46,14 @@ export function CurrentBranchLike(props: CurrentBranchLikeProps) {
component,
component: { configuration },
currentBranchLike,
hasManyBranches
hasManyBranches,
projectBinding
} = props;

const displayName = getBranchLikeDisplayName(currentBranchLike);
const isApplication = component.qualifier === ComponentQualifier.Application;
const canAdminComponent = configuration && configuration.showSettings;
const isGitLab = projectBinding !== undefined && projectBinding.alm === AlmKeys.GitLab;

const additionalIcon = () => {
const plusIcon = <PlusCircleIcon fill={colors.blue} size={12} />;
@@ -79,7 +83,14 @@ export function CurrentBranchLike(props: CurrentBranchLikeProps) {
if (!branchesEnabled) {
return (
<DocumentationTooltip
content={translate('branch_like_navigation.no_branch_support.content')}
content={
projectBinding !== undefined
? translateWithParameters(
`branch_like_navigation.no_branch_support.content_x.${isGitLab ? 'mr' : 'pr'}`,
translate('alm', projectBinding.alm)
)
: translate('branch_like_navigation.no_branch_support.content')
}
data-test="branches-support-disabled"
links={[
{
@@ -87,7 +98,14 @@ export function CurrentBranchLike(props: CurrentBranchLikeProps) {
label: translate('learn_more')
}
]}
title={translate('branch_like_navigation.no_branch_support.title')}>
title={
projectBinding !== undefined
? translate(
'branch_like_navigation.no_branch_support.title',
isGitLab ? 'mr' : 'pr'
)
: translate('branch_like_navigation.no_branch_support.title')
}>
{plusIcon}
</DocumentationTooltip>
);

+ 37
- 14
server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/CurrentBranchLike-test.tsx View File

@@ -19,13 +19,17 @@
*/
import { shallow } from 'enzyme';
import * as React from 'react';
import {
mockProjectGithubBindingResponse,
mockProjectGitLabBindingResponse
} from '../../../../../../helpers/mocks/alm-settings';
import { mockMainBranch } from '../../../../../../helpers/mocks/branch-like';
import { mockComponent } from '../../../../../../helpers/testMocks';
import { ComponentQualifier } from '../../../../../../types/component';
import { CurrentBranchLike, CurrentBranchLikeProps } from '../CurrentBranchLike';

describe('CurrentBranchLikeRenderer should render correctly for application when', () => {
it('there is only one branch and the user can admin the application', () => {
describe('applications', () => {
it('should render correctly when there is only one branch and the user can admin the application', () => {
const wrapper = shallowRender({
component: mockComponent({
configuration: { showSettings: true },
@@ -36,7 +40,7 @@ describe('CurrentBranchLikeRenderer should render correctly for application when
expect(wrapper).toMatchSnapshot();
});

it("there is only one branch and the user CAN'T admin the application", () => {
it("should render correctly when there is only one branch and the user CAN'T admin the application", () => {
const wrapper = shallowRender({
component: mockComponent({
configuration: { showSettings: false },
@@ -47,7 +51,7 @@ describe('CurrentBranchLikeRenderer should render correctly for application when
expect(wrapper).toMatchSnapshot();
});

it('there are many branchlikes', () => {
it('should render correctly when there are many branchlikes', () => {
const wrapper = shallowRender({
branchesEnabled: true,
component: mockComponent({
@@ -59,18 +63,37 @@ describe('CurrentBranchLikeRenderer should render correctly for application when
});
});

describe('CurrentBranchLikeRenderer should render correctly for project when', () => {
it('branches support is disabled', () => {
const wrapper = shallowRender({
branchesEnabled: false,
component: mockComponent({
qualifier: ComponentQualifier.Project
describe('projects', () => {
it('should render correctly when branches support is disabled', () => {
expect(
shallowRender({
branchesEnabled: false,
component: mockComponent({
qualifier: ComponentQualifier.Project
})
})
});
expect(wrapper).toMatchSnapshot();
).toMatchSnapshot('default');
expect(
shallowRender({
branchesEnabled: false,
component: mockComponent({
qualifier: ComponentQualifier.Project
}),
projectBinding: mockProjectGithubBindingResponse()
})
).toMatchSnapshot('alm with prs');
expect(
shallowRender({
branchesEnabled: false,
component: mockComponent({
qualifier: ComponentQualifier.Project
}),
projectBinding: mockProjectGitLabBindingResponse()
})
).toMatchSnapshot('alm with mrs');
});

it('there is only one branchlike', () => {
it('should render correctly when there is only one branchlike', () => {
const wrapper = shallowRender({
branchesEnabled: true,
component: mockComponent({
@@ -81,7 +104,7 @@ describe('CurrentBranchLikeRenderer should render correctly for project when', (
expect(wrapper).toMatchSnapshot();
});

it('there are many branchlikes', () => {
it('should render correctly when there are many branchlikes', () => {
const wrapper = shallowRender({
branchesEnabled: true,
component: mockComponent({

+ 88
- 6
server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/CurrentBranchLike-test.tsx.snap View File

@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`CurrentBranchLikeRenderer should render correctly for application when there are many branchlikes 1`] = `
exports[`applications should render correctly when there are many branchlikes 1`] = `
<span
className="display-flex-center flex-shrink text-ellipsis"
>
@@ -24,7 +24,7 @@ exports[`CurrentBranchLikeRenderer should render correctly for application when
</span>
`;

exports[`CurrentBranchLikeRenderer should render correctly for application when there is only one branch and the user CAN'T admin the application 1`] = `
exports[`applications should render correctly when there is only one branch and the user CAN'T admin the application 1`] = `
<span
className="display-flex-center flex-shrink text-ellipsis"
>
@@ -47,7 +47,7 @@ exports[`CurrentBranchLikeRenderer should render correctly for application when
</span>
`;

exports[`CurrentBranchLikeRenderer should render correctly for application when there is only one branch and the user can admin the application 1`] = `
exports[`applications should render correctly when there is only one branch and the user can admin the application 1`] = `
<span
className="display-flex-center flex-shrink text-ellipsis"
>
@@ -101,7 +101,89 @@ exports[`CurrentBranchLikeRenderer should render correctly for application when
</span>
`;

exports[`CurrentBranchLikeRenderer should render correctly for project when branches support is disabled 1`] = `
exports[`projects should render correctly when branches support is disabled: alm with mrs 1`] = `
<span
className="display-flex-center flex-shrink text-ellipsis"
>
<BranchLikeIcon
branchLike={
Object {
"analysisDate": "2018-01-01",
"excludedFromPurge": true,
"isMain": true,
"name": "master",
}
}
/>
<span
className="spacer-left spacer-right flex-shrink text-ellipsis js-branch-like-name"
title="master"
>
master
</span>
<DocumentationTooltip
content="branch_like_navigation.no_branch_support.content_x.mr.alm.gitlab"
data-test="branches-support-disabled"
links={
Array [
Object {
"href": "https://redirect.sonarsource.com/editions/developer.html",
"label": "learn_more",
},
]
}
title="branch_like_navigation.no_branch_support.title.mr"
>
<PlusCircleIcon
fill="#4b9fd5"
size={12}
/>
</DocumentationTooltip>
</span>
`;

exports[`projects should render correctly when branches support is disabled: alm with prs 1`] = `
<span
className="display-flex-center flex-shrink text-ellipsis"
>
<BranchLikeIcon
branchLike={
Object {
"analysisDate": "2018-01-01",
"excludedFromPurge": true,
"isMain": true,
"name": "master",
}
}
/>
<span
className="spacer-left spacer-right flex-shrink text-ellipsis js-branch-like-name"
title="master"
>
master
</span>
<DocumentationTooltip
content="branch_like_navigation.no_branch_support.content_x.pr.alm.github"
data-test="branches-support-disabled"
links={
Array [
Object {
"href": "https://redirect.sonarsource.com/editions/developer.html",
"label": "learn_more",
},
]
}
title="branch_like_navigation.no_branch_support.title.pr"
>
<PlusCircleIcon
fill="#4b9fd5"
size={12}
/>
</DocumentationTooltip>
</span>
`;

exports[`projects should render correctly when branches support is disabled: default 1`] = `
<span
className="display-flex-center flex-shrink text-ellipsis"
>
@@ -142,7 +224,7 @@ exports[`CurrentBranchLikeRenderer should render correctly for project when bran
</span>
`;

exports[`CurrentBranchLikeRenderer should render correctly for project when there are many branchlikes 1`] = `
exports[`projects should render correctly when there are many branchlikes 1`] = `
<span
className="display-flex-center flex-shrink text-ellipsis"
>
@@ -166,7 +248,7 @@ exports[`CurrentBranchLikeRenderer should render correctly for project when ther
</span>
`;

exports[`CurrentBranchLikeRenderer should render correctly for project when there is only one branchlike 1`] = `
exports[`projects should render correctly when there is only one branchlike 1`] = `
<span
className="display-flex-center flex-shrink text-ellipsis"
>

+ 4
- 1
server/sonar-web/src/main/js/apps/overview/components/App.tsx View File

@@ -22,6 +22,7 @@ import { lazyLoadComponent } from 'sonar-ui-common/components/lazyLoadComponent'
import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
import { Router, withRouter } from '../../../components/hoc/withRouter';
import { isPullRequest } from '../../../helpers/branch-like';
import { ProjectAlmBindingResponse } from '../../../types/alm-settings';
import { BranchLike } from '../../../types/branch-like';
import { isPortfolioLike } from '../../../types/component';
import BranchOverview from '../branches/BranchOverview';
@@ -35,6 +36,7 @@ interface Props {
component: T.Component;
isInProgress?: boolean;
isPending?: boolean;
projectBinding?: ProjectAlmBindingResponse;
router: Pick<Router, 'replace'>;
}

@@ -44,7 +46,7 @@ export class App extends React.PureComponent<Props> {
};

render() {
const { branchLike, branchLikes, component } = this.props;
const { branchLike, branchLikes, component, projectBinding } = this.props;

if (this.isPortfolio()) {
return null;
@@ -65,6 +67,7 @@ export class App extends React.PureComponent<Props> {
branchLikes={branchLikes}
component={component}
hasAnalyses={this.props.isPending || this.props.isInProgress}
projectBinding={projectBinding}
/>
) : (
<BranchOverview branch={branchLike} component={component} />

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

@@ -26,19 +26,21 @@ import TutorialSelection from '../../../components/tutorials/TutorialSelection';
import { getBranchLikeDisplayName, isBranch, isMainBranch } from '../../../helpers/branch-like';
import { isLoggedIn } from '../../../helpers/users';
import { getCurrentUser, Store } from '../../../store/rootReducer';
import { ProjectAlmBindingResponse } from '../../../types/alm-settings';
import { BranchLike } from '../../../types/branch-like';
import { ComponentQualifier } from '../../../types/component';

interface Props {
export interface EmptyOverviewProps {
branchLike?: BranchLike;
branchLikes: BranchLike[];
component: T.Component;
currentUser: T.CurrentUser;
hasAnalyses?: boolean;
projectBinding?: ProjectAlmBindingResponse;
}

export function EmptyOverview(props: Props) {
const { branchLike, branchLikes, component, currentUser, hasAnalyses } = props;
export function EmptyOverview(props: EmptyOverviewProps) {
const { branchLike, branchLikes, component, currentUser, hasAnalyses, projectBinding } = props;

if (component.qualifier === ComponentQualifier.Application) {
return (
@@ -87,7 +89,13 @@ export function EmptyOverview(props: Props) {
{isLoggedIn(currentUser) ? (
<>
{showWarning && <Alert variant="warning">{warning}</Alert>}
{showTutorial && <TutorialSelection component={component} currentUser={currentUser} />}
{showTutorial && (
<TutorialSelection
component={component}
currentUser={currentUser}
projectBinding={projectBinding}
/>
)}
</>
) : (
<Alert variant="warning">{warning}</Alert>

+ 5
- 3
server/sonar-web/src/main/js/apps/overview/components/__tests__/EmptyOverview-test.tsx View File

@@ -19,15 +19,17 @@
*/
import { shallow } from 'enzyme';
import * as React from 'react';
import { mockProjectGithubBindingResponse } from '../../../../helpers/mocks/alm-settings';
import { mockBranch, mockMainBranch, mockPullRequest } from '../../../../helpers/mocks/branch-like';
import { mockComponent, mockCurrentUser, mockLoggedInUser } from '../../../../helpers/testMocks';
import { ComponentQualifier } from '../../../../types/component';
import { EmptyOverview } from '../EmptyOverview';
import { EmptyOverview, EmptyOverviewProps } from '../EmptyOverview';

it('renders correctly', () => {
expect(shallowRender()).toMatchSnapshot();
expect(shallowRender({ hasAnalyses: true })).toMatchSnapshot();
expect(shallowRender({ currentUser: mockCurrentUser() })).toMatchSnapshot();
expect(shallowRender({ projectBinding: mockProjectGithubBindingResponse() })).toMatchSnapshot();
});

it('should render another message when there are branches', () => {
@@ -49,8 +51,8 @@ it('should not render the tutorial for applications', () => {
).toMatchSnapshot();
});

function shallowRender(props = {}) {
return shallow(
function shallowRender(props: Partial<EmptyOverviewProps> = {}) {
return shallow<EmptyOverviewProps>(
<EmptyOverview
branchLike={mockMainBranch()}
branchLikes={[mockMainBranch()]}

+ 49
- 0
server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/EmptyOverview-test.tsx.snap View File

@@ -67,6 +67,55 @@ exports[`renders correctly 3`] = `
</div>
`;

exports[`renders correctly 4`] = `
<div
className="page page-limited"
>
<withRouter(TutorialSelection)
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 [],
"version": "0.0.1",
}
}
currentUser={
Object {
"groups": Array [],
"isLoggedIn": true,
"login": "luke",
"name": "Skywalker",
"scmAccounts": Array [],
}
}
projectBinding={
Object {
"alm": "github",
"key": "foo",
"monorepo": true,
"repository": "PROJECT_KEY",
}
}
/>
</div>
`;

exports[`should not render the tutorial for applications 1`] = `
<div
className="page page-limited"

+ 8
- 2
server/sonar-web/src/main/js/apps/tutorials/components/TutorialsApp.tsx View File

@@ -22,14 +22,16 @@ import handleRequiredAuthentication from 'sonar-ui-common/helpers/handleRequired
import { withCurrentUser } from '../../../components/hoc/withCurrentUser';
import TutorialSelection from '../../../components/tutorials/TutorialSelection';
import { isLoggedIn } from '../../../helpers/users';
import { ProjectAlmBindingResponse } from '../../../types/alm-settings';

export interface TutorialsAppProps {
component: T.Component;
currentUser: T.CurrentUser;
projectBinding?: ProjectAlmBindingResponse;
}

export function TutorialsApp(props: TutorialsAppProps) {
const { component, currentUser } = props;
const { component, currentUser, projectBinding } = props;

if (!isLoggedIn(currentUser)) {
handleRequiredAuthentication();
@@ -38,7 +40,11 @@ export function TutorialsApp(props: TutorialsAppProps) {

return (
<div className="page page-limited">
<TutorialSelection component={component} currentUser={currentUser} />
<TutorialSelection
component={component}
currentUser={currentUser}
projectBinding={projectBinding}
/>
</div>
);
}

+ 2
- 0
server/sonar-web/src/main/js/apps/tutorials/components/__tests__/TutorialsApp-test.tsx View File

@@ -20,6 +20,7 @@
import { shallow } from 'enzyme';
import * as React from 'react';
import handleRequiredAuthentication from 'sonar-ui-common/helpers/handleRequiredAuthentication';
import { mockProjectAzureBindingResponse } from '../../../../helpers/mocks/alm-settings';
import { mockComponent, mockCurrentUser, mockLoggedInUser } from '../../../../helpers/testMocks';
import { TutorialsApp, TutorialsAppProps } from '../TutorialsApp';

@@ -27,6 +28,7 @@ jest.mock('sonar-ui-common/helpers/handleRequiredAuthentication', () => ({ defau

it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot();
expect(shallowRender({ projectBinding: mockProjectAzureBindingResponse() })).toMatchSnapshot();
});

it('should redirect if user is not logged in', () => {

+ 50
- 0
server/sonar-web/src/main/js/apps/tutorials/components/__tests__/__snapshots__/TutorialsApp-test.tsx.snap View File

@@ -39,3 +39,53 @@ exports[`should render correctly 1`] = `
/>
</div>
`;

exports[`should render correctly 2`] = `
<div
className="page page-limited"
>
<withRouter(TutorialSelection)
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 [],
}
}
currentUser={
Object {
"groups": Array [],
"isLoggedIn": true,
"login": "luke",
"name": "Skywalker",
"scmAccounts": Array [],
}
}
projectBinding={
Object {
"alm": "azure",
"key": "foo",
"monorepo": false,
"repository": "REPOSITORY_NAME",
"slug": "PROJECT_NAME",
"url": "https://ado.my_company.com/mycollection",
}
}
/>
</div>
`;

+ 11
- 15
server/sonar-web/src/main/js/components/tutorials/TutorialSelection.tsx View File

@@ -20,7 +20,7 @@
import * as React from 'react';
import { WithRouterProps } from 'react-router';
import { getHostUrl } from 'sonar-ui-common/helpers/urls';
import { getAlmDefinitionsNoCatch, getProjectAlmBinding } from '../../api/alm-settings';
import { getAlmDefinitionsNoCatch } from '../../api/alm-settings';
import { getValues } from '../../api/settings';
import { AlmBindingDefinition, ProjectAlmBindingResponse } from '../../types/alm-settings';
import { SettingsKey } from '../../types/settings';
@@ -31,6 +31,7 @@ import { TutorialModes } from './types';
interface Props extends Pick<WithRouterProps, 'router' | 'location'> {
component: T.Component;
currentUser: T.LoggedInUser;
projectBinding?: ProjectAlmBindingResponse;
}

interface State {
@@ -38,7 +39,6 @@ interface State {
baseUrl: string;
forceManual: boolean;
loading: boolean;
projectBinding?: ProjectAlmBindingResponse;
}

export class TutorialSelection extends React.PureComponent<Props, State> {
@@ -64,23 +64,19 @@ export class TutorialSelection extends React.PureComponent<Props, State> {
}

fetchAlmBindings = async () => {
const { component } = this.props;
const { projectBinding } = this.props;

const [almDefinitions, projectBinding] = await Promise.all([
getAlmDefinitionsNoCatch().catch(() => undefined),
getProjectAlmBinding(component.key).catch(() => undefined)
]);

if (this.mounted) {
if (projectBinding === undefined) {
this.setState({ forceManual: true });
} else {
if (projectBinding === undefined) {
this.setState({ forceManual: true });
} else {
const almDefinitions = await getAlmDefinitionsNoCatch().catch(() => undefined);
if (this.mounted) {
let almBinding;
if (almDefinitions !== undefined) {
const specificDefinitions = almDefinitions[projectBinding.alm] as AlmBindingDefinition[];
almBinding = specificDefinitions.find(d => d.key === projectBinding.key);
}
this.setState({ almBinding, forceManual: false, projectBinding });
this.setState({ almBinding, forceManual: false });
}
}
};
@@ -106,8 +102,8 @@ export class TutorialSelection extends React.PureComponent<Props, State> {
};

render() {
const { component, currentUser, location } = this.props;
const { almBinding, baseUrl, forceManual, loading, projectBinding } = this.state;
const { component, currentUser, location, projectBinding } = this.props;
const { almBinding, baseUrl, forceManual, loading } = this.state;

const selectedTutorial: TutorialModes | undefined = forceManual
? TutorialModes.Manual

+ 7
- 7
server/sonar-web/src/main/js/components/tutorials/__tests__/TutorialSelection-test.tsx View File

@@ -21,9 +21,12 @@ import { shallow } from 'enzyme';
import * as React from 'react';
import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils';
import { getHostUrl } from 'sonar-ui-common/helpers/urls';
import { getAlmDefinitionsNoCatch, getProjectAlmBinding } from '../../../api/alm-settings';
import { getAlmDefinitionsNoCatch } from '../../../api/alm-settings';
import { getValues } from '../../../api/settings';
import { mockBitbucketBindingDefinition } from '../../../helpers/mocks/alm-settings';
import {
mockBitbucketBindingDefinition,
mockProjectBitbucketBindingResponse
} from '../../../helpers/mocks/alm-settings';
import {
mockComponent,
mockLocation,
@@ -40,7 +43,6 @@ jest.mock('sonar-ui-common/helpers/urls', () => ({
}));

jest.mock('../../../api/alm-settings', () => ({
getProjectAlmBinding: jest.fn().mockRejectedValue(null),
getAlmDefinitionsNoCatch: jest.fn().mockRejectedValue(null)
}));

@@ -61,8 +63,7 @@ it('should select manual if project is not bound', async () => {
});

it('should not select anything if project is bound', async () => {
(getProjectAlmBinding as jest.Mock).mockResolvedValueOnce({ alm: AlmKeys.BitbucketServer });
const wrapper = shallowRender();
const wrapper = shallowRender({ projectBinding: mockProjectBitbucketBindingResponse() });
await waitAndUpdate(wrapper);
expect(wrapper.state().forceManual).toBe(false);
});
@@ -70,11 +71,10 @@ it('should not select anything if project is bound', async () => {
it('should correctly find the global ALM binding definition', async () => {
const key = 'foo';
const almBinding = mockBitbucketBindingDefinition({ key });
(getProjectAlmBinding as jest.Mock).mockResolvedValueOnce({ alm: AlmKeys.BitbucketServer, key });
(getAlmDefinitionsNoCatch as jest.Mock).mockResolvedValueOnce({
[AlmKeys.BitbucketServer]: [almBinding]
});
const wrapper = shallowRender();
const wrapper = shallowRender({ projectBinding: mockProjectBitbucketBindingResponse({ key }) });
await waitAndUpdate(wrapper);
expect(wrapper.state().almBinding).toBe(almBinding);
});

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

@@ -3756,8 +3756,12 @@ branch_like_navigation.pull_requests=Pull Requests
branch_like_navigation.orphan_pull_requests=Orphan Pull Requests
branch_like_navigation.orphan_pull_requests.tooltip=When the base of a Pull Request is deleted, this Pull Request becomes orphan.
branch_like_navigation.for_merge_into_x_from_y=for merge into {target} from {branch}
branch_like_navigation.no_branch_support.title=Get the most out of SonarQube with branches analysis
branch_like_navigation.no_branch_support.content=Analyze each branch of your project separately with the Developer Edition.
branch_like_navigation.no_branch_support.title=Get the most out of SonarQube with branch and PR/MR analysis
branch_like_navigation.no_branch_support.title.pr=Get the most out of SonarQube with branch and PR analysis
branch_like_navigation.no_branch_support.title.mr=Get the most out of SonarQube with branch and MR analysis
branch_like_navigation.no_branch_support.content=With Developer Edition you can analyze every pull/merge request. You can also watch the quality of your release branches by analyzing each one individually in SonarQube.
branch_like_navigation.no_branch_support.content_x.pr=With Developer Edition you can analyze every pull request and get the results in {0}. You can also watch the quality of your release branches by analyzing each one individually in SonarQube.
branch_like_navigation.no_branch_support.content_x.mr=With Developer Edition you can analyze every merge request and get the results in {0}. You can also watch the quality of your release branches by analyzing each one individually in SonarQube.
branch_like_navigation.only_one_branch.title=Learn how to analyze branches in SonarQube
branch_like_navigation.only_one_branch.content=Quickly setup branch analysis and get separate insights for each of your branches and Pull Requests.
branch_like_navigation.only_one_branch.documentation=Branches documentation

Loading…
Cancel
Save