feat(query row): add query with Assistant action for Loki and Prometheus (#116275)

* feat(query): query with Assistant action

* Restyle and add feature toggle

* Add test

* Update

* use module mapper

* Translations

* Use feature flag config

* Improve naming of props

* Remove queryWithAssistant feature toggle

* Remove feature toggle

* Restore queryWithAssistant feature toggle

* Add Expression property to queryWithAssistant feature toggle

* Bring back feature toggle

* lint
This commit is contained in:
Ivana Huckova 2026-01-19 16:33:24 +01:00 committed by GitHub
parent a6d3fcebcc
commit 74a3d8b0d2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 358 additions and 10 deletions

View file

@ -73,6 +73,8 @@ module.exports = {
// prevent systemjs amd extra from breaking tests.
'systemjs/dist/extras/amd': '<rootDir>/public/test/mocks/systemjsAMDExtra.ts',
'@bsull/augurs': '<rootDir>/public/test/mocks/augurs.ts',
// Mock @grafana/assistant to prevent initialization errors in tests
'^@grafana/assistant$': '<rootDir>/public/test/mocks/assistant.ts',
},
// Log the test results with dynamic Loki tags. Drone CI only
reporters: ['default', ['<rootDir>/public/test/log-reporter.js', { enable: process.env.DRONE === 'true' }]],

View file

@ -1420,4 +1420,9 @@ export interface FeatureToggles {
* @default false
*/
alertingSyncDispatchTimer?: boolean;
/**
* Enables the Query with Assistant button in the query editor
* @default false
*/
queryWithAssistant?: boolean;
}

View file

@ -2240,6 +2240,14 @@ var (
HideFromDocs: true,
Expression: "false",
},
{
Name: "queryWithAssistant",
Description: "Enables the Query with Assistant button in the query editor",
Stage: FeatureStageExperimental,
FrontendOnly: true,
Owner: grafanaOSSBigTent,
Expression: "false",
},
}
)

View file

