Skip to content

Commit

Permalink
Merge pull request #1860 from HubSpot/y_u_load_so_slow
Browse files Browse the repository at this point in the history
Make SingularityUI snappier
  • Loading branch information
ssalinas authored Oct 2, 2018
2 parents eb591e0 + 5c8a881 commit 11a533e
Show file tree
Hide file tree
Showing 19 changed files with 4,095 additions and 3,606 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -638,6 +638,18 @@ public List<SingularityRequestParent> getActiveRequests(
user, valueOrFalse(filterRelevantForUser), valueOrFalse(includeFullRequestData), Optional.fromNullable(limit), requestTypes);
}

@GET
@Path("/ids")
@Operation(summary = "Retrieve the list of active request ids")
public List<String> getActiveRequests(
@Parameter(hidden = true) @Auth SingularityUser user,
@Parameter(description = "Fetched a cached version of this data to limit expensive operations") @QueryParam("useWebCache") Boolean useWebCache) {
return filterAutorized(Lists.newArrayList(requestManager.getRequests(useWebCache(useWebCache))), SingularityAuthorizationScope.READ, user)
.stream()
.map((r) -> r.getRequest().getId())
.collect(Collectors.toList());
}


@GET
@PropertyFiltering
Expand Down
5 changes: 4 additions & 1 deletion SingularityUI/app/actions/api/history.es6
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,10 @@ export const FetchTaskSearchParams = buildApiAction(
} else {
url = `/history/tasks?count=${count}&page=${page}&${Utils.queryParams(args)}`;
}
return { url };
return {
url,
catchStatusCodes: [404, 500]
};
});

