diff --git a/src/components/NimbledroidGraph/index.jsx b/src/components/NimbledroidGraph/index.jsx index f7d27e38..14ae0332 100644 --- a/src/components/NimbledroidGraph/index.jsx +++ b/src/components/NimbledroidGraph/index.jsx @@ -4,11 +4,6 @@ import PropTypes from 'prop-types'; import MetricsGraphics from 'react-metrics-graphics'; class NimbledroidGraph extends Component { - constructor(props) { - super(props); - this.legendTarget = null; - } - render() { const { profile, targetRatio } = this.props; const labels = Object.keys(profile.data); @@ -16,22 +11,22 @@ class NimbledroidGraph extends Component { const target = targetRatio * profile.WV; return ( -
-
this.legendTarget = ele} /> - -
+ ); } } diff --git a/src/components/StatusWidget/Title.jsx b/src/components/StatusWidget/Title.jsx new file mode 100644 index 00000000..19d3d1ad --- /dev/null +++ b/src/components/StatusWidget/Title.jsx @@ -0,0 +1,17 @@ +import PropTypes from 'prop-types'; + +const Title = ({ enrich, hyperlink, text, tooltip }) => { + const className = enrich ? 'enrich' : ''; + const span = {text}; + return (hyperlink) ? + {span} : span; +}; + +Title.propTypes = ({ + enrich: PropTypes.bool, + hyperlink: PropTypes.string, + text: PropTypes.string.isRequired, + tooltip: PropTypes.string, +}); + +export default Title; diff --git a/src/components/StatusWidget/index.jsx b/src/components/StatusWidget/index.jsx index c644dae9..07abdfd6 100644 --- a/src/components/StatusWidget/index.jsx +++ b/src/components/StatusWidget/index.jsx @@ -1,28 +1,20 @@ import PropTypes from 'prop-types'; +import Title from './Title'; const StatusWidget = ({ children, extraInfo, title, - summary, uid, + summary, statusColor, uid, }) => { - const { bgColor, emphasis, hyperlink, text, tooltip } = title; - let $title = ''; - if (hyperlink) { - $title = {text}; - } else { - $title = {text}; - } - if (emphasis) { - $title = {$title}; - } + const { bgColor } = title; return (
- {$title} + {summary && <span>{summary}</span>} </div> {children && <div>{children}</div>} @@ -34,13 +26,8 @@ const StatusWidget = ({ StatusWidget.propTypes = ({ children: PropTypes.shape({}), extraInfo: PropTypes.string, - title: PropTypes.shape({ - text: PropTypes.string.isRequired, - bgColor: PropTypes.string, - emphasis: PropTypes.bool, - hyperlink: PropTypes.string, - tooltip: PropTypes.string, - }).isRequired, + statusColor: PropTypes.string.isRequired, + title: PropTypes.shape().isRequired, summary: PropTypes.string, uid: PropTypes.string, }); diff --git a/src/components/SummaryTable/index.jsx b/src/components/SummaryTable/index.jsx index 5c2ce9e3..e543eae4 100644 --- a/src/components/SummaryTable/index.jsx +++ b/src/components/SummaryTable/index.jsx @@ -4,15 +4,16 @@ import StatusWidget from '../../components/StatusWidget'; const SummaryTable = ({ content, header }) => { const $rows = content.map(({ - title, dataPoints = [], + title, + statusColor, summary, uid, }) => { return ( <tr key={uid}> <td className='title-container'> - <StatusWidget title={title} /> + <StatusWidget statusColor={statusColor} title={title} /> </td> {dataPoints.map((datum, index) => ( <td key={index}>{datum}</td> diff --git a/src/index.css b/src/index.css index ad68fab5..a8824ea5 100644 --- a/src/index.css +++ b/src/index.css @@ -193,10 +193,10 @@ svg path { } } -svg { +/* svg { height: 100%; width: 100%; -} +} */ .graphic-details, .graphic-timeline { display: flex; @@ -1060,6 +1060,11 @@ svg { .status-widget { display: flex; justify-content: space-between; + white-space: nowrap; + .enrich { + font-size: 1em; + font-weight: bold; + } } .summary-table { @@ -1070,13 +1075,21 @@ svg { padding: 0 0 0 0.7em; } .title-container { - max-width: 360px; + max-width: 480px; overflow: hidden; padding: 0; text-align: left; } } +.sites-table { + width: 800px; +} +.sites-overview { + margin: 1em auto 2em auto; + width: 480px; +} + .status-red { background-color: var(--newCoral); } @@ -1095,3 +1108,13 @@ svg { .aligned-center { margin: 0px auto; } + +.error-message { + color: 'red'; +} + +.android-view { + background-color: white; + color: black; + padding: 1em; +} diff --git a/src/utils/BackendClient/NimbledroidApiHandler.js b/src/utils/BackendClient/NimbledroidApiHandler.js index 080dde18..ecad4541 100644 --- a/src/utils/BackendClient/NimbledroidApiHandler.js +++ b/src/utils/BackendClient/NimbledroidApiHandler.js @@ -5,7 +5,7 @@ const renameProduct = product => ( ); const matchUrl = profileName => profileName - .replace(/.*http[s]?:\/\/w*\.?(.*?)[/]?[)]/, (match, firstMatch) => firstMatch); + .replace(/.*(http[s]?:\/\/w*\.?.*?)[/]?[)]/, (match, firstMatch) => firstMatch); const matchDomain = url => url .replace(/([a-zA-Z0-9].*\.[a-z0-9]{1,61})\/.*/, (match, firstMatch) => firstMatch); diff --git a/src/utils/nimbledroid/index.jsx b/src/utils/nimbledroid/index.jsx index 84357a3e..f9881c49 100644 --- a/src/utils/nimbledroid/index.jsx +++ b/src/utils/nimbledroid/index.jsx @@ -38,7 +38,66 @@ const statusColor = (ratio, targetRatio) => { export const siteMetrics = (GV, WV, targetRatio) => { const ratio = ratioWithTarget(GV, WV, targetRatio); - const symbol = percentageSymbol(GV, WV, targetRatio); - const color = statusColor(ratio, targetRatio).widgetColor; - return { ratio, symbol, color }; + return { + ratio, + symbol: percentageSymbol(GV, WV, targetRatio), + color: statusColor(ratio, targetRatio).widgetColor, + }; +}; + +const generateSitesSummary = (count, numSites) => ( + [ + { + title: { + text: 'GeckoView > 20% slower than WebView', + }, + statusColor: 'red', + summary: `${count.red}/${numSites}`, + }, + { + title: { + text: 'GeckoView > 0% and <= 20% slower than WebView', + }, + statusColor: 'yellow', + summary: `${count.yellow}/${numSites}`, + }, + { + title: { + text: 'GeckoView <= 0% (i.e. faster than) WebView', + }, + statusColor: 'green', + summary: `${count.green}/${numSites}`, + }, + ] +); + +export const generateSitesTableContent = (nimbledroidData, targetRatio) => { + const numSites = Object.keys(nimbledroidData).length; + const sites = (numSites > 0) ? + Object.values(nimbledroidData).sort(sortSitesByTargetRatio) : []; + const count = { + red: 0, + yellow: 0, + green: 0, + }; + const tableContent = sites.map(({ title, url, GV, WV }) => { + const { ratio, symbol, color } = siteMetrics(GV, WV, targetRatio); + count[color] += 1; + // This matches the format expected by the SummaryTable component + return { + dataPoints: [GV, WV], + statusColor: color, + summary: `${symbol}${((1 - ratio) * 100).toFixed(2)}%`, + title: { + text: url, + hyperlink: `android/graph?site=${url}`, + tooltip: url, + }, + uid: url, + }; + }); + return { + tableContent, + summary: generateSitesSummary(count, numSites), + }; }; diff --git a/src/views/Android/SiteDrillDown.jsx b/src/views/Android/SiteDrillDown.jsx new file mode 100644 index 00000000..198acdf6 --- /dev/null +++ b/src/views/Android/SiteDrillDown.jsx @@ -0,0 +1,41 @@ +import PropTypes from 'prop-types'; +import { siteMetrics } from '../../utils/nimbledroid'; +import GenericErrorBoundary from '../../components/genericErrorBoundary'; +import NimbledroidGraph from '../../components/NimbledroidGraph'; +import StatusWidget from '../../components/StatusWidget'; + +const SiteDrillDown = ({ nimbledroidData, site, targetRatio }) => { + const { ratio, symbol, color } = siteMetrics( + nimbledroidData[site].GV, + nimbledroidData[site].WV, + targetRatio); + return ( + <GenericErrorBoundary> + <StatusWidget + extraInfo='Target: GeckoView <= WebView + 20%' + statusColor={color} + title={{ + enrich: true, + text: site, + hyperlink: site, + }} + > + <NimbledroidGraph + profile={nimbledroidData[site]} + targetRatio={targetRatio} + /> + </StatusWidget> + </GenericErrorBoundary> + ); +}; + +SiteDrillDown.propTypes = ({ + nimbledroidData: PropTypes.shape({ + GV: PropTypes.string.isRequired, + WV: PropTypes.string.isRequired, + }).isRequired, + site: PropTypes.string.isRequired, + targetRatio: PropTypes.number.isRequired, +}); + +export default SiteDrillDown; diff --git a/src/views/Android/SitesTable.jsx b/src/views/Android/SitesTable.jsx new file mode 100644 index 00000000..88b80966 --- /dev/null +++ b/src/views/Android/SitesTable.jsx @@ -0,0 +1,37 @@ +import PropTypes from 'prop-types'; +import { + generateSitesTableContent, + siteMetrics, + sortSitesByTargetRatio, +} from '../../utils/nimbledroid'; +import GenericErrorBoundary from '../../components/genericErrorBoundary'; + +import SummaryTable from '../../components/SummaryTable'; +import StatusWidget from '../../components/StatusWidget'; + +const SitesTable = ({ nimbledroidData, targetRatio }) => { + const { tableContent, summary } = generateSitesTableContent(nimbledroidData, targetRatio); + return ( + <div className='aligned-center sites-table'> + <GenericErrorBoundary> + <div className='sites-overview'> + {summary.map(s => (<StatusWidget key={s.title.text} {...s} /> + ))} + </div> + </GenericErrorBoundary> + <div className='aligned-center'> + <SummaryTable + header={['GeckoView', 'WebView', '% from target']} + content={tableContent} + /> + </div> + </div> + ); +}; + +SitesTable.propTypes = { + nimbledroidData: PropTypes.shape({}), + targetRatio: PropTypes.number.isRequired, +}; + +export default SitesTable; diff --git a/src/views/Android/index.jsx b/src/views/Android/index.jsx index 6e4caeae..f4ef506c 100644 --- a/src/views/Android/index.jsx +++ b/src/views/Android/index.jsx @@ -4,19 +4,9 @@ import propTypes from 'prop-types'; import Raven from 'raven-js'; import BackendClient from '../../utils/BackendClient'; -import { - percentageSymbol, - ratioWithTarget, - siteMetrics, - sortSitesByTargetRatio, - statusColor, -} from '../../utils/nimbledroid'; import Dashboard from '../../dashboard'; -import GenericErrorBoundary from '../../components/genericErrorBoundary'; -import NimbledroidGraph from '../../components/NimbledroidGraph'; -import SummaryTable from '../../components/SummaryTable'; -import StatusWidget from '../../components/StatusWidget'; -import Widget from '../../quantum/widget'; +import SitesTable from './SitesTable'; +import SiteDrillDown from './SiteDrillDown'; export default class Android extends Component { static propTypes = { @@ -59,113 +49,33 @@ export default class Android extends Component { const { errorMessage, nimbledroidData } = this.state; const numSites = Object.keys(nimbledroidData).length; const targetRatio = 1.2; - let $content = null; - if (numSites > 0) { - // Using replace instead of query-string's parse() method allow for supporting - // data for URLs like this "flipkart.com/search?q=moto%20g5%20plus&sid=tyy" - // parse() would return 'flipkart.com/search?q' instead of the full url. - const site = this.props.location.search.replace('?site=', ''); - if (site) { - const siteData = nimbledroidData[site]; - const { ratio, symbol, color } = siteMetrics(siteData.GV, siteData.WV, targetRatio); - $content = ( - <GenericErrorBoundary> - <StatusWidget - extraInfo='Target: GeckoView <= WebView + 20%' - title={{ - bgColor: color, - emphasis: true, - text: site, - }} - > - <NimbledroidGraph - profile={siteData} - targetRatio={targetRatio} - /> - </StatusWidget> - </GenericErrorBoundary> - ); - } else { - const sites = (numSites > 0) ? - Object.values(nimbledroidData).sort(sortSitesByTargetRatio) : []; - const count = { - red: 0, - yellow: 0, - green: 0, - }; - const summaryTableContent = sites.map(({ title, url, GV, WV }) => { - const { ratio, symbol, color } = siteMetrics(GV, WV, targetRatio); - count[color] += 1; - return { - title: { - text: url, - bgColor: color, - hyperlink: `android/graph?site=${url}`, - tooltip: url, - }, - uid: url, - dataPoints: [GV, WV], - summary: `${symbol}${((1 - ratio) * 100).toFixed(2)}%`, - }; - }); - const summaries = [ - { - title: { - text: 'GeckoView > 20% slower than WebView', - bgColor: 'red', - }, - summary: `${count.red}/${numSites}`, - }, - { - title: { - text: 'GeckoView > 0% and <= 20% slower than WebView', - bgColor: 'yellow', - }, - summary: `${count.yellow}/${numSites}`, - }, - { - title: { - text: 'GeckoView <= 0% (i.e. faster than) WebView', - bgColor: 'green', - }, - summary: `${count.green}/${numSites}`, - }, - ]; - $content = ( - <div className='aligned-center' style={{ width: '600px' }} > - <GenericErrorBoundary> - <div className='aligned-center' style={{ width: '360px' }}> - {summaries.map(({ title, summary }) => ( - <StatusWidget - key={title.text} - title={title} - summary={summary} - /> - ))} - </div> - </GenericErrorBoundary> - <div style={{ margin: '2em' }} /> - <SummaryTable - header={['GeckoView', 'WebView', '% from target']} - content={summaryTableContent} - /> - </div> - ); - } - } - if (errorMessage) { - $content = <h2 style={{ color: 'red' }}>{errorMessage}</h2>; - } + // Using replace instead of query-string's parse() method allow for supporting + // data for URLs like this "flipkart.com/search?q=moto%20g5%20plus&sid=tyy" + // parse() would return 'flipkart.com/search?q' instead of the full url. + const site = this.props.location.search.replace('?site=', ''); return ( <Dashboard title='Android' subtitle='GeckoView vs WebView Page load (time in seconds, lower is better)' className={cx('summary')} - loading={!errorMessage && numSites === 0} > - <div style={{ backgroundColor: 'white', color: 'black', padding: '1em' }}> + <div className="android-view"> + {/* Needed until the background of the site is not black */} <div className='aligned-center'> - {$content} + {numSites > 0 && !site && ( + <SitesTable + nimbledroidData={nimbledroidData} + targetRatio={targetRatio} + /> + )} + {errorMessage && <h2 className="error-message">{errorMessage}</h2>} + {numSites > 0 && site && ( + <SiteDrillDown + nimbledroidData={nimbledroidData} + targetRatio={targetRatio} + site={site} + /> + )} </div> </div> </Dashboard>