diff --git a/ui/src/admin/actions/index.js b/ui/src/admin/actions/index.js index 122b2b07d..5b0db3331 100644 --- a/ui/src/admin/actions/index.js +++ b/ui/src/admin/actions/index.js @@ -1,4 +1,5 @@ import {getUsers, getRoles} from 'src/admin/apis' +import {killQuery as killQueryProxy} from 'shared/apis/metaQuery' export const loadUsers = ({users}) => ({ type: 'LOAD_USERS', @@ -7,6 +8,28 @@ export const loadUsers = ({users}) => ({ }, }) +export const killQuery = (queryID) => ({ + type: 'KILL_QUERY', + payload: { + queryID, + }, +}) + +export const setQueryToKill = (queryIDToKill) => ({ + type: 'SET_QUERY_TO_KILL', + payload: { + queryIDToKill, + }, +}) + +export const loadQueries = (queries) => ({ + type: 'LOAD_QUERIES', + payload: { + queries, + }, +}) + +// async actions export const loadUsersAsync = (url) => async (dispatch) => { const {data} = await getUsers(url) dispatch(loadUsers(data)) @@ -22,3 +45,12 @@ export const loadRolesAsync = (url) => async (dispatch) => { const {data} = await getRoles(url) dispatch(loadRoles(data)) } + +export const killQueryAsync = (source, queryID) => (dispatch) => { + // optimistic update + dispatch(killQuery(queryID)) + dispatch(setQueryToKill(null)) + + // kill query on server + killQueryProxy(source, queryID) +} diff --git a/ui/src/admin/containers/QueriesPage.js b/ui/src/admin/containers/QueriesPage.js index c3b0a5e6b..348e9125d 100644 --- a/ui/src/admin/containers/QueriesPage.js +++ b/ui/src/admin/containers/QueriesPage.js @@ -1,51 +1,64 @@ import React, {PropTypes, Component} from 'react' +import {connect} from 'react-redux'; +import {bindActionCreators} from 'redux'; + import flatten from 'lodash/flatten' -import reject from 'lodash/reject' import uniqBy from 'lodash/uniqBy' + import { showDatabases, showQueries, - killQuery, } from 'shared/apis/metaQuery' import QueriesTable from 'src/admin/components/QueriesTable' import showDatabasesParser from 'shared/parsing/showDatabases' import showQueriesParser from 'shared/parsing/showQueries' import {TIMES} from 'src/admin/constants' +import { + loadQueries as loadQueriesAction, + setQueryToKill as setQueryToKillAction, + killQueryAsync, +} from 'src/admin/actions' -export default class QueriesPage extends Component { +class QueriesPage extends Component { constructor(props) { super(props) - this.state = { - queries: [], - queryIDToKill: null, - } - - this.updateQueries = this.updateQueries.bind(this); - this.handleConfirmKillQuery = this.handleConfirmKillQuery.bind(this); - this.handleKillQuery = this.handleKillQuery.bind(this); + this.updateQueries = this.updateQueries.bind(this) + this.handleConfirmKillQuery = this.handleConfirmKillQuery.bind(this) + this.handleKillQuery = this.handleKillQuery.bind(this) } componentDidMount() { - this.updateQueries(); - const updateInterval = 5000; - this.intervalID = setInterval(this.updateQueries, updateInterval); + this.updateQueries() + const updateInterval = 5000 + this.intervalID = setInterval(this.updateQueries, updateInterval) } componentWillUnmount() { - clearInterval(this.intervalID); + clearInterval(this.intervalID) + } + + render() { + const {queries} = this.props; + + return ( +
+ + +
+ ); } updateQueries() { - const {source, addFlashMessage} = this.props; + const {source, addFlashMessage, loadQueries} = this.props showDatabases(source.links.proxy).then((resp) => { - const {databases, errors} = showDatabasesParser(resp.data); + const {databases, errors} = showDatabasesParser(resp.data) if (errors.length) { - errors.forEach((message) => addFlashMessage({type: 'error', text: message})); + errors.forEach((message) => addFlashMessage({type: 'error', text: message})) return; } - const fetches = databases.map((db) => showQueries(source.links.proxy, db)); + const fetches = databases.map((db) => showQueries(source.links.proxy, db)) Promise.all(fetches).then((queryResponses) => { const allQueries = []; @@ -66,50 +79,26 @@ export default class QueriesPage extends Component { const bTime = TIMES.find((t) => b.duration.match(t.test)); return +aTime.magnitude <= +bTime.magnitude; }); - this.setState({ - queries: sortedQueries, - }); + + loadQueries(sortedQueries) }); }); } - render() { - const {queries} = this.state; - return ( -
- - -
- ); - } - handleKillQuery(e) { e.stopPropagation(); const id = e.target.dataset.queryId; - this.setState({ - queryIDToKill: id, - }); + + this.props.setQueryToKill(id) } handleConfirmKillQuery() { - const {queryIDToKill} = this.state; + const {queryIDToKill, source, killQuery} = this.props; 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 {source} = this.props; - killQuery(source.links.proxy, queryIDToKill).then(() => { - this.setState({ - queryIDToKill: null, - }); - }); + killQuery(source.links.proxy, queryIDToKill) } } @@ -126,6 +115,7 @@ const QueriesHeader = () => ( ) const { + arrayOf, func, string, shape, @@ -137,5 +127,23 @@ QueriesPage.propTypes = { proxy: string, }), }), + queries: arrayOf(shape()), addFlashMessage: func, + loadQueries: func, + queryIDToKill: string, + setQueryToKill: func, + killQuery: func, } + +const mapStateToProps = ({admin: {queries, queryIDToKill}}) => ({ + queries, + queryIDToKill, +}) + +const mapDispatchToProps = (dispatch) => ({ + loadQueries: bindActionCreators(loadQueriesAction, dispatch), + setQueryToKill: bindActionCreators(setQueryToKillAction, dispatch), + killQuery: bindActionCreators(killQueryAsync, dispatch), +}) + +export default connect(mapStateToProps, mapDispatchToProps)(QueriesPage) diff --git a/ui/src/admin/reducers/admin.js b/ui/src/admin/reducers/admin.js index 3e8c8d49e..c23ecb6c0 100644 --- a/ui/src/admin/reducers/admin.js +++ b/ui/src/admin/reducers/admin.js @@ -1,6 +1,10 @@ +import reject from 'lodash/reject' + const initialState = { users: [], roles: [], + queries: [], + queryIDToKill: null, } export default function admin(state = initialState, action) { @@ -12,6 +16,23 @@ export default function admin(state = initialState, action) { case 'LOAD_ROLES': { return {...state, ...action.payload} } + + case 'LOAD_QUERIES': { + return {...state, ...action.payload} + } + + case 'KILL_QUERY': { + const {queryID} = action.payload + const nextState = { + queries: reject(state.queries, (q) => +q.id === +queryID), + } + + return {...state, ...nextState} + } + + case 'SET_QUERY_TO_KILL': { + return {...state, ...action.payload} + } } return state diff --git a/ui/src/utils/queryUrlGenerator.js b/ui/src/utils/queryUrlGenerator.js index c6153ea87..eecf77e05 100644 --- a/ui/src/utils/queryUrlGenerator.js +++ b/ui/src/utils/queryUrlGenerator.js @@ -1,13 +1,17 @@ import AJAX from 'utils/ajax'; -export function proxy({source, query, db, rp}) { - return AJAX({ - method: 'POST', - url: source, - data: { - query, - db, - rp, - }, - }); +export const proxy = async ({source, query, db, rp}) => { + try { + return await AJAX({ + method: 'POST', + url: source, + data: { + query, + db, + rp, + }, + }) + } catch (error) { + console.error(error) // eslint-disable-line no-console + } }