// Libraries import React from 'react'; import { hot } from 'react-hot-loader'; import { connect } from 'react-redux'; import _ from 'lodash'; import { AutoSizer } from 'react-virtualized'; // Services & Utils import store from 'app/core/store'; // Components import { Alert } from './Error'; import ErrorBoundary from './ErrorBoundary'; import GraphContainer from './GraphContainer'; import LogsContainer from './LogsContainer'; import QueryRows from './QueryRows'; import TableContainer from './TableContainer'; import TimePicker, { parseTime } from './TimePicker'; // Actions import { changeSize, changeTime, initializeExplore, modifyQueries, scanStart, setQueries } from './state/actions'; // Types import { RawTimeRange, TimeRange, DataQuery } from '@grafana/ui'; import { ExploreItemState, ExploreUrlState, RangeScanner, ExploreId } from 'app/types/explore'; import { StoreState } from 'app/types'; import { LAST_USED_DATASOURCE_KEY, ensureQueries, DEFAULT_RANGE } from 'app/core/utils/explore'; import { Emitter } from 'app/core/utils/emitter'; import { ExploreToolbar } from './ExploreToolbar'; import { scanStopAction } from './state/actionTypes'; interface ExploreProps { StartPage?: any; changeSize: typeof changeSize; changeTime: typeof changeTime; datasourceError: string; datasourceInstance: any; datasourceLoading: boolean | null; datasourceMissing: boolean; exploreId: ExploreId; initialQueries: DataQuery[]; initializeExplore: typeof initializeExplore; initialized: boolean; modifyQueries: typeof modifyQueries; range: RawTimeRange; scanner?: RangeScanner; scanning?: boolean; scanRange?: RawTimeRange; scanStart: typeof scanStart; scanStopAction: typeof scanStopAction; setQueries: typeof setQueries; split: boolean; showingStartPage?: boolean; supportsGraph: boolean | null; supportsLogs: boolean | null; supportsTable: boolean | null; urlState: ExploreUrlState; } /** * Explore provides an area for quick query iteration for a given datasource. * Once a datasource is selected it populates the query section at the top. * When queries are run, their results are being displayed in the main section. * The datasource determines what kind of query editor it brings, and what kind * of results viewers it supports. The state is managed entirely in Redux. * * SPLIT VIEW * * Explore can have two Explore areas side-by-side. This is handled in `Wrapper.tsx`. * Since there can be multiple Explores (e.g., left and right) each action needs * the `exploreId` as first parameter so that the reducer knows which Explore state * is affected. * * DATASOURCE REQUESTS * * A click on Run Query creates transactions for all DataQueries for all expanded * result viewers. New runs are discarding previous runs. Upon completion a transaction * saves the result. The result viewers construct their data from the currently existing * transactions. * * The result viewers determine some of the query options sent to the datasource, e.g., * `format`, to indicate eventual transformations by the datasources' result transformers. */ export class Explore extends React.PureComponent { el: any; exploreEvents: Emitter; /** * Timepicker to control scanning */ timepickerRef: React.RefObject; constructor(props) { super(props); this.exploreEvents = new Emitter(); this.timepickerRef = React.createRef(); } async componentDidMount() { const { exploreId, initialized, urlState } = this.props; // Don't initialize on split, but need to initialize urlparameters when present if (!initialized) { // Load URL state and parse range const { datasource, queries, range = DEFAULT_RANGE } = (urlState || {}) as ExploreUrlState; const initialDatasource = datasource || store.get(LAST_USED_DATASOURCE_KEY); const initialQueries: DataQuery[] = ensureQueries(queries); const initialRange = { from: parseTime(range.from), to: parseTime(range.to) }; const width = this.el ? this.el.offsetWidth : 0; this.props.initializeExplore( exploreId, initialDatasource, initialQueries, initialRange, width, this.exploreEvents ); } } componentWillUnmount() { this.exploreEvents.removeAllListeners(); } getRef = el => { this.el = el; }; onChangeTime = (range: TimeRange, changedByScanner?: boolean) => { if (this.props.scanning && !changedByScanner) { this.onStopScanning(); } this.props.changeTime(this.props.exploreId, range); }; // Use this in help pages to set page to a single query onClickExample = (query: DataQuery) => { this.props.setQueries(this.props.exploreId, [query]); }; onClickLabel = (key: string, value: string) => { this.onModifyQueries({ type: 'ADD_FILTER', key, value }); }; onModifyQueries = (action, index?: number) => { const { datasourceInstance } = this.props; if (datasourceInstance && datasourceInstance.modifyQuery) { const modifier = (queries: DataQuery, modification: any) => datasourceInstance.modifyQuery(queries, modification); this.props.modifyQueries(this.props.exploreId, action, index, modifier); } }; onResize = (size: { height: number; width: number }) => { this.props.changeSize(this.props.exploreId, size); }; onStartScanning = () => { // Scanner will trigger a query const scanner = this.scanPreviousRange; this.props.scanStart(this.props.exploreId, scanner); }; scanPreviousRange = (): RawTimeRange => { // Calling move() on the timepicker will trigger this.onChangeTime() return this.timepickerRef.current.move(-1, true); }; onStopScanning = () => { this.props.scanStopAction({ exploreId: this.props.exploreId }); }; render() { const { StartPage, datasourceInstance, datasourceError, datasourceLoading, datasourceMissing, exploreId, initialQueries, showingStartPage, split, supportsGraph, supportsLogs, supportsTable, } = this.props; const exploreClass = split ? 'explore explore-split' : 'explore'; return (
{datasourceLoading ?
Loading datasource...
: null} {datasourceMissing ? (
Please add a datasource that supports Explore (e.g., Prometheus).
) : null} {datasourceError && (
)} {datasourceInstance && !datasourceError && (
{({ width }) => (
{showingStartPage && } {!showingStartPage && ( <> {supportsGraph && !supportsLogs && } {supportsTable && } {supportsLogs && ( )} )}
)}
)}
); } } function mapStateToProps(state: StoreState, { exploreId }) { const explore = state.explore; const { split } = explore; const item: ExploreItemState = explore[exploreId]; const { StartPage, datasourceError, datasourceInstance, datasourceLoading, datasourceMissing, initialQueries, initialized, range, showingStartPage, supportsGraph, supportsLogs, supportsTable, } = item; return { StartPage, datasourceError, datasourceInstance, datasourceLoading, datasourceMissing, initialQueries, initialized, range, showingStartPage, split, supportsGraph, supportsLogs, supportsTable, }; } const mapDispatchToProps = { changeSize, changeTime, initializeExplore, modifyQueries, scanStart, scanStopAction, setQueries, }; export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(Explore));