Browse Source

SONAR-10994 Connect ComponentContainer component to new branch store

tags/7.8
Wouter Admiraal 5 years ago
parent
commit
c898d85845

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

@@ -27,7 +27,11 @@ import { getBranches, getPullRequests } from '../../api/branches';
import { getTasksForComponent, getAnalysisStatus } from '../../api/ce';
import { getComponentData } from '../../api/components';
import { getComponentNavigation } from '../../api/nav';
import { fetchOrganization, requireAuthorization } from '../../store/rootActions';
import {
fetchOrganization,
requireAuthorization,
registerBranchStatus
} from '../../store/rootActions';
import { STATUSES } from '../../apps/background-tasks/constants';
import {
isPullRequest,
@@ -44,6 +48,7 @@ interface Props {
children: React.ReactElement<any>;
fetchOrganization: (organization: string) => void;
location: Pick<Location, 'query'>;
registerBranchStatus: (branchLike: T.BranchLike, component: string, status: T.Status) => void;
requireAuthorization: (router: Pick<Router, 'replace'>) => void;
router: Pick<Router, 'replace'>;
}
@@ -140,28 +145,26 @@ export class ComponentContainer extends React.PureComponent<Props, State> {
branchLikes: T.BranchLike[];
component: T.Component;
}> => {
const application = component.breadcrumbs.find(({ qualifier }) => qualifier === 'APP');
if (application) {
return getBranches(application.key).then(branchLikes => {
return {
branchLike: this.getCurrentBranchLike(branchLikes),
branchLikes,
component
};
const breadcrumb = component.breadcrumbs.find(({ qualifier }) => {
return ['APP', 'TRK'].includes(qualifier);
});

if (breadcrumb) {
const { key } = breadcrumb;
return Promise.all([
getBranches(key),
breadcrumb.qualifier === 'APP' ? Promise.resolve([]) : getPullRequests(key)
]).then(([branches, pullRequests]) => {
const branchLikes = [...branches, ...pullRequests];
const branchLike = this.getCurrentBranchLike(branchLikes);

this.registerBranchStatuses(branchLikes, component);

return { branchLike, branchLikes, component };
});
} else {
return Promise.resolve({ branchLikes: [], component });
}
const project = component.breadcrumbs.find(({ qualifier }) => qualifier === 'TRK');
if (project) {
return Promise.all([getBranches(project.key), getPullRequests(project.key)]).then(
([branches, pullRequests]) => {
const branchLikes = [...branches, ...pullRequests];
const branchLike = this.getCurrentBranchLike(branchLikes);
return { branchLike, branchLikes, component };
}
);
}

return Promise.resolve({ branchLikes: [], component });
};

fetchStatus = (component: T.Component) => {
@@ -267,6 +270,18 @@ export class ComponentContainer extends React.PureComponent<Props, State> {
return !task.branch && !task.pullRequest;
};

registerBranchStatuses = (branchLikes: T.BranchLike[], component: T.Component) => {
branchLikes.forEach(branchLike => {
if (branchLike.status) {
this.props.registerBranchStatus(
branchLike,
component.key,
branchLike.status.qualityGateStatus
);
}
});
};

handleComponentChange = (changes: Partial<T.Component>) => {
if (this.mounted) {
this.setState(state => {
@@ -304,20 +319,19 @@ export class ComponentContainer extends React.PureComponent<Props, State> {

return (
<div>
{component &&
!['FIL', 'UTS'].includes(component.qualifier) && (
<ComponentNav
branchLikes={branchLikes}
component={component}
currentBranchLike={branchLike}
currentTask={currentTask}
currentTaskOnSameBranch={currentTask && this.isSameBranch(currentTask, branchLike)}
isInProgress={isInProgress}
isPending={isPending}
location={this.props.location}
warnings={this.state.warnings}
/>
)}
{component && !['FIL', 'UTS'].includes(component.qualifier) && (
<ComponentNav
branchLikes={branchLikes}
component={component}
currentBranchLike={branchLike}
currentTask={currentTask}
currentTaskOnSameBranch={currentTask && this.isSameBranch(currentTask, branchLike)}
isInProgress={isInProgress}
isPending={isPending}
location={this.props.location}
warnings={this.state.warnings}
/>
)}
{loading ? (
<div className="page page-limited">
<i className="spinner" />
@@ -340,7 +354,7 @@ export class ComponentContainer extends React.PureComponent<Props, State> {
}
}

const mapDispatchToProps = { fetchOrganization, requireAuthorization };
const mapDispatchToProps = { fetchOrganization, registerBranchStatus, requireAuthorization };

export default withRouter(
connect(

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

@@ -19,7 +19,6 @@
*/
import * as React from 'react';
import { shallow } from 'enzyme';
import { Location } from 'history';
import { ComponentContainer } from '../ComponentContainer';
import { getBranches, getPullRequests } from '../../../api/branches';
import { getTasksForComponent } from '../../../api/ce';
@@ -27,12 +26,33 @@ import { getComponentData } from '../../../api/components';
import { getComponentNavigation } from '../../../api/nav';
import { STATUSES } from '../../../apps/background-tasks/constants';
import { waitAndUpdate } from '../../../helpers/testUtils';
import { getMeasures } from '../../../api/measures';
import { isSonarCloud } from '../../../helpers/system';
import { mockLocation, mockRouter, mockComponent } from '../../../helpers/testMocks';

jest.mock('../../../api/branches', () => ({
getBranches: jest.fn().mockResolvedValue([]),
getPullRequests: jest.fn().mockResolvedValue([])
getBranches: jest.fn().mockResolvedValue([
{
isMain: true,
name: 'master',
type: 'LONG',
status: { qualityGateStatus: 'OK' }
}
]),
getPullRequests: jest.fn().mockResolvedValue([
{
base: 'feature',
branch: 'feature',
key: 'pr-89',
title: 'PR Feature',
status: { qualityGateStatus: 'ERROR' }
},
{
base: 'feature',
branch: 'feature',
key: 'pr-90',
title: 'PR Feature 2'
}
])
}));

jest.mock('../../../api/ce', () => ({
@@ -44,15 +64,6 @@ jest.mock('../../../api/components', () => ({
getComponentData: jest.fn().mockResolvedValue({ analysisDate: '2018-07-30' })
}));

jest.mock('../../../api/measures', () => ({
getMeasures: jest
.fn()
.mockResolvedValue([
{ metric: 'new_coverage', value: '0', periods: [{ index: 1, value: '95.9943' }] },
{ metric: 'new_duplicated_lines_density', periods: [{ index: 1, value: '3.5' }] }
])
}));

jest.mock('../../../api/nav', () => ({
getComponentNavigation: jest.fn().mockResolvedValue({
breadcrumbs: [{ key: 'portfolioKey', name: 'portfolio', qualifier: 'VW' }],
@@ -61,7 +72,7 @@ jest.mock('../../../api/nav', () => ({
}));

jest.mock('../../../helpers/system', () => ({
isSonarCloud: jest.fn()
isSonarCloud: jest.fn().mockReturnValue(false)
}));

// mock this, because some of its children are using redux store
@@ -74,13 +85,7 @@ const Inner = () => <div />;
const mainBranch: T.MainBranch = { isMain: true, name: 'master' };

beforeEach(() => {
(getBranches as jest.Mock).mockClear();
(getPullRequests as jest.Mock).mockClear();
(getComponentData as jest.Mock).mockClear();
(getComponentNavigation as jest.Mock).mockClear();
(getTasksForComponent as jest.Mock).mockClear();
(getMeasures as jest.Mock).mockClear();
(isSonarCloud as jest.Mock).mockReturnValue(false).mockClear();
jest.clearAllMocks();
});

it('changes component', () => {
@@ -96,7 +101,7 @@ it('changes component', () => {
});

it("doesn't load branches portfolio", async () => {
const wrapper = shallowRender({ location: { query: { id: 'portfolioKey' } } as Location });
const wrapper = shallowRender({ location: mockLocation({ query: { id: 'portfolioKey' } }) });
await new Promise(setImmediate);
expect(getBranches).not.toBeCalled();
expect(getPullRequests).not.toBeCalled();
@@ -106,18 +111,24 @@ it("doesn't load branches portfolio", async () => {
expect(wrapper.find(Inner).exists()).toBeTruthy();
});

it('updates branches on change', () => {
const wrapper = shallowRender({ location: { query: { id: 'portfolioKey' } } as Location });
it('updates branches on change', async () => {
const registerBranchStatus = jest.fn();
const wrapper = shallowRender({
location: mockLocation({ query: { id: 'portfolioKey' } }),
registerBranchStatus
});
wrapper.setState({
branchLikes: [mainBranch],
component: {
component: mockComponent({
breadcrumbs: [{ key: 'projectKey', name: 'project', qualifier: 'TRK' }]
} as T.Component,
}),
loading: false
});
wrapper.find(Inner).prop<Function>('onBranchesChange')();
expect(getBranches).toBeCalledWith('projectKey');
expect(getPullRequests).toBeCalledWith('projectKey');
await waitAndUpdate(wrapper);
expect(registerBranchStatus).toBeCalledTimes(2);
});

it('loads organization', async () => {
@@ -216,13 +227,29 @@ it('reload component after task progress finished', async () => {
expect(getTasksForComponent).toHaveBeenCalledTimes(3);
});

it('should show component not found if it does not exist', async () => {
(getComponentNavigation as jest.Mock).mockRejectedValue({ status: 404 });
const wrapper = shallowRender();
await waitAndUpdate(wrapper);
expect(wrapper).toMatchSnapshot();
});

it('should redirect if the user has no access', async () => {
(getComponentNavigation as jest.Mock).mockRejectedValue({ status: 403 });
const requireAuthorization = jest.fn();
const wrapper = shallowRender({ requireAuthorization });
await waitAndUpdate(wrapper);
expect(requireAuthorization).toBeCalled();
});

function shallowRender(props: Partial<ComponentContainer['props']> = {}) {
return shallow<ComponentContainer>(
<ComponentContainer
fetchOrganization={jest.fn()}
location={{ query: { id: 'foo' } }}
location={mockLocation({ query: { id: 'foo' } })}
registerBranchStatus={jest.fn()}
requireAuthorization={jest.fn()}
router={{ replace: jest.fn() }}
router={mockRouter()}
{...props}>
<Inner />
</ComponentContainer>

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

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

exports[`should show component not found if it does not exist 1`] = `<ComponentContainerNotFound />`;

Loading…
Cancel
Save