зеркало из https://github.com/mozilla/treeherder.git
Bug 1539232 - Switch Perfherder to react-router (#5379)
Switch Perfherder to react-router Use top-level of app as a cache for projects, frameworks, alerts data and compare data Cleanup files and move constants to dedicated perfherder file Remove angular-related libraries and bump up the neutrino entry and asset limits
This commit is contained in:
Родитель
4a267309d5
Коммит
59737af771
|
@ -40,8 +40,8 @@ module.exports = {
|
|||
title: 'Push Health',
|
||||
},
|
||||
perf: {
|
||||
entry: 'entry-perf.js',
|
||||
template: 'ui/perf.html',
|
||||
entry: 'perfherder/index.jsx',
|
||||
title: 'Perfherder',
|
||||
},
|
||||
'intermittent-failures': {
|
||||
entry: 'intermittent-failures/index.jsx',
|
||||
|
@ -149,8 +149,8 @@ module.exports = {
|
|||
// to help prevent unknowingly regressing the bundle size (bug 1384255).
|
||||
neutrino.config.performance
|
||||
.hints('error')
|
||||
.maxAssetSize(2 * 1024 * 1024)
|
||||
.maxEntrypointSize(2.25 * 1024 * 1024);
|
||||
.maxAssetSize(1.5 * 1024 * 1024)
|
||||
.maxEntrypointSize(2 * 1024 * 1024);
|
||||
}
|
||||
},
|
||||
],
|
||||
|
|
|
@ -22,22 +22,16 @@
|
|||
"@neutrinojs/copy": "9.0.0-rc.3",
|
||||
"@neutrinojs/react": "9.0.0-rc.3",
|
||||
"@testing-library/react": "9.1.4",
|
||||
"@types/angular": "*",
|
||||
"@types/prop-types": "*",
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"@uirouter/angularjs": "0.4.3",
|
||||
"ajv": "6.10.2",
|
||||
"angular": "1.7.8",
|
||||
"angular-clipboard": "1.7.0",
|
||||
"angular1-ui-bootstrap4": "2.4.22",
|
||||
"auth0-js": "9.11.3",
|
||||
"bootstrap": "4.3.1",
|
||||
"d3": "5.12.0",
|
||||
"fuse.js": "3.4.5",
|
||||
"history": "4.10.0",
|
||||
"jquery": "3.4.1",
|
||||
"jquery.flot": "0.8.3",
|
||||
"js-cookie": "2.2.1",
|
||||
"js-yaml": "3.13.1",
|
||||
"json-e": "3.0.1",
|
||||
|
@ -65,7 +59,6 @@
|
|||
"react-split-pane": "0.1.87",
|
||||
"react-table": "6.10.3",
|
||||
"react-tabs": "3.0.0",
|
||||
"react2angular": "4.0.6",
|
||||
"reactstrap": "7.1.0",
|
||||
"redux": "4.0.4",
|
||||
"redux-debounce": "1.0.1",
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
import AlertsViewControls from '../../../ui/perfherder/alerts/AlertsViewControls';
|
||||
import optionCollectionMap from '../mock/optionCollectionMap';
|
||||
import { summaryStatusMap } from '../../../ui/perfherder/constants';
|
||||
import repos from '../mock/repositories';
|
||||
|
||||
const testUser = {
|
||||
username: 'test user',
|
||||
|
@ -219,10 +220,6 @@ const alertsViewControls = () =>
|
|||
hideDwnToInv: undefined,
|
||||
hideImprovements: undefined,
|
||||
filter: undefined,
|
||||
projects: [
|
||||
{ id: 1, name: 'mozilla-central' },
|
||||
{ id: 2, name: 'mozilla-inbound' },
|
||||
],
|
||||
updateParams: () => {},
|
||||
}}
|
||||
dropdownOptions={testAlertDropdowns}
|
||||
|
@ -233,6 +230,11 @@ const alertsViewControls = () =>
|
|||
updateViewState={() => {}}
|
||||
user={testUser}
|
||||
modifyAlert={(alert, params) => mockModifyAlert.update(alert, params)}
|
||||
projects={repos}
|
||||
location={{
|
||||
pathname: '/alerts',
|
||||
search: '',
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
|
|
|
@ -20,5 +20,5 @@
|
|||
"prod": "https://treeherder.mozilla.org/",
|
||||
"stage": "https://treeherder.allizom.org/"
|
||||
},
|
||||
"keywords": ["css", "django", "angular", "js", "python"]
|
||||
"keywords": ["css", "django", "react", "js", "python"]
|
||||
}
|
||||
|
|
|
@ -3,8 +3,7 @@
|
|||
|
||||
body {
|
||||
text-align: center;
|
||||
overflow-x: auto;
|
||||
overflow-y: auto;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
h1,
|
||||
|
@ -17,33 +16,6 @@ h1,
|
|||
margin-bottom: 7px;
|
||||
}
|
||||
|
||||
.top-navbar {
|
||||
background-color: #222;
|
||||
height: 34px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.btn-navbar {
|
||||
background-color: transparent;
|
||||
border-color: #373d40;
|
||||
border-radius: 0;
|
||||
border-bottom: 0;
|
||||
border-top: 0;
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
.navbar-link {
|
||||
color: lightgray;
|
||||
}
|
||||
|
||||
.navbar-link:hover {
|
||||
color: lightgray;
|
||||
}
|
||||
|
||||
.navbar-link:visited {
|
||||
color: lightgray;
|
||||
}
|
||||
|
||||
.bug-column-header {
|
||||
text-align: left;
|
||||
margin-left: 10px;
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
body {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
section {
|
||||
height: calc(100% - 50px);
|
||||
overflow-y: auto;
|
||||
|
@ -75,6 +79,7 @@ h1 {
|
|||
pointer-events: none;
|
||||
width: 280px;
|
||||
z-index: 999;
|
||||
text-align: left;
|
||||
}
|
||||
.graph-tooltip.locked {
|
||||
pointer-events: auto;
|
||||
|
|
|
@ -447,3 +447,22 @@ fieldset[disabled] .btn-view-nav-closed.active {
|
|||
.dropdown-item:hover {
|
||||
background-color: #d3d3d34d;
|
||||
}
|
||||
|
||||
/* Used by Perfherder and IFV */
|
||||
.top-navbar {
|
||||
background-color: #222;
|
||||
height: 34px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.navbar-link {
|
||||
color: lightgray;
|
||||
}
|
||||
|
||||
.navbar-link:hover {
|
||||
color: lightgray;
|
||||
}
|
||||
|
||||
.navbar-link:visited {
|
||||
color: lightgray;
|
||||
}
|
||||
|
|
|
@ -1,49 +0,0 @@
|
|||
// Webpack entry point for perf.html
|
||||
|
||||
// Vendor Styles
|
||||
import 'angular/angular-csp.css';
|
||||
import 'bootstrap/dist/css/bootstrap.min.css';
|
||||
|
||||
// Vendor JS
|
||||
import 'bootstrap';
|
||||
import { library, dom, config } from '@fortawesome/fontawesome-svg-core';
|
||||
import { faFileCode, faFileWord } from '@fortawesome/free-regular-svg-icons';
|
||||
import {
|
||||
faBug,
|
||||
faCode,
|
||||
faQuestionCircle,
|
||||
} from '@fortawesome/free-solid-svg-icons';
|
||||
import { faGithub } from '@fortawesome/free-brands-svg-icons';
|
||||
|
||||
// The official 'flot' NPM package is out of date, so we're using 'jquery.flot'
|
||||
// instead, which is identical to https://github.com/flot/flot
|
||||
import 'jquery.flot';
|
||||
import 'jquery.flot/jquery.flot.time';
|
||||
import 'jquery.flot/jquery.flot.selection';
|
||||
|
||||
// Perf Styles
|
||||
import './css/treeherder-global.css';
|
||||
import './css/treeherder-navbar.css';
|
||||
import './css/perf.css';
|
||||
|
||||
// Bootstrap the Angular modules against which everything will be registered
|
||||
import './js/perf';
|
||||
|
||||
// Perf JS
|
||||
import './js/perfapp';
|
||||
import './perfherder/compare/CompareSelectorView';
|
||||
import './perfherder/compare/CompareView';
|
||||
import './perfherder/compare/CompareSubtestDistributionView';
|
||||
import './perfherder/compare/CompareSubtestsView';
|
||||
import './perfherder/alerts/AlertTable';
|
||||
import './perfherder/alerts/AlertsView';
|
||||
import './perfherder/graphs/GraphsView';
|
||||
|
||||
config.showMissingIcons = true;
|
||||
|
||||
// TODO: Remove these as Perfherder components switch to using react-fontawesome.
|
||||
library.add(faBug, faCode, faFileCode, faFileWord, faGithub, faQuestionCircle);
|
||||
|
||||
// Replace any existing <i> or <span> tags with <svg> and set up a MutationObserver
|
||||
// to continue doing this as the DOM changes. Remove once using react-fontawesome.
|
||||
dom.watch();
|
|
@ -221,31 +221,6 @@ export const thEvents = {
|
|||
clearPinboard: 'clear-pinboard-EVT',
|
||||
};
|
||||
|
||||
export const phTimeRanges = [
|
||||
{ value: 86400, text: 'Last day' },
|
||||
{ value: 86400 * 2, text: 'Last 2 days' },
|
||||
{ value: 604800, text: 'Last 7 days' },
|
||||
{ value: 1209600, text: 'Last 14 days' },
|
||||
{ value: 2592000, text: 'Last 30 days' },
|
||||
{ value: 5184000, text: 'Last 60 days' },
|
||||
{ value: 7776000, text: 'Last 90 days' },
|
||||
{ value: 31536000, text: 'Last year' },
|
||||
];
|
||||
|
||||
export const phDefaultTimeRangeValue = 1209600;
|
||||
|
||||
export const phFrameworksWithRelatedBranches = [
|
||||
1, // talos
|
||||
10, // raptor
|
||||
11, // js-bench
|
||||
12, // devtools
|
||||
];
|
||||
|
||||
export const compareDefaultTimeRange = {
|
||||
value: 86400 * 2,
|
||||
text: 'Last 2 days',
|
||||
};
|
||||
|
||||
export const thBugSuggestionLimit = 20;
|
||||
|
||||
export const thMaxPushFetchSize = 100;
|
||||
|
|
|
@ -122,3 +122,13 @@ export const bugzillaBugsApi = function bugzillaBugsApi(api, params) {
|
|||
|
||||
export const getRevisionUrl = (revision, projectName) =>
|
||||
revision ? getJobsUrl({ repo: projectName, revision }) : '';
|
||||
|
||||
export const updateQueryParams = function updateHistoryWithQueryParams(
|
||||
queryParams,
|
||||
history,
|
||||
location,
|
||||
) {
|
||||
history.replace({ pathname: location.pathname, search: queryParams });
|
||||
// we do this so the api's won't be called twice (location/history updates will trigger a lifecycle hook)
|
||||
location.search = queryParams;
|
||||
};
|
||||
|
|
|
@ -238,8 +238,8 @@ BugDetailsView.defaultProps = {
|
|||
};
|
||||
|
||||
const defaultState = {
|
||||
route: '/bugdetails',
|
||||
endpoint: bugDetailsEndpoint,
|
||||
route: '/bugdetails',
|
||||
};
|
||||
|
||||
export default withView(defaultState)(BugDetailsView);
|
||||
|
|
|
@ -184,8 +184,8 @@ const defaultState = {
|
|||
.subtract(7, 'days'),
|
||||
),
|
||||
endday: ISODate(moment().utc()),
|
||||
route: '/main',
|
||||
endpoint: bugsEndpoint,
|
||||
route: '/main',
|
||||
};
|
||||
|
||||
export default withView(defaultState)(MainView);
|
||||
|
|
|
@ -7,15 +7,11 @@ import {
|
|||
createQueryParams,
|
||||
createApiUrl,
|
||||
bugzillaBugsApi,
|
||||
updateQueryParams,
|
||||
} from '../helpers/url';
|
||||
import { getData } from '../helpers/http';
|
||||
|
||||
import {
|
||||
updateQueryParams,
|
||||
validateQueryParams,
|
||||
mergeData,
|
||||
formatBugs,
|
||||
} from './helpers';
|
||||
import { validateQueryParams, mergeData, formatBugs } from './helpers';
|
||||
|
||||
const withView = defaultState => WrappedComponent => {
|
||||
class View extends React.Component {
|
||||
|
@ -72,7 +68,7 @@ const withView = defaultState => WrappedComponent => {
|
|||
// if the query params are not specified for mainview, set params based on default state
|
||||
if (location.search === '') {
|
||||
const queryString = createQueryParams(params);
|
||||
updateQueryParams(defaultState.route, queryString, history, location);
|
||||
updateQueryParams(queryString, history, location);
|
||||
}
|
||||
|
||||
this.setState({ initialParamsSet: true });
|
||||
|
@ -157,12 +153,7 @@ const withView = defaultState => WrappedComponent => {
|
|||
|
||||
// update query params if dates or tree are updated
|
||||
const queryString = createQueryParams(params);
|
||||
updateQueryParams(
|
||||
defaultState.route,
|
||||
queryString,
|
||||
this.props.history,
|
||||
this.props.location,
|
||||
);
|
||||
updateQueryParams(queryString, this.props.history, this.props.location);
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -63,17 +63,6 @@ export const calculateMetrics = function calculateMetricsForGraphs(data) {
|
|||
};
|
||||
};
|
||||
|
||||
export const updateQueryParams = function updateHistoryWithQueryParams(
|
||||
view,
|
||||
queryParams,
|
||||
history,
|
||||
location,
|
||||
) {
|
||||
history.replace({ pathname: view, search: queryParams });
|
||||
// we do this so the api's won't be called twice (location/history updates will trigger a lifecycle hook)
|
||||
location.search = queryParams;
|
||||
};
|
||||
|
||||
export const sortData = function sortData(data, sortBy, desc) {
|
||||
data.sort((a, b) => {
|
||||
const item1 = desc ? b[sortBy] : a[sortBy];
|
||||
|
|
|
@ -7,13 +7,13 @@ import Logo from '../../img/treeherder-logo.png';
|
|||
import Login from '../../shared/auth/Login';
|
||||
import LogoMenu from '../../shared/LogoMenu';
|
||||
import { notify } from '../redux/stores/notifications';
|
||||
import HelpMenu from '../../shared/HelpMenu';
|
||||
|
||||
import NotificationsMenu from './NotificationsMenu';
|
||||
import InfraMenu from './InfraMenu';
|
||||
import ReposMenu from './ReposMenu';
|
||||
import TiersMenu from './TiersMenu';
|
||||
import FiltersMenu from './FiltersMenu';
|
||||
import HelpMenu from './HelpMenu';
|
||||
import SecondaryNavBar from './SecondaryNavBar';
|
||||
import HealthMenu from './HealthMenu';
|
||||
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
import angular from 'angular';
|
||||
import angularClipboardModule from 'angular-clipboard';
|
||||
import uiBootstrap from 'angular1-ui-bootstrap4';
|
||||
import uiRouter from '@uirouter/angularjs';
|
||||
import 'ng-text-truncate-2';
|
||||
import { react2angular } from 'react2angular/index.es2015';
|
||||
|
||||
import Login from '../shared/auth/Login';
|
||||
import LogoMenu from '../shared/LogoMenu';
|
||||
|
||||
import treeherderModule from './treeherder';
|
||||
|
||||
const perf = angular.module('perf', [
|
||||
uiRouter,
|
||||
uiBootstrap,
|
||||
treeherderModule.name,
|
||||
angularClipboardModule.name,
|
||||
'ngTextTruncate',
|
||||
]);
|
||||
|
||||
perf.component('login', react2angular(Login, ['user', 'setUser'], []));
|
||||
perf.component('logoMenu', react2angular(LogoMenu, ['menuText'], []));
|
||||
|
||||
export default perf;
|
|
@ -1,79 +0,0 @@
|
|||
// Remove the eslint-disable when rewriting this file during the React conversion.
|
||||
/* eslint-disable func-names */
|
||||
import alertsCtrlTemplate from '../partials/perf/alertsctrl.html';
|
||||
import graphsCtrlTemplate from '../partials/perf/graphsctrl.html';
|
||||
import compareCtrlTemplate from '../partials/perf/comparectrl.html';
|
||||
import compareSubtestCtrlTemplate from '../partials/perf/comparesubtestctrl.html';
|
||||
import compareChooserCtrlTemplate from '../partials/perf/comparechooserctrl.html';
|
||||
import compareSubtestDistributionTemplate from '../partials/perf/comparesubtestdistribution.html';
|
||||
import helpMenuTemplate from '../partials/perf/helpMenu.html';
|
||||
|
||||
import perf from './perf';
|
||||
|
||||
// configure the router here, after we have defined all the controllers etc
|
||||
perf.config(['$compileProvider', '$locationProvider', '$httpProvider', '$stateProvider', '$urlRouterProvider',
|
||||
function ($compileProvider, $locationProvider, $httpProvider, $stateProvider, $urlRouterProvider) {
|
||||
// Disable debug data & legacy comment/class directive syntax, as recommended by:
|
||||
// https://docs.angularjs.org/guide/production
|
||||
$compileProvider.debugInfoEnabled(false);
|
||||
$compileProvider.commentDirectivesEnabled(false);
|
||||
$compileProvider.cssClassDirectivesEnabled(false);
|
||||
|
||||
// Revert to the legacy Angular <=1.5 URL hash prefix to save breaking existing links:
|
||||
// https://docs.angularjs.org/guide/migration#commit-aa077e8
|
||||
$locationProvider.hashPrefix('');
|
||||
|
||||
$httpProvider.defaults.xsrfHeaderName = 'X-CSRFToken';
|
||||
$httpProvider.defaults.xsrfCookieName = 'csrftoken';
|
||||
$httpProvider.useApplyAsync(true);
|
||||
|
||||
$stateProvider
|
||||
.state('alerts', {
|
||||
title: 'Alerts',
|
||||
template: alertsCtrlTemplate,
|
||||
url: '/alerts?id&status&framework&filter&hideImprovements&hideDwnToInv&page',
|
||||
})
|
||||
.state('graphs', {
|
||||
title: 'Graphs',
|
||||
template: graphsCtrlTemplate,
|
||||
url: '/graphs?timerange&series&highlightedRevisions&highlightAlerts&zoom&selected',
|
||||
})
|
||||
.state('compare', {
|
||||
title: 'Compare',
|
||||
template: compareCtrlTemplate,
|
||||
url: '/compare?originalProject&originalRevision?&newProject&newRevision&hideMinorChanges&framework&filter&showOnlyComparable&showOnlyImportant&showOnlyConfident&selectedTimeRange&showOnlyNoise?',
|
||||
})
|
||||
.state('comparesubtest', {
|
||||
title: 'Compare - Subtests',
|
||||
template: compareSubtestCtrlTemplate,
|
||||
url: '/comparesubtest?originalProject&originalRevision?&newProject&newRevision&originalSignature&newSignature&filter&showOnlyComparable&showOnlyImportant&showOnlyConfident&framework&selectedTimeRange&showOnlyNoise?',
|
||||
})
|
||||
.state('comparechooser', {
|
||||
title: 'Compare Chooser',
|
||||
template: compareChooserCtrlTemplate,
|
||||
url: '/comparechooser?originalProject&originalRevision&newProject&newRevision',
|
||||
})
|
||||
.state('comparesubtestdistribution', {
|
||||
title: 'Compare Subtest Distribution',
|
||||
template: compareSubtestDistributionTemplate,
|
||||
url: '/comparesubtestdistribution?originalProject&newProject&originalRevision&newRevision&originalSubtestSignature?newSubtestSignature',
|
||||
});
|
||||
$urlRouterProvider.otherwise('/graphs');
|
||||
}]).run(['$rootScope', '$state', '$stateParams', function ($rootScope, $state, $stateParams) {
|
||||
$rootScope.$state = $state;
|
||||
$rootScope.$stateParams = $stateParams;
|
||||
$rootScope.user = { isLoggedIn: false };
|
||||
|
||||
$rootScope.setUser = (user) => {
|
||||
$rootScope.user = user;
|
||||
$rootScope.$apply();
|
||||
};
|
||||
|
||||
$rootScope.$on('$stateChangeSuccess', function () {
|
||||
if ($state.current.title) {
|
||||
window.document.title = $state.current.title;
|
||||
}
|
||||
});
|
||||
// Templates used by ng-include have to be manually put in the template cache.
|
||||
// Those used by directives should instead be imported at point of use.
|
||||
}]).run(['$templateCache', ($templateCache) => $templateCache.put('partials/perf/helpMenu.html', helpMenuTemplate)]);
|
|
@ -1,3 +0,0 @@
|
|||
import angular from 'angular';
|
||||
|
||||
export default angular.module('treeherder', []);
|
|
@ -1 +0,0 @@
|
|||
<alerts-view user="user"></alerts-view>
|
|
@ -1 +0,0 @@
|
|||
<compare-selector-view />
|
|
@ -1 +0,0 @@
|
|||
<compare-view user="user"></compare-view>
|
|
@ -1 +0,0 @@
|
|||
<compare-subtests-view user="user"/>
|
|
@ -1 +0,0 @@
|
|||
<compare-subtest-distribution-view />
|
|
@ -1 +0,0 @@
|
|||
<graphs-view user="user" />
|
|
@ -1,26 +0,0 @@
|
|||
<!--- TODO: Replace this with the React component used by jobs-view -->
|
||||
<li>
|
||||
<a class="dropdown-item" href="https://treeherder.readthedocs.io/" target="_blank" rel="noopener">
|
||||
<span class="far fa-file-code fa-fw mr-1 midgray"></span>
|
||||
Development Documentation</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item" href="/docs/" target="_blank" rel="noopener">
|
||||
<span class="fas fa-code fa-fw mr-1 midgray"></span>
|
||||
API Reference</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item" href="https://wiki.mozilla.org/EngineeringProductivity/Projects/Perfherder" target="_blank" rel="noopener">
|
||||
<span class="far fa-file-word fa-fw mr-1 midgray"></span>
|
||||
Project Wiki</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item" href="https://bugzilla.mozilla.org/enter_bug.cgi?product=Tree%20Management&component=Perfherder" target="_blank" rel="noopener">
|
||||
<span class="fas fa-bug fa-fw mr-1 midgray"></span>
|
||||
Report a Bug</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item" href="https://github.com/mozilla/treeherder" target="_blank" rel="noopener">
|
||||
<span class="fab fa-github fa-fw mr-1 midgray"></span>
|
||||
Source</a>
|
||||
</li>
|
81
ui/perf.html
81
ui/perf.html
|
@ -1,81 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html ng-app="perf" ng-strict-di ng-csp>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Perfherder</title>
|
||||
<link
|
||||
id="favicon"
|
||||
type="image/png"
|
||||
rel="shortcut icon"
|
||||
href="img/line_chart.png"
|
||||
/>
|
||||
<style>
|
||||
[ng\:cloak],
|
||||
[ng-cloak],
|
||||
[data-ng-cloak],
|
||||
[x-ng-cloak],
|
||||
.ng-cloak,
|
||||
.x-ng-cloak {
|
||||
display: none !important;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<nav
|
||||
id="th-global-navbar"
|
||||
class="navbar navbar-inverse"
|
||||
role="navigation"
|
||||
ng-cloak
|
||||
>
|
||||
<div id="th-global-navbar-top" class="navbar navbar-collapse">
|
||||
<span class="navbar-left d-flex">
|
||||
<logo-menu menu-text="'Perfherder'"></logo-menu>
|
||||
<a
|
||||
ng-class="{active: $state.includes('graphs')}"
|
||||
class="btn btn-view-nav"
|
||||
ui-sref="graphs"
|
||||
>Graphs</a
|
||||
>
|
||||
<a
|
||||
ng-class="{active: $state.current.name.startsWith('compare')}"
|
||||
class="btn btn-view-nav"
|
||||
ui-sref="comparechooser"
|
||||
>Compare</a
|
||||
>
|
||||
<a
|
||||
ng-class="{active: $state.current.name.indexOf('alerts') >= 0}"
|
||||
class="btn btn-view-nav"
|
||||
ui-sref="alerts({id: null, status: null, framework: null, filter: null, hideImprovements: null, hideDwnToInv: 1})"
|
||||
>Alerts</a
|
||||
>
|
||||
</span>
|
||||
<div class="nav navbar-right">
|
||||
<!-- Help Menu -->
|
||||
<span class="dropdown">
|
||||
<button
|
||||
id="helpLabel"
|
||||
title="Perfherder help"
|
||||
aria-label="Perfherder help"
|
||||
role="button"
|
||||
data-toggle="dropdown"
|
||||
class="btn btn-view-nav nav-help-btn dropdown-toggle"
|
||||
>
|
||||
<span class="fas fa-question-circle lightgray"></span>
|
||||
</button>
|
||||
<ul
|
||||
class="dropdown-menu nav-dropdown-menu-right icon-menu"
|
||||
role="menu"
|
||||
aria-labelledby="helpLabel"
|
||||
ng-include="'partials/perf/helpMenu.html'"
|
||||
></ul>
|
||||
</span>
|
||||
<!-- login -->
|
||||
<login user="$root.user" set-user="setUser"></login>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<div style="padding-top: 20px;"></div>
|
||||
|
||||
<section ui-view></section>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,222 @@
|
|||
import React from 'react';
|
||||
import { HashRouter, Route, Switch, Redirect } from 'react-router-dom';
|
||||
import { hot } from 'react-hot-loader/root';
|
||||
import { Container } from 'reactstrap';
|
||||
|
||||
import { getData, processResponse } from '../helpers/http';
|
||||
import { getApiUrl, repoEndpoint } from '../helpers/url';
|
||||
import ErrorMessages from '../shared/ErrorMessages';
|
||||
|
||||
import { endpoints } from './constants';
|
||||
import GraphsView from './graphs/GraphsView';
|
||||
import AlertsView from './alerts/AlertsView';
|
||||
import CompareView from './compare/CompareView';
|
||||
import CompareSelectorView from './compare/CompareSelectorView';
|
||||
import CompareSubtestsView from './compare/CompareSubtestsView';
|
||||
import CompareSubtestDistributionView from './compare/CompareSubtestDistributionView';
|
||||
import Navigation from './Navigation';
|
||||
|
||||
class App extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
// store alerts and comapre view data so the API's won't be
|
||||
// called again when navigating back from related views.
|
||||
this.state = {
|
||||
projects: [],
|
||||
frameworks: [],
|
||||
user: {},
|
||||
errorMessages: [],
|
||||
compareData: [],
|
||||
};
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
const [projects, frameworks] = await Promise.all([
|
||||
getData(getApiUrl(repoEndpoint)),
|
||||
getData(getApiUrl(endpoints.frameworks)),
|
||||
]);
|
||||
const errorMessages = [];
|
||||
const updates = {
|
||||
...processResponse(projects, 'projects', errorMessages),
|
||||
...processResponse(frameworks, 'frameworks', errorMessages),
|
||||
};
|
||||
|
||||
this.setState(updates);
|
||||
}
|
||||
|
||||
updateAppState = state => {
|
||||
this.setState(state);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
user,
|
||||
projects,
|
||||
frameworks,
|
||||
errorMessages,
|
||||
compareData,
|
||||
} = this.state;
|
||||
|
||||
return (
|
||||
<HashRouter>
|
||||
<Navigation
|
||||
user={user}
|
||||
setUser={user => this.setState({ user })}
|
||||
notify={message => this.setState({ errorMessages: [message] })}
|
||||
/>
|
||||
{projects.length > 0 && frameworks.length > 0 && (
|
||||
<main className="pt-5">
|
||||
<Switch>
|
||||
<Route
|
||||
exact
|
||||
path="/alerts"
|
||||
render={props => (
|
||||
<AlertsView
|
||||
{...props}
|
||||
user={user}
|
||||
projects={projects}
|
||||
frameworks={frameworks}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Route
|
||||
path="/alerts?id=:id&status=:status&framework=:framework&filter=:filter&hideImprovements=:hideImprovements&hideDwnToInv=:hideDwnToInv&page=:page"
|
||||
render={props => (
|
||||
<AlertsView
|
||||
{...props}
|
||||
user={user}
|
||||
projects={projects}
|
||||
frameworks={frameworks}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Route
|
||||
path="/graphs"
|
||||
render={props => (
|
||||
<GraphsView
|
||||
{...props}
|
||||
user={user}
|
||||
projects={projects}
|
||||
frameworks={frameworks}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Route
|
||||
path="/graphs?timerange=:timerange&series=:series&highlightedRevisions=:highlightedRevisions&highlightAlerts=:highlightAlerts&zoom=:zoom&selected=:selected"
|
||||
render={props => (
|
||||
<GraphsView
|
||||
{...props}
|
||||
user={user}
|
||||
projects={projects}
|
||||
frameworks={frameworks}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Route
|
||||
path="/comparechooser"
|
||||
render={props => (
|
||||
<CompareSelectorView
|
||||
{...props}
|
||||
user={user}
|
||||
projects={projects}
|
||||
frameworks={frameworks}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Route
|
||||
path="/comparechooser?originalProject=:originalProject&originalRevision=:originalRevision&newProject=:newProject&newRevision=:newRevision"
|
||||
render={props => (
|
||||
<CompareSelectorView
|
||||
{...props}
|
||||
user={user}
|
||||
projects={projects}
|
||||
frameworks={frameworks}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Route
|
||||
path="/compare"
|
||||
render={props => (
|
||||
<CompareView
|
||||
{...props}
|
||||
user={user}
|
||||
projects={projects}
|
||||
frameworks={frameworks}
|
||||
compareData={compareData}
|
||||
updateAppState={this.updateAppState}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Route
|
||||
path="/compare?originalProject=:originalProject&originalRevision=:originalRevison&newProject=:newProject&newRevision=:newRevision&framework=:framework&showOnlyComparable=:showOnlyComparable&showOnlyImportant=:showOnlyImportant&showOnlyConfident=:showOnlyConfident&selectedTimeRange=:selectedTimeRange&showOnlyNoise=:showOnlyNoise"
|
||||
render={props => (
|
||||
<CompareView
|
||||
{...props}
|
||||
user={user}
|
||||
projects={projects}
|
||||
frameworks={frameworks}
|
||||
compareData={compareData}
|
||||
updateAppState={this.updateAppState}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Route
|
||||
path="/comparesubtest"
|
||||
render={props => (
|
||||
<CompareSubtestsView
|
||||
{...props}
|
||||
user={user}
|
||||
projects={projects}
|
||||
frameworks={frameworks}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Route
|
||||
path="/comparesubtest?originalProject=:originalProject&originalRevision=:originalRevision&newProject=:newProject&newRevision=:newRevision&originalSignature=:originalSignature&newSignature=:newSignature&framework=:framework&showOnlyComparable=:showOnlyComparable&showOnlyImportant=:showOnlyImportant&showOnlyConfident=:showOnlyConfident&selectedTimeRange=:selectedTimeRange&showOnlyNoise=:showOnlyNoise"
|
||||
render={props => (
|
||||
<CompareSubtestsView
|
||||
{...props}
|
||||
user={user}
|
||||
projects={projects}
|
||||
frameworks={frameworks}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Route
|
||||
path="/comparesubtestdistribution"
|
||||
render={props => (
|
||||
<CompareSubtestDistributionView
|
||||
{...props}
|
||||
user={user}
|
||||
projects={projects}
|
||||
frameworks={frameworks}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Route
|
||||
path="/comparesubtestdistribution?originalProject=:originalProject&newProject=:newProject&originalRevision=:originalRevision&newRevision=:newRevision&originalSubtestSignature=:originalSubtestSignature&newSubtestSignature=:newSubtestSignature"
|
||||
render={props => (
|
||||
<CompareSubtestDistributionView
|
||||
{...props}
|
||||
user={user}
|
||||
projects={projects}
|
||||
frameworks={frameworks}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Redirect from="/" to="/alerts?hideDwnToInv=1" />
|
||||
</Switch>
|
||||
</main>
|
||||
)}
|
||||
{errorMessages.length > 0 && (
|
||||
<Container className="pt-5 max-width-default">
|
||||
<ErrorMessages errorMessages={errorMessages} />
|
||||
</Container>
|
||||
)}
|
||||
</HashRouter>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default hot(App);
|
|
@ -0,0 +1,42 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Navbar, Nav, NavItem, NavLink } from 'reactstrap';
|
||||
|
||||
import LogoMenu from '../shared/LogoMenu';
|
||||
import Login from '../shared/auth/Login';
|
||||
import HelpMenu from '../shared/HelpMenu';
|
||||
|
||||
const Navigation = ({ user, setUser, notify }) => (
|
||||
<Navbar expand fixed="top" className="top-navbar">
|
||||
<LogoMenu menuText="Perfherder" colorClass="text-info" />
|
||||
<Nav className="navbar navbar-inverse">
|
||||
<NavItem>
|
||||
<NavLink href="#/graphs" className="btn-view-nav">
|
||||
Graphs
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
<NavItem>
|
||||
<NavLink href="#/comparechooser" className="btn-view-nav">
|
||||
Compare
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
<NavItem>
|
||||
<NavLink href="#/alerts?hideDwnToInv=1" className="btn-view-nav">
|
||||
Alerts
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
</Nav>
|
||||
<Navbar className="ml-auto">
|
||||
<HelpMenu />
|
||||
<Login user={user} setUser={setUser} notify={notify} />
|
||||
</Navbar>
|
||||
</Navbar>
|
||||
);
|
||||
|
||||
Navigation.propTypes = {
|
||||
user: PropTypes.shape({}).isRequired,
|
||||
setUser: PropTypes.func.isRequired,
|
||||
notify: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default Navigation;
|
|
@ -2,32 +2,25 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import { Container } from 'reactstrap';
|
||||
|
||||
import { getData, processResponse } from '../helpers/http';
|
||||
import { getApiUrl, repoEndpoint } from '../helpers/url';
|
||||
import {
|
||||
parseQueryParams,
|
||||
createQueryParams,
|
||||
updateQueryParams,
|
||||
} from '../helpers/url';
|
||||
import PushModel from '../models/push';
|
||||
import ErrorMessages from '../shared/ErrorMessages';
|
||||
import LoadingSpinner from '../shared/LoadingSpinner';
|
||||
|
||||
import { endpoints, summaryStatusMap } from './constants';
|
||||
|
||||
// TODO once we switch to react-router
|
||||
// 1) use context in this HOC to share state between compare views, by wrapping router component in it;
|
||||
// advantages include:
|
||||
// * no need to check historical location.state to determine if user has navigated to compare or comparesubtest
|
||||
// views from a previous view (thus params have already been validated and resultsets stored in state)
|
||||
// * if user navigates to compareview from compareChooser and decides to change a project via query params
|
||||
// to a different project, projects will already be stored in state so no fetching data again to validate
|
||||
//
|
||||
import { summaryStatusMap } from './constants';
|
||||
|
||||
const withValidation = (
|
||||
requiredParams,
|
||||
{ requiredParams },
|
||||
verifyRevisions = true,
|
||||
) => WrappedComponent => {
|
||||
class Validation extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
// TODO change $stateParams to location.state once we switch to react-router
|
||||
this.state = {
|
||||
originalProject: null,
|
||||
newProject: null,
|
||||
|
@ -36,38 +29,32 @@ const withValidation = (
|
|||
originalSignature: null,
|
||||
newSignature: null,
|
||||
errorMessages: [],
|
||||
projects: [],
|
||||
originalResultSet: null,
|
||||
newResultSet: null,
|
||||
selectedTimeRange: null,
|
||||
framework: null,
|
||||
frameworks: [],
|
||||
// TODO reset if validateParams method is called from another component
|
||||
validationComplete: false,
|
||||
};
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
const [projects, frameworks] = await Promise.all([
|
||||
getData(getApiUrl(repoEndpoint)),
|
||||
getData(getApiUrl(endpoints.frameworks)),
|
||||
]);
|
||||
|
||||
const updates = {
|
||||
...processResponse(projects, 'projects'),
|
||||
...processResponse(frameworks, 'frameworks'),
|
||||
};
|
||||
this.setState(updates, () =>
|
||||
this.validateParams(this.props.$stateParams),
|
||||
);
|
||||
this.validateParams(parseQueryParams(this.props.location.search));
|
||||
}
|
||||
|
||||
updateParams = param => {
|
||||
const { transitionTo, current } = this.props.$state;
|
||||
transitionTo(current.name, param, {
|
||||
inherit: true,
|
||||
notify: false,
|
||||
});
|
||||
componentDidUpdate(prevProps) {
|
||||
const { location } = this.props;
|
||||
|
||||
if (location.search !== prevProps.location.search) {
|
||||
this.validateParams(parseQueryParams(location.search));
|
||||
}
|
||||
}
|
||||
|
||||
updateParams = params => {
|
||||
const { location, history } = this.props;
|
||||
const newParams = { ...parseQueryParams(location.search), ...params };
|
||||
const queryString = createQueryParams(newParams);
|
||||
|
||||
updateQueryParams(queryString, history, location);
|
||||
};
|
||||
|
||||
errorMessage = (param, value) => `${param} ${value} is not valid`;
|
||||
|
@ -134,7 +121,7 @@ const withValidation = (
|
|||
}
|
||||
|
||||
validateParams(params) {
|
||||
const { projects, frameworks } = this.state;
|
||||
const { projects, frameworks } = this.props;
|
||||
let errors = [];
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
|
@ -173,10 +160,13 @@ const withValidation = (
|
|||
if (verifyRevisions) {
|
||||
return this.checkRevisions(params);
|
||||
}
|
||||
this.setState({
|
||||
...params,
|
||||
validationComplete: true,
|
||||
});
|
||||
this.setState(
|
||||
{
|
||||
...params,
|
||||
validationComplete: true,
|
||||
},
|
||||
this.updateParams({ ...params }),
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -208,11 +198,7 @@ const withValidation = (
|
|||
}
|
||||
|
||||
Validation.propTypes = {
|
||||
$stateParams: PropTypes.shape({}).isRequired,
|
||||
$state: PropTypes.shape({
|
||||
transitionTo: PropTypes.func,
|
||||
current: PropTypes.shape({}),
|
||||
}).isRequired,
|
||||
location: PropTypes.shape({}).isRequired,
|
||||
};
|
||||
|
||||
return Validation;
|
||||
|
|
|
@ -122,7 +122,7 @@ export default class AlertTable extends React.Component {
|
|||
render() {
|
||||
const {
|
||||
user,
|
||||
validated,
|
||||
projects,
|
||||
alertSummaries,
|
||||
issueTrackers,
|
||||
fetchAlertSummaries,
|
||||
|
@ -140,7 +140,7 @@ export default class AlertTable extends React.Component {
|
|||
|
||||
const downstreamIdsLength = downstreamIds.length;
|
||||
const repo = alertSummary
|
||||
? validated.projects.find(repo => repo.name === alertSummary.repository)
|
||||
? projects.find(repo => repo.name === alertSummary.repository)
|
||||
: null;
|
||||
const repoModel = new RepositoryModel(repo);
|
||||
|
||||
|
@ -281,9 +281,6 @@ export default class AlertTable extends React.Component {
|
|||
AlertTable.propTypes = {
|
||||
alertSummary: PropTypes.shape({}),
|
||||
user: PropTypes.shape({}),
|
||||
validated: PropTypes.shape({
|
||||
projects: PropTypes.arrayOf(PropTypes.shape({})),
|
||||
}).isRequired,
|
||||
alertSummaries: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
|
||||
issueTrackers: PropTypes.arrayOf(PropTypes.shape({})),
|
||||
optionCollectionMap: PropTypes.shape({}).isRequired,
|
||||
|
@ -296,6 +293,7 @@ AlertTable.propTypes = {
|
|||
updateViewState: PropTypes.func.isRequired,
|
||||
bugTemplate: PropTypes.shape({}),
|
||||
modifyAlert: PropTypes.func,
|
||||
projects: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
|
||||
};
|
||||
|
||||
AlertTable.defaultProps = {
|
||||
|
|
|
@ -13,10 +13,12 @@ import { createQueryParams } from '../../helpers/url';
|
|||
import { getStatus, getGraphsURL, modifyAlert } from '../helpers';
|
||||
import SimpleTooltip from '../../shared/SimpleTooltip';
|
||||
import ProgressBar from '../ProgressBar';
|
||||
import { alertStatusMap } from '../constants';
|
||||
import { phDefaultTimeRangeValue, phTimeRanges } from '../../helpers/constants';
|
||||
import {
|
||||
alertStatusMap,
|
||||
phDefaultTimeRangeValue,
|
||||
phTimeRanges,
|
||||
} from '../constants';
|
||||
|
||||
// TODO remove $stateParams and $state after switching to react router
|
||||
export default class AlertTableRow extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
@ -94,7 +96,6 @@ export default class AlertTableRow extends React.Component {
|
|||
{` ${text} `}
|
||||
<a
|
||||
href={`#/alerts?id=${alertId}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-info"
|
||||
>{`alert #${alertId}`}</a>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/* eslint-disable react/no-did-update-set-state */
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { react2angular } from 'react2angular/index.es2015';
|
||||
import {
|
||||
Alert,
|
||||
Container,
|
||||
|
@ -10,11 +10,14 @@ import {
|
|||
PaginationLink,
|
||||
} from 'reactstrap';
|
||||
|
||||
import perf from '../../js/perf';
|
||||
import withValidation from '../Validation';
|
||||
import { convertParams, getFrameworkData, getStatus } from '../helpers';
|
||||
import { summaryStatusMap, endpoints } from '../constants';
|
||||
import { createQueryParams, getApiUrl } from '../../helpers/url';
|
||||
import {
|
||||
createQueryParams,
|
||||
getApiUrl,
|
||||
parseQueryParams,
|
||||
} from '../../helpers/url';
|
||||
import { getData, processResponse } from '../../helpers/http';
|
||||
import ErrorMessages from '../../shared/ErrorMessages';
|
||||
import OptionCollectionModel from '../../models/optionCollection';
|
||||
|
@ -27,14 +30,13 @@ import LoadingSpinner from '../../shared/LoadingSpinner';
|
|||
|
||||
import AlertsViewControls from './AlertsViewControls';
|
||||
|
||||
// TODO remove $stateParams and $state after switching to react router
|
||||
export class AlertsView extends React.Component {
|
||||
class AlertsView extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.validated = this.props.validated;
|
||||
this.state = {
|
||||
status: this.getDefaultStatus(),
|
||||
framework: getFrameworkData(this.validated),
|
||||
framework: getFrameworkData(this.props),
|
||||
page: this.validated.page ? parseInt(this.validated.page, 10) : 1,
|
||||
errorMessages: [],
|
||||
alertSummaries: [],
|
||||
|
@ -54,14 +56,26 @@ export class AlertsView extends React.Component {
|
|||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
const { count } = this.state;
|
||||
const { validated } = this.props;
|
||||
|
||||
if (prevState.count !== count) {
|
||||
// eslint-disable-next-line react/no-did-update-set-state
|
||||
this.setState({ totalPages: this.generatePages(count) });
|
||||
}
|
||||
|
||||
const params = parseQueryParams(this.props.location.search);
|
||||
// we're using local state for id instead of validated.id because once
|
||||
// the user navigates from the id=<alert> view back to the main alerts view
|
||||
// the Validation component won't reset the id (since the query param doesn't exist
|
||||
// unless there is a value)
|
||||
if (this.props.location.search !== prevProps.location.search) {
|
||||
this.setState({ id: params.id || null }, this.fetchAlertSummaries);
|
||||
validated.updateParams({ hideDwnToInv: 0 });
|
||||
}
|
||||
}
|
||||
|
||||
getDefaultStatus = () => {
|
||||
const { validated } = this.props;
|
||||
|
||||
const statusParam = convertParams(validated, 'status');
|
||||
if (!statusParam) {
|
||||
return Object.keys(summaryStatusMap)[1];
|
||||
|
@ -70,7 +84,8 @@ export class AlertsView extends React.Component {
|
|||
};
|
||||
|
||||
updateFramework = selection => {
|
||||
const { frameworks, updateParams } = this.props.validated;
|
||||
const { updateParams } = this.props.validated;
|
||||
const { frameworks } = this.props;
|
||||
const framework = frameworks.find(item => item.name === selection);
|
||||
|
||||
updateParams({ framework: framework.id });
|
||||
|
@ -106,7 +121,6 @@ export class AlertsView extends React.Component {
|
|||
return pages;
|
||||
};
|
||||
|
||||
// TODO potentially pass as a prop for testing purposes
|
||||
async fetchAlertSummaries(id = this.state.id, update = false) {
|
||||
// turn off loading when update is true (used to update alert statuses)
|
||||
this.setState({ loading: !update, errorMessages: [] });
|
||||
|
@ -143,7 +157,6 @@ export class AlertsView extends React.Component {
|
|||
`${endpoints.alertSummary}${createQueryParams(params)}`,
|
||||
);
|
||||
|
||||
// TODO OptionCollectionModel to use getData wrapper
|
||||
if (!issueTrackers.length && !optionCollectionMap) {
|
||||
const [optionCollectionMap, issueTrackers] = await Promise.all([
|
||||
OptionCollectionModel.getMap(),
|
||||
|
@ -162,6 +175,8 @@ export class AlertsView extends React.Component {
|
|||
if (response.alertSummaries) {
|
||||
const summary = response.alertSummaries;
|
||||
|
||||
// used with the id argument to update one specific alert summary in the array of
|
||||
// alert summaries that's been updated based on an action taken in the AlertActionPanel
|
||||
if (update) {
|
||||
const index = alertSummaries.findIndex(
|
||||
item => item.id === summary.results[0].id,
|
||||
|
@ -184,7 +199,7 @@ export class AlertsView extends React.Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { user, validated } = this.props;
|
||||
const { user, frameworks } = this.props;
|
||||
const {
|
||||
framework,
|
||||
status,
|
||||
|
@ -198,7 +213,6 @@ export class AlertsView extends React.Component {
|
|||
bugTemplate,
|
||||
id,
|
||||
} = this.state;
|
||||
const { frameworks } = validated;
|
||||
|
||||
const frameworkNames =
|
||||
frameworks && frameworks.length ? frameworks.map(item => item.name) : [];
|
||||
|
@ -240,7 +254,6 @@ export class AlertsView extends React.Component {
|
|||
</Alert>
|
||||
)}
|
||||
<AlertsViewControls
|
||||
validated={validated}
|
||||
dropdownOptions={id ? [] : alertDropdowns}
|
||||
alertSummaries={alertSummaries}
|
||||
issueTrackers={issueTrackers}
|
||||
|
@ -249,6 +262,7 @@ export class AlertsView extends React.Component {
|
|||
updateViewState={state => this.setState(state)}
|
||||
bugTemplate={bugTemplate}
|
||||
user={user}
|
||||
{...this.props}
|
||||
/>
|
||||
{pageNums.length > 0 && (
|
||||
<Row className="justify-content-center pb-5">
|
||||
|
@ -301,29 +315,20 @@ export class AlertsView extends React.Component {
|
|||
}
|
||||
|
||||
AlertsView.propTypes = {
|
||||
$stateParams: PropTypes.shape({}),
|
||||
$state: PropTypes.shape({
|
||||
go: PropTypes.func,
|
||||
}),
|
||||
location: PropTypes.shape({}),
|
||||
user: PropTypes.shape({}).isRequired,
|
||||
validated: PropTypes.shape({
|
||||
projects: PropTypes.arrayOf(PropTypes.shape({})),
|
||||
frameworks: PropTypes.arrayOf(PropTypes.shape({})),
|
||||
updateParams: PropTypes.func.isRequired,
|
||||
framework: PropTypes.string,
|
||||
}).isRequired,
|
||||
projects: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
|
||||
frameworks: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
|
||||
};
|
||||
|
||||
AlertsView.defaultProps = {
|
||||
$stateParams: null,
|
||||
$state: null,
|
||||
location: null,
|
||||
};
|
||||
|
||||
const alertsView = withValidation(new Set([]), false)(AlertsView);
|
||||
|
||||
perf.component(
|
||||
'alertsView',
|
||||
react2angular(alertsView, ['user'], ['$stateParams', '$state']),
|
||||
export default withValidation({ requiredParams: new Set([]) }, false)(
|
||||
AlertsView,
|
||||
);
|
||||
|
||||
export default alertsView;
|
||||
|
|
|
@ -17,6 +17,24 @@ export default class AlertsViewControls extends React.Component {
|
|||
};
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const { validated } = this.props;
|
||||
|
||||
if (
|
||||
validated.hideImprovements !== prevProps.validated.hideImprovements ||
|
||||
validated.hideDwnToInv !== prevProps.validated.hideDwnToInv
|
||||
) {
|
||||
// eslint-disable-next-line react/no-did-update-set-state
|
||||
this.setState({
|
||||
hideImprovements: convertParams(
|
||||
this.props.validated,
|
||||
'hideImprovements',
|
||||
),
|
||||
hideDownstream: convertParams(this.props.validated, 'hideDwnToInv'),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
updateFilter = filter => {
|
||||
this.setState(
|
||||
prevState => ({ [filter]: !prevState[filter] }),
|
||||
|
|
|
@ -7,7 +7,6 @@ import { getData } from '../../helpers/http';
|
|||
import { endpoints } from '../constants';
|
||||
import { getApiUrl } from '../../helpers/url';
|
||||
|
||||
// TODO remove $stateParams and $state after switching to react router
|
||||
export default class DownstreamSummary extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { react2angular } from 'react2angular/index.es2015';
|
||||
import {
|
||||
Container,
|
||||
Col,
|
||||
|
@ -11,28 +9,23 @@ import {
|
|||
DropdownToggle,
|
||||
} from 'reactstrap';
|
||||
|
||||
import perf from '../../js/perf';
|
||||
import { getApiUrl, repoEndpoint } from '../../helpers/url';
|
||||
import { endpoints } from '../constants';
|
||||
import { getData, processResponse } from '../../helpers/http';
|
||||
import { parseQueryParams, createQueryParams } from '../../helpers/url';
|
||||
import ErrorMessages from '../../shared/ErrorMessages';
|
||||
import DropdownMenuItems from '../../shared/DropdownMenuItems';
|
||||
import {
|
||||
compareDefaultTimeRange,
|
||||
genericErrorMessage,
|
||||
errorMessageClass,
|
||||
} from '../../helpers/constants';
|
||||
import { compareDefaultTimeRange } from '../constants';
|
||||
import ErrorBoundary from '../../shared/ErrorBoundary';
|
||||
|
||||
import SelectorCard from './SelectorCard';
|
||||
|
||||
// TODO remove $stateParams and $state after switching to react router
|
||||
export default class CompareSelectorView extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.queryParams = this.props.$stateParams;
|
||||
this.queryParams = parseQueryParams(this.props.location.search);
|
||||
this.state = {
|
||||
projects: [],
|
||||
originalProject: this.queryParams.originalProject || 'mozilla-central',
|
||||
newProject: this.queryParams.newProject || 'try',
|
||||
originalRevision: this.queryParams.originalRevision || '',
|
||||
|
@ -42,37 +35,18 @@ export default class CompareSelectorView extends React.Component {
|
|||
missingRevision: false,
|
||||
framework: 1,
|
||||
frameworkName: 'talos',
|
||||
frameworks: [],
|
||||
frameworkDropdownIsOpen: false,
|
||||
};
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
const { errorMessages } = this.state;
|
||||
|
||||
const [projects, frameworks] = await Promise.all([
|
||||
getData(getApiUrl(repoEndpoint)),
|
||||
getData(getApiUrl(endpoints.frameworks)),
|
||||
]);
|
||||
|
||||
const updates = {
|
||||
...processResponse(projects, 'projects', errorMessages),
|
||||
...processResponse(frameworks, 'frameworks', errorMessages),
|
||||
};
|
||||
|
||||
this.setState(updates);
|
||||
}
|
||||
|
||||
updateFramework = selection => {
|
||||
this.setState(prevState => {
|
||||
const selectedFramework = prevState.frameworks.find(
|
||||
framework => framework.name === selection,
|
||||
);
|
||||
const selectedFramework = this.props.frameworks.find(
|
||||
framework => framework.name === selection,
|
||||
);
|
||||
|
||||
return {
|
||||
framework: selectedFramework.id,
|
||||
frameworkName: selectedFramework.name,
|
||||
};
|
||||
this.setState({
|
||||
framework: selectedFramework.id,
|
||||
frameworkName: selectedFramework.name,
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -84,29 +58,31 @@ export default class CompareSelectorView extends React.Component {
|
|||
newRevision,
|
||||
framework,
|
||||
} = this.state;
|
||||
const { $state } = this.props;
|
||||
const { history } = this.props;
|
||||
let params;
|
||||
|
||||
if (newRevision === '') {
|
||||
return this.setState({ missingRevision: 'Revision is required' });
|
||||
}
|
||||
|
||||
if (originalRevision !== '') {
|
||||
$state.go('compare', {
|
||||
params = {
|
||||
originalProject,
|
||||
originalRevision,
|
||||
newProject,
|
||||
newRevision,
|
||||
framework,
|
||||
});
|
||||
};
|
||||
} else {
|
||||
$state.go('compare', {
|
||||
params = {
|
||||
originalProject,
|
||||
newProject,
|
||||
newRevision,
|
||||
framework,
|
||||
selectedTimeRange: compareDefaultTimeRange.value,
|
||||
});
|
||||
};
|
||||
}
|
||||
history.push(`/compare${createQueryParams(params)}`);
|
||||
};
|
||||
|
||||
toggleFrameworkDropdown = () => {
|
||||
|
@ -119,9 +95,7 @@ export default class CompareSelectorView extends React.Component {
|
|||
const {
|
||||
originalProject,
|
||||
newProject,
|
||||
projects,
|
||||
frameworkName,
|
||||
frameworks,
|
||||
originalRevision,
|
||||
newRevision,
|
||||
errorMessages,
|
||||
|
@ -130,9 +104,11 @@ export default class CompareSelectorView extends React.Component {
|
|||
frameworkDropdownIsOpen,
|
||||
} = this.state;
|
||||
|
||||
const { projects, frameworks } = this.props;
|
||||
const frameworkNames = frameworks.length
|
||||
? frameworks.map(item => item.name)
|
||||
: [];
|
||||
|
||||
return (
|
||||
<Container fluid className="my-5 pt-5 max-width-default">
|
||||
<ErrorBoundary
|
||||
|
@ -147,35 +123,34 @@ export default class CompareSelectorView extends React.Component {
|
|||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
{projects.length > 0 && (
|
||||
<Row className="justify-content-center">
|
||||
<SelectorCard
|
||||
projects={projects}
|
||||
updateState={updates => this.setState(updates)}
|
||||
selectedRepo={originalProject}
|
||||
title="Base"
|
||||
checkbox
|
||||
text="By default, Perfherder will compare against performance data gathered over the last 2 days from when new revision was pushed"
|
||||
projectState="originalProject"
|
||||
revisionState="originalRevision"
|
||||
selectedRevision={originalRevision}
|
||||
queryParam={this.props.$stateParams.originalRevision}
|
||||
errorMessages={errorMessages}
|
||||
/>
|
||||
<SelectorCard
|
||||
projects={projects}
|
||||
updateState={updates => this.setState(updates)}
|
||||
selectedRepo={newProject}
|
||||
title="New"
|
||||
projectState="newProject"
|
||||
revisionState="newRevision"
|
||||
selectedRevision={newRevision}
|
||||
errorMessages={errorMessages}
|
||||
missingRevision={missingRevision}
|
||||
/>
|
||||
</Row>
|
||||
)}
|
||||
<Row className="justify-content-center">
|
||||
<SelectorCard
|
||||
projects={projects}
|
||||
updateState={updates => this.setState(updates)}
|
||||
selectedRepo={originalProject}
|
||||
title="Base"
|
||||
checkbox
|
||||
text="By default, Perfherder will compare against performance data gathered over the last 2 days from when new revision was pushed"
|
||||
projectState="originalProject"
|
||||
revisionState="originalRevision"
|
||||
selectedRevision={originalRevision}
|
||||
queryParam={this.queryParams.originalRevision}
|
||||
errorMessages={errorMessages}
|
||||
/>
|
||||
<SelectorCard
|
||||
projects={projects}
|
||||
updateState={updates => this.setState(updates)}
|
||||
selectedRepo={newProject}
|
||||
title="New"
|
||||
projectState="newProject"
|
||||
revisionState="newRevision"
|
||||
selectedRevision={newRevision}
|
||||
errorMessages={errorMessages}
|
||||
missingRevision={missingRevision}
|
||||
/>
|
||||
</Row>
|
||||
|
||||
<Row className="justify-content-center pt-3">
|
||||
<Col sm="8" className="text-right px-1">
|
||||
<ButtonGroup>
|
||||
<ButtonDropdown
|
||||
|
@ -208,18 +183,3 @@ export default class CompareSelectorView extends React.Component {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
CompareSelectorView.propTypes = {
|
||||
$stateParams: PropTypes.shape({
|
||||
newRevision: PropTypes.string,
|
||||
originalRevision: PropTypes.string,
|
||||
newProject: PropTypes.string,
|
||||
originalProject: PropTypes.string,
|
||||
}).isRequired,
|
||||
$state: PropTypes.shape({}).isRequired,
|
||||
};
|
||||
|
||||
perf.component(
|
||||
'compareSelectorView',
|
||||
react2angular(CompareSelectorView, [], ['$stateParams', '$state']),
|
||||
);
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { react2angular } from 'react2angular/index.es2015';
|
||||
import { Container, Row } from 'reactstrap';
|
||||
|
||||
import perf from '../../js/perf';
|
||||
import RepositoryModel from '../../models/repository';
|
||||
import PushModel from '../../models/push';
|
||||
import { getData } from '../../helpers/http';
|
||||
import { createApiUrl, perfSummaryEndpoint } from '../../helpers/url';
|
||||
import {
|
||||
createApiUrl,
|
||||
perfSummaryEndpoint,
|
||||
parseQueryParams,
|
||||
} from '../../helpers/url';
|
||||
import LoadingSpinner from '../../shared/LoadingSpinner';
|
||||
|
||||
import RevisionInformation from './RevisionInformation';
|
||||
import ReplicatesGraph from './ReplicatesGraph';
|
||||
|
||||
// TODO remove $stateParams after switching to react router
|
||||
export default class CompareSubtestDistributionView extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
@ -32,7 +32,8 @@ export default class CompareSubtestDistributionView extends React.Component {
|
|||
originalRevision,
|
||||
newRevision,
|
||||
newProject: newProjectName,
|
||||
} = this.props.$stateParams;
|
||||
} = parseQueryParams(this.props.location.search);
|
||||
|
||||
const { originalProject, newProject } = await this.fetchProjectsToCompare(
|
||||
originalProjectName,
|
||||
newProjectName,
|
||||
|
@ -100,20 +101,14 @@ export default class CompareSubtestDistributionView extends React.Component {
|
|||
return [originalSyncPromise, newSyncPromise];
|
||||
};
|
||||
|
||||
fetchAllRepositories = async () => {
|
||||
const loadRepositories = RepositoryModel.getList();
|
||||
const results = await Promise.all([loadRepositories]);
|
||||
return results[0];
|
||||
};
|
||||
|
||||
fetchProjectsToCompare = async (originalProjectName, newProjectName) => {
|
||||
const allRepos = await this.fetchAllRepositories();
|
||||
const { projects } = this.props;
|
||||
|
||||
const originalProject = RepositoryModel.getRepo(
|
||||
originalProjectName,
|
||||
allRepos,
|
||||
projects,
|
||||
);
|
||||
const newProject = RepositoryModel.getRepo(newProjectName, allRepos);
|
||||
const newProject = RepositoryModel.getRepo(newProjectName, projects);
|
||||
return { originalProject, newProject };
|
||||
};
|
||||
|
||||
|
@ -155,7 +150,7 @@ export default class CompareSubtestDistributionView extends React.Component {
|
|||
newRevision,
|
||||
originalSubtestSignature,
|
||||
newSubtestSignature,
|
||||
} = this.props.$stateParams;
|
||||
} = parseQueryParams(this.props.location.search);
|
||||
|
||||
return (
|
||||
originalRevision &&
|
||||
|
@ -201,21 +196,3 @@ export default class CompareSubtestDistributionView extends React.Component {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
CompareSubtestDistributionView.propTypes = {
|
||||
$stateParams: PropTypes.shape({
|
||||
originalProject: PropTypes.string,
|
||||
newProject: PropTypes.string,
|
||||
originalRevision: PropTypes.string,
|
||||
newRevision: PropTypes.string,
|
||||
originalResultSet: PropTypes.object,
|
||||
newResultSet: PropTypes.object,
|
||||
originalSubtestSignature: PropTypes.string,
|
||||
newSubtestSignature: PropTypes.string,
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
perf.component(
|
||||
'compareSubtestDistributionView',
|
||||
react2angular(CompareSubtestDistributionView, [], ['$stateParams']),
|
||||
);
|
||||
|
|
|
@ -1,22 +1,20 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { react2angular } from 'react2angular/index.es2015';
|
||||
import difference from 'lodash/difference';
|
||||
|
||||
import perf from '../../js/perf';
|
||||
import { createQueryParams } from '../../helpers/url';
|
||||
import {
|
||||
createNoiseMetric,
|
||||
getCounterMap,
|
||||
createGraphsLinks,
|
||||
onPermalinkClick,
|
||||
} from '../helpers';
|
||||
import { noiseMetricTitle } from '../constants';
|
||||
import withValidation from '../Validation';
|
||||
|
||||
import CompareTableView from './CompareTableView';
|
||||
|
||||
// TODO remove $stateParams and $state after switching to react router
|
||||
export class CompareSubtestsView extends React.PureComponent {
|
||||
class CompareSubtestsView extends React.PureComponent {
|
||||
createQueryParams = (parent_signature, repository, framework) => ({
|
||||
parent_signature,
|
||||
framework,
|
||||
|
@ -43,7 +41,6 @@ export class CompareSubtestsView extends React.PureComponent {
|
|||
if (originalRevision) {
|
||||
originalParams.revision = originalRevision;
|
||||
} else {
|
||||
// can create a helper function for both views
|
||||
const startDateMs =
|
||||
(newResultSet.push_timestamp - timeRange.value) * 1000;
|
||||
const endDateMs = newResultSet.push_timestamp * 1000;
|
||||
|
@ -211,6 +208,8 @@ export class CompareSubtestsView extends React.PureComponent {
|
|||
{...this.props}
|
||||
getQueryParams={this.getQueryParams}
|
||||
getDisplayResults={this.getDisplayResults}
|
||||
onPermalinkClick={hashValue => onPermalinkClick(hashValue, this.props)}
|
||||
hashFragment={this.props.location.hash}
|
||||
hasSubtests
|
||||
/>
|
||||
);
|
||||
|
@ -225,20 +224,14 @@ CompareSubtestsView.propTypes = {
|
|||
originalProject: PropTypes.string,
|
||||
newProject: PropTypes.string,
|
||||
originalRevision: PropTypes.string,
|
||||
projects: PropTypes.arrayOf(PropTypes.shape({})),
|
||||
updateParams: PropTypes.func.isRequired,
|
||||
newSignature: PropTypes.string,
|
||||
originalSignature: PropTypes.string,
|
||||
}),
|
||||
$stateParams: PropTypes.shape({}),
|
||||
$state: PropTypes.shape({}),
|
||||
user: PropTypes.shape({}).isRequired,
|
||||
};
|
||||
|
||||
CompareSubtestsView.defaultProps = {
|
||||
validated: PropTypes.shape({}),
|
||||
$stateParams: null,
|
||||
$state: null,
|
||||
};
|
||||
|
||||
const requiredParams = new Set([
|
||||
|
@ -249,11 +242,4 @@ const requiredParams = new Set([
|
|||
'newSignature',
|
||||
]);
|
||||
|
||||
const compareSubtestsView = withValidation(requiredParams)(CompareSubtestsView);
|
||||
|
||||
perf.component(
|
||||
'compareSubtestsView',
|
||||
react2angular(compareSubtestsView, ['user'], ['$stateParams', '$state']),
|
||||
);
|
||||
|
||||
export default compareSubtestsView;
|
||||
export default withValidation({ requiredParams })(CompareSubtestsView);
|
||||
|
|
|
@ -9,12 +9,12 @@ import {
|
|||
faHashtag,
|
||||
} from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
import JobModel from '../../models/job';
|
||||
import SimpleTooltip from '../../shared/SimpleTooltip';
|
||||
import { displayNumber } from '../helpers';
|
||||
import { compareTableText } from '../constants';
|
||||
import ProgressBar from '../ProgressBar';
|
||||
import { hashFunction } from '../../helpers/utils';
|
||||
import JobModel from '../../models/job';
|
||||
|
||||
import TableAverage from './TableAverage';
|
||||
|
||||
|
@ -33,7 +33,7 @@ export default class CompareTable extends React.PureComponent {
|
|||
displayNumber(percentage),
|
||||
)}% ${improvement ? 'better' : 'worse'})`;
|
||||
|
||||
// humane readable signature name
|
||||
// human readable signature name
|
||||
getSignatureName = (testName, platformName) =>
|
||||
[testName, platformName].filter(item => item !== null).join(' ');
|
||||
|
||||
|
@ -100,14 +100,18 @@ export default class CompareTable extends React.PureComponent {
|
|||
<tr className="subtest-header bg-lightgray">
|
||||
<th className="text-left">
|
||||
<span>{testName}</span>
|
||||
<Button
|
||||
className="permalink p-0 ml-1"
|
||||
color="link"
|
||||
onClick={() => onPermalinkClick(this.getHashBasedId(testName))}
|
||||
title="Permalink to this test table"
|
||||
>
|
||||
<FontAwesomeIcon icon={faHashtag} />
|
||||
</Button>
|
||||
{onPermalinkClick && (
|
||||
<Button
|
||||
className="permalink p-0 ml-1"
|
||||
color="link"
|
||||
onClick={() =>
|
||||
onPermalinkClick(this.getHashBasedId(testName))
|
||||
}
|
||||
title="Permalink to this test table"
|
||||
>
|
||||
<FontAwesomeIcon icon={faHashtag} />
|
||||
</Button>
|
||||
)}
|
||||
</th>
|
||||
<th className="table-width-lg">Base</th>
|
||||
{/* empty for less than/greater than data */}
|
||||
|
@ -145,20 +149,22 @@ export default class CompareTable extends React.PureComponent {
|
|||
<th className="text-left font-weight-normal pl-1">
|
||||
{rowLevelResults.name}
|
||||
<span className="result-links">
|
||||
<span>
|
||||
<Button
|
||||
className="permalink p-0 ml-1"
|
||||
color="link"
|
||||
onClick={() =>
|
||||
onPermalinkClick(
|
||||
this.getHashBasedId(testName, rowLevelResults.name),
|
||||
)
|
||||
}
|
||||
title="Permalink to this test"
|
||||
>
|
||||
<FontAwesomeIcon icon={faHashtag} />
|
||||
</Button>
|
||||
</span>
|
||||
{onPermalinkClick && (
|
||||
<span>
|
||||
<Button
|
||||
className="permalink p-0 ml-1"
|
||||
color="link"
|
||||
onClick={() =>
|
||||
onPermalinkClick(
|
||||
this.getHashBasedId(testName, rowLevelResults.name),
|
||||
)
|
||||
}
|
||||
title="Permalink to this test"
|
||||
>
|
||||
<FontAwesomeIcon icon={faHashtag} />
|
||||
</Button>
|
||||
</span>
|
||||
)}
|
||||
{rowLevelResults.links &&
|
||||
rowLevelResults.links.map(link => (
|
||||
<span key={link.title}>
|
||||
|
@ -289,20 +295,15 @@ CompareTable.propTypes = {
|
|||
data: PropTypes.arrayOf(PropTypes.shape({})),
|
||||
testName: PropTypes.string.isRequired,
|
||||
hashFunction: PropTypes.func,
|
||||
onPermalinkClick: PropTypes.func.isRequired,
|
||||
user: PropTypes.shape({}).isRequired,
|
||||
isBaseAggregate: PropTypes.bool.isRequired,
|
||||
notify: PropTypes.func,
|
||||
hasSubtests: PropTypes.bool,
|
||||
retriggerJob: PropTypes.func,
|
||||
onPermalinkClick: PropTypes.func,
|
||||
getJob: PropTypes.func,
|
||||
retriggerJob: PropTypes.func,
|
||||
};
|
||||
|
||||
CompareTable.defaultProps = {
|
||||
data: null,
|
||||
hashFunction,
|
||||
notify: null,
|
||||
hasSubtests: false,
|
||||
retriggerJob: JobModel.retrigger,
|
||||
onPermalinkClick: undefined,
|
||||
getJob: JobModel.get,
|
||||
retriggerJob: JobModel.retrigger,
|
||||
};
|
||||
|
|
|
@ -202,7 +202,7 @@ CompareTableControls.propTypes = {
|
|||
PropTypes.shape({}),
|
||||
PropTypes.bool,
|
||||
]),
|
||||
onPermalinkClick: PropTypes.func.isRequired,
|
||||
onPermalinkClick: PropTypes.func,
|
||||
};
|
||||
|
||||
CompareTableControls.defaultProps = {
|
||||
|
@ -215,4 +215,5 @@ CompareTableControls.defaultProps = {
|
|||
showOnlyNoise: undefined,
|
||||
},
|
||||
showTestsWithNoise: null,
|
||||
onPermalinkClick: undefined,
|
||||
};
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Alert, Col, Row, Container } from 'reactstrap';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import ErrorMessages from '../../shared/ErrorMessages';
|
||||
import {
|
||||
genericErrorMessage,
|
||||
errorMessageClass,
|
||||
compareDefaultTimeRange,
|
||||
phTimeRanges,
|
||||
} from '../../helpers/constants';
|
||||
import { compareDefaultTimeRange, phTimeRanges } from '../constants';
|
||||
import ErrorBoundary from '../../shared/ErrorBoundary';
|
||||
import { getData } from '../../helpers/http';
|
||||
import {
|
||||
|
@ -25,7 +25,6 @@ import RevisionInformation from './RevisionInformation';
|
|||
import CompareTableControls from './CompareTableControls';
|
||||
import NoiseTable from './NoiseTable';
|
||||
|
||||
// TODO remove $stateParams and $state after switching to react router
|
||||
export default class CompareTableView extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
@ -36,25 +35,35 @@ export default class CompareTableView extends React.Component {
|
|||
failureMessages: [],
|
||||
loading: false,
|
||||
timeRange: this.setTimeRange(),
|
||||
framework: getFrameworkData(this.props.validated),
|
||||
framework: getFrameworkData(this.props),
|
||||
title: '',
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.getPerformanceData();
|
||||
const { compareData, location } = this.props;
|
||||
|
||||
if (
|
||||
compareData &&
|
||||
compareData.size > 0 &&
|
||||
location.pathname === '/compare'
|
||||
) {
|
||||
this.setState({ compareResults: compareData });
|
||||
} else {
|
||||
this.getPerformanceData();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const { loading } = this.state;
|
||||
const { hashFragment } = this.props;
|
||||
|
||||
if (this.props !== prevProps) {
|
||||
if (this.props.location.search !== prevProps.location.search) {
|
||||
this.getPerformanceData();
|
||||
}
|
||||
|
||||
if (!loading && hashFragment) {
|
||||
scrollToLine(`#${hashFragment}`, 100);
|
||||
scrollToLine(hashFragment, 100);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -146,7 +155,9 @@ export default class CompareTableView extends React.Component {
|
|||
};
|
||||
|
||||
updateFramework = selection => {
|
||||
const { frameworks, updateParams } = this.props.validated;
|
||||
const { updateParams } = this.props.validated;
|
||||
const { frameworks } = this.props;
|
||||
|
||||
const framework = frameworks.find(item => item.name === selection);
|
||||
|
||||
updateParams({ framework: framework.id });
|
||||
|
@ -178,10 +189,14 @@ export default class CompareTableView extends React.Component {
|
|||
newRevision,
|
||||
originalResultSet,
|
||||
newResultSet,
|
||||
frameworks,
|
||||
} = this.props.validated;
|
||||
|
||||
const { filterByFramework, hasSubtests, onPermalinkClick } = this.props;
|
||||
const {
|
||||
filterByFramework,
|
||||
hasSubtests,
|
||||
onPermalinkClick,
|
||||
frameworks,
|
||||
} = this.props;
|
||||
const {
|
||||
compareResults,
|
||||
loading,
|
||||
|
@ -198,7 +213,12 @@ export default class CompareTableView extends React.Component {
|
|||
|
||||
const compareDropdowns = [];
|
||||
|
||||
const params = { originalProject, newProject, newRevision };
|
||||
const params = {
|
||||
originalProject,
|
||||
newProject,
|
||||
newRevision,
|
||||
framework: framework.id,
|
||||
};
|
||||
|
||||
if (originalRevision) {
|
||||
params.originalRevision = originalRevision;
|
||||
|
@ -231,11 +251,14 @@ export default class CompareTableView extends React.Component {
|
|||
>
|
||||
<React.Fragment>
|
||||
{hasSubtests && (
|
||||
<p>
|
||||
<a href={`perf.html#/compare${createQueryParams(params)}`}>
|
||||
Show all tests and platforms
|
||||
</a>
|
||||
</p>
|
||||
<Link
|
||||
to={{
|
||||
pathname: '/compare',
|
||||
search: createQueryParams(params),
|
||||
}}
|
||||
>
|
||||
Back to all tests and platforms
|
||||
</Link>
|
||||
)}
|
||||
|
||||
<div className="mx-auto">
|
||||
|
@ -318,8 +341,6 @@ CompareTableView.propTypes = {
|
|||
originalProject: PropTypes.string,
|
||||
newProject: PropTypes.string,
|
||||
originalRevision: PropTypes.string,
|
||||
projects: PropTypes.arrayOf(PropTypes.shape({})),
|
||||
frameworks: PropTypes.arrayOf(PropTypes.shape({})),
|
||||
selectedTimeRange: PropTypes.string,
|
||||
updateParams: PropTypes.func.isRequired,
|
||||
originalSignature: PropTypes.string,
|
||||
|
@ -332,9 +353,9 @@ CompareTableView.propTypes = {
|
|||
getDisplayResults: PropTypes.func.isRequired,
|
||||
getQueryParams: PropTypes.func.isRequired,
|
||||
hasSubtests: PropTypes.bool,
|
||||
onPermalinkClick: PropTypes.func.isRequired,
|
||||
onPermalinkClick: PropTypes.func,
|
||||
hashFragment: PropTypes.string,
|
||||
$stateParams: PropTypes.shape({}).isRequired,
|
||||
frameworks: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
|
||||
};
|
||||
|
||||
CompareTableView.defaultProps = {
|
||||
|
@ -343,4 +364,5 @@ CompareTableView.defaultProps = {
|
|||
validated: PropTypes.shape({}),
|
||||
hasSubtests: false,
|
||||
hashFragment: '',
|
||||
onPermalinkClick: undefined,
|
||||
};
|
||||
|
|
|
@ -1,23 +1,20 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { react2angular } from 'react2angular/index.es2015';
|
||||
import difference from 'lodash/difference';
|
||||
|
||||
import perf from '../../js/perf';
|
||||
import { createQueryParams } from '../../helpers/url';
|
||||
import { phTimeRanges } from '../../helpers/constants';
|
||||
import {
|
||||
createNoiseMetric,
|
||||
getCounterMap,
|
||||
createGraphsLinks,
|
||||
onPermalinkClick,
|
||||
} from '../helpers';
|
||||
import { noiseMetricTitle } from '../constants';
|
||||
import { noiseMetricTitle, phTimeRanges } from '../constants';
|
||||
import withValidation from '../Validation';
|
||||
|
||||
import CompareTableView from './CompareTableView';
|
||||
|
||||
// TODO remove $location, $scope, $stateParams and $state after switching to react router
|
||||
export class CompareView extends React.PureComponent {
|
||||
class CompareView extends React.PureComponent {
|
||||
getInterval = (oldTimestamp, newTimestamp) => {
|
||||
const now = new Date().getTime() / 1000;
|
||||
let timeRange = Math.min(oldTimestamp, newTimestamp);
|
||||
|
@ -106,6 +103,7 @@ export class CompareView extends React.PureComponent {
|
|||
} else {
|
||||
params.selectedTimeRange = timeRange.value;
|
||||
}
|
||||
|
||||
const detailsLink = `perf.html#/comparesubtest${createQueryParams(
|
||||
params,
|
||||
)}`;
|
||||
|
@ -215,6 +213,7 @@ export class CompareView extends React.PureComponent {
|
|||
|
||||
compareResults = new Map([...compareResults.entries()].sort());
|
||||
const updates = { compareResults, testsWithNoise, loading: false };
|
||||
this.props.updateAppState({ compareData: compareResults });
|
||||
|
||||
const resultsArr = Array.from(compareResults.keys());
|
||||
const testsNoResults = difference(tableNames, resultsArr)
|
||||
|
@ -228,17 +227,7 @@ export class CompareView extends React.PureComponent {
|
|||
return updates;
|
||||
};
|
||||
|
||||
onPermalinkClick = hashBasedValue => {
|
||||
const { $location, $scope } = this.props;
|
||||
|
||||
$location.hash(hashBasedValue);
|
||||
$scope.$apply();
|
||||
};
|
||||
|
||||
getHashFragment = () => {
|
||||
const { $location } = this.props;
|
||||
return $location.hash();
|
||||
};
|
||||
getHashFragment = () => this.props.location.hash;
|
||||
|
||||
render() {
|
||||
return (
|
||||
|
@ -246,8 +235,8 @@ export class CompareView extends React.PureComponent {
|
|||
{...this.props}
|
||||
getQueryParams={this.getQueryParams}
|
||||
getDisplayResults={this.getDisplayResults}
|
||||
onPermalinkClick={this.onPermalinkClick}
|
||||
hashFragment={this.getHashFragment()}
|
||||
onPermalinkClick={hashValue => onPermalinkClick(hashValue, this.props)}
|
||||
hashFragment={this.props.location.hash}
|
||||
filterByFramework
|
||||
/>
|
||||
);
|
||||
|
@ -262,20 +251,13 @@ CompareView.propTypes = {
|
|||
originalProject: PropTypes.string,
|
||||
newProject: PropTypes.string,
|
||||
originalRevision: PropTypes.string,
|
||||
projects: PropTypes.arrayOf(PropTypes.shape({})),
|
||||
frameworks: PropTypes.arrayOf(PropTypes.shape({})),
|
||||
framework: PropTypes.string,
|
||||
updateParams: PropTypes.func.isRequired,
|
||||
}),
|
||||
$stateParams: PropTypes.shape({}),
|
||||
$state: PropTypes.shape({}),
|
||||
user: PropTypes.shape({}).isRequired,
|
||||
};
|
||||
|
||||
CompareView.defaultProps = {
|
||||
validated: PropTypes.shape({}),
|
||||
$stateParams: null,
|
||||
$state: null,
|
||||
};
|
||||
|
||||
const requiredParams = new Set([
|
||||
|
@ -284,15 +266,4 @@ const requiredParams = new Set([
|
|||
'newRevision',
|
||||
]);
|
||||
|
||||
const compareView = withValidation(requiredParams)(CompareView);
|
||||
|
||||
perf.component(
|
||||
'compareView',
|
||||
react2angular(
|
||||
compareView,
|
||||
['user'],
|
||||
['$location', '$scope', '$stateParams', '$state'],
|
||||
),
|
||||
);
|
||||
|
||||
export default compareView;
|
||||
export default withValidation({ requiredParams })(CompareView);
|
||||
|
|
|
@ -10,7 +10,6 @@ import { createApiUrl, perfSummaryEndpoint } from '../../helpers/url';
|
|||
import { noDataFoundMessage } from '../constants';
|
||||
import LoadingSpinner from '../../shared/LoadingSpinner';
|
||||
|
||||
// TODO remove $stateParams after switching to react router
|
||||
export default class ReplicatesGraph extends React.Component {
|
||||
// TODO: sync parent with children IRT dataLoading
|
||||
constructor(props) {
|
||||
|
|
|
@ -11,7 +11,7 @@ function getRevisionSpecificDetails(
|
|||
resultSet,
|
||||
selectedTimeRange = undefined,
|
||||
) {
|
||||
const truncatedRevision = revision.substring(0, 12);
|
||||
const truncatedRevision = revision ? revision.substring(0, 12) : '';
|
||||
const baselineOrNew = isBaseline || selectedTimeRange ? 'Base' : 'New';
|
||||
|
||||
return (
|
||||
|
|
|
@ -200,7 +200,7 @@ export default class SelectorCard extends React.Component {
|
|||
missingRevision,
|
||||
} = this.props;
|
||||
return (
|
||||
<Col sm="4" className="p-2">
|
||||
<Col sm="4" className="p-2 text-left">
|
||||
<Card className="card-height">
|
||||
<CardHeader className="bg-lightgray">{title}</CardHeader>
|
||||
<CardBody>
|
||||
|
|
|
@ -64,3 +64,28 @@ export const graphColors = [
|
|||
['darkorchid', '#9932cc'],
|
||||
['blue', '#1752b8'],
|
||||
];
|
||||
|
||||
export const phFrameworksWithRelatedBranches = [
|
||||
1, // talos
|
||||
10, // raptor
|
||||
11, // js-bench
|
||||
12, // devtools
|
||||
];
|
||||
|
||||
export const phTimeRanges = [
|
||||
{ value: 86400, text: 'Last day' },
|
||||
{ value: 86400 * 2, text: 'Last 2 days' },
|
||||
{ value: 604800, text: 'Last 7 days' },
|
||||
{ value: 1209600, text: 'Last 14 days' },
|
||||
{ value: 2592000, text: 'Last 30 days' },
|
||||
{ value: 5184000, text: 'Last 60 days' },
|
||||
{ value: 7776000, text: 'Last 90 days' },
|
||||
{ value: 31536000, text: 'Last year' },
|
||||
];
|
||||
|
||||
export const phDefaultTimeRangeValue = 1209600;
|
||||
|
||||
export const compareDefaultTimeRange = {
|
||||
value: 86400 * 2,
|
||||
text: 'Last 2 days',
|
||||
};
|
||||
|
|
|
@ -1,26 +1,29 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { react2angular } from 'react2angular/index.es2015';
|
||||
import { Container, Col, Row } from 'reactstrap';
|
||||
import unionBy from 'lodash/unionBy';
|
||||
import queryString from 'query-string';
|
||||
|
||||
import { getData, processResponse, processErrors } from '../../helpers/http';
|
||||
import {
|
||||
getApiUrl,
|
||||
repoEndpoint,
|
||||
createApiUrl,
|
||||
perfSummaryEndpoint,
|
||||
createQueryParams,
|
||||
parseQueryParams,
|
||||
updateQueryParams,
|
||||
} from '../../helpers/url';
|
||||
import {
|
||||
phTimeRanges,
|
||||
phDefaultTimeRangeValue,
|
||||
genericErrorMessage,
|
||||
errorMessageClass,
|
||||
} from '../../helpers/constants';
|
||||
import perf from '../../js/perf';
|
||||
import { processSelectedParam } from '../helpers';
|
||||
import { endpoints, graphColors } from '../constants';
|
||||
import {
|
||||
endpoints,
|
||||
graphColors,
|
||||
phTimeRanges,
|
||||
phDefaultTimeRangeValue,
|
||||
} from '../constants';
|
||||
import ErrorMessages from '../../shared/ErrorMessages';
|
||||
import ErrorBoundary from '../../shared/ErrorBoundary';
|
||||
import LoadingSpinner from '../../shared/LoadingSpinner';
|
||||
|
@ -34,8 +37,6 @@ class GraphsView extends React.Component {
|
|||
super(props);
|
||||
this.state = {
|
||||
timeRange: this.getDefaultTimeRange(),
|
||||
frameworks: [],
|
||||
projects: [],
|
||||
zoom: {},
|
||||
selectedDataPoint: null,
|
||||
highlightAlerts: true,
|
||||
|
@ -51,32 +52,19 @@ class GraphsView extends React.Component {
|
|||
}
|
||||
|
||||
async componentDidMount() {
|
||||
this.getData();
|
||||
this.checkQueryParams();
|
||||
}
|
||||
|
||||
getDefaultTimeRange = () => {
|
||||
const { $stateParams } = this.props;
|
||||
const { location } = this.props;
|
||||
const { timerange } = parseQueryParams(location.search);
|
||||
|
||||
const defaultValue = $stateParams.timerange
|
||||
? parseInt($stateParams.timerange, 10)
|
||||
const defaultValue = timerange
|
||||
? parseInt(timerange, 10)
|
||||
: phDefaultTimeRangeValue;
|
||||
return phTimeRanges.find(time => time.value === defaultValue);
|
||||
};
|
||||
|
||||
async getData() {
|
||||
const [projects, frameworks] = await Promise.all([
|
||||
getData(getApiUrl(repoEndpoint)),
|
||||
getData(getApiUrl(endpoints.frameworks)),
|
||||
]);
|
||||
|
||||
const updates = {
|
||||
...processResponse(projects, 'projects'),
|
||||
...processResponse(frameworks, 'frameworks'),
|
||||
};
|
||||
this.setState(updates);
|
||||
}
|
||||
|
||||
checkQueryParams = () => {
|
||||
const {
|
||||
series,
|
||||
|
@ -84,7 +72,7 @@ class GraphsView extends React.Component {
|
|||
selected,
|
||||
highlightAlerts,
|
||||
highlightedRevisions,
|
||||
} = this.props.$stateParams;
|
||||
} = queryString.parse(this.props.location.search);
|
||||
|
||||
const updates = {};
|
||||
|
||||
|
@ -271,11 +259,7 @@ class GraphsView extends React.Component {
|
|||
|
||||
parseSeriesParam = series =>
|
||||
series.map(encodedSeries => {
|
||||
const partialSeriesString = decodeURIComponent(encodedSeries).replace(
|
||||
/[[\]"]/g,
|
||||
'',
|
||||
);
|
||||
const partialSeriesArray = partialSeriesString.split(',');
|
||||
const partialSeriesArray = encodedSeries.split(',');
|
||||
const partialSeriesObject = {
|
||||
repository_name: partialSeriesArray[0],
|
||||
// TODO deprecate signature_hash
|
||||
|
@ -299,14 +283,11 @@ class GraphsView extends React.Component {
|
|||
};
|
||||
|
||||
updateParams = params => {
|
||||
const { transitionTo, current } = this.props.$state;
|
||||
const { location, history } = this.props;
|
||||
let newQueryString = queryString.stringify(params);
|
||||
newQueryString = newQueryString.replace(/%2C/g, ',');
|
||||
|
||||
transitionTo('graphs', params, {
|
||||
location: true,
|
||||
inherit: true,
|
||||
relative: current,
|
||||
notify: false,
|
||||
});
|
||||
updateQueryParams(newQueryString, history, location);
|
||||
};
|
||||
|
||||
changeParams = () => {
|
||||
|
@ -325,21 +306,28 @@ class GraphsView extends React.Component {
|
|||
);
|
||||
const params = {
|
||||
series: newSeries,
|
||||
highlightedRevisions: highlightedRevisions.filter(rev => rev.length),
|
||||
highlightAlerts: +highlightAlerts,
|
||||
timerange: timeRange.value,
|
||||
zoom,
|
||||
};
|
||||
|
||||
const newHighlightedRevisions = highlightedRevisions.filter(
|
||||
rev => rev.length,
|
||||
);
|
||||
|
||||
if (newHighlightedRevisions.length) {
|
||||
params.highlightedRevisions = newHighlightedRevisions;
|
||||
}
|
||||
|
||||
if (!selectedDataPoint) {
|
||||
params.selected = null;
|
||||
delete params.selected;
|
||||
} else {
|
||||
const { signature_id, pushId, x, y } = selectedDataPoint;
|
||||
params.selected = [signature_id, pushId, x, y].join(',');
|
||||
}
|
||||
|
||||
if (Object.keys(zoom).length === 0) {
|
||||
params.zoom = null;
|
||||
delete params.zoom;
|
||||
} else {
|
||||
params.zoom = [...zoom.x.map(z => z.getTime()), ...zoom.y].toString();
|
||||
}
|
||||
|
@ -350,8 +338,6 @@ class GraphsView extends React.Component {
|
|||
render() {
|
||||
const {
|
||||
timeRange,
|
||||
projects,
|
||||
frameworks,
|
||||
testData,
|
||||
highlightAlerts,
|
||||
highlightedRevisions,
|
||||
|
@ -365,6 +351,7 @@ class GraphsView extends React.Component {
|
|||
visibilityChanged,
|
||||
} = this.state;
|
||||
|
||||
const { projects, frameworks } = this.props;
|
||||
return (
|
||||
<ErrorBoundary
|
||||
errorClasses={errorMessageClass}
|
||||
|
@ -464,7 +451,7 @@ class GraphsView extends React.Component {
|
|||
}
|
||||
|
||||
GraphsView.propTypes = {
|
||||
$stateParams: PropTypes.shape({
|
||||
location: PropTypes.shape({
|
||||
zoom: PropTypes.string,
|
||||
selected: PropTypes.string,
|
||||
highlightAlerts: PropTypes.string,
|
||||
|
@ -477,21 +464,11 @@ GraphsView.propTypes = {
|
|||
PropTypes.arrayOf(PropTypes.string),
|
||||
]),
|
||||
}),
|
||||
$state: PropTypes.shape({
|
||||
current: PropTypes.shape({}),
|
||||
transitionTo: PropTypes.func,
|
||||
}),
|
||||
user: PropTypes.shape({}).isRequired,
|
||||
};
|
||||
|
||||
GraphsView.defaultProps = {
|
||||
$stateParams: undefined,
|
||||
$state: undefined,
|
||||
location: undefined,
|
||||
};
|
||||
|
||||
perf.component(
|
||||
'graphsView',
|
||||
react2angular(GraphsView, ['user'], ['$stateParams', '$state']),
|
||||
);
|
||||
|
||||
export default GraphsView;
|
||||
|
|
|
@ -10,7 +10,7 @@ import {
|
|||
Input,
|
||||
} from 'reactstrap';
|
||||
|
||||
import { phTimeRanges } from '../../helpers/constants';
|
||||
import { phTimeRanges } from '../constants';
|
||||
import DropdownMenuItems from '../../shared/DropdownMenuItems';
|
||||
|
||||
import TestDataModal from './TestDataModal';
|
||||
|
@ -34,21 +34,13 @@ export default class GraphsViewControls extends React.Component {
|
|||
highlightedRevisions,
|
||||
updateTimeRange,
|
||||
hasNoData,
|
||||
projects,
|
||||
frameworks,
|
||||
toggle,
|
||||
showModal,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<Container fluid className="justify-content-start">
|
||||
{projects.length > 0 && frameworks.length > 0 && (
|
||||
<TestDataModal
|
||||
showModal={showModal}
|
||||
toggle={toggle}
|
||||
{...this.props}
|
||||
/>
|
||||
)}
|
||||
<TestDataModal showModal={showModal} toggle={toggle} {...this.props} />
|
||||
<Row className="pb-3">
|
||||
<Col sm="auto" className="pl-0 py-2 pr-2" key={timeRange}>
|
||||
<UncontrolledDropdown
|
||||
|
@ -134,21 +126,17 @@ GraphsViewControls.propTypes = {
|
|||
]).isRequired,
|
||||
updateTimeRange: PropTypes.func.isRequired,
|
||||
hasNoData: PropTypes.bool.isRequired,
|
||||
projects: PropTypes.arrayOf(PropTypes.shape({})),
|
||||
getTestData: PropTypes.func.isRequired,
|
||||
options: PropTypes.shape({
|
||||
option: PropTypes.string,
|
||||
relatedSeries: PropTypes.shape({}),
|
||||
}),
|
||||
testData: PropTypes.arrayOf(PropTypes.shape({})),
|
||||
frameworks: PropTypes.arrayOf(PropTypes.shape({})),
|
||||
showModal: PropTypes.bool,
|
||||
toggle: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
GraphsViewControls.defaultProps = {
|
||||
frameworks: [],
|
||||
projects: [],
|
||||
options: undefined,
|
||||
testData: [],
|
||||
showModal: false,
|
||||
|
|
|
@ -8,11 +8,7 @@ import PerfSeriesModel, {
|
|||
getSeriesName,
|
||||
getTestName,
|
||||
} from '../models/perfSeries';
|
||||
import {
|
||||
phFrameworksWithRelatedBranches,
|
||||
phTimeRanges,
|
||||
thPerformanceBranches,
|
||||
} from '../helpers/constants';
|
||||
import { thPerformanceBranches } from '../helpers/constants';
|
||||
|
||||
import {
|
||||
endpoints,
|
||||
|
@ -21,6 +17,8 @@ import {
|
|||
noiseMetricTitle,
|
||||
summaryStatusMap,
|
||||
alertStatusMap,
|
||||
phFrameworksWithRelatedBranches,
|
||||
phTimeRanges,
|
||||
} from './constants';
|
||||
|
||||
export const displayNumber = input =>
|
||||
|
@ -114,7 +112,7 @@ const analyzeSet = (values, testName) => {
|
|||
average,
|
||||
stddev,
|
||||
stddevPct: Math.round(calcPercentOf(stddev, average) * 100) / 100,
|
||||
// TODO verify this is needed
|
||||
|
||||
// We use slice to keep the original values at their original order
|
||||
// in case the order is important elsewhere.
|
||||
runs: values.slice().sort(numericCompare),
|
||||
|
@ -171,7 +169,6 @@ export const getCounterMap = function getCounterMap(
|
|||
originalData,
|
||||
newData,
|
||||
) {
|
||||
// TODO setting this value seems a bit odd, look into how its being used
|
||||
const cmap = { isEmpty: false };
|
||||
const hasOrig = originalData && originalData.values.length;
|
||||
const hasNew = newData && newData.values.length;
|
||||
|
@ -278,7 +275,7 @@ export const getCounterMap = function getCounterMap(
|
|||
return cmap;
|
||||
};
|
||||
|
||||
// TODO look into using signature_id instead of the hash
|
||||
// TODO change usage of signature_hash to signature.id
|
||||
export const getGraphsLink = function getGraphsLink(
|
||||
seriesList,
|
||||
resultSets,
|
||||
|
@ -329,7 +326,6 @@ export const createNoiseMetric = function createNoiseMetric(
|
|||
return compareResults;
|
||||
};
|
||||
|
||||
// TODO
|
||||
export const createGraphsLinks = (
|
||||
validatedProps,
|
||||
links,
|
||||
|
@ -368,7 +364,7 @@ export const createGraphsLinks = (
|
|||
return links;
|
||||
};
|
||||
|
||||
// TODO change all usage of signature_hash to signature.id
|
||||
// TODO change usage of signature_hash to signature.id
|
||||
// for originalSignature and newSignature query params
|
||||
const Alert = (alertData, optionCollectionMap) => ({
|
||||
...alertData,
|
||||
|
@ -377,7 +373,7 @@ const Alert = (alertData, optionCollectionMap) => ({
|
|||
}),
|
||||
});
|
||||
|
||||
// TODO look into using signature_id instead of the hash and remove all other params
|
||||
// TODO change usage of signature_hash to signature.id
|
||||
export const getGraphsURL = (
|
||||
alert,
|
||||
timeRange,
|
||||
|
@ -386,11 +382,9 @@ export const getGraphsURL = (
|
|||
) => {
|
||||
let url = `#/graphs?timerange=${timeRange}&series=${alertRepository},${alert.series_signature.id},1,${alert.series_signature.framework_id}`;
|
||||
|
||||
// TODO deprecate usage of signature hash
|
||||
// automatically add related branches (we take advantage of
|
||||
// the otherwise rather useless signature hash to avoid having to fetch this
|
||||
// information from the server)
|
||||
|
||||
if (phFrameworksWithRelatedBranches.includes(performanceFrameworkId)) {
|
||||
const branches =
|
||||
alertRepository === 'mozilla-beta'
|
||||
|
@ -537,11 +531,11 @@ export const convertParams = (params, value) =>
|
|||
Boolean(params[value] !== undefined && parseInt(params[value], 10));
|
||||
|
||||
export const getFrameworkData = props => {
|
||||
const { framework, frameworks } = props;
|
||||
const { validated, frameworks } = props;
|
||||
|
||||
if (framework) {
|
||||
if (validated.framework) {
|
||||
const frameworkObject = frameworks.find(
|
||||
item => item.id === parseInt(framework, 10),
|
||||
item => item.id === parseInt(validated.framework, 10),
|
||||
);
|
||||
return frameworkObject;
|
||||
}
|
||||
|
@ -619,3 +613,9 @@ export const getSeriesData = async (
|
|||
|
||||
return updates;
|
||||
};
|
||||
|
||||
export const onPermalinkClick = (hashBasedValue, props) => {
|
||||
const { history, location } = props;
|
||||
|
||||
history.replace(`${location.pathname}${location.search}#${hashBasedValue}`);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
import React from 'react';
|
||||
import { render } from 'react-dom';
|
||||
import 'bootstrap/dist/css/bootstrap.min.css';
|
||||
import 'react-table/react-table.css';
|
||||
|
||||
import '../css/treeherder-global.css';
|
||||
import '../css/treeherder-navbar.css';
|
||||
import '../css/perf.css';
|
||||
|
||||
import App from './App';
|
||||
|
||||
render(<App />, document.getElementById('root'));
|
69
yarn.lock
69
yarn.lock
|
@ -1154,11 +1154,6 @@
|
|||
"@testing-library/dom" "^6.1.0"
|
||||
"@types/testing-library__react" "^9.1.0"
|
||||
|
||||
"@types/angular@*", "@types/angular@^1.6.39":
|
||||
version "1.6.55"
|
||||
resolved "https://registry.yarnpkg.com/@types/angular/-/angular-1.6.55.tgz#1d745af6deadcb5c2c0f6f6e9d85bc18c25a520b"
|
||||
integrity sha512-jpQ5K6t76thd9Ook5x4Xj9K462hMje6/Q84SFxglBmaW8HOLblVnALpvg6w6aV3Oe6o9jWXnYH8gHHKrfSRxNQ==
|
||||
|
||||
"@types/babel__core@^7.1.0":
|
||||
version "7.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.0.tgz#710f2487dda4dcfd010ca6abb2b4dc7394365c51"
|
||||
|
@ -1231,18 +1226,6 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.3.tgz#bdfd69d61e464dcc81b25159c270d75a73c1a636"
|
||||
integrity sha512-Il2DtDVRGDcqjDtE+rF8iqg1CArehSK84HZJCT7AMITlyXRBpuPhqGLDQMowraqqu1coEaimg4ZOqggt6L6L+A==
|
||||
|
||||
"@types/lodash.frompairs@^4.0.5":
|
||||
version "4.0.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/lodash.frompairs/-/lodash.frompairs-4.0.6.tgz#09b082c10fa753dc2001302b75ac79ca1e0a9ea3"
|
||||
integrity sha512-rwCUf4NMKhXpiVjL/RXP8YOk+rd02/J4tACADEgaMXRVnzDbSSlBMKFZoX/ARmHVLg3Qc98Um4PErGv8FbxU7w==
|
||||
dependencies:
|
||||
"@types/lodash" "*"
|
||||
|
||||
"@types/lodash@*", "@types/lodash@^4.14.85":
|
||||
version "4.14.136"
|
||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.136.tgz#413e85089046b865d960c9ff1d400e04c31ab60f"
|
||||
integrity sha512-0GJhzBdvsW2RUccNHOBkabI8HZVdOXmXbXhuKlDEd5Vv12P7oAVGfomGp3Ne21o5D/qu1WmthlNKFaoZJJeErA==
|
||||
|
||||
"@types/minimatch@*":
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
|
||||
|
@ -1339,13 +1322,6 @@
|
|||
lodash.unescape "4.0.1"
|
||||
semver "5.5.0"
|
||||
|
||||
"@uirouter/angularjs@0.4.3":
|
||||
version "0.4.3"
|
||||
resolved "https://registry.yarnpkg.com/@uirouter/angularjs/-/angularjs-0.4.3.tgz#7e2630c59b2bd69ca485ff124f53b0169edddf39"
|
||||
integrity sha512-jLmZ+VcsvS63E01wJWEqNLND6/6Ju9dZP6t21T+v6q8s9+Xzr8RX6QrrnRt35S0ARugFwJxFlmNFZSIef3jvDw==
|
||||
dependencies:
|
||||
angular "^1.0.8"
|
||||
|
||||
"@webassemblyjs/ast@1.8.5":
|
||||
version "1.8.5"
|
||||
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.8.5.tgz#51b1c5fe6576a34953bf4b253df9f0d490d9e359"
|
||||
|
@ -1596,21 +1572,6 @@ ajv@6.10.2, ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.5.5, ajv@^6.9.1:
|
|||
json-schema-traverse "^0.4.1"
|
||||
uri-js "^4.2.2"
|
||||
|
||||
angular-clipboard@1.7.0:
|
||||
version "1.7.0"
|
||||
resolved "https://registry.yarnpkg.com/angular-clipboard/-/angular-clipboard-1.7.0.tgz#9621a6ce66eab1ea9549aa8bfb3b71352307554f"
|
||||
integrity sha512-4/eg3zZw1MJpIsMc+mWzeVNyWBu8YWpXPTdmbgyPRp/6f0xB6I3XR2iC6Mb4mg/5E9q6exCd0sX2yiIsw+ZLJw==
|
||||
|
||||
angular1-ui-bootstrap4@2.4.22:
|
||||
version "2.4.22"
|
||||
resolved "https://registry.yarnpkg.com/angular1-ui-bootstrap4/-/angular1-ui-bootstrap4-2.4.22.tgz#378697405c957b96f947f42322f36660cd3fc88d"
|
||||
integrity sha1-N4aXQFyVe5b5R/QjIvNmYM0/yI0=
|
||||
|
||||
angular@1.7.8, angular@>=1.5, angular@>=1.5.0, angular@^1.0.8:
|
||||
version "1.7.8"
|
||||
resolved "https://registry.yarnpkg.com/angular/-/angular-1.7.8.tgz#b77ede272ce1b261e3be30c1451a0b346905a3c9"
|
||||
integrity sha512-wtef/y4COxM7ZVhddd7JtAAhyYObq9YXKar9tsW7558BImeVYteJiTxCKeJOL45lJ/+7B4wrAC49j8gTFYEthg==
|
||||
|
||||
ansi-colors@^3.0.0:
|
||||
version "3.2.4"
|
||||
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf"
|
||||
|
@ -5802,11 +5763,6 @@ jest@24.9.0:
|
|||
import-local "^2.0.0"
|
||||
jest-cli "^24.9.0"
|
||||
|
||||
jquery.flot@0.8.3:
|
||||
version "0.8.3"
|
||||
resolved "https://registry.yarnpkg.com/jquery.flot/-/jquery.flot-0.8.3.tgz#81d2ec4ffdf0dee729c8a442e8faa399e9b48207"
|
||||
integrity sha512-/tEE8J5NjwvStHDaCHkvTJpD7wDS4hE1OEL8xEmhgQfUe0gLUem923PIceNez1mz4yBNx6Hjv7pJcowLNd+nbg==
|
||||
|
||||
jquery@3.4.1:
|
||||
version "3.4.1"
|
||||
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.4.1.tgz#714f1f8d9dde4bdfa55764ba37ef214630d80ef2"
|
||||
|
@ -6107,11 +6063,6 @@ lodash.flattendeep@^4.4.0:
|
|||
resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2"
|
||||
integrity sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=
|
||||
|
||||
lodash.frompairs@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.frompairs/-/lodash.frompairs-4.0.1.tgz#bc4e5207fa2757c136e573614e9664506b2b1bd2"
|
||||
integrity sha1-vE5SB/onV8E25XNhTpZkUGsrG9I=
|
||||
|
||||
lodash.isarguments@^3.0.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a"
|
||||
|
@ -6637,16 +6588,6 @@ ng-text-truncate-2@1.0.1:
|
|||
resolved "https://registry.yarnpkg.com/ng-text-truncate-2/-/ng-text-truncate-2-1.0.1.tgz#167b92b04f092e940cc6d60a336fa604570392bd"
|
||||
integrity sha1-FnuSsE8JLpQMxtYKM2+mBFcDkr0=
|
||||
|
||||
ngcomponent@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/ngcomponent/-/ngcomponent-4.1.0.tgz#793e379138f552ea0cd2c767ad0aa7057678e228"
|
||||
integrity sha512-cGL3iVoqMWTpCfaIwgRKhdaGqiy2Z+CCG0cVfjlBvdqE8saj8xap9B4OTf+qwObxLVZmDTJPDgx3bN6Q/lZ7BQ==
|
||||
dependencies:
|
||||
"@types/angular" "^1.6.39"
|
||||
"@types/lodash" "^4.14.85"
|
||||
angular ">=1.5.0"
|
||||
lodash "^4.17.4"
|
||||
|
||||
nice-try@^1.0.4:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
|
||||
|
@ -7935,16 +7876,6 @@ react-virtualized@^9.21.0:
|
|||
prop-types "^15.6.0"
|
||||
react-lifecycles-compat "^3.0.4"
|
||||
|
||||
react2angular@4.0.6:
|
||||
version "4.0.6"
|
||||
resolved "https://registry.yarnpkg.com/react2angular/-/react2angular-4.0.6.tgz#ec49ef834d101c9a320e25229fc5afa5b29edc4f"
|
||||
integrity sha512-MDl2WRoTyu7Gyh4+FAIlmsM2mxIa/DjSz6G/d90L1tK8ZRubqVEayKF6IPyAruC5DMhGDVJ7tlAIcu/gMNDjXg==
|
||||
dependencies:
|
||||
"@types/lodash.frompairs" "^4.0.5"
|
||||
angular ">=1.5"
|
||||
lodash.frompairs "^4.0.1"
|
||||
ngcomponent "^4.1.0"
|
||||
|
||||
react@16.9.0:
|
||||
version "16.9.0"
|
||||
resolved "https://registry.yarnpkg.com/react/-/react-16.9.0.tgz#40ba2f9af13bc1a38d75dbf2f4359a5185c4f7aa"
|
||||
|
|
Загрузка…
Ссылка в новой задаче