diff --git a/docs/sources/developers/plugins/_index.md b/docs/sources/developers/plugins/_index.md index 1ad8e53ee96..cfe7a0d3181 100644 --- a/docs/sources/developers/plugins/_index.md +++ b/docs/sources/developers/plugins/_index.md @@ -46,6 +46,7 @@ Improve an existing plugin with one of our guides: - [Add support for annotations]({{< relref "add-support-for-annotations.md" >}}) - [Add support for Explore queries]({{< relref "add-support-for-explore-queries.md" >}}) - [Add support for variables]({{< relref "add-support-for-variables.md" >}}) +- [Add a query editor help component]({{< relref "add-query-editor-help.md" >}}) - [Build a logs data source plugin]({{< relref "build-a-logs-data-source-plugin.md" >}}) - [Build a streaming data source plugin]({{< relref "build-a-streaming-data-source-plugin.md" >}}) - [Error handling]({{< relref "error-handling.md" >}}) diff --git a/docs/sources/developers/plugins/add-query-editor-help.md b/docs/sources/developers/plugins/add-query-editor-help.md new file mode 100644 index 00000000000..61830c9c3d1 --- /dev/null +++ b/docs/sources/developers/plugins/add-query-editor-help.md @@ -0,0 +1,72 @@ +## Add a query editor help component + +By adding a help component to your plugin, you can for example create "cheat sheets" with commonly used queries. When the user clicks on one of the examples, it automatically updates the query editor. It's a great way to increase productivity for your users. + +1. Create a file `QueryEditorHelp.tsx` in the `src` directory of your plugin, with the following content: + + ```ts + import React from 'react'; + import { QueryEditorHelpProps } from '@grafana/data'; + + export default (props: QueryEditorHelpProps) => { + return ( +

My cheat sheet

+ ); + }; + ``` + +1. Configure the plugin to use the `QueryEditorHelp`. + + ```ts + import QueryEditorHelp from './QueryEditorHelp'; + ``` + + ```ts + export const plugin = new DataSourcePlugin(DataSource) + .setConfigEditor(ConfigEditor) + .setQueryEditor(QueryEditor) + .setExploreQueryField(ExploreQueryEditor) + .setQueryEditorHelp(QueryEditorHelp); + ``` + +1. Create a few examples. + + ```ts + import React from 'react'; + import { QueryEditorHelpProps, DataQuery } from '@grafana/data'; + + const examples = [ + { + title: 'Addition', + expression: '1 + 2', + label: 'Add two integers', + }, + { + title: 'Subtraction', + expression: '2 - 1', + label: 'Subtract an integer from another', + }, + ]; + + export default (props: QueryEditorHelpProps) => { + return ( +
+

Cheat Sheet

+ {examples.map((item, index) => ( +
+
{item.title}
+ {item.expression ? ( +
props.onClickExample({ refId: 'A', queryText: item.expression } as DataQuery)} + > + {item.expression} +
+ ) : null} +
{item.label}
+
+ ))} +
+ ); + }; + ``` diff --git a/docs/sources/developers/plugins/add-support-for-explore-queries.md b/docs/sources/developers/plugins/add-support-for-explore-queries.md index d327ef8e6ac..f5c8ab7016e 100644 --- a/docs/sources/developers/plugins/add-support-for-explore-queries.md +++ b/docs/sources/developers/plugins/add-support-for-explore-queries.md @@ -10,7 +10,7 @@ This guide assumes that you're already familiar with how to [Build a data source With Explore, users can make ad-hoc queries without the use of a dashboard. This is useful when users want to troubleshoot or to learn more about the data. -Your data source already supports Explore by default, and will use the existing query editor for the data source. If you want to offer extended Explore functionality for your data source however, you can define a Explore-specific query editor. Optionally, your plugin can also define a _start page_ for Explore. +Your data source already supports Explore by default, and will use the existing query editor for the data source. If you want to offer extended Explore functionality for your data source however, you can define a Explore-specific query editor. ## Add a query editor for Explore @@ -85,79 +85,6 @@ The query editor for Explore is similar to the query editor for the data source }; ``` -## Add a start page for Explore - -By adding an Explore start page for your plugin, you can for example create "cheat sheets" with commonly used queries. When the user clicks on one of the examples, it automatically updates the query editor, and runs the query. It's a great way to increase productivity for your users. - -1. Create a file `ExploreStartPage.tsx` in the `src` directory of your plugin, with the following content: - - ```ts - import React from 'react'; - import { ExploreStartPageProps } from '@grafana/data'; - - export default (props: ExploreStartPageProps) => { - return ( -

