]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-15914 Clean up redux
authorJeremy Davis <jeremy.davis@sonarsource.com>
Tue, 15 Mar 2022 16:45:43 +0000 (17:45 +0100)
committersonartech <sonartech@sonarsource.com>
Wed, 23 Mar 2022 20:02:45 +0000 (20:02 +0000)
23 files changed:
server/sonar-web/src/main/js/api/auth.ts
server/sonar-web/src/main/js/app/components/ComponentContainer.tsx
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/__tests__/AppContainer-test.tsx
server/sonar-web/src/main/js/apps/overview/pullRequests/PullRequestOverview.tsx
server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsApp.tsx
server/sonar-web/src/main/js/apps/sessions/components/LoginContainer.tsx
server/sonar-web/src/main/js/apps/sessions/components/Logout.tsx
server/sonar-web/src/main/js/apps/sessions/components/__tests__/LoginContainer-test.tsx
server/sonar-web/src/main/js/apps/sessions/components/__tests__/Logout-test.tsx
server/sonar-web/src/main/js/components/workspace/WorkspaceComponentViewer.tsx
server/sonar-web/src/main/js/helpers/__tests__/branch-like-test.ts [new file with mode: 0644]
server/sonar-web/src/main/js/helpers/__tests__/branches-test.ts [deleted file]
server/sonar-web/src/main/js/store/__tests__/branches-test.ts
server/sonar-web/src/main/js/store/__tests__/rootActions-test.tsx [deleted file]
server/sonar-web/src/main/js/store/__tests__/rootReducers-test.tsx
server/sonar-web/src/main/js/store/branches.ts
server/sonar-web/src/main/js/store/globalMessages.ts
server/sonar-web/src/main/js/store/rootActions.ts [deleted file]
server/sonar-web/src/main/js/store/utils/actions.ts [deleted file]
server/sonar-web/src/main/js/types/actions.ts [new file with mode: 0644]

index d556401ff8d2aa3d74b6c7fd673b8962968a5c75..958dc9f41753f0603d55e67a198cc44158d688a1 100644 (file)
@@ -19,7 +19,7 @@
  */
 import { request } from '../helpers/request';
 
