diff --git a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md index db79bea4a8f..3810ab99007 100644 --- a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md +++ b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md @@ -154,6 +154,7 @@ Experimental features might be changed or removed without prior notice. | `pluginsInstrumentationStatusSource` | Include a status source label for plugin request metrics and logs | | `costManagementUi` | Toggles the display of the cost management ui plugin | | `managedPluginsInstall` | Install managed plugins directly from plugins catalog | +| `prometheusPromQAIL` | Prometheus and AI/ML to assist users in creating a query | ## Development feature toggles diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 9c20e806721..e48bb5d8ff7 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -148,4 +148,5 @@ export interface FeatureToggles { pluginsInstrumentationStatusSource?: boolean; costManagementUi?: boolean; managedPluginsInstall?: boolean; + prometheusPromQAIL?: boolean; } diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index ac41ee33222..cc21c71bbe7 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -911,5 +911,12 @@ var ( RequiresDevMode: false, Owner: grafanaPluginsPlatformSquad, }, + { + Name: "prometheusPromQAIL", + Description: "Prometheus and AI/ML to assist users in creating a query", + Stage: FeatureStageExperimental, + FrontendOnly: true, + Owner: grafanaObservabilityMetricsSquad, + }, } ) diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index 777166d5c28..45ad58008bf 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -129,3 +129,4 @@ panelTitleSearchInV1,experimental,@grafana/backend-platform,true,false,false,fal pluginsInstrumentationStatusSource,experimental,@grafana/plugins-platform-backend,false,false,false,false costManagementUi,experimental,@grafana/databases-frontend,false,false,false,false managedPluginsInstall,experimental,@grafana/plugins-platform-backend,false,false,false,false +prometheusPromQAIL,experimental,@grafana/observability-metrics,false,false,false,true diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index 237018d2a32..7cb380e9015 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -526,4 +526,8 @@ const ( // FlagManagedPluginsInstall // Install managed plugins directly from plugins catalog FlagManagedPluginsInstall = "managedPluginsInstall" + + // FlagPrometheusPromQAIL + // Prometheus and AI/ML to assist users in creating a query + FlagPrometheusPromQAIL = "prometheusPromQAIL" ) diff --git a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilder.tsx b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilder.tsx index 49183bf7940..fbb17cd8d9f 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilder.tsx +++ b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilder.tsx @@ -1,7 +1,10 @@ +import { css } from '@emotion/css'; import React, { useState } from 'react'; import { DataSourceApi, PanelData } from '@grafana/data'; import { EditorRow } from '@grafana/experimental'; +import { config } from '@grafana/runtime'; +import { Button, Drawer } from '@grafana/ui'; import { PrometheusDatasource } from '../../datasource'; import promqlGrammar from '../../promql'; @@ -19,6 +22,8 @@ import { PromVisualQuery } from '../types'; 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'; export interface Props { query: PromVisualQuery; @@ -29,9 +34,14 @@ export interface Props { showExplain: boolean; } +// initial commit for hackathon-2023-08-promqail +// AI/ML + Prometheus +const prometheusPromQAIL = config.featureToggles.prometheusPromQAIL; + export const PromQueryBuilder = React.memo((props) => { const { datasource, query, onChange, onRunQuery, data, showExplain } = props; const [highlightedOp, setHighlightedOp] = useState(); + const [showDrawer, setShowDrawer] = useState(false); const lang = { grammar: promqlGrammar, name: 'promql' }; @@ -39,6 +49,16 @@ export const PromQueryBuilder = React.memo((props) => { return ( <> + {prometheusPromQAIL && showDrawer && ( + setShowDrawer(false)}> + setShowDrawer(false)} + onChange={onChange} + datasource={datasource} + /> + + )} @@ -72,6 +92,25 @@ export const PromQueryBuilder = React.memo((props) => { onRunQuery={onRunQuery} highlightedOp={highlightedOp} /> + {prometheusPromQAIL && ( +
+ +
+ )} datasource={datasource} query={query} diff --git a/public/app/plugins/datasource/prometheus/querybuilder/components/promQail/PromQail.test.tsx b/public/app/plugins/datasource/prometheus/querybuilder/components/promQail/PromQail.test.tsx new file mode 100644 index 00000000000..47d2c09fe34 --- /dev/null +++ b/public/app/plugins/datasource/prometheus/querybuilder/components/promQail/PromQail.test.tsx @@ -0,0 +1,149 @@ +import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React from 'react'; + +import { DataSourceInstanceSettings, DataSourcePluginMeta } from '@grafana/data'; + +import { PrometheusDatasource } from '../../../datasource'; +import PromQlLanguageProvider from '../../../language_provider'; +import { EmptyLanguageProviderMock } from '../../../language_provider.mock'; +import { PromOptions } from '../../../types'; +import { PromVisualQuery } from '../../types'; + +import { PromQail, testIds } from './PromQail'; + +// don't care about interaction tracking in our unit tests +jest.mock('@grafana/runtime', () => ({ + ...jest.requireActual('@grafana/runtime'), + reportInteraction: jest.fn(), +})); + +window.HTMLElement.prototype.scrollIntoView = jest.fn(); + +describe('PromQail', () => { + it('renders the drawer', async () => { + setup(defaultQuery); + await waitFor(() => { + expect(screen.getByText('Query advisor')).toBeInTheDocument(); + }); + }); + + it('shows an option to not show security warning', async () => { + setup(defaultQuery); + await waitFor(() => { + expect(screen.getByText("Don't show this message again")).toBeInTheDocument(); + }); + }); + + it('shows selected metric and asks for a prompt', async () => { + setup(defaultQuery); + + clickSecurityButton(); + + await waitFor(() => { + expect(screen.getByText('random_metric')).toBeInTheDocument(); + expect(screen.getByText('Do you know what you want to query?')).toBeInTheDocument(); + }); + }); + + it('displays a prompt when the user knows what they want to query', async () => { + setup(defaultQuery); + + clickSecurityButton(); + + await waitFor(() => { + expect(screen.getByText('random_metric')).toBeInTheDocument(); + expect(screen.getByText('Do you know what you want to query?')).toBeInTheDocument(); + }); + + const aiPrompt = screen.getByTestId(testIds.clickForAi); + + userEvent.click(aiPrompt); + + await waitFor(() => { + expect(screen.getByText('What kind of data do you want to see with your metric?')).toBeInTheDocument(); + }); + }); + + it('does not display a prompt when choosing historical', async () => { + setup(defaultQuery); + + clickSecurityButton(); + + await waitFor(() => { + expect(screen.getByText('random_metric')).toBeInTheDocument(); + expect(screen.getByText('Do you know what you want to query?')).toBeInTheDocument(); + }); + + const historicalPrompt = screen.getByTestId(testIds.clickForHistorical); + + userEvent.click(historicalPrompt); + + await waitFor(() => { + expect(screen.queryByText('What kind of data do you want to see with your metric?')).toBeNull(); + }); + }); +}); + +const defaultQuery: PromVisualQuery = { + metric: 'random_metric', + labels: [], + operations: [], +}; + +function createDatasource(withLabels?: boolean) { + const languageProvider = new EmptyLanguageProviderMock() as unknown as PromQlLanguageProvider; + + languageProvider.metricsMetadata = { + 'all-metrics': { + type: 'all-metrics-type', + help: 'all-metrics-help', + }, + a: { + type: 'counter', + help: 'a-metric-help', + }, + a_bucket: { + type: 'counter', + help: 'for functions', + }, + }; + + const datasource = new PrometheusDatasource( + { + url: '', + jsonData: {}, + meta: {} as DataSourcePluginMeta, + } as DataSourceInstanceSettings, + undefined, + undefined, + languageProvider + ); + return datasource; +} + +function createProps(query: PromVisualQuery, datasource: PrometheusDatasource) { + return { + datasource, + onChange: jest.fn(), + closeDrawer: jest.fn(), + query: query, + }; +} + +function setup(query: PromVisualQuery) { + const withLabels: boolean = query.labels.length > 0; + const datasource = createDatasource(withLabels); + const props = createProps(query, datasource); + + // render the drawer only + const { container } = render(); + + return container; +} + +function clickSecurityButton() { + const securityInfoButton = screen.getByTestId(testIds.securityInfoButton); + + userEvent.click(securityInfoButton); +} diff --git a/public/app/plugins/datasource/prometheus/querybuilder/components/promQail/PromQail.tsx b/public/app/plugins/datasource/prometheus/querybuilder/components/promQail/PromQail.tsx new file mode 100644 index 00000000000..cae32b6baf1 --- /dev/null +++ b/public/app/plugins/datasource/prometheus/querybuilder/components/promQail/PromQail.tsx @@ -0,0 +1,534 @@ +import { css, cx } from '@emotion/css'; +import React, { useEffect, useReducer, useRef, useState } from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { Button, Checkbox, Input, Spinner, useTheme2 } from '@grafana/ui'; +import store from 'app/core/store'; + +import { PrometheusDatasource } from '../../../datasource'; +import { PromVisualQuery } from '../../types'; + +import { QuerySuggestionContainer } from './QuerySuggestionContainer'; +// @ts-ignore until we can get these added for icons +import AI_Logo_color from './resources/AI_Logo_color.svg'; +import { promQailExplain, promQailSuggest } from './state/helpers'; +import { initialState, stateSlice } from './state/state'; +import { Interaction, SuggestionType } from './types'; + +// actions to update the state +const { showStartingMessage, indicateCheckbox, addInteraction, updateInteraction } = stateSlice.actions; + +export type PromQailProps = { + query: PromVisualQuery; + closeDrawer: () => void; + onChange: (query: PromVisualQuery) => void; + datasource: PrometheusDatasource; +}; + +const SKIP_STARTING_MESSAGE = 'SKIP_STARTING_MESSAGE'; + +export const PromQail = (props: PromQailProps) => { + const { query, closeDrawer, onChange, datasource } = props; + const skipStartingMessage = store.getBool(SKIP_STARTING_MESSAGE, false); + + const [state, dispatch] = useReducer(stateSlice.reducer, initialState(query, !skipStartingMessage)); + + const [labelNames, setLabelNames] = useState([]); + + const suggestions = state.interactions.reduce((acc, int) => acc + int.suggestions.length, 0); + + const responsesEndRef = useRef(null); + + const scrollToBottom = () => { + if (responsesEndRef) { + // @ts-ignore for React.MutableRefObject + responsesEndRef?.current?.scrollIntoView({ behavior: 'smooth' }); + } + }; + + useEffect(() => { + // only scroll when an interaction has been added or the suggestions have been updated + scrollToBottom(); + }, [state.interactions.length, suggestions]); + + useEffect(() => { + const fetchLabels = async () => { + let labelsIndex: Record; + if (datasource.hasLabelsMatchAPISupport()) { + labelsIndex = await datasource.languageProvider.fetchSeriesLabelsMatch(query.metric); + } else { + labelsIndex = await datasource.languageProvider.fetchSeriesLabels(query.metric); + } + setLabelNames(Object.keys(labelsIndex)); + }; + fetchLabels(); + }, [query, datasource]); + + const theme = useTheme2(); + const styles = getStyles(theme); + + return ( +
+ {/* Query Advisor */} + {/* header */} +
+

