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
@ -147,11 +147,10 @@ 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:
|
||||
|
||||
| Name | Description |
|
||||
| ------------------ | ----------------------------------------------------------------------------------------- |
|
||||
| **Query patterns** | 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. |
|
||||
| **Raw query** | Displays the raw query generated by the Builder that will be sent to Prometheus instance. |
|
||||
| Name | Description |
|
||||
| ------------------------- | ----------------------------------------------------------------------------------------- |
|
||||
| **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. |
|
||||
|
||||
### 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.
|
||||
|
||||
### 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
|
||||
|
||||
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 { getOperationDefinitions } from './operations';
|
||||
import { LokiAndPromQueryModellerBase } from './shared/LokiAndPromQueryModellerBase';
|
||||
import { PromQueryPattern, PromVisualQueryOperationCategory } from './types';
|
||||
import { PromQueryPattern, PromQueryPatternType, PromVisualQueryOperationCategory } from './types';
|
||||
|
||||
export class PromQueryModeller extends LokiAndPromQueryModellerBase {
|
||||
constructor() {
|
||||
@ -32,6 +32,7 @@ export class PromQueryModeller extends LokiAndPromQueryModellerBase {
|
||||
return [
|
||||
{
|
||||
name: 'Rate then sum',
|
||||
type: PromQueryPatternType.Rate,
|
||||
operations: [
|
||||
{ id: 'rate', params: ['$__rate_interval'] },
|
||||
{ id: 'sum', params: [] },
|
||||
@ -39,6 +40,7 @@ export class PromQueryModeller extends LokiAndPromQueryModellerBase {
|
||||
},
|
||||
{
|
||||
name: 'Rate then sum by(label) then avg',
|
||||
type: PromQueryPatternType.Rate,
|
||||
operations: [
|
||||
{ id: 'rate', params: ['$__rate_interval'] },
|
||||
{ id: '__sum_by', params: [''] },
|
||||
@ -47,6 +49,7 @@ export class PromQueryModeller extends LokiAndPromQueryModellerBase {
|
||||
},
|
||||
{
|
||||
name: 'Histogram quantile on rate',
|
||||
type: PromQueryPatternType.Histogram,
|
||||
operations: [
|
||||
{ id: 'rate', params: ['$__rate_interval'] },
|
||||
{ id: '__sum_by', params: ['le'] },
|
||||
@ -54,13 +57,35 @@ export class PromQueryModeller extends LokiAndPromQueryModellerBase {
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Histogram quantile on increase ',
|
||||
name: 'Histogram quantile on increase',
|
||||
type: PromQueryPatternType.Histogram,
|
||||
operations: [
|
||||
{ id: 'increase', params: ['$__rate_interval'] },
|
||||
{ id: '__max_by', params: ['le'] },
|
||||
{ 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 { 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 { Button, ConfirmModal } from '@grafana/ui';
|
||||
|
||||
import { PromQueryEditorProps } from '../../components/types';
|
||||
import { PromQuery } from '../../types';
|
||||
import { promQueryModeller } from '../PromQueryModeller';
|
||||
import { QueryPatternsModal } from '../QueryPatternsModal';
|
||||
import { buildVisualQueryFromString } from '../parsing';
|
||||
import { QueryEditorModeToggle } from '../shared/QueryEditorModeToggle';
|
||||
import { QueryHeaderSwitch } from '../shared/QueryHeaderSwitch';
|
||||
@ -39,9 +40,13 @@ export const PromQueryEditorSelector = React.memo<Props>((props) => {
|
||||
onRunQuery,
|
||||
data,
|
||||
app,
|
||||
onAddQuery,
|
||||
datasource: { defaultEditor },
|
||||
queries,
|
||||
} = props;
|
||||
|
||||
const [parseModalOpen, setParseModalOpen] = useState(false);
|
||||
const [queryPatternsModalOpen, setQueryPatternsModalOpen] = useState(false);
|
||||
const [dataIsStale, setDataIsStale] = useState(false);
|
||||
const { flag: explain, setFlag: setExplain } = useFlag(promQueryEditorExplainKey);
|
||||
|
||||
@ -97,23 +102,24 @@ export const PromQueryEditorSelector = React.memo<Props>((props) => {
|
||||
}}
|
||||
onDismiss={() => setParseModalOpen(false)}
|
||||
/>
|
||||
<QueryPatternsModal
|
||||
isOpen={queryPatternsModalOpen}
|
||||
onClose={() => setQueryPatternsModalOpen(false)}
|
||||
query={query}
|
||||
queries={queries}
|
||||
app={app}
|
||||
onChange={onChange}
|
||||
onAddQuery={onAddQuery}
|
||||
/>
|
||||
<EditorHeader>
|
||||
<InlineSelect
|
||||
value={null}
|
||||
placeholder="Query patterns"
|
||||
allowCustomValue
|
||||
onChange={({ value }) => {
|
||||
// TODO: Bit convoluted as we don't have access to visualQuery model here. Maybe would make sense to
|
||||
// move it inside the editor?
|
||||
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 }))}
|
||||
/>
|
||||
<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} />
|
||||
<FlexItem grow={1} />
|
||||
{app !== CoreApp.Explore && app !== CoreApp.Correlations && (
|
||||
|
@ -119,7 +119,15 @@ export enum PromOperationId {
|
||||
LessOrEqual = '__less_or_equal',
|
||||
}
|
||||
|
||||
export enum PromQueryPatternType {
|
||||
Rate = 'rate',
|
||||
Histogram = 'histogram',
|
||||
Binary = 'binary',
|
||||
}
|
||||
|
||||
export interface PromQueryPattern {
|
||||
name: string;
|
||||
operations: QueryBuilderOperation[];
|
||||
type: PromQueryPatternType;
|
||||
binaryQueries?: PromVisualQueryBinary[];
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user