]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-15914 Extract branchstatus from redux
authorJeremy Davis <jeremy.davis@sonarsource.com>
Tue, 22 Mar 2022 15:24:08 +0000 (16:24 +0100)
committersonartech <sonartech@sonarsource.com>
Wed, 23 Mar 2022 20:02:45 +0000 (20:02 +0000)
37 files changed:
server/sonar-web/src/main/js/app/components/ComponentContainer.tsx
server/sonar-web/src/main/js/app/components/GlobalContainer.tsx
server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx
server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalContainer-test.tsx.snap
server/sonar-web/src/main/js/app/components/branch-status/BranchStatusContext.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/app/components/branch-status/BranchStatusContextProvider.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/app/components/branch-status/__tests__/BranchStatusContextProvider-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/app/components/branch-status/withBranchStatus.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/app/components/branch-status/withBranchStatusActions.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/app/components/nav/component/HeaderMeta.tsx
server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/HeaderMeta-test.tsx.snap
server/sonar-web/src/main/js/app/components/nav/component/branch-like/MenuItem.tsx
server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/MenuItem-test.tsx.snap
server/sonar-web/src/main/js/apps/code/components/CodeApp.tsx
server/sonar-web/src/main/js/apps/component-measures/components/App.tsx
server/sonar-web/src/main/js/apps/issues/components/AppContainer.tsx
server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx
server/sonar-web/src/main/js/apps/issues/components/__tests__/AppContainer-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/issues/components/__tests__/IssuesApp-test.tsx
server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/IssuesApp-test.tsx.snap
server/sonar-web/src/main/js/apps/overview/pullRequests/PullRequestOverview.tsx
server/sonar-web/src/main/js/apps/projectBranches/components/BranchLikeRow.tsx
server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/BranchLikeRow-test.tsx.snap
server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsApp.tsx
server/sonar-web/src/main/js/components/common/BranchStatus.tsx
server/sonar-web/src/main/js/components/common/__tests__/BranchStatus-test.tsx
server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/BranchStatus-test.tsx.snap
server/sonar-web/src/main/js/components/workspace/WorkspaceComponentViewer.tsx
server/sonar-web/src/main/js/helpers/branch-like.ts
server/sonar-web/src/main/js/helpers/testMocks.ts
server/sonar-web/src/main/js/store/__tests__/branches-test.ts [deleted file]
server/sonar-web/src/main/js/store/__tests__/globalMessages-test.ts [new file with mode: 0644]
server/sonar-web/src/main/js/store/__tests__/rootReducers-test.tsx [deleted file]
server/sonar-web/src/main/js/store/branches.ts [deleted file]
server/sonar-web/src/main/js/store/globalMessages.ts
server/sonar-web/src/main/js/store/rootReducer.ts
server/sonar-web/src/main/js/types/branch-like.ts

index fc98bc60e170cb1b1f98ccb34df4cd0af7c0fa7c..a3d571fcb2edbdc622ee9756012e49c1e48cd98f 100644 (file)
@@ -19,7 +19,6 @@
  */
 import { differenceBy } from 'lodash';
 import * as React from 'react';
-import { connect } from 'react-redux';
 import { getProjectAlmBinding, validateProjectAlmBinding } from '../../api/alm-settings';
 import { getBranches, getPullRequests } from '../../api/branches';
 import { getAnalysisStatus, getTasksForComponent } from '../../api/ce';
@@ -34,7 +33,6 @@ import {
 } from '../../helpers/branch-like';
 import { HttpStatus } from '../../helpers/request';
 import { getPortfolioUrl } from '../../helpers/urls';