Query advisor

+
+ {/* Starting message */} +
+
+ AI logo color Assistant +
+ {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. +
+ + {/* don't show this message again, store in localstorage */} +
+ { + const val = store.getBool(SKIP_STARTING_MESSAGE, false); + store.set(SKIP_STARTING_MESSAGE, !val); + dispatch(indicateCheckbox(!val)); + }} + label="Don't show this message again" + /> +
+
+
+ + +
+
+ + ) : ( +
+ {/* MAKE THIS TABLE RESPONSIVE */} + {/* FIT SUPER LONG METRICS AND LABELS IN HERE */} +
Here is the metric you have selected:
+
+
+ + + + + + + + {state.query.labels.map((label, idx) => { + const text = idx === 0 ? 'labels' : ''; + return ( + + + + + + ); + })} + +
metric{state.query.metric} + +
{text}{`${label.label}${label.op}${label.value}`}
+
+
+ + {/* Ask if you know what you want to query? */} + {!state.askForQueryHelp && state.interactions.length === 0 && ( + <> +
Do you know what you want to query?
+
+
+ + +
+
+ + )} + + {state.interactions.map((interaction: Interaction, idx: number) => { + return ( +
+ {interaction.suggestionType === SuggestionType.AI ? ( + <> +
What kind of data do you want to see with your metric?
+
+
You do not need to enter in a metric or a label again in the prompt.
+
Example: I want to monitor request latency, not errors.
+
+
+ 0} + onChange={(e) => { + const prompt = e.currentTarget.value; + + const payload = { + idx: idx, + interaction: { ...interaction, prompt }, + }; + + dispatch(updateInteraction(payload)); + }} + /> +
+ {interaction.suggestions.length === 0 ? ( + interaction.isLoading ? ( + <> +
+ Waiting for OpenAI +
+ + ) : ( + <> +
+
+ + + +
+
+ + ) + ) : ( + // LIST OF SUGGESTED QUERIES FROM AI + { + const isLoading = false; + const suggestionType = SuggestionType.AI; + dispatch(addInteraction({ suggestionType, isLoading })); + }} + queryExplain={(suggIdx: number) => + interaction.suggestions[suggIdx].explanation === '' + ? promQailExplain(dispatch, idx, query, interaction, suggIdx, datasource) + : interaction.suggestions[suggIdx].explanation + } + onChange={onChange} + prompt={interaction.prompt ?? ''} + /> + )} + + ) : // HISTORICAL SUGGESTIONS + interaction.isLoading ? ( + <> +
+ Waiting for OpenAI +
+ + ) : ( + // LIST OF SUGGESTED QUERIES FROM HISTORICAL DATA + { + const isLoading = false; + const suggestionType = SuggestionType.AI; + dispatch(addInteraction({ suggestionType, isLoading })); + }} + queryExplain={(suggIdx: number) => + interaction.suggestions[suggIdx].explanation === '' + ? promQailExplain(dispatch, idx, query, interaction, suggIdx, datasource) + : interaction.suggestions[suggIdx].explanation + } + onChange={onChange} + prompt={interaction.prompt ?? ''} + /> + )} +
+ ); + })} +
+ )} +
+
+
+ ); +}; + +export const getStyles = (theme: GrafanaTheme2) => { + return { + sectionPadding: css({ + padding: '20px', + }), + header: css({ + display: 'flex', + + button: { + marginLeft: 'auto', + }, + }), + iconSection: css({ + padding: '0 0 10px 0', + color: `${theme.colors.text.secondary}`, + + img: { + paddingRight: '4px', + }, + }), + rightButtonsWrapper: css({ + display: 'flex', + }), + rightButtons: css({ + marginLeft: 'auto', + }), + leftButton: css({ + marginRight: '10px', + }), + dataList: css({ + padding: '0px 28px 28px 28px', + }), + textPadding: css({ + paddingBottom: '12px', + }), + containerPadding: css({ + padding: '28px', + }), + infoContainer: css({ + border: `${theme.colors.border.strong}`, + padding: '16px', + backgroundColor: `${theme.colors.background.secondary}`, + borderRadius: `8px`, + borderBottomLeftRadius: 0, + }), + infoContainerWrapper: css({ + paddingBottom: '24px', + }), + metricTable: css({ + width: '100%', + }), + metricTableName: css({ + width: '15%', + }), + metricTableValue: css({ + fontFamily: `${theme.typography.fontFamilyMonospace}`, + fontSize: `${theme.typography.bodySmall.fontSize}`, + overflow: 'scroll', + textWrap: 'nowrap', + maxWidth: '150px', + width: '60%', + maskImage: `linear-gradient(to right, rgba(0, 0, 0, 1) 90%, rgba(0, 0, 0, 0))`, + }), + metricTableButton: css({ + float: 'right', + }), + queryQuestion: css({ + textAlign: 'end', + padding: '8px 0', + }), + secondaryText: css({ + color: `${theme.colors.text.secondary}`, + }), + loadingMessageContainer: css({ + border: `${theme.colors.border.strong}`, + padding: `16px`, + backgroundColor: `${theme.colors.background.secondary}`, + marginBottom: `20px`, + borderRadius: `8px`, + color: `${theme.colors.text.secondary}`, + fontStyle: 'italic', + }), + floatRight: css({ + float: 'right', + }), + codeText: css({ + fontFamily: `${theme.typography.fontFamilyMonospace}`, + fontSize: `${theme.typography.bodySmall.fontSize}`, + }), + bodySmall: css({ + fontSize: `${theme.typography.bodySmall.fontSize}`, + }), + explainPadding: css({ + paddingLeft: '26px', + }), + bottomMargin: css({ + marginBottom: '20px', + }), + topPadding: css({ + paddingTop: '22px', + }), + doc: css({ + textDecoration: 'underline', + }), + afterButtons: css({ + display: 'flex', + justifyContent: 'flex-end', + }), + feedbackStyle: css({ + margin: 0, + textAlign: 'right', + paddingTop: '22px', + paddingBottom: '22px', + }), + nextInteractionHeight: css({ + height: '88px', + }), + center: css({ + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + }), + inputPadding: css({ + paddingBottom: '24px', + }), + querySuggestion: css({ + display: 'flex', + flexWrap: 'nowrap', + }), + longCode: css({ + width: '90%', + textWrap: 'nowrap', + overflow: 'scroll', + maskImage: `linear-gradient(to right, rgba(0, 0, 0, 1) 90%, rgba(0, 0, 0, 0))`, + + div: { + display: 'inline-block', + }, + }), + useButton: css({ + marginLeft: 'auto', + }), + suggestionFeedback: css({ + textAlign: 'left', + }), + feedbackQuestion: css({ + display: 'flex', + padding: '8px 0px', + h6: { marginBottom: 0 }, + i: { + marginTop: '1px', + }, + }), + explationTextInput: css({ + paddingLeft: '24px', + }), + submitFeedback: css({ + padding: '16px 0', + }), + }; +}; + +export const testIds = { + promQail: 'prom-qail', + securityInfoButton: 'security-info-button', + clickForHistorical: 'click-for-historical', + clickForAi: 'click-for-ai', + submitPrompt: 'submit-prompt', + refinePrompt: 'refine-prompt', +}; diff --git a/public/app/plugins/datasource/prometheus/querybuilder/components/promQail/QuerySuggestionContainer.tsx b/public/app/plugins/datasource/prometheus/querybuilder/components/promQail/QuerySuggestionContainer.tsx new file mode 100644 index 00000000000..5eb48cb5a8b --- /dev/null +++ b/public/app/plugins/datasource/prometheus/querybuilder/components/promQail/QuerySuggestionContainer.tsx @@ -0,0 +1,95 @@ +import { cx } from '@emotion/css'; +import React, { useState } from 'react'; + +import { Button, useTheme2 } from '@grafana/ui'; + +import { PromVisualQuery } from '../../types'; + +import { getStyles, testIds } from './PromQail'; +import { QuerySuggestionItem } from './QuerySuggestionItem'; +import { QuerySuggestion, SuggestionType } from './types'; + +export type Props = { + querySuggestions: QuerySuggestion[]; + suggestionType: SuggestionType; + closeDrawer: () => void; + nextInteraction: () => void; + queryExplain: (idx: number) => void; + onChange: (query: PromVisualQuery) => void; + prompt: string; +}; + +export function QuerySuggestionContainer(props: Props) { + const { suggestionType, querySuggestions, closeDrawer, nextInteraction, queryExplain, onChange, prompt } = props; + + const [hasNextInteraction, updateHasNextInteraction] = useState(false); + + const theme = useTheme2(); + const styles = getStyles(theme); + + let text, secondaryText, refineText; + + 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:'; + secondaryText = + 'This query is based off of natural language descriptions of the most commonly used PromQL queries.'; + refineText = 'Refine prompt'; + } + + return ( + <> +
{text}
+
{secondaryText}
+
+
+ {querySuggestions.map((qs: QuerySuggestion, idx: number) => { + return ( + { + return acc + '$$' + qs.query; + }, '')} + prompt={prompt ?? ''} + /> + ); + })} +
+
+ {!hasNextInteraction && ( +
+
+ +
+
+ +
+
+ )} + + ); +} diff --git a/public/app/plugins/datasource/prometheus/querybuilder/components/promQail/QuerySuggestionItem.tsx b/public/app/plugins/datasource/prometheus/querybuilder/components/promQail/QuerySuggestionItem.tsx new file mode 100644 index 00000000000..aa91d00b15e --- /dev/null +++ b/public/app/plugins/datasource/prometheus/querybuilder/components/promQail/QuerySuggestionItem.tsx @@ -0,0 +1,318 @@ +import { cx } from '@emotion/css'; +import React, { FormEvent, useState } from 'react'; + +import { SelectableValue } from '@grafana/data'; +import { reportInteraction } from '@grafana/runtime'; +import { Button, RadioButtonList, Spinner, TextArea, Toggletip, useTheme2 } from '@grafana/ui'; + +import { buildVisualQueryFromString } from '../../parsing'; +import { PromVisualQuery } from '../../types'; + +import { getStyles } from './PromQail'; +import { QuerySuggestion } from './types'; + +export type Props = { + querySuggestion: QuerySuggestion; + order: number; + queryExplain: (idx: number) => void; + historical: boolean; + onChange: (query: PromVisualQuery) => void; + closeDrawer: () => void; + last: boolean; + prompt: string; + allSuggestions: string | undefined; +}; + +const suggestionOptions: SelectableValue[] = [ + { label: 'Yes', value: 'yes' }, + { label: 'No', value: 'no' }, +]; +const explationOptions: SelectableValue[] = [ + { label: 'Too vague', value: 'too vague' }, + { label: 'Too technical', value: 'too technical' }, + { label: 'Inaccurate', value: 'inaccurate' }, + { label: 'Other', value: 'other' }, +]; + +export function QuerySuggestionItem(props: Props) { + const { querySuggestion, order, queryExplain, historical, onChange, closeDrawer, last, allSuggestions, prompt } = + props; + const [showExp, updShowExp] = useState(false); + + const [gaveExplanationFeedback, updateGaveExplanationFeedback] = useState(false); + const [gaveSuggestionFeedback, updateGaveSuggestionFeedback] = useState(false); + + const [suggestionFeedback, setSuggestionFeedback] = useState({ + radioInput: '', + text: '', + }); + + const [explanationFeedback, setExplanationFeedback] = useState({ + radioInput: '', + text: '', + }); + + const theme = useTheme2(); + const styles = getStyles(theme); + + const { query, explanation } = querySuggestion; + + const feedbackToggleTip = (type: string) => { + const updateRadioFeedback = (value: string) => { + if (type === 'explanation') { + setExplanationFeedback({ + ...explanationFeedback, + radioInput: value, + }); + } else { + setSuggestionFeedback({ + ...suggestionFeedback, + radioInput: value, + }); + } + }; + + const updateTextFeedback = (e: FormEvent) => { + if (type === 'explanation') { + setExplanationFeedback({ + ...explanationFeedback, + text: e.currentTarget.value, + }); + } else { + setSuggestionFeedback({ + ...suggestionFeedback, + text: e.currentTarget.value, + }); + } + }; + + const disabledButton = () => + type === 'explanation' ? !explanationFeedback.radioInput : !suggestionFeedback.radioInput; + + const questionOne = + type === 'explanation' ? 'Why was the explanation not helpful?' : 'Were the query suggestions helpful?'; + + return ( +
+
+
+
{questionOne}
+ (Required) +
+ +
+
+ {type !== 'explanation' && ( +
+
How can we improve the query suggestions?
+
+ )} +