From: Stas Vilchik Date: Wed, 14 Feb 2018 09:51:22 +0000 (+0100) Subject: rewrite maintenance app in react (#3055) X-Git-Tag: 7.5~1654 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=8053754d961994e78ed973d37005bb6b5e8ceeae;p=sonarqube.git rewrite maintenance app in react (#3055) --- diff --git a/server/sonar-web/src/main/js/api/system.ts b/server/sonar-web/src/main/js/api/system.ts index 794000c0389..597669a2dab 100644 --- a/server/sonar-web/src/main/js/api/system.ts +++ b/server/sonar-web/src/main/js/api/system.ts @@ -76,7 +76,7 @@ export function getSystemInfo(): Promise { return getJSON('/api/system/info').catch(throwGlobalError); } -export function getSystemStatus(): Promise { +export function getSystemStatus(): Promise<{ id: string; version: string; status: string }> { return getJSON('/api/system/status'); } @@ -87,11 +87,19 @@ export function getSystemUpgrades(): Promise<{ return getJSON('/api/system/upgrades'); } -export function getMigrationStatus(): Promise { +export function getMigrationStatus(): Promise<{ + message?: string; + startedAt?: string; + state: string; +}> { return getJSON('/api/system/db_migration_status'); } -export function migrateDatabase() { +export function migrateDatabase(): Promise<{ + message?: string; + startedAt?: string; + state: string; +}> { return postJSON('/api/system/migrate_db'); } diff --git a/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/App-test.tsx index a264af89975..3b2019fe566 100644 --- a/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/App-test.tsx +++ b/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/App-test.tsx @@ -20,6 +20,7 @@ import * as React from 'react'; import { shallow } from 'enzyme'; import App from '../App'; +import { waitAndUpdate } from '../../../../helpers/testUtils'; jest.mock('../../../../api/measures', () => ({ getCustomMeasures: () => @@ -55,8 +56,7 @@ it('should work', async () => { const wrapper = shallow(); expect(wrapper).toMatchSnapshot(); - await new Promise(setImmediate); - wrapper.update(); + await waitAndUpdate(wrapper); expect(wrapper).toMatchSnapshot(); // create @@ -65,8 +65,7 @@ it('should work', async () => { metricKey: 'metricKey', value: 'value' }); - await new Promise(setImmediate); - wrapper.update(); + await waitAndUpdate(wrapper); expect(wrapper.state().measures).toMatchSnapshot(); expect(wrapper.state().paging.total).toBe(2); @@ -76,15 +75,13 @@ it('should work', async () => { id: '2', value: 'other' }); - await new Promise(setImmediate); - wrapper.update(); + await waitAndUpdate(wrapper); expect(wrapper.state().measures).toMatchSnapshot(); expect(wrapper.state().paging.total).toBe(2); // delete wrapper.find('List').prop('onDelete')('2'); - await new Promise(setImmediate); - wrapper.update(); + await waitAndUpdate(wrapper); expect(wrapper.state().measures).toMatchSnapshot(); expect(wrapper.state().paging.total).toBe(1); }); diff --git a/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/Form-test.tsx b/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/Form-test.tsx index 3af7a3c000a..e6252db478f 100644 --- a/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/Form-test.tsx +++ b/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/Form-test.tsx @@ -20,7 +20,7 @@ import * as React from 'react'; import { shallow } from 'enzyme'; import Form from '../Form'; -import { change, submit, click } from '../../../../helpers/testUtils'; +import { change, submit, click, waitAndUpdate } from '../../../../helpers/testUtils'; jest.mock('../../../../api/metrics', () => ({ getAllMetrics: () => @@ -44,8 +44,7 @@ it('should render form', async () => { ); expect(wrapper.dive()).toMatchSnapshot(); - await new Promise(setImmediate); - wrapper.update(); + await waitAndUpdate(wrapper); const form = wrapper.dive(); expect(form).toMatchSnapshot(); diff --git a/server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/App-test.tsx index d4b9e0772d5..76ca8e0510f 100644 --- a/server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/App-test.tsx +++ b/server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/App-test.tsx @@ -20,6 +20,7 @@ import * as React from 'react'; import { shallow } from 'enzyme'; import App from '../App'; +import { waitAndUpdate } from '../../../../helpers/testUtils'; jest.mock('../../../../api/metrics', () => ({ getMetricDomains: () => Promise.resolve(['Coverage', 'Issues']), @@ -42,8 +43,7 @@ it('should work', async () => { (wrapper.instance() as App).mounted = true; expect(wrapper).toMatchSnapshot(); - await new Promise(setImmediate); - wrapper.update(); + await waitAndUpdate(wrapper); expect(wrapper).toMatchSnapshot(); // create @@ -53,8 +53,7 @@ it('should work', async () => { name: 'Bar', type: 'INT' }); - await new Promise(setImmediate); - wrapper.update(); + await waitAndUpdate(wrapper); expect(wrapper.state().metrics).toMatchSnapshot(); expect(wrapper.state().paging.total).toBe(2); @@ -66,15 +65,13 @@ it('should work', async () => { name: 'Bar', type: 'STRING' }); - await new Promise(setImmediate); - wrapper.update(); + await waitAndUpdate(wrapper); expect(wrapper.state().metrics).toMatchSnapshot(); expect(wrapper.state().paging.total).toBe(2); // delete wrapper.find('List').prop('onDelete')('bar'); - await new Promise(setImmediate); - wrapper.update(); + await waitAndUpdate(wrapper); expect(wrapper.state().metrics).toMatchSnapshot(); expect(wrapper.state().paging.total).toBe(1); }); diff --git a/server/sonar-web/src/main/js/apps/maintenance/components/App.tsx b/server/sonar-web/src/main/js/apps/maintenance/components/App.tsx new file mode 100644 index 00000000000..3bc5e06b616 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/maintenance/components/App.tsx @@ -0,0 +1,285 @@ +/* + * 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 * as classNames from 'classnames'; +import { getMigrationStatus, getSystemStatus, migrateDatabase } from '../../../api/system'; +import DateFromNow from '../../../components/intl/DateFromNow'; +import TimeFormatter from '../../../components/intl/TimeFormatter'; +import { translate } from '../../../helpers/l10n'; +import { getBaseUrl } from '../../../helpers/urls'; +import '../styles.css'; + +interface Props { + // eslint-disable-next-line camelcase + location: { query: { return_to?: string } }; + setup: boolean; +} + +interface State { + message?: string; + startedAt?: string; + state?: string; + status?: string; + wasStarting?: boolean; +} + +export default class App extends React.PureComponent { + interval?: number; + mounted = false; + state: State = {}; + + componentDidMount() { + this.mounted = true; + this.fetchStatus(); + } + + componentWillUnmount() { + this.mounted = false; + if (this.interval) { + window.clearInterval(this.interval); + } + } + + fetchStatus = () => { + const request = this.props.setup ? this.fetchMigrationState() : this.fetchSystemStatus(); + request.catch(() => { + if (this.mounted) { + this.setState({ + message: undefined, + startedAt: undefined, + state: undefined, + status: 'OFFLINE' + }); + } + }); + }; + + fetchSystemStatus = () => { + return getSystemStatus().then(({ status }) => { + if (this.mounted) { + this.setState({ status }); + + if (status === 'STARTING') { + this.setState({ wasStarting: true }); + this.scheduleRefresh(); + } else if (status === 'UP') { + if (this.state.wasStarting) { + this.loadPreviousPage(); + } + } else { + this.scheduleRefresh(); + } + } + }); + }; + + fetchMigrationState = () => { + return getMigrationStatus().then(({ message, startedAt, state }) => { + if (this.mounted) { + this.setState({ message, startedAt, state }); + if (state === 'MIGRATION_SUCCEEDED') { + this.loadPreviousPage(); + } else if (state !== 'NO_MIGRATION') { + this.scheduleRefresh(); + } + } + }); + }; + + scheduleRefresh = () => { + this.interval = window.setTimeout(this.fetchStatus, 5000); + }; + + loadPreviousPage = () => { + setInterval(() => { + window.location.href = this.props.location.query['return_to'] || getBaseUrl() + '/'; + }, 2500); + }; + + handleMigrateClick = (event: React.SyntheticEvent) => { + event.preventDefault(); + event.currentTarget.blur(); + migrateDatabase().then( + ({ message, startedAt, state }) => { + if (this.mounted) { + this.setState({ message, startedAt, state }); + } + }, + () => {} + ); + }; + + render() { + const { state, status } = this.state; + + return ( +
+
+ {status === 'OFFLINE' && ( + <> +

+ {translate('maintenance.sonarqube_is_offline')} +

+

+ {translate('maintenance.sonarqube_is_offline.text')} +

+

+ {translate('maintenance.try_again')} +

+ + )} + + {status === 'UP' && ( + <> +

{translate('maintenance.sonarqube_is_up')}

+

+ {translate('maintenance.all_systems_opetational')} +

+

+ {translate('layout.home')} +

+ + )} + + {status === 'STARTING' && ( + <> +

+ {translate('maintenance.sonarqube_is_starting')} +

+

+ +

+ + )} + + {status === 'DOWN' && ( + <> +

+ {translate('maintenance.sonarqube_is_down')} +

+

{translate('maintenance.sonarqube_is_down.text')}

+

+ {translate('maintenance.try_again')} +

+ + )} + + {(status === 'DB_MIGRATION_NEEDED' || status === 'DB_MIGRATION_RUNNING') && ( + <> +

+ {translate('maintenance.sonarqube_is_under_maintenance')} +

+

+

+ + )} + + {state === 'NO_MIGRATION' && ( + <> +

+ {translate('maintenance.database_is_up_to_date')} +

+

+ {translate('layout.home')} +

+ + )} + + {state === 'MIGRATION_REQUIRED' && ( + <> +

{translate('maintenance.upgrade_database')}

+

{translate('maintenance.upgrade_database.1')}

+

{translate('maintenance.upgrade_database.2')}

+

{translate('maintenance.upgrade_database.3')}

+
+ +
+ + )} + + {state === 'NOT_SUPPORTED' && ( + <> +

+ {translate('maintenance.migration_not_supported')} +

+

{translate('maintenance.migration_not_supported.text')}

+ + )} + + {state === 'MIGRATION_RUNNING' && ( + <> +

{translate('maintenance.database_migration')}

+ {this.state.message && ( +

{this.state.message}

+ )} + {this.state.startedAt && ( +

+ {translate('background_tasks.table.started')}{' '} + +
+ + + +

+ )} +

+ +

+ + )} + + {state === 'MIGRATION_SUCCEEDED' && ( + <> +

+ {translate('maintenance.database_is_up_to_date')} +

+

+ {translate('layout.home')} +

+ + )} + + {state === 'MIGRATION_FAILED' && ( + <> +

+ {translate('maintenance.upgrade_failed')} +

+

{translate('maintenance.upgrade_failed.text')}

+ + )} +
+
+ ); + } +} diff --git a/server/sonar-web/src/main/js/apps/maintenance/components/MaintenanceAppContainer.tsx b/server/sonar-web/src/main/js/apps/maintenance/components/MaintenanceAppContainer.tsx index 38be383b830..1a84ccdcd6d 100644 --- a/server/sonar-web/src/main/js/apps/maintenance/components/MaintenanceAppContainer.tsx +++ b/server/sonar-web/src/main/js/apps/maintenance/components/MaintenanceAppContainer.tsx @@ -18,19 +18,13 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import init from '../init'; -import '../styles.css'; +import App from './App'; interface Props { + // eslint-disable-next-line camelcase location: { query: { return_to: string } }; } -export default class MaintenanceAppContainer extends React.PureComponent { - componentDidMount() { - init(this.refs.container, false, this.props.location.query['return_to']); - } - - render() { - return
; - } +export default function MaintenanceAppContainer(props: Props) { + return ; } diff --git a/server/sonar-web/src/main/js/apps/maintenance/components/SetupAppContainer.tsx b/server/sonar-web/src/main/js/apps/maintenance/components/SetupAppContainer.tsx index 46d68176189..4cd1e8fe5b5 100644 --- a/server/sonar-web/src/main/js/apps/maintenance/components/SetupAppContainer.tsx +++ b/server/sonar-web/src/main/js/apps/maintenance/components/SetupAppContainer.tsx @@ -18,19 +18,13 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import init from '../init'; -import '../styles.css'; +import App from './App'; interface Props { + // eslint-disable-next-line camelcase location: { query: { return_to: string } }; } -export default class SetupAppContainer extends React.PureComponent { - componentDidMount() { - init(this.refs.container, true, this.props.location.query['return_to']); - } - - render() { - return
; - } +export default function MaintenanceAppContainer(props: Props) { + return ; } diff --git a/server/sonar-web/src/main/js/apps/maintenance/components/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/maintenance/components/__tests__/App-test.tsx new file mode 100644 index 00000000000..8d4511d09e7 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/maintenance/components/__tests__/App-test.tsx @@ -0,0 +1,134 @@ +/* + * 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. + */ +/* eslint-disable import/order */ +import * as React from 'react'; +import { shallow } from 'enzyme'; +import App from '../App'; +import { click, waitAndUpdate } from '../../../../helpers/testUtils'; + +jest.mock('../../../../api/system', () => ({ + getMigrationStatus: jest.fn(), + getSystemStatus: jest.fn(), + migrateDatabase: jest.fn() +})); + +jest.useFakeTimers(); + +const getMigrationStatus = require('../../../../api/system').getMigrationStatus as jest.Mock; +const getSystemStatus = require('../../../../api/system').getSystemStatus as jest.Mock; +const migrateDatabase = require('../../../../api/system').migrateDatabase as jest.Mock; + +const location = { query: {} }; + +beforeEach(() => { + getMigrationStatus.mockClear(); + getSystemStatus.mockClear(); + migrateDatabase.mockClear(); +}); + +afterEach(() => { + jest.clearAllTimers(); +}); + +describe('Maintenance Page', () => { + ['UP', 'DOWN', 'STARTING', 'DB_MIGRATION_NEEDED', 'DB_MIGRATION_RUNNING'].forEach(status => { + it(`should render ${status} status`, async () => { + getSystemStatus.mockImplementationOnce(() => Promise.resolve({ status })); + await checkApp(false); + }); + }); + + it('should render OFFLINE status', async () => { + getSystemStatus.mockImplementationOnce(() => Promise.reject(undefined)); + await checkApp(false); + }); + + it('should poll status', async () => { + getSystemStatus.mockImplementationOnce(() => + Promise.resolve({ status: 'DB_MIGRATION_RUNNING' }) + ); + const wrapper = shallow(); + await waitAndUpdate(wrapper); + expect(getSystemStatus).toBeCalled(); + + getSystemStatus.mockClear(); + getSystemStatus.mockImplementationOnce(() => + Promise.resolve({ status: 'DB_MIGRATION_RUNNING' }) + ); + jest.runOnlyPendingTimers(); + await waitAndUpdate(wrapper); + expect(getSystemStatus).toBeCalled(); + + getSystemStatus.mockClear(); + getSystemStatus.mockImplementationOnce(() => + Promise.resolve({ status: 'DB_MIGRATION_RUNNING' }) + ); + jest.runOnlyPendingTimers(); + await waitAndUpdate(wrapper); + expect(getSystemStatus).toBeCalled(); + }); + + it('should open previous page', async () => { + getSystemStatus.mockImplementationOnce(() => Promise.resolve({ status: 'STARTING' })); + const wrapper = shallow(); + const loadPreviousPage = jest.fn(); + (wrapper.instance() as App).loadPreviousPage = loadPreviousPage; + await waitAndUpdate(wrapper); + + getSystemStatus.mockImplementationOnce(() => Promise.resolve({ status: 'UP' })); + jest.runOnlyPendingTimers(); + await waitAndUpdate(wrapper); + expect(loadPreviousPage).toBeCalled(); + }); +}); + +describe('Setup Page', () => { + ['NO_MIGRATION', 'NOT_SUPPORTED', 'MIGRATION_SUCCEEDED', 'MIGRATION_FAILED'].forEach(state => { + it(`should render ${state} state`, async () => { + getMigrationStatus.mockImplementationOnce(() => + Promise.resolve({ message: 'message', startedAt: '2017-01-02T00:00:00.000Z', state }) + ); + await checkApp(true); + }); + }); + + it('should start migration', async () => { + getMigrationStatus.mockImplementationOnce(() => + Promise.resolve({ state: 'MIGRATION_REQUIRED' }) + ); + migrateDatabase.mockImplementationOnce(() => + Promise.resolve({ startedAt: '2017-01-02T00:00:00.000Z', state: 'MIGRATION_RUNNING' }) + ); + const wrapper = shallow(); + await waitAndUpdate(wrapper); + expect(wrapper).toMatchSnapshot(); + + click(wrapper.find('button')); + expect(migrateDatabase).toBeCalled(); + await waitAndUpdate(wrapper); + expect(wrapper).toMatchSnapshot(); + }); +}); + +async function checkApp(setup: boolean) { + const wrapper = shallow(); + await waitAndUpdate(wrapper); + expect(wrapper).toMatchSnapshot(); +} diff --git a/server/sonar-web/src/main/js/apps/maintenance/components/__tests__/__snapshots__/App-test.tsx.snap b/server/sonar-web/src/main/js/apps/maintenance/components/__tests__/__snapshots__/App-test.tsx.snap new file mode 100644 index 00000000000..784a8eb1969 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/maintenance/components/__tests__/__snapshots__/App-test.tsx.snap @@ -0,0 +1,399 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Maintenance Page should render DB_MIGRATION_NEEDED status 1`] = ` +
+
+ +

