CommanPalette: Add Assistant integration for empty state (#112601)

* CommanPalette: Add Assistant integration for empty state

* Update assistant package and use new onClick pop

* i18n

* Update public/locales/en-US/grafana.json

Co-authored-by: Sven Grossmann <sven.grossmann@grafana.com>

* Update public/app/features/commandPalette/CommandPalette.tsx

Co-authored-by: Sven Grossmann <sven.grossmann@grafana.com>

* Update test

---------

Co-authored-by: Sven Grossmann <sven.grossmann@grafana.com>
This commit is contained in:
Ivana Huckova
2025-11-05 16:13:47 +01:00
committed by GitHub
co-authored by Sven Grossmann
parent 9a8d17a209
commit 1830e2ce9d
7 changed files with 91 additions and 17 deletions
+1 -1
View File
@@ -278,7 +278,7 @@
"@glideapps/glide-data-grid": "^6.0.0",
"@grafana/alerting": "workspace:*",
"@grafana/api-clients": "workspace:*",
"@grafana/assistant": "0.1.0",
"@grafana/assistant": "0.1.1",
"@grafana/aws-sdk": "0.7.1",
"@grafana/azure-sdk": "0.0.8",
"@grafana/data": "workspace:*",
+1 -1
View File
@@ -83,7 +83,7 @@
"typescript": "5.9.2"
},
"peerDependencies": {
"@grafana/assistant": "^0.1.0",
"@grafana/assistant": "^0.1.1",
"react": "^18.0.0",
"react-dom": "^18.0.0"
}
@@ -0,0 +1,60 @@
import { KBarProvider } from 'kbar';
import { render, screen } from 'test/test-utils';
import { useAssistant } from '@grafana/assistant';
import { setPluginLinksHook } from '@grafana/runtime';
import { setGetObservablePluginLinks } from '@grafana/runtime/internal';
import { getObservablePluginLinks } from '../plugins/extensions/getPluginExtensions';
import { CommandPalette } from './CommandPalette';
setPluginLinksHook(() => ({
links: [],
isLoading: false,
}));
setGetObservablePluginLinks(getObservablePluginLinks);
jest.mock('@grafana/assistant', () => ({
...jest.requireActual('@grafana/assistant'),
useAssistant: jest.fn(),
OpenAssistantButton: jest.fn().mockImplementation(({ title }) => <button>{title}</button>),
}));
jest.mock('kbar', () => ({
...jest.requireActual('kbar'),
KBarPortal: jest.fn().mockImplementation(({ children }) => <div>{children}</div>),
KBarAnimator: jest.fn().mockImplementation(({ children }) => <div>{children}</div>),
}));
const setup = () => {
return render(
<KBarProvider>
<CommandPalette />
</KBarProvider>
);
};
describe('CommandPalette', () => {
it('should render empty state with AI Assistant button when no results and assistant is available', async () => {
// Mock assistant being available
(useAssistant as jest.Mock).mockReturnValue({ isAvailable: true });
setup();
// Check if empty state message is rendered
expect(await screen.findByText('No results found')).toBeInTheDocument();
// Check if AI Assistant button is rendered with correct props
expect(screen.getByRole('button', { name: 'Try searching with Grafana Assistant' })).toBeInTheDocument();
});
it('should render empty state without AI Assistant button when assistant is not available', async () => {
// Mock assistant being unavailable
(useAssistant as jest.Mock).mockReturnValue({ isAvailable: false });
setup();
// Check if empty state message is rendered
expect(await screen.findByText('No results found')).toBeInTheDocument();
// Check that AI Assistant button is not rendered
expect(screen.queryByRole('button', { name: 'Try searching with Grafana Assistant' })).not.toBeInTheDocument();
});
});
@@ -5,6 +5,7 @@ import { useOverlay } from '@react-aria/overlays';
import { KBarAnimator, KBarPortal, KBarPositioner, VisualState, useKBar, ActionImpl } from 'kbar';
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
import { OpenAssistantButton, useAssistant } from '@grafana/assistant';
import { GrafanaTheme2 } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { t } from '@grafana/i18n';
@@ -88,7 +89,11 @@ function CommandPaletteContents() {
</div>
{scopesRow ? <div className={styles.searchContainer}>{scopesRow}</div> : null}
<div className={styles.resultsContainer}>
<RenderResults isFetchingSearchResults={isFetchingSearchResults} searchResults={searchResults} />
<RenderResults
isFetchingSearchResults={isFetchingSearchResults}
searchResults={searchResults}
searchQuery={searchQuery}
/>
</div>
</div>
</FocusScope>
@@ -131,10 +136,13 @@ function AncestorBreadcrumbs() {
interface RenderResultsProps {
isFetchingSearchResults: boolean;
searchResults: CommandPaletteAction[];
searchQuery: string;
}
const RenderResults = ({ isFetchingSearchResults, searchResults }: RenderResultsProps) => {
const RenderResults = ({ isFetchingSearchResults, searchResults, searchQuery }: RenderResultsProps) => {
const { results: kbarResults, rootActionId } = useMatches();
const { query } = useKBar();
const { isAvailable: isAssistantAvailable } = useAssistant();
const lateralSpace = getCommandPalettePosition();
const styles = useStyles2(getSearchStyles, lateralSpace);
@@ -176,11 +184,16 @@ const RenderResults = ({ isFetchingSearchResults, searchResults }: RenderResults
}, [showEmptyState]);
return showEmptyState ? (
<EmptyState
variant="not-found"
role="alert"
message={t('command-palette.empty-state.message', 'No results found')}
/>
<EmptyState variant="not-found" role="alert" message={t('command-palette.empty-state.message', 'No results found')}>
{isAssistantAvailable && (
<OpenAssistantButton
origin="grafana/command-palette-empty-state"
prompt={`Search for ${searchQuery}`}
title={t('command-palette.empty-state.button-title', 'Try searching with Grafana Assistant')}
onClick={query.toggle}
/>
)}
</EmptyState>
) : (
<KBarResults
items={items}
@@ -39,7 +39,7 @@
"webpack": "5.101.0"
},
"peerDependencies": {
"@grafana/assistant": "^0.1.0",
"@grafana/assistant": "^0.1.1",
"@grafana/runtime": "*"
},
"scripts": {
+1
View File
@@ -4130,6 +4130,7 @@
"scopes": "Scopes"
},
"empty-state": {
"button-title": "Try searching with Grafana Assistant",
"message": "No results found"
},
"scopes": {
+7 -7
View File
@@ -2543,7 +2543,7 @@ __metadata:
typescript: "npm:5.9.2"
webpack: "npm:5.101.0"
peerDependencies:
"@grafana/assistant": ^0.1.0
"@grafana/assistant": ^0.1.1
"@grafana/runtime": "*"
languageName: unknown
linkType: soft
@@ -3017,9 +3017,9 @@ __metadata:
languageName: unknown
linkType: soft
"@grafana/assistant@npm:0.1.0":
version: 0.1.0
resolution: "@grafana/assistant@npm:0.1.0"
"@grafana/assistant@npm:0.1.1":
version: 0.1.1
resolution: "@grafana/assistant@npm:0.1.1"
peerDependencies:
"@grafana/data": ">=12.1.0"
"@grafana/runtime": ">=12.1.0"
@@ -3027,7 +3027,7 @@ __metadata:
"@grafana/ui": ">=12.1.0"
react: ">=18.0.0"
rxjs: ">=7.0.0"
checksum: 10/90cfee9860bc128190ec1357e554685892b3ddd080d914457da311f1d45294dfcc15a427be36c381463eefe39268b5abbc39e6b541ad402e78264cc6d038c23f
checksum: 10/33e5b3f59b3b7a747a736f36183c77014214fa6e8048faac7a680dbba64833b1827dd7ef2e8c1ddf566a31829347449579ef6255811586b39914ca0510bb34a5
languageName: node
linkType: hard
@@ -3248,7 +3248,7 @@ __metadata:
tslib: "npm:2.8.1"
typescript: "npm:5.9.2"
peerDependencies:
"@grafana/assistant": ^0.1.0
"@grafana/assistant": ^0.1.1
react: ^18.0.0
react-dom: ^18.0.0
languageName: unknown
@@ -18844,7 +18844,7 @@ __metadata:
"@glideapps/glide-data-grid": "npm:^6.0.0"
"@grafana/alerting": "workspace:*"
"@grafana/api-clients": "workspace:*"
"@grafana/assistant": "npm:0.1.0"
"@grafana/assistant": "npm:0.1.1"
"@grafana/aws-sdk": "npm:0.7.1"
"@grafana/azure-sdk": "npm:0.0.8"
"@grafana/data": "workspace:*"