Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
a5a7173
declare the share plugin as a security_solution dependency
denar50 Nov 19, 2025
cf27925
allow exporting ai value reports in ess
denar50 Nov 19, 2025
c3639fc
tune cost savings insight prompt
denar50 Nov 19, 2025
28fa6ed
Changes from node scripts/lint_ts_projects --fix
kibanamachine Nov 19, 2025
daacc5d
update securitySolution bundle limits
denar50 Nov 19, 2025
0b39652
fix unit tests
denar50 Nov 20, 2025
675448d
Merge branch 'main' into enable-value-reports-in-ech-with-only-ai-gen…
denar50 Nov 20, 2025
44268e0
Merge branch 'main' into enable-value-reports-in-ech-with-only-ai-gen…
denar50 Nov 21, 2025
2b3fd3d
add new ESS settings to stack management schema
denar50 Nov 21, 2025
eeef8ea
Changes from node scripts/telemetry_check
kibanamachine Nov 21, 2025
2313b91
Merge branch 'main' into enable-value-reports-in-ech-with-only-ai-gen…
denar50 Nov 21, 2025
8ba9c2d
fix cypress tests
denar50 Nov 21, 2025
cdd06e6
Merge branch 'main' into enable-value-reports-in-ech-with-only-ai-gen…
denar50 Nov 24, 2025
243caef
Changes from node scripts/regenerate_moon_projects.js --update
kibanamachine Nov 24, 2025
400824f
enable scheduling an export report task
denar50 Nov 24, 2025
920f4e3
use AI_VALUE_REPORT_LOCATOR from @kbn/deeplinks-analytics
denar50 Nov 24, 2025
3e9f5eb
Update x-pack/solutions/security/plugins/security_solution/public/rep…
denar50 Nov 24, 2025
d0204d2
Update x-pack/solutions/security/plugins/security_solution/public/rep…
denar50 Nov 24, 2025
9590b4b
Update x-pack/solutions/security/plugins/security_solution/public/rep…
denar50 Nov 24, 2025
9a47f65
fix syntax error
denar50 Nov 24, 2025
45df8ae
use callback ref in ai value page
denar50 Nov 24, 2025
2789336
memoise loading state of CostSavingsKeyInsightView
denar50 Nov 24, 2025
5c221c0
add telemetry support
denar50 Nov 25, 2025
cf7bee7
added telemetry and error handling to hash generation
denar50 Nov 25, 2025
4d15c2a
fix linting error
denar50 Nov 25, 2025
eeea191
Changes from node scripts/eslint_all_files --no-cache --fix
kibanamachine Nov 25, 2025
0d2279f
add isExportMode boolean to exportContext, hide title edit pencil in …
denar50 Nov 25, 2025
dcc0f99
Merge branch 'main' into enable-value-reports-in-ech-with-only-ai-gen…
denar50 Nov 25, 2025
7e9df73
Changes from node scripts/regenerate_moon_projects.js --update
kibanamachine Nov 25, 2025
042035c
make valueReport non optional in UrlInputsModel
denar50 Nov 25, 2025
0dc225e
use loading state for export button
denar50 Nov 25, 2025
34d2431
fix type issues related to valueReport being now always available in …
denar50 Nov 25, 2025
e8c7e19
Changes from node scripts/eslint_all_files --no-cache --fix
kibanamachine Nov 25, 2025
7a02485
adjust the ValueReportSettingsComponent: reword the change rate cta i…
denar50 Nov 25, 2025
076712c
adjust copy
denar50 Nov 25, 2025
48dd736
miscellaneous UI improvements
denar50 Nov 26, 2025
323ba8e
fix unit tests
denar50 Nov 26, 2025
ba23bb6
Merge remote-tracking branch 'upstream/main' into enable-value-report…
denar50 Nov 26, 2025
966c170
Merge branch 'main' into enable-value-reports-in-ech-with-only-ai-gen…
denar50 Nov 27, 2025
3709a8a
fix unit test
denar50 Nov 27, 2025
1554f8a
further ui adjustments
denar50 Nov 27, 2025
148755e
Merge branch 'main' into enable-value-reports-in-ech-with-only-ai-gen…
denar50 Nov 28, 2025
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
2 changes: 1 addition & 1 deletion packages/kbn-optimizer/limits.yml
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ pageLoadAssetSize:
searchQueryRules: 6689
searchSynonyms: 6371
security: 79627
securitySolution: 119378
securitySolution: 186285
securitySolutionEss: 43319
securitySolutionServerless: 62689
serverless: 7852
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
DASHBOARD_APP_LOCATOR,
LENS_APP_LOCATOR,
VISUALIZE_APP_LOCATOR,
AI_VALUE_REPORT_LOCATOR,
} from '@kbn/deeplinks-analytics';
import type { LicenseType } from '@kbn/licensing-types';

