]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-13413 Issues page is not available while indexation is in progress
authorPhilippe Perrin <philippe.perrin@sonarsource.com>
Mon, 8 Jun 2020 11:35:26 +0000 (13:35 +0200)
committersonartech <sonartech@sonarsource.com>
Fri, 26 Jun 2020 20:04:58 +0000 (20:04 +0000)
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

server/sonar-web/src/main/js/app/components/ComponentContainer.tsx
server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx
server/sonar-web/src/main/js/app/components/extensions/PortfoliosPage.tsx
server/sonar-web/src/main/js/app/components/indexation/PageUnavailableDueToIndexation.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/app/components/indexation/__tests__/PageUnavailableDueToIndexation-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/app/components/indexation/__tests__/__snapshots__/PageUnavailableDueToIndexation-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/app/utils/startReactApp.tsx
server/sonar-web/src/main/js/components/hoc/__tests__/withIndexationGuard-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/hoc/withIndexationGuard.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/types/types.d.ts
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index cb82b50cc0b89a78b1ebc76e72b2ab120014263c..ecab0962600a722d53b012711732d30ea930efa2 100644 (file)
@@ -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;
 
index a69eb36c59b430a84d4180dbc92e115e7d6bb514..6d12970fe6b770ac9e51b6845a5f62953e97b9ea 100644 (file)
@@ -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
index 16cfa0a4cafb0287af677a49bf152b88fa7b7438..3beaf4c1f4be66078f6a752bdff0c6744b01fdd3 100644 (file)
  * 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 (file)
index 0000000..ef88aa8
--- /dev/null
@@ -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 (file)
index 0000000..d988c87
--- /dev/null
@@ -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 (file)
index 0000000..be114de
--- /dev/null
@@ -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>
+`;
index cc3c004f5e04b323583fe0983bd812263a77419a..091ca9f14573ca8b4ddc7541c9982c60ee473bc2 100644 (file)
@@ -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 (file)
index 0000000..fca47cf
--- /dev/null
@@ -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 (file)
index 0000000..0036b19
--- /dev/null
@@ -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>
+      );
+    }
+  };
+}
index 72b81c469c00dd4e555454805dfff87e004225ba..9f92aa7cd85f5c3c40870eb49c5ba330d0ef444d 100644 (file)
@@ -126,6 +126,7 @@ declare namespace T {
     isFavorite?: boolean;
     leakPeriodDate?: string;
     name: string;
+    needIssueSync?: boolean;
     path?: string;
     refKey?: string;
     qualityProfiles?: ComponentQualityProfile[];
index bd2f2512a13a3a45273d86b96ca05f4d7c3ed4c6..97bd3af3747ec5465782d379fa9ad73a0696c564 100644 (file)
@@ -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