-import { registerBranchStatus } from '../../store/branches';
 import {
   ProjectAlmBindingConfigurationErrors,
   ProjectAlmBindingResponse
@@ -46,6 +44,7 @@ import { Task, TaskStatuses, TaskTypes, TaskWarning } from '../../types/tasks';
 import { Component, Status } from '../../types/types';
 import handleRequiredAuthorization from '../utils/handleRequiredAuthorization';
 import withAppStateContext from './app-state/withAppStateContext';
+import withBranchStatusActions from './branch-status/withBranchStatusActions';
 import ComponentContainerNotFound from './ComponentContainerNotFound';
 import { ComponentContext } from './ComponentContext';
 import PageUnavailableDueToIndexation from './indexation/PageUnavailableDueToIndexation';
@@ -55,7 +54,7 @@ interface Props {
   appState: AppState;
   children: React.ReactElement;
   location: Pick<Location, 'query' | 'pathname'>;
-  registerBranchStatus: (branchLike: BranchLike, component: string, status: Status) => void;
+  updateBranchStatus: (branchLike: BranchLike, component: string, status: Status) => void;
   router: Pick<Router, 'replace'>;
 }
 
@@ -359,7 +358,7 @@ export class ComponentContainer extends React.PureComponent<Props, State> {
   registerBranchStatuses = (branchLikes: BranchLike[], component: Component) => {
     branchLikes.forEach(branchLike => {
       if (branchLike.status) {
-        this.props.registerBranchStatus(
+        this.props.updateBranchStatus(
           branchLike,
           component.key,
           branchLike.status.qualityGateStatus
@@ -467,8 +466,4 @@ export class ComponentContainer extends React.PureComponent<Props, State> {
   }
 }
 
-const mapDispatchToProps = { registerBranchStatus };
-
-export default withRouter(
-  connect(null, mapDispatchToProps)(withAppStateContext(ComponentContainer))
-);
+export default withRouter(withAppStateContext(withBranchStatusActions(ComponentContainer)));
index 52a5eccf943b986d2be18fe62b3092df72342388..3ee41a0606eb8a99799f7d6a7d536e9e2a2deef6 100644 (file)
@@ -21,6 +21,7 @@ import * as React from 'react';
 import Workspace from '../../components/workspace/Workspace';
 import A11yProvider from './a11y/A11yProvider';
 import A11ySkipLinks from './a11y/A11ySkipLinks';
+import BranchStatusContextProvider from './branch-status/BranchStatusContextProvider';
 import SuggestionsProvider from './embed-docs-modal/SuggestionsProvider';
 import GlobalFooter from './GlobalFooter';
 import GlobalMessagesContainer from './GlobalMessagesContainer';
@@ -50,19 +51,21 @@ export default function GlobalContainer(props: Props) {
           <div className="global-container">
             <div className="page-wrapper" id="container">
               <div className="page-container">
-                <Workspace>
-                  <IndexationContextProvider>
-                    <LanguagesContextProvider>
-                      <MetricsContextProvider>
-                        <GlobalNav location={props.location} />
-                        <GlobalMessagesContainer />
-                        <IndexationNotification />
-                        <UpdateNotification dismissable={true} />
-                        {props.children}
-                      </MetricsContextProvider>
-                    </LanguagesContextProvider>
-                  </IndexationContextProvider>
-                </Workspace>
+                <BranchStatusContextProvider>
+                  <Workspace>
+                    <IndexationContextProvider>
+                      <LanguagesContextProvider>
+                        <MetricsContextProvider>
+                          <GlobalNav location={props.location} />
+                          <GlobalMessagesContainer />
+                          <IndexationNotification />
+                          <UpdateNotification dismissable={true} />
+                          {props.children}
+                        </MetricsContextProvider>
+                      </LanguagesContextProvider>
+                    </IndexationContextProvider>
+                  </Workspace>
+                </BranchStatusContextProvider>
               </div>
               <PromotionNotification />
             </div>
index a0bbceabb686f9e0beec720f309366fa084c1e4b..f1c4226abca8239a4a1d987585edec8e9e02d737 100644 (file)
@@ -144,10 +144,10 @@ it("doesn't load branches portfolio", async () => {
 });
 
 it('updates branches on change', async () => {
-  const registerBranchStatus = jest.fn();
+  const updateBranchStatus = jest.fn();
   const wrapper = shallowRender({
     location: mockLocation({ query: { id: 'portfolioKey' } }),
-    registerBranchStatus
+    updateBranchStatus
   });
   wrapper.setState({
     branchLikes: [mockMainBranch()],
@@ -160,7 +160,7 @@ it('updates branches on change', async () => {
   expect(getBranches).toBeCalledWith('projectKey');
   expect(getPullRequests).toBeCalledWith('projectKey');
   await waitAndUpdate(wrapper);
-  expect(registerBranchStatus).toBeCalledTimes(2);
+  expect(updateBranchStatus).toBeCalledTimes(2);
 });
 
 it('fetches status', async () => {
@@ -441,7 +441,7 @@ function shallowRender(props: Partial<ComponentContainer['props']> = {}) {
     <ComponentContainer
       appState={mockAppState()}
       location={mockLocation({ query: { id: 'foo' } })}
-      registerBranchStatus={jest.fn()}
+      updateBranchStatus={jest.fn()}
       router={mockRouter()}
       {...props}>
       <Inner />
index ab5e59052290f6aaa1109f3e00961e0564faae7b..2f15d0754f40019b95599e222cfc87fc1411f930 100644 (file)
@@ -15,33 +15,35 @@ exports[`should render correctly 1`] = `
           <div
             className="page-container"
           >
-            <Workspace>
-              <withAppStateContext(IndexationContextProvider)>
-                <LanguagesContextProvider>
-                  <MetricsContextProvider>
-                    <withCurrentUserContext(GlobalNav)
-                      location={
-                        Object {
-                          "action": "PUSH",
-                          "hash": "",
-                          "key": "key",
-                          "pathname": "/path",
-                          "query": Object {},
-                          "search": "",
-                          "state": Object {},
+            <BranchStatusContextProvider>
+              <Workspace>
+                <withAppStateContext(IndexationContextProvider)>
+                  <LanguagesContextProvider>
+                    <MetricsContextProvider>
+                      <withCurrentUserContext(GlobalNav)
+                        location={
+                          Object {
+                            "action": "PUSH",
+                            "hash": "",
+                            "key": "key",
+                            "pathname": "/path",
+                            "query": Object {},
+                            "search": "",
+                            "state": Object {},
+                          }
                         }
-                      }
-                    />
-                    <Connect(GlobalMessages) />
-                    <withCurrentUserContext(withIndexationContext(IndexationNotification)) />
-                    <withCurrentUserContext(withAppStateContext(UpdateNotification))
-                      dismissable={true}
-                    />
-                    <ChildComponent />
-                  </MetricsContextProvider>
-                </LanguagesContextProvider>
-              </withAppStateContext(IndexationContextProvider)>
-            </Workspace>
+                      />
+                      <Connect(GlobalMessages) />
+                      <withCurrentUserContext(withIndexationContext(IndexationNotification)) />
+                      <withCurrentUserContext(withAppStateContext(UpdateNotification))
+                        dismissable={true}
+                      />
+                      <ChildComponent />
+                    </MetricsContextProvider>
+                  </LanguagesContextProvider>
+                </withAppStateContext(IndexationContextProvider)>
+              </Workspace>
+            </BranchStatusContextProvider>
           </div>
           <withCurrentUserContext(PromotionNotification) />
         </div>
diff --git a/server/sonar-web/src/main/js/app/components/branch-status/BranchStatusContext.tsx b/server/sonar-web/src/main/js/app/components/branch-status/BranchStatusContext.tsx
new file mode 100644 (file)
index 0000000..64220f8
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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 { BranchLike, BranchStatusData } from '../../../types/branch-like';
+import { QualityGateStatusCondition } from '../../../types/quality-gates';
+import { Dict, Status } from '../../../types/types';
+
+export interface BranchStatusContextInterface {
+  branchStatusByComponent: Dict<Dict<BranchStatusData>>;
+  fetchBranchStatus: (branchLike: BranchLike, projectKey: string) => void;
+  updateBranchStatus: (
+    branchLike: BranchLike,
+    projectKey: string,
+    status: Status,
+    conditions?: QualityGateStatusCondition[],
+    ignoredConditions?: boolean
+  ) => void;
+}
+
+export const BranchStatusContext = React.createContext<BranchStatusContextInterface>({
+  branchStatusByComponent: {},
+  fetchBranchStatus: () => {
+    throw Error('BranchStatusContext is not provided');
+  },
+  updateBranchStatus: () => {
+    throw Error('BranchStatusContext is not provided');
+  }
+});
diff --git a/server/sonar-web/src/main/js/app/components/branch-status/BranchStatusContextProvider.tsx b/server/sonar-web/src/main/js/app/components/branch-status/BranchStatusContextProvider.tsx
new file mode 100644 (file)
index 0000000..fc0e828
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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 { getQualityGateProjectStatus } from '../../../api/quality-gates';
+import { getBranchLikeKey, getBranchLikeQuery } from '../../../helpers/branch-like';
+import { extractStatusConditionsFromProjectStatus } from '../../../helpers/qualityGates';
+import { BranchLike, BranchStatusData } from '../../../types/branch-like';
+import { QualityGateStatusCondition } from '../../../types/quality-gates';
+import { Dict, Status } from '../../../types/types';
+import { BranchStatusContext } from './BranchStatusContext';
+
+interface State {
+  branchStatusByComponent: Dict<Dict<BranchStatusData>>;
+}
+
+export default class BranchStatusContextProvider extends React.PureComponent<{}, State> {
+  mounted = false;
+  state: State = {
+    branchStatusByComponent: {}
+  };
+
+  componentDidMount() {
+    this.mounted = true;
+  }
+
+  componentWillUnmount() {
+    this.mounted = false;
+  }
+
+  fetchBranchStatus = async (branchLike: BranchLike, projectKey: string) => {
+    const projectStatus = await getQualityGateProjectStatus({
+      projectKey,
+      ...getBranchLikeQuery(branchLike)
+    }).catch(() => undefined);
+
+    if (!this.mounted || projectStatus === undefined) {
+      return;
+    }
+
+    const { ignoredConditions, status } = projectStatus;
+    const conditions = extractStatusConditionsFromProjectStatus(projectStatus);
+
+    this.updateBranchStatus(branchLike, projectKey, status, conditions, ignoredConditions);
+  };
+
+  updateBranchStatus = (
+    branchLike: BranchLike,
+    projectKey: string,
+    status: Status,
+    conditions?: QualityGateStatusCondition[],
+    ignoredConditions?: boolean
+  ) => {
+    const branchLikeKey = getBranchLikeKey(branchLike);
+
+    this.setState(({ branchStatusByComponent }) => ({
+      branchStatusByComponent: {
+        ...branchStatusByComponent,
+        [projectKey]: {
+          ...(branchStatusByComponent[projectKey] || {}),
+          [branchLikeKey]: {
+            conditions,
+            ignoredConditions,
+            status
+          }
+        }
+      }
+    }));
+  };
+
+  render() {
+    return (
+      <BranchStatusContext.Provider
+        value={{
+          branchStatusByComponent: this.state.branchStatusByComponent,
+          fetchBranchStatus: this.fetchBranchStatus,
+          updateBranchStatus: this.updateBranchStatus
+        }}>
+        {this.props.children}
+      </BranchStatusContext.Provider>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/app/components/branch-status/__tests__/BranchStatusContextProvider-test.tsx b/server/sonar-web/src/main/js/app/components/branch-status/__tests__/BranchStatusContextProvider-test.tsx
new file mode 100644 (file)
index 0000000..9b9e26d
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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 { getQualityGateProjectStatus } from '../../../../api/quality-gates';
+import { mockBranch } from '../../../../helpers/mocks/branch-like';
+import { waitAndUpdate } from '../../../../helpers/testUtils';
+import { QualityGateProjectStatus } from '../../../../types/quality-gates';
+import BranchStatusContextProvider from '../BranchStatusContextProvider';
+
+jest.mock('../../../../api/quality-gates', () => ({
+  getQualityGateProjectStatus: jest.fn().mockResolvedValue({})
+}));
+
+describe('fetchBranchStatus', () => {
+  it('should get the branch status', async () => {
+    const projectKey = 'projectKey';
+    const branchName = 'branch-6.7';
+    const status: QualityGateProjectStatus = {
+      status: 'OK',
+      conditions: [],
+      ignoredConditions: false
+    };
+    (getQualityGateProjectStatus as jest.Mock).mockResolvedValueOnce(status);
+    const wrapper = shallowRender();
+
+    wrapper.instance().fetchBranchStatus(mockBranch({ name: branchName }), projectKey);
+
+    expect(getQualityGateProjectStatus).toBeCalledWith({ projectKey, branch: branchName });
+
+    await waitAndUpdate(wrapper);
+
+    expect(wrapper.state().branchStatusByComponent).toEqual({
+      [projectKey]: { [`branch-${branchName}`]: status }
+    });
+  });
+
+  it('should ignore errors', async () => {
+    (getQualityGateProjectStatus as jest.Mock).mockRejectedValueOnce('error');
+    const wrapper = shallowRender();
+
+    wrapper.instance().fetchBranchStatus(mockBranch(), 'project');
+
+    await waitAndUpdate(wrapper);
+
+    expect(wrapper.state().branchStatusByComponent).toEqual({});
+  });
+});
+
+function shallowRender() {
+  return shallow<BranchStatusContextProvider>(
+    <BranchStatusContextProvider>
+      <div />
+    </BranchStatusContextProvider>
+  );
+}
diff --git a/server/sonar-web/src/main/js/app/components/branch-status/withBranchStatus.tsx b/server/sonar-web/src/main/js/app/components/branch-status/withBranchStatus.tsx
new file mode 100644 (file)
index 0000000..8f1ccf4
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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 { getWrappedDisplayName } from '../../../components/hoc/utils';
+import { getBranchStatusByBranchLike } from '../../../helpers/branch-like';
+import { BranchLike, BranchStatusData } from '../../../types/branch-like';
+import { Component } from '../../../types/types';
+import { BranchStatusContext } from './BranchStatusContext';
+
+export default function withBranchStatus<
+  P extends { branchLike: BranchLike; component: Component }
+>(WrappedComponent: React.ComponentType<P & BranchStatusData>) {
+  return class WithBranchStatus extends React.PureComponent<Omit<P, keyof BranchStatusData>> {
+    static displayName = getWrappedDisplayName(WrappedComponent, 'withBranchStatus');
+
+    render() {
+      const { branchLike, component } = this.props;
+
+      return (
+        <BranchStatusContext.Consumer>
+          {({ branchStatusByComponent }) => {
+            const { conditions, ignoredConditions, status } = getBranchStatusByBranchLike(
+              branchStatusByComponent,
+              component.key,
+              branchLike
+            );
+
+            return (
+              <WrappedComponent
+                conditions={conditions}
+                ignoredConditions={ignoredConditions}
+                status={status}
+                {...(this.props as P)}
+              />
+            );
+          }}
+        </BranchStatusContext.Consumer>
+      );
+    }
+  };
+}
diff --git a/server/sonar-web/src/main/js/app/components/branch-status/withBranchStatusActions.tsx b/server/sonar-web/src/main/js/app/components/branch-status/withBranchStatusActions.tsx
new file mode 100644 (file)
index 0000000..97ffc3d
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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 { getWrappedDisplayName } from '../../../components/hoc/utils';
+import { BranchStatusContext, BranchStatusContextInterface } from './BranchStatusContext';
+
+export type WithBranchStatusActionsProps =
+  | Pick<BranchStatusContextInterface, 'fetchBranchStatus'>
+  | Pick<BranchStatusContextInterface, 'updateBranchStatus'>;
+
+export default function withBranchStatusActions<P>(
+  WrappedComponent: React.ComponentType<P & WithBranchStatusActionsProps>
+) {
+  return class WithBranchStatusActions extends React.PureComponent<
+    Omit<P, keyof BranchStatusContextInterface>
+  > {
+    static displayName = getWrappedDisplayName(WrappedComponent, 'withBranchStatusActions');
+
+    render() {
+      return (
+        <BranchStatusContext.Consumer>
+          {({ fetchBranchStatus, updateBranchStatus }) => (
+            <WrappedComponent
+              fetchBranchStatus={fetchBranchStatus}
+              updateBranchStatus={updateBranchStatus}
+              {...(this.props as P)}
+            />
+          )}
+        </BranchStatusContext.Consumer>
+      );
+    }
+  };
+}
index f77c4429974016ed94f2e86aee22b819fc2dc332..1729155f1a015b6f178ea5d359042e6ed6a7135a 100644 (file)
@@ -86,7 +86,7 @@ export function HeaderMeta(props: HeaderMetaProps) {
               <DetachIcon className="little-spacer-left" size={12} />
             </a>
           )}
-          <BranchStatus branchLike={branchLike} component={component.key} />
+          <BranchStatus branchLike={branchLike} component={component} />
         </div>
       )}
     </>
index 14a257a1b3c92b75b6c0a7efa39f04dee215a491..110542461d9cb4af3cdab703e65215a3e4d4ccc3 100644 (file)
@@ -196,7 +196,7 @@ exports[`should render correctly for a pull request 1`] = `
         size={12}
       />
     </a>
-    <Connect(BranchStatus)
+    <withBranchStatus(BranchStatus)
       branchLike={
         Object {
           "analysisDate": "2018-01-01",
@@ -208,7 +208,30 @@ exports[`should render correctly for a pull request 1`] = `
           "url": "https://example.com/pull/1234",
         }
       }
-      component="my-project"
+      component={
+        Object {
+          "analysisDate": "2017-01-02T00:00:00.000Z",
+          "breadcrumbs": Array [],
+          "key": "my-project",
+          "name": "MyProject",
+          "qualifier": "TRK",
+          "qualityGate": Object {
+            "isDefault": true,
+            "key": "30",
+            "name": "Sonar way",
+          },
+          "qualityProfiles": Array [
+            Object {
+              "deleted": false,
+              "key": "my-qp",
+              "language": "ts",
+              "name": "Sonar way",
+            },
+          ],
+          "tags": Array [],
+          "version": "0.0.1",
+        }
+      }
     />
   </div>
 </Fragment>
index eeb24400e8a01331bce2b14b2c9b01ad1bba8503..298ae9448f0c34f794015b99e2d9487c8d2ab26a 100644 (file)
@@ -58,7 +58,7 @@ export function MenuItem(props: MenuItemProps) {
           )}
         </div>
         <div className="spacer-left">
-          <BranchStatus branchLike={branchLike} component={component.key} />
+          <BranchStatus branchLike={branchLike} component={component} />
         </div>
       </div>
     </li>
index 393f960966dbb1a2fb7348e32d513c6cf264cb13..e9df1e0578f6796eecc5d7eca6dea36a65755fb0 100644 (file)
@@ -36,7 +36,7 @@ exports[`should render a main branch correctly 1`] = `
     <div
       className="spacer-left"
     >
-      <Connect(BranchStatus)
+      <withBranchStatus(BranchStatus)
         branchLike={
           Object {
             "analysisDate": "2018-01-01",
@@ -45,7 +45,28 @@ exports[`should render a main branch correctly 1`] = `
             "name": "master",
           }
         }
-        component="my-project"
+        component={
+          Object {
+            "breadcrumbs": Array [],
+            "key": "my-project",
+            "name": "MyProject",
+            "qualifier": "TRK",
+            "qualityGate": Object {
+              "isDefault": true,
+              "key": "30",
+              "name": "Sonar way",
+            },
+            "qualityProfiles": Array [
+              Object {
+                "deleted": false,
+                "key": "my-qp",
+                "language": "ts",
+                "name": "Sonar way",
+              },
+            ],
+            "tags": Array [],
+          }
+        }
       />
     </div>
   </div>
@@ -85,7 +106,7 @@ exports[`should render a non-main branch, indented and selected item correctly 1
     <div
       className="spacer-left"
     >
-      <Connect(BranchStatus)
+      <withBranchStatus(BranchStatus)
         branchLike={
           Object {
             "analysisDate": "2018-01-01",
@@ -96,7 +117,28 @@ exports[`should render a non-main branch, indented and selected item correctly 1
             "title": "Foo Bar feature",
           }
         }
-        component="my-project"
+        component={
+          Object {
+            "breadcrumbs": Array [],
+            "key": "my-project",
+            "name": "MyProject",
+            "qualifier": "TRK",
+            "qualityGate": Object {
+              "isDefault": true,
+              "key": "30",
+              "name": "Sonar way",
+            },
+            "qualityProfiles": Array [
+              Object {
+                "deleted": false,
+                "key": "my-qp",
+                "language": "ts",
+                "name": "Sonar way",
+              },
+            ],
+            "tags": Array [],
+          }
+        }
       />
     </div>
   </div>
index 57bb881e8429c29fe7b43c509bf7a3fc6708c4d7..c6e556793570bbde6ba5ee16461ec6bf0b79e1ce 100644 (file)
@@ -24,9 +24,9 @@ import { Location } from 'history';
 import { debounce, intersection } from 'lodash';
 import * as React from 'react';
 import { Helmet } from 'react-helmet-async';
-import { connect } from 'react-redux';
 import { InjectedRouter } from 'react-router';
 import A11ySkipTarget from '../../../app/components/a11y/A11ySkipTarget';
+import withBranchStatusActions from '../../../app/components/branch-status/withBranchStatusActions';
 import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
 import withMetricsContext from '../../../app/components/metrics/withMetricsContext';
 import HelpTooltip from '../../../components/controls/HelpTooltip';
@@ -35,7 +35,6 @@ import { Alert } from '../../../components/ui/Alert';
 import { isPullRequest, isSameBranchLike } from '../../../helpers/branch-like';
 import { translate } from '../../../helpers/l10n';
 import { getCodeUrl, getProjectUrl } from '../../../helpers/urls';
-import { fetchBranchStatus } from '../../../store/branches';
 import { BranchLike } from '../../../types/branch-like';
 import { isPortfolioLike } from '../../../types/component';
 import { Breadcrumb, Component, ComponentMeasure, Dict, Issue, Metric } from '../../../types/types';
@@ -52,20 +51,15 @@ import Components from './Components';
 import Search from './Search';
 import SourceViewerWrapper from './SourceViewerWrapper';
 
-interface DispatchToProps {
-  fetchBranchStatus: (branchLike: BranchLike, projectKey: string) => Promise<void>;
-}
-
-interface OwnProps {
+interface Props {
   branchLike?: BranchLike;
   component: Component;
+  fetchBranchStatus: (branchLike: BranchLike, projectKey: string) => Promise<void>;
   location: Pick<Location, 'query'>;
   router: Pick<InjectedRouter, 'push'>;
   metrics: Dict<Metric>;
 }
 
-type Props = DispatchToProps & OwnProps;
-
 interface State {
   baseComponent?: ComponentMeasure;
   breadcrumbs: Breadcrumb[];
@@ -404,8 +398,4 @@ const AlertContent = styled.div`
   align-items: center;
 `;
 
-const mapDispatchToProps: DispatchToProps = {
-  fetchBranchStatus: fetchBranchStatus as any
-};
-
-export default connect(null, mapDispatchToProps)(withMetricsContext(CodeApp));
+export default withBranchStatusActions(withMetricsContext(CodeApp));
index ce61093edf2e7fed9d694bb947e07cbe9be35dd4..df8f249abe21ee29b94bfa47747889f2b4303edc 100644 (file)
@@ -22,10 +22,10 @@ import key from 'keymaster';
 import { debounce, keyBy } from 'lodash';
 import * as React from 'react';
 import { Helmet } from 'react-helmet-async';
-import { connect } from 'react-redux';
 import { withRouter, WithRouterProps } from 'react-router';
 import { getMeasuresWithPeriod } from '../../../api/measures';
 import { getAllMetrics } from '../../../api/metrics';
+import withBranchStatusActions from '../../../app/components/branch-status/withBranchStatusActions';
 import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
 import ScreenPositionHelper from '../../../components/common/ScreenPositionHelper';
 import HelpTooltip from '../../../components/controls/HelpTooltip';
@@ -44,7 +44,6 @@ import {
   removeSideBarClass,
   removeWhitePageClass
 } from '../../../helpers/pages';
-import { fetchBranchStatus } from '../../../store/branches';
 import { BranchLike } from '../../../types/branch-like';
 import { ComponentQualifier, isPortfolioLike } from '../../../types/component';
 import {
@@ -354,11 +353,9 @@ export class App extends React.PureComponent<Props, State> {
   }
 }
 
-const mapDispatchToProps = { fetchBranchStatus: fetchBranchStatus as any };
-
 const AlertContent = styled.div`
   display: flex;
   align-items: center;
 `;
 
-export default withRouter(connect(null, mapDispatchToProps)(App));
+export default withRouter(withBranchStatusActions(App));
index 49e326575cb02f1796cdb20c99e3ce9ac8f58c7f..e3060ab0d0f4a4e6b98170104a655b9a6a966159 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import { connect } from 'react-redux';
-import { searchIssues } from '../../../api/issues';
+import withBranchStatusActions from '../../../app/components/branch-status/withBranchStatusActions';
 import withCurrentUserContext from '../../../app/components/current-user/withCurrentUserContext';
 import { withRouter } from '../../../components/hoc/withRouter';
 import { lazyLoadComponent } from '../../../components/lazyLoadComponent';
-import { parseIssueFromResponse } from '../../../helpers/issues';
-import { fetchBranchStatus } from '../../../store/branches';
-import { Store } from '../../../store/rootReducer';
-import { FetchIssuesPromise } from '../../../types/issues';
-import { RawQuery } from '../../../types/types';
 
 const IssuesAppContainer = lazyLoadComponent(() => import('./IssuesApp'), 'IssuesAppContainer');
 
-const fetchIssues = (query: RawQuery) => {
-  return searchIssues({
-    ...query,
-    additionalFields: '_all',
-    timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone
-  }).then(response => {
-    const parsedIssues = response.issues.map(issue =>
-      parseIssueFromResponse(issue, response.components, response.users, response.rules)
-    );
-    return { ...response, issues: parsedIssues } as FetchIssuesPromise;
-  });
-};
-
-const mapStateToProps = (_state: Store) => ({
-  fetchIssues
-});
-
-const mapDispatchToProps = { fetchBranchStatus };
-
-export default withRouter(
-  withCurrentUserContext(connect(mapStateToProps, mapDispatchToProps)(IssuesAppContainer))
-);
+export default withRouter(withCurrentUserContext(withBranchStatusActions(IssuesAppContainer)));
index 92db8e0acc80c083d1b74815717ed404a51a33ab..7da860bcc43893aba0c18b6ae4599eea9557adf1 100644 (file)
@@ -23,6 +23,7 @@ import { debounce, keyBy, omit, without } from 'lodash';
 import * as React from 'react';
 import { Helmet } from 'react-helmet-async';
 import { FormattedMessage } from 'react-intl';
+import { searchIssues } from '../../../api/issues';
 import A11ySkipTarget from '../../../app/components/a11y/A11ySkipTarget';
 import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
 import EmptySearch from '../../../components/common/EmptySearch';
@@ -43,6 +44,7 @@ import {
   isSameBranchLike
 } from '../../../helpers/branch-like';
 import handleRequiredAuthentication from '../../../helpers/handleRequiredAuthentication';
+import { parseIssueFromResponse } from '../../../helpers/issues';
 import { KeyboardCodes, KeyboardKeys } from '../../../helpers/keycodes';
 import { translate, translateWithParameters } from '../../../helpers/l10n';
 import {
@@ -97,7 +99,6 @@ interface Props {
   component?: Component;
   currentUser: CurrentUser;
   fetchBranchStatus: (branchLike: BranchLike, projectKey: string) => void;
-  fetchIssues: (query: RawQuery) => Promise<FetchIssuesPromise>;
   location: Location;
   onBranchesChange?: () => void;
   router: Pick<Router, 'push' | 'replace'>;
@@ -405,6 +406,19 @@ export default class App extends React.PureComponent<Props, State> {
 
   createdAfterIncludesTime = () => Boolean(this.props.location.query.createdAfter?.includes('T'));
 
+  fetchIssuesHelper = (query: RawQuery) => {
+    return searchIssues({
+      ...query,
+      additionalFields: '_all',
+      timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone
+    }).then(response => {
+      const parsedIssues = response.issues.map(issue =>
+        parseIssueFromResponse(issue, response.components, response.users, response.rules)
+      );
+      return { ...response, issues: parsedIssues } as FetchIssuesPromise;
+    });
+  };
+
   fetchIssues = (additional: RawQuery, requestFacets = false): Promise<FetchIssuesPromise> => {
     const { component } = this.props;
     const { myIssues, openFacets, query } = this.state;
@@ -437,7 +451,8 @@ export default class App extends React.PureComponent<Props, State> {
     if (myIssues) {
       Object.assign(parameters, { assignees: '__me__' });
     }
-    return this.props.fetchIssues(parameters);
+
+    return this.fetchIssuesHelper(parameters);
   };
 
   fetchFirstIssues() {
@@ -701,7 +716,7 @@ export default class App extends React.PureComponent<Props, State> {
       Object.assign(parameters, { assignees: '__me__' });
     }
 
-    return this.props.fetchIssues(parameters).then(({ facets }) => parseFacets(facets)[property]);
+    return this.fetchIssuesHelper(parameters).then(({ facets }) => parseFacets(facets)[property]);
   };
 
   closeFacet = (property: string) => {
diff --git a/server/sonar-web/src/main/js/apps/issues/components/__tests__/AppContainer-test.tsx b/server/sonar-web/src/main/js/apps/issues/components/__tests__/AppContainer-test.tsx
deleted file mode 100644 (file)
index bc78aea..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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 { connect } from 'react-redux';
-import { searchIssues } from '../../../../api/issues';
-import { fetchBranchStatus } from '../../../../store/branches';
-import '../AppContainer';
-
-jest.mock('react-redux', () => ({
-  connect: jest.fn(() => (a: any) => a)
-}));
-
-jest.mock('../../../../api/issues', () => ({
-  searchIssues: jest.fn().mockResolvedValue({ issues: [{ some: 'issue' }], bar: 'baz' })
-}));
-
-jest.mock('../../../../helpers/issues', () => ({
-  parseIssueFromResponse: jest.fn(() => 'parsedIssue')
-}));
-
-describe('redux', () => {
-  it('should correctly map state and dispatch props', async () => {
-    const [mapStateToProps, mapDispatchToProps] = (connect as jest.Mock).mock.calls[0];
-    const { fetchIssues } = mapStateToProps({});
-
-    expect(mapDispatchToProps).toEqual(expect.objectContaining({ fetchBranchStatus }));
-
-    const result = await fetchIssues({ foo: 'bar' });
-    expect(searchIssues).toBeCalledWith(
-      expect.objectContaining({ foo: 'bar', additionalFields: '_all' })
-    );
-    expect(result).toEqual({ issues: ['parsedIssue'], bar: 'baz' });
-  });
-});
index 0e4ed2e7a22f7d2b18cad049ff86e588bdf9cb7c..5a43255cc15cef10970549bda0673bed2b0dfef2 100644 (file)
@@ -20,6 +20,7 @@
 import { shallow } from 'enzyme';
 import key from 'keymaster';
 import * as React from 'react';
+import { searchIssues } from '../../../../api/issues';
 import handleRequiredAuthentication from '../../../../helpers/handleRequiredAuthentication';
 import { KeyboardCodes, KeyboardKeys } from '../../../../helpers/keycodes';
 import { mockPullRequest } from '../../../../helpers/mocks/branch-like';
@@ -36,10 +37,12 @@ import {
   mockIssue,
   mockLocation,
   mockLoggedInUser,
+  mockRawIssue,
   mockRouter
 } from '../../../../helpers/testMocks';
 import { KEYCODE_MAP, keydown, waitAndUpdate } from '../../../../helpers/testUtils';
 import { ComponentQualifier } from '../../../../types/component';
+import { ReferencedComponent } from '../../../../types/issues';
 import { Issue, Paging } from '../../../../types/types';
 import {
   disableLocationsNavigator,
@@ -51,6 +54,7 @@ import {
 } from '../../actions';
 import BulkChangeModal from '../BulkChangeModal';
 import App from '../IssuesApp';
+import IssuesSourceViewer from '../IssuesSourceViewer';
 
 jest.mock('../../../../helpers/pages', () => ({
   addSideBarClass: jest.fn(),
@@ -82,6 +86,16 @@ jest.mock('keymaster', () => {
   return key;
 });
 
+jest.mock('../../../../api/issues', () => ({
+  searchIssues: jest.fn().mockResolvedValue({ facets: [], issues: [] })
+}));
+
+const RAW_ISSUES = [
+  mockRawIssue(false, { key: 'foo' }),
+  mockRawIssue(false, { key: 'bar' }),
+  mockRawIssue(true, { key: 'third' }),
+  mockRawIssue(false, { key: 'fourth' })
+];
 const ISSUES = [
   mockIssue(false, { key: 'foo' }),
   mockIssue(false, { key: 'bar' }),
@@ -91,7 +105,7 @@ const ISSUES = [
 const FACETS = [{ property: 'severities', values: [{ val: 'MINOR', count: 4 }] }];
 const PAGING = { pageIndex: 1, pageSize: 100, total: 4 };
 
-const referencedComponent = { key: 'foo-key', name: 'bar', uuid: 'foo-uuid' };
+const referencedComponent: ReferencedComponent = { key: 'foo-key', name: 'bar', uuid: 'foo-uuid' };
 
 const originalAddEventListener = window.addEventListener;
 const originalRemoveEventListener = window.removeEventListener;
@@ -103,6 +117,17 @@ beforeEach(() => {
   Object.defineProperty(window, 'removeEventListener', {
     value: jest.fn()
   });
+
+  (searchIssues as jest.Mock).mockResolvedValue({
+    components: [referencedComponent],
+    effortTotal: 1,
+    facets: FACETS,
+    issues: RAW_ISSUES,
+    languages: [],
+    paging: PAGING,
+    rules: [],
+    users: []
+  });
 });
 
 afterEach(() => {
@@ -112,6 +137,9 @@ afterEach(() => {
   Object.defineProperty(window, 'removeEventListener', {
     value: originalRemoveEventListener
   });
+
+  jest.clearAllMocks();
+  (searchIssues as jest.Mock).mockReset();
 });
 
 it('should show warnning when not all projects are accessible', () => {
@@ -205,11 +233,11 @@ it('should open standard facets for vulnerabilities and hotspots', () => {
 it('should switch to source view if an issue is selected', async () => {
   const wrapper = shallowRender();
   await waitAndUpdate(wrapper);
-  expect(wrapper).toMatchSnapshot();
+  expect(wrapper.find(IssuesSourceViewer).exists()).toBe(false);
 
   wrapper.setProps({ location: mockLocation({ query: { open: 'third' } }) });
   await waitAndUpdate(wrapper);
-  expect(wrapper).toMatchSnapshot();
+  expect(wrapper.find(IssuesSourceViewer).exists()).toBe(true);
 });
 
 it('should correctly bind key events for issue navigation', async () => {
@@ -297,18 +325,18 @@ it('should be able to check all issue with global checkbox', async () => {
 });
 
 it('should check all issues, even the ones that are not visible', async () => {
-  const wrapper = shallowRender({
-    fetchIssues: jest.fn().mockResolvedValue({
-      components: [referencedComponent],
-      effortTotal: 1,
-      facets: FACETS,
-      issues: ISSUES,
-      languages: [],
-      paging: { pageIndex: 1, pageSize: 100, total: 250 },
-      rules: [],
-      users: []
-    })
+  (searchIssues as jest.Mock).mockResolvedValueOnce({
+    components: [referencedComponent],
+    effortTotal: 1,
+    facets: FACETS,
+    issues: ISSUES,
+    languages: [],
+    paging: { pageIndex: 1, pageSize: 100, total: 250 },
+    rules: [],
+    users: []
   });
+
+  const wrapper = shallowRender();
   const instance = wrapper.instance();
   await waitAndUpdate(wrapper);
 
@@ -319,18 +347,17 @@ it('should check all issues, even the ones that are not visible', async () => {
 });
 
 it('should check max 500 issues', async () => {
-  const wrapper = shallowRender({
-    fetchIssues: jest.fn().mockResolvedValue({
-      components: [referencedComponent],
-      effortTotal: 1,
-      facets: FACETS,
-      issues: ISSUES,
-      languages: [],
-      paging: { pageIndex: 1, pageSize: 100, total: 1000 },
-      rules: [],
-      users: []
-    })
+  (searchIssues as jest.Mock).mockResolvedValue({
+    components: [referencedComponent],
+    effortTotal: 1,
+    facets: FACETS,
+    issues: ISSUES,
+    languages: [],
+    paging: { pageIndex: 1, pageSize: 100, total: 1000 },
+    rules: [],
+    users: []
   });
+  const wrapper = shallowRender();
   const instance = wrapper.instance();
   await waitAndUpdate(wrapper);
 
@@ -342,8 +369,8 @@ it('should check max 500 issues', async () => {
 });
 
 it('should fetch issues for component', async () => {
+  (searchIssues as jest.Mock).mockImplementation(mockSearchIssuesResponse());
   const wrapper = shallowRender({
-    fetchIssues: fetchIssuesMockFactory(),
     location: mockLocation({
       query: { open: '0' }
     })
@@ -405,11 +432,12 @@ it('should correctly handle filter changes', () => {
 });
 
 it('should fetch issues until defined', async () => {
+  (searchIssues as jest.Mock).mockImplementation(mockSearchIssuesResponse());
+
   const mockDone = (_: Issue[], paging: Paging) =>
     paging.total <= paging.pageIndex * paging.pageSize;
 
   const wrapper = shallowRender({
-    fetchIssues: fetchIssuesMockFactory(),
     location: mockLocation({
       query: { open: '0' }
     })
@@ -488,9 +516,8 @@ describe('keyup event handler', () => {
 });
 
 it('should fetch more issues', async () => {
-  const wrapper = shallowRender({
-    fetchIssues: fetchIssuesMockFactory()
-  });
+  (searchIssues as jest.Mock).mockImplementation(mockSearchIssuesResponse());
+  const wrapper = shallowRender({});
   const instance = wrapper.instance();
   await waitAndUpdate(wrapper);
 
@@ -509,7 +536,7 @@ it('should refresh branch status if issues are updated', async () => {
 
   const updatedIssue: Issue = { ...ISSUES[0], type: 'SECURITY_HOTSPOT' };
   instance.handleIssueChange(updatedIssue);
-  expect(wrapper.state().issues).toEqual([updatedIssue, ISSUES[1], ISSUES[2], ISSUES[3]]);
+  expect(wrapper.state().issues[0].type).toEqual(updatedIssue.type);
   expect(fetchBranchStatus).toBeCalledWith(branchLike, component.key);
 
   fetchBranchStatus.mockClear();
@@ -530,9 +557,9 @@ it('should update the open issue when it is changed', async () => {
 });
 
 it('should handle createAfter query param with time', async () => {
-  const fetchIssues = fetchIssuesMockFactory();
+  (searchIssues as jest.Mock).mockImplementation(mockSearchIssuesResponse());
+
   const wrapper = shallowRender({
-    fetchIssues,
     location: mockLocation({ query: { createdAfter: '2020-10-21' } })
   });
   expect(wrapper.instance().createdAfterIncludesTime()).toBe(false);
@@ -541,23 +568,23 @@ it('should handle createAfter query param with time', async () => {
   wrapper.setProps({ location: mockLocation({ query: { createdAfter: '2020-10-21T17:21:00Z' } }) });
   expect(wrapper.instance().createdAfterIncludesTime()).toBe(true);
 
-  fetchIssues.mockClear();
+  (searchIssues as jest.Mock).mockClear();
 
   wrapper.instance().fetchIssues({});
-  expect(fetchIssues).toBeCalledWith(
+  expect(searchIssues).toBeCalledWith(
     expect.objectContaining({ createdAfter: '2020-10-21T17:21:00+0000' })
   );
 });
 
-function fetchIssuesMockFactory(keyCount = 0, lineCount = 1) {
-  return jest.fn().mockImplementation(({ p }: { p: number }) =>
+function mockSearchIssuesResponse(keyCount = 0, lineCount = 1) {
+  return ({ p = 1 }) =>
     Promise.resolve({
       components: [referencedComponent],
       effortTotal: 1,
       facets: FACETS,
       issues: [
-        mockIssue(false, {
-          key: '' + keyCount++,
+        mockRawIssue(false, {
+          key: `${keyCount++}`,
           textRange: {
             startLine: lineCount++,
             endLine: lineCount,
@@ -565,8 +592,8 @@ function fetchIssuesMockFactory(keyCount = 0, lineCount = 1) {
             endOffset: 15
           }
         }),
-        mockIssue(false, {
-          key: '' + keyCount++,
+        mockRawIssue(false, {
+          key: `${keyCount}`,
           textRange: {
             startLine: lineCount++,
             endLine: lineCount,
@@ -576,11 +603,10 @@ function fetchIssuesMockFactory(keyCount = 0, lineCount = 1) {
         })
       ],
       languages: [],
-      paging: { pageIndex: p || 1, pageSize: 2, total: 6 },
+      paging: { pageIndex: p, pageSize: 2, total: 6 },
       rules: [],
       users: []
-    })
-  );
+    });
 }
 
 function shallowRender(props: Partial<App['props']> = {}) {
@@ -594,16 +620,6 @@ function shallowRender(props: Partial<App['props']> = {}) {
       }}
       currentUser={mockLoggedInUser()}
       fetchBranchStatus={jest.fn()}
-      fetchIssues={jest.fn().mockResolvedValue({
-        components: [referencedComponent],
-        effortTotal: 1,
-        facets: FACETS,
-        issues: ISSUES,
-        languages: [],
-        paging: PAGING,
-        rules: [],
-        users: []
-      })}
       location={mockLocation({ pathname: '/issues', query: {} })}
       onBranchesChange={() => {}}
       router={mockRouter()}
index 5af18892050ad4fe84e19cca22419cf7c8a2f3e4..13685dbe0a5880f8a7658c5a9de71e166c1280f0 100644 (file)
@@ -149,674 +149,3 @@ exports[`should show warnning when not all projects are accessible 1`] = `
   </section>
 </div>
 `;
-
-exports[`should switch to source view if an issue is selected 1`] = `
-<div
-  className="layout-page issues"
-  id="issues-page"
->
-  <Suggestions
-    suggestions="issues"
-  />
-  <Helmet
-    defer={false}
-    encodeSpecialCharacters={true}
-    prioritizeSeoTags={false}
-    title="issues.page"
-  />
-  <h1
-    className="a11y-hidden"
-  >
-    issues.page
-  </h1>
-  <ScreenPositionHelper
-    className="layout-page-side-outer"
-  >
-    <Component />
-  </ScreenPositionHelper>
-  <div
-    className="layout-page-main"
-    role="main"
-  >
-    <div
-      className="layout-page-header-panel layout-page-main-header issues-main-header"
-    >
-      <div
-        className="layout-page-header-panel-inner layout-page-main-header-inner"
-      >
-        <div
-          className="layout-page-main-inner"
-        >
-          <A11ySkipTarget
-            anchor="issues_main"
-          />
-          <div
-            className="pull-left"
-          >
-            <Checkbox
-              checked={false}
-              className="spacer-right text-middle"
-              disabled={false}
-              id="issues-selection"
-              onCheck={[Function]}
-              thirdState={false}
-              title="issues.select_all_issues"
-            />
-            <Button
-              disabled={true}
-              id="issues-bulk-change"
-              onClick={[Function]}
-            >
-              bulk_change
-            </Button>
-          </div>
-          <PageActions
-            canSetHome={false}
-            effortTotal={1}
-            paging={
-              Object {
-                "pageIndex": 1,
-                "pageSize": 100,
-                "total": 4,
-              }
-            }
-            selectedIndex={0}
-          />
-        </div>
-      </div>
-    </div>
-    <div
-      className="layout-page-main-inner"
-    >
-      <DeferredSpinner
-        loading={false}
-      >
-        <div>
-          <h2
-            className="a11y-hidden"
-          >
-            list_of_issues
-          </h2>
-          <IssuesList
-            checked={Array []}
-            component={
-              Object {
-                "breadcrumbs": Array [],
-                "key": "foo",
-                "name": "bar",
-                "qualifier": "Doe",
-              }
-            }
-            issues={
-              Array [
-                Object {
-                  "actions": Array [],
-                  "component": "main.js",
-                  "componentLongName": "main.js",
-                  "componentQualifier": "FIL",
-                  "componentUuid": "foo1234",
-                  "creationDate": "2017-03-01T09:36:01+0100",
-                  "flows": Array [],
-                  "fromHotspot": false,
-                  "key": "foo",
-                  "line": 25,
-                  "message": "Reduce the number of conditional operators (4) used in the expression",
-                  "project": "myproject",
-                  "projectKey": "foo",
-                  "projectName": "Foo",
-                  "rule": "javascript:S1067",
-                  "ruleName": "foo",
-                  "secondaryLocations": Array [],
-                  "severity": "MAJOR",
-                  "status": "OPEN",
-                  "textRange": Object {
-                    "endLine": 26,
-                    "endOffset": 15,
-                    "startLine": 25,
-                    "startOffset": 0,
-                  },
-                  "transitions": Array [],
-                  "type": "BUG",
-                },
-                Object {
-                  "actions": Array [],
-                  "component": "main.js",
-                  "componentLongName": "main.js",
-                  "componentQualifier": "FIL",
-                  "componentUuid": "foo1234",
-                  "creationDate": "2017-03-01T09:36:01+0100",
-                  "flows": Array [],
-                  "fromHotspot": false,
-                  "key": "bar",
-                  "line": 25,
-                  "message": "Reduce the number of conditional operators (4) used in the expression",
-                  "project": "myproject",
-                  "projectKey": "foo",
-                  "projectName": "Foo",
-                  "rule": "javascript:S1067",
-                  "ruleName": "foo",
-                  "secondaryLocations": Array [],
-                  "severity": "MAJOR",
-                  "status": "OPEN",
-                  "textRange": Object {
-                    "endLine": 26,
-                    "endOffset": 15,
-                    "startLine": 25,
-                    "startOffset": 0,
-                  },
-                  "transitions": Array [],
-                  "type": "BUG",
-                },
-                Object {
-                  "actions": Array [],
-                  "component": "main.js",
-                  "componentLongName": "main.js",
-                  "componentQualifier": "FIL",
-                  "componentUuid": "foo1234",
-                  "creationDate": "2017-03-01T09:36:01+0100",
-                  "flows": Array [
-                    Array [
-                      Object {
-                        "component": "main.js",
-                        "textRange": Object {
-                          "endLine": 2,
-                          "endOffset": 2,
-                          "startLine": 1,
-                          "startOffset": 1,
-                        },
-                      },
-                      Object {
-                        "component": "main.js",
-                        "textRange": Object {
-                          "endLine": 2,
-                          "endOffset": 2,
-                          "startLine": 1,
-                          "startOffset": 1,
-                        },
-                      },
-                      Object {
-                        "component": "main.js",
-                        "textRange": Object {
-                          "endLine": 2,
-                          "endOffset": 2,
-                          "startLine": 1,
-                          "startOffset": 1,
-                        },
-                      },
-                    ],
-                    Array [
-                      Object {
-                        "component": "main.js",
-                        "textRange": Object {
-                          "endLine": 2,
-                          "endOffset": 2,
-                          "startLine": 1,
-                          "startOffset": 1,
-                        },
-                      },
-                      Object {
-                        "component": "main.js",
-                        "textRange": Object {
-                          "endLine": 2,
-                          "endOffset": 2,
-                          "startLine": 1,
-                          "startOffset": 1,
-                        },
-                      },
-                    ],
-                  ],
-                  "fromHotspot": false,
-                  "key": "third",
-                  "line": 25,
-                  "message": "Reduce the number of conditional operators (4) used in the expression",
-                  "project": "myproject",
-                  "projectKey": "foo",
-                  "projectName": "Foo",
-                  "rule": "javascript:S1067",
-                  "ruleName": "foo",
-                  "secondaryLocations": Array [
-                    Object {
-                      "component": "main.js",
-                      "textRange": Object {
-                        "endLine": 2,
-                        "endOffset": 2,
-                        "startLine": 1,
-                        "startOffset": 1,
-                      },
-                    },
-                    Object {
-                      "component": "main.js",
-                      "textRange": Object {
-                        "endLine": 2,
-                        "endOffset": 2,
-                        "startLine": 1,
-                        "startOffset": 1,
-                      },
-                    },
-                  ],
-                  "severity": "MAJOR",
-                  "status": "OPEN",
-                  "textRange": Object {
-                    "endLine": 26,
-                    "endOffset": 15,
-                    "startLine": 25,
-                    "startOffset": 0,
-                  },
-                  "transitions": Array [],
-                  "type": "BUG",
-                },
-                Object {
-                  "actions": Array [],
-                  "component": "main.js",
-                  "componentLongName": "main.js",
-                  "componentQualifier": "FIL",
-                  "componentUuid": "foo1234",
-                  "creationDate": "2017-03-01T09:36:01+0100",
-                  "flows": Array [],
-                  "fromHotspot": false,
-                  "key": "fourth",
-                  "line": 25,
-                  "message": "Reduce the number of conditional operators (4) used in the expression",
-                  "project": "myproject",
-                  "projectKey": "foo",
-                  "projectName": "Foo",
-                  "rule": "javascript:S1067",
-                  "ruleName": "foo",
-                  "secondaryLocations": Array [],
-                  "severity": "MAJOR",
-                  "status": "OPEN",
-                  "textRange": Object {
-                    "endLine": 26,
-                    "endOffset": 15,
-                    "startLine": 25,
-                    "startOffset": 0,
-                  },
-                  "transitions": Array [],
-                  "type": "BUG",
-                },
-              ]
-            }
-            onFilterChange={[Function]}
-            onIssueChange={[Function]}
-            onIssueCheck={[Function]}
-            onIssueClick={[Function]}
-            onPopupToggle={[Function]}
-            selectedIssue={
-              Object {
-                "actions": Array [],
-                "component": "main.js",
-                "componentLongName": "main.js",
-                "componentQualifier": "FIL",
-                "componentUuid": "foo1234",
-                "creationDate": "2017-03-01T09:36:01+0100",
-                "flows": Array [],
-                "fromHotspot": false,
-                "key": "foo",
-                "line": 25,
-                "message": "Reduce the number of conditional operators (4) used in the expression",
-                "project": "myproject",
-                "projectKey": "foo",
-                "projectName": "Foo",
-                "rule": "javascript:S1067",
-                "ruleName": "foo",
-                "secondaryLocations": Array [],
-                "severity": "MAJOR",
-                "status": "OPEN",
-                "textRange": Object {
-                  "endLine": 26,
-                  "endOffset": 15,
-                  "startLine": 25,
-                  "startOffset": 0,
-                },
-                "transitions": Array [],
-                "type": "BUG",
-              }
-            }
-          />
-          <ListFooter
-            count={4}
-            loadMore={[Function]}
-            loading={false}
-            total={4}
-          />
-        </div>
-      </DeferredSpinner>
-    </div>
-  </div>
-</div>
-`;
-
-exports[`should switch to source view if an issue is selected 2`] = `
-<div
-  className="layout-page issues"
-  id="issues-page"
->
-  <Suggestions
-    suggestions="issues"
-  />
-  <Helmet
-    defer={false}
-    encodeSpecialCharacters={true}
-    prioritizeSeoTags={false}
-    title="Reduce the number of conditional operators (4) used in the expression"
-  />
-  <h1
-    className="a11y-hidden"
-  >
-    issues.page
-  </h1>
-  <ScreenPositionHelper
-    className="layout-page-side-outer"
-  >
-    <Component />
-  </ScreenPositionHelper>
-  <div
-    className="layout-page-main"
-    role="main"
-  >
-    <A11ySkipTarget
-      anchor="issues_main"
-    />
-    <div
-      className="layout-page-main-inner"
-    >
-      <IssuesSourceViewer
-        issues={
-          Array [
-            Object {
-              "actions": Array [],
-              "component": "main.js",
-              "componentLongName": "main.js",
-              "componentQualifier": "FIL",
-              "componentUuid": "foo1234",
-              "creationDate": "2017-03-01T09:36:01+0100",
-              "flows": Array [],
-              "fromHotspot": false,
-              "key": "foo",
-              "line": 25,
-              "message": "Reduce the number of conditional operators (4) used in the expression",
-              "project": "myproject",
-              "projectKey": "foo",
-              "projectName": "Foo",
-              "rule": "javascript:S1067",
-              "ruleName": "foo",
-              "secondaryLocations": Array [],
-              "severity": "MAJOR",
-              "status": "OPEN",
-              "textRange": Object {
-                "endLine": 26,
-                "endOffset": 15,
-                "startLine": 25,
-                "startOffset": 0,
-              },
-              "transitions": Array [],
-              "type": "BUG",
-            },
-            Object {
-              "actions": Array [],
-              "component": "main.js",
-              "componentLongName": "main.js",
-              "componentQualifier": "FIL",
-              "componentUuid": "foo1234",
-              "creationDate": "2017-03-01T09:36:01+0100",
-              "flows": Array [],
-              "fromHotspot": false,
-              "key": "bar",
-              "line": 25,
-              "message": "Reduce the number of conditional operators (4) used in the expression",
-              "project": "myproject",
-              "projectKey": "foo",
-              "projectName": "Foo",
-              "rule": "javascript:S1067",
-              "ruleName": "foo",
-              "secondaryLocations": Array [],
-              "severity": "MAJOR",
-              "status": "OPEN",
-              "textRange": Object {
-                "endLine": 26,
-                "endOffset": 15,
-                "startLine": 25,
-                "startOffset": 0,
-              },
-              "transitions": Array [],
-              "type": "BUG",
-            },
-            Object {
-              "actions": Array [],
-              "component": "main.js",
-              "componentLongName": "main.js",
-              "componentQualifier": "FIL",
-              "componentUuid": "foo1234",
-              "creationDate": "2017-03-01T09:36:01+0100",
-              "flows": Array [
-                Array [
-                  Object {
-                    "component": "main.js",
-                    "textRange": Object {
-                      "endLine": 2,
-                      "endOffset": 2,
-                      "startLine": 1,
-                      "startOffset": 1,
-                    },
-                  },
-                  Object {
-                    "component": "main.js",
-                    "textRange": Object {
-                      "endLine": 2,
-                      "endOffset": 2,
-                      "startLine": 1,
-                      "startOffset": 1,
-                    },
-                  },
-                  Object {
-                    "component": "main.js",
-                    "textRange": Object {
-                      "endLine": 2,
-                      "endOffset": 2,
-                      "startLine": 1,
-                      "startOffset": 1,
-                    },
-                  },
-                ],
-                Array [
-                  Object {
-                    "component": "main.js",
-                    "textRange": Object {
-                      "endLine": 2,
-                      "endOffset": 2,
-                      "startLine": 1,
-                      "startOffset": 1,
-                    },
-                  },
-                  Object {
-                    "component": "main.js",
-                    "textRange": Object {
-                      "endLine": 2,
-                      "endOffset": 2,
-                      "startLine": 1,
-                      "startOffset": 1,
-                    },
-                  },
-                ],
-              ],
-              "fromHotspot": false,
-              "key": "third",
-              "line": 25,
-              "message": "Reduce the number of conditional operators (4) used in the expression",
-              "project": "myproject",
-              "projectKey": "foo",
-              "projectName": "Foo",
-              "rule": "javascript:S1067",
-              "ruleName": "foo",
-              "secondaryLocations": Array [
-                Object {
-                  "component": "main.js",
-                  "textRange": Object {
-                    "endLine": 2,
-                    "endOffset": 2,
-                    "startLine": 1,
-                    "startOffset": 1,
-                  },
-                },
-                Object {
-                  "component": "main.js",
-                  "textRange": Object {
-                    "endLine": 2,
-                    "endOffset": 2,
-                    "startLine": 1,
-                    "startOffset": 1,
-                  },
-                },
-              ],
-              "severity": "MAJOR",
-              "status": "OPEN",
-              "textRange": Object {
-                "endLine": 26,
-                "endOffset": 15,
-                "startLine": 25,
-                "startOffset": 0,
-              },
-              "transitions": Array [],
-              "type": "BUG",
-            },
-            Object {
-              "actions": Array [],
-              "component": "main.js",
-              "componentLongName": "main.js",
-              "componentQualifier": "FIL",
-              "componentUuid": "foo1234",
-              "creationDate": "2017-03-01T09:36:01+0100",
-              "flows": Array [],
-              "fromHotspot": false,
-              "key": "fourth",
-              "line": 25,
-              "message": "Reduce the number of conditional operators (4) used in the expression",
-              "project": "myproject",
-              "projectKey": "foo",
-              "projectName": "Foo",
-              "rule": "javascript:S1067",
-              "ruleName": "foo",
-              "secondaryLocations": Array [],
-              "severity": "MAJOR",
-              "status": "OPEN",
-              "textRange": Object {
-                "endLine": 26,
-                "endOffset": 15,
-                "startLine": 25,
-                "startOffset": 0,
-              },
-              "transitions": Array [],
-              "type": "BUG",
-            },
-          ]
-        }
-        loadIssues={[Function]}
-        locationsNavigator={false}
-        onIssueChange={[Function]}
-        onIssueSelect={[Function]}
-        onLocationSelect={[Function]}
-        openIssue={
-          Object {
-            "actions": Array [],
-            "component": "main.js",
-            "componentLongName": "main.js",
-            "componentQualifier": "FIL",
-            "componentUuid": "foo1234",
-            "creationDate": "2017-03-01T09:36:01+0100",
-            "flows": Array [
-              Array [
-                Object {
-                  "component": "main.js",
-                  "textRange": Object {
-                    "endLine": 2,
-                    "endOffset": 2,
-                    "startLine": 1,
-                    "startOffset": 1,
-                  },
-                },
-                Object {
-                  "component": "main.js",
-                  "textRange": Object {
-                    "endLine": 2,
-                    "endOffset": 2,
-                    "startLine": 1,
-                    "startOffset": 1,
-                  },
-                },
-                Object {
-                  "component": "main.js",
-                  "textRange": Object {
-                    "endLine": 2,
-                    "endOffset": 2,
-                    "startLine": 1,
-                    "startOffset": 1,
-                  },
-                },
-              ],
-              Array [
-                Object {
-                  "component": "main.js",
-                  "textRange": Object {
-                    "endLine": 2,
-                    "endOffset": 2,
-                    "startLine": 1,
-                    "startOffset": 1,
-                  },
-                },
-                Object {
-                  "component": "main.js",
-                  "textRange": Object {
-                    "endLine": 2,
-                    "endOffset": 2,
-                    "startLine": 1,
-                    "startOffset": 1,
-                  },
-                },
-              ],
-            ],
-            "fromHotspot": false,
-            "key": "third",
-            "line": 25,
-            "message": "Reduce the number of conditional operators (4) used in the expression",
-            "project": "myproject",
-            "projectKey": "foo",
-            "projectName": "Foo",
-            "rule": "javascript:S1067",
-            "ruleName": "foo",
-            "secondaryLocations": Array [
-              Object {
-                "component": "main.js",
-                "textRange": Object {
-                  "endLine": 2,
-                  "endOffset": 2,
-                  "startLine": 1,
-                  "startOffset": 1,
-                },
-              },
-              Object {
-                "component": "main.js",
-                "textRange": Object {
-                  "endLine": 2,
-                  "endOffset": 2,
-                  "startLine": 1,
-                  "startOffset": 1,
-                },
-              },
-            ],
-            "severity": "MAJOR",
-            "status": "OPEN",
-            "textRange": Object {
-              "endLine": 26,
-              "endOffset": 15,
-              "startLine": 25,
-              "startOffset": 0,
-            },
-            "transitions": Array [],
-            "type": "BUG",
-          }
-        }
-      />
-    </div>
-  </div>
-</div>
-`;
index bfb4c232ec33282f80476ced38ce26ff5e14ea65..50b98a20b6429263599b80ced62b1aee43530265 100644 (file)
 import classNames from 'classnames';
 import { differenceBy, uniq } from 'lodash';
 import * as React from 'react';
-import { connect } from 'react-redux';
 import { getMeasuresWithMetrics } from '../../../api/measures';
+import { BranchStatusContextInterface } from '../../../app/components/branch-status/BranchStatusContext';
+import withBranchStatus from '../../../app/components/branch-status/withBranchStatus';
+import withBranchStatusActions from '../../../app/components/branch-status/withBranchStatusActions';
 import HelpTooltip from '../../../components/controls/HelpTooltip';
 import { Alert } from '../../../components/ui/Alert';
 import { getBranchLikeQuery } from '../../../helpers/branch-like';
 import { translate } from '../../../helpers/l10n';
 import { enhanceConditionWithMeasure, enhanceMeasuresWithMetrics } from '../../../helpers/measures';
 import { isDefined } from '../../../helpers/types';
-import { fetchBranchStatus } from '../../../store/branches';
-import { getBranchStatusByBranchLike, Store } from '../../../store/rootReducer';
-import { BranchLike, PullRequest } from '../../../types/branch-like';
+import { BranchStatusData, PullRequest } from '../../../types/branch-like';
 import { IssueType } from '../../../types/issues';
-import { QualityGateStatusCondition } from '../../../types/quality-gates';
-import { Component, MeasureEnhanced, Status } from '../../../types/types';
+import { Component, MeasureEnhanced } from '../../../types/types';
 import IssueLabel from '../components/IssueLabel';
 import IssueRating from '../components/IssueRating';
 import MeasurementLabel from '../components/MeasurementLabel';
@@ -44,23 +43,11 @@ import { MeasurementType, PR_METRICS } from '../utils';
 import AfterMergeEstimate from './AfterMergeEstimate';
 import LargeQualityGateBadge from './LargeQualityGateBadge';
 
-interface StateProps {
-  conditions?: QualityGateStatusCondition[];
-  ignoredConditions?: boolean;
-  status?: Status;
-}
-
-interface DispatchProps {
-  fetchBranchStatus: (branchLike: BranchLike, projectKey: string) => void;
-}
-
-interface OwnProps {
+interface Props extends BranchStatusData, Pick<BranchStatusContextInterface, 'fetchBranchStatus'> {
   branchLike: PullRequest;
   component: Component;
 }
 
-type Props = StateProps & DispatchProps & OwnProps;
-
 interface State {
   loading: boolean;
   measures: MeasureEnhanced[];
@@ -281,18 +268,4 @@ export class PullRequestOverview extends React.PureComponent<Props, State> {
   }
 }
 
-const mapStateToProps = (state: Store, { branchLike, component }: Props) => {
-  const { conditions, ignoredConditions, status } = getBranchStatusByBranchLike(
-    state,
-    component.key,
-    branchLike
-  );
-  return { conditions, ignoredConditions, status };
-};
-
-const mapDispatchToProps = { fetchBranchStatus: fetchBranchStatus as any };
-
-export default connect<StateProps, DispatchProps, OwnProps>(
-  mapStateToProps,
-  mapDispatchToProps
-)(PullRequestOverview);
+export default withBranchStatus(withBranchStatusActions(PullRequestOverview));
index 5a37613cc24f384a65f6c2197a70c8ff9335ce47..50aa2dc36d908d96a8b075b2a2bd181b6bf16ec5 100644 (file)
@@ -58,7 +58,7 @@ export function BranchLikeRow(props: BranchLikeRowProps) {
         </span>
       </td>
       <td className="nowrap">
-        <BranchStatus branchLike={branchLike} component={component.key} />
+        <BranchStatus branchLike={branchLike} component={component} />
       </td>
       <td className="nowrap">{<DateFromNow date={branchLike.analysisDate} />}</td>
       {displayPurgeSetting && isBranch(branchLike) && (
index a39fb5dd0ec62c2e4e8f6d8602762294c0b3bbd1..ba11b5fc0c274008f5bd487b2b797ac96dff2e13 100644 (file)
@@ -26,7 +26,7 @@ exports[`should render correctly for branch 1`] = `
   <td
     className="nowrap"
   >
-    <Connect(BranchStatus)
+    <withBranchStatus(BranchStatus)
       branchLike={
         Object {
           "analysisDate": "2018-01-01",
@@ -35,7 +35,28 @@ exports[`should render correctly for branch 1`] = `
           "name": "branch-6.7",
         }
       }
-      component="my-project"
+      component={
+        Object {
+          "breadcrumbs": Array [],
+          "key": "my-project",
+          "name": "MyProject",
+          "qualifier": "TRK",
+          "qualityGate": Object {
+            "isDefault": true,
+            "key": "30",
+            "name": "Sonar way",
+          },
+          "qualityProfiles": Array [
+            Object {
+              "deleted": false,
+              "key": "my-qp",
+              "language": "ts",
+              "name": "Sonar way",
+            },
+          ],
+          "tags": Array [],
+        }
+      }
     />
   </td>
   <td
@@ -130,7 +151,7 @@ exports[`should render correctly for main branch 1`] = `
   <td
     className="nowrap"
   >
-    <Connect(BranchStatus)
+    <withBranchStatus(BranchStatus)
       branchLike={
         Object {
           "analysisDate": "2018-01-01",
@@ -139,7 +160,28 @@ exports[`should render correctly for main branch 1`] = `
           "name": "master",
         }
       }
-      component="my-project"
+      component={
+        Object {
+          "breadcrumbs": Array [],
+          "key": "my-project",
+          "name": "MyProject",
+          "qualifier": "TRK",
+          "qualityGate": Object {
+            "isDefault": true,
+            "key": "30",
+            "name": "Sonar way",
+          },
+          "qualityProfiles": Array [
+            Object {
+              "deleted": false,
+              "key": "my-qp",
+              "language": "ts",
+              "name": "Sonar way",
+            },
+          ],
+          "tags": Array [],
+        }
+      }
     />
   </td>
   <td
@@ -229,7 +271,7 @@ exports[`should render correctly for pull request 1`] = `
   <td
     className="nowrap"
   >
-    <Connect(BranchStatus)
+    <withBranchStatus(BranchStatus)
       branchLike={
         Object {
           "analysisDate": "2018-01-01",
@@ -240,7 +282,28 @@ exports[`should render correctly for pull request 1`] = `
           "title": "Foo Bar feature",
         }
       }
-      component="my-project"
+      component={
+        Object {
+          "breadcrumbs": Array [],
+          "key": "my-project",
+          "name": "MyProject",
+          "qualifier": "TRK",
+          "qualityGate": Object {
+            "isDefault": true,
+            "key": "30",
+            "name": "Sonar way",
+          },
+          "qualityProfiles": Array [
+            Object {
+              "deleted": false,
+              "key": "my-qp",
+              "language": "ts",
+              "name": "Sonar way",
+            },
+          ],
+          "tags": Array [],
+        }
+      }
     />
   </td>
   <td
index 2eaf52d295012150cd69c7f83bcce773a7a2fd36..f9814f838c7eb4fdbae1928f14673ca96185a39a 100644 (file)
@@ -21,9 +21,9 @@ import { Location } from 'history';
 import key from 'keymaster';
 import { flatMap, range } from 'lodash';
 import * as React from 'react';
-import { connect } from 'react-redux';
 import { getMeasures } from '../../api/measures';
 import { getSecurityHotspotList, getSecurityHotspots } from '../../api/security-hotspots';
+import withBranchStatusActions from '../../app/components/branch-status/withBranchStatusActions';
 import withCurrentUserContext from '../../app/components/current-user/withCurrentUserContext';
 import { Router } from '../../components/hoc/withRouter';
 import { getLeakValue } from '../../components/measure/utils';
@@ -31,7 +31,6 @@ import { getBranchLikeQuery, isPullRequest, isSameBranchLike } from '../../helpe
 import { KeyboardCodes, KeyboardKeys } from '../../helpers/keycodes';
 import { scrollToElement } from '../../helpers/scrolling';
 import { getStandards } from '../../helpers/security-standard';
-import { fetchBranchStatus } from '../../store/branches';
 import { BranchLike } from '../../types/branch-like';
 import { SecurityStandard, Standards } from '../../types/security';
 import {
@@ -547,6 +546,4 @@ export class SecurityHotspotsApp extends React.PureComponent<Props, State> {
   }
 }
 
-const mapDispatchToProps = { fetchBranchStatus };
-
-export default withCurrentUserContext(connect(null, mapDispatchToProps)(SecurityHotspotsApp));
+export default withCurrentUserContext(withBranchStatusActions(SecurityHotspotsApp));
index 570c60afcca4fbfe4bb201a6c48695a02fee0878..04f153888bcd49e17dc0c45e2a62299bde28ed87 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as React from 'react';
-import { connect } from 'react-redux';
+import withBranchStatus from '../../app/components/branch-status/withBranchStatus';
 import Level from '../../components/ui/Level';
-import { getBranchStatusByBranchLike, Store } from '../../store/rootReducer';
-import { BranchLike } from '../../types/branch-like';
+import { BranchStatusData } from '../../types/branch-like';
 
-interface ExposedProps {
-  branchLike: BranchLike;
-  component: string;
-}
+export type BranchStatusProps = Pick<BranchStatusData, 'status'>;
 
-interface BranchStatusProps {
-  status?: string;
-}
+export function BranchStatus(props: BranchStatusProps) {
+  const { status } = props;
 
-export function BranchStatus({ status }: BranchStatusProps) {
   if (!status) {
     return null;
   }
@@ -40,10 +34,4 @@ export function BranchStatus({ status }: BranchStatusProps) {
   return <Level level={status} small={true} />;
 }
 
-const mapStateToProps = (state: Store, props: ExposedProps) => {
-  const { branchLike, component } = props;
-  const { status } = getBranchStatusByBranchLike(state, component, branchLike);
-  return { status };
-};
-
-export default connect(mapStateToProps)(BranchStatus);
+export default withBranchStatus(BranchStatus);
index 3c725df069edbf761b23f7ab6b4c27f7bb55ee51..551674c475bee449e17fd1474411d4b003a08976 100644 (file)
  */
 import { shallow } from 'enzyme';
 import * as React from 'react';
-import { BranchStatus } from '../BranchStatus';
+import { BranchStatus, BranchStatusProps } from '../BranchStatus';
 
 it('should render correctly', () => {
   expect(shallowRender().type()).toBeNull();
-  expect(shallowRender('OK')).toMatchSnapshot();
-  expect(shallowRender('ERROR')).toMatchSnapshot();
+  expect(
+    shallowRender({
+      status: 'OK'
+    })
+  ).toMatchSnapshot('Successful');
+  expect(
+    shallowRender({
+      status: 'ERROR'
+    })
+  ).toMatchSnapshot('Error');
 });
 
-function shallowRender(status?: string) {
-  return shallow(<BranchStatus status={status} />);
+function shallowRender(overrides: Partial<BranchStatusProps> = {}) {
+  return shallow(<BranchStatus {...overrides} />);
 }
index df907fd0bfb198c8ef9d861e10edfd57d4479d29..aeb2bda6418faffa0ca34c6b849482fe8ec19d5b 100644 (file)
@@ -1,15 +1,15 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`should render correctly 1`] = `
+exports[`should render correctly: Error 1`] = `
 <Level
-  level="OK"
+  level="ERROR"
   small={true}
 />
 `;
 
-exports[`should render correctly 2`] = `
+exports[`should render correctly: Successful 1`] = `
 <Level
-  level="ERROR"
+  level="OK"
   small={true}
 />
 `;
index 23cf4dfcd3c7e2f4b862b2b242aad8b68d4c4ff2..d7561d6e604f8e6fc4b662191959cc9e587625e5 100644 (file)
  */
 import { debounce } from 'lodash';
 import * as React from 'react';
-import { connect } from 'react-redux';
 import { getParents } from '../../api/components';
+import withBranchStatusActions from '../../app/components/branch-status/withBranchStatusActions';
 import { isPullRequest } from '../../helpers/branch-like';
 import { scrollToElement } from '../../helpers/scrolling';
-import { fetchBranchStatus } from '../../store/branches';
 import { BranchLike } from '../../types/branch-like';
 import { Issue, SourceViewerFile } from '../../types/types';
 import SourceViewer from '../SourceViewer/SourceViewer';
@@ -137,6 +136,4 @@ export class WorkspaceComponentViewer extends React.PureComponent<Props> {
   }
 }
 
-const mapDispatchToProps = { fetchBranchStatus: fetchBranchStatus as any };
-
-export default connect(null, mapDispatchToProps)(WorkspaceComponentViewer);
+export default withBranchStatusActions(WorkspaceComponentViewer);
index b332e9acd2ef8027629de571c867ef2502c387e8..ce6cc4ec13f8cdd36b7fcc6389da95eb6979928e 100644 (file)
@@ -23,9 +23,11 @@ import {
   BranchLike,
   BranchLikeTree,
   BranchParameters,
+  BranchStatusData,
   MainBranch,
   PullRequest
 } from '../types/branch-like';
+import { Dict } from '../types/types';
 
 export function isBranch(branchLike?: BranchLike): branchLike is Branch {
   return branchLike !== undefined && (branchLike as Branch).isMain !== undefined;
@@ -136,3 +138,12 @@ export function fillBranchLike(
   }
   return undefined;
 }
+
+export function getBranchStatusByBranchLike(
+  branchStatusByComponent: Dict<Dict<BranchStatusData>>,
+  component: string,
+  branchLike: BranchLike
+): BranchStatusData {
+  const branchLikeKey = getBranchLikeKey(branchLike);
+  return branchStatusByComponent[component] && branchStatusByComponent[component][branchLikeKey];
+}
index 3c4c452ddd7f79af43118956e4183c7774ad516f..7269073b7603d60a684f7b1f1f91af5a771c3538 100644 (file)
@@ -24,6 +24,7 @@ import { DocumentationEntry } from '../apps/documentation/utils';
 import { Exporter, Profile } from '../apps/quality-profiles/types';
 import { AppState } from '../types/appstate';
 import { EditionKey } from '../types/editions';
+import { RawIssue } from '../types/issues';
 import { Language } from '../types/languages';
 import { DumpStatus, DumpTask } from '../types/project-dump';
 import { TaskStatuses } from '../types/tasks';
@@ -367,6 +368,31 @@ export function mockEvent(overrides = {}) {
   } as any;
 }
 
+export function mockRawIssue(withLocations = false, overrides: Partial<RawIssue> = {}): RawIssue {
+  const rawIssue: RawIssue = {
+    component: 'main.js',
+    key: 'AVsae-CQS-9G3txfbFN2',
+    line: 25,
+    project: 'myproject',
+    rule: 'javascript:S1067',
+    severity: 'MAJOR',
+    status: 'OPEN',
+    textRange: { startLine: 25, endLine: 26, startOffset: 0, endOffset: 15 },
+    ...overrides
+  };
+
+  if (withLocations) {
+    const loc = mockFlowLocation;
+
+    rawIssue.flows = [{ locations: [loc(), loc()] }];
+  }
+
+  return {
+    ...rawIssue,
+    ...overrides
+  };
+}
+
 export function mockIssue(withLocations = false, overrides: Partial<Issue> = {}) {
   const issue: Issue = {
     actions: [],
diff --git a/server/sonar-web/src/main/js/store/__tests__/branches-test.ts b/server/sonar-web/src/main/js/store/__tests__/branches-test.ts
deleted file mode 100644 (file)
index eb52257..0000000
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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 { getBranchLikeKey } from '../../helpers/branch-like';
-import { mockBranch, mockPullRequest } from '../../helpers/mocks/branch-like';
-import { mockQualityGateStatusCondition } from '../../helpers/mocks/quality-gates';
-import { BranchLike } from '../../types/branch-like';
-import { QualityGateStatusCondition } from '../../types/quality-gates';
-import { Status } from '../../types/types';
-import reducer, {
-  fetchBranchStatus,
-  getBranchStatusByBranchLike,
-  registerBranchStatus,
-  registerBranchStatusAction,
-  State
-} from '../branches';
-
-type TestArgs = [BranchLike, string, Status, QualityGateStatusCondition[], boolean?];
-
-const FAILING_CONDITION = mockQualityGateStatusCondition();
-const COMPONENT = 'foo';
-const BRANCH_STATUS_1: TestArgs = [mockPullRequest(), COMPONENT, 'ERROR', [FAILING_CONDITION]];
-const BRANCH_STATUS_2: TestArgs = [mockBranch(), 'bar', 'OK', [], true];
-const BRANCH_STATUS_3: TestArgs = [mockBranch(), COMPONENT, 'OK', []];
-
-it('should allow to register new branche statuses', () => {
-  const initialState: State = convertToState();
-
-  const newState = reducer(initialState, registerBranchStatusAction(...BRANCH_STATUS_1));
-  expect(newState).toEqual(convertToState([BRANCH_STATUS_1]));
-
-  const newerState = reducer(newState, registerBranchStatusAction(...BRANCH_STATUS_2));
-  expect(newerState).toEqual(convertToState([BRANCH_STATUS_1, BRANCH_STATUS_2]));
-  expect(newState).toEqual(convertToState([BRANCH_STATUS_1]));
-});
-
-it('should allow to update branche statuses', () => {
-  const initialState: State = convertToState([BRANCH_STATUS_1, BRANCH_STATUS_2, BRANCH_STATUS_3]);
-  const branchLike: BranchLike = { ...BRANCH_STATUS_1[0], status: { qualityGateStatus: 'OK' } };
-  const branchStatus: TestArgs = [branchLike, COMPONENT, 'OK', []];
-
-  const newState = reducer(initialState, registerBranchStatusAction(...branchStatus));
-  expect(newState).toEqual(convertToState([branchStatus, BRANCH_STATUS_2, BRANCH_STATUS_3]));
-  expect(initialState).toEqual(convertToState([BRANCH_STATUS_1, BRANCH_STATUS_2, BRANCH_STATUS_3]));
-});
-
-it('should get the branche statuses from state', () => {
-  const initialState: State = convertToState([BRANCH_STATUS_1, BRANCH_STATUS_2]);
-
-  const [branchLike, component] = BRANCH_STATUS_1;
-  expect(getBranchStatusByBranchLike(initialState, component, branchLike)).toEqual({
-    conditions: [FAILING_CONDITION],
-    status: 'ERROR'
-  });
-  expect(getBranchStatusByBranchLike(initialState, component, BRANCH_STATUS_2[0])).toBeUndefined();
-});
-
-function convertToState(items: TestArgs[] = []) {
-  const state: State = { byComponent: {} };
-
-  items.forEach(item => {
-    const [branchLike, component, status, conditions, ignoredConditions] = item;
-    state.byComponent[component] = {
-      ...(state.byComponent[component] || {}),
-      [getBranchLikeKey(branchLike)]: { conditions, ignoredConditions, status }
-    };
-  });
-
-  return state;
-}
-
-jest.mock('../../app/utils/addGlobalErrorMessage', () => ({
-  __esModule: true,
-  default: jest.fn()
-}));
-
-jest.mock('../../api/quality-gates', () => {
-  const { mockQualityGateProjectStatus } = jest.requireActual('../../helpers/mocks/quality-gates');
-  return {
-    getQualityGateProjectStatus: jest.fn().mockResolvedValue(
-      mockQualityGateProjectStatus({
-        conditions: [
-          {
-            actualValue: '10',
-            comparator: 'GT',
-            errorThreshold: '0',
-            metricKey: 'foo',
-            periodIndex: 1,
-            status: 'ERROR'
-          }
-        ]
-      })
-    )
-  };
-});
-
-describe('branch store actions', () => {
-  const branchLike = mockBranch();
-  const component = 'foo';
-  const status = 'OK';
-
-  it('correctly registers a new branch status', () => {
-    const dispatch = jest.fn();
-
-    registerBranchStatus(branchLike, component, status)(dispatch);
-    expect(dispatch).toBeCalledWith({
-      branchLike,
-      component,
-      status,
-      type: 'REGISTER_BRANCH_STATUS'
-    });
-  });
-
-  it('correctly fetches a branch status', async () => {
-    const dispatch = jest.fn();
-
-    fetchBranchStatus(branchLike, component)(dispatch);
-    await new Promise(setImmediate);
-
-    expect(dispatch).toBeCalledWith({
-      branchLike,
-      component,
-      status,
-      conditions: [
-        mockQualityGateStatusCondition({
-          period: 1
-        })
-      ],
-      ignoredConditions: false,
-      type: 'REGISTER_BRANCH_STATUS'
-    });
-  });
-});
diff --git a/server/sonar-web/src/main/js/store/__tests__/globalMessages-test.ts b/server/sonar-web/src/main/js/store/__tests__/globalMessages-test.ts
new file mode 100644 (file)
index 0000000..a5ceed7
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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 globalMessagesReducer, { MessageLevel } from '../globalMessages';
+
+describe('globalMessagesReducer', () => {
+  it('should handle ADD_GLOBAL_MESSAGE', () => {
+    const actionAttributes = { id: 'id', message: 'There was an error', level: MessageLevel.Error };
+
+    expect(
+      globalMessagesReducer([], {
+        type: 'ADD_GLOBAL_MESSAGE',
+        ...actionAttributes
+      })
+    ).toEqual([actionAttributes]);
+  });
+
+  it('should handle CLOSE_GLOBAL_MESSAGE', () => {
+    const state = [
+      { id: 'm1', message: 'message 1', level: MessageLevel.Success },
+      { id: 'm2', message: 'message 2', level: MessageLevel.Success }
+    ];
+
+    expect(globalMessagesReducer(state, { type: 'CLOSE_GLOBAL_MESSAGE', id: 'm2' })).toEqual([
+      state[0]
+    ]);
+  });
+});
diff --git a/server/sonar-web/src/main/js/store/__tests__/rootReducers-test.tsx b/server/sonar-web/src/main/js/store/__tests__/rootReducers-test.tsx
deleted file mode 100644 (file)
index c6cffde..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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 { mockPullRequest } from '../../helpers/mocks/branch-like';
-import * as fromBranches from '../branches';
-import { getBranchStatusByBranchLike, Store } from '../rootReducer';
-
-it('correctly reduce state for branches', () => {
-  const spiedOn = jest.spyOn(fromBranches, 'getBranchStatusByBranchLike').mockReturnValueOnce({});
-
-  const branches = { byComponent: {} };
-  const component = 'foo';
-  const branchLike = mockPullRequest();
-  getBranchStatusByBranchLike({ branches } as Store, component, branchLike);
-  expect(spiedOn).toBeCalledWith(branches, component, branchLike);
-});
diff --git a/server/sonar-web/src/main/js/store/branches.ts b/server/sonar-web/src/main/js/store/branches.ts
deleted file mode 100644 (file)
index e6e4dad..0000000
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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 { Dispatch } from 'redux';
-import { getQualityGateProjectStatus } from '../api/quality-gates';
-import addGlobalErrorMessage from '../app/utils/addGlobalErrorMessage';
-import { getBranchLikeKey, getBranchLikeQuery } from '../helpers/branch-like';
-import { extractStatusConditionsFromProjectStatus } from '../helpers/qualityGates';
-import { ActionType } from '../types/actions';
-import { BranchLike } from '../types/branch-like';
-import { QualityGateStatusCondition } from '../types/quality-gates';
-import { Dict, Status } from '../types/types';
-
-export interface BranchStatusData {
-  conditions?: QualityGateStatusCondition[];
-  ignoredConditions?: boolean;
-  status?: Status;
-}
-
-export interface State {
-  byComponent: Dict<Dict<BranchStatusData>>;
-}
-
-const enum Actions {
-  RegisterBranchStatus = 'REGISTER_BRANCH_STATUS'
-}
-
-type Action = ActionType<typeof registerBranchStatusAction, Actions.RegisterBranchStatus>;
-
-export function registerBranchStatusAction(
-  branchLike: BranchLike,
-  component: string,
-  status: Status,
-  conditions?: QualityGateStatusCondition[],
-  ignoredConditions?: boolean
-) {
-  return {
-    type: Actions.RegisterBranchStatus,
-    branchLike,
-    component,
-    conditions,
-    ignoredConditions,
-    status
-  };
-}
-
-export default function branchesReducer(state: State = { byComponent: {} }, action: Action): State {
-  if (action.type === Actions.RegisterBranchStatus) {
-    const { component, conditions, branchLike, ignoredConditions, status } = action;
-    const branchLikeKey = getBranchLikeKey(branchLike);
-    return {
-      byComponent: {
-        ...state.byComponent,
-        [component]: {
-          ...(state.byComponent[component] || {}),
-          [branchLikeKey]: {
-            conditions,
-            ignoredConditions,
-            status
-          }
-        }
-      }
-    };
-  }
-
-  return state;
-}
-
-export function getBranchStatusByBranchLike(
-  state: State,
-  component: string,
-  branchLike: BranchLike
-): BranchStatusData {
-  const branchLikeKey = getBranchLikeKey(branchLike);
-  return state.byComponent[component] && state.byComponent[component][branchLikeKey];
-}
-
-export function fetchBranchStatus(branchLike: BranchLike, projectKey: string) {
-  return (dispatch: Dispatch) => {
-    getQualityGateProjectStatus({ projectKey, ...getBranchLikeQuery(branchLike) }).then(
-      projectStatus => {
-        const { ignoredConditions, status } = projectStatus;
-        const conditions = extractStatusConditionsFromProjectStatus(projectStatus);
-        dispatch(
-          registerBranchStatusAction(branchLike, projectKey, status, conditions, ignoredConditions)
-        );
-      },
-      () => {
-        addGlobalErrorMessage('Fetching Quality Gate status failed');
-      }
-    );
-  };
-}
-
-export function registerBranchStatus(branchLike: BranchLike, component: string, status: Status) {
-  return (dispatch: Dispatch) => {
-    dispatch(registerBranchStatusAction(branchLike, component, status));
-  };
-}
index c64ac9dafdb7af8f680e8a1654794d3bb89c2623..58776e7d253445d8c322eea50054e58983033b2f 100644 (file)
@@ -21,7 +21,7 @@ import { uniqueId } from 'lodash';
 import { Dispatch } from 'redux';
 import { ActionType } from '../types/actions';
 
-enum MessageLevel {
+export enum MessageLevel {
   Error = 'ERROR',
   Success = 'SUCCESS'
 }
index 885d7b25cdc1c4bb4d29e14b00422b42841adf4d..2a68fa75667891ecc69952bec4f4cb48c0ea3897 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import { combineReducers } from 'redux';
-import { BranchLike } from '../types/branch-like';
-import branches, * as fromBranches from './branches';
 import globalMessages, * as fromGlobalMessages from './globalMessages';
 
 export type Store = {
-  branches: fromBranches.State;
   globalMessages: fromGlobalMessages.State;
 };
 
 export default combineReducers<Store>({
-  branches,
   globalMessages
 });
 
 export function getGlobalMessages(state: Store) {
   return fromGlobalMessages.getGlobalMessages(state.globalMessages);
 }
-
-export function getBranchStatusByBranchLike(
-  state: Store,
-  component: string,
-  branchLike: BranchLike
-) {
-  return fromBranches.getBranchStatusByBranchLike(state.branches, component, branchLike);
-}
index 7a479af7ae2c12b1b13ff56471f4b6baa6629df8..1495e07d4cd03daa47b7ee733955c70162d3ec42 100644 (file)
@@ -17,6 +17,7 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+import { QualityGateStatusCondition } from './quality-gates';
 import { NewCodePeriod, Status } from './types';
 
 export interface Branch {
@@ -62,3 +63,9 @@ export type BranchParameters = { branch?: string } | { pullRequest?: string };
 export interface BranchWithNewCodePeriod extends Branch {
   newCodePeriod?: NewCodePeriod;
 }
+
+export interface BranchStatusData {
+  conditions?: QualityGateStatusCondition[];
+  ignoredConditions?: boolean;
+  status?: Status;
+}