@ -46,7 +46,7 @@ Created,Name,Stage,Owner,requiresDevMode,RequiresRestart,FrontendOnly
2023-09-28,externalServiceAccounts,preview,@grafana/identity-access-team,false,false,false
2023-10-03,enableNativeHTTPHistogram,experimental,@grafana/grafana-backend-services-squad,false,true,false
2024-06-18,disableClassicHTTPHistogram,experimental,@grafana/grafana-backend-services-squad,false,true,false
2023-12-06,kubernetesSnapshots,experimental,@grafana/grafana-app-platform-squad,false,true,false
2023-12-05,kubernetesSnapshots,experimental,@grafana/grafana-app-platform-squad,false,true,false
2025-06-26,kubernetesLibraryPanels,experimental,@grafana/grafana-app-platform-squad,false,true,false
2024-06-05,kubernetesDashboards,GA,@grafana/dashboards-squad,false,false,false
2025-08-01,kubernetesShortURLs,experimental,@grafana/grafana-app-platform-squad,false,true,false
@ -54,7 +54,7 @@ Created,Name,Stage,Owner,requiresDevMode,RequiresRestart,FrontendOnly
2025-08-01,kubernetesAlertingRules,experimental,@grafana/alerting-squad,false,true,false
2025-08-29,kubernetesCorrelations,experimental,@grafana/datapro,false,true,false
2025-12-09,kubernetesUnifiedStorageQuotas,experimental,@grafana/search-and-storage,false,true,false
2025-10-17,kubernetesLogsDrilldown,experimental,@grafana/observability-logs,false,true,false
2025-10-16,kubernetesLogsDrilldown,experimental,@grafana/observability-logs,false,true,false
2025-10-20,kubernetesQueryCaching,experimental,@grafana/grafana-operator-experience-squad,false,true,false
2025-04-11,dashboardDisableSchemaValidationV1,experimental,@grafana/grafana-app-platform-squad,false,false,false
2025-04-11,dashboardDisableSchemaValidationV2,experimental,@grafana/grafana-app-platform-squad,false,false,false
@ -100,8 +100,8 @@ Created,Name,Stage,Owner,requiresDevMode,RequiresRestart,FrontendOnly
2025-07-31,useScopeSingleNodeEndpoint,experimental,@grafana/grafana-operator-experience-squad,false,false,true
2025-08-29,useMultipleScopeNodesEndpoint,experimental,@grafana/grafana-operator-experience-squad,false,false,true
2024-11-11,logQLScope,privatePreview,@grafana/oss-big-tent,false,false,false
2024-02-28,sqlExpressions,preview,@grafana/grafana-datasources-core-services,false,false,false
2025-07-24,sqlExpressionsColumnAutoComplete,experimental,@grafana/datapro,false,false,true
2024-02-27,sqlExpressions,preview,@grafana/grafana-datasources-core-services,false,false,false
2025-07-23,sqlExpressionsColumnAutoComplete,experimental,@grafana/datapro,false,false,true
2024-02-12,kubernetesAggregator,experimental,@grafana/grafana-app-platform-squad,false,true,false
2025-05-15,kubernetesAggregatorCapTokenAuth,experimental,@grafana/grafana-app-platform-squad,false,true,false
2024-02-14,groupByVariable,experimental,@grafana/dashboards-squad,false,false,false
@ -151,11 +151,11 @@ Created,Name,Stage,Owner,requiresDevMode,RequiresRestart,FrontendOnly
2024-10-17,unifiedStorageBigObjectsSupport,experimental,@grafana/search-and-storage,false,false,false
2024-10-22,timeRangeProvider,experimental,@grafana/grafana-frontend-platform,false,false,false
2025-11-05,timeRangePan,experimental,@grafana/dataviz-squad,false,false,true
2025-11-21,newTimeRangeZoomShortcuts,experimental,@grafana/dataviz-squad,false,false,true
2025-11-20,newTimeRangeZoomShortcuts,experimental,@grafana/dataviz-squad,false,false,true
2024-10-24,azureMonitorDisableLogLimit,GA,@grafana/partner-datasources,false,false,false
2024-12-20,playlistsReconciler,experimental,@grafana/grafana-app-platform-squad,false,true,false
2024-11-14,passwordlessMagicLinkAuthentication,experimental,@grafana/identity-access-team,false,false,false
2024-12-19,prometheusSpecialCharsInLabelValues,experimental,@grafana/oss-big-tent,false,false,true
2024-12-18,prometheusSpecialCharsInLabelValues,experimental,@grafana/oss-big-tent,false,false,true
2024-11-05,enableExtensionsAdminPage,experimental,@grafana/plugins-platform-backend,false,true,false
2024-11-07,enableSCIM,preview,@grafana/identity-access-team,false,false,false
2024-11-12,crashDetection,experimental,@grafana/observability-traces-and-profiling,false,false,true
@ -170,7 +170,7 @@ Created,Name,Stage,Owner,requiresDevMode,RequiresRestart,FrontendOnly
2025-07-16,alertingAIAnalyzeCentralStateHistory,experimental,@grafana/alerting-squad,false,false,false
2024-11-22,alertingNotificationsStepMode,GA,@grafana/alerting-squad,false,false,true
2024-12-19,unifiedStorageSearchUI,experimental,@grafana/search-and-storage,false,false,false
2024-12-13,elasticsearchCrossClusterSearch,GA,@grafana/partner-datasources,false,false,false
2024-12-12,elasticsearchCrossClusterSearch,GA,@grafana/partner-datasources,false,false,false
2024-12-13,lokiLabelNamesQueryApi,GA,@grafana/oss-big-tent,false,false,false
2024-12-27,k8SFolderCounts,experimental,@grafana/search-and-storage,false,false,false
2025-01-09,improvedExternalSessionHandlingSAML,GA,@grafana/identity-access-team,false,false,false
@ -227,7 +227,7 @@ Created,Name,Stage,Owner,requiresDevMode,RequiresRestart,FrontendOnly
2025-08-01,alertEnrichmentConditional,experimental,@grafana/alerting-squad,false,false,false
2025-06-10,alertingImportAlertmanagerAPI,experimental,@grafana/alerting-squad,false,false,false
2025-08-01,alertingImportAlertmanagerUI,experimental,@grafana/alerting-squad,false,false,false
2025-07-16,sharingDashboardImage,GA,@grafana/sharing-squad,false,false,true
2025-07-15,sharingDashboardImage,GA,@grafana/sharing-squad,false,false,true
2025-06-17,preferLibraryPanelTitle,privatePreview,@grafana/dashboards-squad,false,false,false
2025-06-24,tabularNumbers,GA,@grafana/grafana-frontend-platform,false,false,false
2025-06-25,newInfluxDSConfigPageDesign,privatePreview,@grafana/partner-datasources,false,false,false
@ -256,7 +256,7 @@ Created,Name,Stage,Owner,requiresDevMode,RequiresRestart,FrontendOnly
2025-10-20,newGauge,preview,@grafana/dataviz-squad,false,false,true
2025-11-12,newVizSuggestions,preview,@grafana/dataviz-squad,false,false,true
2025-12-02,externalVizSuggestions,experimental,@grafana/dataviz-squad,false,false,true
2025-12-19,heatmapRowsAxisOptions,experimental,@grafana/dataviz-squad,false,false,true
2025-12-18,heatmapRowsAxisOptions,experimental,@grafana/dataviz-squad,false,false,true
2025-10-17,preventPanelChromeOverflow,preview,@grafana/grafana-frontend-platform,false,false,true
2025-10-31,jaegerEnableGrpcEndpoint,experimental,@grafana/oss-big-tent,false,false,false
2025-10-17,pluginStoreServiceLoading,experimental,@grafana/plugins-platform-backend,false,false,false
@ -279,3 +279,4 @@ Created,Name,Stage,Owner,requiresDevMode,RequiresRestart,FrontendOnly
2026-01-06,secretsManagementAppPlatformAwsKeeper,experimental,@grafana/grafana-operator-experience-squad,false,false,false
2026-01-07,profilesExemplars,experimental,@grafana/observability-traces-and-profiling,false,false,false
2026-01-14,alertingSyncDispatchTimer,experimental,@grafana/alerting-squad,false,true,false
2026-01-15,queryWithAssistant,experimental,@grafana/oss-big-tent,false,false,true