export const FetchRequestRunHistory = buildApiAction(
Expand Down
26 changes: 13 additions & 13 deletions SingularityUI/app/actions/api/requests.es6
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,26 @@ export const FetchRequests = buildApiAction(
{url: '/requests'}
);

export const FetchUserRelevantRequests = buildApiAction(
'FETCH_USER_RELEVANT_REQUESTS',
(requestTypes = []) => {
let params = 'filterRelevantForUser=true&includeFullRequestData=true&limit=50'
_.each(requestTypes, (requestType) => {
params = params + `&requestType=${requestType}`
});
return {url: `/requests?${params}`}
}
export const FetchRequestIds = buildApiAction(
'FETCH_REQUESTS',
{url: '/requests/ids?useWebCache=true'}
);

export const FetchRequestsInState = buildApiAction(
'FETCH_REQUESTS_IN_STATE',
(state, renderNotFoundIf404) => {
(state = 'all', renderNotFoundIf404 = true, propertyFilter = null) => {
let queryString = '?useWebCache=true';
const propertyJoin = '&property=';
if (propertyFilter != null) {
queryString += '&property=';
queryString += propertyFilter.join(propertyJoin);
}
if (_.contains(['pending', 'cleanup'], state)) {
return {url: `/requests/queued/${state}`, renderNotFoundIf404};
return {url: `/requests/queued/${state}`, renderNotFoundIf404}; // no property filter for these, different format
} else if (_.contains(['all', 'noDeploy', 'activeDeploy', 'overUtilizedCpu', 'underUtilizedCpu', 'underUtilizedMem', 'underUtilizedDisk'], state)) {
return {url: '/requests', renderNotFoundIf404};
return {url: `/requests${queryString}`, renderNotFoundIf404};
}
return {url: `/requests/${state}`, renderNotFoundIf404};
return {url: `/requests/${state}${queryString}`, renderNotFoundIf404};
}
);

Expand Down
3 changes: 0 additions & 3 deletions SingularityUI/app/actions/ui/dashboard.es6

This file was deleted.

20 changes: 14 additions & 6 deletions SingularityUI/app/actions/ui/requests.es6
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
import { FetchRequestsInState } from '../../actions/api/requests';
import { FetchRequestsInState, FetchRequestIds } from '../../actions/api/requests';
import { FetchRequestUtilizations } from '../../actions/api/utilization';

export const refresh = (state) => (dispatch) => {
const promises = []
promises.push(dispatch(FetchRequestsInState.trigger(state === 'cleaning' ? 'cleanup' : state, true)));
promises.push(dispatch(FetchRequestUtilizations.trigger()));
return Promise.all(promises);
export const refresh = (state, propertyFilter) => (dispatch) => {
const promises = []
promises.push(dispatch(FetchRequestsInState.trigger(state === 'cleaning' ? 'cleanup' : state, true, propertyFilter)));
promises.push(dispatch(FetchRequestUtilizations.trigger()));
return Promise.all(promises);
}

export const initialize = (requestIds) => (dispatch) => {
if (requestIds.isFetching || requestIds.data.length) {
return Promise.resolve();
} else {
return dispatch(FetchRequestIds.trigger());
}
}
30 changes: 10 additions & 20 deletions SingularityUI/app/components/globalSearch/GlobalSearch.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ import React from 'react';
import classNames from 'classnames';
import { connect } from 'react-redux';
import { withRouter } from 'react-router';
import { FetchRequests } from '../../actions/api/requests';
import { FetchRequestIds } from '../../actions/api/requests';
import { SetVisibility } from '../../actions/ui/globalSearch';
import { refresh } from '../../actions/ui/requestDetail';
import { push } from 'react-router-redux';
import { Link } from 'react-router';

import { Typeahead } from 'react-typeahead';
import key from 'keymaster';
import filterSelector from '../../selectors/requests/filterSelector';
import idSelector from '../../selectors/idSelector';

class GlobalSearch extends React.Component {

Expand Down Expand Up @@ -72,18 +72,10 @@ class GlobalSearch extends React.Component {
}

searchOptions(inputValue, options) {
const searched = filterSelector({
requestsInState: options,
const searched = idSelector({
options: options,
filter: {
state: 'all',
searchFilter: inputValue,
subFilter: [
'SERVICE',
'WORKER',
'SCHEDULED',
'ON_DEMAND',
'RUN_ONCE'
]
searchFilter: inputValue
}
});

Expand Down Expand Up @@ -111,10 +103,8 @@ class GlobalSearch extends React.Component {
}

render() {
const options = _.map(this.props.requests, (requestParent) => ({
request: requestParent.request,
id: requestParent.request.id,
requestDeployState: requestParent.requestDeployState
const options = _.map(this.props.requests, (id) => ({
id: id,
}));

const globalSearchClasses = classNames('global-search', {
Expand All @@ -141,8 +131,8 @@ class GlobalSearch extends React.Component {
}}
placeholder="Search all requests"
onOptionSelected={this.optionSelected}
searchOptions={this.searchOptions}
displayOption={this.renderOption}
searchOptions={this.searchOptions}
formInputOption={this.getValueFromOption}
inputDisplayOption={this.getValueFromOption}
/>
Expand All @@ -155,10 +145,10 @@ class GlobalSearch extends React.Component {
}

export default connect((state) => ({
requests: state.api.requests.data,
requests: state.api.requestIds.data,
visible: state.ui.globalSearch.visible
}), {
getRequests: FetchRequests.trigger,
getRequests: FetchRequestIds.trigger,
setVisibility: SetVisibility,
push,
refresh
Expand Down
31 changes: 26 additions & 5 deletions SingularityUI/app/components/requests/RequestsPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,22 @@ import {
} from '../../actions/api/requests';
import { FetchRequestRunHistory } from '../../actions/api/history';
import { FetchTaskFiles } from '../../actions/api/sandbox';
import { refresh } from '../../actions/ui/requests';
import { refresh, initialize } from '../../actions/ui/requests';

import UITable from '../common/table/UITable';
import RequestFilters from './RequestFilters';
import * as Cols from './Columns';

import filterSelector from '../../selectors/requests/filterSelector';
import idSelector from '../../selectors/idSelector';

import Utils from '../../utils';
import Loader from '../common/Loader';

class RequestsPage extends Component {

static propertyFilter = ['state', 'request.id', 'request.requestType', 'request.instances', 'request.group', 'request.bounceAfterScale', 'requestDeployState.activeDeploy.deployId', 'requestDeployState.activeDeploy.user', 'requestDeployState.activeDeploy.timestamp', 'requestDeployState.activeDeploy.loadBalancerOptions']

static propTypes = {
requestsInState: PropTypes.array,
fetchFilter: PropTypes.func,
Expand Down Expand Up @@ -56,6 +59,10 @@ class RequestsPage extends Component {
};
}

componentWillMount() {
this.props.fetchFilter();
}

handleFilterChange(filter) {
const lastFilterState = this.props.filter.state;
this.setState({
Expand Down Expand Up @@ -107,7 +114,17 @@ class RequestsPage extends Component {
}

render() {
const displayRequests = filterSelector({requestsInState: this.props.requestsInState, filter: this.props.filter, requestUtilizations: this.props.requestUtilizations});
let displayRequests = []
if (this.props.requestsInState.length) {
displayRequests = filterSelector({requestsInState: this.props.requestsInState, filter: this.props.filter, requestUtilizations: this.props.requestUtilizations});
} else if (this.props.requestIds.data.length) {
const options = _.map(this.props.requestIds.data, (id) => ({
id: id,
starred: _.contains(Utils.maybe(this.props.user, ['data', 'settings', 'starredRequestIds'], []), id) ? 1 : 0,
request: {id: id}
}));
displayRequests = idSelector({options: options, filter: this.props.filter}).sort((a, b) => b.starred - a.starred);
}

let table;
if (this.state.loading) {
Expand All @@ -120,6 +137,8 @@ class RequestsPage extends Component {
ref="table"
data={displayRequests}
keyGetter={(request) => (request.request ? request.request.id : request.requestId)}
rowChunkSize={20}
paginated={true}
>
{this.getColumns()}
</UITable>
Expand Down Expand Up @@ -167,14 +186,16 @@ function mapStateToProps(state, ownProps) {
notFound: statusCode === 404,
requestsInState: modifiedRequests,
requestUtilizations: state.api.requestUtilizations.data,
requestIds: state.api.requestIds,
groups: userGroups,
user: state.api.user,
filter
};
}

function mapDispatchToProps(dispatch) {
function mapDispatchToProps(dispatch, ownProps) {
return {
fetchFilter: (state) => dispatch(FetchRequestsInState.trigger(state === 'cleaning' ? 'cleanup' : state, true)),
fetchFilter: (state) => dispatch(FetchRequestsInState.trigger(state === 'cleaning' ? 'cleanup' : state, true, RequestsPage.propertyFilter)),
removeRequest: (requestid, data) => dispatch(RemoveRequest.trigger(requestid, data)),
unpauseRequest: (requestId, data) => dispatch(UnpauseRequest.trigger(requestId, data)),
runNow: (requestId, data) => dispatch(RunRequest.trigger(requestId, data)),
Expand All @@ -188,4 +209,4 @@ function mapDispatchToProps(dispatch) {
};
}

export default connect(mapStateToProps, mapDispatchToProps)(rootComponent(withRouter(RequestsPage), (props) => refresh(props.params.state || 'all'), true, false));
export default connect(mapStateToProps, mapDispatchToProps)(rootComponent(withRouter(RequestsPage), (props) => refresh(props.params.state || 'all', RequestsPage.propertyFilter), true, false, (props) => initialize(props.requestIds)));
9 changes: 0 additions & 9 deletions SingularityUI/app/initialize.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
import Raven from 'raven-js';

if (window.config.sentryDsn) {
Raven.config(window.config.sentryDsn).install();
}

// explicit polyfills for older browsers
import 'core-js/es6';

Expand Down Expand Up @@ -77,9 +71,6 @@ document.addEventListener('DOMContentLoaded', () => {
if (!store.getState().api.user.data.user) {
return renderUserIdForm();
} else {
if (window.config.sentryDsn) {
Raven.setUserContext({ email: store.getState().api.user.data.user.email });
}
userId = store.getState().api.user.data.user.id
// Set up starred requests
maybeImportStarredRequests(store, store.getState().api.user, userId);
Expand Down
8 changes: 4 additions & 4 deletions SingularityUI/app/reducers/api/index.es6
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ import {

import {
FetchRequests,
FetchUserRelevantRequests,
FetchRequestIds,
FetchRequest,
SaveRequest,
RemoveRequest,
Expand Down Expand Up @@ -116,9 +116,9 @@ const decommissionRack = buildApiActionReducer(DecommissionRack, []);
const removeRack = buildApiActionReducer(RemoveRack, []);
const reactivateRack = buildApiActionReducer(ReactivateRack, []);
const request = buildKeyedApiActionReducer(FetchRequest);
const requestIds = buildApiActionReducer(FetchRequestIds, [])
const saveRequest = buildApiActionReducer(SaveRequest);
const requests = buildApiActionReducer(FetchRequests, []);
const userRelevantRequests = buildApiActionReducer(FetchUserRelevantRequests, []);
const requestsInState = buildApiActionReducer(FetchRequestsInState, []);
const requestHistory = buildKeyedApiActionReducer(FetchRequestHistory, []);
const requestArgHistory = buildKeyedApiActionReducer(FetchRequestArgHistory, []);
Expand Down Expand Up @@ -150,7 +150,7 @@ const requestGroups = buildApiActionReducer(FetchGroups, []);
const inactiveHosts = buildApiActionReducer(FetchInactiveHosts, []);
const utilization = buildApiActionReducer(FetchUtilization, {});
const requestUtilizations = buildApiActionReducer(FetchRequestUtilizations, []);
const requestUtilization = buildKeyedApiActionReducer(FetchRequestUtilization, []);
const requestUtilization = buildKeyedApiActionReducer(FetchRequestUtilization, {});

export default combineReducers({
user,
Expand Down Expand Up @@ -180,7 +180,7 @@ export default combineReducers({
unpauseRequest,
exitRequestCooldown,
requests,
userRelevantRequests,
requestIds,
requestsInState,
requestHistory,
requestArgHistory,
Expand Down
25 changes: 0 additions & 25 deletions SingularityUI/app/reducers/ui/dashboard.es6

This file was deleted.

2 changes: 0 additions & 2 deletions SingularityUI/app/reducers/ui/index.es6
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,11 @@ import { combineReducers } from 'redux';
import refresh from './refresh';
import form from './form';
import globalSearch from './globalSearch';
import dashboard from './dashboard';
import slaves from './slaves';

export default combineReducers({
refresh,
form,
globalSearch,
dashboard,
slaves
});
28 changes: 28 additions & 0 deletions SingularityUI/app/selectors/idSelector.es6
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { createSelector } from 'reselect';
import micromatch from 'micromatch';
import fuzzy from 'fuzzy';
import Utils from '../utils';

const getOptions = (state) => state.options;
const getFilter = (state) => state.filter;

export default createSelector([getOptions, getFilter], (options, filter) => {
let filteredOptions = options;
const getId = (parent) => parent.id || '';
if (Utils.isGlobFilter(filter.searchFilter)) {
const idMatches = _.filter(filteredOptions, (parent) => (
micromatch.isMatch(getId(parent), `${filter.searchFilter}*`)
));
filteredOptions = idMatches;
} else if (filter.searchFilter) {
// Allow searching by the first letter of each word by applying same
// search heuristics to just the upper case characters of each option
const idMatches = fuzzy.filter(filter.searchFilter, filteredOptions, {
extract: Utils.isAllUpperCase(filter.searchFilter)
? (parent) => Utils.getUpperCaseCharacters(getId(parent))
: getId,
});
filteredOptions = Utils.fuzzyFilter(filter.searchFilter, idMatches);
}
return filteredOptions;
});
Loading

0 comments on commit 11a533e

Please sign in to comment.