]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-15911 Extract Metrics from Redux
authorGuillaume Peoc'h <guillaume.peoch@sonarsource.com>
Thu, 27 Jan 2022 12:55:21 +0000 (13:55 +0100)
committersonartech <sonartech@sonarsource.com>
Tue, 1 Feb 2022 20:02:59 +0000 (20:02 +0000)
40 files changed:
server/sonar-web/src/main/js/app/components/GlobalContainer.tsx
server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalContainer-test.tsx.snap
server/sonar-web/src/main/js/app/components/metrics/MetricsContext.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/app/components/metrics/MetricsContextProvider.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/app/components/metrics/__tests__/MetricsContextProvider-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/app/components/metrics/__tests__/withMetricsContext-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/app/components/metrics/withMetricsContext.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNav-test.tsx.snap
server/sonar-web/src/main/js/app/components/nav/component/projectInformation/ProjectInformation.tsx
server/sonar-web/src/main/js/app/components/nav/component/projectInformation/__tests__/ProjectInformation-test.tsx
server/sonar-web/src/main/js/app/components/nav/component/projectInformation/__tests__/__snapshots__/ProjectInformation-test.tsx.snap
server/sonar-web/src/main/js/app/components/nav/component/projectInformation/badges/BadgeParams.tsx
server/sonar-web/src/main/js/app/components/nav/component/projectInformation/badges/ProjectBadges.tsx
server/sonar-web/src/main/js/app/components/nav/component/projectInformation/badges/__tests__/BadgeParams-test.tsx
server/sonar-web/src/main/js/app/components/nav/component/projectInformation/badges/__tests__/ProjectBadges-test.tsx
server/sonar-web/src/main/js/app/components/nav/component/projectInformation/badges/__tests__/__snapshots__/ProjectBadges-test.tsx.snap
server/sonar-web/src/main/js/apps/code/components/CodeApp.tsx
server/sonar-web/src/main/js/apps/code/components/Components.tsx
server/sonar-web/src/main/js/apps/code/components/__tests__/CodeApp-test.tsx
server/sonar-web/src/main/js/apps/quality-gates/__tests__/utils-test.ts
server/sonar-web/src/main/js/apps/quality-gates/components/Condition.tsx
server/sonar-web/src/main/js/apps/quality-gates/components/ConditionModal.tsx
server/sonar-web/src/main/js/apps/quality-gates/components/Conditions.tsx
server/sonar-web/src/main/js/apps/quality-gates/components/Details.tsx
server/sonar-web/src/main/js/apps/quality-gates/components/DetailsContent.tsx
server/sonar-web/src/main/js/apps/quality-gates/components/MetricSelect.tsx
server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/Condition-test.tsx
server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/ConditionModal-test.tsx
server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/Details-test.tsx
server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/DetailsContent-test.tsx
server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/MetricSelect-test.tsx
server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/App.tsx.snap
server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/ConditionModal-test.tsx.snap
server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/Conditions-test.tsx.snap
server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/Details-test.tsx.snap
server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/DetailsContent-test.tsx.snap
server/sonar-web/src/main/js/apps/quality-gates/utils.ts
server/sonar-web/src/main/js/store/metrics.ts [deleted file]
server/sonar-web/src/main/js/store/rootActions.ts
server/sonar-web/src/main/js/store/rootReducer.ts

index f824e12e575b7992c08fbc4f19b93479622f3df2..c9423bff3318731742d13a7639f8bcd7efa2cec6 100644 (file)
@@ -27,6 +27,7 @@ import GlobalMessagesContainer from './GlobalMessagesContainer';
 import IndexationContextProvider from './indexation/IndexationContextProvider';
 import IndexationNotification from './indexation/IndexationNotification';
 import LanguageContextProvider from './languages/LanguagesContextProvider';
+import MetricContextProvider from './metrics/MetricsContextProvider';
 import GlobalNav from './nav/global/GlobalNav';
 import PromotionNotification from './promotion-notification/PromotionNotification';
 import StartupModal from './StartupModal';
