// Libraries import React, { PureComponent } from 'react'; import { debounce, has } from 'lodash'; import { hot } from 'react-hot-loader'; // @ts-ignore import { connect } from 'react-redux'; // Components import AngularQueryEditor from './QueryEditor'; import { QueryRowActions } from './QueryRowActions'; // Types import { StoreState } from 'app/types'; import { DataQuery, DataSourceApi, PanelData, HistoryItem, TimeRange, AbsoluteTimeRange, LoadingState, EventBusExtended, } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { ExploreItemState, ExploreId } from 'app/types/explore'; import { highlightLogsExpressionAction } from './state/explorePane'; import { ErrorContainer } from './ErrorContainer'; import { changeQuery, modifyQueries, removeQueryRowAction, runQueries } from './state/query'; import { HelpToggle } from '../query/components/HelpToggle'; interface PropsFromParent { exploreId: ExploreId; index: number; } export interface QueryRowProps extends PropsFromParent { changeQuery: typeof changeQuery; className?: string; exploreId: ExploreId; datasourceInstance: DataSourceApi; highlightLogsExpressionAction: typeof highlightLogsExpressionAction; history: HistoryItem[]; query: DataQuery; modifyQueries: typeof modifyQueries; range: TimeRange; absoluteRange: AbsoluteTimeRange; removeQueryRowAction: typeof removeQueryRowAction; runQueries: typeof runQueries; queryResponse: PanelData; latency: number; exploreEvents: EventBusExtended; } interface QueryRowState { textEditModeEnabled: boolean; } // Empty function to override blur execution on query field const noopOnBlur = () => {}; export class QueryRow extends PureComponent { state: QueryRowState = { textEditModeEnabled: false, }; onRunQuery = () => { const { exploreId } = this.props; this.props.runQueries(exploreId); }; onChange = (query: DataQuery, override?: boolean) => { const { datasourceInstance, exploreId, index } = this.props; this.props.changeQuery(exploreId, query, index, override); if (query && !override && datasourceInstance.getHighlighterExpression && index === 0) { // Live preview of log search matches. Only use on first row for now this.updateLogsHighlights(query); } }; onClickToggleDisabled = () => { const { exploreId, index, query } = this.props; const newQuery = { ...query, hide: !query.hide, }; this.props.changeQuery(exploreId, newQuery, index, true); }; onClickRemoveButton = () => { const { exploreId, index } = this.props; this.props.removeQueryRowAction({ exploreId, index }); this.props.runQueries(exploreId); }; onClickToggleEditorMode = () => { this.setState({ textEditModeEnabled: !this.state.textEditModeEnabled }); }; setReactQueryEditor = () => { const { datasourceInstance } = this.props; let QueryEditor; // TODO:unification if (datasourceInstance.components?.ExploreMetricsQueryField) { QueryEditor = datasourceInstance.components.ExploreMetricsQueryField; } else if (datasourceInstance.components?.ExploreLogsQueryField) { QueryEditor = datasourceInstance.components.ExploreLogsQueryField; } else if (datasourceInstance.components?.ExploreQueryField) { QueryEditor = datasourceInstance.components.ExploreQueryField; } else { QueryEditor = datasourceInstance.components?.QueryEditor; } return QueryEditor; }; renderQueryEditor = () => { const { datasourceInstance, history, query, exploreEvents, range, queryResponse, exploreId } = this.props; const queryErrors = queryResponse.error && queryResponse.error.refId === query.refId ? [queryResponse.error] : []; const ReactQueryEditor = this.setReactQueryEditor(); let QueryEditor: JSX.Element; if (ReactQueryEditor) { QueryEditor = ( ); } else { QueryEditor = ( ); } const DatasourceCheatsheet = datasourceInstance.components?.QueryEditorHelp; return ( <> {QueryEditor} {DatasourceCheatsheet && ( this.onChange(query)} datasource={datasourceInstance} /> )} ); }; updateLogsHighlights = debounce((value: DataQuery) => { const { datasourceInstance } = this.props; if (datasourceInstance.getHighlighterExpression) { const { exploreId } = this.props; const expressions = datasourceInstance.getHighlighterExpression(value); this.props.highlightLogsExpressionAction({ exploreId, expressions }); } }, 500); render() { const { datasourceInstance, query, queryResponse, latency } = this.props; const canToggleEditorModes = has(datasourceInstance, 'components.QueryCtrl.prototype.toggleEditorMode'); const isNotStarted = queryResponse.state === LoadingState.NotStarted; // We show error without refId in ResponseErrorContainer so this condition needs to match se we don't loose errors. const queryErrors = queryResponse.error && queryResponse.error.refId === query.refId ? [queryResponse.error] : []; return ( <>
{this.renderQueryEditor()}
{queryErrors.length > 0 && } ); } } function mapStateToProps(state: StoreState, { exploreId, index }: QueryRowProps) { const explore = state.explore; const item: ExploreItemState = explore[exploreId]!; const { datasourceInstance, history, queries, range, absoluteRange, queryResponse, latency, eventBridge } = item; const query = queries[index]; return { datasourceInstance, history, query, range, absoluteRange, queryResponse, latency, exploreEvents: eventBridge, }; } const mapDispatchToProps = { changeQuery, highlightLogsExpressionAction, modifyQueries, removeQueryRowAction, runQueries, }; export default hot(module)( connect(mapStateToProps, mapDispatchToProps)(QueryRow) as React.ComponentType );