188 lines
5.8 KiB
JavaScript
188 lines
5.8 KiB
JavaScript
import React, {PropTypes} from 'react';
|
||
import flatten from 'lodash/flatten';
|
||
import reject from 'lodash/reject';
|
||
import uniqBy from 'lodash/uniqBy';
|
||
import {
|
||
showDatabases,
|
||
showQueries,
|
||
killQuery,
|
||
} from 'shared/apis/metaQuery';
|
||
|
||
import showDatabasesParser from 'shared/parsing/showDatabases';
|
||
import showQueriesParser from 'shared/parsing/showQueries';
|
||
|
||
const times = [
|
||
{test: /ns/, magnitude: 0},
|
||
{test: /^\d*u/, magnitude: 1},
|
||
{test: /^\d*ms/, magnitude: 2},
|
||
{test: /^\d*s/, magnitude: 3},
|
||
{test: /^\d*m\d*s/, magnitude: 4},
|
||
{test: /^\d*h\d*m\d*s/, magnitude: 5},
|
||
];
|
||
|
||
export const QueriesPage = React.createClass({
|
||
propTypes: {
|
||
dataNodes: PropTypes.arrayOf(PropTypes.string.isRequired).isRequired,
|
||
addFlashMessage: PropTypes.func,
|
||
params: PropTypes.shape({
|
||
clusterID: PropTypes.string.isRequired,
|
||
}),
|
||
},
|
||
|
||
getInitialState() {
|
||
return {
|
||
queries: [],
|
||
queryIDToKill: null,
|
||
};
|
||
},
|
||
|
||
componentDidMount() {
|
||
this.updateQueries();
|
||
const updateInterval = 5000;
|
||
this.intervalID = setInterval(this.updateQueries, updateInterval);
|
||
},
|
||
|
||
componentWillUnmount() {
|
||
clearInterval(this.intervalID);
|
||
},
|
||
|
||
updateQueries() {
|
||
const {dataNodes, addFlashMessage, params} = this.props;
|
||
showDatabases(dataNodes, params.clusterID).then((resp) => {
|
||
const {databases, errors} = showDatabasesParser(resp.data);
|
||
if (errors.length) {
|
||
errors.forEach((message) => addFlashMessage({type: 'error', text: message}));
|
||
return;
|
||
}
|
||
|
||
const fetches = databases.map((db) => showQueries(dataNodes, db, params.clusterID));
|
||
|
||
Promise.all(fetches).then((queryResponses) => {
|
||
const allQueries = [];
|
||
queryResponses.forEach((queryResponse) => {
|
||
const result = showQueriesParser(queryResponse.data);
|
||
if (result.errors.length) {
|
||
result.erorrs.forEach((message) => this.props.addFlashMessage({type: 'error', text: message}));
|
||
}
|
||
|
||
allQueries.push(...result.queries);
|
||
});
|
||
|
||
const queries = uniqBy(flatten(allQueries), (q) => q.id);
|
||
|
||
// sorting queries by magnitude, so generally longer queries will appear atop the list
|
||
const sortedQueries = queries.sort((a, b) => {
|
||
const aTime = times.find((t) => a.duration.match(t.test));
|
||
const bTime = times.find((t) => b.duration.match(t.test));
|
||
return +aTime.magnitude <= +bTime.magnitude;
|
||
});
|
||
this.setState({
|
||
queries: sortedQueries,
|
||
});
|
||
});
|
||
});
|
||
},
|
||
|
||
render() {
|
||
const {queries} = this.state;
|
||
return (
|
||
<div>
|
||
<div className="enterprise-header">
|
||
<div className="enterprise-header__container">
|
||
<div className="enterprise-header__left">
|
||
<h1>
|
||
Queries
|
||
</h1>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="container-fluid">
|
||
<div className="row">
|
||
<div className="col-md-12">
|
||
<div className="panel panel-minimal">
|
||
<div className="panel-body">
|
||
<table className="table v-center">
|
||
<thead>
|
||
<tr>
|
||
<th>Database</th>
|
||
<th>Query</th>
|
||
<th>Running</th>
|
||
<th></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{queries.map((q) => {
|
||
return (
|
||
<tr key={q.id}>
|
||
<td>{q.database}</td>
|
||
<td><code>{q.query}</code></td>
|
||
<td>{q.duration}</td>
|
||
<td className="text-right">
|
||
<button className="btn btn-xs btn-link-danger" onClick={this.handleKillQuery} data-toggle="modal" data-query-id={q.id} data-target="#killModal">
|
||
Kill
|
||
</button>
|
||
</td>
|
||
</tr>
|
||
);
|
||
})}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="modal fade" id="killModal" tabIndex="-1" role="dialog" aria-labelledby="myModalLabel">
|
||
<div className="modal-dialog" role="document">
|
||
<div className="modal-content">
|
||
<div className="modal-header">
|
||
<button type="button" className="close" data-dismiss="modal" aria-label="Close">
|
||
<span aria-hidden="true">×</span>
|
||
</button>
|
||
<h4 className="modal-title" id="myModalLabel">Are you sure you want to kill this query?</h4>
|
||
</div>
|
||
<div className="modal-footer">
|
||
<button type="button" className="btn btn-default" data-dismiss="modal">No</button>
|
||
<button type="button" className="btn btn-danger" data-dismiss="modal" onClick={this.handleConfirmKillQuery}>Yes, kill it!</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
},
|
||
|
||
handleKillQuery(e) {
|
||
e.stopPropagation();
|
||
const id = e.target.dataset.queryId;
|
||
this.setState({
|
||
queryIDToKill: id,
|
||
});
|
||
},
|
||
|
||
handleConfirmKillQuery() {
|
||
const {queryIDToKill} = this.state;
|
||
if (queryIDToKill === null) {
|
||
return;
|
||
}
|
||
|
||
// optimitstic update
|
||
const {queries} = this.state;
|
||
this.setState({
|
||
queries: reject(queries, (q) => +q.id === +queryIDToKill),
|
||
});
|
||
|
||
// kill the query over http
|
||
const {dataNodes, params} = this.props;
|
||
killQuery(dataNodes, queryIDToKill, params.clusterID).then(() => {
|
||
this.setState({
|
||
queryIDToKill: null,
|
||
});
|
||
});
|
||
},
|
||
});
|
||
|
||
export default QueriesPage;
|