canAdmin: boolean;
loading: boolean;
onSonarCloud: boolean;
+ organizationsEnabled: boolean;
}
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
};
}
canAdmin: appState.canAdmin,
onSonarCloud: Boolean(
appState.settings && appState.settings['sonar.sonarcloud.enabled'] === 'true'
- )
+ ),
+ organizationsEnabled: appState.organizationsEnabled
});
}
return appState;
* 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';
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 {
location: {
query: { branch?: string; id: string };
};
- organizationsEnabled?: boolean;
}
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 };
([nav, data]) => {
const component = this.addQualifier({ ...nav, ...data });
- if (this.props.organizationsEnabled) {
+ if (this.context.organizationsEnabled) {
this.props.fetchOrganizations([component.organization]);
}
}
}
-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);
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);
);
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);
* 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();
}
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 });
}
}
}
-
-const mapDispatchToProps: DispatchToProps = { fetchMetrics };
-
-const mapStateToProps = (state: any): StateToProps => ({
- metrics: getMetrics(state)
-});
-
-export default connect<StateToProps, DispatchToProps, OwnProps>(
- mapStateToProps,
- mapDispatchToProps
-)(BadgeParams);
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;
}
</p>
<BadgeParams
className="big-spacer-bottom"
+ metrics={this.props.metrics}
options={badgeOptions}
type={selectedType}
updateOptions={this.handleUpdateOptions}
*/
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';
function getWrapper(props = {}) {
return shallow(
<BadgeParams
- fetchMetrics={jest.fn()}
metrics={METRICS}
options={{ color: 'white', metric: 'alert_status' }}
type={BadgeType.marketing}
}));
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();
>
overview.badges.measure.description
</p>
- <Connect(BadgeParams)
+ <BadgeParams
className="big-spacer-bottom"
+ metrics={Object {}}
options={
Object {
"color": "white",
*/
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';
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;
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, () => {});
}
component={component}
history={history}
measures={measures}
+ metrics={this.props.metrics}
onComponentChange={this.props.onComponentChange}
/>
</div>
);
}
}
+
+const mapDispatchToProps: DispatchToProps = { fetchMetrics };
+
+const mapStateToProps = (state: any): StateToProps => ({
+ metrics: getMetrics(state)
+});
+
+export default connect<StateToProps, DispatchToProps, OwnProps>(
+ mapStateToProps,
+ mapDispatchToProps
+)(OverviewApp);
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';
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;
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 });
}
},
() => {
branch={this.props.branch}
history={this.props.history}
project={this.props.component.key}
- metrics={this.state.metrics}
+ metrics={this.props.metrics}
/>
{this.renderList(analyses)}
* 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';
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);
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';
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> {
}
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 => {
}));
history[measure.metric] = measureHistory;
});
- this.setState({ history, loading: false, metrics });
+ this.setState({ history, loading: false });
}
},
() => {
{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}
/>
* 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';
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 };
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();
}
<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>
);
}
}
+
+const mapDispatchToProps: DispatchToProps = { fetchMetrics };
+
+const mapStateToProps = (state: any): StateToProps => ({
+ metrics: getMetrics(state)
+});
+
+export default connect<StateToProps, DispatchToProps, Props>(mapStateToProps, mapDispatchToProps)(
+ App
+);
getGraph: () => 'custom'
}));
-jest.mock('../../../../api/metrics', () => ({
- getAllMetrics: jest.fn(() => Promise.resolve([]))
-}));
-
jest.mock('../../../../api/time-machine', () => ({
getAllTimeMachineData: jest.fn(() =>
Promise.resolve({
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: [
});
it('fetches history', () => {
- mount(<Activity component="foo" />);
- expect(getAllMetrics).toBeCalled();
+ mount(<Activity component="foo" metrics={{}} />);
expect(getAllTimeMachineData).toBeCalledWith('foo', ['coverage']);
});
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>;
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' },
});
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();
});
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',
],
}
}
- metrics={
- Array [
- Object {
- "key": "coverage",
- },
- ]
- }
+ metrics={Object {}}
project="foo"
renderWhenEmpty={[Function]}
/>
/>
<Activity
component="foo"
+ metrics={Object {}}
/>
<Report
component={
>
<Activity
component="foo"
+ metrics={Object {}}
/>
<Report
component={
>
<Activity
component="foo"
+ metrics={Object {}}
/>
<Report
component={
};
};
+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) {
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,
interface Props {
branch?: string;
history?: History;
- metrics: Metric[];
+ metrics: { [key: string]: Metric };
project: string;
renderWhenEmpty?: () => void;
}
type Props = {
branch?: string,
history: ?History,
- metrics: Array<Metric>,
+ metrics: { [string]: Metric },
project: string,
renderWhenEmpty?: () => void
};
history /*: ?History */,
graph /*: string */,
customMetrics /*: Array<string> */,
- metrics /*: Array<Metric> */
+ metrics /*: { [string]: Metric } */
) => {
const myHistory = history;
if (!myHistory) {