aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web
diff options
context:
space:
mode:
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>2018-01-18 09:10:50 +0100
committerGrégoire Aubert <gregoire.aubert@sonarsource.com>2018-01-25 15:16:50 +0100
commit57e81b11a8e94d3d21a24ff8b14aea368582b7f0 (patch)
treee6a00045d4251bdf1e7f32299475d232654c3aec /server/sonar-web
parentdf96a05cc325b2946c25ee8277f64638ed72288c (diff)
downloadsonarqube-57e81b11a8e94d3d21a24ff8b14aea368582b7f0.tar.gz
sonarqube-57e81b11a8e94d3d21a24ff8b14aea368582b7f0.zip
Fetch metrics with redux on project dashboard pages
Diffstat (limited to 'server/sonar-web')
-rw-r--r--server/sonar-web/src/main/js/app/components/App.tsx18
-rw-r--r--server/sonar-web/src/main/js/app/components/ComponentContainer.tsx15
-rw-r--r--server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx16
-rw-r--r--server/sonar-web/src/main/js/apps/overview/badges/BadgeParams.tsx42
-rw-r--r--server/sonar-web/src/main/js/apps/overview/badges/BadgesModal.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/overview/badges/__tests__/BadgeParams-test.tsx3
-rw-r--r--server/sonar-web/src/main/js/apps/overview/badges/__tests__/BadgesModal-test.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/overview/badges/__tests__/__snapshots__/BadgesModal-test.tsx.snap3
-rw-r--r--server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx32
-rw-r--r--server/sonar-web/src/main/js/apps/overview/events/AnalysesList.tsx25
-rw-r--r--server/sonar-web/src/main/js/apps/overview/meta/Meta.tsx128
-rw-r--r--server/sonar-web/src/main/js/apps/portfolio/components/Activity.tsx18
-rw-r--r--server/sonar-web/src/main/js/apps/portfolio/components/App.tsx31
-rw-r--r--server/sonar-web/src/main/js/apps/portfolio/components/__tests__/Activity-test.tsx11
-rw-r--r--server/sonar-web/src/main/js/apps/portfolio/components/__tests__/App-test.tsx10
-rw-r--r--server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Activity-test.tsx.snap8
-rw-r--r--server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/App-test.tsx.snap3
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/utils.js11
-rw-r--r--server/sonar-web/src/main/js/components/preview-graph/PreviewGraph.d.ts2
-rw-r--r--server/sonar-web/src/main/js/components/preview-graph/PreviewGraph.js4
20 files changed, 202 insertions, 185 deletions
diff --git a/server/sonar-web/src/main/js/app/components/App.tsx b/server/sonar-web/src/main/js/app/components/App.tsx
index 2b83e2a6468..0b7375c9c93 100644
--- a/server/sonar-web/src/main/js/app/components/App.tsx
+++ b/server/sonar-web/src/main/js/app/components/App.tsx
@@ -38,6 +38,7 @@ interface State {
canAdmin: boolean;
loading: boolean;
onSonarCloud: boolean;
+ organizationsEnabled: boolean;
}
class App extends React.PureComponent<Props, State> {
@@ -46,19 +47,27 @@ class App extends React.PureComponent<Props, State> {
static childContextTypes = {
branchesEnabled: PropTypes.bool.isRequired,
canAdmin: PropTypes.bool.isRequired,
- onSonarCloud: PropTypes.bool
+ onSonarCloud: PropTypes.bool,
+ organizationsEnabled: PropTypes.bool
};
constructor(props: Props) {
super(props);
- this.state = { branchesEnabled: false, canAdmin: false, loading: true, onSonarCloud: false };
+ this.state = {
+ branchesEnabled: false,
+ canAdmin: false,
+ loading: true,
+ onSonarCloud: false,
+ organizationsEnabled: false
+ };
}
getChildContext() {
return {
branchesEnabled: this.state.branchesEnabled,
canAdmin: this.state.canAdmin,
- onSonarCloud: this.state.onSonarCloud
+ onSonarCloud: this.state.onSonarCloud,
+ organizationsEnabled: this.state.organizationsEnabled
};
}
@@ -93,7 +102,8 @@ class App extends React.PureComponent<Props, State> {
canAdmin: appState.canAdmin,
onSonarCloud: Boolean(
appState.settings && appState.settings['sonar.sonarcloud.enabled'] === 'true'
- )
+ ),
+ organizationsEnabled: appState.organizationsEnabled
});
}
return appState;
diff --git a/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx b/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx
index de3102a3957..75d48a09dff 100644
--- a/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx
+++ b/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx
@@ -18,6 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
+import * as PropTypes from 'prop-types';
import { connect } from 'react-redux';
import ComponentContainerNotFound from './ComponentContainerNotFound';
import ComponentNav from './nav/component/ComponentNav';
@@ -28,7 +29,6 @@ import { Task, getTasksForComponent } from '../../api/ce';
import { getComponentData } from '../../api/components';
import { getComponentNavigation } from '../../api/nav';
import { fetchOrganizations } from '../../store/rootActions';
-import { areThereCustomOrganizations } from '../../store/rootReducer';
import { STATUSES } from '../../apps/background-tasks/constants';
interface Props {
@@ -37,7 +37,6 @@ interface Props {
location: {
query: { branch?: string; id: string };
};
- organizationsEnabled?: boolean;
}
interface State {
@@ -52,6 +51,10 @@ interface State {
export class ComponentContainer extends React.PureComponent<Props, State> {
mounted: boolean;
+ static contextTypes = {
+ organizationsEnabled: PropTypes.bool
+ };
+
constructor(props: Props) {
super(props);
this.state = { branches: [], loading: true };
@@ -98,7 +101,7 @@ export class ComponentContainer extends React.PureComponent<Props, State> {
([nav, data]) => {
const component = this.addQualifier({ ...nav, ...data });
- if (this.props.organizationsEnabled) {
+ if (this.context.organizationsEnabled) {
this.props.fetchOrganizations([component.organization]);
}
@@ -197,10 +200,6 @@ export class ComponentContainer extends React.PureComponent<Props, State> {
}
}
-const mapStateToProps = (state: any) => ({
- organizationsEnabled: areThereCustomOrganizations(state)
-});
-
const mapDispatchToProps = { fetchOrganizations };
-export default connect<any, any, any>(mapStateToProps, mapDispatchToProps)(ComponentContainer);
+export default connect<any, any, any>(null, mapDispatchToProps)(ComponentContainer);
diff --git a/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx b/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx
index bb6c69b6098..b66a2aff8da 100644
--- a/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx
+++ b/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx
@@ -132,12 +132,10 @@ it('loads organization', async () => {
const fetchOrganizations = jest.fn();
mount(
- <ComponentContainer
- fetchOrganizations={fetchOrganizations}
- location={{ query: { id: 'foo' } }}
- organizationsEnabled={true}>
+ <ComponentContainer fetchOrganizations={fetchOrganizations} location={{ query: { id: 'foo' } }}>
<Inner />
- </ComponentContainer>
+ </ComponentContainer>,
+ { context: { organizationsEnabled: true } }
);
await new Promise(setImmediate);
@@ -150,12 +148,10 @@ it('fetches status', async () => {
);
mount(
- <ComponentContainer
- fetchOrganizations={jest.fn()}
- location={{ query: { id: 'foo' } }}
- organizationsEnabled={true}>
+ <ComponentContainer fetchOrganizations={jest.fn()} location={{ query: { id: 'foo' } }}>
<Inner />
- </ComponentContainer>
+ </ComponentContainer>,
+ { context: { organizationsEnabled: true } }
);
await new Promise(setImmediate);
diff --git a/server/sonar-web/src/main/js/apps/overview/badges/BadgeParams.tsx b/server/sonar-web/src/main/js/apps/overview/badges/BadgeParams.tsx
index e0b1ca1d754..fc9a9db4554 100644
--- a/server/sonar-web/src/main/js/apps/overview/badges/BadgeParams.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/badges/BadgeParams.tsx
@@ -18,43 +18,30 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { connect } from 'react-redux';
import Select from '../../../components/controls/Select';
import { fetchWebApi } from '../../../api/web-api';
-import { Metric } from '../../../app/types';
import { BadgeColors, BadgeType, BadgeOptions } from './utils';
import { getLocalizedMetricName, translate } from '../../../helpers/l10n';
-import { fetchMetrics } from '../../../store/rootActions';
-import { getMetrics } from '../../../store/rootReducer';
-
-interface StateToProps {
- metrics: { [key: string]: Metric };
-}
-
-interface DispatchToProps {
- fetchMetrics: () => void;
-}
+import { Metric } from '../../../app/types';
-interface OwnProps {
+interface Props {
className?: string;
+ metrics: { [key: string]: Metric };
options: BadgeOptions;
type: BadgeType;
updateOptions: (options: Partial<BadgeOptions>) => void;
}
-type Props = StateToProps & DispatchToProps & OwnProps;
-
interface State {
badgeMetrics: string[];
}
-export class BadgeParams extends React.PureComponent<Props> {
+export default class BadgeParams extends React.PureComponent<Props> {
mounted: boolean;
state: State = { badgeMetrics: [] };
componentDidMount() {
this.mounted = true;
- this.props.fetchMetrics();
this.fetchBadgeMetrics();
}
@@ -84,16 +71,14 @@ export class BadgeParams extends React.PureComponent<Props> {
value: color
}));
- getMetricOptions = () => {
- const { metrics } = this.props;
- return this.state.badgeMetrics.map(key => {
- const metric = metrics[key];
+ getMetricOptions = () =>
+ this.state.badgeMetrics.map(key => {
+ const metric = this.props.metrics[key];
return {
value: key,
- label: metric && getLocalizedMetricName(metric)
+ label: metric ? getLocalizedMetricName(metric) : key
};
});
- };
handleColorChange = ({ value }: { value: BadgeColors }) =>
this.props.updateOptions({ color: value });
@@ -143,14 +128,3 @@ export class BadgeParams extends React.PureComponent<Props> {
}
}
}
-
-const mapDispatchToProps: DispatchToProps = { fetchMetrics };
-
-const mapStateToProps = (state: any): StateToProps => ({
- metrics: getMetrics(state)
-});
-
-export default connect<StateToProps, DispatchToProps, OwnProps>(
- mapStateToProps,
- mapDispatchToProps
-)(BadgeParams);
diff --git a/server/sonar-web/src/main/js/apps/overview/badges/BadgesModal.tsx b/server/sonar-web/src/main/js/apps/overview/badges/BadgesModal.tsx
index 7e4e0daf249..ae664043b7c 100644
--- a/server/sonar-web/src/main/js/apps/overview/badges/BadgesModal.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/badges/BadgesModal.tsx
@@ -22,12 +22,14 @@ import Modal from '../../../components/controls/Modal';
import BadgeButton from './BadgeButton';
import BadgeSnippet from './BadgeSnippet';
import BadgeParams from './BadgeParams';
-import { getBadgeUrl, BadgeType, BadgeOptions } from './utils';
+import { BadgeType, BadgeOptions, getBadgeUrl } from './utils';
+import { Metric } from '../../../app/types';
import { translate } from '../../../helpers/l10n';
import './styles.css';
interface Props {
branch?: string;
+ metrics: { [key: string]: Metric };
project: string;
}
@@ -90,6 +92,7 @@ export default class BadgesModal extends React.PureComponent<Props, State> {
</p>
<BadgeParams
className="big-spacer-bottom"
+ metrics={this.props.metrics}
options={badgeOptions}
type={selectedType}
updateOptions={this.handleUpdateOptions}
diff --git a/server/sonar-web/src/main/js/apps/overview/badges/__tests__/BadgeParams-test.tsx b/server/sonar-web/src/main/js/apps/overview/badges/__tests__/BadgeParams-test.tsx
index 5108e572be4..5e8936739b3 100644
--- a/server/sonar-web/src/main/js/apps/overview/badges/__tests__/BadgeParams-test.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/badges/__tests__/BadgeParams-test.tsx
@@ -19,7 +19,7 @@
*/
import * as React from 'react';
import { shallow } from 'enzyme';
-import { BadgeParams } from '../BadgeParams';
+import BadgeParams from '../BadgeParams';
import { BadgeType } from '../utils';
import { Metric } from '../../../../app/types';
@@ -62,7 +62,6 @@ it('should display measure badge params', () => {
function getWrapper(props = {}) {
return shallow(
<BadgeParams
- fetchMetrics={jest.fn()}
metrics={METRICS}
options={{ color: 'white', metric: 'alert_status' }}
type={BadgeType.marketing}
diff --git a/server/sonar-web/src/main/js/apps/overview/badges/__tests__/BadgesModal-test.tsx b/server/sonar-web/src/main/js/apps/overview/badges/__tests__/BadgesModal-test.tsx
index 3eee7bf7074..fc403fc3f17 100644
--- a/server/sonar-web/src/main/js/apps/overview/badges/__tests__/BadgesModal-test.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/badges/__tests__/BadgesModal-test.tsx
@@ -27,7 +27,7 @@ jest.mock('../../../../helpers/urls', () => ({
}));
it('should display the modal after click', () => {
- const wrapper = shallow(<BadgesModal branch="branch-6.6" project="foo" />);
+ const wrapper = shallow(<BadgesModal branch="branch-6.6" metrics={{}} project="foo" />);
expect(wrapper).toMatchSnapshot();
click(wrapper.find('button'));
expect(wrapper.find('Modal')).toMatchSnapshot();
diff --git a/server/sonar-web/src/main/js/apps/overview/badges/__tests__/__snapshots__/BadgesModal-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/badges/__tests__/__snapshots__/BadgesModal-test.tsx.snap
index 00e92664a30..f43e19a5715 100644
--- a/server/sonar-web/src/main/js/apps/overview/badges/__tests__/__snapshots__/BadgesModal-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/overview/badges/__tests__/__snapshots__/BadgesModal-test.tsx.snap
@@ -61,8 +61,9 @@ exports[`should display the modal after click 2`] = `
>
overview.badges.measure.description
</p>
- <Connect(BadgeParams)
+ <BadgeParams
className="big-spacer-bottom"
+ metrics={Object {}}
options={
Object {
"color": "white",
diff --git a/server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx b/server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx
index 07d22017edf..44285fe03da 100644
--- a/server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx
@@ -19,6 +19,7 @@
*/
import * as React from 'react';
import { uniq } from 'lodash';
+import { connect } from 'react-redux';
import QualityGate from '../qualityGate/QualityGate';
import ApplicationQualityGate from '../qualityGate/ApplicationQualityGate';
import BugsAndVulnerabilities from '../main/BugsAndVulnerabilities';
@@ -36,15 +37,27 @@ import { getCustomGraph, getGraph } from '../../../helpers/storage';
import { METRICS, HISTORY_METRICS_LIST } from '../utils';
import { DEFAULT_GRAPH, getDisplayedHistoryMetrics } from '../../projectActivity/utils';
import { getBranchName } from '../../../helpers/branches';
-import { Branch, Component } from '../../../app/types';
+import { fetchMetrics } from '../../../store/rootActions';
+import { getMetrics } from '../../../store/rootReducer';
+import { Branch, Component, Metric } from '../../../app/types';
import '../styles.css';
-interface Props {
+interface OwnProps {
branch?: Branch;
component: Component;
onComponentChange: (changes: {}) => void;
}
+interface StateToProps {
+ metrics: { [key: string]: Metric };
+}
+
+interface DispatchToProps {
+ fetchMetrics: () => void;
+}
+
+type Props = StateToProps & DispatchToProps & OwnProps;
+
interface State {
history?: History;
historyStartDate?: Date;
@@ -53,12 +66,13 @@ interface State {
periods?: Period[];
}
-export default class OverviewApp extends React.PureComponent<Props, State> {
+export class OverviewApp extends React.PureComponent<Props, State> {
mounted: boolean;
state: State = { loading: true, measures: [] };
componentDidMount() {
this.mounted = true;
+ this.props.fetchMetrics();
this.loadMeasures().then(this.loadHistory, () => {});
}
@@ -183,6 +197,7 @@ export default class OverviewApp extends React.PureComponent<Props, State> {
component={component}
history={history}
measures={measures}
+ metrics={this.props.metrics}
onComponentChange={this.props.onComponentChange}
/>
</div>
@@ -191,3 +206,14 @@ export default class OverviewApp extends React.PureComponent<Props, State> {
);
}
}
+
+const mapDispatchToProps: DispatchToProps = { fetchMetrics };
+
+const mapStateToProps = (state: any): StateToProps => ({
+ metrics: getMetrics(state)
+});
+
+export default connect<StateToProps, DispatchToProps, OwnProps>(
+ mapStateToProps,
+ mapDispatchToProps
+)(OverviewApp);
diff --git a/server/sonar-web/src/main/js/apps/overview/events/AnalysesList.tsx b/server/sonar-web/src/main/js/apps/overview/events/AnalysesList.tsx
index 30a3d98e7b1..ac3f80bb2a8 100644
--- a/server/sonar-web/src/main/js/apps/overview/events/AnalysesList.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/events/AnalysesList.tsx
@@ -20,7 +20,6 @@
import * as React from 'react';
import { Link } from 'react-router';
import Analysis from './Analysis';
-import { getAllMetrics } from '../../../api/metrics';
import { getProjectActivity, Analysis as IAnalysis } from '../../../api/projectActivity';
import PreviewGraph from '../../../components/preview-graph/PreviewGraph';
import { translate } from '../../../helpers/l10n';
@@ -31,20 +30,20 @@ interface Props {
branch?: string;
component: Component;
history?: History;
+ metrics: { [key: string]: Metric };
qualifier: string;
}
interface State {
analyses: IAnalysis[];
loading: boolean;
- metrics: Metric[];
}
const PAGE_SIZE = 3;
export default class AnalysesList extends React.PureComponent<Props, State> {
mounted: boolean;
- state: State = { analyses: [], loading: true, metrics: [] };
+ state: State = { analyses: [], loading: true };
componentDidMount() {
this.mounted = true;
@@ -75,17 +74,15 @@ export default class AnalysesList extends React.PureComponent<Props, State> {
fetchData = () => {
this.setState({ loading: true });
- Promise.all([
- getProjectActivity({
- branch: this.props.branch,
- project: this.getTopLevelComponent(),
- ps: PAGE_SIZE
- }),
- getAllMetrics()
- ]).then(
- ([{ analyses }, metrics]) => {
+
+ getProjectActivity({
+ branch: this.props.branch,
+ project: this.getTopLevelComponent(),
+ ps: PAGE_SIZE
+ }).then(
+ ({ analyses }) => {
if (this.mounted) {
- this.setState({ analyses, metrics, loading: false });
+ this.setState({ analyses, loading: false });
}
},
() => {
@@ -125,7 +122,7 @@ export default class AnalysesList extends React.PureComponent<Props, State> {
branch={this.props.branch}
history={this.props.history}
project={this.props.component.key}
- metrics={this.state.metrics}
+ metrics={this.props.metrics}
/>
{this.renderList(analyses)}
diff --git a/server/sonar-web/src/main/js/apps/overview/meta/Meta.tsx b/server/sonar-web/src/main/js/apps/overview/meta/Meta.tsx
index bfffecd1f7c..b84da6cb1f8 100644
--- a/server/sonar-web/src/main/js/apps/overview/meta/Meta.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/meta/Meta.tsx
@@ -18,7 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { connect } from 'react-redux';
+import * as PropTypes from 'prop-types';
import MetaKey from './MetaKey';
import MetaOrganizationKey from './MetaOrganizationKey';
import MetaLinks from './MetaLinks';
@@ -28,90 +28,86 @@ import AnalysesList from '../events/AnalysesList';
import MetaSize from './MetaSize';
import MetaTags from './MetaTags';
import BadgesModal from '../badges/BadgesModal';
-import { areThereCustomOrganizations, getGlobalSettingValue } from '../../../store/rootReducer';
-import { Visibility, Component } from '../../../app/types';
+import { Visibility, Component, Metric } from '../../../app/types';
import { History } from '../../../api/time-machine';
import { MeasureEnhanced } from '../../../helpers/measures';
-interface OwnProps {
+interface Props {
branch?: string;
component: Component;
history?: History;
measures: MeasureEnhanced[];
+ metrics: { [key: string]: Metric };
onComponentChange: (changes: {}) => void;
}
-interface StateToProps {
- areThereCustomOrganizations: boolean;
- onSonarCloud: boolean;
-}
-
-export function Meta(props: OwnProps & StateToProps) {
- const { branch, component, areThereCustomOrganizations } = props;
- const { qualifier, description, qualityProfiles, qualityGate, visibility } = component;
-
- const isProject = qualifier === 'TRK';
- const isPrivate = visibility === Visibility.Private;
+export default class Meta extends React.PureComponent<Props> {
+ static contextTypes = {
+ onSonarCloud: PropTypes.bool,
+ organizationsEnabled: PropTypes.bool
+ };
- const hasDescription = !!description;
- const hasQualityProfiles = Array.isArray(qualityProfiles) && qualityProfiles.length > 0;
- const hasQualityGate = !!qualityGate;
+ render() {
+ const { onSonarCloud, organizationsEnabled } = this.context;
+ const { branch, component, metrics } = this.props;
+ const { qualifier, description, qualityProfiles, qualityGate, visibility } = component;
- const shouldShowQualityProfiles = isProject && hasQualityProfiles;
- const shouldShowQualityGate = isProject && hasQualityGate;
- const hasOrganization = component.organization != null && areThereCustomOrganizations;
+ const isProject = qualifier === 'TRK';
+ const isPrivate = visibility === Visibility.Private;
- return (
- <div className="overview-meta">
- {hasDescription && (
- <div className="overview-meta-card overview-meta-description">{description}</div>
- )}
+ const hasDescription = !!description;
+ const hasQualityProfiles = Array.isArray(qualityProfiles) && qualityProfiles.length > 0;
+ const hasQualityGate = !!qualityGate;
- <MetaSize branch={branch} component={component} measures={props.measures} />
+ const shouldShowQualityProfiles = isProject && hasQualityProfiles;
+ const shouldShowQualityGate = isProject && hasQualityGate;
+ const hasOrganization = component.organization != null && organizationsEnabled;
- {isProject && <MetaTags component={component} onComponentChange={props.onComponentChange} />}
+ return (
+ <div className="overview-meta">
+ {hasDescription && (
+ <div className="overview-meta-card overview-meta-description">{description}</div>
+ )}
- <AnalysesList
- branch={branch}
- component={component}
- qualifier={component.qualifier}
- history={props.history}
- />
+ <MetaSize branch={branch} component={component} measures={this.props.measures} />
- {shouldShowQualityGate && (
- <MetaQualityGate
- gate={qualityGate}
- organization={hasOrganization && component.organization}
- />
- )}
+ {isProject && (
+ <MetaTags component={component} onComponentChange={this.props.onComponentChange} />
+ )}
- {shouldShowQualityProfiles && (
- <MetaQualityProfiles
+ <AnalysesList
+ branch={branch}
component={component}
- customOrganizations={areThereCustomOrganizations}
- profiles={qualityProfiles}
+ history={this.props.history}
+ metrics={metrics}
+ qualifier={component.qualifier}
/>
- )}
-
- {isProject && <MetaLinks component={component} />}
- <MetaKey component={component} />
-
- {hasOrganization && <MetaOrganizationKey component={component} />}
-
- {props.onSonarCloud &&
- isProject &&
- !isPrivate && <BadgesModal branch={branch} project={component.key} />}
- </div>
- );
+ {shouldShowQualityGate && (
+ <MetaQualityGate
+ gate={qualityGate}
+ organization={hasOrganization && component.organization}
+ />
+ )}
+
+ {shouldShowQualityProfiles && (
+ <MetaQualityProfiles
+ component={component}
+ customOrganizations={organizationsEnabled}
+ profiles={qualityProfiles}
+ />
+ )}
+
+ {isProject && <MetaLinks component={component} />}
+
+ <MetaKey component={component} />
+
+ {hasOrganization && <MetaOrganizationKey component={component} />}
+
+ {onSonarCloud &&
+ isProject &&
+ !isPrivate && <BadgesModal branch={branch} metrics={metrics} project={component.key} />}
+ </div>
+ );
+ }
}
-
-const mapStateToProps = (state: any): StateToProps => {
- const sonarCloudSetting = getGlobalSettingValue(state, 'sonar.sonarcloud.enabled');
- return {
- areThereCustomOrganizations: areThereCustomOrganizations(state),
- onSonarCloud: Boolean(sonarCloudSetting && sonarCloudSetting.value === 'true')
- };
-};
-
-export default connect<StateToProps, {}, OwnProps>(mapStateToProps)(Meta);
diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/Activity.tsx b/server/sonar-web/src/main/js/apps/portfolio/components/Activity.tsx
index e433633238e..b90026b8524 100644
--- a/server/sonar-web/src/main/js/apps/portfolio/components/Activity.tsx
+++ b/server/sonar-web/src/main/js/apps/portfolio/components/Activity.tsx
@@ -20,8 +20,7 @@
import * as React from 'react';
import { getDisplayedHistoryMetrics, DEFAULT_GRAPH } from '../../projectActivity/utils';
import PreviewGraph from '../../../components/preview-graph/PreviewGraph';
-import { getAllMetrics } from '../../../api/metrics';
-import { getAllTimeMachineData } from '../../../api/time-machine';
+import { getAllTimeMachineData, History } from '../../../api/time-machine';
import { Metric } from '../../../app/types';
import { parseDate } from '../../../helpers/dates';
import { translate } from '../../../helpers/l10n';
@@ -29,18 +28,14 @@ import { getCustomGraph, getGraph } from '../../../helpers/storage';
const AnyPreviewGraph = PreviewGraph as any;
-interface History {
- [metric: string]: Array<{ date: Date; value: string }>;
-}
-
interface Props {
component: string;
+ metrics: { [key: string]: Metric };
}
interface State {
history?: History;
loading: boolean;
- metrics?: Metric[];
}
export default class Activity extends React.PureComponent<Props> {
@@ -71,8 +66,8 @@ export default class Activity extends React.PureComponent<Props> {
}
this.setState({ loading: true });
- return Promise.all([getAllTimeMachineData(component, graphMetrics), getAllMetrics()]).then(
- ([timeMachine, metrics]) => {
+ return getAllTimeMachineData(component, graphMetrics).then(
+ timeMachine => {
if (this.mounted) {
const history: History = {};
timeMachine.measures.forEach(measure => {
@@ -82,7 +77,7 @@ export default class Activity extends React.PureComponent<Props> {
}));
history[measure.metric] = measureHistory;
});
- this.setState({ history, loading: false, metrics });
+ this.setState({ history, loading: false });
}
},
() => {
@@ -103,11 +98,10 @@ export default class Activity extends React.PureComponent<Props> {
{this.state.loading ? (
<i className="spinner" />
) : (
- this.state.metrics !== undefined &&
this.state.history !== undefined && (
<AnyPreviewGraph
history={this.state.history}
- metrics={this.state.metrics}
+ metrics={this.props.metrics}
project={this.props.component}
renderWhenEmpty={this.renderWhenEmpty}
/>
diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/App.tsx b/server/sonar-web/src/main/js/apps/portfolio/components/App.tsx
index f5450166b5d..84ba6bbbf0a 100644
--- a/server/sonar-web/src/main/js/apps/portfolio/components/App.tsx
+++ b/server/sonar-web/src/main/js/apps/portfolio/components/App.tsx
@@ -18,6 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
+import { connect } from 'react-redux';
import Summary from './Summary';
import Report from './Report';
import WorstProjects from './WorstProjects';
@@ -31,12 +32,25 @@ import { PORTFOLIO_METRICS, SUB_COMPONENTS_METRICS, convertMeasures } from '../u
import { getMeasures } from '../../../api/measures';
import { getChildren } from '../../../api/components';
import { translate } from '../../../helpers/l10n';
+import { fetchMetrics } from '../../../store/rootActions';
+import { getMetrics } from '../../../store/rootReducer';
+import { Metric } from '../../../app/types';
import '../styles.css';
-interface Props {
+interface OwnProps {
component: { key: string; name: string };
}
+interface StateToProps {
+ metrics: { [key: string]: Metric };
+}
+
+interface DispatchToProps {
+ fetchMetrics: () => void;
+}
+
+type Props = StateToProps & DispatchToProps & OwnProps;
+
interface State {
loading: boolean;
measures?: { [key: string]: string | undefined };
@@ -44,12 +58,13 @@ interface State {
totalSubComponents?: number;
}
-export default class App extends React.PureComponent<Props, State> {
+export class App extends React.PureComponent<Props, State> {
mounted: boolean;
state: State = { loading: true };
componentDidMount() {
this.mounted = true;
+ this.props.fetchMetrics();
this.fetchData();
}
@@ -171,7 +186,7 @@ export default class App extends React.PureComponent<Props, State> {
<aside className="page-sidebar-fixed">
{!this.isEmpty() &&
!this.isNotComputed() && <Summary component={component} measures={measures!} />}
- <Activity component={component.key} />
+ <Activity component={component.key} metrics={this.props.metrics} />
<Report component={component} />
</aside>
</div>
@@ -179,3 +194,13 @@ export default class App extends React.PureComponent<Props, State> {
);
}
}
+
+const mapDispatchToProps: DispatchToProps = { fetchMetrics };
+
+const mapStateToProps = (state: any): StateToProps => ({
+ metrics: getMetrics(state)
+});
+
+export default connect<StateToProps, DispatchToProps, Props>(mapStateToProps, mapDispatchToProps)(
+ App
+);
diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/Activity-test.tsx b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/Activity-test.tsx
index 8a9462dd697..c8c2e81f295 100644
--- a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/Activity-test.tsx
+++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/Activity-test.tsx
@@ -23,10 +23,6 @@ jest.mock('../../../../helpers/storage', () => ({
getGraph: () => 'custom'
}));
-jest.mock('../../../../api/metrics', () => ({
- getAllMetrics: jest.fn(() => Promise.resolve([]))
-}));
-
jest.mock('../../../../api/time-machine', () => ({
getAllTimeMachineData: jest.fn(() =>
Promise.resolve({
@@ -47,17 +43,15 @@ import * as React from 'react';
import { mount, shallow } from 'enzyme';
import Activity from '../Activity';
-const getAllMetrics = require('../../../../api/metrics').getAllMetrics as jest.Mock<any>;
const getAllTimeMachineData = require('../../../../api/time-machine')
.getAllTimeMachineData as jest.Mock<any>;
beforeEach(() => {
- getAllMetrics.mockClear();
getAllTimeMachineData.mockClear();
});
it('renders', () => {
- const wrapper = shallow(<Activity component="foo" />);
+ const wrapper = shallow(<Activity component="foo" metrics={{}} />);
wrapper.setState({
history: {
coverage: [
@@ -72,7 +66,6 @@ it('renders', () => {
});
it('fetches history', () => {
- mount(<Activity component="foo" />);
- expect(getAllMetrics).toBeCalled();
+ mount(<Activity component="foo" metrics={{}} />);
expect(getAllTimeMachineData).toBeCalledWith('foo', ['coverage']);
});
diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/App-test.tsx
index dc474149e9f..6cfe8d6890b 100644
--- a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/App-test.tsx
+++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/App-test.tsx
@@ -43,7 +43,7 @@ jest.mock('../Report', () => ({
import * as React from 'react';
import { shallow, mount } from 'enzyme';
-import App from '../App';
+import { App } from '../App';
const getMeasures = require('../../../../api/measures').getMeasures as jest.Mock<any>;
const getChildren = require('../../../../api/components').getChildren as jest.Mock<any>;
@@ -51,7 +51,7 @@ const getChildren = require('../../../../api/components').getChildren as jest.Mo
const component = { key: 'foo', name: 'Foo' };
it('renders', () => {
- const wrapper = shallow(<App component={component} />);
+ const wrapper = shallow(<App component={component} fetchMetrics={jest.fn()} metrics={{}} />);
wrapper.setState({
loading: false,
measures: { ncloc: '173', reliability_rating: '1' },
@@ -62,13 +62,13 @@ it('renders', () => {
});
it('renders when portfolio is empty', () => {
- const wrapper = shallow(<App component={component} />);
+ const wrapper = shallow(<App component={component} fetchMetrics={jest.fn()} metrics={{}} />);
wrapper.setState({ loading: false, measures: { reliability_rating: '1' } });
expect(wrapper).toMatchSnapshot();
});
it('renders when portfolio is not computed', () => {
- const wrapper = shallow(<App component={component} />);
+ const wrapper = shallow(<App component={component} fetchMetrics={jest.fn()} metrics={{}} />);
wrapper.setState({ loading: false, measures: { ncloc: '173' } });
expect(wrapper).toMatchSnapshot();
});
@@ -76,7 +76,7 @@ it('renders when portfolio is not computed', () => {
it('fetches measures and children components', () => {
getMeasures.mockClear();
getChildren.mockClear();
- mount(<App component={component} />);
+ mount(<App component={component} fetchMetrics={jest.fn()} metrics={{}} />);
expect(getMeasures).toBeCalledWith('foo', [
'projects',
'ncloc',
diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Activity-test.tsx.snap b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Activity-test.tsx.snap
index 2a0038daecb..d48d3089a5d 100644
--- a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Activity-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Activity-test.tsx.snap
@@ -22,13 +22,7 @@ exports[`renders 1`] = `
],
}
}
- metrics={
- Array [
- Object {
- "key": "coverage",
- },
- ]
- }
+ metrics={Object {}}
project="foo"
renderWhenEmpty={[Function]}
/>
diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/App-test.tsx.snap b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/App-test.tsx.snap
index 2e26c3cc408..76ccc5b4cb8 100644
--- a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/App-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/App-test.tsx.snap
@@ -77,6 +77,7 @@ exports[`renders 1`] = `
/>
<Activity
component="foo"
+ metrics={Object {}}
/>
<Report
component={
@@ -117,6 +118,7 @@ exports[`renders when portfolio is empty 1`] = `
>
<Activity
component="foo"
+ metrics={Object {}}
/>
<Report
component={
@@ -154,6 +156,7 @@ exports[`renders when portfolio is not computed 1`] = `
>
<Activity
component="foo"
+ metrics={Object {}}
/>
<Report
component={
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/utils.js b/server/sonar-web/src/main/js/apps/projectActivity/utils.js
index 0718ef5d03d..5a8ae9aa5a1 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/utils.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/utils.js
@@ -98,10 +98,17 @@ export const generateCoveredLinesMetric = (
};
};
+function findMetric(key /*: string */, metrics /*: Array<Metric> | { [string]: Metric } */) {
+ if (Array.isArray(metrics)) {
+ return metrics.find(metric => metric.key === key);
+ }
+ return metrics[key];
+}
+
export function generateSeries(
measuresHistory /*: Array<MeasureHistory> */,
graph /*: string */,
- metrics /*: Array<Metric> */,
+ metrics /*: Array<Metric> | { [string]: Metric } */,
displayedMetrics /*: Array<string> */
) /*: Array<Serie> */ {
if (displayedMetrics.length <= 0) {
@@ -114,7 +121,7 @@ export function generateSeries(
if (measure.metric === 'uncovered_lines' && !isCustomGraph(graph)) {
return generateCoveredLinesMetric(measure, measuresHistory);
}
- const metric = metrics.find(metric => metric.key === measure.metric);
+ const metric = findMetric(measure.metric, metrics);
return {
data: measure.history.map(analysis => ({
x: analysis.date,
diff --git a/server/sonar-web/src/main/js/components/preview-graph/PreviewGraph.d.ts b/server/sonar-web/src/main/js/components/preview-graph/PreviewGraph.d.ts
index 744eed0493d..61de9d4f695 100644
--- a/server/sonar-web/src/main/js/components/preview-graph/PreviewGraph.d.ts
+++ b/server/sonar-web/src/main/js/components/preview-graph/PreviewGraph.d.ts
@@ -24,7 +24,7 @@ import { Metric } from '../../app/types';
interface Props {
branch?: string;
history?: History;
- metrics: Metric[];
+ metrics: { [key: string]: Metric };
project: string;
renderWhenEmpty?: () => void;
}
diff --git a/server/sonar-web/src/main/js/components/preview-graph/PreviewGraph.js b/server/sonar-web/src/main/js/components/preview-graph/PreviewGraph.js
index 9483414c1a8..8a8ac8443c5 100644
--- a/server/sonar-web/src/main/js/components/preview-graph/PreviewGraph.js
+++ b/server/sonar-web/src/main/js/components/preview-graph/PreviewGraph.js
@@ -41,7 +41,7 @@ import { formatMeasure, getShortType } from '../../helpers/measures';
type Props = {
branch?: string,
history: ?History,
- metrics: Array<Metric>,
+ metrics: { [string]: Metric },
project: string,
renderWhenEmpty?: () => void
};
@@ -121,7 +121,7 @@ export default class PreviewGraph extends React.PureComponent {
history /*: ?History */,
graph /*: string */,
customMetrics /*: Array<string> */,
- metrics /*: Array<Metric> */
+ metrics /*: { [string]: Metric } */
) => {
const myHistory = history;
if (!myHistory) {