diff --git a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilder.tsx b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilder.tsx index 884f196dc7f..00d0f830398 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilder.tsx +++ b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilder.tsx @@ -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) => { const { datasource, query, onChange, onRunQuery, data, showExplain } = props; const [highlightedOp, setHighlightedOp] = useState(); const [showDrawer, setShowDrawer] = useState(false); + const [llmAppEnabled, updateLlmAppEnabled] = useState(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 && ( - setShowDrawer(false)}> + setShowDrawer(false)}> setShowDrawer(false)} @@ -98,20 +108,7 @@ export const PromQueryBuilder = React.memo((props) => { padding: '0 0 0 6px', })} > - + )} diff --git a/public/app/plugins/datasource/prometheus/querybuilder/components/promQail/PromQail.tsx b/public/app/plugins/datasource/prometheus/querybuilder/components/promQail/PromQail.tsx index b2c331536ae..fc249e1b2b2 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/components/promQail/PromQail.tsx +++ b/public/app/plugins/datasource/prometheus/querybuilder/components/promQail/PromQail.tsx @@ -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) => { {state.showStartingMessage ? ( <> -
- This assistant can suggest queries based on your use case and the metric you want to query -
-
- The assistant will connect to OpenAI using your API key. The following information will be sent to OpenAI: -
-
    -
  • Metrics
  • -
  • Labels
  • -
  • Metrics metadata
  • -
-
-
Check with OpenAI to understand how your data is being used.
-
- 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. +
    +
  1. + Query Advisor suggests queries based on a metric and requests you type in. +
  2. +
  3. + 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. +
  4. +
  5. + An AI-suggested query may not fully answer your question. Always take a moment to understand a query + before you use it. +
  6. +
+ + Query Advisor is currently in Private Preview. Feedback is appreciated and can be provided on explanations + and suggestions. + {/* don't show this message again, store in localstorage */}
@@ -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`, + }), }; }; diff --git a/public/app/plugins/datasource/prometheus/querybuilder/components/promQail/QueryAssistantButton.test.tsx b/public/app/plugins/datasource/prometheus/querybuilder/components/promQail/QueryAssistantButton.test.tsx new file mode 100644 index 00000000000..d45180fa7b5 --- /dev/null +++ b/public/app/plugins/datasource/prometheus/querybuilder/components/promQail/QueryAssistantButton.test.tsx @@ -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(); + 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(); + 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(); + 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(); + 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, + }; +} diff --git a/public/app/plugins/datasource/prometheus/querybuilder/components/promQail/QueryAssistantButton.tsx b/public/app/plugins/datasource/prometheus/querybuilder/components/promQail/QueryAssistantButton.tsx new file mode 100644 index 00000000000..f7cb661efe7 --- /dev/null +++ b/public/app/plugins/datasource/prometheus/querybuilder/components/promQail/QueryAssistantButton.tsx @@ -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 ( + + ); + }; + + const selectMetricMessage = ( + + {button()} + + ); + + const llmAppMessage = ( + +
Query Advisor is disabled
+
To enable Query Advisor you must:
+
+ +
+
+ } + > + {button()} + + ); + + if (llmAppDisabled) { + return llmAppMessage; + } else if (noMetricSelected) { + return selectMetricMessage; + } else { + return button(); + } +} diff --git a/public/app/plugins/datasource/prometheus/querybuilder/components/promQail/QuerySuggestionContainer.tsx b/public/app/plugins/datasource/prometheus/querybuilder/components/promQail/QuerySuggestionContainer.tsx index 5eb48cb5a8b..1f3380cc739 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/components/promQail/QuerySuggestionContainer.tsx +++ b/public/app/plugins/datasource/prometheus/querybuilder/components/promQail/QuerySuggestionContainer.tsx @@ -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 ( <> -
{text}
-
{secondaryText}
+ {suggestionType === SuggestionType.Historical ? ( +
{text}
+ ) : ( + <> +
{text}
+
{secondaryText}
+ + )} +
{querySuggestions.map((qs: QuerySuggestion, idx: number) => { diff --git a/public/app/plugins/datasource/prometheus/querybuilder/components/promQail/state/helpers.ts b/public/app/plugins/datasource/prometheus/querybuilder/components/promQail/state/helpers.ts index 85a0c9aae5f..dd430cf85e2 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/components/promQail/state/helpers.ts +++ b/public/app/plugins/datasource/prometheus/querybuilder/components/promQail/state/helpers.ts @@ -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 { @@ -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((resolve) => { return setTimeout(() => { const suggestions = getTemplateSuggestions(