зеркало из 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',
|
title: 'Push Health',
|
||||||
},
|
},
|
||||||
perf: {
|
perf: {
|
||||||
entry: 'entry-perf.js',
|
entry: 'perfherder/index.jsx',
|
||||||
template: 'ui/perf.html',
|
title: 'Perfherder',
|
||||||
},
|
},
|
||||||
'intermittent-failures': {
|
'intermittent-failures': {
|
||||||
entry: 'intermittent-failures/index.jsx',
|
entry: 'intermittent-failures/index.jsx',
|
||||||
|
@ -149,8 +149,8 @@ module.exports = {
|
||||||
// to help prevent unknowingly regressing the bundle size (bug 1384255).
|
// to help prevent unknowingly regressing the bundle size (bug 1384255).
|
||||||
neutrino.config.performance
|
neutrino.config.performance
|
||||||
.hints('error')
|
.hints('error')
|
||||||
.maxAssetSize(2 * 1024 * 1024)
|
.maxAssetSize(1.5 * 1024 * 1024)
|
||||||
.maxEntrypointSize(2.25 * 1024 * 1024);
|
.maxEntrypointSize(2 * 1024 * 1024);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -22,22 +22,16 @@
|
||||||
"@neutrinojs/copy": "9.0.0-rc.3",
|
"@neutrinojs/copy": "9.0.0-rc.3",
|
||||||
"@neutrinojs/react": "9.0.0-rc.3",
|
"@neutrinojs/react": "9.0.0-rc.3",
|
||||||
"@testing-library/react": "9.1.4",
|
"@testing-library/react": "9.1.4",
|
||||||
"@types/angular": "*",
|
|
||||||
"@types/prop-types": "*",
|
"@types/prop-types": "*",
|
||||||
"@types/react": "*",
|
"@types/react": "*",
|
||||||
"@types/react-dom": "*",
|
"@types/react-dom": "*",
|
||||||
"@uirouter/angularjs": "0.4.3",
|
|
||||||
"ajv": "6.10.2",
|
"ajv": "6.10.2",
|
||||||
"angular": "1.7.8",
|
|
||||||
"angular-clipboard": "1.7.0",
|
|
||||||
"angular1-ui-bootstrap4": "2.4.22",
|
|
||||||
"auth0-js": "9.11.3",
|
"auth0-js": "9.11.3",
|
||||||
"bootstrap": "4.3.1",
|
"bootstrap": "4.3.1",
|
||||||
"d3": "5.12.0",
|
"d3": "5.12.0",
|
||||||
"fuse.js": "3.4.5",
|
"fuse.js": "3.4.5",
|
||||||
"history": "4.10.0",
|
"history": "4.10.0",
|
||||||
"jquery": "3.4.1",
|
"jquery": "3.4.1",
|
||||||
"jquery.flot": "0.8.3",
|
|
||||||
"js-cookie": "2.2.1",
|
"js-cookie": "2.2.1",
|
||||||
"js-yaml": "3.13.1",
|
"js-yaml": "3.13.1",
|
||||||
"json-e": "3.0.1",
|
"json-e": "3.0.1",
|
||||||
|
@ -65,7 +59,6 @@
|
||||||
"react-split-pane": "0.1.87",
|
"react-split-pane": "0.1.87",
|
||||||
"react-table": "6.10.3",
|
"react-table": "6.10.3",
|
||||||
"react-tabs": "3.0.0",
|
"react-tabs": "3.0.0",
|
||||||
"react2angular": "4.0.6",
|
|
||||||
"reactstrap": "7.1.0",
|
"reactstrap": "7.1.0",
|
||||||
"redux": "4.0.4",
|
"redux": "4.0.4",
|
||||||
"redux-debounce": "1.0.1",
|
"redux-debounce": "1.0.1",
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {
|
||||||
import AlertsViewControls from '../../../ui/perfherder/alerts/AlertsViewControls';
|
import AlertsViewControls from '../../../ui/perfherder/alerts/AlertsViewControls';
|
||||||
import optionCollectionMap from '../mock/optionCollectionMap';
|
import optionCollectionMap from '../mock/optionCollectionMap';
|
||||||
import { summaryStatusMap } from '../../../ui/perfherder/constants';
|
import { summaryStatusMap } from '../../../ui/perfherder/constants';
|
||||||
|
import repos from '../mock/repositories';
|
||||||
|
|
||||||
const testUser = {
|
const testUser = {
|
||||||
username: 'test user',
|
username: 'test user',
|
||||||
|
@ -219,10 +220,6 @@ const alertsViewControls = () =>
|
||||||
hideDwnToInv: undefined,
|
hideDwnToInv: undefined,
|
||||||
hideImprovements: undefined,
|
hideImprovements: undefined,
|
||||||
filter: undefined,
|
filter: undefined,
|
||||||
projects: [
|
|
||||||
{ id: 1, name: 'mozilla-central' },
|
|
||||||
{ id: 2, name: 'mozilla-inbound' },
|
|
||||||
],
|
|
||||||
updateParams: () => {},
|
updateParams: () => {},
|
||||||
}}
|
}}
|
||||||
dropdownOptions={testAlertDropdowns}
|
dropdownOptions={testAlertDropdowns}
|
||||||
|
@ -233,6 +230,11 @@ const alertsViewControls = () =>
|
||||||
updateViewState={() => {}}
|
updateViewState={() => {}}
|
||||||
user={testUser}
|
user={testUser}
|
||||||
modifyAlert={(alert, params) => mockModifyAlert.update(alert, params)}
|
modifyAlert={(alert, params) => mockModifyAlert.update(alert, params)}
|
||||||
|
projects={repos}
|
||||||
|
location={{
|
||||||
|
pathname: '/alerts',
|
||||||
|
search: '',
|
||||||
|
}}
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -20,5 +20,5 @@
|
||||||
"prod": "https://treeherder.mozilla.org/",
|
"prod": "https://treeherder.mozilla.org/",
|
||||||
"stage": "https://treeherder.allizom.org/"
|
"stage": "https://treeherder.allizom.org/"
|
||||||
},
|
},
|
||||||
"keywords": ["css", "django", "angular", "js", "python"]
|
"keywords": ["css", "django", "react", "js", "python"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,7 @@
|
||||||
|
|
||||||
body {
|
body {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
overflow-x: auto;
|
overflow: auto;
|
||||||
overflow-y: auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
h1,
|
h1,
|
||||||
|
@ -17,33 +16,6 @@ h1,
|
||||||
margin-bottom: 7px;
|
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 {
|
.bug-column-header {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
body {
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
section {
|
section {
|
||||||
height: calc(100% - 50px);
|
height: calc(100% - 50px);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
@ -75,6 +79,7 @@ h1 {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
width: 280px;
|
width: 280px;
|
||||||
z-index: 999;
|
z-index: 999;
|
||||||
|
text-align: left;
|
||||||
}
|
}
|
||||||
.graph-tooltip.locked {
|
.graph-tooltip.locked {
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
|
|
|
@ -447,3 +447,22 @@ fieldset[disabled] .btn-view-nav-closed.active {
|
||||||
.dropdown-item:hover {
|
.dropdown-item:hover {
|
||||||
background-color: #d3d3d34d;
|
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',
|
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 thBugSuggestionLimit = 20;
|
||||||
|
|
||||||
export const thMaxPushFetchSize = 100;
|
export const thMaxPushFetchSize = 100;
|
||||||
|
|
|
@ -122,3 +122,13 @@ export const bugzillaBugsApi = function bugzillaBugsApi(api, params) {
|
||||||
|
|
||||||
export const getRevisionUrl = (revision, projectName) =>
|
export const getRevisionUrl = (revision, projectName) =>
|
||||||
revision ? getJobsUrl({ repo: projectName, revision }) : '';
|
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 = {
|
const defaultState = {
|
||||||
route: '/bugdetails',
|
|
||||||
endpoint: bugDetailsEndpoint,
|
endpoint: bugDetailsEndpoint,
|
||||||
|
route: '/bugdetails',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withView(defaultState)(BugDetailsView);
|
export default withView(defaultState)(BugDetailsView);
|
||||||
|
|
|
@ -184,8 +184,8 @@ const defaultState = {
|
||||||
.subtract(7, 'days'),
|
.subtract(7, 'days'),
|
||||||
),
|
),
|
||||||
endday: ISODate(moment().utc()),
|
endday: ISODate(moment().utc()),
|
||||||
route: '/main',
|
|
||||||
endpoint: bugsEndpoint,
|
endpoint: bugsEndpoint,
|
||||||
|
route: '/main',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withView(defaultState)(MainView);
|
export default withView(defaultState)(MainView);
|
||||||
|
|
|
@ -7,15 +7,11 @@ import {
|
||||||
createQueryParams,
|
createQueryParams,
|
||||||
createApiUrl,
|
createApiUrl,
|
||||||
bugzillaBugsApi,
|
bugzillaBugsApi,
|
||||||
|
updateQueryParams,
|
||||||
} from '../helpers/url';
|
} from '../helpers/url';
|
||||||
import { getData } from '../helpers/http';
|
import { getData } from '../helpers/http';
|
||||||
|
|
||||||
import {
|
import { validateQueryParams, mergeData, formatBugs } from './helpers';
|
||||||
updateQueryParams,
|
|
||||||
validateQueryParams,
|
|
||||||
mergeData,
|
|
||||||
formatBugs,
|
|
||||||
} from './helpers';
|
|
||||||
|
|
||||||
const withView = defaultState => WrappedComponent => {
|
const withView = defaultState => WrappedComponent => {
|
||||||
class View extends React.Component {
|
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 the query params are not specified for mainview, set params based on default state
|
||||||
if (location.search === '') {
|
if (location.search === '') {
|
||||||
const queryString = createQueryParams(params);
|
const queryString = createQueryParams(params);
|
||||||
updateQueryParams(defaultState.route, queryString, history, location);
|
updateQueryParams(queryString, history, location);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({ initialParamsSet: true });
|
this.setState({ initialParamsSet: true });
|
||||||
|
@ -157,12 +153,7 @@ const withView = defaultState => WrappedComponent => {
|
||||||
|
|
||||||
// update query params if dates or tree are updated
|
// update query params if dates or tree are updated
|
||||||
const queryString = createQueryParams(params);
|
const queryString = createQueryParams(params);
|
||||||
updateQueryParams(
|
updateQueryParams(queryString, this.props.history, this.props.location);
|
||||||
defaultState.route,
|
|
||||||
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) {
|
export const sortData = function sortData(data, sortBy, desc) {
|
||||||
data.sort((a, b) => {
|
data.sort((a, b) => {
|
||||||
const item1 = desc ? b[sortBy] : a[sortBy];
|
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 Login from '../../shared/auth/Login';
|
||||||
import LogoMenu from '../../shared/LogoMenu';
|
import LogoMenu from '../../shared/LogoMenu';
|
||||||
import { notify } from '../redux/stores/notifications';
|
import { notify } from '../redux/stores/notifications';
|
||||||
|
import HelpMenu from '../../shared/HelpMenu';
|
||||||
|
|
||||||
import NotificationsMenu from './NotificationsMenu';
|
import NotificationsMenu from './NotificationsMenu';
|
||||||
import InfraMenu from './InfraMenu';
|
import InfraMenu from './InfraMenu';
|
||||||
import ReposMenu from './ReposMenu';
|
import ReposMenu from './ReposMenu';
|
||||||
import TiersMenu from './TiersMenu';
|
import TiersMenu from './TiersMenu';
|
||||||
import FiltersMenu from './FiltersMenu';
|
import FiltersMenu from './FiltersMenu';
|
||||||
import HelpMenu from './HelpMenu';
|
|
||||||
import SecondaryNavBar from './SecondaryNavBar';
|
import SecondaryNavBar from './SecondaryNavBar';
|
||||||
import HealthMenu from './HealthMenu';
|
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 PropTypes from 'prop-types';
|
||||||
import { Container } from 'reactstrap';
|
import { Container } from 'reactstrap';
|
||||||
|
|
||||||
import { getData, processResponse } from '../helpers/http';
|
import {
|
||||||
import { getApiUrl, repoEndpoint } from '../helpers/url';
|
parseQueryParams,
|
||||||
|
createQueryParams,
|
||||||
|
updateQueryParams,
|
||||||
|
} from '../helpers/url';
|
||||||
import PushModel from '../models/push';
|
import PushModel from '../models/push';
|
||||||
import ErrorMessages from '../shared/ErrorMessages';
|
import ErrorMessages from '../shared/ErrorMessages';
|
||||||
import LoadingSpinner from '../shared/LoadingSpinner';
|
import LoadingSpinner from '../shared/LoadingSpinner';
|
||||||
|
|
||||||
import { endpoints, summaryStatusMap } from './constants';
|
import { 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
|
|
||||||
//
|
|
||||||
|
|
||||||
const withValidation = (
|
const withValidation = (
|
||||||
requiredParams,
|
{ requiredParams },
|
||||||
verifyRevisions = true,
|
verifyRevisions = true,
|
||||||
) => WrappedComponent => {
|
) => WrappedComponent => {
|
||||||
class Validation extends React.Component {
|
class Validation extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
// TODO change $stateParams to location.state once we switch to react-router
|
|
||||||
this.state = {
|
this.state = {
|
||||||
originalProject: null,
|
originalProject: null,
|
||||||
newProject: null,
|
newProject: null,
|
||||||
|
@ -36,38 +29,32 @@ const withValidation = (
|
||||||
originalSignature: null,
|
originalSignature: null,
|
||||||
newSignature: null,
|
newSignature: null,
|
||||||
errorMessages: [],
|
errorMessages: [],
|
||||||
projects: [],
|
|
||||||
originalResultSet: null,
|
originalResultSet: null,
|
||||||
newResultSet: null,
|
newResultSet: null,
|
||||||
selectedTimeRange: null,
|
selectedTimeRange: null,
|
||||||
framework: null,
|
framework: null,
|
||||||
frameworks: [],
|
|
||||||
// TODO reset if validateParams method is called from another component
|
|
||||||
validationComplete: false,
|
validationComplete: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
const [projects, frameworks] = await Promise.all([
|
this.validateParams(parseQueryParams(this.props.location.search));
|
||||||
getData(getApiUrl(repoEndpoint)),
|
|
||||||
getData(getApiUrl(endpoints.frameworks)),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const updates = {
|
|
||||||
...processResponse(projects, 'projects'),
|
|
||||||
...processResponse(frameworks, 'frameworks'),
|
|
||||||
};
|
|
||||||
this.setState(updates, () =>
|
|
||||||
this.validateParams(this.props.$stateParams),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateParams = param => {
|
componentDidUpdate(prevProps) {
|
||||||
const { transitionTo, current } = this.props.$state;
|
const { location } = this.props;
|
||||||
transitionTo(current.name, param, {
|
|
||||||
inherit: true,
|
if (location.search !== prevProps.location.search) {
|
||||||
notify: false,
|
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`;
|
errorMessage = (param, value) => `${param} ${value} is not valid`;
|
||||||
|
@ -134,7 +121,7 @@ const withValidation = (
|
||||||
}
|
}
|
||||||
|
|
||||||
validateParams(params) {
|
validateParams(params) {
|
||||||
const { projects, frameworks } = this.state;
|
const { projects, frameworks } = this.props;
|
||||||
let errors = [];
|
let errors = [];
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
@ -173,10 +160,13 @@ const withValidation = (
|
||||||
if (verifyRevisions) {
|
if (verifyRevisions) {
|
||||||
return this.checkRevisions(params);
|
return this.checkRevisions(params);
|
||||||
}
|
}
|
||||||
this.setState({
|
this.setState(
|
||||||
...params,
|
{
|
||||||
validationComplete: true,
|
...params,
|
||||||
});
|
validationComplete: true,
|
||||||
|
},
|
||||||
|
this.updateParams({ ...params }),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -208,11 +198,7 @@ const withValidation = (
|
||||||
}
|
}
|
||||||
|
|
||||||
Validation.propTypes = {
|
Validation.propTypes = {
|
||||||
$stateParams: PropTypes.shape({}).isRequired,
|
location: PropTypes.shape({}).isRequired,
|
||||||
$state: PropTypes.shape({
|
|
||||||
transitionTo: PropTypes.func,
|
|
||||||
current: PropTypes.shape({}),
|
|
||||||
}).isRequired,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return Validation;
|
return Validation;
|
||||||
|
|
|
@ -122,7 +122,7 @@ export default class AlertTable extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
user,
|
user,
|
||||||
validated,
|
projects,
|
||||||
alertSummaries,
|
alertSummaries,
|
||||||
issueTrackers,
|
issueTrackers,
|
||||||
fetchAlertSummaries,
|
fetchAlertSummaries,
|
||||||
|
@ -140,7 +140,7 @@ export default class AlertTable extends React.Component {
|
||||||
|
|
||||||
const downstreamIdsLength = downstreamIds.length;
|
const downstreamIdsLength = downstreamIds.length;
|
||||||
const repo = alertSummary
|
const repo = alertSummary
|
||||||
? validated.projects.find(repo => repo.name === alertSummary.repository)
|
? projects.find(repo => repo.name === alertSummary.repository)
|
||||||
: null;
|
: null;
|
||||||
const repoModel = new RepositoryModel(repo);
|
const repoModel = new RepositoryModel(repo);
|
||||||
|
|
||||||
|
@ -281,9 +281,6 @@ export default class AlertTable extends React.Component {
|
||||||
AlertTable.propTypes = {
|
AlertTable.propTypes = {
|
||||||
alertSummary: PropTypes.shape({}),
|
alertSummary: PropTypes.shape({}),
|
||||||
user: PropTypes.shape({}),
|
user: PropTypes.shape({}),
|
||||||
validated: PropTypes.shape({
|
|
||||||
projects: PropTypes.arrayOf(PropTypes.shape({})),
|
|
||||||
}).isRequired,
|
|
||||||
alertSummaries: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
|
alertSummaries: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
|
||||||
issueTrackers: PropTypes.arrayOf(PropTypes.shape({})),
|
issueTrackers: PropTypes.arrayOf(PropTypes.shape({})),
|
||||||
optionCollectionMap: PropTypes.shape({}).isRequired,
|
optionCollectionMap: PropTypes.shape({}).isRequired,
|
||||||
|
@ -296,6 +293,7 @@ AlertTable.propTypes = {
|
||||||
updateViewState: PropTypes.func.isRequired,
|
updateViewState: PropTypes.func.isRequired,
|
||||||
bugTemplate: PropTypes.shape({}),
|
bugTemplate: PropTypes.shape({}),
|
||||||
modifyAlert: PropTypes.func,
|
modifyAlert: PropTypes.func,
|
||||||
|
projects: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
AlertTable.defaultProps = {
|
AlertTable.defaultProps = {
|
||||||
|
|
|
@ -13,10 +13,12 @@ import { createQueryParams } from '../../helpers/url';
|
||||||
import { getStatus, getGraphsURL, modifyAlert } from '../helpers';
|
import { getStatus, getGraphsURL, modifyAlert } from '../helpers';
|
||||||
import SimpleTooltip from '../../shared/SimpleTooltip';
|
import SimpleTooltip from '../../shared/SimpleTooltip';
|
||||||
import ProgressBar from '../ProgressBar';
|
import ProgressBar from '../ProgressBar';
|
||||||
import { alertStatusMap } from '../constants';
|
import {
|
||||||
import { phDefaultTimeRangeValue, phTimeRanges } from '../../helpers/constants';
|
alertStatusMap,
|
||||||
|
phDefaultTimeRangeValue,
|
||||||
|
phTimeRanges,
|
||||||
|
} from '../constants';
|
||||||
|
|
||||||
// TODO remove $stateParams and $state after switching to react router
|
|
||||||
export default class AlertTableRow extends React.Component {
|
export default class AlertTableRow extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
@ -94,7 +96,6 @@ export default class AlertTableRow extends React.Component {
|
||||||
{` ${text} `}
|
{` ${text} `}
|
||||||
<a
|
<a
|
||||||
href={`#/alerts?id=${alertId}`}
|
href={`#/alerts?id=${alertId}`}
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="text-info"
|
className="text-info"
|
||||||
>{`alert #${alertId}`}</a>
|
>{`alert #${alertId}`}</a>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
|
/* eslint-disable react/no-did-update-set-state */
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { react2angular } from 'react2angular/index.es2015';
|
|
||||||
import {
|
import {
|
||||||
Alert,
|
Alert,
|
||||||
Container,
|
Container,
|
||||||
|
@ -10,11 +10,14 @@ import {
|
||||||
PaginationLink,
|
PaginationLink,
|
||||||
} from 'reactstrap';
|
} from 'reactstrap';
|
||||||
|
|
||||||
import perf from '../../js/perf';
|
|
||||||
import withValidation from '../Validation';
|
import withValidation from '../Validation';
|
||||||
import { convertParams, getFrameworkData, getStatus } from '../helpers';
|
import { convertParams, getFrameworkData, getStatus } from '../helpers';
|
||||||
import { summaryStatusMap, endpoints } from '../constants';
|
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 { getData, processResponse } from '../../helpers/http';
|
||||||
import ErrorMessages from '../../shared/ErrorMessages';
|
import ErrorMessages from '../../shared/ErrorMessages';
|
||||||
import OptionCollectionModel from '../../models/optionCollection';
|
import OptionCollectionModel from '../../models/optionCollection';
|
||||||
|
@ -27,14 +30,13 @@ import LoadingSpinner from '../../shared/LoadingSpinner';
|
||||||
|
|
||||||
import AlertsViewControls from './AlertsViewControls';
|
import AlertsViewControls from './AlertsViewControls';
|
||||||
|
|
||||||
// TODO remove $stateParams and $state after switching to react router
|
class AlertsView extends React.Component {
|
||||||
export class AlertsView extends React.Component {
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.validated = this.props.validated;
|
this.validated = this.props.validated;
|
||||||
this.state = {
|
this.state = {
|
||||||
status: this.getDefaultStatus(),
|
status: this.getDefaultStatus(),
|
||||||
framework: getFrameworkData(this.validated),
|
framework: getFrameworkData(this.props),
|
||||||
page: this.validated.page ? parseInt(this.validated.page, 10) : 1,
|
page: this.validated.page ? parseInt(this.validated.page, 10) : 1,
|
||||||
errorMessages: [],
|
errorMessages: [],
|
||||||
alertSummaries: [],
|
alertSummaries: [],
|
||||||
|
@ -54,14 +56,26 @@ export class AlertsView extends React.Component {
|
||||||
|
|
||||||
componentDidUpdate(prevProps, prevState) {
|
componentDidUpdate(prevProps, prevState) {
|
||||||
const { count } = this.state;
|
const { count } = this.state;
|
||||||
|
const { validated } = this.props;
|
||||||
|
|
||||||
if (prevState.count !== count) {
|
if (prevState.count !== count) {
|
||||||
// eslint-disable-next-line react/no-did-update-set-state
|
|
||||||
this.setState({ totalPages: this.generatePages(count) });
|
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 = () => {
|
getDefaultStatus = () => {
|
||||||
const { validated } = this.props;
|
const { validated } = this.props;
|
||||||
|
|
||||||
const statusParam = convertParams(validated, 'status');
|
const statusParam = convertParams(validated, 'status');
|
||||||
if (!statusParam) {
|
if (!statusParam) {
|
||||||
return Object.keys(summaryStatusMap)[1];
|
return Object.keys(summaryStatusMap)[1];
|
||||||
|
@ -70,7 +84,8 @@ export class AlertsView extends React.Component {
|
||||||
};
|
};
|
||||||
|
|
||||||
updateFramework = selection => {
|
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);
|
const framework = frameworks.find(item => item.name === selection);
|
||||||
|
|
||||||
updateParams({ framework: framework.id });
|
updateParams({ framework: framework.id });
|
||||||
|
@ -106,7 +121,6 @@ export class AlertsView extends React.Component {
|
||||||
return pages;
|
return pages;
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO potentially pass as a prop for testing purposes
|
|
||||||
async fetchAlertSummaries(id = this.state.id, update = false) {
|
async fetchAlertSummaries(id = this.state.id, update = false) {
|
||||||
// turn off loading when update is true (used to update alert statuses)
|
// turn off loading when update is true (used to update alert statuses)
|
||||||
this.setState({ loading: !update, errorMessages: [] });
|
this.setState({ loading: !update, errorMessages: [] });
|
||||||
|
@ -143,7 +157,6 @@ export class AlertsView extends React.Component {
|
||||||
`${endpoints.alertSummary}${createQueryParams(params)}`,
|
`${endpoints.alertSummary}${createQueryParams(params)}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO OptionCollectionModel to use getData wrapper
|
|
||||||
if (!issueTrackers.length && !optionCollectionMap) {
|
if (!issueTrackers.length && !optionCollectionMap) {
|
||||||
const [optionCollectionMap, issueTrackers] = await Promise.all([
|
const [optionCollectionMap, issueTrackers] = await Promise.all([
|
||||||
OptionCollectionModel.getMap(),
|
OptionCollectionModel.getMap(),
|
||||||
|
@ -162,6 +175,8 @@ export class AlertsView extends React.Component {
|
||||||
if (response.alertSummaries) {
|
if (response.alertSummaries) {
|
||||||
const summary = 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) {
|
if (update) {
|
||||||
const index = alertSummaries.findIndex(
|
const index = alertSummaries.findIndex(
|
||||||
item => item.id === summary.results[0].id,
|
item => item.id === summary.results[0].id,
|
||||||
|
@ -184,7 +199,7 @@ export class AlertsView extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { user, validated } = this.props;
|
const { user, frameworks } = this.props;
|
||||||
const {
|
const {
|
||||||
framework,
|
framework,
|
||||||
status,
|
status,
|
||||||
|
@ -198,7 +213,6 @@ export class AlertsView extends React.Component {
|
||||||
bugTemplate,
|
bugTemplate,
|
||||||
id,
|
id,
|
||||||
} = this.state;
|
} = this.state;
|
||||||
const { frameworks } = validated;
|
|
||||||
|
|
||||||
const frameworkNames =
|
const frameworkNames =
|
||||||
frameworks && frameworks.length ? frameworks.map(item => item.name) : [];
|
frameworks && frameworks.length ? frameworks.map(item => item.name) : [];
|
||||||
|
@ -240,7 +254,6 @@ export class AlertsView extends React.Component {
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
<AlertsViewControls
|
<AlertsViewControls
|
||||||
validated={validated}
|
|
||||||
dropdownOptions={id ? [] : alertDropdowns}
|
dropdownOptions={id ? [] : alertDropdowns}
|
||||||
alertSummaries={alertSummaries}
|
alertSummaries={alertSummaries}
|
||||||
issueTrackers={issueTrackers}
|
issueTrackers={issueTrackers}
|
||||||
|
@ -249,6 +262,7 @@ export class AlertsView extends React.Component {
|
||||||
updateViewState={state => this.setState(state)}
|
updateViewState={state => this.setState(state)}
|
||||||
bugTemplate={bugTemplate}
|
bugTemplate={bugTemplate}
|
||||||
user={user}
|
user={user}
|
||||||
|
{...this.props}
|
||||||
/>
|
/>
|
||||||
{pageNums.length > 0 && (
|
{pageNums.length > 0 && (
|
||||||
<Row className="justify-content-center pb-5">
|
<Row className="justify-content-center pb-5">
|
||||||
|
@ -301,29 +315,20 @@ export class AlertsView extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
AlertsView.propTypes = {
|
AlertsView.propTypes = {
|
||||||
$stateParams: PropTypes.shape({}),
|
location: PropTypes.shape({}),
|
||||||
$state: PropTypes.shape({
|
|
||||||
go: PropTypes.func,
|
|
||||||
}),
|
|
||||||
user: PropTypes.shape({}).isRequired,
|
user: PropTypes.shape({}).isRequired,
|
||||||
validated: PropTypes.shape({
|
validated: PropTypes.shape({
|
||||||
projects: PropTypes.arrayOf(PropTypes.shape({})),
|
|
||||||
frameworks: PropTypes.arrayOf(PropTypes.shape({})),
|
|
||||||
updateParams: PropTypes.func.isRequired,
|
updateParams: PropTypes.func.isRequired,
|
||||||
framework: PropTypes.string,
|
framework: PropTypes.string,
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
|
projects: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
|
||||||
|
frameworks: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
AlertsView.defaultProps = {
|
AlertsView.defaultProps = {
|
||||||
$stateParams: null,
|
location: null,
|
||||||
$state: null,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const alertsView = withValidation(new Set([]), false)(AlertsView);
|
export default withValidation({ requiredParams: new Set([]) }, false)(
|
||||||
|
AlertsView,
|
||||||
perf.component(
|
|
||||||
'alertsView',
|
|
||||||
react2angular(alertsView, ['user'], ['$stateParams', '$state']),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
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 => {
|
updateFilter = filter => {
|
||||||
this.setState(
|
this.setState(
|
||||||
prevState => ({ [filter]: !prevState[filter] }),
|
prevState => ({ [filter]: !prevState[filter] }),
|
||||||
|
|
|
@ -7,7 +7,6 @@ import { getData } from '../../helpers/http';
|
||||||
import { endpoints } from '../constants';
|
import { endpoints } from '../constants';
|
||||||
import { getApiUrl } from '../../helpers/url';
|
import { getApiUrl } from '../../helpers/url';
|
||||||
|
|
||||||
// TODO remove $stateParams and $state after switching to react router
|
|
||||||
export default class DownstreamSummary extends React.Component {
|
export default class DownstreamSummary extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { react2angular } from 'react2angular/index.es2015';
|
|
||||||
import {
|
import {
|
||||||
Container,
|
Container,
|
||||||
Col,
|
Col,
|
||||||
|
@ -11,28 +9,23 @@ import {
|
||||||
DropdownToggle,
|
DropdownToggle,
|
||||||
} from 'reactstrap';
|
} from 'reactstrap';
|
||||||
|
|
||||||
import perf from '../../js/perf';
|
import { parseQueryParams, createQueryParams } from '../../helpers/url';
|
||||||
import { getApiUrl, repoEndpoint } from '../../helpers/url';
|
|
||||||
import { endpoints } from '../constants';
|
|
||||||
import { getData, processResponse } from '../../helpers/http';
|
|
||||||
import ErrorMessages from '../../shared/ErrorMessages';
|
import ErrorMessages from '../../shared/ErrorMessages';
|
||||||
import DropdownMenuItems from '../../shared/DropdownMenuItems';
|
import DropdownMenuItems from '../../shared/DropdownMenuItems';
|
||||||
import {
|
import {
|
||||||
compareDefaultTimeRange,
|
|
||||||
genericErrorMessage,
|
genericErrorMessage,
|
||||||
errorMessageClass,
|
errorMessageClass,
|
||||||
} from '../../helpers/constants';
|
} from '../../helpers/constants';
|
||||||
|
import { compareDefaultTimeRange } from '../constants';
|
||||||
import ErrorBoundary from '../../shared/ErrorBoundary';
|
import ErrorBoundary from '../../shared/ErrorBoundary';
|
||||||
|
|
||||||
import SelectorCard from './SelectorCard';
|
import SelectorCard from './SelectorCard';
|
||||||
|
|
||||||
// TODO remove $stateParams and $state after switching to react router
|
|
||||||
export default class CompareSelectorView extends React.Component {
|
export default class CompareSelectorView extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.queryParams = this.props.$stateParams;
|
this.queryParams = parseQueryParams(this.props.location.search);
|
||||||
this.state = {
|
this.state = {
|
||||||
projects: [],
|
|
||||||
originalProject: this.queryParams.originalProject || 'mozilla-central',
|
originalProject: this.queryParams.originalProject || 'mozilla-central',
|
||||||
newProject: this.queryParams.newProject || 'try',
|
newProject: this.queryParams.newProject || 'try',
|
||||||
originalRevision: this.queryParams.originalRevision || '',
|
originalRevision: this.queryParams.originalRevision || '',
|
||||||
|
@ -42,37 +35,18 @@ export default class CompareSelectorView extends React.Component {
|
||||||
missingRevision: false,
|
missingRevision: false,
|
||||||
framework: 1,
|
framework: 1,
|
||||||
frameworkName: 'talos',
|
frameworkName: 'talos',
|
||||||
frameworks: [],
|
|
||||||
frameworkDropdownIsOpen: false,
|
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 => {
|
updateFramework = selection => {
|
||||||
this.setState(prevState => {
|
const selectedFramework = this.props.frameworks.find(
|
||||||
const selectedFramework = prevState.frameworks.find(
|
framework => framework.name === selection,
|
||||||
framework => framework.name === selection,
|
);
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
this.setState({
|
||||||
framework: selectedFramework.id,
|
framework: selectedFramework.id,
|
||||||
frameworkName: selectedFramework.name,
|
frameworkName: selectedFramework.name,
|
||||||
};
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -84,29 +58,31 @@ export default class CompareSelectorView extends React.Component {
|
||||||
newRevision,
|
newRevision,
|
||||||
framework,
|
framework,
|
||||||
} = this.state;
|
} = this.state;
|
||||||
const { $state } = this.props;
|
const { history } = this.props;
|
||||||
|
let params;
|
||||||
|
|
||||||
if (newRevision === '') {
|
if (newRevision === '') {
|
||||||
return this.setState({ missingRevision: 'Revision is required' });
|
return this.setState({ missingRevision: 'Revision is required' });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (originalRevision !== '') {
|
if (originalRevision !== '') {
|
||||||
$state.go('compare', {
|
params = {
|
||||||
originalProject,
|
originalProject,
|
||||||
originalRevision,
|
originalRevision,
|
||||||
newProject,
|
newProject,
|
||||||
newRevision,
|
newRevision,
|
||||||
framework,
|
framework,
|
||||||
});
|
};
|
||||||
} else {
|
} else {
|
||||||
$state.go('compare', {
|
params = {
|
||||||
originalProject,
|
originalProject,
|
||||||
newProject,
|
newProject,
|
||||||
newRevision,
|
newRevision,
|
||||||
framework,
|
framework,
|
||||||
selectedTimeRange: compareDefaultTimeRange.value,
|
selectedTimeRange: compareDefaultTimeRange.value,
|
||||||
});
|
};
|
||||||
}
|
}
|
||||||
|
history.push(`/compare${createQueryParams(params)}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
toggleFrameworkDropdown = () => {
|
toggleFrameworkDropdown = () => {
|
||||||
|
@ -119,9 +95,7 @@ export default class CompareSelectorView extends React.Component {
|
||||||
const {
|
const {
|
||||||
originalProject,
|
originalProject,
|
||||||
newProject,
|
newProject,
|
||||||
projects,
|
|
||||||
frameworkName,
|
frameworkName,
|
||||||
frameworks,
|
|
||||||
originalRevision,
|
originalRevision,
|
||||||
newRevision,
|
newRevision,
|
||||||
errorMessages,
|
errorMessages,
|
||||||
|
@ -130,9 +104,11 @@ export default class CompareSelectorView extends React.Component {
|
||||||
frameworkDropdownIsOpen,
|
frameworkDropdownIsOpen,
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
|
const { projects, frameworks } = this.props;
|
||||||
const frameworkNames = frameworks.length
|
const frameworkNames = frameworks.length
|
||||||
? frameworks.map(item => item.name)
|
? frameworks.map(item => item.name)
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container fluid className="my-5 pt-5 max-width-default">
|
<Container fluid className="my-5 pt-5 max-width-default">
|
||||||
<ErrorBoundary
|
<ErrorBoundary
|
||||||
|
@ -147,35 +123,34 @@ export default class CompareSelectorView extends React.Component {
|
||||||
)}
|
)}
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</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">
|
<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">
|
<Col sm="8" className="text-right px-1">
|
||||||
<ButtonGroup>
|
<ButtonGroup>
|
||||||
<ButtonDropdown
|
<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 React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { react2angular } from 'react2angular/index.es2015';
|
|
||||||
import { Container, Row } from 'reactstrap';
|
import { Container, Row } from 'reactstrap';
|
||||||
|
|
||||||
import perf from '../../js/perf';
|
|
||||||
import RepositoryModel from '../../models/repository';
|
import RepositoryModel from '../../models/repository';
|
||||||
import PushModel from '../../models/push';
|
import PushModel from '../../models/push';
|
||||||
import { getData } from '../../helpers/http';
|
import { getData } from '../../helpers/http';
|
||||||
import { createApiUrl, perfSummaryEndpoint } from '../../helpers/url';
|
import {
|
||||||
|
createApiUrl,
|
||||||
|
perfSummaryEndpoint,
|
||||||
|
parseQueryParams,
|
||||||
|
} from '../../helpers/url';
|
||||||
import LoadingSpinner from '../../shared/LoadingSpinner';
|
import LoadingSpinner from '../../shared/LoadingSpinner';
|
||||||
|
|
||||||
import RevisionInformation from './RevisionInformation';
|
import RevisionInformation from './RevisionInformation';
|
||||||
import ReplicatesGraph from './ReplicatesGraph';
|
import ReplicatesGraph from './ReplicatesGraph';
|
||||||
|
|
||||||
// TODO remove $stateParams after switching to react router
|
|
||||||
export default class CompareSubtestDistributionView extends React.Component {
|
export default class CompareSubtestDistributionView extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
@ -32,7 +32,8 @@ export default class CompareSubtestDistributionView extends React.Component {
|
||||||
originalRevision,
|
originalRevision,
|
||||||
newRevision,
|
newRevision,
|
||||||
newProject: newProjectName,
|
newProject: newProjectName,
|
||||||
} = this.props.$stateParams;
|
} = parseQueryParams(this.props.location.search);
|
||||||
|
|
||||||
const { originalProject, newProject } = await this.fetchProjectsToCompare(
|
const { originalProject, newProject } = await this.fetchProjectsToCompare(
|
||||||
originalProjectName,
|
originalProjectName,
|
||||||
newProjectName,
|
newProjectName,
|
||||||
|
@ -100,20 +101,14 @@ export default class CompareSubtestDistributionView extends React.Component {
|
||||||
return [originalSyncPromise, newSyncPromise];
|
return [originalSyncPromise, newSyncPromise];
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchAllRepositories = async () => {
|
|
||||||
const loadRepositories = RepositoryModel.getList();
|
|
||||||
const results = await Promise.all([loadRepositories]);
|
|
||||||
return results[0];
|
|
||||||
};
|
|
||||||
|
|
||||||
fetchProjectsToCompare = async (originalProjectName, newProjectName) => {
|
fetchProjectsToCompare = async (originalProjectName, newProjectName) => {
|
||||||
const allRepos = await this.fetchAllRepositories();
|
const { projects } = this.props;
|
||||||
|
|
||||||
const originalProject = RepositoryModel.getRepo(
|
const originalProject = RepositoryModel.getRepo(
|
||||||
originalProjectName,
|
originalProjectName,
|
||||||
allRepos,
|
projects,
|
||||||
);
|
);
|
||||||
const newProject = RepositoryModel.getRepo(newProjectName, allRepos);
|
const newProject = RepositoryModel.getRepo(newProjectName, projects);
|
||||||
return { originalProject, newProject };
|
return { originalProject, newProject };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -155,7 +150,7 @@ export default class CompareSubtestDistributionView extends React.Component {
|
||||||
newRevision,
|
newRevision,
|
||||||
originalSubtestSignature,
|
originalSubtestSignature,
|
||||||
newSubtestSignature,
|
newSubtestSignature,
|
||||||
} = this.props.$stateParams;
|
} = parseQueryParams(this.props.location.search);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
originalRevision &&
|
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 React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { react2angular } from 'react2angular/index.es2015';
|
|
||||||
import difference from 'lodash/difference';
|
import difference from 'lodash/difference';
|
||||||
|
|
||||||
import perf from '../../js/perf';
|
|
||||||
import { createQueryParams } from '../../helpers/url';
|
import { createQueryParams } from '../../helpers/url';
|
||||||
import {
|
import {
|
||||||
createNoiseMetric,
|
createNoiseMetric,
|
||||||
getCounterMap,
|
getCounterMap,
|
||||||
createGraphsLinks,
|
createGraphsLinks,
|
||||||
|
onPermalinkClick,
|
||||||
} from '../helpers';
|
} from '../helpers';
|
||||||
import { noiseMetricTitle } from '../constants';
|
import { noiseMetricTitle } from '../constants';
|
||||||
import withValidation from '../Validation';
|
import withValidation from '../Validation';
|
||||||
|
|
||||||
import CompareTableView from './CompareTableView';
|
import CompareTableView from './CompareTableView';
|
||||||
|
|
||||||
// TODO remove $stateParams and $state after switching to react router
|
class CompareSubtestsView extends React.PureComponent {
|
||||||
export class CompareSubtestsView extends React.PureComponent {
|
|
||||||
createQueryParams = (parent_signature, repository, framework) => ({
|
createQueryParams = (parent_signature, repository, framework) => ({
|
||||||
parent_signature,
|
parent_signature,
|
||||||
framework,
|
framework,
|
||||||
|
@ -43,7 +41,6 @@ export class CompareSubtestsView extends React.PureComponent {
|
||||||
if (originalRevision) {
|
if (originalRevision) {
|
||||||
originalParams.revision = originalRevision;
|
originalParams.revision = originalRevision;
|
||||||
} else {
|
} else {
|
||||||
// can create a helper function for both views
|
|
||||||
const startDateMs =
|
const startDateMs =
|
||||||
(newResultSet.push_timestamp - timeRange.value) * 1000;
|
(newResultSet.push_timestamp - timeRange.value) * 1000;
|
||||||
const endDateMs = newResultSet.push_timestamp * 1000;
|
const endDateMs = newResultSet.push_timestamp * 1000;
|
||||||
|
@ -211,6 +208,8 @@ export class CompareSubtestsView extends React.PureComponent {
|
||||||
{...this.props}
|
{...this.props}
|
||||||
getQueryParams={this.getQueryParams}
|
getQueryParams={this.getQueryParams}
|
||||||
getDisplayResults={this.getDisplayResults}
|
getDisplayResults={this.getDisplayResults}
|
||||||
|
onPermalinkClick={hashValue => onPermalinkClick(hashValue, this.props)}
|
||||||
|
hashFragment={this.props.location.hash}
|
||||||
hasSubtests
|
hasSubtests
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -225,20 +224,14 @@ CompareSubtestsView.propTypes = {
|
||||||
originalProject: PropTypes.string,
|
originalProject: PropTypes.string,
|
||||||
newProject: PropTypes.string,
|
newProject: PropTypes.string,
|
||||||
originalRevision: PropTypes.string,
|
originalRevision: PropTypes.string,
|
||||||
projects: PropTypes.arrayOf(PropTypes.shape({})),
|
|
||||||
updateParams: PropTypes.func.isRequired,
|
updateParams: PropTypes.func.isRequired,
|
||||||
newSignature: PropTypes.string,
|
newSignature: PropTypes.string,
|
||||||
originalSignature: PropTypes.string,
|
originalSignature: PropTypes.string,
|
||||||
}),
|
}),
|
||||||
$stateParams: PropTypes.shape({}),
|
|
||||||
$state: PropTypes.shape({}),
|
|
||||||
user: PropTypes.shape({}).isRequired,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
CompareSubtestsView.defaultProps = {
|
CompareSubtestsView.defaultProps = {
|
||||||
validated: PropTypes.shape({}),
|
validated: PropTypes.shape({}),
|
||||||
$stateParams: null,
|
|
||||||
$state: null,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const requiredParams = new Set([
|
const requiredParams = new Set([
|
||||||
|
@ -249,11 +242,4 @@ const requiredParams = new Set([
|
||||||
'newSignature',
|
'newSignature',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const compareSubtestsView = withValidation(requiredParams)(CompareSubtestsView);
|
export default withValidation({ requiredParams })(CompareSubtestsView);
|
||||||
|
|
||||||
perf.component(
|
|
||||||
'compareSubtestsView',
|
|
||||||
react2angular(compareSubtestsView, ['user'], ['$stateParams', '$state']),
|
|
||||||
);
|
|
||||||
|
|
||||||
export default compareSubtestsView;
|
|
||||||
|
|
|
@ -9,12 +9,12 @@ import {
|
||||||
faHashtag,
|
faHashtag,
|
||||||
} from '@fortawesome/free-solid-svg-icons';
|
} from '@fortawesome/free-solid-svg-icons';
|
||||||
|
|
||||||
import JobModel from '../../models/job';
|
|
||||||
import SimpleTooltip from '../../shared/SimpleTooltip';
|
import SimpleTooltip from '../../shared/SimpleTooltip';
|
||||||
import { displayNumber } from '../helpers';
|
import { displayNumber } from '../helpers';
|
||||||
import { compareTableText } from '../constants';
|
import { compareTableText } from '../constants';
|
||||||
import ProgressBar from '../ProgressBar';
|
import ProgressBar from '../ProgressBar';
|
||||||
import { hashFunction } from '../../helpers/utils';
|
import { hashFunction } from '../../helpers/utils';
|
||||||
|
import JobModel from '../../models/job';
|
||||||
|
|
||||||
import TableAverage from './TableAverage';
|
import TableAverage from './TableAverage';
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ export default class CompareTable extends React.PureComponent {
|
||||||
displayNumber(percentage),
|
displayNumber(percentage),
|
||||||
)}% ${improvement ? 'better' : 'worse'})`;
|
)}% ${improvement ? 'better' : 'worse'})`;
|
||||||
|
|
||||||
// humane readable signature name
|
// human readable signature name
|
||||||
getSignatureName = (testName, platformName) =>
|
getSignatureName = (testName, platformName) =>
|
||||||
[testName, platformName].filter(item => item !== null).join(' ');
|
[testName, platformName].filter(item => item !== null).join(' ');
|
||||||
|
|
||||||
|
@ -100,14 +100,18 @@ export default class CompareTable extends React.PureComponent {
|
||||||
<tr className="subtest-header bg-lightgray">
|
<tr className="subtest-header bg-lightgray">
|
||||||
<th className="text-left">
|
<th className="text-left">
|
||||||
<span>{testName}</span>
|
<span>{testName}</span>
|
||||||
<Button
|
{onPermalinkClick && (
|
||||||
className="permalink p-0 ml-1"
|
<Button
|
||||||
color="link"
|
className="permalink p-0 ml-1"
|
||||||
onClick={() => onPermalinkClick(this.getHashBasedId(testName))}
|
color="link"
|
||||||
title="Permalink to this test table"
|
onClick={() =>
|
||||||
>
|
onPermalinkClick(this.getHashBasedId(testName))
|
||||||
<FontAwesomeIcon icon={faHashtag} />
|
}
|
||||||
</Button>
|
title="Permalink to this test table"
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faHashtag} />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</th>
|
</th>
|
||||||
<th className="table-width-lg">Base</th>
|
<th className="table-width-lg">Base</th>
|
||||||
{/* empty for less than/greater than data */}
|
{/* 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">
|
<th className="text-left font-weight-normal pl-1">
|
||||||
{rowLevelResults.name}
|
{rowLevelResults.name}
|
||||||
<span className="result-links">
|
<span className="result-links">
|
||||||
<span>
|
{onPermalinkClick && (
|
||||||
<Button
|
<span>
|
||||||
className="permalink p-0 ml-1"
|
<Button
|
||||||
color="link"
|
className="permalink p-0 ml-1"
|
||||||
onClick={() =>
|
color="link"
|
||||||
onPermalinkClick(
|
onClick={() =>
|
||||||
this.getHashBasedId(testName, rowLevelResults.name),
|
onPermalinkClick(
|
||||||
)
|
this.getHashBasedId(testName, rowLevelResults.name),
|
||||||
}
|
)
|
||||||
title="Permalink to this test"
|
}
|
||||||
>
|
title="Permalink to this test"
|
||||||
<FontAwesomeIcon icon={faHashtag} />
|
>
|
||||||
</Button>
|
<FontAwesomeIcon icon={faHashtag} />
|
||||||
</span>
|
</Button>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
{rowLevelResults.links &&
|
{rowLevelResults.links &&
|
||||||
rowLevelResults.links.map(link => (
|
rowLevelResults.links.map(link => (
|
||||||
<span key={link.title}>
|
<span key={link.title}>
|
||||||
|
@ -289,20 +295,15 @@ CompareTable.propTypes = {
|
||||||
data: PropTypes.arrayOf(PropTypes.shape({})),
|
data: PropTypes.arrayOf(PropTypes.shape({})),
|
||||||
testName: PropTypes.string.isRequired,
|
testName: PropTypes.string.isRequired,
|
||||||
hashFunction: PropTypes.func,
|
hashFunction: PropTypes.func,
|
||||||
onPermalinkClick: PropTypes.func.isRequired,
|
onPermalinkClick: PropTypes.func,
|
||||||
user: PropTypes.shape({}).isRequired,
|
|
||||||
isBaseAggregate: PropTypes.bool.isRequired,
|
|
||||||
notify: PropTypes.func,
|
|
||||||
hasSubtests: PropTypes.bool,
|
|
||||||
retriggerJob: PropTypes.func,
|
|
||||||
getJob: PropTypes.func,
|
getJob: PropTypes.func,
|
||||||
|
retriggerJob: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
CompareTable.defaultProps = {
|
CompareTable.defaultProps = {
|
||||||
data: null,
|
data: null,
|
||||||
hashFunction,
|
hashFunction,
|
||||||
notify: null,
|
onPermalinkClick: undefined,
|
||||||
hasSubtests: false,
|
|
||||||
retriggerJob: JobModel.retrigger,
|
|
||||||
getJob: JobModel.get,
|
getJob: JobModel.get,
|
||||||
|
retriggerJob: JobModel.retrigger,
|
||||||
};
|
};
|
||||||
|
|
|
@ -202,7 +202,7 @@ CompareTableControls.propTypes = {
|
||||||
PropTypes.shape({}),
|
PropTypes.shape({}),
|
||||||
PropTypes.bool,
|
PropTypes.bool,
|
||||||
]),
|
]),
|
||||||
onPermalinkClick: PropTypes.func.isRequired,
|
onPermalinkClick: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
CompareTableControls.defaultProps = {
|
CompareTableControls.defaultProps = {
|
||||||
|
@ -215,4 +215,5 @@ CompareTableControls.defaultProps = {
|
||||||
showOnlyNoise: undefined,
|
showOnlyNoise: undefined,
|
||||||
},
|
},
|
||||||
showTestsWithNoise: null,
|
showTestsWithNoise: null,
|
||||||
|
onPermalinkClick: undefined,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Alert, Col, Row, Container } from 'reactstrap';
|
import { Alert, Col, Row, Container } from 'reactstrap';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
import ErrorMessages from '../../shared/ErrorMessages';
|
import ErrorMessages from '../../shared/ErrorMessages';
|
||||||
import {
|
import {
|
||||||
genericErrorMessage,
|
genericErrorMessage,
|
||||||
errorMessageClass,
|
errorMessageClass,
|
||||||
compareDefaultTimeRange,
|
|
||||||
phTimeRanges,
|
|
||||||
} from '../../helpers/constants';
|
} from '../../helpers/constants';
|
||||||
|
import { compareDefaultTimeRange, phTimeRanges } from '../constants';
|
||||||
import ErrorBoundary from '../../shared/ErrorBoundary';
|
import ErrorBoundary from '../../shared/ErrorBoundary';
|
||||||
import { getData } from '../../helpers/http';
|
import { getData } from '../../helpers/http';
|
||||||
import {
|
import {
|
||||||
|
@ -25,7 +25,6 @@ import RevisionInformation from './RevisionInformation';
|
||||||
import CompareTableControls from './CompareTableControls';
|
import CompareTableControls from './CompareTableControls';
|
||||||
import NoiseTable from './NoiseTable';
|
import NoiseTable from './NoiseTable';
|
||||||
|
|
||||||
// TODO remove $stateParams and $state after switching to react router
|
|
||||||
export default class CompareTableView extends React.Component {
|
export default class CompareTableView extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
@ -36,25 +35,35 @@ export default class CompareTableView extends React.Component {
|
||||||
failureMessages: [],
|
failureMessages: [],
|
||||||
loading: false,
|
loading: false,
|
||||||
timeRange: this.setTimeRange(),
|
timeRange: this.setTimeRange(),
|
||||||
framework: getFrameworkData(this.props.validated),
|
framework: getFrameworkData(this.props),
|
||||||
title: '',
|
title: '',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
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) {
|
componentDidUpdate(prevProps) {
|
||||||
const { loading } = this.state;
|
const { loading } = this.state;
|
||||||
const { hashFragment } = this.props;
|
const { hashFragment } = this.props;
|
||||||
|
|
||||||
if (this.props !== prevProps) {
|
if (this.props.location.search !== prevProps.location.search) {
|
||||||
this.getPerformanceData();
|
this.getPerformanceData();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!loading && hashFragment) {
|
if (!loading && hashFragment) {
|
||||||
scrollToLine(`#${hashFragment}`, 100);
|
scrollToLine(hashFragment, 100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,7 +155,9 @@ export default class CompareTableView extends React.Component {
|
||||||
};
|
};
|
||||||
|
|
||||||
updateFramework = selection => {
|
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);
|
const framework = frameworks.find(item => item.name === selection);
|
||||||
|
|
||||||
updateParams({ framework: framework.id });
|
updateParams({ framework: framework.id });
|
||||||
|
@ -178,10 +189,14 @@ export default class CompareTableView extends React.Component {
|
||||||
newRevision,
|
newRevision,
|
||||||
originalResultSet,
|
originalResultSet,
|
||||||
newResultSet,
|
newResultSet,
|
||||||
frameworks,
|
|
||||||
} = this.props.validated;
|
} = this.props.validated;
|
||||||
|
|
||||||
const { filterByFramework, hasSubtests, onPermalinkClick } = this.props;
|
const {
|
||||||
|
filterByFramework,
|
||||||
|
hasSubtests,
|
||||||
|
onPermalinkClick,
|
||||||
|
frameworks,
|
||||||
|
} = this.props;
|
||||||
const {
|
const {
|
||||||
compareResults,
|
compareResults,
|
||||||
loading,
|
loading,
|
||||||
|
@ -198,7 +213,12 @@ export default class CompareTableView extends React.Component {
|
||||||
|
|
||||||
const compareDropdowns = [];
|
const compareDropdowns = [];
|
||||||
|
|
||||||
const params = { originalProject, newProject, newRevision };
|
const params = {
|
||||||
|
originalProject,
|
||||||
|
newProject,
|
||||||
|
newRevision,
|
||||||
|
framework: framework.id,
|
||||||
|
};
|
||||||
|
|
||||||
if (originalRevision) {
|
if (originalRevision) {
|
||||||
params.originalRevision = originalRevision;
|
params.originalRevision = originalRevision;
|
||||||
|
@ -231,11 +251,14 @@ export default class CompareTableView extends React.Component {
|
||||||
>
|
>
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{hasSubtests && (
|
{hasSubtests && (
|
||||||
<p>
|
<Link
|
||||||
<a href={`perf.html#/compare${createQueryParams(params)}`}>
|
to={{
|
||||||
Show all tests and platforms
|
pathname: '/compare',
|
||||||
</a>
|
search: createQueryParams(params),
|
||||||
</p>
|
}}
|
||||||
|
>
|
||||||
|
Back to all tests and platforms
|
||||||
|
</Link>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="mx-auto">
|
<div className="mx-auto">
|
||||||
|
@ -318,8 +341,6 @@ CompareTableView.propTypes = {
|
||||||
originalProject: PropTypes.string,
|
originalProject: PropTypes.string,
|
||||||
newProject: PropTypes.string,
|
newProject: PropTypes.string,
|
||||||
originalRevision: PropTypes.string,
|
originalRevision: PropTypes.string,
|
||||||
projects: PropTypes.arrayOf(PropTypes.shape({})),
|
|
||||||
frameworks: PropTypes.arrayOf(PropTypes.shape({})),
|
|
||||||
selectedTimeRange: PropTypes.string,
|
selectedTimeRange: PropTypes.string,
|
||||||
updateParams: PropTypes.func.isRequired,
|
updateParams: PropTypes.func.isRequired,
|
||||||
originalSignature: PropTypes.string,
|
originalSignature: PropTypes.string,
|
||||||
|
@ -332,9 +353,9 @@ CompareTableView.propTypes = {
|
||||||
getDisplayResults: PropTypes.func.isRequired,
|
getDisplayResults: PropTypes.func.isRequired,
|
||||||
getQueryParams: PropTypes.func.isRequired,
|
getQueryParams: PropTypes.func.isRequired,
|
||||||
hasSubtests: PropTypes.bool,
|
hasSubtests: PropTypes.bool,
|
||||||
onPermalinkClick: PropTypes.func.isRequired,
|
onPermalinkClick: PropTypes.func,
|
||||||
hashFragment: PropTypes.string,
|
hashFragment: PropTypes.string,
|
||||||
$stateParams: PropTypes.shape({}).isRequired,
|
frameworks: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
CompareTableView.defaultProps = {
|
CompareTableView.defaultProps = {
|
||||||
|
@ -343,4 +364,5 @@ CompareTableView.defaultProps = {
|
||||||
validated: PropTypes.shape({}),
|
validated: PropTypes.shape({}),
|
||||||
hasSubtests: false,
|
hasSubtests: false,
|
||||||
hashFragment: '',
|
hashFragment: '',
|
||||||
|
onPermalinkClick: undefined,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,23 +1,20 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { react2angular } from 'react2angular/index.es2015';
|
|
||||||
import difference from 'lodash/difference';
|
import difference from 'lodash/difference';
|
||||||
|
|
||||||
import perf from '../../js/perf';
|
|
||||||
import { createQueryParams } from '../../helpers/url';
|
import { createQueryParams } from '../../helpers/url';
|
||||||
import { phTimeRanges } from '../../helpers/constants';
|
|
||||||
import {
|
import {
|
||||||
createNoiseMetric,
|
createNoiseMetric,
|
||||||
getCounterMap,
|
getCounterMap,
|
||||||
createGraphsLinks,
|
createGraphsLinks,
|
||||||
|
onPermalinkClick,
|
||||||
} from '../helpers';
|
} from '../helpers';
|
||||||
import { noiseMetricTitle } from '../constants';
|
import { noiseMetricTitle, phTimeRanges } from '../constants';
|
||||||
import withValidation from '../Validation';
|
import withValidation from '../Validation';
|
||||||
|
|
||||||
import CompareTableView from './CompareTableView';
|
import CompareTableView from './CompareTableView';
|
||||||
|
|
||||||
// TODO remove $location, $scope, $stateParams and $state after switching to react router
|
class CompareView extends React.PureComponent {
|
||||||
export class CompareView extends React.PureComponent {
|
|
||||||
getInterval = (oldTimestamp, newTimestamp) => {
|
getInterval = (oldTimestamp, newTimestamp) => {
|
||||||
const now = new Date().getTime() / 1000;
|
const now = new Date().getTime() / 1000;
|
||||||
let timeRange = Math.min(oldTimestamp, newTimestamp);
|
let timeRange = Math.min(oldTimestamp, newTimestamp);
|
||||||
|
@ -106,6 +103,7 @@ export class CompareView extends React.PureComponent {
|
||||||
} else {
|
} else {
|
||||||
params.selectedTimeRange = timeRange.value;
|
params.selectedTimeRange = timeRange.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
const detailsLink = `perf.html#/comparesubtest${createQueryParams(
|
const detailsLink = `perf.html#/comparesubtest${createQueryParams(
|
||||||
params,
|
params,
|
||||||
)}`;
|
)}`;
|
||||||
|
@ -215,6 +213,7 @@ export class CompareView extends React.PureComponent {
|
||||||
|
|
||||||
compareResults = new Map([...compareResults.entries()].sort());
|
compareResults = new Map([...compareResults.entries()].sort());
|
||||||
const updates = { compareResults, testsWithNoise, loading: false };
|
const updates = { compareResults, testsWithNoise, loading: false };
|
||||||
|
this.props.updateAppState({ compareData: compareResults });
|
||||||
|
|
||||||
const resultsArr = Array.from(compareResults.keys());
|
const resultsArr = Array.from(compareResults.keys());
|
||||||
const testsNoResults = difference(tableNames, resultsArr)
|
const testsNoResults = difference(tableNames, resultsArr)
|
||||||
|
@ -228,17 +227,7 @@ export class CompareView extends React.PureComponent {
|
||||||
return updates;
|
return updates;
|
||||||
};
|
};
|
||||||
|
|
||||||
onPermalinkClick = hashBasedValue => {
|
getHashFragment = () => this.props.location.hash;
|
||||||
const { $location, $scope } = this.props;
|
|
||||||
|
|
||||||
$location.hash(hashBasedValue);
|
|
||||||
$scope.$apply();
|
|
||||||
};
|
|
||||||
|
|
||||||
getHashFragment = () => {
|
|
||||||
const { $location } = this.props;
|
|
||||||
return $location.hash();
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
|
@ -246,8 +235,8 @@ export class CompareView extends React.PureComponent {
|
||||||
{...this.props}
|
{...this.props}
|
||||||
getQueryParams={this.getQueryParams}
|
getQueryParams={this.getQueryParams}
|
||||||
getDisplayResults={this.getDisplayResults}
|
getDisplayResults={this.getDisplayResults}
|
||||||
onPermalinkClick={this.onPermalinkClick}
|
onPermalinkClick={hashValue => onPermalinkClick(hashValue, this.props)}
|
||||||
hashFragment={this.getHashFragment()}
|
hashFragment={this.props.location.hash}
|
||||||
filterByFramework
|
filterByFramework
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -262,20 +251,13 @@ CompareView.propTypes = {
|
||||||
originalProject: PropTypes.string,
|
originalProject: PropTypes.string,
|
||||||
newProject: PropTypes.string,
|
newProject: PropTypes.string,
|
||||||
originalRevision: PropTypes.string,
|
originalRevision: PropTypes.string,
|
||||||
projects: PropTypes.arrayOf(PropTypes.shape({})),
|
|
||||||
frameworks: PropTypes.arrayOf(PropTypes.shape({})),
|
|
||||||
framework: PropTypes.string,
|
framework: PropTypes.string,
|
||||||
updateParams: PropTypes.func.isRequired,
|
updateParams: PropTypes.func.isRequired,
|
||||||
}),
|
}),
|
||||||
$stateParams: PropTypes.shape({}),
|
|
||||||
$state: PropTypes.shape({}),
|
|
||||||
user: PropTypes.shape({}).isRequired,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
CompareView.defaultProps = {
|
CompareView.defaultProps = {
|
||||||
validated: PropTypes.shape({}),
|
validated: PropTypes.shape({}),
|
||||||
$stateParams: null,
|
|
||||||
$state: null,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const requiredParams = new Set([
|
const requiredParams = new Set([
|
||||||
|
@ -284,15 +266,4 @@ const requiredParams = new Set([
|
||||||
'newRevision',
|
'newRevision',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const compareView = withValidation(requiredParams)(CompareView);
|
export default withValidation({ requiredParams })(CompareView);
|
||||||
|
|
||||||
perf.component(
|
|
||||||
'compareView',
|
|
||||||
react2angular(
|
|
||||||
compareView,
|
|
||||||
['user'],
|
|
||||||
['$location', '$scope', '$stateParams', '$state'],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
export default compareView;
|
|
||||||
|
|
|
@ -10,7 +10,6 @@ import { createApiUrl, perfSummaryEndpoint } from '../../helpers/url';
|
||||||
import { noDataFoundMessage } from '../constants';
|
import { noDataFoundMessage } from '../constants';
|
||||||
import LoadingSpinner from '../../shared/LoadingSpinner';
|
import LoadingSpinner from '../../shared/LoadingSpinner';
|
||||||
|
|
||||||
// TODO remove $stateParams after switching to react router
|
|
||||||
export default class ReplicatesGraph extends React.Component {
|
export default class ReplicatesGraph extends React.Component {
|
||||||
// TODO: sync parent with children IRT dataLoading
|
// TODO: sync parent with children IRT dataLoading
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
|
|
@ -11,7 +11,7 @@ function getRevisionSpecificDetails(
|
||||||
resultSet,
|
resultSet,
|
||||||
selectedTimeRange = undefined,
|
selectedTimeRange = undefined,
|
||||||
) {
|
) {
|
||||||
const truncatedRevision = revision.substring(0, 12);
|
const truncatedRevision = revision ? revision.substring(0, 12) : '';
|
||||||
const baselineOrNew = isBaseline || selectedTimeRange ? 'Base' : 'New';
|
const baselineOrNew = isBaseline || selectedTimeRange ? 'Base' : 'New';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -200,7 +200,7 @@ export default class SelectorCard extends React.Component {
|
||||||
missingRevision,
|
missingRevision,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
return (
|
return (
|
||||||
<Col sm="4" className="p-2">
|
<Col sm="4" className="p-2 text-left">
|
||||||
<Card className="card-height">
|
<Card className="card-height">
|
||||||
<CardHeader className="bg-lightgray">{title}</CardHeader>
|
<CardHeader className="bg-lightgray">{title}</CardHeader>
|
||||||
<CardBody>
|
<CardBody>
|
||||||
|
|
|
@ -64,3 +64,28 @@ export const graphColors = [
|
||||||
['darkorchid', '#9932cc'],
|
['darkorchid', '#9932cc'],
|
||||||
['blue', '#1752b8'],
|
['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 React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { react2angular } from 'react2angular/index.es2015';
|
|
||||||
import { Container, Col, Row } from 'reactstrap';
|
import { Container, Col, Row } from 'reactstrap';
|
||||||
import unionBy from 'lodash/unionBy';
|
import unionBy from 'lodash/unionBy';
|
||||||
|
import queryString from 'query-string';
|
||||||
|
|
||||||
import { getData, processResponse, processErrors } from '../../helpers/http';
|
import { getData, processResponse, processErrors } from '../../helpers/http';
|
||||||
import {
|
import {
|
||||||
getApiUrl,
|
getApiUrl,
|
||||||
repoEndpoint,
|
|
||||||
createApiUrl,
|
createApiUrl,
|
||||||
perfSummaryEndpoint,
|
perfSummaryEndpoint,
|
||||||
createQueryParams,
|
createQueryParams,
|
||||||
|
parseQueryParams,
|
||||||
|
updateQueryParams,
|
||||||
} from '../../helpers/url';
|
} from '../../helpers/url';
|
||||||
import {
|
import {
|
||||||
phTimeRanges,
|
|
||||||
phDefaultTimeRangeValue,
|
|
||||||
genericErrorMessage,
|
genericErrorMessage,
|
||||||
errorMessageClass,
|
errorMessageClass,
|
||||||
} from '../../helpers/constants';
|
} from '../../helpers/constants';
|
||||||
import perf from '../../js/perf';
|
|
||||||
import { processSelectedParam } from '../helpers';
|
import { processSelectedParam } from '../helpers';
|
||||||
import { endpoints, graphColors } from '../constants';
|
import {
|
||||||
|
endpoints,
|
||||||
|
graphColors,
|
||||||
|
phTimeRanges,
|
||||||
|
phDefaultTimeRangeValue,
|
||||||
|
} from '../constants';
|
||||||
import ErrorMessages from '../../shared/ErrorMessages';
|
import ErrorMessages from '../../shared/ErrorMessages';
|
||||||
import ErrorBoundary from '../../shared/ErrorBoundary';
|
import ErrorBoundary from '../../shared/ErrorBoundary';
|
||||||
import LoadingSpinner from '../../shared/LoadingSpinner';
|
import LoadingSpinner from '../../shared/LoadingSpinner';
|
||||||
|
@ -34,8 +37,6 @@ class GraphsView extends React.Component {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
timeRange: this.getDefaultTimeRange(),
|
timeRange: this.getDefaultTimeRange(),
|
||||||
frameworks: [],
|
|
||||||
projects: [],
|
|
||||||
zoom: {},
|
zoom: {},
|
||||||
selectedDataPoint: null,
|
selectedDataPoint: null,
|
||||||
highlightAlerts: true,
|
highlightAlerts: true,
|
||||||
|
@ -51,32 +52,19 @@ class GraphsView extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
this.getData();
|
|
||||||
this.checkQueryParams();
|
this.checkQueryParams();
|
||||||
}
|
}
|
||||||
|
|
||||||
getDefaultTimeRange = () => {
|
getDefaultTimeRange = () => {
|
||||||
const { $stateParams } = this.props;
|
const { location } = this.props;
|
||||||
|
const { timerange } = parseQueryParams(location.search);
|
||||||
|
|
||||||
const defaultValue = $stateParams.timerange
|
const defaultValue = timerange
|
||||||
? parseInt($stateParams.timerange, 10)
|
? parseInt(timerange, 10)
|
||||||
: phDefaultTimeRangeValue;
|
: phDefaultTimeRangeValue;
|
||||||
return phTimeRanges.find(time => time.value === defaultValue);
|
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 = () => {
|
checkQueryParams = () => {
|
||||||
const {
|
const {
|
||||||
series,
|
series,
|
||||||
|
@ -84,7 +72,7 @@ class GraphsView extends React.Component {
|
||||||
selected,
|
selected,
|
||||||
highlightAlerts,
|
highlightAlerts,
|
||||||
highlightedRevisions,
|
highlightedRevisions,
|
||||||
} = this.props.$stateParams;
|
} = queryString.parse(this.props.location.search);
|
||||||
|
|
||||||
const updates = {};
|
const updates = {};
|
||||||
|
|
||||||
|
@ -271,11 +259,7 @@ class GraphsView extends React.Component {
|
||||||
|
|
||||||
parseSeriesParam = series =>
|
parseSeriesParam = series =>
|
||||||
series.map(encodedSeries => {
|
series.map(encodedSeries => {
|
||||||
const partialSeriesString = decodeURIComponent(encodedSeries).replace(
|
const partialSeriesArray = encodedSeries.split(',');
|
||||||
/[[\]"]/g,
|
|
||||||
'',
|
|
||||||
);
|
|
||||||
const partialSeriesArray = partialSeriesString.split(',');
|
|
||||||
const partialSeriesObject = {
|
const partialSeriesObject = {
|
||||||
repository_name: partialSeriesArray[0],
|
repository_name: partialSeriesArray[0],
|
||||||
// TODO deprecate signature_hash
|
// TODO deprecate signature_hash
|
||||||
|
@ -299,14 +283,11 @@ class GraphsView extends React.Component {
|
||||||
};
|
};
|
||||||
|
|
||||||
updateParams = params => {
|
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, {
|
updateQueryParams(newQueryString, history, location);
|
||||||
location: true,
|
|
||||||
inherit: true,
|
|
||||||
relative: current,
|
|
||||||
notify: false,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
changeParams = () => {
|
changeParams = () => {
|
||||||
|
@ -325,21 +306,28 @@ class GraphsView extends React.Component {
|
||||||
);
|
);
|
||||||
const params = {
|
const params = {
|
||||||
series: newSeries,
|
series: newSeries,
|
||||||
highlightedRevisions: highlightedRevisions.filter(rev => rev.length),
|
|
||||||
highlightAlerts: +highlightAlerts,
|
highlightAlerts: +highlightAlerts,
|
||||||
timerange: timeRange.value,
|
timerange: timeRange.value,
|
||||||
zoom,
|
zoom,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const newHighlightedRevisions = highlightedRevisions.filter(
|
||||||
|
rev => rev.length,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (newHighlightedRevisions.length) {
|
||||||
|
params.highlightedRevisions = newHighlightedRevisions;
|
||||||
|
}
|
||||||
|
|
||||||
if (!selectedDataPoint) {
|
if (!selectedDataPoint) {
|
||||||
params.selected = null;
|
delete params.selected;
|
||||||
} else {
|
} else {
|
||||||
const { signature_id, pushId, x, y } = selectedDataPoint;
|
const { signature_id, pushId, x, y } = selectedDataPoint;
|
||||||
params.selected = [signature_id, pushId, x, y].join(',');
|
params.selected = [signature_id, pushId, x, y].join(',');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Object.keys(zoom).length === 0) {
|
if (Object.keys(zoom).length === 0) {
|
||||||
params.zoom = null;
|
delete params.zoom;
|
||||||
} else {
|
} else {
|
||||||
params.zoom = [...zoom.x.map(z => z.getTime()), ...zoom.y].toString();
|
params.zoom = [...zoom.x.map(z => z.getTime()), ...zoom.y].toString();
|
||||||
}
|
}
|
||||||
|
@ -350,8 +338,6 @@ class GraphsView extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
timeRange,
|
timeRange,
|
||||||
projects,
|
|
||||||
frameworks,
|
|
||||||
testData,
|
testData,
|
||||||
highlightAlerts,
|
highlightAlerts,
|
||||||
highlightedRevisions,
|
highlightedRevisions,
|
||||||
|
@ -365,6 +351,7 @@ class GraphsView extends React.Component {
|
||||||
visibilityChanged,
|
visibilityChanged,
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
|
const { projects, frameworks } = this.props;
|
||||||
return (
|
return (
|
||||||
<ErrorBoundary
|
<ErrorBoundary
|
||||||
errorClasses={errorMessageClass}
|
errorClasses={errorMessageClass}
|
||||||
|
@ -464,7 +451,7 @@ class GraphsView extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
GraphsView.propTypes = {
|
GraphsView.propTypes = {
|
||||||
$stateParams: PropTypes.shape({
|
location: PropTypes.shape({
|
||||||
zoom: PropTypes.string,
|
zoom: PropTypes.string,
|
||||||
selected: PropTypes.string,
|
selected: PropTypes.string,
|
||||||
highlightAlerts: PropTypes.string,
|
highlightAlerts: PropTypes.string,
|
||||||
|
@ -477,21 +464,11 @@ GraphsView.propTypes = {
|
||||||
PropTypes.arrayOf(PropTypes.string),
|
PropTypes.arrayOf(PropTypes.string),
|
||||||
]),
|
]),
|
||||||
}),
|
}),
|
||||||
$state: PropTypes.shape({
|
|
||||||
current: PropTypes.shape({}),
|
|
||||||
transitionTo: PropTypes.func,
|
|
||||||
}),
|
|
||||||
user: PropTypes.shape({}).isRequired,
|
user: PropTypes.shape({}).isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
GraphsView.defaultProps = {
|
GraphsView.defaultProps = {
|
||||||
$stateParams: undefined,
|
location: undefined,
|
||||||
$state: undefined,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
perf.component(
|
|
||||||
'graphsView',
|
|
||||||
react2angular(GraphsView, ['user'], ['$stateParams', '$state']),
|
|
||||||
);
|
|
||||||
|
|
||||||
export default GraphsView;
|
export default GraphsView;
|
||||||
|
|
|
@ -10,7 +10,7 @@ import {
|
||||||
Input,
|
Input,
|
||||||
} from 'reactstrap';
|
} from 'reactstrap';
|
||||||
|
|
||||||
import { phTimeRanges } from '../../helpers/constants';
|
import { phTimeRanges } from '../constants';
|
||||||
import DropdownMenuItems from '../../shared/DropdownMenuItems';
|
import DropdownMenuItems from '../../shared/DropdownMenuItems';
|
||||||
|
|
||||||
import TestDataModal from './TestDataModal';
|
import TestDataModal from './TestDataModal';
|
||||||
|
@ -34,21 +34,13 @@ export default class GraphsViewControls extends React.Component {
|
||||||
highlightedRevisions,
|
highlightedRevisions,
|
||||||
updateTimeRange,
|
updateTimeRange,
|
||||||
hasNoData,
|
hasNoData,
|
||||||
projects,
|
|
||||||
frameworks,
|
|
||||||
toggle,
|
toggle,
|
||||||
showModal,
|
showModal,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container fluid className="justify-content-start">
|
<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">
|
<Row className="pb-3">
|
||||||
<Col sm="auto" className="pl-0 py-2 pr-2" key={timeRange}>
|
<Col sm="auto" className="pl-0 py-2 pr-2" key={timeRange}>
|
||||||
<UncontrolledDropdown
|
<UncontrolledDropdown
|
||||||
|
@ -134,21 +126,17 @@ GraphsViewControls.propTypes = {
|
||||||
]).isRequired,
|
]).isRequired,
|
||||||
updateTimeRange: PropTypes.func.isRequired,
|
updateTimeRange: PropTypes.func.isRequired,
|
||||||
hasNoData: PropTypes.bool.isRequired,
|
hasNoData: PropTypes.bool.isRequired,
|
||||||
projects: PropTypes.arrayOf(PropTypes.shape({})),
|
|
||||||
getTestData: PropTypes.func.isRequired,
|
getTestData: PropTypes.func.isRequired,
|
||||||
options: PropTypes.shape({
|
options: PropTypes.shape({
|
||||||
option: PropTypes.string,
|
option: PropTypes.string,
|
||||||
relatedSeries: PropTypes.shape({}),
|
relatedSeries: PropTypes.shape({}),
|
||||||
}),
|
}),
|
||||||
testData: PropTypes.arrayOf(PropTypes.shape({})),
|
testData: PropTypes.arrayOf(PropTypes.shape({})),
|
||||||
frameworks: PropTypes.arrayOf(PropTypes.shape({})),
|
|
||||||
showModal: PropTypes.bool,
|
showModal: PropTypes.bool,
|
||||||
toggle: PropTypes.func.isRequired,
|
toggle: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
GraphsViewControls.defaultProps = {
|
GraphsViewControls.defaultProps = {
|
||||||
frameworks: [],
|
|
||||||
projects: [],
|
|
||||||
options: undefined,
|
options: undefined,
|
||||||
testData: [],
|
testData: [],
|
||||||
showModal: false,
|
showModal: false,
|
||||||
|
|
|
@ -8,11 +8,7 @@ import PerfSeriesModel, {
|
||||||
getSeriesName,
|
getSeriesName,
|
||||||
getTestName,
|
getTestName,
|
||||||
} from '../models/perfSeries';
|
} from '../models/perfSeries';
|
||||||
import {
|
import { thPerformanceBranches } from '../helpers/constants';
|
||||||
phFrameworksWithRelatedBranches,
|
|
||||||
phTimeRanges,
|
|
||||||
thPerformanceBranches,
|
|
||||||
} from '../helpers/constants';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
endpoints,
|
endpoints,
|
||||||
|
@ -21,6 +17,8 @@ import {
|
||||||
noiseMetricTitle,
|
noiseMetricTitle,
|
||||||
summaryStatusMap,
|
summaryStatusMap,
|
||||||
alertStatusMap,
|
alertStatusMap,
|
||||||
|
phFrameworksWithRelatedBranches,
|
||||||
|
phTimeRanges,
|
||||||
} from './constants';
|
} from './constants';
|
||||||
|
|
||||||
export const displayNumber = input =>
|
export const displayNumber = input =>
|
||||||
|
@ -114,7 +112,7 @@ const analyzeSet = (values, testName) => {
|
||||||
average,
|
average,
|
||||||
stddev,
|
stddev,
|
||||||
stddevPct: Math.round(calcPercentOf(stddev, average) * 100) / 100,
|
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
|
// We use slice to keep the original values at their original order
|
||||||
// in case the order is important elsewhere.
|
// in case the order is important elsewhere.
|
||||||
runs: values.slice().sort(numericCompare),
|
runs: values.slice().sort(numericCompare),
|
||||||
|
@ -171,7 +169,6 @@ export const getCounterMap = function getCounterMap(
|
||||||
originalData,
|
originalData,
|
||||||
newData,
|
newData,
|
||||||
) {
|
) {
|
||||||
// TODO setting this value seems a bit odd, look into how its being used
|
|
||||||
const cmap = { isEmpty: false };
|
const cmap = { isEmpty: false };
|
||||||
const hasOrig = originalData && originalData.values.length;
|
const hasOrig = originalData && originalData.values.length;
|
||||||
const hasNew = newData && newData.values.length;
|
const hasNew = newData && newData.values.length;
|
||||||
|
@ -278,7 +275,7 @@ export const getCounterMap = function getCounterMap(
|
||||||
return cmap;
|
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(
|
export const getGraphsLink = function getGraphsLink(
|
||||||
seriesList,
|
seriesList,
|
||||||
resultSets,
|
resultSets,
|
||||||
|
@ -329,7 +326,6 @@ export const createNoiseMetric = function createNoiseMetric(
|
||||||
return compareResults;
|
return compareResults;
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO
|
|
||||||
export const createGraphsLinks = (
|
export const createGraphsLinks = (
|
||||||
validatedProps,
|
validatedProps,
|
||||||
links,
|
links,
|
||||||
|
@ -368,7 +364,7 @@ export const createGraphsLinks = (
|
||||||
return links;
|
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
|
// for originalSignature and newSignature query params
|
||||||
const Alert = (alertData, optionCollectionMap) => ({
|
const Alert = (alertData, optionCollectionMap) => ({
|
||||||
...alertData,
|
...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 = (
|
export const getGraphsURL = (
|
||||||
alert,
|
alert,
|
||||||
timeRange,
|
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}`;
|
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
|
// automatically add related branches (we take advantage of
|
||||||
// the otherwise rather useless signature hash to avoid having to fetch this
|
// the otherwise rather useless signature hash to avoid having to fetch this
|
||||||
// information from the server)
|
// information from the server)
|
||||||
|
|
||||||
if (phFrameworksWithRelatedBranches.includes(performanceFrameworkId)) {
|
if (phFrameworksWithRelatedBranches.includes(performanceFrameworkId)) {
|
||||||
const branches =
|
const branches =
|
||||||
alertRepository === 'mozilla-beta'
|
alertRepository === 'mozilla-beta'
|
||||||
|
@ -537,11 +531,11 @@ export const convertParams = (params, value) =>
|
||||||
Boolean(params[value] !== undefined && parseInt(params[value], 10));
|
Boolean(params[value] !== undefined && parseInt(params[value], 10));
|
||||||
|
|
||||||
export const getFrameworkData = props => {
|
export const getFrameworkData = props => {
|
||||||
const { framework, frameworks } = props;
|
const { validated, frameworks } = props;
|
||||||
|
|
||||||
if (framework) {
|
if (validated.framework) {
|
||||||
const frameworkObject = frameworks.find(
|
const frameworkObject = frameworks.find(
|
||||||
item => item.id === parseInt(framework, 10),
|
item => item.id === parseInt(validated.framework, 10),
|
||||||
);
|
);
|
||||||
return frameworkObject;
|
return frameworkObject;
|
||||||
}
|
}
|
||||||
|
@ -619,3 +613,9 @@ export const getSeriesData = async (
|
||||||
|
|
||||||
return updates;
|
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"
|
"@testing-library/dom" "^6.1.0"
|
||||||
"@types/testing-library__react" "^9.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":
|
"@types/babel__core@^7.1.0":
|
||||||
version "7.1.0"
|
version "7.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.0.tgz#710f2487dda4dcfd010ca6abb2b4dc7394365c51"
|
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"
|
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.3.tgz#bdfd69d61e464dcc81b25159c270d75a73c1a636"
|
||||||
integrity sha512-Il2DtDVRGDcqjDtE+rF8iqg1CArehSK84HZJCT7AMITlyXRBpuPhqGLDQMowraqqu1coEaimg4ZOqggt6L6L+A==
|
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@*":
|
"@types/minimatch@*":
|
||||||
version "3.0.3"
|
version "3.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
|
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
|
||||||
|
@ -1339,13 +1322,6 @@
|
||||||
lodash.unescape "4.0.1"
|
lodash.unescape "4.0.1"
|
||||||
semver "5.5.0"
|
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":
|
"@webassemblyjs/ast@1.8.5":
|
||||||
version "1.8.5"
|
version "1.8.5"
|
||||||
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.8.5.tgz#51b1c5fe6576a34953bf4b253df9f0d490d9e359"
|
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"
|
json-schema-traverse "^0.4.1"
|
||||||
uri-js "^4.2.2"
|
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:
|
ansi-colors@^3.0.0:
|
||||||
version "3.2.4"
|
version "3.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf"
|
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"
|
import-local "^2.0.0"
|
||||||
jest-cli "^24.9.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:
|
jquery@3.4.1:
|
||||||
version "3.4.1"
|
version "3.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.4.1.tgz#714f1f8d9dde4bdfa55764ba37ef214630d80ef2"
|
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"
|
resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2"
|
||||||
integrity sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=
|
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:
|
lodash.isarguments@^3.0.0:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a"
|
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"
|
resolved "https://registry.yarnpkg.com/ng-text-truncate-2/-/ng-text-truncate-2-1.0.1.tgz#167b92b04f092e940cc6d60a336fa604570392bd"
|
||||||
integrity sha1-FnuSsE8JLpQMxtYKM2+mBFcDkr0=
|
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:
|
nice-try@^1.0.4:
|
||||||
version "1.0.5"
|
version "1.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
|
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"
|
prop-types "^15.6.0"
|
||||||
react-lifecycles-compat "^3.0.4"
|
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:
|
react@16.9.0:
|
||||||
version "16.9.0"
|
version "16.9.0"
|
||||||
resolved "https://registry.yarnpkg.com/react/-/react-16.9.0.tgz#40ba2f9af13bc1a38d75dbf2f4359a5185c4f7aa"
|
resolved "https://registry.yarnpkg.com/react/-/react-16.9.0.tgz#40ba2f9af13bc1a38d75dbf2f4359a5185c4f7aa"
|
||||||
|
|
Загрузка…
Ссылка в новой задаче