Expand Down Expand Up @@ -74,6 +75,7 @@ export const REPORTING_REDIRECT_ALLOWED_LOCATOR_TYPES = [
DASHBOARD_APP_LOCATOR,
LENS_APP_LOCATOR,
VISUALIZE_APP_LOCATOR,
AI_VALUE_REPORT_LOCATOR,
];

// Redirection URL used to load app state for screenshotting
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const reportingPDFExportShareIntegration = ({
apiClient,
startServices$,
}: ExportModalShareOpts): RegisterShareIntegrationArgs<ExportShare> => {
const supportedObjectTypes = ['dashboard', 'visualization', 'lens'];
const supportedObjectTypes = ['dashboard', 'visualization', 'lens', 'ai_value_report'];

return {
id: 'pdfReports',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,5 @@ export const DASHBOARD_SAVED_OBJECT_TYPE = 'dashboard';
export const LENS_APP_LOCATOR = 'LENS_APP_LOCATOR';

export const VISUALIZE_APP_LOCATOR = 'VISUALIZE_APP_LOCATOR';

export const AI_VALUE_REPORT_LOCATOR = 'AI_VALUE_REPORT_LOCATOR';
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export {
LENS_APP_LOCATOR,
DISCOVER_ESQL_LOCATOR,
DASHBOARD_APP_LOCATOR,
AI_VALUE_REPORT_LOCATOR,
} from './constants';

export type { AppId, DeepLinkId } from './deep_links';
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,18 @@ export const stackManagementSchema: MakeSchemaFrom<UsageStats> = {
type: 'keyword',
_meta: { description: 'Non-default value of setting.' },
},
'securitySolution:defaultValueReportMinutes': {
type: 'keyword',
_meta: { description: 'Non-default value of setting.' },
},
'securitySolution:defaultValueReportRate': {
type: 'keyword',
_meta: { description: 'Non-default value of setting.' },
},
'securitySolution:defaultValueReportTitle': {
type: 'keyword',
_meta: { description: 'Non-default value of setting.' },
},
'xpackReporting:customPdfLogo': {
type: 'keyword',
_meta: { description: 'Default value of the setting was changed.' },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ export interface UsageStats {
'securitySolution:enableEsqlRiskScoring': boolean;
'securitySolution:enableCloudConnector': boolean;
'securitySolution:suppressionBehaviorOnAlertClosure': string;
'securitySolution:defaultValueReportMinutes': string;
'securitySolution:defaultValueReportRate': string;
'securitySolution:defaultValueReportTitle': string;
'search:includeFrozen': boolean;
'courier:maxConcurrentShardRequests': number;
'courier:setRequestPreference': string;
Expand Down
18 changes: 18 additions & 0 deletions src/platform/plugins/shared/telemetry/schema/oss_platform.json
Original file line number Diff line number Diff line change
Expand Up @@ -10109,6 +10109,24 @@
"description": "Non-default value of setting."
}
},
"securitySolution:defaultValueReportMinutes": {
"type": "keyword",
"_meta": {
"description": "Non-default value of setting."
}
},
"securitySolution:defaultValueReportRate": {
"type": "keyword",
"_meta": {
"description": "Non-default value of setting."
}
},
"securitySolution:defaultValueReportTitle": {
"type": "keyword",
"_meta": {
"description": "Non-default value of setting."
}
},
"xpackReporting:customPdfLogo": {
"type": "keyword",
"_meta": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ export const starterPromptPrompt4 =
'Can you provide examples of questions I can ask about Elastic Security, such as investigating alerts, running ES|QL queries, incident response, or threat intelligence?';

export const costSavingsInsightPart1 = `You are given Elasticsearch Lens aggregation results showing cost savings over time:`;
export const costSavingsInsightPart2 = `Generate a concise bulleted summary in mdx markdown. Follow the style and tone of the example below, highlighting key trends, averages, peaks, and projections:
export const costSavingsInsightPart2 = `Generate a concise bulleted summary in mdx markdown, no more than 500 characters. Follow the style and tone of the example below, highlighting key trends, averages, peaks, and projections:

\`\`\`
- Between July 18 and August 18, daily cost savings **averaged around $135K**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* 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 { AI_VALUE_REPORT_LOCATOR } from '@kbn/deeplinks-analytics';
import { AIValueReportLocatorDefinition, parseLocationState } from './locator';
import { AI_VALUE_PATH, APP_UI_ID } from '../../constants';

describe('AIValueReportLocatorDefinition', () => {
const locator = new AIValueReportLocatorDefinition();

const validParams = {
timeRange: {
from: '2024-01-01T00:00:00Z',
to: '2024-01-02T00:00:00Z',
},
insight: 'Some valuable insight!',
reportDataHash: 'abc123',
};

test('id should match constant', () => {
expect(locator.id).toBe(AI_VALUE_REPORT_LOCATOR);
});

test('getLocation returns correct location object', async () => {
const result = await locator.getLocation(validParams);

expect(result).toEqual({
app: APP_UI_ID,
path: AI_VALUE_PATH,
state: validParams,
});
});
});

describe('parseLocationState', () => {
const validState = {
timeRange: {
from: '2024-01-01T00:00:00Z',
to: '2024-01-01T00:00:00Z',
},
insight: 'Some valuable insight!',
reportDataHash: 'hash123',
};

it('returns parsed state when valid', () => {
const result = parseLocationState(validState);
expect(result).toEqual(validState);
});

it('strips unknown fields but preserves valid ones', () => {
const stateWithExtras = {
...validState,
extraField: 'foo',
anotherOne: 42,
};

const result = parseLocationState(stateWithExtras);

expect(result).toEqual(stateWithExtras);
});

it('returns undefined for invalid state (missing fields)', () => {
const invalid = {
insight: 'missing timeRange and hash',
};

const result = parseLocationState(invalid);
expect(result).toBeUndefined();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* 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 { z } from '@kbn/zod';
import type { LocatorDefinition, LocatorPublic } from '@kbn/share-plugin/public';
import { AI_VALUE_REPORT_LOCATOR } from '@kbn/deeplinks-analytics';
import { AI_VALUE_PATH, APP_UI_ID } from '../../constants';

const AIValueReportParamsSchema = z.object({
timeRange: z.object({
to: z.string().nonempty(),
from: z.string().nonempty(),
}),
insight: z.string().nonempty(),
reportDataHash: z.string().nonempty(),
});

export type AIValueReportParams = z.infer<typeof AIValueReportParamsSchema>;

export type ForwardedAIValueReportState = AIValueReportParams;

export type AIValueReportLocator = LocatorPublic<AIValueReportParams>;

export class AIValueReportLocatorDefinition implements LocatorDefinition<AIValueReportParams> {
public readonly id = AI_VALUE_REPORT_LOCATOR;

public readonly getLocation = async (params: AIValueReportParams) => {
return {
app: APP_UI_ID,
path: AI_VALUE_PATH,
state: params,
};
};
}

export const parseLocationState = (state: unknown): ForwardedAIValueReportState | undefined => {
const result = AIValueReportParamsSchema.passthrough().safeParse(state);
if (result.error) {
// This will cause the page to fallback to rendering normally
return undefined;
}

return result.data;
};
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"productDocBase",
"telemetry",
"elasticAssistantSharedState",
"share"
],
"optionalPlugins": [
"encryptedSavedObjects",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ dependsOn:
- '@kbn/response-ops-rule-form'
- '@kbn/core-lifecycle-browser-mocks'
- '@kbn/connector-schemas'
- '@kbn/deeplinks-analytics'
- '@kbn/tour-queue'
tags:
- plugin
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,9 @@ export const getNavCategories = (
type: LinkCategoryType.separator,
linkIds: [SecurityPageName.siemReadiness],
},
{
type: LinkCategoryType.separator,
linkIds: [SecurityPageName.aiValue],
},
];
};
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,10 @@ describe('SearchBarComponent', () => {
timerange: mockGlobalState.inputs.timeline.timerange,
linkTo: mockGlobalState.inputs.timeline.linkTo,
},
valueReport: {
timerange: mockGlobalState.inputs.valueReport.timerange,
linkTo: mockGlobalState.inputs.valueReport.linkTo,
},
},
]);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import * as redux from 'react-redux';
import * as experimentalFeatures from '../use_experimental_features';
import * as globalQueryString from '../../utils/global_query_string';
import { TestProviders } from '../../mock';
import { useKibana } from '../../lib/kibana';

jest.mock('react-redux', () => ({
...jest.requireActual('react-redux'),
Expand All @@ -27,12 +26,6 @@ describe('useInitTimerangeFromUrlParam', () => {
jest.clearAllMocks();
(redux.useDispatch as jest.Mock).mockReturnValue(dispatch);
(experimentalFeatures.useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(false);
(useKibana as jest.Mock).mockReturnValue({
services: {
serverless: {},
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any);
});

it('should call useInitializeUrlParam with correct params', () => {
Expand All @@ -45,20 +38,7 @@ describe('useInitTimerangeFromUrlParam', () => {
);
});

it('should call dispatch 2 times on init url params when not serverless', () => {
(useKibana as jest.Mock).mockReturnValue({
services: {},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any);
renderHook(() => useInitTimerangeFromUrlParam(), {
wrapper: TestProviders,
});
const callback = (globalQueryString.useInitializeUrlParam as jest.Mock).mock.calls[0][1];
callback({ valueReport: { timerange: { kind: 'absolute' } } });
expect(dispatch).toHaveBeenCalledTimes(2);
});

it('should call dispatch 3 times on init url params when serverless and valueReport exists', () => {
it('should call dispatch 3 times on init url params when valueReport exists', () => {
renderHook(() => useInitTimerangeFromUrlParam(), {
wrapper: TestProviders,
});
Expand All @@ -67,7 +47,7 @@ describe('useInitTimerangeFromUrlParam', () => {
expect(dispatch).toHaveBeenCalledTimes(3);
});

it('should call dispatch 6 times on init url params when serverless, valueReport exists, and isSocTrendsEnabled=true', () => {
it('should call dispatch 6 times on init url params when valueReport exists, and isSocTrendsEnabled=true', () => {
(experimentalFeatures.useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(true);
renderHook(() => useInitTimerangeFromUrlParam(), {
wrapper: TestProviders,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { useCallback } from 'react';
import { get, isEmpty } from 'lodash/fp';
import { useDispatch } from 'react-redux';
import type { Dispatch } from 'redux';
import { useKibana } from '../../lib/kibana';
import { useIsExperimentalFeatureEnabled } from '../use_experimental_features';
import type { TimeRangeKinds } from '../../store/inputs/constants';
import type {
Expand All @@ -28,18 +27,11 @@ import { InputsModelId } from '../../store/inputs/constants';
export const useInitTimerangeFromUrlParam = () => {
const dispatch = useDispatch();
const isSocTrendsEnabled = useIsExperimentalFeatureEnabled('socTrendsEnabled');
const { serverless } = useKibana().services;
// only on serverless
const isValueReportEnabled = !!serverless;

const onInitialize = useCallback(
(initialState: UrlInputsModel | null) =>
initializeTimerangeFromUrlParam(
initialState,
dispatch,
isSocTrendsEnabled,
isValueReportEnabled
),
[dispatch, isSocTrendsEnabled, isValueReportEnabled]
initializeTimerangeFromUrlParam(initialState, dispatch, isSocTrendsEnabled),
[dispatch, isSocTrendsEnabled]
);

useInitializeUrlParam(URL_PARAM_KEY.timerange, onInitialize);
Expand All @@ -48,8 +40,7 @@ export const useInitTimerangeFromUrlParam = () => {
const initializeTimerangeFromUrlParam = (
initialState: UrlInputsModel | null,
dispatch: Dispatch,
isSocTrendsEnabled: boolean,
isValueReportEnabled: boolean
isSocTrendsEnabled: boolean
) => {
if (initialState != null) {
const globalLinkTo: LinkTo = { linkTo: get('global.linkTo', initialState) };
Expand Down Expand Up @@ -188,7 +179,7 @@ const initializeTimerangeFromUrlParam = (
);
}
}
if (valueReportType && isValueReportEnabled) {
if (valueReportType) {
if (valueReportType === 'absolute') {
const absoluteRange = normalizeTimeRange<AbsoluteTimeRange>(
get('valueReport.timerange', initialState)
Expand Down
Loading