My start page

- ); - }; - ``` - -1. Configure the plugin to use the `ExploreStartPage`. - - ```ts - import ExploreStartPage from './ExploreStartPage'; - ``` - - ```ts - export const plugin = new DataSourcePlugin(DataSource) - .setConfigEditor(ConfigEditor) - .setQueryEditor(QueryEditor) - .setExploreQueryField(ExploreQueryEditor) - .setExploreStartPage(ExploreStartPage); - ``` - -1. Create a few examples. - - ```ts - import React from 'react'; - import { ExploreStartPageProps, DataQuery } from '@grafana/data'; - - const examples = [ - { - title: 'Addition', - expression: '1 + 2', - label: 'Add two integers', - }, - { - title: 'Subtraction', - expression: '2 - 1', - label: 'Subtract an integer from another', - }, - ]; - - export default (props: ExploreStartPageProps) => { - return ( -
-

Cheat Sheet

- {examples.map((item, index) => ( -
-
{item.title}
- {item.expression ? ( -
props.onClickExample({ refId: 'A', queryText: item.expression } as DataQuery)} - > - {item.expression} -
- ) : null} -
{item.label}
-
- ))} -
- ); - }; - ``` - ## Support multiple Explore modes Explore lets you query any data source, regardless of whether it returns metrics or logs. You can change which type of query you want to make, by setting the _Explore mode_. diff --git a/docs/sources/panels/queries.md b/docs/sources/panels/queries.md index 2a65eba1439..46696dc2a7b 100644 --- a/docs/sources/panels/queries.md +++ b/docs/sources/panels/queries.md @@ -118,6 +118,8 @@ You can: | Icon | Description | |:--:|:---| +| {{< docs-imagebox img="/img/docs/queries/query-editor-help-7-4.png" class="docs-image--no-shadow" max-width="30px" max-height="30px" >}} | Toggle query editor help. If supported by the data source, this will toggle displaying information on how to use its query editor, or provide quick +access to commonly-used queries. | | {{< docs-imagebox img="/img/docs/queries/duplicate-query-icon-7-0.png" class="docs-image--no-shadow" max-width="30px" max-height="30px" >}} | Copy a query. Duplicating queries is useful when working with multiple complex queries that are similar and you want to either experiment with different variants or do minor alterations. | | {{< docs-imagebox img="/img/docs/queries/hide-query-icon-7-0.png" class="docs-image--no-shadow" max-width="30px" max-height="30px" >}} | Hide a query. Grafana does not send hidden queries to the data source. | | {{< docs-imagebox img="/img/docs/queries/remove-query-icon-7-0.png" class="docs-image--no-shadow" max-width="30px" max-height="30px" >}} | Remove a query. Removing a query permanently deletes it, but sometimes you can recover deleted queries by reverting to previously saved versions of the panel. | diff --git a/packages/grafana-data/src/types/datasource.ts b/packages/grafana-data/src/types/datasource.ts index 661048d7068..fa2f2ef2a68 100644 --- a/packages/grafana-data/src/types/datasource.ts +++ b/packages/grafana-data/src/types/datasource.ts @@ -75,11 +75,18 @@ export class DataSourcePlugin< return this; } - setExploreStartPage(ExploreStartPage: ComponentType) { - this.components.ExploreStartPage = ExploreStartPage; + setQueryEditorHelp(QueryEditorHelp: ComponentType) { + this.components.QueryEditorHelp = QueryEditorHelp; return this; } + /** + * @deprecated prefer using `setQueryEditorHelp` + */ + setExploreStartPage(ExploreStartPage: ComponentType) { + return this.setQueryEditorHelp(ExploreStartPage); + } + /* * @deprecated -- prefer using {@link StandardVariableSupport} or {@link CustomVariableSupport} or {@link DataSourceVariableSupport} in data source instead * */ @@ -99,8 +106,8 @@ export class DataSourcePlugin< this.components.QueryCtrl = pluginExports.QueryCtrl; this.components.AnnotationsQueryCtrl = pluginExports.AnnotationsQueryCtrl; this.components.ExploreQueryField = pluginExports.ExploreQueryField; - this.components.ExploreStartPage = pluginExports.ExploreStartPage; this.components.QueryEditor = pluginExports.QueryEditor; + this.components.QueryEditorHelp = pluginExports.QueryEditorHelp; this.components.VariableQueryEditor = pluginExports.VariableQueryEditor; } } @@ -140,7 +147,7 @@ export interface DataSourcePluginComponents< ExploreQueryField?: ComponentType>; ExploreMetricsQueryField?: ComponentType>; ExploreLogsQueryField?: ComponentType>; - ExploreStartPage?: ComponentType; + QueryEditorHelp?: ComponentType; ConfigEditor?: ComponentType>; MetadataInspector?: ComponentType>; } @@ -365,7 +372,7 @@ export interface ExploreQueryFieldProps< exploreId?: any; } -export interface ExploreStartPageProps { +export interface QueryEditorHelpProps { datasource: DataSourceApi; onClickExample: (query: DataQuery) => void; exploreId?: any; diff --git a/public/app/features/explore/Explore.test.tsx b/public/app/features/explore/Explore.test.tsx index f3fc5ebe352..dfbe17207f0 100644 --- a/public/app/features/explore/Explore.test.tsx +++ b/public/app/features/explore/Explore.test.tsx @@ -16,7 +16,7 @@ const dummyProps: ExploreProps = { logs: true, }, components: { - ExploreStartPage: {}, + QueryEditorHelp: {}, }, } as DataSourceApi, datasourceMissing: false, diff --git a/public/app/features/explore/Explore.tsx b/public/app/features/explore/Explore.tsx index e81f64567ec..4ab2b051b80 100644 --- a/public/app/features/explore/Explore.tsx +++ b/public/app/features/explore/Explore.tsx @@ -382,8 +382,7 @@ export class Explore extends React.PureComponent { const { openDrawer } = this.state; const exploreClass = split ? 'explore explore-split' : 'explore'; const styles = getStyles(theme); - const StartPage = datasourceInstance?.components?.ExploreStartPage; - const showStartPage = !queryResponse || queryResponse.state === LoadingState.NotStarted; + const showPanels = queryResponse && queryResponse.state !== LoadingState.NotStarted; // gets an error without a refID, so non-query-row-related error, like a connection error const queryErrors = @@ -423,16 +422,7 @@ export class Explore extends React.PureComponent { return (
- {showStartPage && StartPage && ( -
- -
- )} - {!showStartPage && ( + {showPanels && ( <> {showMetrics && graphResult && this.renderGraphPanel(width)} {showTable && this.renderTablePanel(width)} diff --git a/public/app/features/explore/QueryRow.tsx b/public/app/features/explore/QueryRow.tsx index e5aefff47c7..057ea21868e 100644 --- a/public/app/features/explore/QueryRow.tsx +++ b/public/app/features/explore/QueryRow.tsx @@ -26,6 +26,7 @@ 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; @@ -119,8 +120,9 @@ export class QueryRow extends PureComponent { const ReactQueryEditor = this.setReactQueryEditor(); + let QueryEditor: JSX.Element; if (ReactQueryEditor) { - return ( + QueryEditor = ( { exploreId={exploreId} /> ); + } else { + QueryEditor = ( + + ); } + + const DatasourceCheatsheet = datasourceInstance.components?.QueryEditorHelp; return ( - + <> + {QueryEditor} + {DatasourceCheatsheet && ( + + this.onChange(query)} datasource={datasourceInstance} /> + + )} + ); }; diff --git a/public/app/features/explore/state/datasource.test.ts b/public/app/features/explore/state/datasource.test.ts index 1841895c664..504a85bef13 100644 --- a/public/app/features/explore/state/datasource.test.ts +++ b/public/app/features/explore/state/datasource.test.ts @@ -12,7 +12,7 @@ describe('Datasource reducer', () => { logs: true, }, components: { - ExploreStartPage: StartPage, + QueryEditorHelp: StartPage, }, } as DataSourceApi; const queries: DataQuery[] = []; diff --git a/public/app/features/query/components/HelpToggle.tsx b/public/app/features/query/components/HelpToggle.tsx new file mode 100644 index 00000000000..7bfbd31502c --- /dev/null +++ b/public/app/features/query/components/HelpToggle.tsx @@ -0,0 +1,26 @@ +import { GrafanaTheme } from '@grafana/data'; +import { Icon, InfoBox, stylesFactory, useTheme } from '@grafana/ui'; +import { css, cx } from 'emotion'; +import React, { useState } from 'react'; + +const getStyles = stylesFactory((theme: GrafanaTheme) => ({ + infoBox: css` + margin-top: ${theme.spacing.xs}; + `, +})); + +export const HelpToggle: React.FunctionComponent = ({ children }) => { + const [isHelpVisible, setIsHelpVisible] = useState(false); + const theme = useTheme(); + const styles = getStyles(theme); + + return ( + <> + + {isHelpVisible && {children}} + + ); +}; diff --git a/public/app/features/query/components/QueryEditorRow.tsx b/public/app/features/query/components/QueryEditorRow.tsx index 1775035bec0..af6a84fca1d 100644 --- a/public/app/features/query/components/QueryEditorRow.tsx +++ b/public/app/features/query/components/QueryEditorRow.tsx @@ -6,7 +6,7 @@ import _ from 'lodash'; import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; import { AngularComponent, getAngularLoader } from '@grafana/runtime'; import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv'; -import { ErrorBoundaryAlert, HorizontalGroup } from '@grafana/ui'; +import { ErrorBoundaryAlert, HorizontalGroup, InfoBox } from '@grafana/ui'; import { DataQuery, DataSourceApi, @@ -48,6 +48,7 @@ interface State { hasTextEditMode: boolean; data?: PanelData; isOpen?: boolean; + showingHelp: boolean; } export class QueryEditorRow extends PureComponent { @@ -60,6 +61,7 @@ export class QueryEditorRow extends PureComponent { hasTextEditMode: false, data: undefined, isOpen: true, + showingHelp: false, }; componentDidMount() { @@ -226,6 +228,20 @@ export class QueryEditorRow extends PureComponent { this.props.onRunQuery(); }; + onToggleHelp = () => { + this.setState(state => ({ + showingHelp: !state.showingHelp, + })); + }; + + onClickExample = (query: DataQuery) => { + this.props.onChange({ + ...query, + refId: this.props.query.refId, + }); + this.onToggleHelp(); + }; + renderCollapsedText(): string | null { const { datasource } = this.state; if (datasource?.getQueryDisplayText) { @@ -240,11 +256,16 @@ export class QueryEditorRow extends PureComponent { renderActions = (props: QueryOperationRowRenderProps) => { const { query } = this.props; - const { hasTextEditMode } = this.state; + const { hasTextEditMode, datasource } = this.state; const isDisabled = query.hide; + const hasEditorHelp = datasource?.components?.QueryEditorHelp; + return ( + {hasEditorHelp && ( + + )} {hasTextEditMode && ( { render() { const { query, id, index } = this.props; - const { datasource } = this.state; + const { datasource, showingHelp } = this.state; const isDisabled = query.hide; const rowClasses = classNames('query-editor-row', { @@ -299,6 +320,7 @@ export class QueryEditorRow extends PureComponent { } const editor = this.renderPluginEditor(); + const DatasourceCheatsheet = datasource.components?.QueryEditorHelp; return (
@@ -311,7 +333,14 @@ export class QueryEditorRow extends PureComponent { onOpen={this.onOpen} >
- {editor} + + {showingHelp && DatasourceCheatsheet && ( + + this.onClickExample(query)} datasource={datasource} /> + + )} + {editor} +
diff --git a/public/app/plugins/datasource/cloudwatch/components/LogsCheatSheet.tsx b/public/app/plugins/datasource/cloudwatch/components/LogsCheatSheet.tsx index cd636cbdbf0..87cfc1c8024 100644 --- a/public/app/plugins/datasource/cloudwatch/components/LogsCheatSheet.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/LogsCheatSheet.tsx @@ -1,6 +1,6 @@ import React, { PureComponent } from 'react'; import { stripIndent, stripIndents } from 'common-tags'; -import { ExploreStartPageProps } from '@grafana/data'; +import { QueryEditorHelpProps } from '@grafana/data'; import Prism from 'prismjs'; import tokenizer from '../syntax'; import { flattenTokens } from '@grafana/ui/src/slate-plugins/slate-prism'; @@ -214,7 +214,7 @@ const exampleCategory = css` margin-top: 5px; `; -export default class LogsCheatSheet extends PureComponent { +export default class LogsCheatSheet extends PureComponent { onClickExample(query: CloudWatchLogsQuery) { this.props.onClickExample(query); } diff --git a/public/app/plugins/datasource/cloudwatch/module.tsx b/public/app/plugins/datasource/cloudwatch/module.tsx index 81a738d4df1..9c82f4ef377 100644 --- a/public/app/plugins/datasource/cloudwatch/module.tsx +++ b/public/app/plugins/datasource/cloudwatch/module.tsx @@ -11,7 +11,7 @@ import LogsCheatSheet from './components/LogsCheatSheet'; export const plugin = new DataSourcePlugin( CloudWatchDatasource ) - .setExploreStartPage(LogsCheatSheet) + .setQueryEditorHelp(LogsCheatSheet) .setConfigEditor(ConfigEditor) .setQueryEditor(PanelQueryEditor) .setExploreMetricsQueryField(PanelQueryEditor) diff --git a/public/app/plugins/datasource/influxdb/components/InfluxStartPage.tsx b/public/app/plugins/datasource/influxdb/components/InfluxStartPage.tsx index 53b435cabd7..846d0155ea7 100644 --- a/public/app/plugins/datasource/influxdb/components/InfluxStartPage.tsx +++ b/public/app/plugins/datasource/influxdb/components/InfluxStartPage.tsx @@ -1,8 +1,8 @@ import React, { PureComponent } from 'react'; -import { ExploreStartPageProps } from '@grafana/data'; +import { QueryEditorHelpProps } from '@grafana/data'; import InfluxCheatSheet from './InfluxCheatSheet'; -export default class InfluxStartPage extends PureComponent { +export default class InfluxStartPage extends PureComponent { render() { return ; } diff --git a/public/app/plugins/datasource/influxdb/module.ts b/public/app/plugins/datasource/influxdb/module.ts index 1f643df3aeb..b69501e7bfc 100644 --- a/public/app/plugins/datasource/influxdb/module.ts +++ b/public/app/plugins/datasource/influxdb/module.ts @@ -17,4 +17,4 @@ export const plugin = new DataSourcePlugin(InfluxDatasource) .setQueryCtrl(InfluxQueryCtrl) .setAnnotationQueryCtrl(InfluxAnnotationsQueryCtrl) .setVariableQueryEditor(VariableQueryEditor) - .setExploreStartPage(InfluxStartPage); + .setQueryEditorHelp(InfluxStartPage); diff --git a/public/app/plugins/datasource/loki/components/LokiCheatSheet.tsx b/public/app/plugins/datasource/loki/components/LokiCheatSheet.tsx index a425a32acd5..a034bc43a3c 100644 --- a/public/app/plugins/datasource/loki/components/LokiCheatSheet.tsx +++ b/public/app/plugins/datasource/loki/components/LokiCheatSheet.tsx @@ -1,6 +1,6 @@ import React, { PureComponent } from 'react'; import { shuffle } from 'lodash'; -import { ExploreStartPageProps, DataQuery } from '@grafana/data'; +import { QueryEditorHelpProps, DataQuery } from '@grafana/data'; import LokiLanguageProvider from '../language_provider'; const DEFAULT_EXAMPLES = ['{job="default/prometheus"}']; @@ -32,7 +32,7 @@ const LOGQL_EXAMPLES = [ }, ]; -export default class LokiCheatSheet extends PureComponent { +export default class LokiCheatSheet extends PureComponent { userLabelTimer: NodeJS.Timeout; state = { userExamples: DEFAULT_EXAMPLES, diff --git a/public/app/plugins/datasource/loki/module.ts b/public/app/plugins/datasource/loki/module.ts index 126d0d80d58..a32760427ac 100644 --- a/public/app/plugins/datasource/loki/module.ts +++ b/public/app/plugins/datasource/loki/module.ts @@ -11,5 +11,5 @@ export const plugin = new DataSourcePlugin(Datasource) .setQueryEditor(LokiQueryEditor) .setConfigEditor(ConfigEditor) .setExploreQueryField(LokiExploreQueryEditor) - .setExploreStartPage(LokiCheatSheet) + .setQueryEditorHelp(LokiCheatSheet) .setAnnotationQueryCtrl(LokiAnnotationsQueryCtrl); diff --git a/public/app/plugins/datasource/prometheus/components/PromCheatSheet.tsx b/public/app/plugins/datasource/prometheus/components/PromCheatSheet.tsx index c114405e8bf..efd669ba20f 100644 --- a/public/app/plugins/datasource/prometheus/components/PromCheatSheet.tsx +++ b/public/app/plugins/datasource/prometheus/components/PromCheatSheet.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { ExploreStartPageProps, DataQuery } from '@grafana/data'; +import { QueryEditorHelpProps, DataQuery } from '@grafana/data'; const CHEAT_SHEET_ITEMS = [ { @@ -25,7 +25,7 @@ const CHEAT_SHEET_ITEMS = [ }, ]; -const PromCheatSheet = (props: ExploreStartPageProps) => ( +const PromCheatSheet = (props: QueryEditorHelpProps) => (

PromQL Cheat Sheet

{CHEAT_SHEET_ITEMS.map((item, index) => ( diff --git a/public/app/plugins/datasource/prometheus/module.ts b/public/app/plugins/datasource/prometheus/module.ts index eba42cec3f9..60d48c0b7dd 100644 --- a/public/app/plugins/datasource/prometheus/module.ts +++ b/public/app/plugins/datasource/prometheus/module.ts @@ -17,4 +17,4 @@ export const plugin = new DataSourcePlugin(PrometheusDatasource) .setConfigEditor(ConfigEditor) .setExploreMetricsQueryField(PromExploreQueryEditor) .setAnnotationQueryCtrl(PrometheusAnnotationsQueryCtrl) - .setExploreStartPage(PromCheatSheet); + .setQueryEditorHelp(PromCheatSheet);