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';
<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>
<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>
--- /dev/null
+/*
+ * 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>>({});
--- /dev/null
+/*
+ * 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>
+ );
+ }
+}
--- /dev/null
+/*
+ * 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>
+ );
+}
--- /dev/null
+/*
+ * 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 });
+});
--- /dev/null
+/*
+ * 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>
+ );
+ }
+ };
+}
onClose={[Function]}
top={120}
>
- <Connect(ProjectInformation)
+ <Connect(withMetricsContext(ProjectInformation))
component={
Object {
"breadcrumbs": Array [
onClose={[Function]}
top={120}
>
- <Connect(ProjectInformation)
+ <Connect(withMetricsContext(ProjectInformation))
component={
Object {
"breadcrumbs": Array [
onClose={[Function]}
top={120}
>
- <Connect(ProjectInformation)
+ <Connect(withMetricsContext(ProjectInformation))
component={
Object {
"breadcrumbs": Array [
onClose={[Function]}
top={120}
>
- <Connect(ProjectInformation)
+ <Connect(withMetricsContext(ProjectInformation))
component={
Object {
"breadcrumbs": Array [
onClose={[Function]}
top={120}
>
- <Connect(ProjectInformation)
+ <Connect(withMetricsContext(ProjectInformation))
component={
Object {
"breadcrumbs": Array [
onClose={[Function]}
top={120}
>
- <Connect(ProjectInformation)
+ <Connect(withMetricsContext(ProjectInformation))
component={
Object {
"breadcrumbs": Array [
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';
branchLike?: BranchLike;
component: Component;
currentUser: CurrentUser;
- fetchMetrics: () => void;
onComponentChange: (changes: {}) => void;
metrics: Dict<Metric>;
}
componentDidMount() {
this.mounted = true;
- this.props.fetchMetrics();
this.loadMeasures();
}
<InfoDrawerPage
displayed={page === ProjectInformationPages.badges}
onPageChange={this.setPage}>
- <ProjectBadges branchLike={branchLike} metrics={metrics} component={component} />
+ <ProjectBadges branchLike={branchLike} component={component} />
</InfoDrawerPage>
)}
{canConfigureNotifications && (
}
}
-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));
<ProjectInformation
component={mockComponent()}
currentUser={mockCurrentUser()}
- fetchMetrics={jest.fn()}
metrics={{
coverage: mockMetric()
}}
"tags": Array [],
}
}
- metrics={
- Object {
- "coverage": Object {
- "id": "coverage",
- "key": "coverage",
- "name": "Coverage",
- "type": "PERCENT",
- },
- }
- }
/>
</InfoDrawerPage>
</Fragment>
"tags": Array [],
}
}
- metrics={
- Object {
- "coverage": Object {
- "id": "coverage",
- "key": "coverage",
- "name": "Coverage",
- "type": "PERCENT",
- },
- }
- }
/>
</InfoDrawerPage>
<InfoDrawerPage
"tags": Array [],
}
}
- metrics={
- Object {
- "coverage": Object {
- "id": "coverage",
- "key": "coverage",
- "name": "Coverage",
- "type": "PERCENT",
- },
- }
- }
/>
</InfoDrawerPage>
</Fragment>
"visibility": "private",
}
}
- metrics={
- Object {
- "coverage": Object {
- "id": "coverage",
- "key": "coverage",
- "name": "Coverage",
- "type": "PERCENT",
- },
- }
- }
/>
</InfoDrawerPage>
</Fragment>
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 {
badgeMetrics: string[];
}
-export default class BadgeParams extends React.PureComponent<Props> {
+export class BadgeParams extends React.PureComponent<Props> {
mounted = false;
state: State = { badgeMetrics: [] };
/>
</>
);
- } else {
- return null;
}
+ return null;
};
render() {
);
}
}
+
+export default withMetricsContext(BadgeParams);
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';
interface Props {
branchLike?: BranchLike;
- metrics: Dict<Metric>;
component: Component;
}
</p>
<BadgeParams
className="big-spacer-bottom display-flex-column"
- metrics={this.props.metrics}
options={badgeOptions}
type={selectedType}
updateOptions={this.handleUpdateOptions}
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', () => ({
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';
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}
/>
>
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",
+/* eslint-disable no-console */
/*
* SonarQube
* Copyright (C) 2009-2022 SonarSource SA
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';
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 {
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;
componentDidMount() {
this.mounted = true;
- this.props.fetchMetrics();
this.handleComponentChange();
}
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));
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 (
<ComponentsHeader
baseComponent={baseComponent}
canBePinned={canBePinned}
- metrics={this.props.metrics.map(metric => metric.key)}
+ metrics={metrics.map(metric => metric.key)}
rootComponent={rootComponent}
/>
)}
hasBaseComponent={false}
isBaseComponent={true}
key={baseComponent.key}
- metrics={this.props.metrics}
+ metrics={metrics}
rootComponent={rootComponent}
/>
<tr className="blank">
qualifier: 'FOO'
}}
fetchBranchStatus={jest.fn()}
- fetchMetrics={jest.fn()}
location={{ query: { branch: 'b', id: 'foo', line: '7' } }}
metrics={METRICS}
router={mockRouter()}
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');
});
});
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';
onSaveCondition: (newCondition: ConditionType, oldCondition: ConditionType) => void;
qualityGate: QualityGate;
updated?: boolean;
+ metrics: Dict<Metric>;
}
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 = {
}
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>
)}
);
}
}
+
+export default withMetricsContext(ConditionComponent);
{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}
/>
*/
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';
}
}
-export default withAppState(Conditions);
+export default withAppState(withMetricsContext(Conditions));
*/
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();
}
};
render() {
- const { metrics, refreshQualityGates } = this.props;
+ const { refreshQualityGates } = this.props;
const { loading, qualityGate, updatedConditionId } = this.state;
return (
/>
<DetailsContent
isDefault={checkIfDefault(qualityGate, this.props.qualityGates)}
- metrics={metrics}
onAddCondition={this.handleAddCondition}
onRemoveCondition={this.handleRemoveCondition}
onSaveCondition={this.handleSaveCondition}
);
}
}
-
-const mapDispatchToProps: DispatchToProps = { fetchMetrics };
-
-const mapStateToProps = (state: Store): StateToProps => ({
- metrics: getMetrics(state)
-});
-
-export default connect(mapStateToProps, mapDispatchToProps)(Details);
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;
}
export function DetailsContent(props: DetailsContentProps) {
- const { isDefault, metrics, qualityGate, updatedConditionId } = props;
+ const { isDefault, qualityGate, updatedConditionId } = props;
const conditions = qualityGate.conditions || [];
const actions = qualityGate.actions || {};
<Conditions
canEdit={Boolean(actions.manageConditions)}
conditions={conditions}
- metrics={metrics}
onAddCondition={props.onAddCondition}
onRemoveCondition={props.onRemoveCondition}
onSaveCondition={props.onSaveCondition}
*/
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;
}
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);
};
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'
);
);
}
}
+
+export default withMetricsContext(MetricSelectComponent);
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');
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()}
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', () => {
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');
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);
function shallowRender(props: Partial<Details['props']> = {}) {
return shallow<Details>(
<Details
- fetchMetrics={jest.fn()}
id="1"
- metrics={{}}
onSetDefault={jest.fn()}
qualityGates={[mockQualityGate()]}
refreshQualityGates={jest.fn()}
function shallowRender(props: Partial<DetailsContentProps> = {}) {
return shallow(
<DetailsContent
- metrics={{}}
onAddCondition={jest.fn()}
onRemoveCondition={jest.fn()}
onSaveCondition={jest.fn()}
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();
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}
+ />
);
}
>
<Component />
</ScreenPositionHelper>
- <Connect(Details)
+ <Details
id="2"
onSetDefault={[Function]}
qualityGates={
>
<Component />
</ScreenPositionHelper>
- <Connect(Details)
+ <Details
id="1"
onSetDefault={[Function]}
qualityGates={
>
quality_gates.conditions.fails_when
</label>
- <MetricSelect
- metrics={
+ <withMetricsContext(MetricSelectComponent)
+ metricsArray={
Array [
Object {
"id": "new_coverage",
>
quality_gates.conditions.fails_when
</label>
- <MetricSelect
- metrics={
+ <withMetricsContext(MetricSelectComponent)
+ metricsArray={
Array [
Object {
"id": "coverage",
>
quality_gates.conditions.fails_when
</label>
- <MetricSelect
- metrics={
+ <withMetricsContext(MetricSelectComponent)
+ metricsArray={
Array [
Object {
"id": "new_coverage",
>
quality_gates.conditions.fails_when
</label>
- <MetricSelect
- metrics={
+ <withMetricsContext(MetricSelectComponent)
+ metricsArray={
Array [
Object {
"id": "new_coverage",
>
quality_gates.conditions.fails_when
</label>
- <MetricSelect
+ <withMetricsContext(MetricSelectComponent)
metric={
Object {
"id": "coverage",
"type": "PERCENT",
}
}
- metrics={
+ metricsArray={
Array [
Object {
"id": "new_coverage",
</tr>
</thead>
<tbody>
- <Condition
+ <withMetricsContext(ConditionComponent)
canEdit={false}
condition={
Object {
}
updated={false}
/>
- <Condition
+ <withMetricsContext(ConditionComponent)
canEdit={false}
condition={
Object {
</tr>
</thead>
<tbody>
- <Condition
+ <withMetricsContext(ConditionComponent)
canEdit={false}
condition={
Object {
}
updated={true}
/>
- <Condition
+ <withMetricsContext(ConditionComponent)
canEdit={false}
condition={
Object {
</tr>
</thead>
<tbody>
- <Condition
+ <withMetricsContext(ConditionComponent)
canEdit={false}
condition={
Object {
}
updated={false}
/>
- <Condition
+ <withMetricsContext(ConditionComponent)
canEdit={false}
condition={
Object {
</tr>
</thead>
<tbody>
- <Condition
+ <withMetricsContext(ConditionComponent)
canEdit={false}
condition={
Object {
}
updated={false}
/>
- <Condition
+ <withMetricsContext(ConditionComponent)
canEdit={false}
condition={
Object {
</tr>
</thead>
<tbody>
- <Condition
+ <withMetricsContext(ConditionComponent)
canEdit={true}
condition={
Object {
}
updated={false}
/>
- <Condition
+ <withMetricsContext(ConditionComponent)
canEdit={true}
condition={
Object {
/>
<Memo(DetailsContent)
isDefault={false}
- metrics={Object {}}
onAddCondition={[Function]}
onRemoveCondition={[Function]}
onSaveCondition={[Function]}
<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]}
<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]}
>
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]}
<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]}
* 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);
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));
}
+++ /dev/null
-/*
- * 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];
-}
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';
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>) => {
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
appState,
branches,
globalMessages,
- metrics,
users,
// apps
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);
}