1 Created Name Stage Owner requiresDevMode RequiresRestart FrontendOnly
46 2023-09-28 externalServiceAccounts preview @grafana/identity-access-team false false false
47 2023-10-03 enableNativeHTTPHistogram experimental @grafana/grafana-backend-services-squad false true false
48 2024-06-18 disableClassicHTTPHistogram experimental @grafana/grafana-backend-services-squad false true false
49 2023-12-06 2023-12-05 kubernetesSnapshots experimental @grafana/grafana-app-platform-squad false true false
50 2025-06-26 kubernetesLibraryPanels experimental @grafana/grafana-app-platform-squad false true false
51 2024-06-05 kubernetesDashboards GA @grafana/dashboards-squad false false false
52 2025-08-01 kubernetesShortURLs experimental @grafana/grafana-app-platform-squad false true false
54 2025-08-01 kubernetesAlertingRules experimental @grafana/alerting-squad false true false
55 2025-08-29 kubernetesCorrelations experimental @grafana/datapro false true false
56 2025-12-09 kubernetesUnifiedStorageQuotas experimental @grafana/search-and-storage false true false
57 2025-10-17 2025-10-16 kubernetesLogsDrilldown experimental @grafana/observability-logs false true false
58 2025-10-20 kubernetesQueryCaching experimental @grafana/grafana-operator-experience-squad false true false
59 2025-04-11 dashboardDisableSchemaValidationV1 experimental @grafana/grafana-app-platform-squad false false false
60 2025-04-11 dashboardDisableSchemaValidationV2 experimental @grafana/grafana-app-platform-squad false false false
100 2025-07-31 useScopeSingleNodeEndpoint experimental @grafana/grafana-operator-experience-squad false false true
101 2025-08-29 useMultipleScopeNodesEndpoint experimental @grafana/grafana-operator-experience-squad false false true
102 2024-11-11 logQLScope privatePreview @grafana/oss-big-tent false false false
103 2024-02-28 2024-02-27 sqlExpressions preview @grafana/grafana-datasources-core-services false false false
104 2025-07-24 2025-07-23 sqlExpressionsColumnAutoComplete experimental @grafana/datapro false false true
105 2024-02-12 kubernetesAggregator experimental @grafana/grafana-app-platform-squad false true false
106 2025-05-15 kubernetesAggregatorCapTokenAuth experimental @grafana/grafana-app-platform-squad false true false
107 2024-02-14 groupByVariable experimental @grafana/dashboards-squad false false false
151 2024-10-17 unifiedStorageBigObjectsSupport experimental @grafana/search-and-storage false false false
152 2024-10-22 timeRangeProvider experimental @grafana/grafana-frontend-platform false false false
153 2025-11-05 timeRangePan experimental @grafana/dataviz-squad false false true
154 2025-11-21 2025-11-20 newTimeRangeZoomShortcuts experimental @grafana/dataviz-squad false false true
155 2024-10-24 azureMonitorDisableLogLimit GA @grafana/partner-datasources false false false
156 2024-12-20 playlistsReconciler experimental @grafana/grafana-app-platform-squad false true false
157 2024-11-14 passwordlessMagicLinkAuthentication experimental @grafana/identity-access-team false false false
158 2024-12-19 2024-12-18 prometheusSpecialCharsInLabelValues experimental @grafana/oss-big-tent false false true
159 2024-11-05 enableExtensionsAdminPage experimental @grafana/plugins-platform-backend false true false
160 2024-11-07 enableSCIM preview @grafana/identity-access-team false false false
161 2024-11-12 crashDetection experimental @grafana/observability-traces-and-profiling false false true
170 2025-07-16 alertingAIAnalyzeCentralStateHistory experimental @grafana/alerting-squad false false false
171 2024-11-22 alertingNotificationsStepMode GA @grafana/alerting-squad false false true
172 2024-12-19 unifiedStorageSearchUI experimental @grafana/search-and-storage false false false
173 2024-12-13 2024-12-12 elasticsearchCrossClusterSearch GA @grafana/partner-datasources false false false
174 2024-12-13 lokiLabelNamesQueryApi GA @grafana/oss-big-tent false false false
175 2024-12-27 k8SFolderCounts experimental @grafana/search-and-storage false false false
176 2025-01-09 improvedExternalSessionHandlingSAML GA @grafana/identity-access-team false false false
227 2025-08-01 alertEnrichmentConditional experimental @grafana/alerting-squad false false false
228 2025-06-10 alertingImportAlertmanagerAPI experimental @grafana/alerting-squad false false false
229 2025-08-01 alertingImportAlertmanagerUI experimental @grafana/alerting-squad false false false
230 2025-07-16 2025-07-15 sharingDashboardImage GA @grafana/sharing-squad false false true
231 2025-06-17 preferLibraryPanelTitle privatePreview @grafana/dashboards-squad false false false
232 2025-06-24 tabularNumbers GA @grafana/grafana-frontend-platform false false false
233 2025-06-25 newInfluxDSConfigPageDesign privatePreview @grafana/partner-datasources false false false
256 2025-10-20 newGauge preview @grafana/dataviz-squad false false true
257 2025-11-12 newVizSuggestions preview @grafana/dataviz-squad false false true
258 2025-12-02 externalVizSuggestions experimental @grafana/dataviz-squad false false true
259 2025-12-19 2025-12-18 heatmapRowsAxisOptions experimental @grafana/dataviz-squad false false true
260 2025-10-17 preventPanelChromeOverflow preview @grafana/grafana-frontend-platform false false true
261 2025-10-31 jaegerEnableGrpcEndpoint experimental @grafana/oss-big-tent false false false
262 2025-10-17 pluginStoreServiceLoading experimental @grafana/plugins-platform-backend false false false
279 2026-01-06 secretsManagementAppPlatformAwsKeeper experimental @grafana/grafana-operator-experience-squad false false false
280 2026-01-07 profilesExemplars experimental @grafana/observability-traces-and-profiling false false false
281 2026-01-14 alertingSyncDispatchTimer experimental @grafana/alerting-squad false true false
282 2026-01-15 queryWithAssistant experimental @grafana/oss-big-tent false false true