-export function login(login: string, password: string): Promise<Response> {
+export function logIn(login: string, password: string): Promise<Response> {
   return request('/api/authentication/login')
     .setMethod('POST')
     .setData({ login, password })
@@ -27,7 +27,7 @@ export function login(login: string, password: string): Promise<Response> {
     .then(basicCheckStatus);
 }
 
-export function logout(): Promise<Response> {
+export function logOut(): Promise<Response> {
   return request('/api/authentication/logout')
     .setMethod('POST')
     .submit()
index e663080820ae9d198f2ff83f83276956ddd884c6..fc98bc60e170cb1b1f98ccb34df4cd0af7c0fa7c 100644 (file)
@@ -34,7 +34,7 @@ import {
 } from '../../helpers/branch-like';
 import { HttpStatus } from '../../helpers/request';
 import { getPortfolioUrl } from '../../helpers/urls';
-import { registerBranchStatus } from '../../store/rootActions';
+import { registerBranchStatus } from '../../store/branches';
 import {
   ProjectAlmBindingConfigurationErrors,
   ProjectAlmBindingResponse
index 34f37f04af9bc8bf3951e628d31f688e80ad86ca..57bb881e8429c29fe7b43c509bf7a3fc6708c4d7 100644 (file)
@@ -35,7 +35,7 @@ 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/rootActions';
+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';
index 4078382e7f97729b62315168ae3129cc67fec94e..ce61093edf2e7fed9d694bb947e07cbe9be35dd4 100644 (file)
@@ -44,7 +44,7 @@ import {
   removeSideBarClass,
   removeWhitePageClass
 } from '../../../helpers/pages';
-import { fetchBranchStatus } from '../../../store/rootActions';
+import { fetchBranchStatus } from '../../../store/branches';
 import { BranchLike } from '../../../types/branch-like';
 import { ComponentQualifier, isPortfolioLike } from '../../../types/component';
 import {
index 1e3578afcb163d0c4203be72319a550a5b7e70e4..49e326575cb02f1796cdb20c99e3ce9ac8f58c7f 100644 (file)
@@ -23,7 +23,7 @@ import withCurrentUserContext from '../../../app/components/current-user/withCur
 import { withRouter } from '../../../components/hoc/withRouter';
 import { lazyLoadComponent } from '../../../components/lazyLoadComponent';
 import { parseIssueFromResponse } from '../../../helpers/issues';
-import { fetchBranchStatus } from '../../../store/rootActions';
+import { fetchBranchStatus } from '../../../store/branches';
 import { Store } from '../../../store/rootReducer';
 import { FetchIssuesPromise } from '../../../types/issues';
 import { RawQuery } from '../../../types/types';
index b1543639017a8f9b779be69dbfc21fe9f31b8e48..bc78aeab32e7bde4640d002a954a15abcf727c94 100644 (file)
@@ -19,7 +19,7 @@
  */
 import { connect } from 'react-redux';
 import { searchIssues } from '../../../../api/issues';
-import { fetchBranchStatus } from '../../../../store/rootActions';
+import { fetchBranchStatus } from '../../../../store/branches';
 import '../AppContainer';
 
 jest.mock('react-redux', () => ({
index a2866c937e1470556ecdcee8230747b1c0d85600..bfb4c232ec33282f80476ced38ce26ff5e14ea65 100644 (file)
@@ -28,7 +28,7 @@ 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/rootActions';
+import { fetchBranchStatus } from '../../../store/branches';
 import { getBranchStatusByBranchLike, Store } from '../../../store/rootReducer';
 import { BranchLike, PullRequest } from '../../../types/branch-like';
 import { IssueType } from '../../../types/issues';
index e774afc2b45b9d3b493166e115c2ad3d50f59b12..2eaf52d295012150cd69c7f83bcce773a7a2fd36 100644 (file)
@@ -31,7 +31,7 @@ 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/rootActions';
+import { fetchBranchStatus } from '../../store/branches';
 import { BranchLike } from '../../types/branch-like';
 import { SecurityStandard, Standards } from '../../types/security';
 import {
index 197caadbd43420bbcbc95b64aa0ed70a83d72532..294c57e47e8916b1eddd5d199142abcca65b8348 100644 (file)
  */
 import { Location } from 'history';
 import * as React from 'react';
-import { connect } from 'react-redux';
+import { logIn } from '../../../api/auth';
 import { getIdentityProviders } from '../../../api/users';
+import addGlobalErrorMessage from '../../../app/utils/addGlobalErrorMessage';
 import { getReturnUrl } from '../../../helpers/urls';
-import { doLogin } from '../../../store/rootActions';
 import { IdentityProvider } from '../../../types/types';
 import Login from './Login';
 
-interface OwnProps {
+interface Props {
   location: Pick<Location, 'hash' | 'pathname' | 'query'> & {
     query: { advanced?: string; return_to?: string };
   };
 }
-
-interface DispatchToProps {
-  doLogin: (login: string, password: string) => Promise<void>;
-}
-
-type Props = OwnProps & DispatchToProps;
-
 interface State {
   identityProviders?: IdentityProvider[];
 }
@@ -55,7 +48,9 @@ export class LoginContainer extends React.PureComponent<Props, State> {
           this.setState({ identityProviders });
         }
       },
-      () => {}
+      () => {
+        /* already handled */
+      }
     );
   }
 
@@ -67,8 +62,13 @@ export class LoginContainer extends React.PureComponent<Props, State> {
     window.location.href = getReturnUrl(this.props.location);
   };
 
-  handleSubmit = (login: string, password: string) => {
-    return this.props.doLogin(login, password).then(this.handleSuccessfulLogin, () => {});
+  handleSubmit = (id: string, password: string) => {
+    return logIn(id, password)
+      .then(this.handleSuccessfulLogin)
+      .catch(() => {
+        addGlobalErrorMessage('Authentication failed');
+        return Promise.reject();
+      });
   };
 
   render() {
@@ -89,7 +89,4 @@ export class LoginContainer extends React.PureComponent<Props, State> {
   }
 }
 
-const mapStateToProps = null;
-const mapDispatchToProps = { doLogin: doLogin as any };
-
-export default connect(mapStateToProps, mapDispatchToProps)(LoginContainer);
+export default LoginContainer;
index 3d5d250ec47f0d659698999aa53f59393c02cb96..4e4cae3b24540061736cabc2ef68a1991f582688 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as React from 'react';
-import { connect } from 'react-redux';
+import { logOut } from '../../../api/auth';
 import GlobalMessagesContainer from '../../../app/components/GlobalMessagesContainer';
 import RecentHistory from '../../../app/components/RecentHistory';
+import addGlobalErrorMessage from '../../../app/utils/addGlobalErrorMessage';
 import { translate } from '../../../helpers/l10n';
 import { getBaseUrl } from '../../../helpers/system';
-import { doLogout } from '../../../store/rootActions';
 
-interface Props {
-  doLogout: () => Promise<void>;
-}
-
-export class Logout extends React.PureComponent<Props> {
+export class Logout extends React.PureComponent<{}> {
   componentDidMount() {
-    this.props.doLogout().then(
-      () => {
+    logOut()
+      .then(() => {
         RecentHistory.clear();
         window.location.replace(getBaseUrl() + '/');
-      },
-      () => {}
-    );
+      })
+      .catch(() => {
+        addGlobalErrorMessage('Logout failed');
+      });
   }
 
   render() {
@@ -50,8 +47,4 @@ export class Logout extends React.PureComponent<Props> {
   }
 }
 
-const mapStateToProps = () => ({});
-
-const mapDispatchToProps = { doLogout };
-
-export default connect(mapStateToProps, mapDispatchToProps)(Logout as any);
+export default Logout;
index f09d24aba35eb6e4d299a659db81a6d1eaf645c1..510f7c4074fd18d3fe476f8060147019f17c6bc0 100644 (file)
@@ -19,6 +19,7 @@
  */
 import { shallow } from 'enzyme';
 import * as React from 'react';
+import { logIn } from '../../../../api/auth';
 import { getIdentityProviders } from '../../../../api/users';
 import { mockLocation } from '../../../../helpers/testMocks';
 import { waitAndUpdate } from '../../../../helpers/testUtils';
@@ -33,6 +34,10 @@ jest.mock('../../../../api/users', () => {
   };
 });
 
+jest.mock('../../../../api/auth', () => ({
+  logIn: jest.fn().mockResolvedValue({})
+}));
+
 beforeEach(jest.clearAllMocks);
 
 it('should render correctly', async () => {
@@ -53,14 +58,12 @@ it('should not provide any options if no IdPs are present', async () => {
 });
 
 it('should handle submission', () => {
-  const doLogin = jest.fn().mockResolvedValue(null);
-  const wrapper = shallowRender({ doLogin });
+  (logIn as jest.Mock).mockResolvedValue(null);
+  const wrapper = shallowRender();
   wrapper.instance().handleSubmit('user', 'pass');
-  expect(doLogin).toBeCalledWith('user', 'pass');
+  expect(logIn).toBeCalledWith('user', 'pass');
 });
 
 function shallowRender(props: Partial<LoginContainer['props']> = {}) {
-  return shallow<LoginContainer>(
-    <LoginContainer doLogin={jest.fn()} location={mockLocation()} {...props} />
-  );
+  return shallow<LoginContainer>(<LoginContainer location={mockLocation()} {...props} />);
 }
index dcb44bf2ed901b37da402d6ffa47646347482d99..5b0d001291375a2439f04d5f35a5df6fdbfb1cc4 100644 (file)
  */
 import { shallow } from 'enzyme';
 import * as React from 'react';
+import { logOut } from '../../../../api/auth';
+import addGlobalErrorMessage from '../../../../app/utils/addGlobalErrorMessage';
 import { waitAndUpdate } from '../../../../helpers/testUtils';
 import { Logout } from '../Logout';
 
-const originalLocation = window.location;
+jest.mock('../../../../api/auth', () => ({
+  logOut: jest.fn().mockResolvedValue(true)
+}));
+
+jest.mock('../../../../app/utils/addGlobalErrorMessage', () => ({
+  __esModule: true,
+  default: jest.fn()
+}));
 
+const originalLocation = window.location;
 beforeAll(() => {
   const location = {
     ...window.location,
@@ -47,26 +57,28 @@ afterAll(() => {
 });
 
 it('should logout correctly', async () => {
-  const doLogout = jest.fn().mockResolvedValue(true);
+  (logOut as jest.Mock).mockResolvedValue(true);
 
-  const wrapper = shallowRender({ doLogout });
+  const wrapper = shallowRender();
   await waitAndUpdate(wrapper);
 
-  expect(doLogout).toHaveBeenCalled();
+  expect(logOut).toHaveBeenCalled();
   expect(window.location.replace).toHaveBeenCalledWith('/');
+  expect(addGlobalErrorMessage).not.toHaveBeenCalled();
 });
 
 it('should not redirect if logout fails', async () => {
-  const doLogout = jest.fn().mockRejectedValue(false);
+  (logOut as jest.Mock).mockRejectedValue(false);
 
-  const wrapper = shallowRender({ doLogout });
+  const wrapper = shallowRender();
   await waitAndUpdate(wrapper);
 
-  expect(doLogout).toHaveBeenCalled();
+  expect(logOut).toHaveBeenCalled();
   expect(window.location.replace).not.toHaveBeenCalled();
+  expect(addGlobalErrorMessage).toHaveBeenCalled();
   expect(wrapper).toMatchSnapshot();
 });
 
-function shallowRender(props: Partial<Logout['props']> = {}) {
-  return shallow(<Logout doLogout={jest.fn()} {...props} />);
+function shallowRender() {
+  return shallow(<Logout />);
 }
index ae5700e8cc98837ff29a1cc209a067e8f12c14ef..23cf4dfcd3c7e2f4b862b2b242aad8b68d4c4ff2 100644 (file)
@@ -23,7 +23,7 @@ import { connect } from 'react-redux';
 import { getParents } from '../../api/components';
 import { isPullRequest } from '../../helpers/branch-like';
 import { scrollToElement } from '../../helpers/scrolling';
-import { fetchBranchStatus } from '../../store/rootActions';
+import { fetchBranchStatus } from '../../store/branches';
 import { BranchLike } from '../../types/branch-like';
 import { Issue, SourceViewerFile } from '../../types/types';
 import SourceViewer from '../SourceViewer/SourceViewer';
diff --git a/server/sonar-web/src/main/js/helpers/__tests__/branch-like-test.ts b/server/sonar-web/src/main/js/helpers/__tests__/branch-like-test.ts
new file mode 100644 (file)
index 0000000..639a3ea
--- /dev/null
@@ -0,0 +1,123 @@
+/*
+ * 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 { getBrancheLikesAsTree, isSameBranchLike, sortBranches } from '../branch-like';
+import { mockBranch, mockMainBranch, mockPullRequest } from '../mocks/branch-like';
+
+describe('#getBrancheLikesAsTree', () => {
+  it('should correctly map branches and prs to tree object', () => {
+    const main = mockMainBranch({ name: 'master' });
+    const branch1 = mockBranch({ name: 'branch-1' });
+    const branch2 = mockBranch({ name: 'branch-2' });
+    const branch3 = mockBranch({ name: 'branch-3' });
+    const branch4 = mockBranch({ name: 'branch-4' });
+
+    const mainPr1 = mockPullRequest({ base: main.name, key: 'PR1' });
+    const mainPr2 = mockPullRequest({ base: main.name, key: 'PR2' });
+    const llb1Pr1 = mockPullRequest({ base: branch1.name, key: 'PR1' });
+    const llb1Pr2 = mockPullRequest({ base: branch1.name, key: 'PR2' });
+    const llb2Pr1 = mockPullRequest({ base: branch2.name, key: 'PR1' });
+    const llb2Pr2 = mockPullRequest({ base: branch2.name, key: 'PR1' });
+    const orphanPR1 = mockPullRequest({ isOrphan: true, key: 'PR1' });
+    const orphanPR2 = mockPullRequest({ isOrphan: true, key: 'PR2' });
+    const parentlessPR1 = mockPullRequest({ base: 'not_present_branch_1', key: 'PR1' });
+    const parentlessPR2 = mockPullRequest({ base: 'not_present_branch_2', key: 'PR2' });
+
+    expect(
+      getBrancheLikesAsTree([
+        branch2,
+        branch1,
+        main,
+        orphanPR2,
+        orphanPR1,
+        branch4,
+        branch3,
+        mainPr2,
+        mainPr1,
+        parentlessPR2,
+        parentlessPR1,
+        llb2Pr2,
+        llb2Pr1,
+        llb1Pr2,
+        llb1Pr1
+      ])
+    ).toEqual({
+      mainBranchTree: {
+        branch: main,
+        pullRequests: [mainPr1, mainPr2]
+      },
+      branchTree: [
+        { branch: branch1, pullRequests: [llb1Pr1, llb1Pr2] },
+        { branch: branch2, pullRequests: [llb2Pr1, llb2Pr1] },
+        { branch: branch3, pullRequests: [] },
+        { branch: branch4, pullRequests: [] }
+      ],
+      parentlessPullRequests: [parentlessPR1, parentlessPR2],
+      orphanPullRequests: [orphanPR1, orphanPR2]
+    });
+  });
+});
+
+describe('#sortBranches', () => {
+  it('should sort branches correctly', () => {
+    const main = mockMainBranch();
+    const foo = mockBranch({ name: 'shortFoo' });
+    const bar = mockBranch({ name: 'shortBar' });
+    const pre = mockBranch({ name: 'shortPre' });
+    const baz = mockBranch({ name: 'longBaz' });
+    const qux = mockBranch({ name: 'longQux' });
+    const qwe = mockBranch({ name: 'longQwe' });
+    const branchList = [foo, baz, pre, qux, main, qwe, bar];
+
+    const sortedBrancList = sortBranches(branchList);
+
+    expect(sortedBrancList).toEqual([main, baz, qux, qwe, bar, foo, pre]);
+  });
+});
+
+describe('#isSameBranchLike', () => {
+  it('compares different kinds', () => {
+    const main = mockMainBranch();
+    const foo = mockBranch({ name: 'foo' });
+    const foo1 = mockBranch({ name: 'foo-1' });
+    const pr = mockPullRequest();
+    expect(isSameBranchLike(main, pr)).toBe(false);
+    expect(isSameBranchLike(main, foo1)).toBe(false);
+    expect(isSameBranchLike(main, foo)).toBe(false);
+    expect(isSameBranchLike(pr, foo1)).toBe(false);
+    expect(isSameBranchLike(pr, foo)).toBe(false);
+    expect(isSameBranchLike(foo1, foo)).toBe(false);
+  });
+
+  it('compares pull requests', () => {
+    expect(
+      isSameBranchLike(mockPullRequest({ key: '1234' }), mockPullRequest({ key: '1234' }))
+    ).toBe(true);
+    expect(
+      isSameBranchLike(mockPullRequest({ key: '1234' }), mockPullRequest({ key: '5678' }))
+    ).toBe(false);
+  });
+
+  it('compares branches', () => {
+    expect(isSameBranchLike(mockBranch({ name: 'foo' }), mockBranch({ name: 'foo' }))).toBe(true);
+    expect(isSameBranchLike(mockBranch({ name: 'foo' }), mockBranch({ name: 'foo' }))).toBe(true);
+    expect(isSameBranchLike(mockBranch({ name: 'foo' }), mockBranch({ name: 'bar' }))).toBe(false);
+    expect(isSameBranchLike(mockBranch({ name: 'foo' }), mockBranch({ name: 'bar' }))).toBe(false);
+  });
+});
diff --git a/server/sonar-web/src/main/js/helpers/__tests__/branches-test.ts b/server/sonar-web/src/main/js/helpers/__tests__/branches-test.ts
deleted file mode 100644 (file)
index 639a3ea..0000000
+++ /dev/null
@@ -1,123 +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 { getBrancheLikesAsTree, isSameBranchLike, sortBranches } from '../branch-like';
-import { mockBranch, mockMainBranch, mockPullRequest } from '../mocks/branch-like';
-
-describe('#getBrancheLikesAsTree', () => {
-  it('should correctly map branches and prs to tree object', () => {
-    const main = mockMainBranch({ name: 'master' });
-    const branch1 = mockBranch({ name: 'branch-1' });
-    const branch2 = mockBranch({ name: 'branch-2' });
-    const branch3 = mockBranch({ name: 'branch-3' });
-    const branch4 = mockBranch({ name: 'branch-4' });
-
-    const mainPr1 = mockPullRequest({ base: main.name, key: 'PR1' });
-    const mainPr2 = mockPullRequest({ base: main.name, key: 'PR2' });
-    const llb1Pr1 = mockPullRequest({ base: branch1.name, key: 'PR1' });
-    const llb1Pr2 = mockPullRequest({ base: branch1.name, key: 'PR2' });
-    const llb2Pr1 = mockPullRequest({ base: branch2.name, key: 'PR1' });
-    const llb2Pr2 = mockPullRequest({ base: branch2.name, key: 'PR1' });
-    const orphanPR1 = mockPullRequest({ isOrphan: true, key: 'PR1' });
-    const orphanPR2 = mockPullRequest({ isOrphan: true, key: 'PR2' });
-    const parentlessPR1 = mockPullRequest({ base: 'not_present_branch_1', key: 'PR1' });
-    const parentlessPR2 = mockPullRequest({ base: 'not_present_branch_2', key: 'PR2' });
-
-    expect(
-      getBrancheLikesAsTree([
-        branch2,
-        branch1,
-        main,
-        orphanPR2,
-        orphanPR1,
-        branch4,
-        branch3,
-        mainPr2,
-        mainPr1,
-        parentlessPR2,
-        parentlessPR1,
-        llb2Pr2,
-        llb2Pr1,
-        llb1Pr2,
-        llb1Pr1
-      ])
-    ).toEqual({
-      mainBranchTree: {
-        branch: main,
-        pullRequests: [mainPr1, mainPr2]
-      },
-      branchTree: [
-        { branch: branch1, pullRequests: [llb1Pr1, llb1Pr2] },
-        { branch: branch2, pullRequests: [llb2Pr1, llb2Pr1] },
-        { branch: branch3, pullRequests: [] },
-        { branch: branch4, pullRequests: [] }
-      ],
-      parentlessPullRequests: [parentlessPR1, parentlessPR2],
-      orphanPullRequests: [orphanPR1, orphanPR2]
-    });
-  });
-});
-
-describe('#sortBranches', () => {
-  it('should sort branches correctly', () => {
-    const main = mockMainBranch();
-    const foo = mockBranch({ name: 'shortFoo' });
-    const bar = mockBranch({ name: 'shortBar' });
-    const pre = mockBranch({ name: 'shortPre' });
-    const baz = mockBranch({ name: 'longBaz' });
-    const qux = mockBranch({ name: 'longQux' });
-    const qwe = mockBranch({ name: 'longQwe' });
-    const branchList = [foo, baz, pre, qux, main, qwe, bar];
-
-    const sortedBrancList = sortBranches(branchList);
-
-    expect(sortedBrancList).toEqual([main, baz, qux, qwe, bar, foo, pre]);
-  });
-});
-
-describe('#isSameBranchLike', () => {
-  it('compares different kinds', () => {
-    const main = mockMainBranch();
-    const foo = mockBranch({ name: 'foo' });
-    const foo1 = mockBranch({ name: 'foo-1' });
-    const pr = mockPullRequest();
-    expect(isSameBranchLike(main, pr)).toBe(false);
-    expect(isSameBranchLike(main, foo1)).toBe(false);
-    expect(isSameBranchLike(main, foo)).toBe(false);
-    expect(isSameBranchLike(pr, foo1)).toBe(false);
-    expect(isSameBranchLike(pr, foo)).toBe(false);
-    expect(isSameBranchLike(foo1, foo)).toBe(false);
-  });
-
-  it('compares pull requests', () => {
-    expect(
-      isSameBranchLike(mockPullRequest({ key: '1234' }), mockPullRequest({ key: '1234' }))
-    ).toBe(true);
-    expect(
-      isSameBranchLike(mockPullRequest({ key: '1234' }), mockPullRequest({ key: '5678' }))
-    ).toBe(false);
-  });
-
-  it('compares branches', () => {
-    expect(isSameBranchLike(mockBranch({ name: 'foo' }), mockBranch({ name: 'foo' }))).toBe(true);
-    expect(isSameBranchLike(mockBranch({ name: 'foo' }), mockBranch({ name: 'foo' }))).toBe(true);
-    expect(isSameBranchLike(mockBranch({ name: 'foo' }), mockBranch({ name: 'bar' }))).toBe(false);
-    expect(isSameBranchLike(mockBranch({ name: 'foo' }), mockBranch({ name: 'bar' }))).toBe(false);
-  });
-});
index 9ef4f9d1ae62eb7cf9f91c25ec22a303667738f2..eb52257e42a684b527ef29fb422dc2e6bff9eb1a 100644 (file)
@@ -24,7 +24,9 @@ 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';
@@ -82,3 +84,66 @@ function convertToState(items: TestArgs[] = []) {
 
   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__/rootActions-test.tsx b/server/sonar-web/src/main/js/store/__tests__/rootActions-test.tsx
deleted file mode 100644 (file)
index f53d45a..0000000
+++ /dev/null
@@ -1,82 +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 { mockBranch } from '../../helpers/mocks/branch-like';
-import { mockQualityGateStatusCondition } from '../../helpers/mocks/quality-gates';
-import { registerBranchStatusAction } from '../branches';
-import { fetchBranchStatus, registerBranchStatus } from '../rootActions';
-
-jest.mock('../branches', () => ({
-  ...jest.requireActual('../branches'),
-  registerBranchStatusAction: 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(registerBranchStatusAction).toBeCalledWith(branchLike, component, status);
-    expect(dispatch).toBeCalled();
-  });
-
-  it('correctly fetches a branch status', async () => {
-    const dispatch = jest.fn();
-
-    fetchBranchStatus(branchLike, component)(dispatch);
-    await new Promise(setImmediate);
-
-    expect(registerBranchStatusAction).toBeCalledWith(
-      branchLike,
-      component,
-      status,
-      [
-        mockQualityGateStatusCondition({
-          period: 1
-        })
-      ],
-      false
-    );
-    expect(dispatch).toBeCalled();
-  });
-});
index 0f97725f12a44a7371559bab259f1019c8707b07..c6cffde06bdb1bf0ba2aae7c5cb347b5a036d52b 100644 (file)
@@ -21,17 +21,12 @@ import { mockPullRequest } from '../../helpers/mocks/branch-like';
 import * as fromBranches from '../branches';
 import { getBranchStatusByBranchLike, Store } from '../rootReducer';
 
-jest.mock('../branches', () => {
-  return {
-    ...jest.requireActual('../branches'),
-    getBranchStatusByBranchLike: jest.fn()
-  };
-});
-
 it('correctly reduce state for branches', () => {
-  const branches = {};
+  const spiedOn = jest.spyOn(fromBranches, 'getBranchStatusByBranchLike').mockReturnValueOnce({});
+
+  const branches = { byComponent: {} };
   const component = 'foo';
   const branchLike = mockPullRequest();
   getBranchStatusByBranchLike({ branches } as Store, component, branchLike);
-  expect(fromBranches.getBranchStatusByBranchLike).toBeCalledWith(branches, component, branchLike);
+  expect(spiedOn).toBeCalledWith(branches, component, branchLike);
 });
index c24b94bceb72e19ffc39c8d4e52c736f4a3ca9a1..e6e4dada3334ed2c1e866edb5e40e34e805f9d6b 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 { getBranchLikeKey } from '../helpers/branch-like';
+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';
-import { ActionType } from './utils/actions';
 
 export interface BranchStatusData {
   conditions?: QualityGateStatusCondition[];
@@ -56,7 +60,7 @@ export function registerBranchStatusAction(
   };
 }
 
-export default function(state: State = { byComponent: {} }, action: Action): State {
+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);
@@ -86,3 +90,26 @@ export function getBranchStatusByBranchLike(
   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 a220d15cb47aef4d303af0e16fed8b29f2b7cd2b..c64ac9dafdb7af8f680e8a1654794d3bb89c2623 100644 (file)
@@ -19,7 +19,7 @@
  */
 import { uniqueId } from 'lodash';
 import { Dispatch } from 'redux';
-import { ActionType } from './utils/actions';
+import { ActionType } from '../types/actions';
 
 enum MessageLevel {
   Error = 'ERROR',
@@ -32,6 +32,10 @@ interface Message {
   level: MessageLevel;
 }
 
+const MESSAGE_DISPLAY_TIME = 5000;
+
+/* Action creators */
+
 function addGlobalMessageActionCreator(id: string, message: string, level: MessageLevel) {
   return { type: 'ADD_GLOBAL_MESSAGE', message, level, id };
 }
@@ -40,20 +44,15 @@ export function closeGlobalMessage(id: string) {
   return { type: 'CLOSE_GLOBAL_MESSAGE', id };
 }
 
-export function closeAllGlobalMessages() {
-  return { type: 'CLOSE_ALL_GLOBAL_MESSAGES' };
-}
-
 type Action =
   | ActionType<typeof addGlobalMessageActionCreator, 'ADD_GLOBAL_MESSAGE'>
-  | ActionType<typeof closeGlobalMessage, 'CLOSE_GLOBAL_MESSAGE'>
-  | ActionType<typeof closeAllGlobalMessages, 'CLOSE_ALL_GLOBAL_MESSAGES'>;
+  | ActionType<typeof closeGlobalMessage, 'CLOSE_GLOBAL_MESSAGE'>;
 
 function addGlobalMessage(message: string, level: MessageLevel) {
   return (dispatch: Dispatch) => {
     const id = uniqueId('global-message-');
     dispatch(addGlobalMessageActionCreator(id, message, level));
-    setTimeout(() => dispatch(closeGlobalMessage(id)), 5000);
+    setTimeout(() => dispatch(closeGlobalMessage(id)), MESSAGE_DISPLAY_TIME);
   };
 }
 
@@ -67,7 +66,7 @@ export function addGlobalSuccessMessage(message: string) {
 
 export type State = Message[];
 
-export default function(state: State = [], action: Action): State {
+export default function globalMessagesReducer(state: State = [], action: Action): State {
   switch (action.type) {
     case 'ADD_GLOBAL_MESSAGE':
       return [{ id: action.id, message: action.message, level: action.level }];
@@ -75,8 +74,6 @@ export default function(state: State = [], action: Action): State {
     case 'CLOSE_GLOBAL_MESSAGE':
       return state.filter(message => message.id !== action.id);
 
-    case 'CLOSE_ALL_GLOBAL_MESSAGES':
-      return [];
     default:
       return state;
   }
diff --git a/server/sonar-web/src/main/js/store/rootActions.ts b/server/sonar-web/src/main/js/store/rootActions.ts
deleted file mode 100644 (file)
index fd2d12c..0000000
+++ /dev/null
@@ -1,77 +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 * as auth from '../api/auth';
-import { getQualityGateProjectStatus } from '../api/quality-gates';
-import { getBranchLikeQuery } from '../helpers/branch-like';
-import { extractStatusConditionsFromProjectStatus } from '../helpers/qualityGates';
-import { BranchLike } from '../types/branch-like';
-import { Status } from '../types/types';
-import { registerBranchStatusAction } from './branches';
-import { addGlobalErrorMessage } from './globalMessages';
-
-export function fetchBranchStatus(branchLike: BranchLike, projectKey: string) {
-  return (dispatch: Dispatch<any>) => {
-    getQualityGateProjectStatus({ projectKey, ...getBranchLikeQuery(branchLike) }).then(
-      projectStatus => {
-        const { ignoredConditions, status } = projectStatus;
-        const conditions = extractStatusConditionsFromProjectStatus(projectStatus);
-        dispatch(
-          registerBranchStatusAction(branchLike, projectKey, status, conditions, ignoredConditions)
-        );
-      },
-      () => {
-        dispatch(addGlobalErrorMessage('Fetching Quality Gate status failed'));
-      }
-    );
-  };
-}
-
-export function doLogin(login: string, password: string) {
-  return (dispatch: Dispatch<any>) =>
-    auth.login(login, password).then(
-      () => {
-        /* everything is fine */
-      },
-      () => {
-        dispatch(addGlobalErrorMessage('Authentication failed'));
-        return Promise.reject();
-      }
-    );
-}
-
-export function doLogout() {
-  return (dispatch: Dispatch<any>) =>
-    auth.logout().then(
-      () => {
-        /* everything is fine */
-      },
-      () => {
-        dispatch(addGlobalErrorMessage('Logout failed'));
-        return Promise.reject();
-      }
-    );
-}
-
-export function registerBranchStatus(branchLike: BranchLike, component: string, status: Status) {
-  return (dispatch: Dispatch) => {
-    dispatch(registerBranchStatusAction(branchLike, component, status));
-  };
-}
diff --git a/server/sonar-web/src/main/js/store/utils/actions.ts b/server/sonar-web/src/main/js/store/utils/actions.ts
deleted file mode 100644 (file)
index 1e340e0..0000000
+++ /dev/null
@@ -1,22 +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.
- */
-type ActionCreator = (...args: any[]) => { type: string };
-
-export type ActionType<F extends ActionCreator, T> = Omit<ReturnType<F>, 'type'> & { type: T };
diff --git a/server/sonar-web/src/main/js/types/actions.ts b/server/sonar-web/src/main/js/types/actions.ts
new file mode 100644 (file)
index 0000000..1e340e0
--- /dev/null
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+type ActionCreator = (...args: any[]) => { type: string };
+
+export type ActionType<F extends ActionCreator, T> = Omit<ReturnType<F>, 'type'> & { type: T };