+ maintenance.sonarqube_is_under_maintenance +

+

+

+ +

+
+`; + +exports[`Maintenance Page should render DB_MIGRATION_RUNNING status 1`] = ` +
+
+ +

+ maintenance.sonarqube_is_under_maintenance +

+

+

+ +

+
+`; + +exports[`Maintenance Page should render DOWN status 1`] = ` +
+
+ +

+ maintenance.sonarqube_is_down +

+

+ maintenance.sonarqube_is_down.text +

+

+ + maintenance.try_again + +

+
+
+
+`; + +exports[`Maintenance Page should render OFFLINE status 1`] = ` +
+
+ +

+ maintenance.sonarqube_is_offline +

+

+ maintenance.sonarqube_is_offline.text +

+

+ + maintenance.try_again + +

+
+
+
+`; + +exports[`Maintenance Page should render STARTING status 1`] = ` +
+
+ +

+ maintenance.sonarqube_is_starting +

+

+ +

+
+
+
+`; + +exports[`Maintenance Page should render UP status 1`] = ` +
+
+ +

+ maintenance.sonarqube_is_up +

+

+ maintenance.all_systems_opetational +

+

+ + layout.home + +

+
+
+
+`; + +exports[`Setup Page should render MIGRATION_FAILED state 1`] = ` +
+
+ +

