aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main/js
diff options
context:
space:
mode:
authorJeremy Davis <jeremy.davis@sonarsource.com>2022-03-22 16:24:08 +0100
committersonartech <sonartech@sonarsource.com>2022-03-23 20:02:45 +0000
commita38eda46c2f174efe54e6a584f12895a3d3c7f3c (patch)
treeef05b7691529a4cd77dba965e523469e7e5b7d3b /server/sonar-web/src/main/js
parent9a35b0e3e65302ee4f6c1b4cc2c5db71df5b0a5d (diff)
downloadsonarqube-a38eda46c2f174efe54e6a584f12895a3d3c7f3c.tar.gz
sonarqube-a38eda46c2f174efe54e6a584f12895a3d3c7f3c.zip
SONAR-15914 Extract branchstatus from redux
Diffstat (limited to 'server/sonar-web/src/main/js')
-rw-r--r--server/sonar-web/src/main/js/app/components/ComponentContainer.tsx13
-rw-r--r--server/sonar-web/src/main/js/app/components/GlobalContainer.tsx29
-rw-r--r--server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx8
-rw-r--r--server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalContainer-test.tsx.snap54
-rw-r--r--server/sonar-web/src/main/js/app/components/branch-status/BranchStatusContext.tsx46
-rw-r--r--server/sonar-web/src/main/js/app/components/branch-status/BranchStatusContextProvider.tsx100
-rw-r--r--server/sonar-web/src/main/js/app/components/branch-status/__tests__/BranchStatusContextProvider-test.tsx73
-rw-r--r--server/sonar-web/src/main/js/app/components/branch-status/withBranchStatus.tsx59
-rw-r--r--server/sonar-web/src/main/js/app/components/branch-status/withBranchStatusActions.tsx51
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/HeaderMeta.tsx2
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/HeaderMeta-test.tsx.snap27
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/branch-like/MenuItem.tsx2
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/MenuItem-test.tsx.snap50
-rw-r--r--server/sonar-web/src/main/js/apps/code/components/CodeApp.tsx18
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/App.tsx7
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/AppContainer.tsx31
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx21
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/__tests__/AppContainer-test.tsx50
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/__tests__/IssuesApp-test.tsx124
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/IssuesApp-test.tsx.snap671
-rw-r--r--server/sonar-web/src/main/js/apps/overview/pullRequests/PullRequestOverview.tsx41
-rw-r--r--server/sonar-web/src/main/js/apps/projectBranches/components/BranchLikeRow.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/BranchLikeRow-test.tsx.snap75
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsApp.tsx7
-rw-r--r--server/sonar-web/src/main/js/components/common/BranchStatus.tsx24
-rw-r--r--server/sonar-web/src/main/js/components/common/__tests__/BranchStatus-test.tsx18
-rw-r--r--server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/BranchStatus-test.tsx.snap8
-rw-r--r--server/sonar-web/src/main/js/components/workspace/WorkspaceComponentViewer.tsx7
-rw-r--r--server/sonar-web/src/main/js/helpers/branch-like.ts11
-rw-r--r--server/sonar-web/src/main/js/helpers/testMocks.ts26
-rw-r--r--server/sonar-web/src/main/js/store/__tests__/branches-test.ts149
-rw-r--r--server/sonar-web/src/main/js/store/__tests__/globalMessages-test.ts (renamed from server/sonar-web/src/main/js/store/__tests__/rootReducers-test.tsx)32
-rw-r--r--server/sonar-web/src/main/js/store/branches.ts115
-rw-r--r--server/sonar-web/src/main/js/store/globalMessages.ts2
-rw-r--r--server/sonar-web/src/main/js/store/rootReducer.ts12
-rw-r--r--server/sonar-web/src/main/js/types/branch-like.ts7
36 files changed, 721 insertions, 1251 deletions
diff --git a/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx b/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx
index fc98bc60e17..a3d571fcb2e 100644
--- a/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx
+++ b/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx
@@ -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)));
diff --git a/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx b/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx
index 52a5eccf943..3ee41a0606e 100644
--- a/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx
+++ b/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx
@@ -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>
diff --git a/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx b/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx
index a0bbceabb68..f1c4226abca 100644
--- a/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx
+++ b/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx
@@ -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 />
diff --git a/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalContainer-test.tsx.snap b/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalContainer-test.tsx.snap
index ab5e5905229..2f15d0754f4 100644
--- a/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalContainer-test.tsx.snap
+++ b/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalContainer-test.tsx.snap
@@ -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
index 00000000000..64220f8c13b
--- /dev/null
+++ b/server/sonar-web/src/main/js/app/components/branch-status/BranchStatusContext.tsx
@@ -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
index 00000000000..fc0e828d5a2
--- /dev/null
+++ b/server/sonar-web/src/main/js/app/components/branch-status/BranchStatusContextProvider.tsx
@@ -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
index 00000000000..9b9e26dfea3
--- /dev/null
+++ b/server/sonar-web/src/main/js/app/components/branch-status/__tests__/BranchStatusContextProvider-test.tsx
@@ -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
index 00000000000..8f1ccf414de
--- /dev/null
+++ b/server/sonar-web/src/main/js/app/components/branch-status/withBranchStatus.tsx
@@ -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
index 00000000000..97ffc3de903
--- /dev/null
+++ b/server/sonar-web/src/main/js/app/components/branch-status/withBranchStatusActions.tsx
@@ -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>
+ );
+ }
+ };
+}
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/HeaderMeta.tsx b/server/sonar-web/src/main/js/app/components/nav/component/HeaderMeta.tsx
index f77c4429974..1729155f1a0 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/HeaderMeta.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/component/HeaderMeta.tsx
@@ -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>
)}
</>
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/HeaderMeta-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/HeaderMeta-test.tsx.snap
index 14a257a1b3c..110542461d9 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/HeaderMeta-test.tsx.snap
+++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/HeaderMeta-test.tsx.snap
@@ -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>
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/MenuItem.tsx b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/MenuItem.tsx
index eeb24400e8a..298ae9448f0 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/MenuItem.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/MenuItem.tsx
@@ -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>
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/MenuItem-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/MenuItem-test.tsx.snap
index 393f960966d..e9df1e0578f 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/MenuItem-test.tsx.snap
+++ b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/MenuItem-test.tsx.snap
@@ -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>
diff --git a/server/sonar-web/src/main/js/apps/code/components/CodeApp.tsx b/server/sonar-web/src/main/js/apps/code/components/CodeApp.tsx
index 57bb881e842..c6e55679357 100644
--- a/server/sonar-web/src/main/js/apps/code/components/CodeApp.tsx
+++ b/server/sonar-web/src/main/js/apps/code/components/CodeApp.tsx
@@ -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));
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/App.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/App.tsx
index ce61093edf2..df8f249abe2 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/components/App.tsx
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/App.tsx
@@ -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));
diff --git a/server/sonar-web/src/main/js/apps/issues/components/AppContainer.tsx b/server/sonar-web/src/main/js/apps/issues/components/AppContainer.tsx
index 49e326575cb..e3060ab0d0f 100644
--- a/server/sonar-web/src/main/js/apps/issues/components/AppContainer.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/components/AppContainer.tsx
@@ -17,38 +17,11 @@
* 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)));
diff --git a/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx b/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx
index 92db8e0acc8..7da860bcc43 100644
--- a/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx
@@ -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
index bc78aeab32e..00000000000
--- a/server/sonar-web/src/main/js/apps/issues/components/__tests__/AppContainer-test.tsx
+++ /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' });
- });
-});
diff --git a/server/sonar-web/src/main/js/apps/issues/components/__tests__/IssuesApp-test.tsx b/server/sonar-web/src/main/js/apps/issues/components/__tests__/IssuesApp-test.tsx
index 0e4ed2e7a22..5a43255cc15 100644
--- a/server/sonar-web/src/main/js/apps/issues/components/__tests__/IssuesApp-test.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/components/__tests__/IssuesApp-test.tsx
@@ -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()}
diff --git a/server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/IssuesApp-test.tsx.snap b/server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/IssuesApp-test.tsx.snap
index 5af18892050..13685dbe0a5 100644
--- a/server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/IssuesApp-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/IssuesApp-test.tsx.snap
@@ -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>
-`;
diff --git a/server/sonar-web/src/main/js/apps/overview/pullRequests/PullRequestOverview.tsx b/server/sonar-web/src/main/js/apps/overview/pullRequests/PullRequestOverview.tsx
index bfb4c232ec3..50b98a20b64 100644
--- a/server/sonar-web/src/main/js/apps/overview/pullRequests/PullRequestOverview.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/pullRequests/PullRequestOverview.tsx
@@ -20,20 +20,19 @@
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));
diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/BranchLikeRow.tsx b/server/sonar-web/src/main/js/apps/projectBranches/components/BranchLikeRow.tsx
index 5a37613cc24..50aa2dc36d9 100644
--- a/server/sonar-web/src/main/js/apps/projectBranches/components/BranchLikeRow.tsx
+++ b/server/sonar-web/src/main/js/apps/projectBranches/components/BranchLikeRow.tsx
@@ -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) && (
diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/BranchLikeRow-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/BranchLikeRow-test.tsx.snap
index a39fb5dd0ec..ba11b5fc0c2 100644
--- a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/BranchLikeRow-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/BranchLikeRow-test.tsx.snap
@@ -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
diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsApp.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsApp.tsx
index 2eaf52d2950..f9814f838c7 100644
--- a/server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsApp.tsx
+++ b/server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsApp.tsx
@@ -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));
diff --git a/server/sonar-web/src/main/js/components/common/BranchStatus.tsx b/server/sonar-web/src/main/js/components/common/BranchStatus.tsx
index 570c60afcca..04f153888bc 100644
--- a/server/sonar-web/src/main/js/components/common/BranchStatus.tsx
+++ b/server/sonar-web/src/main/js/components/common/BranchStatus.tsx
@@ -18,21 +18,15 @@
* 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);
diff --git a/server/sonar-web/src/main/js/components/common/__tests__/BranchStatus-test.tsx b/server/sonar-web/src/main/js/components/common/__tests__/BranchStatus-test.tsx
index 3c725df069e..551674c475b 100644
--- a/server/sonar-web/src/main/js/components/common/__tests__/BranchStatus-test.tsx
+++ b/server/sonar-web/src/main/js/components/common/__tests__/BranchStatus-test.tsx
@@ -19,14 +19,22 @@
*/
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} />);
}
diff --git a/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/BranchStatus-test.tsx.snap b/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/BranchStatus-test.tsx.snap
index df907fd0bfb..aeb2bda6418 100644
--- a/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/BranchStatus-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/BranchStatus-test.tsx.snap
@@ -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}
/>
`;
diff --git a/server/sonar-web/src/main/js/components/workspace/WorkspaceComponentViewer.tsx b/server/sonar-web/src/main/js/components/workspace/WorkspaceComponentViewer.tsx
index 23cf4dfcd3c..d7561d6e604 100644
--- a/server/sonar-web/src/main/js/components/workspace/WorkspaceComponentViewer.tsx
+++ b/server/sonar-web/src/main/js/components/workspace/WorkspaceComponentViewer.tsx
@@ -19,11 +19,10 @@
*/
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);
diff --git a/server/sonar-web/src/main/js/helpers/branch-like.ts b/server/sonar-web/src/main/js/helpers/branch-like.ts
index b332e9acd2e..ce6cc4ec13f 100644
--- a/server/sonar-web/src/main/js/helpers/branch-like.ts
+++ b/server/sonar-web/src/main/js/helpers/branch-like.ts
@@ -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];
+}
diff --git a/server/sonar-web/src/main/js/helpers/testMocks.ts b/server/sonar-web/src/main/js/helpers/testMocks.ts
index 3c4c452ddd7..7269073b760 100644
--- a/server/sonar-web/src/main/js/helpers/testMocks.ts
+++ b/server/sonar-web/src/main/js/helpers/testMocks.ts
@@ -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
index eb52257e42a..00000000000
--- a/server/sonar-web/src/main/js/store/__tests__/branches-test.ts
+++ /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__/rootReducers-test.tsx b/server/sonar-web/src/main/js/store/__tests__/globalMessages-test.ts
index c6cffde06bd..a5ceed74a08 100644
--- a/server/sonar-web/src/main/js/store/__tests__/rootReducers-test.tsx
+++ b/server/sonar-web/src/main/js/store/__tests__/globalMessages-test.ts
@@ -17,16 +17,28 @@
* 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';
+import globalMessagesReducer, { MessageLevel } from '../globalMessages';
-it('correctly reduce state for branches', () => {
- const spiedOn = jest.spyOn(fromBranches, 'getBranchStatusByBranchLike').mockReturnValueOnce({});
+describe('globalMessagesReducer', () => {
+ it('should handle ADD_GLOBAL_MESSAGE', () => {
+ const actionAttributes = { id: 'id', message: 'There was an error', level: MessageLevel.Error };
- const branches = { byComponent: {} };
- const component = 'foo';
- const branchLike = mockPullRequest();
- getBranchStatusByBranchLike({ branches } as Store, component, branchLike);
- expect(spiedOn).toBeCalledWith(branches, component, branchLike);
+ 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/branches.ts b/server/sonar-web/src/main/js/store/branches.ts
deleted file mode 100644
index e6e4dada333..00000000000
--- a/server/sonar-web/src/main/js/store/branches.ts
+++ /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));
- };
-}
diff --git a/server/sonar-web/src/main/js/store/globalMessages.ts b/server/sonar-web/src/main/js/store/globalMessages.ts
index c64ac9dafdb..58776e7d253 100644
--- a/server/sonar-web/src/main/js/store/globalMessages.ts
+++ b/server/sonar-web/src/main/js/store/globalMessages.ts
@@ -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'
}
diff --git a/server/sonar-web/src/main/js/store/rootReducer.ts b/server/sonar-web/src/main/js/store/rootReducer.ts
index 885d7b25cdc..2a68fa75667 100644
--- a/server/sonar-web/src/main/js/store/rootReducer.ts
+++ b/server/sonar-web/src/main/js/store/rootReducer.ts
@@ -18,28 +18,16 @@
* 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);
-}
diff --git a/server/sonar-web/src/main/js/types/branch-like.ts b/server/sonar-web/src/main/js/types/branch-like.ts
index 7a479af7ae2..1495e07d4cd 100644
--- a/server/sonar-web/src/main/js/types/branch-like.ts
+++ b/server/sonar-web/src/main/js/types/branch-like.ts
@@ -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;
+}