| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422 |
- // Libraries
- import React, { ComponentClass } from 'react';
- import { hot } from 'react-hot-loader';
- // @ts-ignore
- import { connect } from 'react-redux';
- import { AutoSizer } from 'react-virtualized';
- import memoizeOne from 'memoize-one';
- // Services & Utils
- import store from 'app/core/store';
- // Components
- import { Alert, DataQuery, ExploreStartPageProps, DataSourceApi, PanelData } from '@grafana/ui';
- import { ErrorBoundary } from './ErrorBoundary';
- import LogsContainer from './LogsContainer';
- import QueryRows from './QueryRows';
- import TableContainer from './TableContainer';
- // Actions
- import {
- changeSize,
- initializeExplore,
- modifyQueries,
- scanStart,
- setQueries,
- refreshExplore,
- reconnectDatasource,
- updateTimeRange,
- toggleGraph,
- } from './state/actions';
- // Types
- import { RawTimeRange, GraphSeriesXY, TimeZone, AbsoluteTimeRange } from '@grafana/data';
- import {
- ExploreItemState,
- ExploreUrlState,
- ExploreId,
- ExploreUpdateState,
- ExploreUIState,
- ExploreMode,
- } from 'app/types/explore';
- import { StoreState } from 'app/types';
- import {
- ensureQueries,
- DEFAULT_RANGE,
- DEFAULT_UI_STATE,
- getTimeRangeFromUrl,
- lastUsedDatasourceKeyForOrgId,
- } from 'app/core/utils/explore';
- import { Emitter } from 'app/core/utils/emitter';
- import { ExploreToolbar } from './ExploreToolbar';
- import { NoDataSourceCallToAction } from './NoDataSourceCallToAction';
- import { FadeIn } from 'app/core/components/Animations/FadeIn';
- import { getTimeZone } from '../profile/state/selectors';
- import { ErrorContainer } from './ErrorContainer';
- import { scanStopAction } from './state/actionTypes';
- import { ExploreGraphPanel } from './ExploreGraphPanel';
- interface ExploreProps {
- StartPage?: ComponentClass<ExploreStartPageProps>;
- changeSize: typeof changeSize;
- datasourceError: string;
- datasourceInstance: DataSourceApi;
- datasourceLoading: boolean | null;
- datasourceMissing: boolean;
- exploreId: ExploreId;
- initializeExplore: typeof initializeExplore;
- initialized: boolean;
- modifyQueries: typeof modifyQueries;
- update: ExploreUpdateState;
- reconnectDatasource: typeof reconnectDatasource;
- refreshExplore: typeof refreshExplore;
- scanning?: boolean;
- scanRange?: RawTimeRange;
- scanStart: typeof scanStart;
- scanStopAction: typeof scanStopAction;
- setQueries: typeof setQueries;
- split: boolean;
- showingStartPage?: boolean;
- queryKeys: string[];
- initialDatasource: string;
- initialQueries: DataQuery[];
- initialRange: RawTimeRange;
- mode: ExploreMode;
- initialUI: ExploreUIState;
- isLive: boolean;
- updateTimeRange: typeof updateTimeRange;
- graphResult?: GraphSeriesXY[];
- loading?: boolean;
- absoluteRange: AbsoluteTimeRange;
- showingGraph?: boolean;
- showingTable?: boolean;
- timeZone?: TimeZone;
- onHiddenSeriesChanged?: (hiddenSeries: string[]) => void;
- toggleGraph: typeof toggleGraph;
- queryResponse: PanelData;
- }
- /**
- * 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<ExploreProps> {
- el: any;
- exploreEvents: Emitter;
- constructor(props: ExploreProps) {
- super(props);
- this.exploreEvents = new Emitter();
- }
- componentDidMount() {
- const { initialized, exploreId, initialDatasource, initialQueries, initialRange, mode, initialUI } = this.props;
- const width = this.el ? this.el.offsetWidth : 0;
- // initialize the whole explore first time we mount and if browser history contains a change in datasource
- if (!initialized) {
- this.props.initializeExplore(
- exploreId,
- initialDatasource,
- initialQueries,
- initialRange,
- mode,
- width,
- this.exploreEvents,
- initialUI
- );
- }
- }
- componentWillUnmount() {
- this.exploreEvents.removeAllListeners();
- }
- componentDidUpdate(prevProps: ExploreProps) {
- this.refreshExplore();
- }
- getRef = (el: any) => {
- this.el = el;
- };
- onChangeTime = (rawRange: RawTimeRange) => {
- const { updateTimeRange, exploreId } = this.props;
- updateTimeRange({ exploreId, rawRange });
- };
- // 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: any, 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
- this.props.scanStart(this.props.exploreId);
- };
- onStopScanning = () => {
- this.props.scanStopAction({ exploreId: this.props.exploreId });
- };
- onToggleGraph = (showingGraph: boolean) => {
- const { toggleGraph, exploreId } = this.props;
- toggleGraph(exploreId, showingGraph);
- };
- onUpdateTimeRange = (absoluteRange: AbsoluteTimeRange) => {
- const { updateTimeRange, exploreId } = this.props;
- updateTimeRange({ exploreId, absoluteRange });
- };
- refreshExplore = () => {
- const { exploreId, update } = this.props;
- if (update.queries || update.ui || update.range || update.datasource || update.mode) {
- this.props.refreshExplore(exploreId);
- }
- };
- renderEmptyState = () => {
- return (
- <div className="explore-container">
- <NoDataSourceCallToAction />
- </div>
- );
- };
- onReconnect = (event: React.MouseEvent<HTMLButtonElement>) => {
- const { exploreId, reconnectDatasource } = this.props;
- event.preventDefault();
- reconnectDatasource(exploreId);
- };
- render() {
- const {
- StartPage,
- datasourceInstance,
- datasourceError,
- datasourceLoading,
- datasourceMissing,
- exploreId,
- showingStartPage,
- split,
- queryKeys,
- mode,
- graphResult,
- loading,
- absoluteRange,
- showingGraph,
- showingTable,
- timeZone,
- queryResponse,
- } = this.props;
- const exploreClass = split ? 'explore explore-split' : 'explore';
- return (
- <div className={exploreClass} ref={this.getRef}>
- <ExploreToolbar exploreId={exploreId} onChangeTime={this.onChangeTime} />
- {datasourceLoading ? <div className="explore-container">Loading datasource...</div> : null}
- {datasourceMissing ? this.renderEmptyState() : null}
- <FadeIn duration={datasourceError ? 150 : 5} in={datasourceError ? true : false}>
- <div className="explore-container">
- <Alert
- title={`Error connecting to datasource: ${datasourceError}`}
- button={{ text: 'Reconnect', onClick: this.onReconnect }}
- />
- </div>
- </FadeIn>
- {datasourceInstance && (
- <div className="explore-container">
- <QueryRows exploreEvents={this.exploreEvents} exploreId={exploreId} queryKeys={queryKeys} />
- <ErrorContainer queryErrors={[queryResponse.error]} />
- <AutoSizer onResize={this.onResize} disableHeight>
- {({ width }) => {
- if (width === 0) {
- return null;
- }
- return (
- <main className="m-t-2" style={{ width }}>
- <ErrorBoundary>
- {showingStartPage && <StartPage onClickExample={this.onClickExample} />}
- {!showingStartPage && (
- <>
- {mode === ExploreMode.Metrics && (
- <ExploreGraphPanel
- series={graphResult}
- width={width}
- loading={loading}
- absoluteRange={absoluteRange}
- isStacked={false}
- showPanel={true}
- showingGraph={showingGraph}
- showingTable={showingTable}
- timeZone={timeZone}
- onToggleGraph={this.onToggleGraph}
- onUpdateTimeRange={this.onUpdateTimeRange}
- showBars={false}
- showLines={true}
- />
- )}
- {mode === ExploreMode.Metrics && (
- <TableContainer exploreId={exploreId} onClickCell={this.onClickLabel} />
- )}
- {mode === ExploreMode.Logs && (
- <LogsContainer
- width={width}
- exploreId={exploreId}
- onClickLabel={this.onClickLabel}
- onStartScanning={this.onStartScanning}
- onStopScanning={this.onStopScanning}
- />
- )}
- </>
- )}
- </ErrorBoundary>
- </main>
- );
- }}
- </AutoSizer>
- </div>
- )}
- </div>
- );
- }
- }
- const ensureQueriesMemoized = memoizeOne(ensureQueries);
- const getTimeRangeFromUrlMemoized = memoizeOne(getTimeRangeFromUrl);
- function mapStateToProps(state: StoreState, { exploreId }: ExploreProps) {
- const explore = state.explore;
- const { split } = explore;
- const item: ExploreItemState = explore[exploreId];
- const timeZone = getTimeZone(state.user);
- const {
- StartPage,
- datasourceError,
- datasourceInstance,
- datasourceLoading,
- datasourceMissing,
- initialized,
- showingStartPage,
- queryKeys,
- urlState,
- update,
- isLive,
- supportedModes,
- mode,
- graphResult,
- loading,
- showingGraph,
- showingTable,
- absoluteRange,
- queryResponse,
- } = item;
- const { datasource, queries, range: urlRange, mode: urlMode, ui } = (urlState || {}) as ExploreUrlState;
- const initialDatasource = datasource || store.get(lastUsedDatasourceKeyForOrgId(state.user.orgId));
- const initialQueries: DataQuery[] = ensureQueriesMemoized(queries);
- const initialRange = urlRange ? getTimeRangeFromUrlMemoized(urlRange, timeZone).raw : DEFAULT_RANGE;
- let newMode: ExploreMode;
- if (supportedModes.length) {
- const urlModeIsValid = supportedModes.includes(urlMode);
- const modeStateIsValid = supportedModes.includes(mode);
- if (modeStateIsValid) {
- newMode = mode;
- } else if (urlModeIsValid) {
- newMode = urlMode;
- } else {
- newMode = supportedModes[0];
- }
- } else {
- newMode = [ExploreMode.Metrics, ExploreMode.Logs].includes(mode) ? mode : ExploreMode.Metrics;
- }
- const initialUI = ui || DEFAULT_UI_STATE;
- return {
- StartPage,
- datasourceError,
- datasourceInstance,
- datasourceLoading,
- datasourceMissing,
- initialized,
- showingStartPage,
- split,
- queryKeys,
- update,
- initialDatasource,
- initialQueries,
- initialRange,
- mode: newMode,
- initialUI,
- isLive,
- graphResult,
- loading,
- showingGraph,
- showingTable,
- absoluteRange,
- queryResponse,
- };
- }
- const mapDispatchToProps = {
- changeSize,
- initializeExplore,
- modifyQueries,
- reconnectDatasource,
- refreshExplore,
- scanStart,
- scanStopAction,
- setQueries,
- updateTimeRange,
- toggleGraph,
- };
- export default hot(module)(
- connect(
- mapStateToProps,
- mapDispatchToProps
- )(Explore)
- ) as React.ComponentType<{ exploreId: ExploreId }>;
|