+ maintenance.upgrade_failed +

+

+ maintenance.upgrade_failed.text +

+
+
+
+`; + +exports[`Setup Page should render MIGRATION_SUCCEEDED state 1`] = ` +
+
+ +

+ maintenance.database_is_up_to_date +

+

+ + layout.home + +

+
+
+
+`; + +exports[`Setup Page should render NO_MIGRATION state 1`] = ` +
+
+ +

+ maintenance.database_is_up_to_date +

+

+ + layout.home + +

+
+
+
+`; + +exports[`Setup Page should render NOT_SUPPORTED state 1`] = ` +
+
+ +

+ maintenance.migration_not_supported +

+

+ maintenance.migration_not_supported.text +

+
+
+
+`; + +exports[`Setup Page should start migration 1`] = ` +
+
+ +

+ maintenance.upgrade_database +

+

+ maintenance.upgrade_database.1 +

+

+ maintenance.upgrade_database.2 +

+

+ maintenance.upgrade_database.3 +

+
+ +
+
+
+
+`; + +exports[`Setup Page should start migration 2`] = ` +
+
+ +

+ maintenance.database_migration +

+

+ background_tasks.table.started + + +
+ + + +

+

+ +

+
+
+
+`; diff --git a/server/sonar-web/src/main/js/apps/maintenance/init.js b/server/sonar-web/src/main/js/apps/maintenance/init.js deleted file mode 100644 index 15c84c25ed9..00000000000 --- a/server/sonar-web/src/main/js/apps/maintenance/init.js +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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 Backbone from 'backbone'; -import Marionette from 'backbone.marionette'; -import MainView from './main-view'; - -const App = new Marionette.Application(); - -App.on('start', options => { - const viewOptions = { - ...options, - model: new Backbone.Model() - }; - const mainView = new MainView(viewOptions); - mainView.render().refresh(); -}); - -export default function(el, setup, returnTo) { - App.start({ el, setup, returnTo }); -} diff --git a/server/sonar-web/src/main/js/apps/maintenance/main-view.js b/server/sonar-web/src/main/js/apps/maintenance/main-view.js deleted file mode 100644 index da558206ba2..00000000000 --- a/server/sonar-web/src/main/js/apps/maintenance/main-view.js +++ /dev/null @@ -1,95 +0,0 @@ -/* - * 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 Marionette from 'backbone.marionette'; -import Template from './templates/maintenance-main.hbs'; -import { getSystemStatus, getMigrationStatus, migrateDatabase } from '../../api/system'; -import { getBaseUrl } from '../../helpers/urls'; - -export default Marionette.ItemView.extend({ - template: Template, - - events: { - 'click #start-migration': 'startMigration' - }, - - initialize() { - this.pollingInternal = setInterval(() => { - this.refresh(); - }, 5000); - this.wasStarting = false; - }, - - getStatus() { - return this.options.setup ? getMigrationStatus() : getSystemStatus(); - }, - - refresh() { - return this.getStatus().then( - r => { - if (r.status === 'STARTING') { - this.wasStarting = true; - } - // unset `status` in case if was `OFFLINE` previously - this.model.set({ status: undefined, ...r }); - this.render(); - if (this.model.get('status') === 'UP' || this.model.get('state') === 'NO_MIGRATION') { - this.stopPolling(); - } - if (this.model.get('status') === 'UP' && this.wasStarting) { - this.loadPreviousPage(); - } - if (this.model.get('state') === 'MIGRATION_SUCCEEDED') { - this.loadPreviousPage(); - } - }, - () => { - this.model.set({ status: 'OFFLINE' }); - this.render(); - } - ); - }, - - stopPolling() { - clearInterval(this.pollingInternal); - }, - - startMigration() { - migrateDatabase().then( - r => { - this.model.set(r); - this.render(); - }, - () => {} - ); - }, - - loadPreviousPage() { - setInterval(() => { - window.location = this.options.returnTo || getBaseUrl(); - }, 2500); - }, - - serializeData() { - return { - ...Marionette.ItemView.prototype.serializeData.apply(this, arguments), - setup: this.options.setup - }; - } -}); diff --git a/server/sonar-web/src/main/js/apps/maintenance/routes.tsx b/server/sonar-web/src/main/js/apps/maintenance/routes.tsx index 7ef353bbe4a..ac4c46ec21c 100644 --- a/server/sonar-web/src/main/js/apps/maintenance/routes.tsx +++ b/server/sonar-web/src/main/js/apps/maintenance/routes.tsx @@ -19,9 +19,12 @@ */ import * as React from 'react'; import { IndexRoute } from 'react-router'; -import MaintenanceAppContainer from './components/MaintenanceAppContainer'; -import SetupAppContainer from './components/SetupAppContainer'; +import { lazyLoad } from '../../components/lazyLoad'; -export const maintenanceRoutes = ; +export const maintenanceRoutes = ( + import('./components/MaintenanceAppContainer'))} /> +); -export const setupRoutes = ; +export const setupRoutes = ( + import('./components/SetupAppContainer'))} /> +); diff --git a/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-state-migration-failed.hbs b/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-state-migration-failed.hbs deleted file mode 100644 index ab1421b6b7e..00000000000 --- a/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-state-migration-failed.hbs +++ /dev/null @@ -1,2 +0,0 @@ -

{{t 'maintenance.upgrade_failed'}}

-

{{t 'maintenance.upgrade_failed.text'}}

diff --git a/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-state-migration-not-supported.hbs b/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-state-migration-not-supported.hbs deleted file mode 100644 index 6d023044313..00000000000 --- a/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-state-migration-not-supported.hbs +++ /dev/null @@ -1,2 +0,0 @@ -

{{t 'maintenance.migration_not_supported'}}

-

{{t 'maintenance.migration_not_supported.text'}}

diff --git a/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-state-migration-required.hbs b/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-state-migration-required.hbs deleted file mode 100644 index 3236ff5a80f..00000000000 --- a/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-state-migration-required.hbs +++ /dev/null @@ -1,7 +0,0 @@ -

{{t 'maintenance.upgrade_database'}}

-

{{t 'maintenance.upgrade_database.1'}}

-

{{t 'maintenance.upgrade_database.2'}}

-

{{t 'maintenance.upgrade_database.3'}}

-
- -
diff --git a/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-state-migration-running.hbs b/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-state-migration-running.hbs deleted file mode 100644 index c69baef7434..00000000000 --- a/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-state-migration-running.hbs +++ /dev/null @@ -1,11 +0,0 @@ -

{{t 'maintenance.database_migration'}}

-{{#if message}} -

{{message}}

-{{/if}} -{{#if startedAt}} -

- {{t 'background_tasks.table.started'}} {{fromNow startedAt}}
- {{dt startedAt}} -

-{{/if}} -

diff --git a/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-state-migration-succeeded.hbs b/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-state-migration-succeeded.hbs deleted file mode 100644 index 7e70e68cf30..00000000000 --- a/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-state-migration-succeeded.hbs +++ /dev/null @@ -1,4 +0,0 @@ -

{{t 'maintenance.database_is_up_to_date'}}

-

- {{t 'layout.home'}} -

diff --git a/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-state-no-migration.hbs b/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-state-no-migration.hbs deleted file mode 100644 index 6c30631c9a5..00000000000 --- a/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-state-no-migration.hbs +++ /dev/null @@ -1,4 +0,0 @@ -

{{t 'maintenance.database_is_up_to_date'}}

-

- {{t 'layout.home'}} -

diff --git a/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-status-down.hbs b/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-status-down.hbs deleted file mode 100644 index 811aa16ef5b..00000000000 --- a/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-status-down.hbs +++ /dev/null @@ -1,5 +0,0 @@ -

{{t 'maintenance.sonarqube_is_down'}}

-

{{t 'maintenance.sonarqube_is_down.text'}}

-

- {{t 'maintenance.try_again'}} -

diff --git a/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-status-migration.hbs b/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-status-migration.hbs deleted file mode 100644 index f51f4c72475..00000000000 --- a/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-status-migration.hbs +++ /dev/null @@ -1,3 +0,0 @@ -

{{t 'maintenance.sonarqube_is_under_maintenance'}}

-

{{{t 'maintenance.sonarqube_is_under_maintenance.1'}}}

-

{{{t 'maintenance.sonarqube_is_under_maintenance.2'}}}

diff --git a/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-status-offline.hbs b/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-status-offline.hbs deleted file mode 100644 index 6e7bac35c3f..00000000000 --- a/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-status-offline.hbs +++ /dev/null @@ -1,5 +0,0 @@ -

{{t 'maintenance.sonarqube_is_offline'}}

-

{{t 'maintenance.sonarqube_is_offline.text'}}

-

- {{t 'maintenance.try_again'}} -

diff --git a/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-status-starting.hbs b/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-status-starting.hbs deleted file mode 100644 index 96f7d89f122..00000000000 --- a/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-status-starting.hbs +++ /dev/null @@ -1,2 +0,0 @@ -

{{t 'maintenance.sonarqube_is_starting'}}

-

diff --git a/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-status-up.hbs b/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-status-up.hbs deleted file mode 100644 index e97b550f342..00000000000 --- a/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-status-up.hbs +++ /dev/null @@ -1,5 +0,0 @@ -

{{t 'maintenance.sonarqube_is_up'}}

-

{{t 'maintenance.all_systems_opetational'}}

-

- {{t 'layout.home'}} -

diff --git a/server/sonar-web/src/main/js/apps/maintenance/templates/maintenance-main.hbs b/server/sonar-web/src/main/js/apps/maintenance/templates/maintenance-main.hbs deleted file mode 100644 index 44a4bc8798d..00000000000 --- a/server/sonar-web/src/main/js/apps/maintenance/templates/maintenance-main.hbs +++ /dev/null @@ -1,32 +0,0 @@ -
-
- - {{#eq status 'OFFLINE'}} - - {{> '_maintenance-status-offline'}} - - {{else}} - - {{#unless setup}} - - {{#eq status 'UP'}}{{> '_maintenance-status-up'}}{{/eq}} - {{#eq status 'STARTING'}}{{> '_maintenance-status-starting'}}{{/eq}} - {{#eq status 'DOWN'}}{{> '_maintenance-status-down'}}{{/eq}} - {{#eq status 'DB_MIGRATION_NEEDED'}}{{> '_maintenance-status-migration'}}{{/eq}} - {{#eq status 'DB_MIGRATION_RUNNING'}}{{> '_maintenance-status-migration'}}{{/eq}} - - {{else}} - - {{#eq state 'NO_MIGRATION'}}{{> '_maintenance-state-no-migration'}}{{/eq}} - {{#eq state 'MIGRATION_REQUIRED'}}{{> '_maintenance-state-migration-required'}}{{/eq}} - {{#eq state 'NOT_SUPPORTED'}}{{> '_maintenance-state-migration-not-supported'}}{{/eq}} - {{#eq state 'MIGRATION_RUNNING'}}{{> '_maintenance-state-migration-running'}}{{/eq}} - {{#eq state 'MIGRATION_SUCCEEDED'}}{{> '_maintenance-state-migration-succeeded'}}{{/eq}} - {{#eq state 'MIGRATION_FAILED'}}{{> '_maintenance-state-migration-failed'}}{{/eq}} - - {{/unless}} - - {{/eq}} - -
-
diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/LicenseEditionSet-test.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/LicenseEditionSet-test.tsx index fbf8be8d1f7..158a52befed 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/LicenseEditionSet-test.tsx +++ b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/LicenseEditionSet-test.tsx @@ -20,7 +20,7 @@ /* eslint-disable import/order */ import * as React from 'react'; import { shallow } from 'enzyme'; -import { change } from '../../../../helpers/testUtils'; +import { change, waitAndUpdate } from '../../../../helpers/testUtils'; import LicenseEditionSet from '../LicenseEditionSet'; jest.mock('../../../../api/marketplace', () => ({ @@ -59,8 +59,7 @@ it('should display correctly', () => { it('should display the get license link with parameters', async () => { const wrapper = getWrapper(); - await new Promise(setImmediate); - wrapper.update(); + await waitAndUpdate(wrapper); expect(wrapper.find('a')).toMatchSnapshot(); }); diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/Subscription-test.tsx b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/Subscription-test.tsx index fbf21407ac9..da053905930 100644 --- a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/Subscription-test.tsx +++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/Subscription-test.tsx @@ -28,7 +28,7 @@ jest.mock('../../../../api/report', () => { import * as React from 'react'; import { mount, shallow } from 'enzyme'; import Subscription from '../Subscription'; -import { click } from '../../../../helpers/testUtils'; +import { click, waitAndUpdate } from '../../../../helpers/testUtils'; const subscribe = require('../../../../api/report').subscribe as jest.Mock; const unsubscribe = require('../../../../api/report').unsubscribe as jest.Mock; @@ -77,8 +77,7 @@ it('changes subscription', async () => { click(wrapper.find('button')); expect(unsubscribe).toBeCalledWith('foo'); - await new Promise(setImmediate); - wrapper.update(); + await waitAndUpdate(wrapper); click(wrapper.find('button')); expect(subscribe).toBeCalledWith('foo'); diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/DeleteBranchModal-test.tsx b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/DeleteBranchModal-test.tsx index 50a2baa79a3..d23beef1c6e 100644 --- a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/DeleteBranchModal-test.tsx +++ b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/DeleteBranchModal-test.tsx @@ -24,7 +24,7 @@ import * as React from 'react'; import { shallow, ShallowWrapper } from 'enzyme'; import DeleteBranchModal from '../DeleteBranchModal'; import { ShortLivingBranch, BranchType } from '../../../../app/types'; -import { submit, doAsync, click } from '../../../../helpers/testUtils'; +import { submit, doAsync, click, waitAndUpdate } from '../../../../helpers/testUtils'; import { deleteBranch } from '../../../../api/branches'; beforeEach(() => { @@ -38,19 +38,17 @@ it('renders', () => { expect(wrapper).toMatchSnapshot(); }); -it('deletes branch', () => { +it('deletes branch', async () => { (deleteBranch as jest.Mock).mockImplementation(() => Promise.resolve()); const onDelete = jest.fn(); const wrapper = shallowRender(onDelete); submitForm(wrapper); - return doAsync().then(() => { - wrapper.update(); - expect(wrapper.state().loading).toBe(false); - expect(onDelete).toBeCalled(); - expect(deleteBranch).toBeCalledWith('foo', 'feature'); - }); + await waitAndUpdate(wrapper); + expect(wrapper.state().loading).toBe(false); + expect(onDelete).toBeCalled(); + expect(deleteBranch).toBeCalledWith('foo', 'feature'); }); it('cancels', () => { @@ -64,19 +62,17 @@ it('cancels', () => { }); }); -it('stops loading on WS error', () => { +it('stops loading on WS error', async () => { (deleteBranch as jest.Mock).mockImplementation(() => Promise.reject(null)); const onDelete = jest.fn(); const wrapper = shallowRender(onDelete); submitForm(wrapper); - return doAsync().then(() => { - wrapper.update(); - expect(wrapper.state().loading).toBe(false); - expect(onDelete).not.toBeCalled(); - expect(deleteBranch).toBeCalledWith('foo', 'feature'); - }); + await waitAndUpdate(wrapper); + expect(wrapper.state().loading).toBe(false); + expect(onDelete).not.toBeCalled(); + expect(deleteBranch).toBeCalledWith('foo', 'feature'); }); function shallowRender(onDelete: () => void = jest.fn(), onClose: () => void = jest.fn()) { diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/RenameBranchModal-test.tsx b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/RenameBranchModal-test.tsx index 1c55b08314f..3d897c3a953 100644 --- a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/RenameBranchModal-test.tsx +++ b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/RenameBranchModal-test.tsx @@ -24,7 +24,7 @@ import * as React from 'react'; import { shallow, ShallowWrapper } from 'enzyme'; import RenameBranchModal from '../RenameBranchModal'; import { MainBranch } from '../../../../app/types'; -import { submit, doAsync, click, change } from '../../../../helpers/testUtils'; +import { submit, doAsync, click, change, waitAndUpdate } from '../../../../helpers/testUtils'; import { renameBranch } from '../../../../api/branches'; beforeEach(() => { @@ -40,19 +40,17 @@ it('renders', () => { expect(wrapper).toMatchSnapshot(); }); -it('renames branch', () => { +it('renames branch', async () => { (renameBranch as jest.Mock).mockImplementation(() => Promise.resolve()); const onRename = jest.fn(); const wrapper = shallowRender(onRename); fillAndSubmit(wrapper); - return doAsync().then(() => { - wrapper.update(); - expect(wrapper.state().loading).toBe(false); - expect(onRename).toBeCalled(); - expect(renameBranch).toBeCalledWith('foo', 'dev'); - }); + await waitAndUpdate(wrapper); + expect(wrapper.state().loading).toBe(false); + expect(onRename).toBeCalled(); + expect(renameBranch).toBeCalledWith('foo', 'dev'); }); it('cancels', () => { @@ -66,18 +64,16 @@ it('cancels', () => { }); }); -it('stops loading on WS error', () => { +it('stops loading on WS error', async () => { (renameBranch as jest.Mock).mockImplementation(() => Promise.reject(null)); const onRename = jest.fn(); const wrapper = shallowRender(onRename); fillAndSubmit(wrapper); - return doAsync().then(() => { - wrapper.update(); - expect(wrapper.state().loading).toBe(false); - expect(onRename).not.toBeCalled(); - }); + await waitAndUpdate(wrapper); + expect(wrapper.state().loading).toBe(false); + expect(onRename).not.toBeCalled(); }); function shallowRender(onRename: () => void = jest.fn(), onClose: () => void = jest.fn()) { diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/BulkApplyTemplateModal-test.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/BulkApplyTemplateModal-test.tsx index ed9c56dbcb1..09c56289581 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/BulkApplyTemplateModal-test.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/BulkApplyTemplateModal-test.tsx @@ -21,7 +21,7 @@ import * as React from 'react'; import { mount, shallow } from 'enzyme'; import BulkApplyTemplateModal, { Props } from '../BulkApplyTemplateModal'; -import { click } from '../../../helpers/testUtils'; +import { click, waitAndUpdate } from '../../../helpers/testUtils'; jest.mock('react-dom'); @@ -67,8 +67,7 @@ it('bulk applies template to all results', async () => { }); expect(wrapper).toMatchSnapshot(); - await new Promise(setImmediate); - wrapper.update(); + await waitAndUpdate(wrapper); expect(wrapper).toMatchSnapshot(); }); diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/CreateProjectForm-test.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/CreateProjectForm-test.tsx index 1cdac62d85e..d79aa247497 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/CreateProjectForm-test.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/CreateProjectForm-test.tsx @@ -27,7 +27,7 @@ jest.mock('../../../api/components', () => ({ import * as React from 'react'; import { shallow } from 'enzyme'; import CreateProjectForm from '../CreateProjectForm'; -import { change, submit, click } from '../../../helpers/testUtils'; +import { change, submit, click, waitAndUpdate } from '../../../helpers/testUtils'; import { Visibility } from '../../../app/types'; const createProject = require('../../../api/components').createProject as jest.Mock; @@ -65,8 +65,7 @@ it('creates project', async () => { }); expect(wrapper).toMatchSnapshot(); - await new Promise(setImmediate); - wrapper.update(); + await waitAndUpdate(wrapper); expect(wrapper).toMatchSnapshot(); }); diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectRowActions-test.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectRowActions-test.tsx index a615b0e0c77..f9a5eee8406 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectRowActions-test.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectRowActions-test.tsx @@ -21,7 +21,7 @@ import * as React from 'react'; import { shallow } from 'enzyme'; import ProjectRowActions, { Props } from '../ProjectRowActions'; import { Visibility } from '../../../app/types'; -import { click } from '../../../helpers/testUtils'; +import { click, waitAndUpdate } from '../../../helpers/testUtils'; jest.mock('../../../api/components', () => ({ getComponentShow: jest.fn(() => Promise.reject(undefined)) @@ -45,8 +45,7 @@ it('restores access', async () => { expect(wrapper).toMatchSnapshot(); wrapper.prop('onToggleClick')(); - await new Promise(setImmediate); - wrapper.update(); + await waitAndUpdate(wrapper); expect(wrapper).toMatchSnapshot(); click(wrapper.find('.js-restore-access')); diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfilePermissions-test.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfilePermissions-test.tsx index 0536833ec18..08452e22416 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfilePermissions-test.tsx +++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfilePermissions-test.tsx @@ -21,7 +21,7 @@ import * as React from 'react'; import { mount, shallow } from 'enzyme'; import ProfilePermissions from '../ProfilePermissions'; -import { click } from '../../../../helpers/testUtils'; +import { click, waitAndUpdate } from '../../../../helpers/testUtils'; jest.mock('../../../../api/quality-profiles', () => ({ searchUsers: jest.fn(() => Promise.resolve([])), @@ -56,8 +56,7 @@ it('opens add users form', async () => { ); const wrapper = shallow(); expect(searchUsers).toHaveBeenCalled(); - await new Promise(setImmediate); - wrapper.update(); + await waitAndUpdate(wrapper); expect(wrapper.find('ProfilePermissionsForm').exists()).toBeFalsy(); click(wrapper.find('button')); diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRules-test.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRules-test.tsx index 0f75a3da1e3..fcabce9784a 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRules-test.tsx +++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRules-test.tsx @@ -22,6 +22,7 @@ import { shallow } from 'enzyme'; import ProfileRules from '../ProfileRules'; import * as apiRules from '../../../../api/rules'; import * as apiQP from '../../../../api/quality-profiles'; +import { waitAndUpdate } from '../../../../helpers/testUtils'; const PROFILE = { activeRuleCount: 68, @@ -86,8 +87,7 @@ it('should render the quality profiles rules with sonarway comparison', async () const instance = wrapper.instance() as any; instance.mounted = true; instance.loadRules(); - await new Promise(setImmediate); - wrapper.update(); + await waitAndUpdate(wrapper); expect(wrapper.find('ProfileRulesSonarWayComparison')).toHaveLength(1); expect(wrapper).toMatchSnapshot(); }); @@ -136,8 +136,7 @@ it('should not show sonarway comparison if there is no missing rules', async () }) ); const wrapper = shallow(); - await new Promise(setImmediate); - wrapper.update(); + await waitAndUpdate(wrapper); expect(apiQP.getQualityProfile).toHaveBeenCalledTimes(1); expect(wrapper.find('ProfileRulesSonarWayComparison')).toHaveLength(0); }); diff --git a/server/sonar-web/src/main/js/apps/sessions/components/__tests__/EmailAlreadyExists-test.tsx b/server/sonar-web/src/main/js/apps/sessions/components/__tests__/EmailAlreadyExists-test.tsx index f0a4baca93d..81b2176c785 100644 --- a/server/sonar-web/src/main/js/apps/sessions/components/__tests__/EmailAlreadyExists-test.tsx +++ b/server/sonar-web/src/main/js/apps/sessions/components/__tests__/EmailAlreadyExists-test.tsx @@ -20,6 +20,7 @@ import * as React from 'react'; import { shallow } from 'enzyme'; import EmailAlreadyExists from '../EmailAlreadyExists'; +import { waitAndUpdate } from '../../../../helpers/testUtils'; jest.mock('../../../../api/users', () => ({ getIdentityProviders: () => @@ -52,7 +53,6 @@ it('render', async () => { const wrapper = shallow(); (wrapper.instance() as EmailAlreadyExists).mounted = true; (wrapper.instance() as EmailAlreadyExists).fetchIdentityProviders(); - await new Promise(setImmediate); - wrapper.update(); + await waitAndUpdate(wrapper); expect(wrapper).toMatchSnapshot(); }); diff --git a/server/sonar-web/src/main/js/apps/system/components/system-upgrade/__tests__/SystemUpgradeNotif-test.tsx b/server/sonar-web/src/main/js/apps/system/components/system-upgrade/__tests__/SystemUpgradeNotif-test.tsx index 49e07c3a824..0ae8979a9d7 100644 --- a/server/sonar-web/src/main/js/apps/system/components/system-upgrade/__tests__/SystemUpgradeNotif-test.tsx +++ b/server/sonar-web/src/main/js/apps/system/components/system-upgrade/__tests__/SystemUpgradeNotif-test.tsx @@ -20,7 +20,7 @@ /* eslint-disable import/order */ import * as React from 'react'; import { mount, shallow } from 'enzyme'; -import { click } from '../../../../../helpers/testUtils'; +import { click, waitAndUpdate } from '../../../../../helpers/testUtils'; import SystemUpgradeNotif from '../SystemUpgradeNotif'; jest.mock('../../../../../api/system', () => ({ @@ -82,8 +82,7 @@ beforeEach(() => { it('should display correctly', async () => { const wrapper = shallow(); expect(wrapper.type()).toBeNull(); - await new Promise(setImmediate); - wrapper.update(); + await waitAndUpdate(wrapper); expect(wrapper).toMatchSnapshot(); }); @@ -92,8 +91,7 @@ it('should display nothing', async () => { return Promise.resolve({ updateCenterRefresh: '', upgrades: [] }); }); const wrapper = shallow(); - await new Promise(setImmediate); - wrapper.update(); + await waitAndUpdate(wrapper); expect(wrapper.type()).toBeNull(); }); @@ -104,8 +102,7 @@ it('should fetch upgrade when mounting', () => { it('should open the upgrade form', async () => { const wrapper = shallow(); - await new Promise(setImmediate); - wrapper.update(); + await waitAndUpdate(wrapper); click(wrapper.find('button')); expect(wrapper.find('SystemUpgradeForm').exists()).toBeTruthy(); }); diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/NewOrganizationForm-test.js b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/NewOrganizationForm-test.js index 3db2d2faaa6..fcfbf8d810c 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/NewOrganizationForm-test.js +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/NewOrganizationForm-test.js @@ -21,7 +21,7 @@ import React from 'react'; import { mount } from 'enzyme'; import NewOrganizationForm from '../NewOrganizationForm'; -import { change, submit } from '../../../../helpers/testUtils'; +import { change, submit, waitAndUpdate } from '../../../../helpers/testUtils'; jest.mock('../../../../api/organizations', () => ({ createOrganization: () => Promise.resolve(), @@ -38,8 +38,7 @@ it('creates new organization', async () => { change(wrapper.find('input'), 'foo'); submit(wrapper.find('form')); expect(wrapper).toMatchSnapshot(); // spinner - await new Promise(setImmediate); - wrapper.update(); + await waitAndUpdate(wrapper); expect(wrapper).toMatchSnapshot(); expect(onDone).toBeCalledWith('foo'); }); @@ -52,8 +51,7 @@ it('deletes organization', async () => { wrapper.find('DeleteButton').prop('onClick')(); wrapper.update(); expect(wrapper).toMatchSnapshot(); // spinner - await new Promise(setImmediate); - wrapper.update(); + await waitAndUpdate(wrapper); expect(wrapper).toMatchSnapshot(); expect(onDelete).toBeCalled(); }); diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/NewProjectForm-test.js b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/NewProjectForm-test.js index 24f4669106f..2d290f10503 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/NewProjectForm-test.js +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/NewProjectForm-test.js @@ -21,7 +21,7 @@ import React from 'react'; import { mount } from 'enzyme'; import NewProjectForm from '../NewProjectForm'; -import { change, submit } from '../../../../helpers/testUtils'; +import { change, submit, waitAndUpdate } from '../../../../helpers/testUtils'; jest.mock('../../../../api/components', () => ({ createProject: () => Promise.resolve(), @@ -37,8 +37,7 @@ it('creates new project', async () => { change(wrapper.find('input'), 'foo'); submit(wrapper.find('form')); expect(wrapper).toMatchSnapshot(); // spinner - await new Promise(setImmediate); - wrapper.update(); + await waitAndUpdate(wrapper); expect(wrapper).toMatchSnapshot(); expect(onDone).toBeCalledWith('foo'); }); @@ -51,8 +50,7 @@ it('deletes project', async () => { wrapper.find('DeleteButton').prop('onClick')(); wrapper.update(); expect(wrapper).toMatchSnapshot(); // spinner - await new Promise(setImmediate); - wrapper.update(); + await waitAndUpdate(wrapper); expect(wrapper).toMatchSnapshot(); expect(onDelete).toBeCalled(); }); diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/OrganizationStep-test.js b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/OrganizationStep-test.js index d284ebc0461..01add62b539 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/OrganizationStep-test.js +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/OrganizationStep-test.js @@ -21,7 +21,7 @@ import React from 'react'; import { mount } from 'enzyme'; import OrganizationStep from '../OrganizationStep'; -import { click } from '../../../../helpers/testUtils'; +import { click, waitAndUpdate } from '../../../../helpers/testUtils'; import { getOrganizations } from '../../../../api/organizations'; jest.mock('../../../../api/organizations', () => ({ @@ -70,8 +70,7 @@ it('works with existing organization', async () => { stepNumber={1} /> ); - await new Promise(setImmediate); - wrapper.update(); + await waitAndUpdate(wrapper); click(wrapper.find('.js-existing')); expect(wrapper).toMatchSnapshot(); wrapper @@ -95,8 +94,7 @@ it('works with new organization', async () => { stepNumber={1} /> ); - await new Promise(setImmediate); - wrapper.update(); + await waitAndUpdate(wrapper); click(wrapper.find('.js-new')); wrapper.find('NewOrganizationForm').prop('onDone')('new'); wrapper.update(); diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/TokenStep-test.js b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/TokenStep-test.js index c2fc31a6ab7..d336accbca7 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/TokenStep-test.js +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/TokenStep-test.js @@ -21,7 +21,7 @@ import React from 'react'; import { mount } from 'enzyme'; import TokenStep from '../TokenStep'; -import { change, click, submit } from '../../../../helpers/testUtils'; +import { change, click, submit, waitAndUpdate } from '../../../../helpers/testUtils'; jest.mock('../../../../api/user-tokens', () => ({ getTokens: () => Promise.resolve([{ name: 'foo' }]), @@ -44,14 +44,12 @@ it('generates token', async () => { stepNumber={1} /> ); - await new Promise(setImmediate); - wrapper.update(); + await waitAndUpdate(wrapper); expect(wrapper).toMatchSnapshot(); change(wrapper.find('input'), 'my token'); submit(wrapper.find('form')); expect(wrapper).toMatchSnapshot(); // spinner - await new Promise(setImmediate); - wrapper.update(); + await waitAndUpdate(wrapper); expect(wrapper).toMatchSnapshot(); }); @@ -72,8 +70,7 @@ it('revokes token', async () => { wrapper.find('DeleteButton').prop('onClick')(); wrapper.update(); expect(wrapper).toMatchSnapshot(); // spinner - await new Promise(setImmediate); - wrapper.update(); + await waitAndUpdate(wrapper); expect(wrapper).toMatchSnapshot(); }); diff --git a/server/sonar-web/src/main/js/apps/users/__tests__/UsersApp-test.tsx b/server/sonar-web/src/main/js/apps/users/__tests__/UsersApp-test.tsx index 1c059f39f31..5adb4d2dae6 100644 --- a/server/sonar-web/src/main/js/apps/users/__tests__/UsersApp-test.tsx +++ b/server/sonar-web/src/main/js/apps/users/__tests__/UsersApp-test.tsx @@ -22,6 +22,7 @@ import * as React from 'react'; import { Location } from 'history'; import { shallow } from 'enzyme'; import UsersApp from '../UsersApp'; +import { waitAndUpdate } from '../../../helpers/testUtils'; jest.mock('../../../api/users', () => ({ getIdentityProviders: jest.fn(() => @@ -72,8 +73,7 @@ it('should render correctly', async () => { expect(wrapper).toMatchSnapshot(); expect(getIdentityProviders).toHaveBeenCalled(); expect(searchUsers).toHaveBeenCalled(); - await new Promise(setImmediate); - wrapper.update(); + await waitAndUpdate(wrapper); expect(wrapper).toMatchSnapshot(); }); diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/SimpleModal-test.tsx b/server/sonar-web/src/main/js/components/controls/__tests__/SimpleModal-test.tsx index 32d388fa739..c4ea074996c 100644 --- a/server/sonar-web/src/main/js/components/controls/__tests__/SimpleModal-test.tsx +++ b/server/sonar-web/src/main/js/components/controls/__tests__/SimpleModal-test.tsx @@ -20,7 +20,7 @@ import * as React from 'react'; import { shallow } from 'enzyme'; import SimpleModal, { ChildrenProps } from '../SimpleModal'; -import { click } from '../../../helpers/testUtils'; +import { click, waitAndUpdate } from '../../../helpers/testUtils'; it('renders', () => { const inner = () =>
; @@ -64,7 +64,6 @@ it('submits', async () => { expect(onSubmit).toBeCalled(); expect(wrapper).toMatchSnapshot(); - await new Promise(setImmediate); - wrapper.update(); + await waitAndUpdate(wrapper); expect(wrapper).toMatchSnapshot(); }); diff --git a/server/sonar-web/src/main/js/components/lazyLoad.tsx b/server/sonar-web/src/main/js/components/lazyLoad.tsx index f12b4734b6f..34f6de91d6b 100644 --- a/server/sonar-web/src/main/js/components/lazyLoad.tsx +++ b/server/sonar-web/src/main/js/components/lazyLoad.tsx @@ -19,13 +19,15 @@ */ import * as React from 'react'; -interface Loader { - (): Promise<{ default: React.ComponentClass }>; +type ReactComponent

= React.ComponentClass

| React.StatelessComponent

; + +interface Loader

{ + (): Promise<{ default: ReactComponent

}>; } -export function lazyLoad(loader: Loader) { +export function lazyLoad

(loader: Loader

) { interface State { - Component?: React.ComponentClass; + Component?: ReactComponent

; } // use `React.Component`, not `React.PureComponent` to always re-render @@ -43,7 +45,7 @@ export function lazyLoad(loader: Loader) { this.mounted = false; } - receiveComponent = (Component: React.ComponentClass) => { + receiveComponent = (Component: ReactComponent

) => { if (this.mounted) { this.setState({ Component }); } diff --git a/server/sonar-web/src/main/js/helpers/testUtils.ts b/server/sonar-web/src/main/js/helpers/testUtils.ts index 25730a2e4b1..5e7ad5f1d4c 100644 --- a/server/sonar-web/src/main/js/helpers/testUtils.ts +++ b/server/sonar-web/src/main/js/helpers/testUtils.ts @@ -87,3 +87,8 @@ const { intl } = intlProvider.getChildContext(); export function shallowWithIntl(node: React.ReactElement, options: ShallowRendererProps = {}) { return shallow(node, { ...options, context: { intl, ...options.context } }); } + +export async function waitAndUpdate(wrapper: ShallowWrapper | ReactWrapper) { + await new Promise(setImmediate); + wrapper.update(); +}