Skip to content

Commit

Permalink
Adds metrics table to Compare page, creates Metric component (#1284)
Browse files Browse the repository at this point in the history
* Adds metrics table to Compare page, creates Metric atom

* Cleanup

* Adds tests for metric component

* Adds CompareUtils tests

* Adds MetricUtils tests

* Adds compare page tests
  • Loading branch information
rileyjbauer authored May 16, 2019
1 parent 8382595 commit 814fe28
Show file tree
Hide file tree
Showing 14 changed files with 1,481 additions and 412 deletions.
106 changes: 106 additions & 0 deletions frontend/src/components/Metric.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* Copyright 2019 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import * as React from 'react';
import Metric from './Metric';
import { ReactWrapper, ShallowWrapper, shallow } from 'enzyme';
import { RunMetricFormat } from '../apis/run';

describe('Metric', () => {
let tree: ShallowWrapper | ReactWrapper;

const onErrorSpy = jest.fn();

beforeEach(() => {
onErrorSpy.mockClear();
});

afterEach(async () => {
// unmount() should be called before resetAllMocks() in case any part of the unmount life cycle
// depends on mocks/spies
if (tree) {
await tree.unmount();
}
jest.resetAllMocks();
});

it('renders an empty metric when there is no metric', () => {
tree = shallow(<Metric />);
expect(tree).toMatchSnapshot();
});

it('renders an empty metric when metric has no value', () => {
tree = shallow(<Metric metric={{}} />);
expect(tree).toMatchSnapshot();
});

it('renders a metric when metric has value and percentage format', () => {
tree = shallow(<Metric metric={{ format: RunMetricFormat.PERCENTAGE, number_value: 0.54 }} />);
expect(tree).toMatchSnapshot();
});

it('renders an empty metric when metric has no metadata and unspecified format', () => {
tree = shallow(<Metric metric={{ format: RunMetricFormat.UNSPECIFIED, number_value: 0.54 }} />);
expect(tree).toMatchSnapshot();
});

it('renders an empty metric when metric has no metadata and raw format', () => {
tree = shallow(<Metric metric={{ format: RunMetricFormat.RAW, number_value: 0.54 }} />);
expect(tree).toMatchSnapshot();
});

it('renders a metric when metric has max and min value of 0', () => {
tree = shallow(
<Metric
metadata={{ name: 'some metric', count: 1, maxValue: 0, minValue: 0 }}
metric={{ format: RunMetricFormat.RAW, number_value: 0.54 }}
/>);
expect(tree).toMatchSnapshot();
});

it('renders a metric and does not log an error when metric is between max and min value', () => {
const consoleSpy = jest.spyOn(console, 'error').mockImplementation();
tree = shallow(
<Metric
metadata={{ name: 'some metric', count: 1, maxValue: 1, minValue: 0 }}
metric={{ format: RunMetricFormat.RAW, number_value: 0.54 }}
/>);
expect(consoleSpy).toHaveBeenCalledTimes(0);
expect(tree).toMatchSnapshot();
});

it('renders a metric and logs an error when metric has value less than min value', () => {
const consoleSpy = jest.spyOn(console, 'error').mockImplementation();
tree = shallow(
<Metric
metadata={{ name: 'some metric', count: 1, maxValue: 1, minValue: 0 }}
metric={{ format: RunMetricFormat.RAW, number_value: -0.54 }}
/>);
expect(consoleSpy).toHaveBeenCalled();
expect(tree).toMatchSnapshot();
});

it('renders a metric and logs an error when metric has value greater than max value', () => {
const consoleSpy = jest.spyOn(console, 'error').mockImplementation();
tree = shallow(
<Metric
metadata={{ name: 'some metric', count: 1, maxValue: 1, minValue: 0 }}
metric={{ format: RunMetricFormat.RAW, number_value: 2 }}
/>);
expect(consoleSpy).toHaveBeenCalled();
expect(tree).toMatchSnapshot();
});
});
101 changes: 101 additions & 0 deletions frontend/src/components/Metric.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* Copyright 2019 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import * as React from 'react';
import { ApiRunMetric, RunMetricFormat } from '../apis/run';
import { MetricMetadata } from '../lib/RunUtils';
import { stylesheet } from 'typestyle';
import { logger } from '../lib/Utils';
import MetricUtils from '../lib/MetricUtils';

const css = stylesheet({
metricContainer: {
background: '#f6f7f9',
marginLeft: 6,
marginRight: 10,
},
metricFill: {
background: '#cbf0f8',
boxSizing: 'border-box',
color: '#202124',
fontFamily: 'Roboto',
fontSize: 13,
textIndent: 6,
},
});

interface MetricProps {
metadata?: MetricMetadata;
metric?: ApiRunMetric;
}

class Metric extends React.PureComponent<MetricProps> {

public render(): JSX.Element {
const { metric, metadata } = this.props;
if (!metric || metric.number_value === undefined) {
return <div />;
}

const displayString = MetricUtils.getMetricDisplayString(metric);
let width = '';

if (metric.format === RunMetricFormat.PERCENTAGE) {
width = `calc(${displayString})`;
} else {
// Non-percentage metrics must contain metadata
if (!metadata) {
return <div />;
}

const leftSpace = 6;

if (metadata.maxValue === 0 && metadata.minValue === 0) {
return <div style={{ paddingLeft: leftSpace }}>{displayString}</div>;
}

if (metric.number_value - metadata.minValue < 0) {
logger.error(`Metric ${metadata.name}'s value:`
+ ` (${metric.number_value}) was lower than the supposed minimum of`
+ ` (${metadata.minValue})`);
return <div style={{ paddingLeft: leftSpace }}>{displayString}</div>;
}

if (metadata.maxValue - metric.number_value < 0) {
logger.error(`Metric ${metadata.name}'s value:`
+ ` (${metric.number_value}) was greater than the supposed maximum of`
+ ` (${metadata.maxValue})`);
return <div style={{ paddingLeft: leftSpace }}>{displayString}</div>;
}

const barWidth =
(metric.number_value - metadata.minValue)
/ (metadata.maxValue - metadata.minValue)
* 100;

width = `calc(${barWidth}%)`;
}
return (
<div className={css.metricContainer}>
<div className={css.metricFill} style={{ width }}>
{displayString}
</div>
</div>
);
}
}

export default Metric;
79 changes: 79 additions & 0 deletions frontend/src/components/__snapshots__/Metric.test.tsx.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Metric renders a metric and does not log an error when metric is between max and min value 1`] = `
<div
className="metricContainer"
>
<div
className="metricFill"
style={
Object {
"width": "calc(54%)",
}
}
>
0.540
</div>
</div>
`;

exports[`Metric renders a metric and logs an error when metric has value greater than max value 1`] = `
<div
style={
Object {
"paddingLeft": 6,
}
}
>
2.000
</div>
`;

exports[`Metric renders a metric and logs an error when metric has value less than min value 1`] = `
<div
style={
Object {
"paddingLeft": 6,
}
}
>
-0.540
</div>
`;

exports[`Metric renders a metric when metric has max and min value of 0 1`] = `
<div
style={
Object {
"paddingLeft": 6,
}
}
>
0.540
</div>
`;

exports[`Metric renders a metric when metric has value and percentage format 1`] = `
<div
className="metricContainer"
>
<div
className="metricFill"
style={
Object {
"width": "calc(54.000%)",
}
}
>
54.000%
</div>
</div>
`;

exports[`Metric renders an empty metric when metric has no metadata and raw format 1`] = `<div />`;

exports[`Metric renders an empty metric when metric has no metadata and unspecified format 1`] = `<div />`;

exports[`Metric renders an empty metric when metric has no value 1`] = `<div />`;

exports[`Metric renders an empty metric when there is no metric 1`] = `<div />`;
Loading

0 comments on commit 814fe28

Please sign in to comment.