summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPhilippe Perrin <philippe.perrin@sonarsource.com>2020-06-08 13:35:26 +0200
committersonartech <sonartech@sonarsource.com>2020-06-26 20:04:58 +0000
commit337ba04ed0aeb49728775657ebcfad739109548d (patch)
tree346bdf4142854e6368fddaf8a24180d3fd075dcd
parent02c96005860a0e8dd76491e3ff612f876f7bb09f (diff)
downloadsonarqube-337ba04ed0aeb49728775657ebcfad739109548d.tar.gz
sonarqube-337ba04ed0aeb49728775657ebcfad739109548d.zip
SONAR-13413 Issues page is not available while indexation is in progress
SONAR-13400 Portfolios pages are not available while indexation is in progress SONAR-13398 Project page is not available while indexation is in progress SONAR-13402 Application page is not available while indexation is in progress
-rw-r--r--server/sonar-web/src/main/js/app/components/ComponentContainer.tsx5
-rw-r--r--server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx13
-rw-r--r--server/sonar-web/src/main/js/app/components/extensions/PortfoliosPage.tsx6
-rw-r--r--server/sonar-web/src/main/js/app/components/indexation/PageUnavailableDueToIndexation.tsx79
-rw-r--r--server/sonar-web/src/main/js/app/components/indexation/__tests__/PageUnavailableDueToIndexation-test.tsx57
-rw-r--r--server/sonar-web/src/main/js/app/components/indexation/__tests__/__snapshots__/PageUnavailableDueToIndexation-test.tsx.snap40
-rw-r--r--server/sonar-web/src/main/js/app/utils/startReactApp.tsx7
-rw-r--r--server/sonar-web/src/main/js/components/hoc/__tests__/withIndexationGuard-test.tsx50
-rw-r--r--server/sonar-web/src/main/js/components/hoc/withIndexationGuard.tsx46
-rw-r--r--server/sonar-web/src/main/js/types/types.d.ts1
-rw-r--r--sonar-core/src/main/resources/org/sonar/l10n/core.properties6
11 files changed, 307 insertions, 3 deletions
diff --git a/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx b/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx
index cb82b50cc0b..ecab0962600 100644
--- a/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx
+++ b/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx
@@ -42,6 +42,7 @@ import { BranchLike } from '../../types/branch-like';
import { isPortfolioLike } from '../../types/component';
import ComponentContainerNotFound from './ComponentContainerNotFound';
import { ComponentContext } from './ComponentContext';
+import PageUnavailableDueToIndexation from './indexation/PageUnavailableDueToIndexation';
import ComponentNav from './nav/component/ComponentNav';
interface Props {
@@ -322,6 +323,10 @@ export class ComponentContainer extends React.PureComponent<Props, State> {
return <ComponentContainerNotFound />;
}
+ if (component?.needIssueSync) {
+ return <PageUnavailableDueToIndexation component={component} />;
+ }
+
const { branchLike, branchLikes, currentTask, isPending, tasksInProgress } = this.state;
const isInProgress = tasksInProgress && tasksInProgress.length > 0;
diff --git a/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx b/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx
index a69eb36c59b..6d12970fe6b 100644
--- a/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx
+++ b/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx
@@ -29,6 +29,7 @@ import { mockBranch, mockMainBranch, mockPullRequest } from '../../../helpers/mo
import { mockComponent, mockLocation, mockRouter } from '../../../helpers/testMocks';
import { ComponentQualifier } from '../../../types/component';
import { ComponentContainer } from '../ComponentContainer';
+import PageUnavailableDueToIndexation from '../indexation/PageUnavailableDueToIndexation';
jest.mock('../../../api/branches', () => {
const { mockMainBranch, mockPullRequest } = require.requireActual(
@@ -210,6 +211,18 @@ it('should redirect if the component is a portfolio', async () => {
expect(replace).toBeCalledWith({ pathname: '/portfolio', query: { id: componentKey } });
});
+it('should display display the unavailable page if the component needs issue sync', async () => {
+ (getComponentData as jest.Mock).mockResolvedValueOnce({
+ component: { key: 'test', qualifier: ComponentQualifier.Project, needIssueSync: true }
+ });
+
+ const wrapper = shallowRender();
+
+ await waitAndUpdate(wrapper);
+
+ expect(wrapper.find(PageUnavailableDueToIndexation).exists()).toBe(true);
+});
+
function shallowRender(props: Partial<ComponentContainer['props']> = {}) {
return shallow<ComponentContainer>(
<ComponentContainer
diff --git a/server/sonar-web/src/main/js/app/components/extensions/PortfoliosPage.tsx b/server/sonar-web/src/main/js/app/components/extensions/PortfoliosPage.tsx
index 16cfa0a4caf..3beaf4c1f4b 100644
--- a/server/sonar-web/src/main/js/app/components/extensions/PortfoliosPage.tsx
+++ b/server/sonar-web/src/main/js/app/components/extensions/PortfoliosPage.tsx
@@ -18,8 +18,12 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
+import withIndexationGuard from '../../../components/hoc/withIndexationGuard';
+import { PageContext } from '../indexation/PageUnavailableDueToIndexation';
import GlobalPageExtension from './GlobalPageExtension';
-export default function PortfoliosPage() {
+export function PortfoliosPage() {
return <GlobalPageExtension params={{ pluginKey: 'governance', extensionKey: 'portfolios' }} />;
}
+
+export default withIndexationGuard(PortfoliosPage, PageContext.Portfolios);
diff --git a/server/sonar-web/src/main/js/app/components/indexation/PageUnavailableDueToIndexation.tsx b/server/sonar-web/src/main/js/app/components/indexation/PageUnavailableDueToIndexation.tsx
new file mode 100644
index 00000000000..ef88aa8cef6
--- /dev/null
+++ b/server/sonar-web/src/main/js/app/components/indexation/PageUnavailableDueToIndexation.tsx
@@ -0,0 +1,79 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 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 { FormattedMessage } from 'react-intl';
+import { Alert } from 'sonar-ui-common/components/ui/Alert';
+import { translate } from 'sonar-ui-common/helpers/l10n';
+import withIndexationContext, {
+ WithIndexationContextProps
+} from '../../../components/hoc/withIndexationContext';
+
+interface Props extends WithIndexationContextProps {
+ pageContext?: PageContext;
+ component?: Pick<T.Component, 'qualifier' | 'name'>;
+}
+
+export enum PageContext {
+ Issues = 'issues',
+ Portfolios = 'portfolios'
+}
+
+export class PageUnavailableDueToIndexation extends React.PureComponent<Props> {
+ componentDidUpdate() {
+ if (this.props.indexationContext?.status.isCompleted) {
+ window.location.reload();
+ }
+ }
+
+ render() {
+ const { pageContext, component } = this.props;
+ let messageKey = 'indexation.page_unavailable.title';
+
+ if (pageContext) {
+ messageKey = `${messageKey}.${pageContext}`;
+ }
+
+ return (
+ <div className="page-wrapper-simple">
+ <div className="page-simple">
+ <h1 className="big-spacer-bottom">
+ <FormattedMessage
+ id={messageKey}
+ defaultMessage={translate(messageKey)}
+ values={{
+ componentQualifier: translate('qualifier', component?.qualifier ?? ''),
+ componentName: <em>{component?.name}</em>
+ }}
+ />
+ </h1>
+ <Alert variant="info">
+ <p>{translate('indexation.page_unavailable.description')}</p>
+ <p className="spacer-top">
+ {translate('indexation.page_unavailable.description.additional_information')}
+ </p>
+ </Alert>
+ </div>
+ </div>
+ );
+ }
+}
+
+export default withIndexationContext(PageUnavailableDueToIndexation);
diff --git a/server/sonar-web/src/main/js/app/components/indexation/__tests__/PageUnavailableDueToIndexation-test.tsx b/server/sonar-web/src/main/js/app/components/indexation/__tests__/PageUnavailableDueToIndexation-test.tsx
new file mode 100644
index 00000000000..d988c8764dc
--- /dev/null
+++ b/server/sonar-web/src/main/js/app/components/indexation/__tests__/PageUnavailableDueToIndexation-test.tsx
@@ -0,0 +1,57 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 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 { shallow } from 'enzyme';
+import * as React from 'react';
+import { ComponentQualifier } from '../../../../types/component';
+import { PageContext, PageUnavailableDueToIndexation } from '../PageUnavailableDueToIndexation';
+
+it('should render correctly', () => {
+ const wrapper = shallowRender();
+ expect(wrapper).toMatchSnapshot();
+});
+
+it('should refresh the page once the indexation is complete', () => {
+ const reload = jest.fn();
+ delete window.location;
+ (window as any).location = { reload };
+
+ const wrapper = shallowRender();
+
+ expect(reload).not.toHaveBeenCalled();
+
+ wrapper.setProps({ indexationContext: { status: { isCompleted: true } } });
+ wrapper.update();
+
+ expect(reload).toHaveBeenCalled();
+});
+
+function shallowRender(props?: PageUnavailableDueToIndexation['props']) {
+ return shallow(
+ <PageUnavailableDueToIndexation
+ indexationContext={{
+ status: { isCompleted: false }
+ }}
+ pageContext={PageContext.Issues}
+ component={{ qualifier: ComponentQualifier.Portfolio, name: 'test-portfolio' }}
+ {...props}
+ />
+ );
+}
diff --git a/server/sonar-web/src/main/js/app/components/indexation/__tests__/__snapshots__/PageUnavailableDueToIndexation-test.tsx.snap b/server/sonar-web/src/main/js/app/components/indexation/__tests__/__snapshots__/PageUnavailableDueToIndexation-test.tsx.snap
new file mode 100644
index 00000000000..be114de0437
--- /dev/null
+++ b/server/sonar-web/src/main/js/app/components/indexation/__tests__/__snapshots__/PageUnavailableDueToIndexation-test.tsx.snap
@@ -0,0 +1,40 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<div
+ className="page-wrapper-simple"
+>
+ <div
+ className="page-simple"
+ >
+ <h1
+ className="big-spacer-bottom"
+ >
+ <FormattedMessage
+ defaultMessage="indexation.page_unavailable.title.issues"
+ id="indexation.page_unavailable.title.issues"
+ values={
+ Object {
+ "componentName": <em>
+ test-portfolio
+ </em>,
+ "componentQualifier": "qualifier.VW",
+ }
+ }
+ />
+ </h1>
+ <Alert
+ variant="info"
+ >
+ <p>
+ indexation.page_unavailable.description
+ </p>
+ <p
+ className="spacer-top"
+ >
+ indexation.page_unavailable.description.additional_information
+ </p>
+ </Alert>
+ </div>
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/app/utils/startReactApp.tsx b/server/sonar-web/src/main/js/app/utils/startReactApp.tsx
index cc3c004f5e0..091ca9f1457 100644
--- a/server/sonar-web/src/main/js/app/utils/startReactApp.tsx
+++ b/server/sonar-web/src/main/js/app/utils/startReactApp.tsx
@@ -65,8 +65,10 @@ import systemRoutes from '../../apps/system/routes';
import usersRoutes from '../../apps/users/routes';
import webAPIRoutes from '../../apps/web-api/routes';
import webhooksRoutes from '../../apps/webhooks/routes';
+import withIndexationGuard from '../../components/hoc/withIndexationGuard';
import App from '../components/App';
import GlobalContainer from '../components/GlobalContainer';
+import { PageContext } from '../components/indexation/PageUnavailableDueToIndexation';
import MigrationContainer from '../components/MigrationContainer';
import * as theme from '../theme';
import getStore from './getStore';
@@ -300,7 +302,10 @@ export default function startReactApp(
import('../components/extensions/GlobalPageExtension')
)}
/>
- <Route path="issues" component={Issues} />
+ <Route
+ path="issues"
+ component={withIndexationGuard(Issues, PageContext.Issues)}
+ />
<RouteWithChildRoutes path="organizations" childRoutes={organizationsRoutes} />
<RouteWithChildRoutes path="projects" childRoutes={projectsRoutes} />
<RouteWithChildRoutes path="quality_gates" childRoutes={qualityGatesRoutes} />
diff --git a/server/sonar-web/src/main/js/components/hoc/__tests__/withIndexationGuard-test.tsx b/server/sonar-web/src/main/js/components/hoc/__tests__/withIndexationGuard-test.tsx
new file mode 100644
index 00000000000..fca47cf1b8d
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/hoc/__tests__/withIndexationGuard-test.tsx
@@ -0,0 +1,50 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 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 { mount } from 'enzyme';
+import * as React from 'react';
+import { IndexationContext } from '../../../app/components/indexation/IndexationContext';
+import { PageContext } from '../../../app/components/indexation/PageUnavailableDueToIndexation';
+import { IndexationContextInterface } from '../../../types/indexation';
+import withIndexationGuard from '../withIndexationGuard';
+
+it('should render correctly', () => {
+ let wrapper = mountRender();
+ expect(wrapper.find(TestComponent).exists()).toBe(false);
+
+ wrapper = mountRender({ status: { isCompleted: true } });
+ expect(wrapper.find(TestComponent).exists()).toBe(true);
+});
+
+function mountRender(context?: Partial<IndexationContextInterface>) {
+ return mount(
+ <IndexationContext.Provider value={{ status: { isCompleted: false }, ...context }}>
+ <TestComponentWithGuard />
+ </IndexationContext.Provider>
+ );
+}
+
+class TestComponent extends React.PureComponent {
+ render() {
+ return <h1>TestComponent</h1>;
+ }
+}
+
+const TestComponentWithGuard = withIndexationGuard(TestComponent, PageContext.Issues);
diff --git a/server/sonar-web/src/main/js/components/hoc/withIndexationGuard.tsx b/server/sonar-web/src/main/js/components/hoc/withIndexationGuard.tsx
new file mode 100644
index 00000000000..0036b19fdec
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/hoc/withIndexationGuard.tsx
@@ -0,0 +1,46 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 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 { IndexationContext } from '../../app/components/indexation/IndexationContext';
+import PageUnavailableDueToIndexation, {
+ PageContext
+} from '../../app/components/indexation/PageUnavailableDueToIndexation';
+
+export default function withIndexationGuard<P>(
+ WrappedComponent: React.ComponentType<P>,
+ pageContext: PageContext
+) {
+ return class WithIndexationGuard extends React.PureComponent<P> {
+ render() {
+ return (
+ <IndexationContext.Consumer>
+ {context =>
+ context?.status.isCompleted ? (
+ <WrappedComponent {...this.props} />
+ ) : (
+ <PageUnavailableDueToIndexation pageContext={pageContext} />
+ )
+ }
+ </IndexationContext.Consumer>
+ );
+ }
+ };
+}
diff --git a/server/sonar-web/src/main/js/types/types.d.ts b/server/sonar-web/src/main/js/types/types.d.ts
index 72b81c469c0..9f92aa7cd85 100644
--- a/server/sonar-web/src/main/js/types/types.d.ts
+++ b/server/sonar-web/src/main/js/types/types.d.ts
@@ -126,6 +126,7 @@ declare namespace T {
isFavorite?: boolean;
leakPeriodDate?: string;
name: string;
+ needIssueSync?: boolean;
path?: string;
refKey?: string;
qualityProfiles?: ComponentQualityProfile[];
diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
index bd2f2512a13..97bd3af3747 100644
--- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties
+++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
@@ -3566,7 +3566,11 @@ indexation.in_progress=SonarQube is reloading project data. Some projects will b
indexation.in_progress.details={0}% completed.
indexation.in_progress.admin_details=See {link}.
indexation.completed=All project data has been reloaded.
-
+indexation.page_unavailable.title.issues=Issues page is temporarily unavailable
+indexation.page_unavailable.title.portfolios=Portfolios page is temporarily unavailable
+indexation.page_unavailable.title={componentQualifier} {componentName} is temporarily unavailable
+indexation.page_unavailable.description=This page will be available after the data is reloaded. This might take a while depending on the amount of projects and issues in your SonarQube instance.
+indexation.page_unavailable.description.additional_information=You can keep analyzing your projects during this process.
#------------------------------------------------------------------------------
#
# HOMEPAGE