@@ -53,11 +54,13 @@ export default function GlobalContainer(props: Props) {
                 <Workspace>
                   <IndexationContextProvider>
                     <LanguageContextProvider>
-                      <GlobalNav location={props.location} />
-                      <GlobalMessagesContainer />
-                      <IndexationNotification />
-                      <UpdateNotification dismissable={true} />
-                      {props.children}
+                      <MetricContextProvider>
+                        <GlobalNav location={props.location} />
+                        <GlobalMessagesContainer />
+                        <IndexationNotification />
+                        <UpdateNotification dismissable={true} />
+                        {props.children}
+                      </MetricContextProvider>
                     </LanguageContextProvider>
                   </IndexationContextProvider>
                 </Workspace>
index e68741350699e21fe22877dd7e8884a8e1f07ec9..1e1d574f0a03147be5d90d54772daf1d6d0d7381 100644 (file)
@@ -18,25 +18,27 @@ exports[`should render correctly 1`] = `
             <Workspace>
               <Connect(withAppState(IndexationContextProvider))>
                 <LanguageContextProvider>
-                  <Connect(GlobalNav)
-                    location={
-                      Object {
-                        "action": "PUSH",
-                        "hash": "",
-                        "key": "key",
-                        "pathname": "/path",
-                        "query": Object {},
-                        "search": "",
-                        "state": Object {},
+                  <MetricContextProvider>
+                    <Connect(GlobalNav)
+                      location={
+                        Object {
+                          "action": "PUSH",
+                          "hash": "",
+                          "key": "key",
+                          "pathname": "/path",
+                          "query": Object {},
+                          "search": "",
+                          "state": Object {},
+                        }
                       }
-                    }
-                  />
-                  <Connect(GlobalMessages) />
-                  <Connect(withCurrentUser(withIndexationContext(IndexationNotification))) />
-                  <Connect(withCurrentUser(Connect(withAppState(UpdateNotification))))
-                    dismissable={true}
-                  />
-                  <ChildComponent />
+                    />
+                    <Connect(GlobalMessages) />
+                    <Connect(withCurrentUser(withIndexationContext(IndexationNotification))) />
+                    <Connect(withCurrentUser(Connect(withAppState(UpdateNotification))))
+                      dismissable={true}
+                    />
+                    <ChildComponent />
+                  </MetricContextProvider>
                 </LanguageContextProvider>
               </Connect(withAppState(IndexationContextProvider))>
             </Workspace>
diff --git a/server/sonar-web/src/main/js/app/components/metrics/MetricsContext.tsx b/server/sonar-web/src/main/js/app/components/metrics/MetricsContext.tsx
new file mode 100644 (file)
index 0000000..589666d
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * 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 { Dict, Metric } from '../../../types/types';
+
+export const MetricsContext = React.createContext<Dict<Metric>>({});
diff --git a/server/sonar-web/src/main/js/app/components/metrics/MetricsContextProvider.tsx b/server/sonar-web/src/main/js/app/components/metrics/MetricsContextProvider.tsx
new file mode 100644 (file)
index 0000000..8e3e239
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+import { keyBy } from 'lodash';
+import * as React from 'react';
+import { getAllMetrics } from '../../../api/metrics';
+import { Dict, Metric } from '../../../types/types';
+import { MetricsContext } from './MetricsContext';
+
+interface State {
+  metrics: Dict<Metric>;
+}
+
+export default class MetricContextProvider extends React.PureComponent<{}, State> {
+  mounted = false;
+  state: State = {
+    metrics: {}
+  };
+
+  componentDidMount() {
+    this.mounted = true;
+
+    this.fetchMetrics();
+  }
+
+  componentWillUnmount() {
+    this.mounted = false;
+  }
+
+  fetchMetrics = async () => {
+    const metricList = await getAllMetrics();
+    this.setState({ metrics: keyBy(metricList, 'key') });
+  };
+
+  render() {
+    return (
+      <MetricsContext.Provider value={this.state.metrics}>
+        {this.props.children}
+      </MetricsContext.Provider>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/app/components/metrics/__tests__/MetricsContextProvider-test.tsx b/server/sonar-web/src/main/js/app/components/metrics/__tests__/MetricsContextProvider-test.tsx
new file mode 100644 (file)
index 0000000..753d73b
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * 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 { getAllMetrics } from '../../../../api/metrics';
+import { mockMetric } from '../../../../helpers/testMocks';
+import { waitAndUpdate } from '../../../../helpers/testUtils';
+import MetricContextProvider from '../MetricsContextProvider';
+
+jest.mock('../../../../api/metrics', () => ({
+  getAllMetrics: jest.fn().mockResolvedValue({})
+}));
+
+it('should call metric', async () => {
+  const metrics = { coverage: mockMetric() };
+  (getAllMetrics as jest.Mock).mockResolvedValueOnce(Object.values(metrics));
+  const wrapper = shallowRender();
+
+  expect(getAllMetrics).toBeCalled();
+  await waitAndUpdate(wrapper);
+  expect(wrapper.state()).toEqual({ metrics });
+});
+
+function shallowRender() {
+  return shallow<MetricContextProvider>(
+    <MetricContextProvider>
+      <div />
+    </MetricContextProvider>
+  );
+}
diff --git a/server/sonar-web/src/main/js/app/components/metrics/__tests__/withMetricsContext-test.tsx b/server/sonar-web/src/main/js/app/components/metrics/__tests__/withMetricsContext-test.tsx
new file mode 100644 (file)
index 0000000..f4d43f7
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * 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 { mockMetric } from '../../../../helpers/testMocks';
+import { Dict, Metric } from '../../../../types/types';
+import withMetricsContext from '../withMetricsContext';
+
+const metrics = {
+  coverage: mockMetric()
+};
+
+jest.mock('../MetricsContext', () => {
+  return {
+    MetricsContext: {
+      Consumer: ({ children }: { children: (props: {}) => React.ReactNode }) => {
+        return children({ metrics });
+      }
+    }
+  };
+});
+
+class Wrapped extends React.Component<{ metrics: Dict<Metric> }> {
+  render() {
+    return <div />;
+  }
+}
+
+const UnderTest = withMetricsContext(Wrapped);
+
+it('should inject metrics', () => {
+  const wrapper = shallow(<UnderTest />);
+  expect(wrapper.dive().type()).toBe(Wrapped);
+  expect(wrapper.dive<Wrapped>().props().metrics).toEqual({ metrics });
+});
diff --git a/server/sonar-web/src/main/js/app/components/metrics/withMetricsContext.tsx b/server/sonar-web/src/main/js/app/components/metrics/withMetricsContext.tsx
new file mode 100644 (file)
index 0000000..e9d3d9e
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+import * as React from 'react';
+import { getWrappedDisplayName } from '../../../components/hoc/utils';
+import { Dict, Metric } from '../../../types/types';
+import { MetricsContext } from './MetricsContext';
+
+export interface WithMetricsContextProps {
+  metrics: Dict<Metric>;
+}
+
+export default function withMetricsContext<P>(
+  WrappedComponent: React.ComponentType<P & WithMetricsContextProps>
+) {
+  return class WithMetricsContext extends React.PureComponent<
+    Omit<P, keyof WithMetricsContextProps>
+  > {
+    static displayName = getWrappedDisplayName(WrappedComponent, 'withMetricsContext');
+
+    render() {
+      return (
+        <MetricsContext.Consumer>
+          {metrics => <WrappedComponent metrics={metrics} {...(this.props as P)} />}
+        </MetricsContext.Consumer>
+      );
+    }
+  };
+}
index 7ccac6e734a371e6e4f51a3f577b3febb69a8f63..4c9f5933de50ceb4f9ce985faee586c1b3e09d57 100644 (file)
@@ -112,7 +112,7 @@ exports[`renders correctly: default 1`] = `
     onClose={[Function]}
     top={120}
   >
-    <Connect(ProjectInformation)
+    <Connect(withMetricsContext(ProjectInformation))
       component={
         Object {
           "breadcrumbs": Array [
@@ -306,7 +306,7 @@ exports[`renders correctly: has failed notification 1`] = `
     onClose={[Function]}
     top={120}
   >
-    <Connect(ProjectInformation)
+    <Connect(withMetricsContext(ProjectInformation))
       component={
         Object {
           "breadcrumbs": Array [
@@ -486,7 +486,7 @@ exports[`renders correctly: has failed project binding 1`] = `
     onClose={[Function]}
     top={120}
   >
-    <Connect(ProjectInformation)
+    <Connect(withMetricsContext(ProjectInformation))
       component={
         Object {
           "breadcrumbs": Array [
@@ -668,7 +668,7 @@ exports[`renders correctly: has in progress notification 1`] = `
     onClose={[Function]}
     top={120}
   >
-    <Connect(ProjectInformation)
+    <Connect(withMetricsContext(ProjectInformation))
       component={
         Object {
           "breadcrumbs": Array [
@@ -850,7 +850,7 @@ exports[`renders correctly: has pending notification 1`] = `
     onClose={[Function]}
     top={120}
   >
-    <Connect(ProjectInformation)
+    <Connect(withMetricsContext(ProjectInformation))
       component={
         Object {
           "breadcrumbs": Array [
@@ -1005,7 +1005,7 @@ exports[`renders correctly: has warnings 1`] = `
     onClose={[Function]}
     top={120}
   >
-    <Connect(ProjectInformation)
+    <Connect(withMetricsContext(ProjectInformation))
       component={
         Object {
           "breadcrumbs": Array [
index e0d792b688d9a815612530c8188f889e879edbaa..6f28bae66978862157035a47067221c8d5b013e7 100644 (file)
@@ -21,12 +21,12 @@ import * as React from 'react';
 import { connect } from 'react-redux';
 import { getMeasures } from '../../../../../api/measures';
 import { isLoggedIn } from '../../../../../helpers/users';
-import { fetchMetrics } from '../../../../../store/rootActions';
-import { getCurrentUser, getMetrics, Store } from '../../../../../store/rootReducer';
+import { getCurrentUser, Store } from '../../../../../store/rootReducer';
 import { BranchLike } from '../../../../../types/branch-like';
 import { ComponentQualifier } from '../../../../../types/component';
 import { MetricKey } from '../../../../../types/metrics';
 import { Component, CurrentUser, Dict, Measure, Metric } from '../../../../../types/types';
+import withMetricsContext from '../../../metrics/withMetricsContext';
 import ProjectBadges from './badges/ProjectBadges';
 import InfoDrawerPage from './InfoDrawerPage';
 import ProjectNotifications from './notifications/ProjectNotifications';
@@ -38,7 +38,6 @@ interface Props {
   branchLike?: BranchLike;
   component: Component;
   currentUser: CurrentUser;
-  fetchMetrics: () => void;
   onComponentChange: (changes: {}) => void;
   metrics: Dict<Metric>;
 }
@@ -56,7 +55,6 @@ export class ProjectInformation extends React.PureComponent<Props, State> {
 
   componentDidMount() {
     this.mounted = true;
-    this.props.fetchMetrics();
     this.loadMeasures();
   }
 
@@ -108,7 +106,7 @@ export class ProjectInformation extends React.PureComponent<Props, State> {
           <InfoDrawerPage
             displayed={page === ProjectInformationPages.badges}
             onPageChange={this.setPage}>
-            <ProjectBadges branchLike={branchLike} metrics={metrics} component={component} />
+            <ProjectBadges branchLike={branchLike} component={component} />
           </InfoDrawerPage>
         )}
         {canConfigureNotifications && (
@@ -123,11 +121,8 @@ export class ProjectInformation extends React.PureComponent<Props, State> {
   }
 }
 
-const mapDispatchToProps = { fetchMetrics };
-
 const mapStateToProps = (state: Store) => ({
-  currentUser: getCurrentUser(state),
-  metrics: getMetrics(state)
+  currentUser: getCurrentUser(state)
 });
 
-export default connect(mapStateToProps, mapDispatchToProps)(ProjectInformation);
+export default connect(mapStateToProps)(withMetricsContext(ProjectInformation));
index 4236116ed44c8b4b350065f81a02f70e5527213a..b1006634dee2006bc1327ddaa16552bd6d5ccf32 100644 (file)
@@ -71,7 +71,6 @@ function shallowRender(props: Partial<ProjectInformation['props']> = {}) {
     <ProjectInformation
       component={mockComponent()}
       currentUser={mockCurrentUser()}
-      fetchMetrics={jest.fn()}
       metrics={{
         coverage: mockMetric()
       }}
index 91b4f6e28fcab294c157b0694a84532631f76973..879dcaea3937c37feb98e69336ec134db17e84c9 100644 (file)
@@ -57,16 +57,6 @@ exports[`should render correctly: default 1`] = `
           "tags": Array [],
         }
       }
-      metrics={
-        Object {
-          "coverage": Object {
-            "id": "coverage",
-            "key": "coverage",
-            "name": "Coverage",
-            "type": "PERCENT",
-          },
-        }
-      }
     />
   </InfoDrawerPage>
 </Fragment>
@@ -129,16 +119,6 @@ exports[`should render correctly: logged in user 1`] = `
           "tags": Array [],
         }
       }
-      metrics={
-        Object {
-          "coverage": Object {
-            "id": "coverage",
-            "key": "coverage",
-            "name": "Coverage",
-            "type": "PERCENT",
-          },
-        }
-      }
     />
   </InfoDrawerPage>
   <InfoDrawerPage
@@ -244,16 +224,6 @@ exports[`should render correctly: measures loaded 1`] = `
           "tags": Array [],
         }
       }
-      metrics={
-        Object {
-          "coverage": Object {
-            "id": "coverage",
-            "key": "coverage",
-            "name": "Coverage",
-            "type": "PERCENT",
-          },
-        }
-      }
     />
   </InfoDrawerPage>
 </Fragment>
@@ -318,16 +288,6 @@ exports[`should render correctly: private 1`] = `
           "visibility": "private",
         }
       }
-      metrics={
-        Object {
-          "coverage": Object {
-            "id": "coverage",
-            "key": "coverage",
-            "name": "Coverage",
-            "type": "PERCENT",
-          },
-        }
-      }
     />
   </InfoDrawerPage>
 </Fragment>
index 2dd6ea6a1d643c0681551596a6d00099a9f134ec..97a10e3e5c6c4cd67833ed7a8a91970f4351af95 100644 (file)
@@ -23,6 +23,7 @@ import { fetchWebApi } from '../../../../../../api/web-api';
 import SelectLegacy from '../../../../../../components/controls/SelectLegacy';
 import { getLocalizedMetricName, translate } from '../../../../../../helpers/l10n';
 import { Dict, Metric } from '../../../../../../types/types';
+import withMetricsContext from '../../../../metrics/withMetricsContext';
 import { BadgeFormats, BadgeOptions, BadgeType } from './utils';
 
 interface Props {
@@ -37,7 +38,7 @@ interface State {
   badgeMetrics: string[];
 }
 
-export default class BadgeParams extends React.PureComponent<Props> {
+export class BadgeParams extends React.PureComponent<Props> {
   mounted = false;
 
   state: State = { badgeMetrics: [] };
@@ -118,9 +119,8 @@ export default class BadgeParams extends React.PureComponent<Props> {
           />
         </>
       );
-    } else {
-      return null;
     }
+    return null;
   };
 
   render() {
@@ -149,3 +149,5 @@ export default class BadgeParams extends React.PureComponent<Props> {
     );
   }
 }
+
+export default withMetricsContext(BadgeParams);
index 17b677229fa47fd01409a463dcd6975504316899..408a275d09696c42557acd6bf09dec7a5aa79e55 100644 (file)
@@ -30,7 +30,7 @@ import { getBranchLikeQuery } from '../../../../../../helpers/branch-like';
 import { translate } from '../../../../../../helpers/l10n';
 import { BranchLike } from '../../../../../../types/branch-like';
 import { MetricKey } from '../../../../../../types/metrics';
-import { Component, Dict, Metric } from '../../../../../../types/types';
+import { Component } from '../../../../../../types/types';
 import BadgeButton from './BadgeButton';
 import BadgeParams from './BadgeParams';
 import './styles.css';
@@ -38,7 +38,6 @@ import { BadgeOptions, BadgeType, getBadgeSnippet, getBadgeUrl } from './utils';
 
 interface Props {
   branchLike?: BranchLike;
-  metrics: Dict<Metric>;
   component: Component;
 }
 
@@ -137,7 +136,6 @@ export default class ProjectBadges extends React.PureComponent<Props, State> {
         </p>
         <BadgeParams
           className="big-spacer-bottom display-flex-column"
-          metrics={this.props.metrics}
           options={badgeOptions}
           type={selectedType}
           updateOptions={this.handleUpdateOptions}
index f19a6ba5bd8789683926e727ba0724fa2f438ba8..6955585fe1dda618816682037632001fbae1a433 100644 (file)
@@ -20,7 +20,7 @@
 import { shallow } from 'enzyme';
 import * as React from 'react';
 import { Metric } from '../../../../../../../types/types';
-import BadgeParams from '../BadgeParams';
+import { BadgeParams } from '../BadgeParams';
 import { BadgeType } from '../utils';
 
 jest.mock('../../../../../../../api/web-api', () => ({
index f225789bb8349d416fd92987b0ad2f0188ea8e71..2adeaa6ad5e50deb89cedddeeb79d0547e089c44 100644 (file)
@@ -23,11 +23,9 @@ import { getProjectBadgesToken } from '../../../../../../../api/project-badges';
 import CodeSnippet from '../../../../../../../components/common/CodeSnippet';
 import { mockBranch } from '../../../../../../../helpers/mocks/branch-like';
 import { mockComponent } from '../../../../../../../helpers/mocks/component';
-import { mockMetric } from '../../../../../../../helpers/testMocks';
 import { waitAndUpdate } from '../../../../../../../helpers/testUtils';
 import { Location } from '../../../../../../../helpers/urls';
 import { ComponentQualifier } from '../../../../../../../types/component';
-import { MetricKey } from '../../../../../../../types/metrics';
 import BadgeButton from '../BadgeButton';
 import ProjectBadges from '../ProjectBadges';
 
@@ -73,10 +71,6 @@ function shallowRender(overrides = {}) {
   return shallow(
     <ProjectBadges
       branchLike={mockBranch()}
-      metrics={{
-        [MetricKey.coverage]: mockMetric({ key: MetricKey.coverage }),
-        [MetricKey.new_code_smells]: mockMetric({ key: MetricKey.new_code_smells })
-      }}
       component={mockComponent({ key: 'foo', qualifier: ComponentQualifier.Project })}
       {...overrides}
     />
index ea5049ad896937ee67b136f8b741ab5d582af5eb..6b02f56e9f47b4bf8474705cf3ce2e3fc49b0d74 100644 (file)
@@ -34,24 +34,8 @@ exports[`should display correctly 1`] = `
   >
     overview.badges.quality_gate.description.TRK
   </p>
-  <BadgeParams
+  <withMetricsContext(BadgeParams)
     className="big-spacer-bottom display-flex-column"
-    metrics={
-      Object {
-        "coverage": Object {
-          "id": "coverage",
-          "key": "coverage",
-          "name": "Coverage",
-          "type": "PERCENT",
-        },
-        "new_code_smells": Object {
-          "id": "new_code_smells",
-          "key": "new_code_smells",
-          "name": "New_code_smells",
-          "type": "PERCENT",
-        },
-      }
-    }
     options={
       Object {
         "metric": "alert_status",
index c608d15fd12ed3a9b36d2e0754e3edf56f9e93c1..34f37f04af9bc8bf3951e628d31f688e80ad86ca 100644 (file)
@@ -1,3 +1,4 @@
+/* eslint-disable no-console */
 /*
  * SonarQube
  * Copyright (C) 2009-2022 SonarSource SA
@@ -27,14 +28,14 @@ import { connect } from 'react-redux';
 import { InjectedRouter } from 'react-router';
 import A11ySkipTarget from '../../../app/components/a11y/A11ySkipTarget';
 import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
+import withMetricsContext from '../../../app/components/metrics/withMetricsContext';
 import HelpTooltip from '../../../components/controls/HelpTooltip';
 import ListFooter from '../../../components/controls/ListFooter';
 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, fetchMetrics } from '../../../store/rootActions';
-import { getMetrics } from '../../../store/rootReducer';
+import { fetchBranchStatus } from '../../../store/rootActions';
 import { BranchLike } from '../../../types/branch-like';
 import { isPortfolioLike } from '../../../types/component';
 import { Breadcrumb, Component, ComponentMeasure, Dict, Issue, Metric } from '../../../types/types';
@@ -51,13 +52,8 @@ import Components from './Components';
 import Search from './Search';
 import SourceViewerWrapper from './SourceViewerWrapper';
 
-interface StateToProps {
-  metrics: Dict<Metric>;
-}
-
 interface DispatchToProps {
   fetchBranchStatus: (branchLike: BranchLike, projectKey: string) => Promise<void>;
-  fetchMetrics: () => void;
 }
 
 interface OwnProps {
@@ -65,9 +61,10 @@ interface OwnProps {
   component: Component;
   location: Pick<Location, 'query'>;
   router: Pick<InjectedRouter, 'push'>;
+  metrics: Dict<Metric>;
 }
 
-type Props = StateToProps & DispatchToProps & OwnProps;
+type Props = DispatchToProps & OwnProps;
 
 interface State {
   baseComponent?: ComponentMeasure;
@@ -100,7 +97,6 @@ export class CodeApp extends React.Component<Props, State> {
 
   componentDidMount() {
     this.mounted = true;
-    this.props.fetchMetrics();
     this.handleComponentChange();
   }
 
@@ -408,16 +404,8 @@ const AlertContent = styled.div`
   align-items: center;
 `;
 
-const mapStateToProps = (state: any): StateToProps => ({
-  metrics: getMetrics(state)
-});
-
 const mapDispatchToProps: DispatchToProps = {
-  fetchBranchStatus: fetchBranchStatus as any,
-  fetchMetrics
+  fetchBranchStatus: fetchBranchStatus as any
 };
 
-export default connect<StateToProps, DispatchToProps, Props>(
-  mapStateToProps,
-  mapDispatchToProps
-)(CodeApp);
+export default connect(null, mapDispatchToProps)(withMetricsContext(CodeApp));
index d0aac1b4f81c4554162d756b253e635254f3592f..4e47f27956aa855c9c9392b22b60bfa420aacb06 100644 (file)
@@ -40,9 +40,9 @@ const BASE_COLUMN_COUNT = 4;
 
 export class Components extends React.PureComponent<Props> {
   render() {
-    const { baseComponent, branchLike, components, rootComponent, selected } = this.props;
+    const { baseComponent, branchLike, components, rootComponent, selected, metrics } = this.props;
 
-    const colSpan = this.props.metrics.length + BASE_COLUMN_COUNT;
+    const colSpan = metrics.length + BASE_COLUMN_COUNT;
     const canBePinned = baseComponent && !['APP', 'VW', 'SVW'].includes(baseComponent.qualifier);
 
     return (
@@ -51,7 +51,7 @@ export class Components extends React.PureComponent<Props> {
           <ComponentsHeader
             baseComponent={baseComponent}
             canBePinned={canBePinned}
-            metrics={this.props.metrics.map(metric => metric.key)}
+            metrics={metrics.map(metric => metric.key)}
             rootComponent={rootComponent}
           />
         )}
@@ -65,7 +65,7 @@ export class Components extends React.PureComponent<Props> {
                 hasBaseComponent={false}
                 isBaseComponent={true}
                 key={baseComponent.key}
-                metrics={this.props.metrics}
+                metrics={metrics}
                 rootComponent={rootComponent}
               />
               <tr className="blank">
index e920184783a4cadf72c175478eaf96aad1fd58e0..e74d049dd86b6bce9e74071d66661dd025450578 100644 (file)
@@ -268,7 +268,6 @@ function shallowRender(props: Partial<CodeApp['props']> = {}) {
         qualifier: 'FOO'
       }}
       fetchBranchStatus={jest.fn()}
-      fetchMetrics={jest.fn()}
       location={{ query: { branch: 'b', id: 'foo', line: '7' } }}
       metrics={METRICS}
       router={mockRouter()}
index 3962281b4b20dddfa8351ff2043767d9df34c9b3..9e22b6392dddba3cb13505448d3a3686c1247742 100644 (file)
 import { mockMetric } from '../../../helpers/testMocks';
 import { getLocalizedMetricNameNoDiffMetric } from '../utils';
 
-jest.mock('../../../store/rootReducer', () => ({
-  getMetricByKey: (store: any, key: string) => store[key]
-}));
-
-jest.mock('../../../app/utils/getStore', () => () => ({
-  getState: () => ({
-    bugs: mockMetric({ key: 'bugs', name: 'Bugs' }),
-    existing_metric: mockMetric(),
-    new_maintainability_rating: mockMetric(),
-    sqale_rating: mockMetric({ key: 'sqale_rating', name: 'Maintainability Rating' })
-  })
-}));
+const METRICS = {
+  bugs: mockMetric({ key: 'bugs', name: 'Bugs' }),
+  existing_metric: mockMetric(),
+  new_maintainability_rating: mockMetric(),
+  sqale_rating: mockMetric({ key: 'sqale_rating', name: 'Maintainability Rating' })
+};
 
 describe('getLocalizedMetricNameNoDiffMetric', () => {
   it('should return the correct corresponding metric', () => {
-    expect(getLocalizedMetricNameNoDiffMetric(mockMetric())).toBe('Coverage');
-    expect(getLocalizedMetricNameNoDiffMetric(mockMetric({ key: 'new_bugs' }))).toBe('Bugs');
+    expect(getLocalizedMetricNameNoDiffMetric(mockMetric(), {})).toBe('Coverage');
+    expect(getLocalizedMetricNameNoDiffMetric(mockMetric({ key: 'new_bugs' }), METRICS)).toBe(
+      'Bugs'
+    );
     expect(
       getLocalizedMetricNameNoDiffMetric(
-        mockMetric({ key: 'new_custom_metric', name: 'Custom Metric on New Code' })
+        mockMetric({ key: 'new_custom_metric', name: 'Custom Metric on New Code' }),
+        METRICS
       )
     ).toBe('Custom Metric on New Code');
     expect(
-      getLocalizedMetricNameNoDiffMetric(mockMetric({ key: 'new_maintainability_rating' }))
+      getLocalizedMetricNameNoDiffMetric(mockMetric({ key: 'new_maintainability_rating' }), METRICS)
     ).toBe('Maintainability Rating');
   });
 });
index 7968f0d2929159222cccfb63a47713a1041a88a2..4f1b4203e15cf0d5695c4b7fe585c91a0a3fdca0 100644 (file)
 import classNames from 'classnames';
 import * as React from 'react';
 import { deleteCondition } from '../../../api/quality-gates';
+import withMetricsContext from '../../../app/components/metrics/withMetricsContext';
 import { DeleteButton, EditButton } from '../../../components/controls/buttons';
 import ConfirmModal from '../../../components/controls/ConfirmModal';
 import { getLocalizedMetricName, translate, translateWithParameters } from '../../../helpers/l10n';
 import { formatMeasure } from '../../../helpers/measures';
-import { Condition as ConditionType, Metric, QualityGate } from '../../../types/types';
+import { Condition as ConditionType, Dict, Metric, QualityGate } from '../../../types/types';
 import { getLocalizedMetricNameNoDiffMetric } from '../utils';
 import ConditionModal from './ConditionModal';
 
@@ -36,6 +37,7 @@ interface Props {
   onSaveCondition: (newCondition: ConditionType, oldCondition: ConditionType) => void;
   qualityGate: QualityGate;
   updated?: boolean;
+  metrics: Dict<Metric>;
 }
 
 interface State {
@@ -43,7 +45,7 @@ interface State {
   modal: boolean;
 }
 
-export default class Condition extends React.PureComponent<Props, State> {
+export class ConditionComponent extends React.PureComponent<Props, State> {
   constructor(props: Props) {
     super(props);
     this.state = {
@@ -88,11 +90,11 @@ export default class Condition extends React.PureComponent<Props, State> {
   }
 
   render() {
-    const { condition, canEdit, metric, qualityGate, updated } = this.props;
+    const { condition, canEdit, metric, qualityGate, updated, metrics } = this.props;
     return (
       <tr className={classNames({ highlighted: updated })}>
         <td className="text-middle">
-          {getLocalizedMetricNameNoDiffMetric(metric)}
+          {getLocalizedMetricNameNoDiffMetric(metric, metrics)}
           {metric.hidden && (
             <span className="text-danger little-spacer-left">{translate('deprecated')}</span>
           )}
@@ -146,3 +148,5 @@ export default class Condition extends React.PureComponent<Props, State> {
     );
   }
 }
+
+export default withMetricsContext(ConditionComponent);
index aa301459f33e0b41e9e0ecc8e28a8d114415a771..03e45767dec227c636996203505cb05f2b82dd97 100644 (file)
@@ -146,8 +146,8 @@ export default class ConditionModal extends React.PureComponent<Props, State> {
           {metrics && (
             <MetricSelect
               metric={metric}
-              metrics={metrics.filter(metric =>
-                scope === 'new' ? isDiffMetric(metric.key) : !isDiffMetric(metric.key)
+              metricsArray={metrics.filter(m =>
+                scope === 'new' ? isDiffMetric(m.key) : !isDiffMetric(m.key)
               )}
               onMetricChange={this.handleMetricChange}
             />
index 2096d1b7f802577ee497877b9bab4812f8ce839b..d7ae7b87b7d32baba7457cc149b9e2ee047a90a2 100644 (file)
@@ -19,6 +19,7 @@
  */
 import { differenceWith, map, sortBy, uniqBy } from 'lodash';
 import * as React from 'react';
+import withMetricsContext from '../../../app/components/metrics/withMetricsContext';
 import DocumentationTooltip from '../../../components/common/DocumentationTooltip';
 import { Button } from '../../../components/controls/buttons';
 import ModalButton from '../../../components/controls/ModalButton';
@@ -227,4 +228,4 @@ export class Conditions extends React.PureComponent<Props> {
   }
 }
 
-export default withAppState(Conditions);
+export default withAppState(withMetricsContext(Conditions));
index 94e2d197a5c6d2eceee78383fede8f93fc255042..b63b1eed83d7b51375dfbc31d8db0864b046d170 100644 (file)
  */
 import * as React from 'react';
 import { Helmet } from 'react-helmet-async';
-import { connect } from 'react-redux';
 import { fetchQualityGate } from '../../../api/quality-gates';
 import addGlobalSuccessMessage from '../../../app/utils/addGlobalSuccessMessage';
 import DeferredSpinner from '../../../components/ui/DeferredSpinner';
 import { translate } from '../../../helpers/l10n';
-import { fetchMetrics } from '../../../store/rootActions';
-import { getMetrics, Store } from '../../../store/rootReducer';
-import { Condition, Dict, Metric, QualityGate } from '../../../types/types';
+import { Condition, QualityGate } from '../../../types/types';
 import { addCondition, checkIfDefault, deleteCondition, replaceCondition } from '../utils';
 import DetailsContent from './DetailsContent';
 import DetailsHeader from './DetailsHeader';
 
-interface OwnProps {
+interface Props {
   id: string;
   onSetDefault: (qualityGate: QualityGate) => void;
   qualityGates: QualityGate[];
   refreshQualityGates: () => Promise<void>;
 }
 
-interface StateToProps {
-  metrics: Dict<Metric>;
-}
-
-interface DispatchToProps {
-  fetchMetrics: () => void;
-}
-
-type Props = StateToProps & DispatchToProps & OwnProps;
-
 interface State {
   loading: boolean;
   qualityGate?: QualityGate;
   updatedConditionId?: number;
 }
 
-export class Details extends React.PureComponent<Props, State> {
+export default class Details extends React.PureComponent<Props, State> {
   mounted = false;
   state: State = { loading: true };
 
   componentDidMount() {
     this.mounted = true;
-    this.props.fetchMetrics();
     this.fetchDetails();
   }
 
@@ -145,7 +131,7 @@ export class Details extends React.PureComponent<Props, State> {
   };
 
   render() {
-    const { metrics, refreshQualityGates } = this.props;
+    const { refreshQualityGates } = this.props;
     const { loading, qualityGate, updatedConditionId } = this.state;
 
     return (
@@ -162,7 +148,6 @@ export class Details extends React.PureComponent<Props, State> {
               />
               <DetailsContent
                 isDefault={checkIfDefault(qualityGate, this.props.qualityGates)}
-                metrics={metrics}
                 onAddCondition={this.handleAddCondition}
                 onRemoveCondition={this.handleRemoveCondition}
                 onSaveCondition={this.handleSaveCondition}
@@ -176,11 +161,3 @@ export class Details extends React.PureComponent<Props, State> {
     );
   }
 }
-
-const mapDispatchToProps: DispatchToProps = { fetchMetrics };
-
-const mapStateToProps = (state: Store): StateToProps => ({
-  metrics: getMetrics(state)
-});
-
-export default connect(mapStateToProps, mapDispatchToProps)(Details);
index 097be9c814a1cd0ca8473d4d755092ee933a3fc0..6542c48f1aea61287b543c99f29f1a8a583074cc 100644 (file)
@@ -21,14 +21,13 @@ import * as React from 'react';
 import HelpTooltip from '../../../components/controls/HelpTooltip';
 import { Alert } from '../../../components/ui/Alert';
 import { translate } from '../../../helpers/l10n';
-import { Condition, Dict, Metric, QualityGate } from '../../../types/types';
+import { Condition, QualityGate } from '../../../types/types';
 import Conditions from './Conditions';
 import Projects from './Projects';
 import QualityGatePermissions from './QualityGatePermissions';
 
 export interface DetailsContentProps {
   isDefault?: boolean;
-  metrics: Dict<Metric>;
   onAddCondition: (condition: Condition) => void;
   onRemoveCondition: (Condition: Condition) => void;
   onSaveCondition: (newCondition: Condition, oldCondition: Condition) => void;
@@ -37,7 +36,7 @@ export interface DetailsContentProps {
 }
 
 export function DetailsContent(props: DetailsContentProps) {
-  const { isDefault, metrics, qualityGate, updatedConditionId } = props;
+  const { isDefault, qualityGate, updatedConditionId } = props;
   const conditions = qualityGate.conditions || [];
   const actions = qualityGate.actions || {};
 
@@ -52,7 +51,6 @@ export function DetailsContent(props: DetailsContentProps) {
       <Conditions
         canEdit={Boolean(actions.manageConditions)}
         conditions={conditions}
-        metrics={metrics}
         onAddCondition={props.onAddCondition}
         onRemoveCondition={props.onRemoveCondition}
         onSaveCondition={props.onSaveCondition}
index ac44ca518903818d57fcf8806967b93b474bc005..ce6879efa169cf1d50372a5a0f9ad877eb8a40c6 100644 (file)
  */
 import { sortBy } from 'lodash';
 import * as React from 'react';
+import withMetricsContext from '../../../app/components/metrics/withMetricsContext';
 import SelectLegacy from '../../../components/controls/SelectLegacy';
 import { getLocalizedMetricDomain, translate } from '../../../helpers/l10n';
-import { Metric } from '../../../types/types';
+import { Dict, Metric } from '../../../types/types';
 import { getLocalizedMetricNameNoDiffMetric } from '../utils';
 
 interface Props {
   metric?: Metric;
-  metrics: Metric[];
+  metricsArray: Metric[];
+  metrics: Dict<Metric>;
   onMetricChange: (metric: Metric) => void;
 }
 
@@ -36,10 +38,10 @@ interface Option {
   value: string;
 }
 
-export default class MetricSelect extends React.PureComponent<Props> {
+export class MetricSelectComponent extends React.PureComponent<Props> {
   handleChange = (option: Option | null) => {
     if (option) {
-      const { metrics } = this.props;
+      const { metricsArray: metrics } = this.props;
       const selectedMetric = metrics.find(metric => metric.key === option.value);
       if (selectedMetric) {
         this.props.onMetricChange(selectedMetric);
@@ -48,13 +50,13 @@ export default class MetricSelect extends React.PureComponent<Props> {
   };
 
   render() {
-    const { metric, metrics } = this.props;
+    const { metric, metricsArray, metrics } = this.props;
 
     const options: Array<Option & { domain?: string }> = sortBy(
-      metrics.map(metric => ({
-        value: metric.key,
-        label: getLocalizedMetricNameNoDiffMetric(metric),
-        domain: metric.domain
+      metricsArray.map(m => ({
+        value: m.key,
+        label: getLocalizedMetricNameNoDiffMetric(m, metrics),
+        domain: m.domain
       })),
       'domain'
     );
@@ -85,3 +87,5 @@ export default class MetricSelect extends React.PureComponent<Props> {
     );
   }
 }
+
+export default withMetricsContext(MetricSelectComponent);
index c113410a3383796392028caa618bbeb18d14fec5..94805c615da7fc70c57e2e7ace87a2d20cd68a48 100644 (file)
@@ -21,7 +21,7 @@ import { shallow } from 'enzyme';
 import * as React from 'react';
 import { mockQualityGate } from '../../../../helpers/mocks/quality-gates';
 import { mockCondition, mockMetric } from '../../../../helpers/testMocks';
-import Condition from '../Condition';
+import { ConditionComponent } from '../Condition';
 
 it('should render correctly', () => {
   expect(shallowRender()).toMatchSnapshot('default');
@@ -41,12 +41,13 @@ it('should render the delete modal correctly', () => {
   expect(wrapper).toMatchSnapshot();
 });
 
-function shallowRender(props: Partial<Condition['props']> = {}) {
-  return shallow<Condition>(
-    <Condition
+function shallowRender(props: Partial<ConditionComponent['props']> = {}) {
+  return shallow<ConditionComponent>(
+    <ConditionComponent
       canEdit={false}
       condition={mockCondition()}
       metric={mockMetric()}
+      metrics={{}}
       onRemoveCondition={jest.fn()}
       onSaveCondition={jest.fn()}
       qualityGate={mockQualityGate()}
index a9e504131b4a13643324d6ce26e92ffea2478eca..41e1ca0fd44339ec80ded5fe37f08742a9a46f8d 100644 (file)
@@ -39,10 +39,10 @@ it('should correctly handle a metric selection', () => {
   const wrapper = shallowRender();
   const metric = mockMetric();
 
-  expect(wrapper.find('MetricSelect').prop('metric')).toBeUndefined();
+  expect(wrapper.find('withMetricsContext(MetricSelectComponent)').prop('metric')).toBeUndefined();
 
   wrapper.instance().handleMetricChange(metric);
-  expect(wrapper.find('MetricSelect').prop('metric')).toEqual(metric);
+  expect(wrapper.find('withMetricsContext(MetricSelectComponent)').prop('metric')).toEqual(metric);
 });
 
 it('should correctly switch scope', () => {
index 463de2c1eda20b25bcc93540e6b0ff7d204bdb0c..960f7379dd1944b6c3440bd5fab099efb15c63f0 100644 (file)
@@ -24,7 +24,7 @@ import { mockQualityGate } from '../../../../helpers/mocks/quality-gates';
 import { mockCondition } from '../../../../helpers/testMocks';
 import { waitAndUpdate } from '../../../../helpers/testUtils';
 import { addCondition, deleteCondition, replaceCondition } from '../../utils';
-import { Details } from '../Details';
+import Details from '../Details';
 
 jest.mock('../../../../api/quality-gates', () => {
   const { mockQualityGate } = jest.requireActual('../../../../helpers/mocks/quality-gates');
@@ -60,12 +60,6 @@ it('should refresh if the QG id changes', async () => {
   expect(fetchQualityGate).toBeCalledWith({ id: '2' });
 });
 
-it('should fetch metrics on mount', () => {
-  const fetchMetrics = jest.fn();
-  shallowRender({ fetchMetrics });
-  expect(fetchMetrics).toBeCalled();
-});
-
 it('should correctly add/replace/remove conditions', async () => {
   const qualityGate = mockQualityGate();
   (fetchQualityGate as jest.Mock).mockResolvedValue(qualityGate);
@@ -120,9 +114,7 @@ it('should correctly handle setting default', async () => {
 function shallowRender(props: Partial<Details['props']> = {}) {
   return shallow<Details>(
     <Details
-      fetchMetrics={jest.fn()}
       id="1"
-      metrics={{}}
       onSetDefault={jest.fn()}
       qualityGates={[mockQualityGate()]}
       refreshQualityGates={jest.fn()}
index 807fb1035ff0ba14968991e1e40b2bcd9740d963..9846a8f2b403f551f742a5d2413db292a717863a 100644 (file)
@@ -39,7 +39,6 @@ it('should render correctly', () => {
 function shallowRender(props: Partial<DetailsContentProps> = {}) {
   return shallow(
     <DetailsContent
-      metrics={{}}
       onAddCondition={jest.fn()}
       onRemoveCondition={jest.fn()}
       onSaveCondition={jest.fn()}
index 61b0ad596dccb4d322bcaed8b41c3b271ccdaf3c..2368e5cf37bb7221e5de6628770f7bb12bd007d7 100644 (file)
@@ -20,7 +20,7 @@
 import { shallow } from 'enzyme';
 import * as React from 'react';
 import { mockMetric } from '../../../../helpers/testMocks';
-import MetricSelect from '../MetricSelect';
+import { MetricSelectComponent } from '../MetricSelect';
 
 it('should render correctly', () => {
   expect(shallowRender()).toMatchSnapshot();
@@ -29,14 +29,19 @@ it('should render correctly', () => {
 it('should correctly handle change', () => {
   const onMetricChange = jest.fn();
   const metric = mockMetric();
-  const metrics = [mockMetric({ key: 'duplication' }), metric];
-  const wrapper = shallowRender({ metrics, onMetricChange });
+  const metricsArray = [mockMetric({ key: 'duplication' }), metric];
+  const wrapper = shallowRender({ metricsArray, onMetricChange });
   wrapper.instance().handleChange({ label: metric.name, value: metric.key });
   expect(onMetricChange).toBeCalledWith(metric);
 });
 
-function shallowRender(props: Partial<MetricSelect['props']> = {}) {
-  return shallow<MetricSelect>(
-    <MetricSelect metrics={[mockMetric()]} onMetricChange={jest.fn()} {...props} />
+function shallowRender(props: Partial<MetricSelectComponent['props']> = {}) {
+  return shallow<MetricSelectComponent>(
+    <MetricSelectComponent
+      metricsArray={[mockMetric()]}
+      metrics={{}}
+      onMetricChange={jest.fn()}
+      {...props}
+    />
   );
 }
index b4268071f9bcb2452b357176704aab39cadb639c..92a242c1ba7e1b00e344d6f76d0882449f91ed7a 100644 (file)
@@ -79,7 +79,7 @@ exports[`should render correctly: default gate 1`] = `
     >
       <Component />
     </ScreenPositionHelper>
-    <Connect(Details)
+    <Details
       id="2"
       onSetDefault={[Function]}
       qualityGates={
@@ -121,7 +121,7 @@ exports[`should render correctly: specific gate 1`] = `
     >
       <Component />
     </ScreenPositionHelper>
-    <Connect(Details)
+    <Details
       id="1"
       onSetDefault={[Function]}
       qualityGates={
index d634c60d729867d5b2768f4786972b900d9292e2..29e4136d15ef15c7375467aa24ba5631eafdc183 100644 (file)
@@ -44,8 +44,8 @@ exports[`should correctly switch scope 1`] = `
     >
       quality_gates.conditions.fails_when
     </label>
-    <MetricSelect
-      metrics={
+    <withMetricsContext(MetricSelectComponent)
+      metricsArray={
         Array [
           Object {
             "id": "new_coverage",
@@ -111,8 +111,8 @@ exports[`should correctly switch scope 2`] = `
     >
       quality_gates.conditions.fails_when
     </label>
-    <MetricSelect
-      metrics={
+    <withMetricsContext(MetricSelectComponent)
+      metricsArray={
         Array [
           Object {
             "id": "coverage",
@@ -178,8 +178,8 @@ exports[`should correctly switch scope 3`] = `
     >
       quality_gates.conditions.fails_when
     </label>
-    <MetricSelect
-      metrics={
+    <withMetricsContext(MetricSelectComponent)
+      metricsArray={
         Array [
           Object {
             "id": "new_coverage",
@@ -245,8 +245,8 @@ exports[`should render correctly 1`] = `
     >
       quality_gates.conditions.fails_when
     </label>
-    <MetricSelect
-      metrics={
+    <withMetricsContext(MetricSelectComponent)
+      metricsArray={
         Array [
           Object {
             "id": "new_coverage",
@@ -285,7 +285,7 @@ exports[`should render correctly 2`] = `
     >
       quality_gates.conditions.fails_when
     </label>
-    <MetricSelect
+    <withMetricsContext(MetricSelectComponent)
       metric={
         Object {
           "id": "coverage",
@@ -294,7 +294,7 @@ exports[`should render correctly 2`] = `
           "type": "PERCENT",
         }
       }
-      metrics={
+      metricsArray={
         Array [
           Object {
             "id": "new_coverage",
index cb7ff83d304716c7936cb0c41d86a47c69f0adba..66ea9de396058290fd000f738dc1bf1aba03f90b 100644 (file)
@@ -63,7 +63,7 @@ exports[`should render correctly 1`] = `
         </tr>
       </thead>
       <tbody>
-        <Condition
+        <withMetricsContext(ConditionComponent)
           canEdit={false}
           condition={
             Object {
@@ -92,7 +92,7 @@ exports[`should render correctly 1`] = `
           }
           updated={false}
         />
-        <Condition
+        <withMetricsContext(ConditionComponent)
           canEdit={false}
           condition={
             Object {
@@ -221,7 +221,7 @@ exports[`should render correctly with an updated condition 1`] = `
         </tr>
       </thead>
       <tbody>
-        <Condition
+        <withMetricsContext(ConditionComponent)
           canEdit={false}
           condition={
             Object {
@@ -250,7 +250,7 @@ exports[`should render correctly with an updated condition 1`] = `
           }
           updated={true}
         />
-        <Condition
+        <withMetricsContext(ConditionComponent)
           canEdit={false}
           condition={
             Object {
@@ -348,7 +348,7 @@ exports[`should render correctly with new code conditions 1`] = `
         </tr>
       </thead>
       <tbody>
-        <Condition
+        <withMetricsContext(ConditionComponent)
           canEdit={false}
           condition={
             Object {
@@ -377,7 +377,7 @@ exports[`should render correctly with new code conditions 1`] = `
           }
           updated={false}
         />
-        <Condition
+        <withMetricsContext(ConditionComponent)
           canEdit={false}
           condition={
             Object {
@@ -449,7 +449,7 @@ exports[`should render correctly with new code conditions 1`] = `
         </tr>
       </thead>
       <tbody>
-        <Condition
+        <withMetricsContext(ConditionComponent)
           canEdit={false}
           condition={
             Object {
@@ -478,7 +478,7 @@ exports[`should render correctly with new code conditions 1`] = `
           }
           updated={false}
         />
-        <Condition
+        <withMetricsContext(ConditionComponent)
           canEdit={false}
           condition={
             Object {
@@ -595,7 +595,7 @@ exports[`should render the add conditions button and modal 1`] = `
         </tr>
       </thead>
       <tbody>
-        <Condition
+        <withMetricsContext(ConditionComponent)
           canEdit={true}
           condition={
             Object {
@@ -624,7 +624,7 @@ exports[`should render the add conditions button and modal 1`] = `
           }
           updated={false}
         />
-        <Condition
+        <withMetricsContext(ConditionComponent)
           canEdit={true}
           condition={
             Object {
index ba5a76662eb205c14556317ad96dc323ace583ea..b17fb93a8139f1e094b302e78ed635bd0206a3af 100644 (file)
@@ -26,7 +26,6 @@ exports[`should render correctly: loaded 1`] = `
     />
     <Memo(DetailsContent)
       isDefault={false}
-      metrics={Object {}}
       onAddCondition={[Function]}
       onRemoveCondition={[Function]}
       onSaveCondition={[Function]}
index c1f3da95dad56ab1e3489775a37c7b250b447d3c..456f401e581d1701956e0a808bcc400cb1f06807 100644 (file)
@@ -4,10 +4,9 @@ exports[`should render correctly: Admin 1`] = `
 <div
   className="layout-page-main-inner"
 >
-  <Connect(withAppState(Conditions))
+  <Connect(withAppState(withMetricsContext(Conditions)))
     canEdit={false}
     conditions={Array []}
-    metrics={Object {}}
     onAddCondition={[MockFunction]}
     onRemoveCondition={[MockFunction]}
     onSaveCondition={[MockFunction]}
@@ -81,7 +80,7 @@ exports[`should render correctly: is default 1`] = `
 <div
   className="layout-page-main-inner"
 >
-  <Connect(withAppState(Conditions))
+  <Connect(withAppState(withMetricsContext(Conditions)))
     canEdit={false}
     conditions={
       Array [
@@ -93,7 +92,6 @@ exports[`should render correctly: is default 1`] = `
         },
       ]
     }
-    metrics={Object {}}
     onAddCondition={[MockFunction]}
     onRemoveCondition={[MockFunction]}
     onSaveCondition={[MockFunction]}
@@ -152,10 +150,9 @@ exports[`should render correctly: is default, no conditions 1`] = `
   >
     quality_gates.is_default_no_conditions
   </Alert>
-  <Connect(withAppState(Conditions))
+  <Connect(withAppState(withMetricsContext(Conditions)))
     canEdit={false}
     conditions={Array []}
-    metrics={Object {}}
     onAddCondition={[MockFunction]}
     onRemoveCondition={[MockFunction]}
     onSaveCondition={[MockFunction]}
@@ -201,7 +198,7 @@ exports[`should render correctly: is not default 1`] = `
 <div
   className="layout-page-main-inner"
 >
-  <Connect(withAppState(Conditions))
+  <Connect(withAppState(withMetricsContext(Conditions)))
     canEdit={false}
     conditions={
       Array [
@@ -213,7 +210,6 @@ exports[`should render correctly: is not default 1`] = `
         },
       ]
     }
-    metrics={Object {}}
     onAddCondition={[MockFunction]}
     onRemoveCondition={[MockFunction]}
     onSaveCondition={[MockFunction]}
index 2cb97805e3e464a3ee8f38a79e3209563f6d54e4..9da8421c8618924cb4d2f5b9c8f1e4dea9bbd768 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import getStore from '../../app/utils/getStore';
 import { getLocalizedMetricName } from '../../helpers/l10n';
 import { isDiffMetric } from '../../helpers/measures';
-import { getMetricByKey } from '../../store/rootReducer';
-import { Condition, Metric, QualityGate } from '../../types/types';
+import { Condition, Dict, Metric, QualityGate } from '../../types/types';
 
 export function checkIfDefault(qualityGate: QualityGate, list: QualityGate[]): boolean {
   const finding = list.find(candidate => candidate.id === qualityGate.id);
@@ -58,27 +56,24 @@ export function getPossibleOperators(metric: Metric) {
     return 'LT';
   } else if (metric.direction === -1) {
     return 'GT';
-  } else {
-    return ['LT', 'GT'];
   }
+  return ['LT', 'GT'];
 }
 
-export function metricKeyExists(key: string) {
-  return getMetricByKey(getStore().getState(), key) !== undefined;
+function metricKeyExists(key: string, metrics: Dict<Metric>) {
+  return metrics && metrics[key] !== undefined;
 }
 
-function getNoDiffMetric(metric: Metric) {
-  const store = getStore().getState();
+function getNoDiffMetric(metric: Metric, metrics: Dict<Metric>) {
   const regularMetricKey = metric.key.replace(/^new_/, '');
-  if (isDiffMetric(metric.key) && metricKeyExists(regularMetricKey)) {
-    return getMetricByKey(store, regularMetricKey);
+  if (isDiffMetric(metric.key) && metricKeyExists(regularMetricKey, metrics)) {
+    return metrics[regularMetricKey];
   } else if (metric.key === 'new_maintainability_rating') {
-    return getMetricByKey(store, 'sqale_rating') || metric;
-  } else {
-    return metric;
+    return metrics['sqale_rating'] || metric;
   }
+  return metric;
 }
 
-export function getLocalizedMetricNameNoDiffMetric(metric: Metric) {
-  return getLocalizedMetricName(getNoDiffMetric(metric));
+export function getLocalizedMetricNameNoDiffMetric(metric: Metric, metrics: Dict<Metric>) {
+  return getLocalizedMetricName(getNoDiffMetric(metric, metrics));
 }
diff --git a/server/sonar-web/src/main/js/store/metrics.ts b/server/sonar-web/src/main/js/store/metrics.ts
deleted file mode 100644 (file)
index 5331762..0000000
+++ /dev/null
@@ -1,60 +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 { keyBy } from 'lodash';
-import { combineReducers } from 'redux';
-import { Dict, Metric } from '../types/types';
-import { ActionType } from './utils/actions';
-
-export function receiveMetrics(metrics: Metric[]) {
-  return { type: 'RECEIVE_METRICS', metrics };
-}
-
-type Action = ActionType<typeof receiveMetrics, 'RECEIVE_METRICS'>;
-
-export type State = { byKey: Dict<Metric>; keys: string[] };
-
-const byKey = (state: State['byKey'] = {}, action: Action) => {
-  if (action.type === 'RECEIVE_METRICS') {
-    return keyBy(action.metrics, 'key');
-  }
-  return state;
-};
-
-const keys = (state: State['keys'] = [], action: Action) => {
-  if (action.type === 'RECEIVE_METRICS') {
-    return action.metrics.map(f => f.key);
-  }
-
-  return state;
-};
-
-export default combineReducers({ byKey, keys });
-
-export function getMetrics(state: State) {
-  return state.byKey;
-}
-
-export function getMetricsKey(state: State) {
-  return state.keys;
-}
-
-export function getMetricByKey(state: State, key: string) {
-  return state.byKey[key];
-}
index af0e6465e7df655f0f889281949da5b1bb1e8d7c..0b8424e206d855ee6f48cf1021304b236de0f3a3 100644 (file)
@@ -20,7 +20,6 @@
 import { InjectedRouter } from 'react-router';
 import { Dispatch } from 'redux';
 import * as auth from '../api/auth';
-import { getAllMetrics } from '../api/metrics';
 import { getQualityGateProjectStatus } from '../api/quality-gates';
 import { getBranchLikeQuery } from '../helpers/branch-like';
 import { extractStatusConditionsFromProjectStatus } from '../helpers/qualityGates';
@@ -29,18 +28,6 @@ import { Status } from '../types/types';
 import { requireAuthorization as requireAuthorizationAction } from './appState';
 import { registerBranchStatusAction } from './branches';
 import { addGlobalErrorMessage } from './globalMessages';
-import { receiveMetrics } from './metrics';
-
-export function fetchMetrics() {
-  return (dispatch: Dispatch) => {
-    getAllMetrics().then(
-      metrics => dispatch(receiveMetrics(metrics)),
-      () => {
-        /* do nothing */
-      }
-    );
-  };
-}
 
 export function fetchBranchStatus(branchLike: BranchLike, projectKey: string) {
   return (dispatch: Dispatch<any>) => {
index 373266b0fe2d3362573da89da61a79c68329deef..84ae0d1ba3ed3acf2e1863491b0b944b063914f3 100644 (file)
@@ -24,15 +24,12 @@ import { AppState, CurrentUserSettingNames } from '../types/types';
 import appState from './appState';
 import branches, * as fromBranches from './branches';
 import globalMessages, * as fromGlobalMessages from './globalMessages';
-import metrics, * as fromMetrics from './metrics';
 import users, * as fromUsers from './users';
 
 export type Store = {
   appState: AppState;
   branches: fromBranches.State;
   globalMessages: fromGlobalMessages.State;
-
-  metrics: fromMetrics.State;
   users: fromUsers.State;
 
   // apps
@@ -43,7 +40,6 @@ export default combineReducers<Store>({
   appState,
   branches,
   globalMessages,
-  metrics,
   users,
 
   // apps
@@ -66,18 +62,6 @@ export function getCurrentUser(state: Store) {
   return fromUsers.getCurrentUser(state.users);
 }
 
-export function getMetrics(state: Store) {
-  return fromMetrics.getMetrics(state.metrics);
-}
-
-export function getMetricsKey(state: Store) {
-  return fromMetrics.getMetricsKey(state.metrics);
-}
-
-export function getMetricByKey(state: Store, key: string) {
-  return fromMetrics.getMetricByKey(state.metrics, key);
-}
-
 export function getGlobalSettingValue(state: Store, key: string) {
   return fromSettingsApp.getValue(state.settingsApp, key);
 }