Skip to content

Commit 68d5ddb

Browse files
saarikabhasikibanamachineviduni94
authored
[Search] Serverless hide AI assistant from side nav for viewer (elastic#246966)
## Summary Currently in the Serverless search solution and when chatExperience set to `classic`, the viewer user is redirected to classic nav home page when tried to visit AI assistant app. In this PR, - [ ] updated to hide AI assistant from side nav for serveless search and observability. - [ ] modified redirect default route to serverless search home page https://github.com/user-attachments/assets/e7af8d8d-cb59-4ede-932d-157741fa3d3d How to test: Manually set chatExperience to `classic`. ``` POST kbn://internal/kibana/settings { "changes": { "aiAssistant:preferredChatExperience": "agent" } } ``` Tests completed: - [ ] Confirmed self managed behaviour remains unchanged. Which is - classic nav AI assistant app is hidden for user with less privilege. - [ ] Confirmed self managed security and observability solution nav works as expected. ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Viduni Wickramarachchi <viduni.ushanka@gmail.com>
1 parent da6ad46 commit 68d5ddb

5 files changed

Lines changed: 64 additions & 13 deletions

File tree

x-pack/platform/plugins/private/observability_ai_assistant_management/public/app.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export const mountManagementSection = async ({ core, mountParams, config }: Moun
4848

4949
ReactDOM.render(
5050
wrapWithTheme(
51-
<RedirectToHomeIfUnauthorized coreStart={coreStart}>
51+
<RedirectToHomeIfUnauthorized coreStart={coreStart} cloud={startDeps?.cloud}>
5252
<I18nProvider>
5353
<KibanaContextProvider services={{ ...coreStart, ...startDeps }}>
5454
<AppContextProvider value={{ setBreadcrumbs, config }}>

x-pack/platform/plugins/private/observability_ai_assistant_management/public/helpers/test_helper.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { RouterProvider } from '@kbn/typed-react-router-config';
1717
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
1818
import { merge } from 'lodash';
1919
import type { DeepPartial } from 'utility-types';
20+
import { cloudMock } from '@kbn/cloud-plugin/public/mocks';
2021
import type { AppContextValue } from '../context/app_context';
2122
import { AppContextProvider } from '../context/app_context';
2223
import { RedirectToHomeIfUnauthorized } from '../routes/components/redirect_to_home_if_unauthorized';
@@ -25,6 +26,7 @@ import type { CoreStartWithStartDeps } from '../hooks/use_kibana';
2526

2627
export const coreStartMock = coreMock.createStart();
2728

29+
export const cloudStartMock = cloudMock.createStart();
2830
const queryClient = new QueryClient({
2931
defaultOptions: {
3032
queries: {
@@ -80,7 +82,7 @@ export const render = (
8082
const TestWrapper = ({ children }: { children: React.ReactNode }) => (
8183
// @ts-ignore
8284
<IntlProvider locale="en-US">
83-
<RedirectToHomeIfUnauthorized coreStart={mergedCoreStartMock}>
85+
<RedirectToHomeIfUnauthorized coreStart={mergedCoreStartMock} cloud={cloudStartMock}>
8486
<KibanaContextProvider services={{ ...mergedCoreStartMock, ...startDeps }}>
8587
<AppContextProvider value={appContextValue}>
8688
<QueryClientProvider client={queryClient}>

x-pack/platform/plugins/private/observability_ai_assistant_management/public/plugin.test.ts

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,11 @@ describe('Observability AI Assistant Management plugin', () => {
5454
} as any);
5555

5656
describe('Licensing', () => {
57-
it('is disabled by default, enabled only for enterprise, and disabled for platinum', async () => {
58-
const plugin = new AiAssistantManagementObservabilityPlugin({
57+
let plugin: any;
58+
let management: ManagementSetup;
59+
let coreSetup: CoreSetup<any, any>;
60+
beforeEach(async () => {
61+
plugin = new AiAssistantManagementObservabilityPlugin({
5962
config: {
6063
get: jest.fn(() => ({
6164
logSourcesEnabled: true,
@@ -64,23 +67,31 @@ describe('Observability AI Assistant Management plugin', () => {
6467
},
6568
env: { packageInfo: { buildFlavor: 'traditional', branch: 'main' } },
6669
} as unknown as PluginInitializerContext);
67-
68-
const management = createManagementMock();
69-
const coreSetup = createCoreSetupMock();
70-
70+
management = createManagementMock();
71+
coreSetup = createCoreSetupMock();
7172
await plugin.setup(coreSetup, {
7273
management,
7374
observabilityAIAssistant: {} as any,
7475
ml: {} as any,
7576
});
77+
});
78+
afterEach(() => {
79+
plugin.stop();
80+
});
7681

82+
it('is disabled by default, enabled for enterprise if the AI Assistant is enabled, and disabled for platinum', () => {
7783
const app = (management.sections.section.ai as any).getApps()[0];
7884
expect(app).toBeDefined();
7985
expect(app.enabled).toBe(false);
8086

8187
// Start with platinum
8288
const license$ = new BehaviorSubject<any>(makeLicense('platinum'));
83-
plugin.start({} as CoreStart, { licensing: { license$ } } as any);
89+
plugin.start(
90+
{
91+
application: { capabilities: { observabilityAIAssistant: { show: true } } },
92+
} as unknown as CoreStart,
93+
{ licensing: { license$ } } as any
94+
);
8495
expect(app.enabled).toBe(false);
8596

8697
// Switch to enterprise
@@ -95,5 +106,33 @@ describe('Observability AI Assistant Management plugin', () => {
95106
license$.next(makeLicense('basic'));
96107
expect(app.enabled).toBe(false);
97108
});
109+
110+
it('is disabled by default for all license types when the AI Assistant is disabled', () => {
111+
const app = (management.sections.section.ai as any).getApps()[0];
112+
expect(app).toBeDefined();
113+
expect(app.enabled).toBe(false);
114+
115+
// Start with platinum
116+
const license$ = new BehaviorSubject<any>(makeLicense('platinum'));
117+
plugin.start(
118+
{
119+
application: { capabilities: { observabilityAIAssistant: { show: false } } },
120+
} as unknown as CoreStart,
121+
{ licensing: { license$ } } as any
122+
);
123+
expect(app.enabled).toBe(false);
124+
125+
// Switch to enterprise
126+
license$.next(makeLicense('enterprise'));
127+
expect(app.enabled).toBe(false);
128+
129+
// Switch to gold
130+
license$.next(makeLicense('gold'));
131+
expect(app.enabled).toBe(false);
132+
133+
// Switch to basic
134+
license$.next(makeLicense('basic'));
135+
expect(app.enabled).toBe(false);
136+
});
98137
});
99138
});

x-pack/platform/plugins/private/observability_ai_assistant_management/public/plugin.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,8 +122,9 @@ export class AiAssistantManagementObservabilityPlugin
122122
if (licensing) {
123123
this.licensingSubscription = licensing.license$.subscribe((license) => {
124124
const isEnterprise = license?.hasAtLeast('enterprise');
125-
126-
if (isEnterprise) {
125+
const isAiAssistantEnabled =
126+
coreStart.application.capabilities.observabilityAIAssistant?.show;
127+
if (isEnterprise && isAiAssistantEnabled) {
127128
this.registeredApp?.enable();
128129
} else {
129130
this.registeredApp?.disable();

x-pack/platform/plugins/private/observability_ai_assistant_management/public/routes/components/redirect_to_home_if_unauthorized.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,15 @@ import type { CoreStart } from '@kbn/core/public';
1212
import { AIChatExperience } from '@kbn/ai-assistant-common';
1313
import { AI_CHAT_EXPERIENCE_TYPE } from '@kbn/management-settings-ids';
1414
import { aiAssistantCapabilities } from '@kbn/observability-ai-assistant-plugin/public';
15+
import type { CloudStart } from '@kbn/cloud-plugin/public';
1516

1617
export function RedirectToHomeIfUnauthorized({
1718
coreStart,
19+
cloud,
1820
children,
1921
}: {
2022
coreStart: CoreStart;
23+
cloud?: CloudStart;
2124
children: ReactNode;
2225
}) {
2326
const {
@@ -30,16 +33,22 @@ export function RedirectToHomeIfUnauthorized({
3033
[settings.client]
3134
);
3235
const chatExperience = useObservable(chatExperience$, AIChatExperience.Classic);
36+
const isServerlessSearchSolution =
37+
cloud?.isServerlessEnabled && cloud?.serverless?.projectType === 'search';
3338

3439
const allowed =
3540
(capabilities?.observabilityAIAssistant?.[aiAssistantCapabilities.show] ?? false) &&
3641
chatExperience !== AIChatExperience.Agent;
3742

3843
useEffect(() => {
3944
if (!allowed) {
40-
navigateToApp('home');
45+
if (isServerlessSearchSolution) {
46+
navigateToApp('searchHomepage');
47+
} else {
48+
navigateToApp('home');
49+
}
4150
}
42-
}, [allowed, navigateToApp]);
51+
}, [allowed, navigateToApp, isServerlessSearchSolution]);
4352

4453
if (!allowed) return null;
4554

0 commit comments

Comments
 (0)