mirror of
https://github.com/grafana/grafana.git
synced 2024-11-22 08:56:43 -06:00
Prometheus: Query advisor added copy for preview (#77903)
* add alert with link to docs * remove historical secondary text * add tooltip to opening button to suggest selecting a metric * fix merge conflict issue * add llm check for tooltip with docs * update starting message copy and alert copy * Update public/app/plugins/datasource/prometheus/querybuilder/components/promQail/PromQail.tsx Co-authored-by: Rob Whelan <github@jtheory.com> * Update public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilder.tsx Co-authored-by: Rob Whelan <github@jtheory.com> * add check for vector for button, remove check in helpers * clean a little, fix tests * refactor out query assistant button * fix styles --------- Co-authored-by: Rob Whelan <github@jtheory.com>
This commit is contained in:
parent
05fb9b5ecb
commit
83eb831a8f
@ -1,10 +1,10 @@
|
||||
import { css } from '@emotion/css';
|
||||
import React, { useState } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import { DataSourceApi, PanelData } from '@grafana/data';
|
||||
import { EditorRow } from '@grafana/experimental';
|
||||
import { config, reportInteraction } from '@grafana/runtime';
|
||||
import { Button, Drawer } from '@grafana/ui';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { Drawer } from '@grafana/ui';
|
||||
|
||||
import { PrometheusDatasource } from '../../datasource';
|
||||
import promqlGrammar from '../../promql';
|
||||
@ -23,7 +23,8 @@ import { MetricsLabelsSection } from './MetricsLabelsSection';
|
||||
import { NestedQueryList } from './NestedQueryList';
|
||||
import { EXPLAIN_LABEL_FILTER_CONTENT } from './PromQueryBuilderExplained';
|
||||
import { PromQail } from './promQail/PromQail';
|
||||
import AI_Logo_color from './promQail/resources/AI_Logo_color.svg';
|
||||
import { QueryAssistantButton } from './promQail/QueryAssistantButton';
|
||||
import { isLLMPluginEnabled } from './promQail/state/helpers';
|
||||
|
||||
export interface Props {
|
||||
query: PromVisualQuery;
|
||||
@ -42,15 +43,24 @@ export const PromQueryBuilder = React.memo<Props>((props) => {
|
||||
const { datasource, query, onChange, onRunQuery, data, showExplain } = props;
|
||||
const [highlightedOp, setHighlightedOp] = useState<QueryBuilderOperation | undefined>();
|
||||
const [showDrawer, setShowDrawer] = useState<boolean>(false);
|
||||
const [llmAppEnabled, updateLlmAppEnabled] = useState<boolean>(false);
|
||||
|
||||
const lang = { grammar: promqlGrammar, name: 'promql' };
|
||||
|
||||
const initHints = datasource.getInitHints();
|
||||
|
||||
useEffect(() => {
|
||||
async function checkLlms() {
|
||||
const check = await isLLMPluginEnabled();
|
||||
updateLlmAppEnabled(check);
|
||||
}
|
||||
checkLlms();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
{prometheusPromQAIL && showDrawer && (
|
||||
<Drawer scrollableContent={true} closeOnMaskClick={false} onClose={() => setShowDrawer(false)}>
|
||||
<Drawer closeOnMaskClick={false} onClose={() => setShowDrawer(false)}>
|
||||
<PromQail
|
||||
query={query}
|
||||
closeDrawer={() => setShowDrawer(false)}
|
||||
@ -98,20 +108,7 @@ export const PromQueryBuilder = React.memo<Props>((props) => {
|
||||
padding: '0 0 0 6px',
|
||||
})}
|
||||
>
|
||||
<Button
|
||||
variant={'secondary'}
|
||||
onClick={() => {
|
||||
reportInteraction('grafana_prometheus_promqail_ai_button_clicked', {
|
||||
metric: query.metric,
|
||||
});
|
||||
setShowDrawer(true);
|
||||
}}
|
||||
title={'Get query suggestions.'}
|
||||
disabled={!query.metric}
|
||||
>
|
||||
<img height={16} src={AI_Logo_color} alt="AI logo black and white" />
|
||||
{'\u00A0'}Get query suggestions
|
||||
</Button>
|
||||
<QueryAssistantButton llmAppEnabled={llmAppEnabled} metric={query.metric} setShowDrawer={setShowDrawer} />
|
||||
</div>
|
||||
)}
|
||||
<QueryBuilderHints<PromVisualQuery>
|
||||
|
@ -3,7 +3,7 @@ import React, { useEffect, useReducer, useRef, useState } from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { reportInteraction } from '@grafana/runtime';
|
||||
import { Button, Checkbox, Input, Spinner, useTheme2 } from '@grafana/ui';
|
||||
import { Alert, Button, Checkbox, Input, Spinner, useTheme2 } from '@grafana/ui';
|
||||
import store from 'app/core/store';
|
||||
|
||||
import { PrometheusDatasource } from '../../../datasource';
|
||||
@ -83,24 +83,30 @@ export const PromQail = (props: PromQailProps) => {
|
||||
</div>
|
||||
{state.showStartingMessage ? (
|
||||
<>
|
||||
<div className={styles.textPadding}>
|
||||
This assistant can suggest queries based on your use case and the metric you want to query
|
||||
</div>
|
||||
<div className={styles.textPadding}>
|
||||
The assistant will connect to OpenAI using your API key. The following information will be sent to OpenAI:
|
||||
</div>
|
||||
<div className={styles.dataList}>
|
||||
<ul>
|
||||
<li>Metrics</li>
|
||||
<li>Labels</li>
|
||||
<li>Metrics metadata</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className={styles.textPadding}>Check with OpenAI to understand how your data is being used.</div>
|
||||
<div>
|
||||
AI-suggested queries may not always be the right one for your use case. Always take a moment to understand
|
||||
the queries before using them.
|
||||
<ol>
|
||||
<li className={styles.textPadding}>
|
||||
Query Advisor suggests queries based on a metric and requests you type in.
|
||||
</li>
|
||||
<li className={styles.textPadding}>
|
||||
Query Advisor sends Prometheus metrics, labels and metadata to the LLM provider you've configured.
|
||||
Be sure to align its usage with your company's internal policies.
|
||||
</li>
|
||||
<li className={styles.textPadding}>
|
||||
An AI-suggested query may not fully answer your question. Always take a moment to understand a query
|
||||
before you use it.
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
<Alert
|
||||
title={''}
|
||||
severity={'info'}
|
||||
key={'promqail-llm-app'}
|
||||
className={cx(styles.textPadding, styles.noMargin)}
|
||||
>
|
||||
Query Advisor is currently in Private Preview. Feedback is appreciated and can be provided on explanations
|
||||
and suggestions.
|
||||
</Alert>
|
||||
|
||||
{/* don't show this message again, store in localstorage */}
|
||||
<div className={styles.textPadding}>
|
||||
@ -409,7 +415,7 @@ export const getStyles = (theme: GrafanaTheme2) => {
|
||||
marginRight: '10px',
|
||||
}),
|
||||
dataList: css({
|
||||
padding: '0px 28px 28px 28px',
|
||||
padding: '0px 28px 0px 28px',
|
||||
}),
|
||||
textPadding: css({
|
||||
paddingBottom: '12px',
|
||||
@ -538,6 +544,21 @@ export const getStyles = (theme: GrafanaTheme2) => {
|
||||
submitFeedback: css({
|
||||
padding: '16px 0',
|
||||
}),
|
||||
noMargin: css({
|
||||
margin: 0,
|
||||
}),
|
||||
enableButtonTooltip: css({
|
||||
padding: 8,
|
||||
}),
|
||||
enableButtonTooltipText: css({
|
||||
color: `${theme.colors.text.secondary}`,
|
||||
ul: {
|
||||
marginLeft: 16,
|
||||
},
|
||||
}),
|
||||
link: css({
|
||||
color: `${theme.colors.text.link} !important`,
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -0,0 +1,51 @@
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
|
||||
import { QueryAssistantButton } from './QueryAssistantButton';
|
||||
|
||||
const setShowDrawer = jest.fn(() => {});
|
||||
|
||||
describe('QueryAssistantButton', () => {
|
||||
it('renders the button', async () => {
|
||||
const props = createProps(true, 'metric', setShowDrawer);
|
||||
render(<QueryAssistantButton {...props} />);
|
||||
expect(screen.getByText('Get query suggestions')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows the LLM app disabled message when LLM app is not set up with vector DB', async () => {
|
||||
const props = createProps(false, 'metric', setShowDrawer);
|
||||
render(<QueryAssistantButton {...props} />);
|
||||
const button = screen.getByText('Get query suggestions');
|
||||
userEvent.hover(button);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Install and enable the LLM plugin')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('shows the message to select a metric when LLM is enabled and no metric is selected', async () => {
|
||||
const props = createProps(true, '', setShowDrawer);
|
||||
render(<QueryAssistantButton {...props} />);
|
||||
const button = screen.getByText('Get query suggestions');
|
||||
userEvent.hover(button);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('First, select a metric.')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('calls setShowDrawer when button is clicked', async () => {
|
||||
const props = createProps(true, 'metric', setShowDrawer);
|
||||
render(<QueryAssistantButton {...props} />);
|
||||
const button = screen.getByText('Get query suggestions');
|
||||
fireEvent.click(button);
|
||||
expect(setShowDrawer).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
function createProps(llmAppEnabled: boolean, metric: string, setShowDrawer: () => void) {
|
||||
return {
|
||||
llmAppEnabled,
|
||||
metric,
|
||||
setShowDrawer,
|
||||
};
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
import React from 'react';
|
||||
|
||||
import { reportInteraction } from '@grafana/runtime';
|
||||
import { Button, Tooltip, useTheme2 } from '@grafana/ui';
|
||||
|
||||
import { getStyles } from './PromQail';
|
||||
import AI_Logo_color from './resources/AI_Logo_color.svg';
|
||||
|
||||
export type Props = {
|
||||
llmAppEnabled: boolean;
|
||||
metric: string;
|
||||
setShowDrawer: (show: boolean) => void;
|
||||
};
|
||||
|
||||
export function QueryAssistantButton(props: Props) {
|
||||
const { llmAppEnabled, metric, setShowDrawer } = props;
|
||||
|
||||
const llmAppDisabled = !llmAppEnabled;
|
||||
const noMetricSelected = !metric;
|
||||
|
||||
const theme = useTheme2();
|
||||
const styles = getStyles(theme);
|
||||
|
||||
const button = () => {
|
||||
return (
|
||||
<Button
|
||||
variant={'secondary'}
|
||||
onClick={() => {
|
||||
reportInteraction('grafana_prometheus_promqail_ai_button_clicked', {
|
||||
metric: metric,
|
||||
});
|
||||
setShowDrawer(true);
|
||||
}}
|
||||
disabled={!metric || !llmAppEnabled}
|
||||
>
|
||||
<img height={16} src={AI_Logo_color} alt="AI logo black and white" />
|
||||
{'\u00A0'}Get query suggestions
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
const selectMetricMessage = (
|
||||
<Tooltip content={'First, select a metric.'} placement={'bottom-end'}>
|
||||
{button()}
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
const llmAppMessage = (
|
||||
<Tooltip
|
||||
interactive={true}
|
||||
placement={'auto-end'}
|
||||
content={
|
||||
<div className={styles.enableButtonTooltip}>
|
||||
<h6>Query Advisor is disabled</h6>
|
||||
<div className={styles.enableButtonTooltipText}>To enable Query Advisor you must:</div>
|
||||
<div className={styles.enableButtonTooltipText}>
|
||||
<ul>
|
||||
<li>
|
||||
<a
|
||||
href={'https://grafana.com/docs/grafana-cloud/alerting-and-irm/machine-learning/llm-plugin/'}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
className={styles.link}
|
||||
>
|
||||
Install and enable the LLM plugin
|
||||
</a>
|
||||
</li>
|
||||
<li>Select a metric</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
{button()}
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
if (llmAppDisabled) {
|
||||
return llmAppMessage;
|
||||
} else if (noMetricSelected) {
|
||||
return selectMetricMessage;
|
||||
} else {
|
||||
return button();
|
||||
}
|
||||
}
|
@ -31,7 +31,6 @@ export function QuerySuggestionContainer(props: Props) {
|
||||
|
||||
if (suggestionType === SuggestionType.Historical) {
|
||||
text = `Here are ${querySuggestions.length} query suggestions:`;
|
||||
secondaryText = 'These queries are based off of historical data (top used queries) for your metric.';
|
||||
refineText = 'I want to write a prompt';
|
||||
} else if (suggestionType === SuggestionType.AI) {
|
||||
text = text = 'Here is your query suggestion:';
|
||||
@ -42,8 +41,15 @@ export function QuerySuggestionContainer(props: Props) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={styles.textPadding}>{text}</div>
|
||||
<div className={cx(styles.secondaryText, styles.bottomMargin)}>{secondaryText}</div>
|
||||
{suggestionType === SuggestionType.Historical ? (
|
||||
<div className={styles.bottomMargin}>{text}</div>
|
||||
) : (
|
||||
<>
|
||||
<div className={styles.textPadding}>{text}</div>
|
||||
<div className={cx(styles.secondaryText, styles.bottomMargin)}>{secondaryText}</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className={styles.infoContainerWrapper}>
|
||||
<div className={styles.infoContainer}>
|
||||
{querySuggestions.map((qs: QuerySuggestion, idx: number) => {
|
||||
|
@ -107,11 +107,6 @@ export async function promQailExplain(
|
||||
suggIdx: number,
|
||||
datasource: PrometheusDatasource
|
||||
) {
|
||||
// const enabled = await llms.openai.enabled();
|
||||
// if (!enabled) {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
const suggestedQuery = interaction.suggestions[suggIdx].query;
|
||||
|
||||
const promptMessages = getExplainMessage(suggestedQuery, query.metric, datasource);
|
||||
@ -273,6 +268,7 @@ function guessMetricFamily(metric: string): string {
|
||||
|
||||
/**
|
||||
* Check if the LLM plugin is enabled.
|
||||
* Used in the PromQueryBuilder to enable/disable the button based on openai and vector db checks
|
||||
* @returns true if the LLM plugin is enabled.
|
||||
*/
|
||||
export async function isLLMPluginEnabled(): Promise<boolean> {
|
||||
@ -302,10 +298,6 @@ export async function promQailSuggest(
|
||||
datasource: PrometheusDatasource,
|
||||
interaction?: Interaction
|
||||
) {
|
||||
// when you're not running promqail
|
||||
// @ts-ignore llms types issue
|
||||
const check = await isLLMPluginEnabled();
|
||||
|
||||
const interactionToUpdate = interaction ? interaction : createInteraction(SuggestionType.Historical);
|
||||
|
||||
// Decide metric type
|
||||
@ -329,7 +321,7 @@ export async function promQailSuggest(
|
||||
metricType = guessMetricType(query.metric, datasource.languageProvider.metrics);
|
||||
}
|
||||
|
||||
if (!check || interactionToUpdate.suggestionType === SuggestionType.Historical) {
|
||||
if (interactionToUpdate.suggestionType === SuggestionType.Historical) {
|
||||
return new Promise<void>((resolve) => {
|
||||
return setTimeout(() => {
|
||||
const suggestions = getTemplateSuggestions(
|
||||
|
Loading…
Reference in New Issue
Block a user