This repository has been archived by the owner on Apr 19, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 70
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
274 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1052,3 +1052,7 @@ svg { | |
.wide-content { | ||
flex: 2; | ||
} | ||
|
||
.no-left-margin { | ||
margin-left: 0 !important; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
} | ||
} |