浏览代码

fix governance pages when using branches (#2446)

tags/6.6-RC1
Stas Vilchik 6 年前
父节点
当前提交
8e2eb6188b

server/sonar-web/src/main/js/app/components/ProjectContainer.tsx → server/sonar-web/src/main/js/app/components/ComponentContainer.tsx 查看文件

@@ -18,7 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import ProjectContainerNotFound from './ProjectContainerNotFound';
import ComponentContainerNotFound from './ComponentContainerNotFound';
import ComponentNav from './nav/component/ComponentNav';
import { Branch, Component } from '../types';
import handleRequiredAuthorization from '../utils/handleRequiredAuthorization';
@@ -39,7 +39,7 @@ interface State {
component: Component | null;
}

export default class ProjectContainer extends React.PureComponent<Props, State> {
export default class ComponentContainer extends React.PureComponent<Props, State> {
mounted: boolean;

constructor(props: Props) {
@@ -49,12 +49,12 @@ export default class ProjectContainer extends React.PureComponent<Props, State>

componentDidMount() {
this.mounted = true;
this.fetchProject();
this.fetchComponent();
}

componentDidUpdate(prevProps: Props) {
if (prevProps.location.query.id !== this.props.location.query.id) {
this.fetchProject();
this.fetchComponent();
}
}

@@ -67,7 +67,7 @@ export default class ProjectContainer extends React.PureComponent<Props, State>
qualifier: component.breadcrumbs[component.breadcrumbs.length - 1].qualifier
});

fetchProject() {
fetchComponent() {
const { branch, id } = this.props.location.query;
this.setState({ loading: true });

@@ -96,7 +96,7 @@ export default class ProjectContainer extends React.PureComponent<Props, State>
return project ? getBranches(project.key) : Promise.resolve([]);
};

handleProjectChange = (changes: {}) => {
handleComponentChange = (changes: {}) => {
if (this.mounted) {
this.setState(state => ({ component: { ...state.component, ...changes } }));
}
@@ -123,12 +123,11 @@ export default class ProjectContainer extends React.PureComponent<Props, State>
return <i className="spinner" />;
}

const branch = branches.find(b => (query.branch ? b.name === query.branch : b.isMain));

if (!component || !branch) {
return <ProjectContainerNotFound />;
if (!component) {
return <ComponentContainerNotFound />;
}

const branch = branches.find(b => (query.branch ? b.name === query.branch : b.isMain));
const isFile = ['FIL', 'UTS'].includes(component.qualifier);
const configuration = component.configuration || {};

@@ -147,7 +146,7 @@ export default class ProjectContainer extends React.PureComponent<Props, State>
branches,
component: component,
onBranchesChange: this.handleBranchesChange,
onComponentChange: this.handleProjectChange
onComponentChange: this.handleComponentChange
})}
</div>
);

server/sonar-web/src/main/js/app/components/ProjectContainerNotFound.tsx → server/sonar-web/src/main/js/app/components/ComponentContainerNotFound.tsx 查看文件

@@ -21,7 +21,7 @@ import * as React from 'react';
import { Link } from 'react-router';
import { translate } from '../../helpers/l10n';

export default class ProjectContainerNotFound extends React.PureComponent {
export default class ComponentContainerNotFound extends React.PureComponent {
componentDidMount() {
const html = document.querySelector('html');
if (html) {

server/sonar-web/src/main/js/app/components/__tests__/ProjectContainer-test.tsx → server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx 查看文件

@@ -21,14 +21,21 @@ jest.mock('../../../api/branches', () => ({ getBranches: jest.fn() }));
jest.mock('../../../api/components', () => ({ getComponentData: jest.fn() }));
jest.mock('../../../api/nav', () => ({ getComponentNavigation: jest.fn() }));

// mock this, because some of its children are using redux store
jest.mock('../nav/component/ComponentNav', () => ({
default: () => null
}));

import * as React from 'react';
import { shallow, mount } from 'enzyme';
import ProjectContainer from '../ProjectContainer';
import ComponentContainer from '../ComponentContainer';
import { getBranches } from '../../../api/branches';
import { getComponentData } from '../../../api/components';
import { getComponentNavigation } from '../../../api/nav';
import { doAsync } from '../../../helpers/testUtils';

const Inner = () => <div />;

beforeEach(() => {
(getBranches as jest.Mock<any>).mockClear();
(getComponentData as jest.Mock<any>).mockClear();
@@ -36,14 +43,12 @@ beforeEach(() => {
});

it('changes component', () => {
const Inner = () => <div />;

const wrapper = shallow(
<ProjectContainer location={{ query: { id: 'foo' } }}>
<ComponentContainer location={{ query: { id: 'foo' } }}>
<Inner />
</ProjectContainer>
</ComponentContainer>
);
(wrapper.instance() as ProjectContainer).mounted = true;
(wrapper.instance() as ComponentContainer).mounted = true;
wrapper.setState({
branches: [{ isMain: true }],
component: { qualifier: 'TRK', visibility: 'public' },
@@ -67,9 +72,9 @@ it("loads branches for module's project", () => {
);

mount(
<ProjectContainer location={{ query: { id: 'moduleKey' } }}>
<div />
</ProjectContainer>
<ComponentContainer location={{ query: { id: 'moduleKey' } }}>
<Inner />
</ComponentContainer>
);

return doAsync().then(() => {
@@ -88,28 +93,28 @@ it("doesn't load branches portfolio", () => {
})
);

mount(
<ProjectContainer location={{ query: { id: 'portfolioKey' } }}>
<div />
</ProjectContainer>
const wrapper = mount(
<ComponentContainer location={{ query: { id: 'portfolioKey' } }}>
<Inner />
</ComponentContainer>
);

return doAsync().then(() => {
expect(getBranches).not.toBeCalled();
expect(getComponentData).toBeCalledWith('portfolioKey', undefined);
expect(getComponentNavigation).toBeCalledWith('portfolioKey');
expect(wrapper.find(Inner).exists()).toBeTruthy();
});
});

it('updates branches on change', () => {
(getBranches as jest.Mock<any>).mockImplementation(() => Promise.resolve([]));
const Inner = () => <div />;
const wrapper = shallow(
<ProjectContainer location={{ query: { id: 'portfolioKey' } }}>
<ComponentContainer location={{ query: { id: 'portfolioKey' } }}>
<Inner />
</ProjectContainer>
</ComponentContainer>
);
(wrapper.instance() as ProjectContainer).mounted = true;
(wrapper.instance() as ComponentContainer).mounted = true;
wrapper.setState({
branches: [{ isMain: true }],
component: { breadcrumbs: [{ key: 'projectKey', name: 'project', qualifier: 'TRK' }] },

server/sonar-web/src/main/js/app/components/extensions/ViewDashboard.js → server/sonar-web/src/main/js/app/components/extensions/PortfolioDashboard.tsx 查看文件

@@ -17,14 +17,19 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import * as React from 'react';
import ProjectPageExtension from './ProjectPageExtension';
import { Component } from '../../types';

export default function ViewDashboard(props /*: Object */) {
interface Props {
component: Component;
location: { query: { id: string } };
}

export default function PortfolioDashboard(props: Props) {
return (
<ProjectPageExtension
location={props.location}
{...props}
params={{ pluginKey: 'governance', extensionKey: 'governance' }}
/>
);

+ 9
- 8
server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.tsx 查看文件

@@ -32,7 +32,7 @@ import './ComponentNav.css';

interface Props {
branches: Branch[];
currentBranch: Branch;
currentBranch?: Branch;
component: Component;
conf: ComponentConfiguration;
location: {};
@@ -99,13 +99,14 @@ export default class ComponentNav extends React.PureComponent<Props, State> {
breadcrumbs={this.props.component.breadcrumbs}
/>

<ComponentNavBranch
branches={this.props.branches}
currentBranch={this.props.currentBranch}
// to close dropdown on any location change
location={this.props.location}
project={this.props.component}
/>
{this.props.currentBranch &&
<ComponentNavBranch
branches={this.props.branches}
currentBranch={this.props.currentBranch}
// to close dropdown on any location change
location={this.props.location}
project={this.props.component}
/>}

<ComponentNavMeta
branch={this.props.currentBranch}

+ 27
- 12
server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.tsx 查看文件

@@ -46,7 +46,7 @@ const SETTINGS_URLS = [
];

interface Props {
branch: Branch;
branch?: Branch;
component: Component;
conf: ComponentConfiguration;
location?: any;
@@ -75,7 +75,7 @@ export default class ComponentNavMenu extends React.PureComponent<Props> {
}

renderDashboardLink() {
if (isShortLivingBranch(this.props.branch)) {
if (this.props.branch && isShortLivingBranch(this.props.branch)) {
return null;
}

@@ -85,7 +85,10 @@ export default class ComponentNavMenu extends React.PureComponent<Props> {
<Link
to={{
pathname,
query: { branch: getBranchName(this.props.branch), id: this.props.component.key }
query: {
branch: this.props.branch && getBranchName(this.props.branch),
id: this.props.component.key
}
}}
activeClassName="active">
{translate('overview.page')}
@@ -104,7 +107,10 @@ export default class ComponentNavMenu extends React.PureComponent<Props> {
<Link
to={{
pathname: '/code',
query: { branch: getBranchName(this.props.branch), id: this.props.component.key }
query: {
branch: this.props.branch && getBranchName(this.props.branch),
id: this.props.component.key
}
}}
activeClassName="active">
{this.isView() || this.isApplication()
@@ -120,7 +126,7 @@ export default class ComponentNavMenu extends React.PureComponent<Props> {
return null;
}

if (isShortLivingBranch(this.props.branch)) {
if (this.props.branch && isShortLivingBranch(this.props.branch)) {
return null;
}

@@ -129,7 +135,10 @@ export default class ComponentNavMenu extends React.PureComponent<Props> {
<Link
to={{
pathname: '/project/activity',
query: { branch: getBranchName(this.props.branch), id: this.props.component.key }
query: {
branch: this.props.branch && getBranchName(this.props.branch),
id: this.props.component.key
}
}}
activeClassName="active">
{translate('project_activity.page')}
@@ -145,7 +154,7 @@ export default class ComponentNavMenu extends React.PureComponent<Props> {
to={{
pathname: '/project/issues',
query: {
branch: getBranchName(this.props.branch),
branch: this.props.branch && getBranchName(this.props.branch),
id: this.props.component.key,
resolved: 'false'
}
@@ -158,7 +167,7 @@ export default class ComponentNavMenu extends React.PureComponent<Props> {
}

renderComponentMeasuresLink() {
if (isShortLivingBranch(this.props.branch)) {
if (this.props.branch && isShortLivingBranch(this.props.branch)) {
return null;
}

@@ -167,7 +176,10 @@ export default class ComponentNavMenu extends React.PureComponent<Props> {
<Link
to={{
pathname: '/component_measures',
query: { branch: getBranchName(this.props.branch), id: this.props.component.key }
query: {
branch: this.props.branch && getBranchName(this.props.branch),
id: this.props.component.key
}
}}
activeClassName="active">
{translate('layout.measures')}
@@ -177,7 +189,7 @@ export default class ComponentNavMenu extends React.PureComponent<Props> {
}

renderAdministration() {
if (isShortLivingBranch(this.props.branch)) {
if (this.props.branch && isShortLivingBranch(this.props.branch)) {
return null;
}

@@ -205,7 +217,7 @@ export default class ComponentNavMenu extends React.PureComponent<Props> {
}

renderAdministrationLinks() {
return isLongLivingBranch(this.props.branch)
return this.props.branch && isLongLivingBranch(this.props.branch)
? [this.renderSettingsLink()]
: [
this.renderSettingsLink(),
@@ -231,7 +243,10 @@ export default class ComponentNavMenu extends React.PureComponent<Props> {
<Link
to={{
pathname: '/project/settings',
query: { branch: getBranchName(this.props.branch), id: this.props.component.key }
query: {
branch: this.props.branch && getBranchName(this.props.branch),
id: this.props.component.key
}
}}
activeClassName="active">
{translate('project_settings.page')}

+ 4
- 4
server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMeta.tsx 查看文件

@@ -28,7 +28,7 @@ import { translate, translateWithParameters } from '../../../../helpers/l10n';
import { isShortLivingBranch } from '../../../../helpers/branches';

interface Props {
branch: Branch;
branch?: Branch;
component: Component;
conf: ComponentConfiguration;
incremental?: boolean;
@@ -91,7 +91,7 @@ export default function ComponentNavMeta(props: Props) {
);
}

if (props.component.analysisDate && props.branch.isMain) {
if (props.component.analysisDate && (!props.branch || props.branch.isMain)) {
metaList.push(
<li key="analysisDate">
<DateTimeFormatter date={props.component.analysisDate} />
@@ -99,7 +99,7 @@ export default function ComponentNavMeta(props: Props) {
);
}

if (props.component.version && props.branch.isMain) {
if (props.component.version && (!props.branch || props.branch.isMain)) {
metaList.push(
<li key="version">
Version {props.component.version}
@@ -115,7 +115,7 @@ export default function ComponentNavMeta(props: Props) {
);
}

if (isShortLivingBranch(props.branch)) {
if (props.branch && isShortLivingBranch(props.branch)) {
metaList.push(
<li className="navbar-context-meta-branch" key="branch-status">
<BranchStatus branch={props.branch} />

+ 3
- 3
server/sonar-web/src/main/js/app/utils/startReactApp.js 查看文件

@@ -32,7 +32,7 @@ import Landing from '../components/Landing';
import ProjectAdminContainer from '../components/ProjectAdminContainer';
import ProjectPageExtension from '../components/extensions/ProjectPageExtension';
import ProjectAdminPageExtension from '../components/extensions/ProjectAdminPageExtension';
import ViewDashboard from '../components/extensions/ViewDashboard';
import PortfolioDashboard from '../components/extensions/PortfolioDashboard';
import PortfoliosPage from '../components/extensions/PortfoliosPage';
import AdminContainer from '../components/AdminContainer';
import GlobalPageExtension from '../components/extensions/GlobalPageExtension';
@@ -170,7 +170,7 @@ const startReactApp = () => {

<Route
getComponent={() =>
import('../components/ProjectContainer').then(i => i.default)}>
import('../components/ComponentContainer').then(i => i.default)}>
<Route path="code" childRoutes={codeRoutes} />
<Route path="component_measures" childRoutes={componentMeasuresRoutes} />
<Route path="custom_measures" childRoutes={customMeasuresRoutes} />
@@ -194,7 +194,7 @@ const startReactApp = () => {
{projectAdminRoutes}
</Route>
<Route path="project_roles" childRoutes={projectPermissionsRoutes} />
<Route path="portfolio" component={ViewDashboard} />
<Route path="portfolio" component={PortfolioDashboard} />
</Route>

<Route component={AdminContainer}>

+ 14
- 5
server/sonar-web/src/main/js/apps/code/components/App.tsx 查看文件

@@ -39,7 +39,7 @@ import '../code.css';
import { Component, Branch } from '../../../app/types';

interface Props {
branch: Branch;
branch?: Branch;
component: Component;
location: { query: { [x: string]: string } };
}
@@ -91,7 +91,7 @@ export default class App extends React.PureComponent<Props, State> {

this.setState({ loading: true });
const isPortfolio = ['VW', 'SVW'].includes(component.qualifier);
retrieveComponentChildren(component.key, isPortfolio, getBranchName(branch))
retrieveComponentChildren(component.key, isPortfolio, branch && getBranchName(branch))
.then(() => {
addComponent(component);
this.handleUpdate();
@@ -108,7 +108,11 @@ export default class App extends React.PureComponent<Props, State> {
this.setState({ loading: true });

const isPortfolio = ['VW', 'SVW'].includes(this.props.component.qualifier);
retrieveComponent(componentKey, isPortfolio, getBranchName(this.props.branch))
retrieveComponent(
componentKey,
isPortfolio,
this.props.branch && getBranchName(this.props.branch)
)
.then(r => {
if (this.mounted) {
if (['FIL', 'UTS'].includes(r.component.qualifier)) {
@@ -154,7 +158,12 @@ export default class App extends React.PureComponent<Props, State> {
return;
}
const isPortfolio = ['VW', 'SVW'].includes(this.props.component.qualifier);
loadMoreChildren(baseComponent.key, page + 1, isPortfolio, getBranchName(this.props.branch))
loadMoreChildren(
baseComponent.key,
page + 1,
isPortfolio,
this.props.branch && getBranchName(this.props.branch)
)
.then(r => {
if (this.mounted) {
this.setState({
@@ -189,7 +198,7 @@ export default class App extends React.PureComponent<Props, State> {
total,
sourceViewer
} = this.state;
const branchName = getBranchName(branch);
const branchName = branch && getBranchName(branch);

const shouldShowBreadcrumbs = breadcrumbs.length > 1;


+ 9
- 5
server/sonar-web/src/main/js/apps/component-measures/components/App.js 查看文件

@@ -34,7 +34,7 @@ import { translate } from '../../../helpers/l10n';
import '../style.css';

/*:: type Props = {|
branch: {},
branch?: {},
component: Component,
currentUser: { isLoggedIn: boolean },
location: { pathname: string, query: RawQuery },
@@ -106,7 +106,7 @@ export default class App extends React.PureComponent {
const filteredKeys = metricsKey.filter(
key => !metrics[key].hidden && !['DATA', 'DISTRIB'].includes(metrics[key].type)
);
fetchMeasures(component.key, filteredKeys, getBranchName(branch)).then(
fetchMeasures(component.key, filteredKeys, branch && getBranchName(branch)).then(
({ measures, leakPeriod }) => {
if (this.mounted) {
this.setState({
@@ -127,7 +127,11 @@ export default class App extends React.PureComponent {
});
this.props.router.push({
pathname: this.props.location.pathname,
query: { ...query, branch: getBranchName(this.props.branch), id: this.props.component.key }
query: {
...query,
branch: this.props.branch && getBranchName(this.props.branch),
id: this.props.component.key
}
});
};

@@ -160,7 +164,7 @@ export default class App extends React.PureComponent {

{metric != null &&
<MeasureContentContainer
branch={getBranchName(branch)}
branch={branch && getBranchName(branch)}
className="layout-page-main"
currentUser={this.props.currentUser}
rootComponent={component}
@@ -176,7 +180,7 @@ export default class App extends React.PureComponent {
{metric == null &&
hasBubbleChart(query.metric) &&
<MeasureOverviewContainer
branch={getBranchName(branch)}
branch={branch && getBranchName(branch)}
className="layout-page-main"
rootComponent={component}
currentUser={this.props.currentUser}

+ 2
- 2
server/sonar-web/src/main/js/apps/overview/components/App.js 查看文件

@@ -28,7 +28,7 @@ import SourceViewer from '../../../components/SourceViewer/SourceViewer';

/*::
type Props = {
branch: { name: string },
branch?: { name: string },
component: {
analysisDate?: string,
id: string,
@@ -75,7 +75,7 @@ export default class App extends React.PureComponent {
if (['FIL', 'UTS'].includes(component.qualifier)) {
return (
<div className="page page-limited">
<SourceViewer branch={getBranchName(branch)} component={component.key} />
<SourceViewer branch={branch && getBranchName(branch)} component={component.key} />
</div>
);
}

+ 4
- 4
server/sonar-web/src/main/js/apps/overview/components/OverviewApp.js 查看文件

@@ -42,7 +42,7 @@ import '../styles.css';

/*::
type Props = {
branch: { name: string },
branch?: { name: string },
component: Component,
onComponentChange: {} => void
};
@@ -98,7 +98,7 @@ export default class OverviewApp extends React.PureComponent {

return getMeasuresAndMeta(component.key, METRICS, {
additionalFields: 'metrics,periods',
branch: getBranchName(branch)
branch: branch && getBranchName(branch)
}).then(
r => {
if (this.mounted) {
@@ -128,7 +128,7 @@ export default class OverviewApp extends React.PureComponent {

const metrics = uniq(HISTORY_METRICS_LIST.concat(graphMetrics));
return getAllTimeMachineData(component.key, metrics, {
branch: getBranchName(branch)
branch: branch && getBranchName(branch)
}).then(r => {
if (this.mounted) {
const history /*: History */ = {};
@@ -166,7 +166,7 @@ export default class OverviewApp extends React.PureComponent {

const leakPeriod =
component.qualifier === 'APP' ? this.getApplicationLeakPeriod() : getLeakPeriod(periods);
const branchName = getBranchName(branch);
const branchName = branch && getBranchName(branch);
const domainProps = {
branch: branchName,
component,

+ 13
- 5
server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppContainer.js 查看文件

@@ -43,7 +43,7 @@ import {

/*::
type Props = {
branch: {},
branch?: {},
location: { pathname: string, query: RawQuery },
component: {
configuration?: { showHistory: boolean },
@@ -95,7 +95,10 @@ export default class ProjectActivityAppContainer extends React.PureComponent {
}
this.context.router.replace({
pathname: props.location.pathname,
query: { ...serializeUrlQuery(newQuery), branch: getBranchName(props.branch) }
query: {
...serializeUrlQuery(newQuery),
branch: props.branch && getBranchName(props.branch)
}
});
}
}
@@ -169,7 +172,12 @@ export default class ProjectActivityAppContainer extends React.PureComponent {
[string]: string
} */
) => {
const parameters = { project, p, ps, branch: getBranchName(this.props.branch) };
const parameters = {
project,
p,
ps,
branch: this.props.branch && getBranchName(this.props.branch)
};
return api
.getProjectActivity({ ...parameters, ...additional })
.then(({ analyses, paging }) => ({
@@ -183,7 +191,7 @@ export default class ProjectActivityAppContainer extends React.PureComponent {
return Promise.resolve([]);
}
return getAllTimeMachineData(this.props.component.key, metrics, {
branch: getBranchName(this.props.branch)
branch: this.props.branch && getBranchName(this.props.branch)
}).then(
({ measures }) =>
measures.map(measure => ({
@@ -285,7 +293,7 @@ export default class ProjectActivityAppContainer extends React.PureComponent {
pathname: this.props.location.pathname,
query: {
...query,
branch: getBranchName(this.props.branch),
branch: this.props.branch && getBranchName(this.props.branch),
id: this.props.component.key
}
});

+ 1
- 1
server/sonar-web/src/main/js/apps/settings/components/DefinitionsList.js 查看文件

@@ -24,7 +24,7 @@ import Definition from './Definition';

export default class DefinitionsList extends React.PureComponent {
static propTypes = {
branch: PropTypes.object,
branch: PropTypes.string,
component: PropTypes.object,
settings: PropTypes.array.isRequired
};

正在加载...
取消
保存