Skip to content
This repository has been archived by the owner on Apr 19, 2021. It is now read-only.

Commit

Permalink
Merge 61aecbc into 46322bc
Browse files Browse the repository at this point in the history
  • Loading branch information
armenzg committed Jul 12, 2018
2 parents 46322bc + 61aecbc commit 6004060
Show file tree
Hide file tree
Showing 7 changed files with 274 additions and 0 deletions.
34 changes: 34 additions & 0 deletions src/components/NimbledroidGraphs/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import PropTypes from 'prop-types';
import NimbledroidWidget from '../NimbledroidWidget';
import GenericErrorBoundary from '../../components/genericErrorBoundary';

const NimbledroidGraphs = ({ nimbledroidData }) => (
nimbledroidData ?
(
<div
style={{
display: 'grid',
gridTemplateColumns: 'repeat(1, 1fr)',
gridGap: '1em',
width: '100%',
}}
>
{Object.keys(nimbledroidData).map(profileName => (
<GenericErrorBoundary key={profileName}>
<NimbledroidWidget
key={profileName}
profile={nimbledroidData[profileName]}
targetRatio={1.2}
/>
</GenericErrorBoundary>
))}
</div>
)
: undefined
);

NimbledroidGraphs.propTypes = {
nimbledroidData: PropTypes.shape({}),
};

export default NimbledroidGraphs;
60 changes: 60 additions & 0 deletions src/components/NimbledroidWidget/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { curveLinear } from 'd3';
import PropTypes from 'prop-types';
import { Component } from 'react';
import MetricsGraphics from 'react-metrics-graphics';

import Widget from '../../quantum/widget';

export default class NimbledroidWidget extends Component {
constructor(props) {
super(props);
this.legendTarget = null;
}

render() {
const { profile } = this.props;
const labels = Object.keys(profile.data);
const data = labels.map(product => profile.data[product]);
const target = this.props.targetRatio * profile.lastDataPoints.focus;
const status = profile.lastDataPoints.klar / profile.lastDataPoints.focus;

return (
<Widget
// This classname is to undo what .criteria-widget:not:first-child sets
className={'no-left-margin'}
title={profile.title}
target='Klar <= Focus + 20%'
targetStatus={status < 1 ? 'pass' : 'fail'}
status={status < 1 ? 'green' : 'yellow'}
>
<div>
<div className='legend' ref={ele => this.legendTarget = ele} />
{profile &&
<MetricsGraphics
width={600}
height={300}
data={data}
x_accessor='date'
y_accessor='value'
legend={labels}
legend_target={this.legendTarget}
interpolate={curveLinear}
baselines={[{
value: target,
label: 'Target',
}]}
/>
}
</div>
</Widget>
);
}
}