View file

@ -3708,6 +3708,24 @@
"expression": "false"
}
},
{
"metadata": {
"name": "queryWithAssistant",
"resourceVersion": "1768832883692",
"creationTimestamp": "2026-01-15T12:40:17Z",
"deletionTimestamp": "2026-01-19T14:25:13Z",
"annotations": {
"grafana.app/updatedTimestamp": "2026-01-19 14:28:03.692934 +0000 UTC"
}
},
"spec": {
"description": "Enables the Query with Assistant button in the query editor",
"stage": "experimental",
"codeowner": "@grafana/oss-big-tent",
"frontend": true,
"expression": "false"
}
},
{
"metadata": {
"name": "recentlyViewedDashboards",

View file

@ -0,0 +1,116 @@
import { render, screen } from '@testing-library/react';
import { AssistantHook, useAssistant } from '@grafana/assistant';
import { CoreApp, DataSourceInstanceSettings } from '@grafana/data';
import { DataQuery } from '@grafana/schema';
import { QueryActionAssistantButton } from './QueryActionAssistantButton';
// Mock the assistant hook
jest.mock('@grafana/assistant', () => ({
useAssistant: jest.fn(),
createAssistantContextItem: jest.fn(),
}));
// Mock the runtime services that assistant depends on
const mockConfig = {
featureToggles: {
queryWithAssistant: false,
},
};
jest.mock('@grafana/runtime', () => ({
...jest.requireActual('@grafana/runtime'),
usePluginLinks: jest.fn().mockReturnValue({ links: [], isLoading: false }),
get config() {
return mockConfig;
},
}));
const useAssistantMock = jest.mocked(useAssistant);
const mockDataSourceInstance: DataSourceInstanceSettings = {
uid: 'test-uid',
name: 'Test Datasource',
type: 'loki',
} as DataSourceInstanceSettings;
const mockQuery: DataQuery = {
refId: 'A',
};
const mockQueries: DataQuery[] = [mockQuery];
const defaultProps = {
query: mockQuery,
queries: mockQueries,
dataSourceInstanceSettings: mockDataSourceInstance,
app: CoreApp.Explore,
datasourceApi: null,
};
describe('QueryActionAssistantButton', () => {
beforeEach(() => {
jest.clearAllMocks();
// Default: feature toggle enabled, assistant available
mockConfig.featureToggles.queryWithAssistant = true;
useAssistantMock.mockReturnValue({
isAvailable: true,
openAssistant: jest.fn(),
} as unknown as AssistantHook);
});
it('should render nothing when feature toggle is disabled', () => {
mockConfig.featureToggles.queryWithAssistant = false;
useAssistantMock.mockReturnValue({
isAvailable: true,
openAssistant: jest.fn(),
} as unknown as AssistantHook);
const { container } = render(<QueryActionAssistantButton {...defaultProps} />);
expect(container.firstChild).toBeNull();
});
it('should render nothing when app is not Explore, Dashboard, or PanelEditor', () => {
const { container } = render(<QueryActionAssistantButton {...defaultProps} app={CoreApp.Unknown} />);
expect(container.firstChild).toBeNull();
});
it('should render nothing when Assistant is not available', () => {
mockConfig.featureToggles.queryWithAssistant = true;
useAssistantMock.mockReturnValue({
isAvailable: false,
openAssistant: undefined,
} as unknown as AssistantHook);
const { container } = render(<QueryActionAssistantButton {...defaultProps} />);
expect(container.firstChild).toBeNull();
});
it('should render nothing when openAssistant is not provided', () => {
mockConfig.featureToggles.queryWithAssistant = true;
useAssistantMock.mockReturnValue({
isAvailable: true,
openAssistant: undefined,
} as unknown as AssistantHook);
const { container } = render(<QueryActionAssistantButton {...defaultProps} />);
expect(container.firstChild).toBeNull();
});
it('should render button when feature toggle is enabled and assistant is available', () => {
mockConfig.featureToggles.queryWithAssistant = true;
const mockOpenAssistant = jest.fn();
useAssistantMock.mockReturnValue({
isAvailable: true,
openAssistant: mockOpenAssistant,
} as unknown as AssistantHook);
render(<QueryActionAssistantButton {...defaultProps} />);
const button = screen.getByRole('button', { name: /query with assistant/i });
expect(button).toBeInTheDocument();
});
});

View file

@ -0,0 +1,154 @@
import { useAssistant, createAssistantContextItem } from '@grafana/assistant';
import { CoreApp, DataSourceApi, DataSourceInstanceSettings } from '@grafana/data';
import { t } from '@grafana/i18n';
import { config } from '@grafana/runtime';
import { DataQuery, DataSourceJsonData } from '@grafana/schema';
import { Button } from '@grafana/ui';
import { queryIsEmpty } from 'app/core/utils/query';
interface QueryActionAssistantButtonProps<TQuery extends DataQuery = DataQuery> {
query: TQuery;
queries: TQuery[];
dataSourceInstanceSettings: DataSourceInstanceSettings;
app?: CoreApp;
datasourceApi: DataSourceApi<TQuery, DataSourceJsonData, {}> | null;
}
export function QueryActionAssistantButton<TQuery extends DataQuery = DataQuery>({
query,
queries,
dataSourceInstanceSettings,
app,
datasourceApi,
}: QueryActionAssistantButtonProps<TQuery>) {
const { isAvailable, openAssistant } = useAssistant();
// Check if the feature toggle is enabled
if (!config.featureToggles.queryWithAssistant) {
return null;
}
if (!isAvailable || !openAssistant) {
return null;
}
// Only show for Explore and Dashboard apps
if (app !== CoreApp.Explore && app !== CoreApp.Dashboard && app !== CoreApp.PanelEditor) {
return null;
}
// Only show for loki and prometheus datasources
const pluginId = dataSourceInstanceSettings.type;
if (pluginId !== 'loki' && pluginId !== 'prometheus') {
return null;
}
const origin = `grafana/query-editor/${pluginId}/${app ?? CoreApp.Unknown}`;
// Check if current query has content
const hasCurrentQuery = !queryIsEmpty(query);
const otherQueries = queries.filter((q) => q.refId !== query.refId && !queryIsEmpty(q));
// Build context items
const context = [
createAssistantContextItem('datasource', {
datasourceUid: dataSourceInstanceSettings.uid,
}),
];
// Add current query if it has content
if (hasCurrentQuery) {
context.push(
createAssistantContextItem('structured', {
title: t('query-operation.header.current-query', 'Current query'),
data: query,
})
);
}
// Add other queries if they exist
if (otherQueries.length > 0) {
context.push(
createAssistantContextItem('structured', {
title: t('query-operation.header.other-queries', 'Other queries'),
data: {
queries: otherQueries,
},
})
);
}
// Get query display text to determine if we're creating or updating
const queryDisplayText =
hasCurrentQuery && datasourceApi?.getQueryDisplayText ? datasourceApi.getQueryDisplayText(query) : null;
// Determine if we're creating or updating based on queryDisplayText
const isUpdating = !!queryDisplayText;
const actionText = isUpdating
? t(
'query-operation.header.assistant-prompt-update',
'Help me update the current query to answer my questions and provide the insights I need.'
)
: t(
'query-operation.header.assistant-prompt-create',
'Help me create a new query to answer my questions and provide the insights I need.'
);
// Format app name nicely
const appName =
app === CoreApp.Explore
? t('query-operation.header.app-explore', 'Explore')
: app === CoreApp.Dashboard
? t('query-operation.header.app-dashboard', 'Dashboard')
: '';
// Build the prompt with proper formatting
const codeBlockLines: string[] = [];
if (queryDisplayText) {
codeBlockLines.push(t('query-operation.header.current-query-label', 'Current query:') + ` ${queryDisplayText}`);
}
codeBlockLines.push(
t('query-operation.header.selected-datasource-label', 'Selected data source:') +
` ${dataSourceInstanceSettings.name}`
);
if (appName) {
codeBlockLines.push(t('query-operation.header.app-label', 'App:') + ` ${appName}`);
}
// Add actionable sentence to motivate users
const actionableSentence = isUpdating
? t(
'query-operation.header.assistant-actionable-update',
'Please describe what you want to change or improve in this query.'
)
: t(
'query-operation.header.assistant-actionable-create',
"Please describe what you want to query and what insights you're looking for."
);
// Build final prompt with code block
const prompt = [actionText, '```', ...codeBlockLines, '```', actionableSentence].join('\n');
const handleClick = () => {
openAssistant({
origin,
prompt,
context,
autoSend: false,
});
};
return (
<Button
size="sm"
variant="secondary"
icon="ai"
onClick={handleClick}
title={t('query-operation.header.query-with-assistant', 'Query with Assistant')}
>
{t('query-operation.header.query-with-assistant', 'Query with Assistant')}
</Button>
);
}

View file

@ -36,6 +36,7 @@ import {
import { useQueryLibraryContext } from '../../explore/QueryLibrary/QueryLibraryContext';
import { ExpressionDatasourceUID } from '../../expressions/types';
import { QueryActionAssistantButton } from './QueryActionAssistantButton';
import { QueryActionComponent, RowActionComponents } from './QueryActionComponent';
import { QueryEditorRowHeader } from './QueryEditorRowHeader';
import { QueryErrorAlert } from './QueryErrorAlert';
@ -467,6 +468,7 @@ export class QueryEditorRow<TQuery extends DataQuery> extends PureComponent<Prop
renderHeader = (props: QueryOperationRowRenderProps) => {
const { app, query, dataSource, onChangeDataSource, onChange, queries, renderHeaderExtras, hideRefId } = this.props;
const { datasource } = this.state;
return (
<QueryEditorRowHeader
@ -477,7 +479,18 @@ export class QueryEditorRow<TQuery extends DataQuery> extends PureComponent<Prop
hidden={query.hide}
onChange={onChange}
collapsedText={!props.isOpen ? this.renderCollapsedText() : null}
renderExtras={renderHeaderExtras}
renderExtras={() => (
<>
<QueryActionAssistantButton
query={query}
queries={queries}
dataSourceInstanceSettings={this.props.dataSource}
datasourceApi={datasource}
app={app}
/>
{renderHeaderExtras && renderHeaderExtras()}
</>
)}
alerting={app === CoreApp.UnifiedAlerting}
hideRefId={hideRefId}
/>

View file

@ -12544,13 +12544,25 @@
},
"query-operation": {
"header": {
"app-dashboard": "Dashboard",
"app-explore": "Explore",
"app-label": "App:",
"assistant-actionable-create": "Please describe what you want to query and what insights you're looking for.",
"assistant-actionable-update": "Please describe what you want to change or improve in this query.",
"assistant-prompt-create": "Help me create a new query to answer my questions and provide the insights I need.",
"assistant-prompt-update": "Help me update the current query to answer my questions and provide the insights I need.",
"collapse-row": "Collapse query row",
"current-query": "Current query",
"current-query-label": "Current query:",
"datasource-help": "Show data source help",
"drag-and-drop": "Drag and drop to reorder",
"duplicate-query": "Duplicate query",
"expand-row": "Expand query row",
"hide-response": "Hide response",
"other-queries": "Other queries",
"query-with-assistant": "Query with Assistant",
"remove-query": "Remove query",
"selected-datasource-label": "Selected data source:",
"show-response": "Show response"
},
"query-editor-not-exported": "Data source plugin does not export any Query Editor component"

View file

@ -0,0 +1,19 @@
// Mock for @grafana/assistant to prevent initialization errors in tests
// The real module tries to call getObservablePluginLinks() during initialization
// which fails because Grafana hasn't started. This mock prevents that.
export const useAssistant = jest.fn().mockReturnValue({
isAvailable: false,
openAssistant: undefined,
closeAssistant: jest.fn(),
toggleAssistant: jest.fn(),
});
export const createAssistantContextItem = jest.fn();
// Additional exports that may be used
export const toggleAssistant = jest.fn();
export const isAssistantAvailable = jest.fn().mockReturnValue(false);
// Type exports (if needed
export type AssistantHook = ReturnType<typeof useAssistant>;