]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-10994 Add new branch store
authorWouter Admiraal <wouter.admiraal@sonarsource.com>
Wed, 6 Mar 2019 14:49:52 +0000 (15:49 +0100)
committersonartech <sonartech@sonarsource.com>
Fri, 29 Mar 2019 08:44:57 +0000 (09:44 +0100)
server/sonar-web/src/main/js/app/types.d.ts
server/sonar-web/src/main/js/store/__tests__/branches-test.ts [new file with mode: 0644]
server/sonar-web/src/main/js/store/__tests__/rootActions-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/store/__tests__/rootReducers-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/store/branches.ts [new file with mode: 0644]
server/sonar-web/src/main/js/store/rootActions.ts
server/sonar-web/src/main/js/store/rootReducer.ts

index 16d74f126c0cfb13d45c983696a9812674227839..340ebfa96c617e48745569daae0153be9329407a 100644 (file)
@@ -108,7 +108,7 @@ declare namespace T {
     analysisDate?: string;
     isMain: boolean;
     name: string;
-    status?: { qualityGateStatus: string };
+    status?: { qualityGateStatus: Status };
   }
 
   export type BranchLike = Branch | PullRequest;
@@ -624,7 +624,7 @@ declare namespace T {
     branch: string;
     key: string;
     isOrphan?: true;
-    status?: { qualityGateStatus: string };
+    status?: { qualityGateStatus: Status };
     title: string;
     url?: string;
   }