NimbledroidWidget.propTypes = {
profile: PropTypes.shape({
title: PropTypes.string.isRequired,
data: PropTypes.shape({}).isRequired,
}),
targetRatio: PropTypes.number.isRequired,
};
4 changes: 4 additions & 0 deletions src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -1052,3 +1052,7 @@ svg {
.wide-content {
flex: 2;
}

.no-left-margin {
margin-left: 0 !important;
}
2 changes: 2 additions & 0 deletions src/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Status from './status/index';
import Devtools from './devtools/index';
import QuantumRoutes from './quantum/routes';
import Android from './views/Android';
import AndroidV2 from './views/AndroidV2';

const NoMatch = () => <div>404</div>;

Expand Down Expand Up @@ -57,6 +58,7 @@ export default class Routes extends Component {
<App>
<Switch>
<Route path='/android' exact component={Android} />
<Route path='/android/v2' exact component={AndroidV2} />
<Route path='/' exact component={Home} />
<Route path='/crashes' exact component={ReleaseCrashes} />
<Route path='/crashes/beta' component={BetaCrashes} />
Expand Down
113 changes: 113 additions & 0 deletions src/utils/BackendClient/NimbledroidApiHandler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import fetchJson from '../fetchJson';

const matchSiteName = profileName => profileName
.replace(/.*.*http[s]*:\/\/[www]*\.*(.*)[/].*/, (match, firstMatch) => firstMatch);

const transformedDataForMetrisGraphics = (nimbledroidData) => {
const metricsGraphicsData = nimbledroidData.reduce((result, elem) => {
const product = elem.package_id.replace('org.mozilla.', '');
if (!result[product]) {
result[product] = {};
}
elem.profiles.forEach((profile) => {
const { scenario_name, status, time_in_ms } = profile;
// In Nimbledroid we have create a number of profiles
// some of them test websites and contain the URL in the name.
// There are other profiles testing non-site behaviours, however,
// we're not interested on plotting those
if (
status !== 'fail' &&
scenario_name.startsWith('customFlow') &&
scenario_name.includes('http')
) {
if (!result[product][scenario_name]) {
result[product][scenario_name] = {
data: [],
title: matchSiteName(scenario_name),
};
}
if (time_in_ms > 0) {
result[product][scenario_name].data.push({
date: new Date(elem.added),
value: time_in_ms / 1000,
});
}
}
});
return result;
}, {});
return metricsGraphicsData;
};

const sortDataPointsByRecency = (a, b) => {
let retVal;
if (a.date < b.date) {
retVal = -1;
} else if (a.date === b.date) {
retVal = 0;
} else {
retVal = 1;
}
return retVal;
};

const mergeProductsData = (productsData) => {
const mergedData = productsData
.reduce((result, productData) => {
const product = Object.keys(productData);
const profileKeys = Object.keys(productData[product]);

profileKeys.forEach((profileKey) => {
const { data, title } = productData[product][profileKey];
const sortedData = data.sort(sortDataPointsByRecency);
const lastDataPoint = sortedData[sortedData.length - 1].value;

// This is the first time we're seing this scenario
if (!result[profileKey]) {
result[profileKey] = {
data: {},
title,
lastDataPoints: {}, // This is a shortcut
};
}
// This is the first time we're seing this product for this scenario
if (!result[profileKey].data[product]) {
result[profileKey].data[product] = sortedData;
result[profileKey].lastDataPoints[product] = lastDataPoint;
}
});
return result;
}, {});
return mergedData;
};

class NimbledroidApiHandler {
products = ['focus', 'klar'];

constructor(backendUrl) {
this.nimbledroidApiUrl = `${backendUrl}/api/android/nimbledroid`;
}

// No clean way to have interfaces on Javascript
getData() {
return this.fetchProducts();
}

productUrl(product) {
return `${this.nimbledroidApiUrl}?product=${product}`;
}

async fetchProductData(product) {
const productData = await fetchJson(this.productUrl(product));
return transformedDataForMetrisGraphics(productData);
}

async fetchProducts() {
const productsData = await Promise.all(
this.products.map(async product => this.fetchProductData(product)),
);
return mergeProductsData(productsData);
}
}

export default NimbledroidApiHandler;
18 changes: 18 additions & 0 deletions src/utils/BackendClient/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/* global fetch */
import SETTINGS from '../../settings';
import NimbledroidApiHandler from './NimbledroidApiHandler';

class BackendClient {
constructor() {
this.baseUrl = SETTINGS.backend;
this.handlers = {
nimbledroid: new NimbledroidApiHandler(this.baseUrl),
};
}

getData(apiName) {
return this.handlers[apiName].getData();
}
}

export default BackendClient;
43 changes: 43 additions & 0 deletions src/views/AndroidV2/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Component } from 'react';
import cx from 'classnames';

import BackendClient from '../../utils/BackendClient';
import Dashboard from '../../dashboard';
import NimbledroidGraphs from '../../components/NimbledroidGraphs';

export default class AndroidV2 extends Component {
state = { nimbledroidData: {} }

componentDidMount() {
this.fetchAndroidData();
}

client = new BackendClient();

async fetchAndroidData() {
const nimbledroidData = await this.client.getData('nimbledroid');
this.setState({ nimbledroidData });
}

render() {
const { nimbledroidData } = this.state;
return (
<Dashboard
title='Android'
subtitle='GeckoView vs WebView Page load (time in seconds, lower is better)'
className={cx('summary')}
>
<div className='row'>
{Object.keys(nimbledroidData).length > 0 &&
<NimbledroidGraphs
nimbledroidData={nimbledroidData}
/>
}
{Object.keys(nimbledroidData).length === 0 &&
<h2>The backend is loading data from Nimbledroid. Thanks for waiting.</h2>
}
</div>
</Dashboard>
);
}
}

0 comments on commit 6004060

Please sign in to comment.