mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Prometheus: Kickstart your query, formerly query patterns (#60718)
* add query pattern prom types * rebase main * update docs replace query patterns with kick start your query * update docs, remove raw query from prom docs Raw query toggle was removed from the code * add binary query pattern to query patterns * add aria labels for accessibility * fix tests * apply/create a query pattern behavior if anything exists in the query editor * fix tests * rebase main
This commit is contained in:
parent
b5fa9e3501
commit
872df59de5
@ -148,10 +148,9 @@ This video demonstrates how to use the visual Prometheus query builder available
|
|||||||
In addition to the **Run query** button and mode switcher, Builder mode includes additional elements:
|
In addition to the **Run query** button and mode switcher, Builder mode includes additional elements:
|
||||||
|
|
||||||
| Name | Description |
|
| Name | Description |
|
||||||
| ------------------ | ----------------------------------------------------------------------------------------- |
|
| ------------------------- | ----------------------------------------------------------------------------------------- |
|
||||||
| **Query patterns** | A list of operation patterns that help you quickly add multiple operations to your query. |
|
| **Kick start your query** | A list of operation patterns that help you quickly add multiple operations to your query. |
|
||||||
| **Explain** | Displays a step-by-step explanation of all query parts and its operations. |
|
| **Explain** | Displays a step-by-step explanation of all query parts and its operations. |
|
||||||
| **Raw query** | Displays the raw query generated by the Builder that will be sent to Prometheus instance. |
|
|
||||||
|
|
||||||
### Metric and labels
|
### Metric and labels
|
||||||
|
|
||||||
@ -203,13 +202,6 @@ To add the operations to your query, click the hint.
|
|||||||
|
|
||||||
Explain mode helps you understand a query by displaying a step-by-step explanation of all query components and operations.
|
Explain mode helps you understand a query by displaying a step-by-step explanation of all query components and operations.
|
||||||
|
|
||||||
### Raw query
|
|
||||||
|
|
||||||
{{< figure src="/static/img/docs/prometheus/raw-query-8-5.png" max-width="500px" class="docs-image--no-shadow" caption="Raw query" >}}
|
|
||||||
|
|
||||||
The query editor displays the raw query only if the **Raw query** switch from the query editor toolbar is enabled.
|
|
||||||
If visible, it displays the raw query that the query editor has created.
|
|
||||||
|
|
||||||
### Additional options
|
### Additional options
|
||||||
|
|
||||||
In addition to these Builder mode-specific options, the query editor also displays the options it shares in common with Code mode.
|
In addition to these Builder mode-specific options, the query editor also displays the options it shares in common with Code mode.
|
||||||
|
@ -3,7 +3,7 @@ import { FUNCTIONS } from '../promql';
|
|||||||
import { getAggregationOperations } from './aggregations';
|
import { getAggregationOperations } from './aggregations';
|
||||||
import { getOperationDefinitions } from './operations';
|
import { getOperationDefinitions } from './operations';
|
||||||
import { LokiAndPromQueryModellerBase } from './shared/LokiAndPromQueryModellerBase';
|
import { LokiAndPromQueryModellerBase } from './shared/LokiAndPromQueryModellerBase';
|
||||||
import { PromQueryPattern, PromVisualQueryOperationCategory } from './types';
|
import { PromQueryPattern, PromQueryPatternType, PromVisualQueryOperationCategory } from './types';
|
||||||
|
|
||||||
export class PromQueryModeller extends LokiAndPromQueryModellerBase {
|
export class PromQueryModeller extends LokiAndPromQueryModellerBase {
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -32,6 +32,7 @@ export class PromQueryModeller extends LokiAndPromQueryModellerBase {
|
|||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
name: 'Rate then sum',
|
name: 'Rate then sum',
|
||||||
|
type: PromQueryPatternType.Rate,
|
||||||
operations: [
|
operations: [
|
||||||
{ id: 'rate', params: ['$__rate_interval'] },
|
{ id: 'rate', params: ['$__rate_interval'] },
|
||||||
{ id: 'sum', params: [] },
|
{ id: 'sum', params: [] },
|
||||||
@ -39,6 +40,7 @@ export class PromQueryModeller extends LokiAndPromQueryModellerBase {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Rate then sum by(label) then avg',
|
name: 'Rate then sum by(label) then avg',
|
||||||
|
type: PromQueryPatternType.Rate,
|
||||||
operations: [
|
operations: [
|
||||||
{ id: 'rate', params: ['$__rate_interval'] },
|
{ id: 'rate', params: ['$__rate_interval'] },
|
||||||
{ id: '__sum_by', params: [''] },
|
{ id: '__sum_by', params: [''] },
|
||||||
@ -47,6 +49,7 @@ export class PromQueryModeller extends LokiAndPromQueryModellerBase {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Histogram quantile on rate',
|
name: 'Histogram quantile on rate',
|
||||||
|
type: PromQueryPatternType.Histogram,
|
||||||
operations: [
|
operations: [
|
||||||
{ id: 'rate', params: ['$__rate_interval'] },
|
{ id: 'rate', params: ['$__rate_interval'] },
|
||||||
{ id: '__sum_by', params: ['le'] },
|
{ id: '__sum_by', params: ['le'] },
|
||||||
@ -55,12 +58,34 @@ export class PromQueryModeller extends LokiAndPromQueryModellerBase {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Histogram quantile on increase',
|
name: 'Histogram quantile on increase',
|
||||||
|
type: PromQueryPatternType.Histogram,
|
||||||
operations: [
|
operations: [
|
||||||
{ id: 'increase', params: ['$__rate_interval'] },
|
{ id: 'increase', params: ['$__rate_interval'] },
|
||||||
{ id: '__max_by', params: ['le'] },
|
{ id: '__max_by', params: ['le'] },
|
||||||
{ id: 'histogram_quantile', params: [0.95] },
|
{ id: 'histogram_quantile', params: [0.95] },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'Binary Query',
|
||||||
|
type: PromQueryPatternType.Binary,
|
||||||
|
operations: [
|
||||||
|
{ id: 'rate', params: ['$__rate_interval'] },
|
||||||
|
{ id: 'sum', params: [] },
|
||||||
|
],
|
||||||
|
binaryQueries: [
|
||||||
|
{
|
||||||
|
operator: '/',
|
||||||
|
query: {
|
||||||
|
metric: '',
|
||||||
|
labels: [],
|
||||||
|
operations: [
|
||||||
|
{ id: 'rate', params: ['$__rate_interval'] },
|
||||||
|
{ id: 'sum', params: [] },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,118 @@
|
|||||||
|
import { css } from '@emotion/css';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
|
import { Button, Card, useStyles2 } from '@grafana/ui';
|
||||||
|
import { RawQuery } from 'app/plugins/datasource/prometheus/querybuilder/shared/RawQuery';
|
||||||
|
|
||||||
|
import promqlGrammar from '../promql';
|
||||||
|
|
||||||
|
import { promQueryModeller } from './PromQueryModeller';
|
||||||
|
import { PromQueryPattern } from './types';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
pattern: PromQueryPattern;
|
||||||
|
hasNewQueryOption: boolean;
|
||||||
|
hasPreviousQuery: boolean | string;
|
||||||
|
selectedPatternName: string | null;
|
||||||
|
setSelectedPatternName: (name: string | null) => void;
|
||||||
|
onPatternSelect: (pattern: PromQueryPattern, selectAsNewQuery?: boolean) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const QueryPattern = (props: Props) => {
|
||||||
|
const { pattern, onPatternSelect, hasNewQueryOption, hasPreviousQuery, selectedPatternName, setSelectedPatternName } =
|
||||||
|
props;
|
||||||
|
|
||||||
|
const styles = useStyles2(getStyles);
|
||||||
|
const lang = { grammar: promqlGrammar, name: 'promql' };
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className={styles.card}>
|
||||||
|
<Card.Heading>{pattern.name}</Card.Heading>
|
||||||
|
<div className={styles.rawQueryContainer}>
|
||||||
|
<RawQuery
|
||||||
|
aria-label={`${pattern.name} raw query`}
|
||||||
|
query={promQueryModeller.renderQuery({
|
||||||
|
labels: [],
|
||||||
|
operations: pattern.operations,
|
||||||
|
binaryQueries: pattern.binaryQueries,
|
||||||
|
})}
|
||||||
|
lang={lang}
|
||||||
|
className={styles.rawQuery}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Card.Actions>
|
||||||
|
{selectedPatternName !== pattern.name ? (
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
aria-label="use this query button"
|
||||||
|
onClick={() => {
|
||||||
|
if (hasPreviousQuery) {
|
||||||
|
// If user has previous query, we need to confirm that they want to apply this query pattern
|
||||||
|
setSelectedPatternName(pattern.name);
|
||||||
|
} else {
|
||||||
|
onPatternSelect(pattern);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Use this query
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<div className={styles.spacing}>
|
||||||
|
{`If you would like to use this query, ${
|
||||||
|
hasNewQueryOption
|
||||||
|
? 'you can either apply this query pattern or create a new query'
|
||||||
|
: 'this query pattern will be applied to your current query'
|
||||||
|
}.`}
|
||||||
|
</div>
|
||||||
|
<Button size="sm" aria-label="back button" fill="outline" onClick={() => setSelectedPatternName(null)}>
|
||||||
|
Back
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
aria-label="apply query starter button"
|
||||||
|
onClick={() => {
|
||||||
|
onPatternSelect(pattern);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Apply query
|
||||||
|
</Button>
|
||||||
|
{hasNewQueryOption && (
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
aria-label="create new query button"
|
||||||
|
onClick={() => {
|
||||||
|
onPatternSelect(pattern, true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Create new query
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Card.Actions>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStyles = (theme: GrafanaTheme2) => {
|
||||||
|
return {
|
||||||
|
card: css`
|
||||||
|
width: 49.5%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
`,
|
||||||
|
rawQueryContainer: css`
|
||||||
|
flex-grow: 1;
|
||||||
|
`,
|
||||||
|
rawQuery: css`
|
||||||
|
background-color: ${theme.colors.background.primary};
|
||||||
|
padding: ${theme.spacing(1)};
|
||||||
|
margin-top: ${theme.spacing(1)};
|
||||||
|
`,
|
||||||
|
spacing: css`
|
||||||
|
margin-bottom: ${theme.spacing(1)};
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
};
|
@ -0,0 +1,143 @@
|
|||||||
|
import { render, screen, waitFor } from '@testing-library/react';
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { promQueryModeller } from './PromQueryModeller';
|
||||||
|
import { QueryPatternsModal } from './QueryPatternsModal';
|
||||||
|
import { PromQueryPatternType } from './types';
|
||||||
|
|
||||||
|
// don't care about interaction tracking in our unit tests
|
||||||
|
jest.mock('@grafana/runtime', () => ({
|
||||||
|
...jest.requireActual('@grafana/runtime'),
|
||||||
|
reportInteraction: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const defaultProps = {
|
||||||
|
isOpen: true,
|
||||||
|
onClose: jest.fn(),
|
||||||
|
onChange: jest.fn(),
|
||||||
|
onAddQuery: jest.fn(),
|
||||||
|
query: {
|
||||||
|
refId: 'A',
|
||||||
|
expr: 'sum(rate({job="grafana"}[$__rate_interval]))',
|
||||||
|
},
|
||||||
|
queries: [
|
||||||
|
{
|
||||||
|
refId: 'A',
|
||||||
|
expr: 'go_goroutines{instance="localhost:9090"}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const queryPatterns = {
|
||||||
|
rateQueryPatterns: promQueryModeller
|
||||||
|
.getQueryPatterns()
|
||||||
|
.filter((pattern) => pattern.type === PromQueryPatternType.Rate),
|
||||||
|
histogramQueryPatterns: promQueryModeller
|
||||||
|
.getQueryPatterns()
|
||||||
|
.filter((pattern) => pattern.type === PromQueryPatternType.Histogram),
|
||||||
|
binaryQueryPatterns: promQueryModeller
|
||||||
|
.getQueryPatterns()
|
||||||
|
.filter((pattern) => pattern.type === PromQueryPatternType.Binary),
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('QueryPatternsModal', () => {
|
||||||
|
it('renders the modal', () => {
|
||||||
|
render(<QueryPatternsModal {...defaultProps} />);
|
||||||
|
expect(screen.getByText('Kick start your query')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
it('renders collapsible elements with all query pattern types', () => {
|
||||||
|
render(<QueryPatternsModal {...defaultProps} />);
|
||||||
|
Object.values(PromQueryPatternType).forEach((pattern) => {
|
||||||
|
expect(screen.getByText(new RegExp(`${pattern} query starters`, 'i'))).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('can open and close query patterns section', async () => {
|
||||||
|
render(<QueryPatternsModal {...defaultProps} />);
|
||||||
|
await userEvent.click(screen.getByText('Rate query starters'));
|
||||||
|
expect(screen.getByText(queryPatterns.rateQueryPatterns[0].name)).toBeInTheDocument();
|
||||||
|
|
||||||
|
await userEvent.click(screen.getByText('Rate query starters'));
|
||||||
|
expect(screen.queryByText(queryPatterns.rateQueryPatterns[0].name)).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can open and close multiple query patterns section', async () => {
|
||||||
|
render(<QueryPatternsModal {...defaultProps} />);
|
||||||
|
await userEvent.click(screen.getByText('Rate query starters'));
|
||||||
|
expect(screen.getByText(queryPatterns.rateQueryPatterns[0].name)).toBeInTheDocument();
|
||||||
|
|
||||||
|
await userEvent.click(screen.getByText('Histogram query starters'));
|
||||||
|
expect(screen.getByText(queryPatterns.histogramQueryPatterns[0].name)).toBeInTheDocument();
|
||||||
|
|
||||||
|
await userEvent.click(screen.getByText('Rate query starters'));
|
||||||
|
expect(screen.queryByText(queryPatterns.rateQueryPatterns[0].name)).not.toBeInTheDocument();
|
||||||
|
|
||||||
|
// Histogram patterns should still be open
|
||||||
|
expect(screen.getByText(queryPatterns.histogramQueryPatterns[0].name)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses pattern if there is no existing query', async () => {
|
||||||
|
render(<QueryPatternsModal {...defaultProps} query={{ expr: '', refId: 'A' }} />);
|
||||||
|
await userEvent.click(screen.getByText('Rate query starters'));
|
||||||
|
expect(screen.getByText(queryPatterns.rateQueryPatterns[0].name)).toBeInTheDocument();
|
||||||
|
const firstUseQueryButton = screen.getAllByRole('button', { name: 'use this query button' })[0];
|
||||||
|
await userEvent.click(firstUseQueryButton);
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(defaultProps.onChange).toHaveBeenCalledWith({
|
||||||
|
expr: 'sum(rate([$__rate_interval]))',
|
||||||
|
refId: 'A',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('gives warning when selecting pattern if there are already existing query', async () => {
|
||||||
|
render(<QueryPatternsModal {...defaultProps} />);
|
||||||
|
await userEvent.click(screen.getByText('Rate query starters'));
|
||||||
|
expect(screen.getByText(queryPatterns.rateQueryPatterns[0].name)).toBeInTheDocument();
|
||||||
|
const firstUseQueryButton = screen.getAllByRole('button', { name: 'use this query button' })[0];
|
||||||
|
await userEvent.click(firstUseQueryButton);
|
||||||
|
|
||||||
|
expect(screen.getByText(/you can either apply this query pattern or create a new query/)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can use create new query when selecting pattern if there is already existing query', async () => {
|
||||||
|
render(<QueryPatternsModal {...defaultProps} />);
|
||||||
|
await userEvent.click(screen.getByText('Rate query starters'));
|
||||||
|
expect(screen.getByText(queryPatterns.rateQueryPatterns[0].name)).toBeInTheDocument();
|
||||||
|
const firstUseQueryButton = screen.getAllByRole('button', { name: 'use this query button' })[0];
|
||||||
|
await userEvent.click(firstUseQueryButton);
|
||||||
|
const createNewQueryButton = screen.getByRole('button', { name: 'create new query button' });
|
||||||
|
expect(createNewQueryButton).toBeInTheDocument();
|
||||||
|
await userEvent.click(createNewQueryButton);
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(defaultProps.onAddQuery).toHaveBeenCalledWith({
|
||||||
|
expr: 'sum(rate([$__rate_interval]))',
|
||||||
|
refId: 'B',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not show create new query option if onAddQuery function is not provided ', async () => {
|
||||||
|
render(<QueryPatternsModal {...defaultProps} onAddQuery={undefined} />);
|
||||||
|
await userEvent.click(screen.getByText('Rate query starters'));
|
||||||
|
expect(screen.getByText(queryPatterns.rateQueryPatterns[0].name)).toBeInTheDocument();
|
||||||
|
const useQueryButton = screen.getAllByRole('button', { name: 'use this query button' })[0];
|
||||||
|
await userEvent.click(useQueryButton);
|
||||||
|
expect(screen.queryByRole('button', { name: 'Create new query' })).not.toBeInTheDocument();
|
||||||
|
expect(screen.getByText(/this query pattern will be applied to your current query/)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('applies binary query patterns to query', async () => {
|
||||||
|
render(<QueryPatternsModal {...defaultProps} query={{ expr: '', refId: 'A' }} />);
|
||||||
|
await userEvent.click(screen.getByText('Binary query starters'));
|
||||||
|
expect(screen.getByText(queryPatterns.binaryQueryPatterns[0].name)).toBeInTheDocument();
|
||||||
|
const firstUseQueryButton = screen.getAllByRole('button', { name: 'use this query button' })[0];
|
||||||
|
await userEvent.click(firstUseQueryButton);
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(defaultProps.onChange).toHaveBeenCalledWith({
|
||||||
|
expr: 'sum(rate([$__rate_interval])) / sum(rate([$__rate_interval]))',
|
||||||
|
refId: 'A',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,132 @@
|
|||||||
|
import { css } from '@emotion/css';
|
||||||
|
import { capitalize } from 'lodash';
|
||||||
|
import React, { useMemo, useState } from 'react';
|
||||||
|
|
||||||
|
import { CoreApp, DataQuery, GrafanaTheme2 } from '@grafana/data';
|
||||||
|
import { reportInteraction } from '@grafana/runtime';
|
||||||
|
import { Button, Collapse, Modal, useStyles2 } from '@grafana/ui';
|
||||||
|
import { getNextRefIdChar } from 'app/core/utils/query';
|
||||||
|
|
||||||
|
import { PromQuery } from '../types';
|
||||||
|
|
||||||
|
import { promQueryModeller } from './PromQueryModeller';
|
||||||
|
import { QueryPattern } from './QueryPattern';
|
||||||
|
import { buildVisualQueryFromString } from './parsing';
|
||||||
|
import { PromQueryPattern, PromQueryPatternType } from './types';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
isOpen: boolean;
|
||||||
|
query: PromQuery;
|
||||||
|
queries: DataQuery[] | undefined;
|
||||||
|
app?: CoreApp;
|
||||||
|
onClose: () => void;
|
||||||
|
onChange: (query: PromQuery) => void;
|
||||||
|
onAddQuery?: (query: PromQuery) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const QueryPatternsModal = (props: Props) => {
|
||||||
|
const { isOpen, onClose, onChange, onAddQuery, query, queries, app } = props;
|
||||||
|
const [openTabs, setOpenTabs] = useState<string[]>([]);
|
||||||
|
const [selectedPatternName, setSelectedPatternName] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const styles = useStyles2(getStyles);
|
||||||
|
const hasNewQueryOption = !!onAddQuery;
|
||||||
|
const hasPreviousQuery = useMemo(() => {
|
||||||
|
const visualQuery = buildVisualQueryFromString(query.expr);
|
||||||
|
// has anything entered in the query, metric, labels, operations, or binary queries
|
||||||
|
const hasOperations = visualQuery.query.operations.length > 0,
|
||||||
|
hasMetric = visualQuery.query.metric,
|
||||||
|
hasLabels = visualQuery.query.labels.length > 0,
|
||||||
|
hasBinaryQueries = visualQuery.query.binaryQueries ? visualQuery.query.binaryQueries.length > 0 : false;
|
||||||
|
|
||||||
|
return hasOperations || hasMetric || hasLabels || hasBinaryQueries;
|
||||||
|
}, [query.expr]);
|
||||||
|
|
||||||
|
const onPatternSelect = (pattern: PromQueryPattern, selectAsNewQuery = false) => {
|
||||||
|
const visualQuery = buildVisualQueryFromString(selectAsNewQuery ? '' : query.expr);
|
||||||
|
reportInteraction('grafana_prom_kickstart_your_query_selected', {
|
||||||
|
app: app ?? '',
|
||||||
|
editorMode: query.editorMode,
|
||||||
|
selectedPattern: pattern.name,
|
||||||
|
preSelectedOperationsCount: visualQuery.query.operations.length,
|
||||||
|
preSelectedLabelsCount: visualQuery.query.labels.length,
|
||||||
|
createNewQuery: hasNewQueryOption && selectAsNewQuery,
|
||||||
|
});
|
||||||
|
|
||||||
|
visualQuery.query.operations = pattern.operations;
|
||||||
|
visualQuery.query.binaryQueries = pattern.binaryQueries;
|
||||||
|
if (hasNewQueryOption && selectAsNewQuery) {
|
||||||
|
onAddQuery({
|
||||||
|
...query,
|
||||||
|
refId: getNextRefIdChar(queries ?? [query]),
|
||||||
|
expr: promQueryModeller.renderQuery(visualQuery.query),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
onChange({
|
||||||
|
...query,
|
||||||
|
expr: promQueryModeller.renderQuery(visualQuery.query),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setSelectedPatternName(null);
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal aria-label="Kick start your query modal" isOpen={isOpen} title="Kick start your query" onDismiss={onClose}>
|
||||||
|
<div className={styles.spacing}>
|
||||||
|
Kick start your query by selecting one of these queries. You can then continue to complete your query.
|
||||||
|
</div>
|
||||||
|
{Object.values(PromQueryPatternType).map((patternType) => {
|
||||||
|
return (
|
||||||
|
<Collapse
|
||||||
|
aria-label={`open and close ${patternType} query starter card`}
|
||||||
|
key={patternType}
|
||||||
|
label={`${capitalize(patternType)} query starters`}
|
||||||
|
isOpen={openTabs.includes(patternType)}
|
||||||
|
collapsible={true}
|
||||||
|
onToggle={() =>
|
||||||
|
setOpenTabs((tabs) =>
|
||||||
|
// close tab if it's already open, otherwise open it
|
||||||
|
tabs.includes(patternType) ? tabs.filter((t) => t !== patternType) : [...tabs, patternType]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className={styles.cardsContainer}>
|
||||||
|
{promQueryModeller
|
||||||
|
.getQueryPatterns()
|
||||||
|
.filter((pattern) => pattern.type === patternType)
|
||||||
|
.map((pattern) => (
|
||||||
|
<QueryPattern
|
||||||
|
key={pattern.name}
|
||||||
|
pattern={pattern}
|
||||||
|
hasNewQueryOption={hasNewQueryOption}
|
||||||
|
hasPreviousQuery={hasPreviousQuery}
|
||||||
|
onPatternSelect={onPatternSelect}
|
||||||
|
selectedPatternName={selectedPatternName}
|
||||||
|
setSelectedPatternName={setSelectedPatternName}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Collapse>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
<Button aria-label="close kick start your query modal" variant="secondary" onClick={onClose}>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStyles = (theme: GrafanaTheme2) => {
|
||||||
|
return {
|
||||||
|
cardsContainer: css`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
`,
|
||||||
|
spacing: css`
|
||||||
|
margin-bottom: ${theme.spacing(1)};
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
};
|
@ -2,13 +2,14 @@ import { map } from 'lodash';
|
|||||||
import React, { SyntheticEvent, useCallback, useEffect, useState } from 'react';
|
import React, { SyntheticEvent, useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { CoreApp, LoadingState, SelectableValue } from '@grafana/data';
|
import { CoreApp, LoadingState, SelectableValue } from '@grafana/data';
|
||||||
import { EditorHeader, EditorRows, FlexItem, InlineSelect, Space } from '@grafana/experimental';
|
import { selectors } from '@grafana/e2e-selectors';
|
||||||
|
import { EditorHeader, EditorRows, FlexItem, Space } from '@grafana/experimental';
|
||||||
import { reportInteraction } from '@grafana/runtime';
|
import { reportInteraction } from '@grafana/runtime';
|
||||||
import { Button, ConfirmModal } from '@grafana/ui';
|
import { Button, ConfirmModal } from '@grafana/ui';
|
||||||
|
|
||||||
import { PromQueryEditorProps } from '../../components/types';
|
import { PromQueryEditorProps } from '../../components/types';
|
||||||
import { PromQuery } from '../../types';
|
import { PromQuery } from '../../types';
|
||||||
import { promQueryModeller } from '../PromQueryModeller';
|
import { QueryPatternsModal } from '../QueryPatternsModal';
|
||||||
import { buildVisualQueryFromString } from '../parsing';
|
import { buildVisualQueryFromString } from '../parsing';
|
||||||
import { QueryEditorModeToggle } from '../shared/QueryEditorModeToggle';
|
import { QueryEditorModeToggle } from '../shared/QueryEditorModeToggle';
|
||||||
import { QueryHeaderSwitch } from '../shared/QueryHeaderSwitch';
|
import { QueryHeaderSwitch } from '../shared/QueryHeaderSwitch';
|
||||||
@ -39,9 +40,13 @@ export const PromQueryEditorSelector = React.memo<Props>((props) => {
|
|||||||
onRunQuery,
|
onRunQuery,
|
||||||
data,
|
data,
|
||||||
app,
|
app,
|
||||||
|
onAddQuery,
|
||||||
datasource: { defaultEditor },
|
datasource: { defaultEditor },
|
||||||
|
queries,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const [parseModalOpen, setParseModalOpen] = useState(false);
|
const [parseModalOpen, setParseModalOpen] = useState(false);
|
||||||
|
const [queryPatternsModalOpen, setQueryPatternsModalOpen] = useState(false);
|
||||||
const [dataIsStale, setDataIsStale] = useState(false);
|
const [dataIsStale, setDataIsStale] = useState(false);
|
||||||
const { flag: explain, setFlag: setExplain } = useFlag(promQueryEditorExplainKey);
|
const { flag: explain, setFlag: setExplain } = useFlag(promQueryEditorExplainKey);
|
||||||
|
|
||||||
@ -97,23 +102,24 @@ export const PromQueryEditorSelector = React.memo<Props>((props) => {
|
|||||||
}}
|
}}
|
||||||
onDismiss={() => setParseModalOpen(false)}
|
onDismiss={() => setParseModalOpen(false)}
|
||||||
/>
|
/>
|
||||||
<EditorHeader>
|
<QueryPatternsModal
|
||||||
<InlineSelect
|
isOpen={queryPatternsModalOpen}
|
||||||
value={null}
|
onClose={() => setQueryPatternsModalOpen(false)}
|
||||||
placeholder="Query patterns"
|
query={query}
|
||||||
allowCustomValue
|
queries={queries}
|
||||||
onChange={({ value }) => {
|
app={app}
|
||||||
// TODO: Bit convoluted as we don't have access to visualQuery model here. Maybe would make sense to
|
onChange={onChange}
|
||||||
// move it inside the editor?
|
onAddQuery={onAddQuery}
|
||||||
const result = buildVisualQueryFromString(query.expr || '');
|
|
||||||
result.query.operations = value?.operations!;
|
|
||||||
onChange({
|
|
||||||
...query,
|
|
||||||
expr: promQueryModeller.renderQuery(result.query),
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
options={promQueryModeller.getQueryPatterns().map((x) => ({ label: x.name, value: x }))}
|
|
||||||
/>
|
/>
|
||||||
|
<EditorHeader>
|
||||||
|
<Button
|
||||||
|
aria-label={selectors.components.QueryBuilder.queryPatterns}
|
||||||
|
variant="secondary"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setQueryPatternsModalOpen((prevValue) => !prevValue)}
|
||||||
|
>
|
||||||
|
Kick start your query
|
||||||
|
</Button>
|
||||||
<QueryHeaderSwitch label="Explain" value={explain} onChange={onShowExplainChange} />
|
<QueryHeaderSwitch label="Explain" value={explain} onChange={onShowExplainChange} />
|
||||||
<FlexItem grow={1} />
|
<FlexItem grow={1} />
|
||||||
{app !== CoreApp.Explore && app !== CoreApp.Correlations && (
|
{app !== CoreApp.Explore && app !== CoreApp.Correlations && (
|
||||||
|
@ -119,7 +119,15 @@ export enum PromOperationId {
|
|||||||
LessOrEqual = '__less_or_equal',
|
LessOrEqual = '__less_or_equal',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum PromQueryPatternType {
|
||||||
|
Rate = 'rate',
|
||||||
|
Histogram = 'histogram',
|
||||||
|
Binary = 'binary',
|
||||||
|
}
|
||||||
|
|
||||||
export interface PromQueryPattern {
|
export interface PromQueryPattern {
|
||||||
name: string;
|
name: string;
|
||||||
operations: QueryBuilderOperation[];
|
operations: QueryBuilderOperation[];
|
||||||
|
type: PromQueryPatternType;
|
||||||
|
binaryQueries?: PromVisualQueryBinary[];
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user