Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import type { ReactNode } from 'react';
import React from 'react';
import { DEFAULTS, useFetchAnonymizationFields } from './use_fetch_anonymization_fields';
import type { HttpSetup } from '@kbn/core-http-browser';
import { useAssistantContext } from '../../../assistant_context';
import { useMaybeAssistantContext } from '../../../assistant_context';
import { API_VERSIONS, defaultAssistantFeatures } from '@kbn/elastic-assistant-common';

const http = {
Expand All @@ -30,7 +30,7 @@ const createWrapper = () => {
};

describe('useFetchAnonymizationFields', () => {
(useAssistantContext as jest.Mock).mockReturnValue({
(useMaybeAssistantContext as jest.Mock).mockReturnValue({
http,
assistantAvailability: {
isAssistantEnabled: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
ELASTIC_AI_ASSISTANT_ANONYMIZATION_FIELDS_URL_FIND,
API_VERSIONS,
} from '@kbn/elastic-assistant-common';
import { useAssistantContext } from '../../../assistant_context';
import { useMaybeAssistantContext } from '../../../assistant_context';

export interface UseFetchAnonymizationFieldsParams {
page?: number; // API uses 1-based index
Expand Down Expand Up @@ -85,13 +85,15 @@ export const useFetchAnonymizationFields = (
filter,
} = params || {};

const {
http,
assistantAvailability: { isAssistantEnabled },
} = useAssistantContext();
const assistantContext = useMaybeAssistantContext();
const http = assistantContext?.http;
const isAssistantEnabled = assistantContext?.assistantAvailability.isAssistantEnabled ?? false;

const fetchPage = useCallback(
async ({ pageParam = { page, perPage, sortField, sortOrder, filter, all } }) => {
if (!http) {
throw new Error('useFetchAnonymizationFields requires AssistantProvider when fetching');
}
const {
page: p = page,
perPage: pp = perPage,
Expand Down Expand Up @@ -122,6 +124,8 @@ export const useFetchAnonymizationFields = (
[page, perPage, sortField, sortOrder, filter, all, http, signal]
);

const queryEnabled = Boolean(http) && isAssistantEnabled;

// Next page param: include current sorting in next request
const getNextPageParam = useCallback(
(lastPage: FindAnonymizationFieldsResponse) => {
Expand Down Expand Up @@ -155,7 +159,7 @@ export const useFetchAnonymizationFields = (
FindAnonymizationFieldsResponse
>(CACHING_KEYS, fetchPage, {
getNextPageParam,
enabled: isAssistantEnabled,
enabled: queryEnabled,
refetchOnWindowFocus: true,
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,14 @@ export const useAssistantContext = () => {
return context;
};

/**
* Same context as {@link useAssistantContext}, but returns `undefined` when no provider is present.
* Prefer {@link useAssistantContext} for assistant UI; use this only when a hook must degrade
* gracefully outside `AssistantProvider` (e.g. embedded previews).
*/
export const useMaybeAssistantContext = (): UseAssistantContext | undefined =>
React.useContext(AssistantContext);

export const useAssistantContextValue = (props: AssistantProviderProps): UseAssistantContext => {
const {
actionTypeRegistry,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@
// happens in the root of your app. Optionally provide a custom title for the assistant:

/** provides context (from the app) to the assistant, and injects Kibana services, like `http` */
export { AssistantProvider, useAssistantContext } from './impl/assistant_context';
export {
AssistantProvider,
useAssistantContext,
useMaybeAssistantContext,
} from './impl/assistant_context';

// Step 2.1: Add the `AssistantOverlay` component to your app. This component displays the assistant
// overlay in a modal, bound to a shortcut key:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { agentBuilderDefaultAgentId } from '@kbn/agent-builder-common';

import {
consumePreserveAgentBuilderSessionGate,
markPreserveAgentBuilderSessionDuringNextSecurityNavigation,
readLastAgentBuilderAgentIdForSecuritySession,
} from './agent_builder_navigation_gate';

describe('agent_builder_navigation_gate', () => {
beforeEach(() => {
sessionStorage.clear();
localStorage.clear();
});

it('consume returns false when not marked', () => {
expect(consumePreserveAgentBuilderSessionGate()).toBe(false);
expect(consumePreserveAgentBuilderSessionGate()).toBe(false);
});

it('consume returns true once after mark', () => {
markPreserveAgentBuilderSessionDuringNextSecurityNavigation();
expect(consumePreserveAgentBuilderSessionGate()).toBe(true);
expect(consumePreserveAgentBuilderSessionGate()).toBe(false);
});

it('readLastAgentBuilderAgentIdForSecuritySession falls back to default', () => {
expect(readLastAgentBuilderAgentIdForSecuritySession()).toBe(agentBuilderDefaultAgentId);
});

it('readLastAgentBuilderAgentIdForSecuritySession reads raw string from localStorage', () => {
localStorage.setItem('agentBuilder.agentId', 'my-agent');
expect(readLastAgentBuilderAgentIdForSecuritySession()).toBe('my-agent');
});

it('readLastAgentBuilderAgentIdForSecuritySession parses JSON-encoded string', () => {
localStorage.setItem('agentBuilder.agentId', JSON.stringify('json-agent'));
expect(readLastAgentBuilderAgentIdForSecuritySession()).toBe('json-agent');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { agentBuilderDefaultAgentId } from '@kbn/agent-builder-common';

/** Same as agent_builder `storageKeys.agentId`. */
const AGENT_BUILDER_LAST_AGENT_ID_STORAGE_KEY = 'agentBuilder.agentId';

/**
* Session flag: the next Security app effect teardown is from an in-app navigation that must not
* clear Agent Builder session state (e.g. "Open entity in Security" from an attachment).
*/
const IN_APP_NAV_PRESERVE_AGENT_BUILDER_SESSION_KEY =
'securitySolution.preserveAgentBuilderSessionDuringInAppNav';

export const readLastAgentBuilderAgentIdForSecuritySession = (): string => {
if (typeof window === 'undefined' || window.localStorage == null) {
return agentBuilderDefaultAgentId;
}
const stored = window.localStorage.getItem(AGENT_BUILDER_LAST_AGENT_ID_STORAGE_KEY);
if (stored == null || stored === '') {
return agentBuilderDefaultAgentId;
}
try {
const parsed = JSON.parse(stored);
return typeof parsed === 'string' ? parsed : stored;
} catch {
return stored;
}
};

export const markPreserveAgentBuilderSessionDuringNextSecurityNavigation = (): void => {
try {
window.sessionStorage?.setItem(IN_APP_NAV_PRESERVE_AGENT_BUILDER_SESSION_KEY, '1');
} catch {
// private mode / quota
}
};

/**
* Returns true once if a preserve gate was set (and clears it). Idempotent per consume call.
*/
export const consumePreserveAgentBuilderSessionGate = (): boolean => {
try {
if (window.sessionStorage?.getItem(IN_APP_NAV_PRESERVE_AGENT_BUILDER_SESSION_KEY) === '1') {
window.sessionStorage.removeItem(IN_APP_NAV_PRESERVE_AGENT_BUILDER_SESSION_KEY);
return true;
}
} catch {
// ignore
}
return false;
};
Original file line number Diff line number Diff line change
Expand Up @@ -718,6 +718,7 @@ export const ESSENTIAL_ALERT_FIELDS: string[] = [
export enum SecurityAgentBuilderAttachments {
alert = 'security.alert',
entity = 'security.entity',
entityAnalyticsDashboard = 'security.entity_analytics_dashboard',
rule = 'security.rule',
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,8 @@ dependsOn:
- '@kbn/search-inference-endpoints'
- '@kbn/shared-ux-column-presets'
- '@kbn/core-overlays-browser'
- '@kbn/workflows-management-plugin'
- '@kbn/core-application-browser-mocks'
tags:
- plugin
- prod
Expand Down
Loading
Loading