import { Branch, Component } from '../types';
import handleRequiredAuthorization from '../utils/handleRequiredAuthorization';
import { getBranches } from '../../api/branches';
+import { Task, getTasksForComponent } from '../../api/ce';
import { getComponentData } from '../../api/components';
import { getComponentNavigation } from '../../api/nav';
import { fetchOrganizations } from '../../store/rootActions';
import { areThereCustomOrganizations } from '../../store/rootReducer';
+import { STATUSES } from '../../apps/background-tasks/constants';
interface Props {
children: any;
branches: Branch[];
loading: boolean;
component: Component | null;
+ currentTask?: Task;
+ isInProgress?: boolean;
+ isPending?: boolean;
}
export class ComponentContainer extends React.PureComponent<Props, State> {
this.setState({ loading: false, branches, component });
}
}, onError);
+
+ this.fetchStatus(component);
},
onError
);
return project ? getBranches(project.key) : Promise.resolve([]);
};
+ fetchStatus = (component: Component) => {
+ getTasksForComponent(component.key).then(
+ ({ current, queue }) => {
+ if (this.mounted) {
+ this.setState({
+ currentTask: current,
+ isInProgress: queue.some(task => task.status === STATUSES.IN_PROGRESS),
+ isPending: queue.some(task => task.status === STATUSES.PENDING)
+ });
+ }
+ },
+ () => {}
+ );
+ };
+
handleComponentChange = (changes: {}) => {
if (this.mounted) {
this.setState(state => ({ component: { ...state.component, ...changes } }));
branches={branches}
currentBranch={branch}
component={component}
+ currentTask={this.state.currentTask}
+ isInProgress={this.state.isInProgress}
+ isPending={this.state.isPending}
location={this.props.location}
/>
)}
branch,
branches,
component,
+ isInProgress: this.state.isInProgress,
+ isPending: this.state.isPending,
onBranchesChange: this.handleBranchesChange,
onComponentChange: this.handleComponentChange
})
import { shallow, mount } from 'enzyme';
import { ComponentContainer } from '../ComponentContainer';
import { getBranches } from '../../../api/branches';
+import { getTasksForComponent } from '../../../api/ce';
import { getComponentData } from '../../../api/components';
import { getComponentNavigation } from '../../../api/nav';
jest.mock('../../../api/branches', () => ({ getBranches: jest.fn(() => Promise.resolve([])) }));
+jest.mock('../../../api/ce', () => ({
+ getTasksForComponent: jest.fn(() => Promise.resolve({ queue: [] }))
+}));
jest.mock('../../../api/components', () => ({
getComponentData: jest.fn(() => Promise.resolve({}))
}));
jest.mock('../../../api/nav', () => ({
getComponentNavigation: jest.fn(() =>
Promise.resolve({
- breadcrumbs: [{ key: 'portfolioKey', name: 'portfolio', qualifier: 'VW' }]
+ breadcrumbs: [{ key: 'portfolioKey', name: 'portfolio', qualifier: 'VW' }],
+ key: 'portfolioKey'
})
)
}));
(getBranches as jest.Mock<any>).mockClear();
(getComponentData as jest.Mock<any>).mockClear();
(getComponentNavigation as jest.Mock<any>).mockClear();
+ (getTasksForComponent as jest.Mock<any>).mockClear();
});
it('changes component', () => {
await new Promise(setImmediate);
expect(fetchOrganizations).toBeCalledWith(['org']);
});
+
+it('fetches status', async () => {
+ (getComponentData as jest.Mock<any>).mockImplementationOnce(() =>
+ Promise.resolve({ organization: 'org' })
+ );
+
+ mount(
+ <ComponentContainer
+ fetchOrganizations={jest.fn()}
+ location={{ query: { id: 'foo' } }}
+ organizationsEnabled={true}>
+ <Inner />
+ </ComponentContainer>
+ );
+
+ await new Promise(setImmediate);
+ expect(getTasksForComponent).toBeCalledWith('portfolioKey');
+});
import * as theme from '../../../theme';
import { Branch, Component } from '../../../types';
import ContextNavBar from '../../../../components/nav/ContextNavBar';
-import { getTasksForComponent, PendingTask, Task } from '../../../../api/ce';
+import { Task } from '../../../../api/ce';
import { STATUSES } from '../../../../apps/background-tasks/constants';
import './ComponentNav.css';
branches: Branch[];
currentBranch?: Branch;
component: Component;
- location: {};
-}
-
-interface State {
currentTask?: Task;
isInProgress?: boolean;
isPending?: boolean;
+ location: {};
}
-export default class ComponentNav extends React.PureComponent<Props, State> {
+export default class ComponentNav extends React.PureComponent<Props> {
mounted: boolean;
- state: State = {};
-
componentDidMount() {
- this.mounted = true;
- this.loadStatus();
this.populateRecentHistory();
}
componentDidUpdate(prevProps: Props) {
if (this.props.component.key !== prevProps.component.key) {
- this.loadStatus();
this.populateRecentHistory();
}
}
- componentWillUnmount() {
- this.mounted = false;
- }
-
- loadStatus = () => {
- getTasksForComponent(this.props.component.key).then(
- (r: { queue: PendingTask[]; current: Task }) => {
- if (this.mounted) {
- this.setState({
- currentTask: r.current,
- isInProgress: r.queue.some(task => task.status === STATUSES.IN_PROGRESS),
- isPending: r.queue.some(task => task.status === STATUSES.PENDING)
- });
- }
- },
- () => {}
- );
- };
-
populateRecentHistory = () => {
const { breadcrumbs } = this.props.component;
const { qualifier } = breadcrumbs[breadcrumbs.length - 1];
};
render() {
- const { currentTask, isInProgress, isPending } = this.state;
+ const { currentTask, isInProgress, isPending } = this.props;
let notifComponent;
if (isInProgress || isPending || (currentTask && currentTask.status === STATUSES.FAILED)) {
notifComponent = (
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-/* eslint-disable import/order */
import * as React from 'react';
-import { mount, shallow } from 'enzyme';
+import { shallow } from 'enzyme';
import ComponentNav from '../ComponentNav';
-jest.mock('../ComponentNavMeta', () => ({
- // eslint-disable-next-line
- default: function ComponentNavMeta() {
- return null;
- }
-}));
-
-jest.mock('../ComponentNavHeader', () => ({
- // eslint-disable-next-line
- default: function ComponentNavHeader() {
- return null;
- }
-}));
-
-jest.mock('../ComponentNavMenu', () => ({
- // eslint-disable-next-line
- default: function ComponentNavMenu() {
- return null;
- }
-}));
-
-jest.mock('../../../RecentHistory', () => ({
- default: { add: jest.fn() }
-}));
-
-jest.mock('../../../../../api/ce', () => ({
- getTasksForComponent: jest.fn(() => Promise.resolve({ queue: [] }))
-}));
-
-const getTasksForComponent = require('../../../../../api/ce').getTasksForComponent as jest.Mock<
- any
->;
-
const component = {
breadcrumbs: [{ key: 'component', name: 'component', qualifier: 'TRK' }],
key: 'component',
qualifier: 'TRK'
};
-it('loads status', () => {
- getTasksForComponent.mockClear();
- mount(<ComponentNav branches={[]} component={component} location={{}} />);
- expect(getTasksForComponent).toBeCalledWith('component');
-});
-
it('renders', () => {
const wrapper = shallow(<ComponentNav branches={[]} component={component} location={{}} />);
wrapper.setState({ isInProgress: true, isPending: true });
exports[`renders 1`] = `
<ContextNavBar
- height={92}
+ height={72}
id="context-navigation"
- notif={
- <ComponentNavBgTaskNotif
- component={
- Object {
- "breadcrumbs": Array [
- Object {
- "key": "component",
- "name": "component",
- "qualifier": "TRK",
- },
- ],
- "key": "component",
- "name": "component",
- "organization": "org",
- "qualifier": "TRK",
- }
- }
- currentTask={undefined}
- isInProgress={true}
- isPending={true}
- />
- }
>
- <ComponentNavHeader
+ <Connect(ComponentNavHeader)
branches={Array []}
component={
Object {
}
location={Object {}}
/>
- <ComponentNavMeta
+ <Connect(ComponentNavMeta)
component={
Object {
"breadcrumbs": Array [
tags: Array<string>,
organization?: string
},
+ isInProgress?: bool,
+ isPending?: bool,
onComponentChange: {} => void,
router: Object
};
}
if (!component.analysisDate) {
- return <EmptyOverview component={component} />;
+ return (
+ <EmptyOverview
+ component={component.key}
+ showWarning={!this.props.isPending && !this.props.isInProgress}
+ />
+ );
}
return (
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.
- */
-// @flow
-import React from 'react';
-import { Link } from 'react-router';
-import { translate } from '../../../helpers/l10n';
-
-/*::
-type Props = {
- component: { key: string }
-};
-*/
-
-export default function EmptyOverview({ component } /*: Props */) {
- const rawMessage = translate('provisioning.no_analysis.delete');
- const head = rawMessage.substr(0, rawMessage.indexOf('{0}'));
- const tail = rawMessage.substr(rawMessage.indexOf('{0}') + 3);
-
- return (
- <div className="page page-limited">
- <div className="alert alert-warning">{translate('provisioning.no_analysis')}</div>
-
- <div className="big-spacer-top">
- {head}
- <Link
- className="text-danger"
- to={{ pathname: '/project/deletion', query: { id: component.key } }}>
- {translate('provisioning.no_analysis.delete_project')}
- </Link>
- {tail}
- </div>
-
- <div className="big-spacer-top">
- <h4>{translate('key')}</h4>
- <code>{component.key}</code>
- </div>
- </div>
- );
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 * as React from 'react';
+import { Link } from 'react-router';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+ component: string;
+ showWarning?: boolean;
+}
+
+export default function EmptyOverview({ component, showWarning }: Props) {
+ const rawMessage = translate('provisioning.no_analysis.delete');
+ const head = rawMessage.substr(0, rawMessage.indexOf('{0}'));
+ const tail = rawMessage.substr(rawMessage.indexOf('{0}') + 3);
+
+ return (
+ <div className="page page-limited">
+ {showWarning && (
+ <div className="big-spacer-bottom">
+ <div className="alert alert-warning">{translate('provisioning.no_analysis')}</div>
+
+ <div className="big-spacer-top">
+ {head}
+ <Link
+ className="text-danger"
+ to={{ pathname: '/project/deletion', query: { id: component } }}>
+ {translate('provisioning.no_analysis.delete_project')}
+ </Link>
+ {tail}
+ </div>
+ </div>
+ )}
+
+ <div>
+ <h4>{translate('key')}</h4>
+ <code>{component}</code>
+ </div>
+ </div>
+ );
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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 React from 'react';
-import { shallow } from 'enzyme';
-import EmptyOverview from '../EmptyOverview';
-
-it('should render component key', () => {
- const component = {
- id: 'id',
- key: 'abcd',
- analysisDate: '2016-01-01'
- };
- const output = shallow(<EmptyOverview component={component} />);
- expect(output.find('code').text()).toBe('abcd');
-});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 * as React from 'react';
+import { shallow } from 'enzyme';
+import EmptyOverview from '../EmptyOverview';
+
+it('renders', () => {
+ expect(shallow(<EmptyOverview component="abcd" />)).toMatchSnapshot();
+});
+
+it('does not render warning', () => {
+ expect(shallow(<EmptyOverview component="abcd" showWarning={false} />)).toMatchSnapshot();
+});
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`does not render warning 1`] = `
+<div
+ className="page page-limited"
+>
+ <div>
+ <h4>
+ key
+ </h4>
+ <code>
+ abcd
+ </code>
+ </div>
+</div>
+`;
+
+exports[`renders 1`] = `
+<div
+ className="page page-limited"
+>
+ <div>
+ <h4>
+ key
+ </h4>
+ <code>
+ abcd
+ </code>
+ </div>
+</div>
+`;