export const cancelAllTasks = (): Promise<*> => post('/api/ce/cancel_all');
-export const getTasksForComponent = (componentId: string): Promise<*> =>
- getJSON('/api/ce/component', { componentId });
+export const getTasksForComponent = (componentKey: string): Promise<*> =>
+ getJSON('/api/ce/component', { componentKey });
export const getTypes = (): Promise<*> => getJSON('/api/ce/task_types').then(r => r.taskTypes);
}
loadStatus = () => {
- getTasksForComponent(this.props.component.id).then(r => {
+ getTasksForComponent(this.props.component.key).then(r => {
if (this.mounted) {
this.setState({
isPending: r.queue.some(task => task.status === STATUSES.PENDING),
import { translate } from '../../../helpers/l10n';
type Props = {|
- onFinish: () => void,
+ onFinish: (projectKey?: string) => void,
onReset: () => void,
open: boolean,
organization?: string,
handleLanguageSelect = (result?: Result) => {
this.setState({ result });
- this.props.onFinish();
+ this.props.onFinish(result && result.projectKey);
};
handleLanguageReset = () => {
import TokenStep from './TokenStep';
import OrganizationStep from './OrganizationStep';
import AnalysisStep from './AnalysisStep';
+import ProjectWatcher from './ProjectWatcher';
import { skipOnboarding } from '../../../api/users';
import { translate } from '../../../helpers/l10n';
+import { getProjectUrl } from '../../../helpers/urls';
import handleRequiredAuthentication from '../../../app/utils/handleRequiredAuthentication';
import './styles.css';
type State = {
finished: boolean,
organization?: string,
+ projectKey?: string,
skipping: boolean,
step: string,
token?: string
props: Props;
state: State;
+ static contextTypes = {
+ router: React.PropTypes.object
+ };
+
constructor(props: Props) {
super(props);
this.state = {
this.mounted = false;
}
- handleTokenDone = (token: string) => {
- this.setState({ step: 'analysis', token });
- };
-
- handleOrganizationDone = (organization: string) => {
- this.setState({ organization, step: 'token' });
- };
-
- handleSkipClick = (event: Event) => {
- event.preventDefault();
+ finishOnboarding = () => {
this.setState({ skipping: true });
skipOnboarding().then(
() => {
if (this.mounted) {
this.props.onSkip();
+
+ if (this.state.projectKey) {
+ this.context.router.push(getProjectUrl(this.state.projectKey));
+ }
}
},
() => {
);
};
- handleFinish = () => this.setState({ finished: true });
+ handleTimeout = () => {
+ // unset `projectKey` to display a generic "Finish this tutorial" button
+ this.setState({ projectKey: undefined });
+ };
+
+ handleTokenDone = (token: string) => {
+ this.setState({ step: 'analysis', token });
+ };
+
+ handleOrganizationDone = (organization: string) => {
+ this.setState({ organization, step: 'token' });
+ };
+
+ handleSkipClick = (event: Event) => {
+ event.preventDefault();
+ this.finishOnboarding();
+ };
+
+ handleFinish = (projectKey?: string) => this.setState({ finished: true, projectKey });
handleReset = () => this.setState({ finished: false });
{this.state.finished &&
!this.state.skipping &&
- <footer className="text-right">
- <a className="button" href="#" onClick={this.handleSkipClick}>
- {translate('tutorials.finish')}
- </a>
- </footer>}
+ (this.state.projectKey
+ ? <ProjectWatcher
+ onFinish={this.finishOnboarding}
+ onTimeout={this.handleTimeout}
+ projectKey={this.state.projectKey}
+ />
+ : <footer className="text-right">
+ <a className="button" href="#" onClick={this.handleSkipClick}>
+ {translate('tutorials.finish')}
+ </a>
+ </footer>)}
</div>
);
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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 { getTasksForComponent } from '../../../api/ce';
+import { STATUSES } from '../../../apps/background-tasks/constants';
+import { translate } from '../../../helpers/l10n';
+
+const INTERVAL = 5000;
+const TIMEOUT = 10 * 60 * 1000; // 10 min
+
+type Props = {
+ onFinish: () => void,
+ onTimeout: () => void,
+ projectKey: string
+};
+
+type State = {
+ inQueue: boolean,
+ status: ?string
+};
+
+export default class ProjectWatcher extends React.PureComponent {
+ interval: number;
+ mounted: boolean;
+ props: Props;
+ timeout: number;
+ state: State = {
+ inQueue: false,
+ status: null
+ };
+
+ componentDidMount() {
+ this.mounted = true;
+ this.watch();
+ this.timeout = setTimeout(this.props.onTimeout, TIMEOUT);
+ }
+
+ componentWillUnmount() {
+ clearInterval(this.interval);
+ clearInterval(this.timeout);
+ this.mounted = false;
+ }
+
+ watch = () => (this.interval = setTimeout(this.checkProject, INTERVAL));
+
+ checkProject = () => {
+ const { projectKey } = this.props;
+ getTasksForComponent(projectKey).then(response => {
+ if (response.queue.length > 0) {
+ this.setState({ inQueue: true });
+ }
+
+ if (response.current != null) {
+ const { status } = response.current;
+ this.setState({ status });
+ if (status === STATUSES.SUCCESS) {
+ this.props.onFinish();
+ } else if (status === STATUSES.PENDING || status === STATUSES.IN_PROGRESS) {
+ this.watch();
+ }
+ } else {
+ this.watch();
+ }
+ });
+ };
+
+ render() {
+ const { inQueue, status } = this.state;
+
+ if (status === STATUSES.SUCCESS) {
+ return (
+ <div className="big-spacer-top note text-center">
+ <i className="icon-check spacer-right" />
+ {translate('onboarding.project_watcher.finished')}
+ </div>
+ );
+ }
+
+ if (inQueue || status === STATUSES.PENDING || status === STATUSES.IN_PROGRESS) {
+ return (
+ <div className="big-spacer-top note text-center">
+ <i className="spinner spacer-right" />
+ {translate('onboarding.project_watcher.in_progress')}
+ </div>
+ );
+ }
+
+ if (status != null) {
+ return (
+ <div className="big-spacer-top note text-center">
+ <i className="icon-alert-danger spacer-right" />
+ {translate('onboarding.project_watcher.failed')}
+ </div>
+ );
+ }
+
+ return (
+ <div className="big-spacer-top note text-center">
+ {translate('onboarding.project_watcher.not_started')}
+ </div>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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 { shallow, mount } from 'enzyme';
+import ProjectWatcher from '../ProjectWatcher';
+
+jest.mock('../../../../api/ce', () => ({
+ getTasksForComponent: () => Promise.resolve({ current: { status: 'SUCCESS' }, queue: [] })
+}));
+
+jest.useFakeTimers();
+
+it('renders', () => {
+ const wrapper = shallow(
+ <ProjectWatcher onFinish={jest.fn()} onTimeout={jest.fn()} projectKey="foo" />
+ );
+ expect(wrapper).toMatchSnapshot();
+ wrapper.setState({ inQueue: true });
+ expect(wrapper).toMatchSnapshot();
+ wrapper.setState({ status: 'SUCCESS' });
+ expect(wrapper).toMatchSnapshot();
+ wrapper.setState({ status: 'FAILED' });
+ expect(wrapper).toMatchSnapshot();
+});
+
+it('finishes', done => {
+ // checking `expect(onFinish).toBeCalled();` is not working, because it's called asynchronously
+ // instead let's finish the test as soon as `onFinish` callback is called
+ const onFinish = jest.fn(done);
+ mount(<ProjectWatcher onFinish={onFinish} onTimeout={jest.fn()} projectKey="foo" />);
+ expect(onFinish).not.toBeCalled();
+ jest.runTimersToTime(5000);
+});
+
+it('timeouts', () => {
+ const onTimeout = jest.fn();
+ mount(<ProjectWatcher onFinish={jest.fn()} onTimeout={onTimeout} projectKey="foo" />);
+ expect(onTimeout).not.toBeCalled();
+ jest.runTimersToTime(10 * 60 * 1000);
+ expect(onTimeout).toBeCalled();
+});
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders 1`] = `
+<div
+ className="big-spacer-top note text-center"
+>
+ onboarding.project_watcher.not_started
+</div>
+`;
+
+exports[`renders 2`] = `
+<div
+ className="big-spacer-top note text-center"
+>
+ <i
+ className="spinner spacer-right"
+ />
+ onboarding.project_watcher.in_progress
+</div>
+`;
+
+exports[`renders 3`] = `
+<div
+ className="big-spacer-top note text-center"
+>
+ <i
+ className="icon-check spacer-right"
+ />
+ onboarding.project_watcher.finished
+</div>
+`;
+
+exports[`renders 4`] = `
+<div
+ className="big-spacer-top note text-center"
+>
+ <i
+ className="spinner spacer-right"
+ />
+ onboarding.project_watcher.in_progress
+</div>
+`;
onboarding.analysis.sq_scanner.execute=Execute the SonarQube Scanner from your computer
onboarding.analysis.sq_scanner.execute.text=Running a SonarQube analysis is straighforward. You just need to execute the following commands in your project's folder.
onboarding.analysis.sq_scanner.docs=Please visit the <a href="http://redirect.sonarsource.com/doc/install-configure-scanner.html" target="_blank">official documentation of the SonarQube Scanner</a> for more details.
+
+onboarding.project_watcher.not_started=Once your project is analyzed, this page will refresh automatically.
+onboarding.project_watcher.in_progress=Analysis is in progress, please wait...
+onboarding.project_watcher.finished=Analysis is finished, redirecting...
+onboarding.project_watcher.failed=Something went wrong, please check the analysis logs.