@@ -646,7 +646,7 @@ declare namespace T {
   }
 
   export interface QualityGateProjectStatusCondition {
-    status: 'ERROR' | 'OK';
+    status: Status;
     metricKey: string;
     comparator: string;
     periodIndex: number;
@@ -658,7 +658,7 @@ declare namespace T {
     projectStatus: {
       conditions?: QualityGateProjectStatusCondition[];
       ignoredConditions: boolean;
-      status: string;
+      status: Status;
     };
   }
 
@@ -736,6 +736,8 @@ declare namespace T {
 
   export type RuleType = 'BUG' | 'VULNERABILITY' | 'CODE_SMELL' | 'SECURITY_HOTSPOT' | 'UNKNOWN';
 
+  export type Status = 'ERROR' | 'OK';
+
   export type Setting = SettingValue & { definition: SettingDefinition };
 
   export type SettingType =
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
new file mode 100644 (file)
index 0000000..554ac0f
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 reducer, {
+  registerBranchStatusAction,
+  getBranchStatusByBranchLike,
+  State
+} from '../branches';
+import {
+  mockPullRequest,
+  mockLongLivingBranch,
+  mockShortLivingBranch
+} from '../../helpers/testMocks';
+import { getBranchLikeKey } from '../../helpers/branches';
+
+type TestArgs = [T.BranchLike, string, T.Status];
+
+const COMPONENT = 'foo';
+const BRANCH_STATUS_1: TestArgs = [mockPullRequest(), COMPONENT, 'ERROR'];
+const BRANCH_STATUS_2: TestArgs = [mockLongLivingBranch(), 'bar', 'OK'];
+const BRANCH_STATUS_3: TestArgs = [mockShortLivingBranch(), 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: T.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('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] = item;
+    state.byComponent[component] = {
+      ...(state.byComponent[component] || {}),
+      [getBranchLikeKey(branchLike)]: { status }
+    };
+  });
+
+  return state;
+}
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
new file mode 100644 (file)
index 0000000..18adff3
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 { registerBranchStatus } from '../rootActions';
+import { mockLongLivingBranch } from '../../helpers/testMocks';
+import { registerBranchStatusAction } from '../branches';
+
+jest.mock('../branches', () => ({
+  ...require.requireActual('../branches'),
+  registerBranchStatusAction: jest.fn()
+}));
+
+it('correctly dispatches actions for branches', () => {
+  const dispatch = jest.fn();
+  const branchLike = mockLongLivingBranch();
+  const component = 'foo';
+  const status = 'OK';
+
+  registerBranchStatus(branchLike, component, status)(dispatch);
+  expect(registerBranchStatusAction).toBeCalledWith(branchLike, component, status);
+  expect(dispatch).toBeCalled();
+});
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
new file mode 100644 (file)
index 0000000..e31f346
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 { getBranchStatusByBranchLike, Store } from '../rootReducer';
+import * as fromBranches from '../branches';
+import { mockPullRequest } from '../../helpers/testMocks';
+
+jest.mock('../branches', () => {
+  return {
+    ...require.requireActual('../branches'),
+    getBranchStatusByBranchLike: jest.fn()
+  };
+});
+
+it('correctly reduce state for branches', () => {
+  const branches = {};
+  const component = 'foo';
+  const branchLike = mockPullRequest();
+  getBranchStatusByBranchLike({ branches } as Store, component, branchLike);
+  expect(fromBranches.getBranchStatusByBranchLike).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
new file mode 100644 (file)
index 0000000..5159344
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 { ActionType } from './utils/actions';
+import { getBranchLikeKey } from '../helpers/branches';
+
+export interface State {
+  byComponent: T.Dict<T.Dict<{ status?: T.Status }>>;
+}
+
+const enum Actions {
+  RegisterBranchStatus = 'REGISTER_BRANCH_STATUS'
+}
+
+type Action = ActionType<typeof registerBranchStatusAction, Actions.RegisterBranchStatus>;
+
+export function registerBranchStatusAction(
+  branchLike: T.BranchLike,
+  component: string,
+  status: T.Status
+) {
+  return { type: Actions.RegisterBranchStatus, branchLike, component, status };
+}
+
+export default function(state: State = { byComponent: {} }, action: Action): State {
+  if (action.type === Actions.RegisterBranchStatus) {
+    const { component, branchLike, status } = action;
+    const branchLikeKey = getBranchLikeKey(branchLike);
+    return {
+      byComponent: {
+        ...state.byComponent,
+        [component]: {
+          ...(state.byComponent[component] || {}),
+          [branchLikeKey]: {
+            status
+          }
+        }
+      }
+    };
+  }
+
+  return state;
+}
+
+export function getBranchStatusByBranchLike(
+  state: State,
+  component: string,
+  branchLike: T.BranchLike
+) {
+  const branchLikeKey = getBranchLikeKey(branchLike);
+  return (
+    state.byComponent[component] &&
+    state.byComponent[component][branchLikeKey] &&
+    state.byComponent[component][branchLikeKey].status
+  );
+}
index 6bab84e7335b6101d737ff1a31fdf1e349497c81..e9d68e0c5c22b64c8ab40a109b928c53c38c5ecd 100644 (file)
@@ -20,6 +20,7 @@
 import { Dispatch } from 'redux';
 import { InjectedRouter } from 'react-router';
 import { requireAuthorization as requireAuthorizationAction } from './appState';
+import { registerBranchStatusAction } from './branches';
 import { addGlobalErrorMessage } from './globalMessages';
 import { receiveLanguages } from './languages';
 import { receiveMetrics } from './metrics';
@@ -92,3 +93,13 @@ export function requireAuthorization(router: Pick<InjectedRouter, 'replace'>) {
   router.replace({ pathname: '/sessions/new', query: { return_to: returnTo } });
   return requireAuthorizationAction();
 }
+
+export function registerBranchStatus(
+  branchLike: T.BranchLike,
+  component: string,
+  status: T.Status
+) {
+  return (dispatch: Dispatch) => {
+    dispatch(registerBranchStatusAction(branchLike, component, status));
+  };
+}
index e4286c79a997313a0064a16b401737c44c944e41..c1b94e09bba63fb78b1bffbc4950215611ea9be9 100644 (file)
@@ -19,6 +19,7 @@
  */
 import { combineReducers } from 'redux';
 import appState from './appState';
+import branches, * as fromBranches from './branches';
 import globalMessages, * as fromGlobalMessages from './globalMessages';
 import languages, * as fromLanguages from './languages';
 import metrics, * as fromMetrics from './metrics';
@@ -28,6 +29,7 @@ import settingsApp, * as fromSettingsApp from '../apps/settings/store/rootReduce
 
 export type Store = {
   appState: T.AppState;
+  branches: fromBranches.State;
   globalMessages: fromGlobalMessages.State;
   languages: T.Languages;
   metrics: fromMetrics.State;
@@ -40,6 +42,7 @@ export type Store = {
 
 export default combineReducers<Store>({
   appState,
+  branches,
   globalMessages,
   languages,
   metrics,
@@ -125,3 +128,11 @@ export function isSettingsAppLoading(state: Store, key: string) {
 export function getSettingsAppValidationMessage(state: Store, key: string) {
   return fromSettingsApp.getValidationMessage(state.settingsApp, key);
 }
+
+export function getBranchStatusByBranchLike(
+  state: Store,
+  component: string,
+  branchLike: T.BranchLike
+) {
+  return fromBranches.getBranchStatusByBranchLike(state.branches, component, branchLike);
+}