From 982f3b5a09945cad1c9bd4cb29408da8d0d2c86c Mon Sep 17 00:00:00 2001 From: Konrad Szwarc Date: Fri, 20 Mar 2026 15:37:57 -0600 Subject: [PATCH 01/11] [Osquery] Enable experimental features for query history rework and unified data table --- .../plugins/shared/osquery/common/experimental_features.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/platform/plugins/shared/osquery/common/experimental_features.ts b/x-pack/platform/plugins/shared/osquery/common/experimental_features.ts index 80670e28dd67b..d33ec571abc05 100644 --- a/x-pack/platform/plugins/shared/osquery/common/experimental_features.ts +++ b/x-pack/platform/plugins/shared/osquery/common/experimental_features.ts @@ -19,12 +19,12 @@ export const allowedExperimentalValues = Object.freeze({ * - Introduces search input and users filter * - Introduces scheduled responses support */ - queryHistoryRework: false, + queryHistoryRework: true, /** * Replaces the legacy EuiDataGrid results table with UnifiedDataTable, * adding KQL search, document flyout, per-row actions, and column curation. */ - unifiedDataTable: false, + unifiedDataTable: true, }); type ExperimentalFeatures = { [K in keyof typeof allowedExperimentalValues]: boolean }; From dc9d3f1c6e7d6026a4550735ef49dd1d63ea9350 Mon Sep 17 00:00:00 2001 From: Tomasz Ciecierski Date: Tue, 24 Mar 2026 15:24:12 +0100 Subject: [PATCH 02/11] [Osquery] Fix missing details on history list of rule packs --- .../map_live_hit_to_row.test.ts | 90 +++++++++++++ .../unified_history/map_live_hit_to_row.ts | 52 ++++++-- .../osquery_response_action.test.ts | 126 ++++++++++++++++++ .../osquery_response_action.ts | 2 + 4 files changed, 262 insertions(+), 8 deletions(-) create mode 100644 x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_response_actions/osquery_response_action.test.ts diff --git a/x-pack/platform/plugins/shared/osquery/server/routes/unified_history/map_live_hit_to_row.test.ts b/x-pack/platform/plugins/shared/osquery/server/routes/unified_history/map_live_hit_to_row.test.ts index aa9481b9c3df5..9e7d41fa03547 100644 --- a/x-pack/platform/plugins/shared/osquery/server/routes/unified_history/map_live_hit_to_row.test.ts +++ b/x-pack/platform/plugins/shared/osquery/server/routes/unified_history/map_live_hit_to_row.test.ts @@ -54,6 +54,77 @@ describe('mapLiveHitToRow', () => { expect(row.agentCount).toBe(2); }); + test('extracts query details for single (non-pack) queries', () => { + const hit = { + _source: { + action_id: 'action-details', + '@timestamp': '2024-01-01T00:00:00.000Z', + agent_ids: ['agent-1', 'agent-2'], + agent_all: true, + agent_platforms: ['linux'], + agent_policy_ids: ['policy-1'], + queries: [ + { + query: 'SELECT * FROM processes', + id: 'q1', + agents: ['agent-1', 'agent-2'], + ecs_mapping: { 'process.name': { field: 'name' } }, + saved_query_id: 'saved-123', + timeout: 30, + }, + ], + }, + }; + + const row = mapLiveHitToRow(hit); + + expect(row.queryText).toBe('SELECT * FROM processes'); + expect(row.ecsMapping).toEqual({ 'process.name': { field: 'name' } }); + expect(row.savedQueryId).toBe('saved-123'); + expect(row.timeout).toBe(30); + expect(row.agentIds).toEqual(['agent-1', 'agent-2']); + expect(row.agentAll).toBe(true); + expect(row.agentPlatforms).toEqual(['linux']); + expect(row.agentPolicyIds).toEqual(['policy-1']); + }); + + test('does not extract query-level details for pack queries', () => { + const hit = { + _source: { + action_id: 'action-pack', + '@timestamp': '2024-01-01T00:00:00.000Z', + pack_id: 'pack-1', + pack_name: 'my_pack', + agent_ids: ['agent-1'], + queries: [ + { + query: 'SELECT 1', + id: 'q1', + agents: ['agent-1'], + ecs_mapping: { 'host.name': { field: 'hostname' } }, + saved_query_id: 'saved-1', + timeout: 60, + }, + { + query: 'SELECT 2', + id: 'q2', + agents: ['agent-1'], + }, + ], + }, + }; + + const row = mapLiveHitToRow(hit); + + expect(row.queryText).toBe(''); + expect(row.packId).toBe('pack-1'); + expect(row.ecsMapping).toBeUndefined(); + expect(row.savedQueryId).toBeUndefined(); + expect(row.timeout).toBeUndefined(); + // Agent selection is still populated for packs + expect(row.agentIds).toEqual(['agent-1']); + }); + test('detects Rule source when alert_ids is present', () => { const hit = { _source: { @@ -106,6 +177,25 @@ describe('mapLiveHitToRow', () => { expect(row.tags).toEqual(['important', 'reviewed']); }); + test('handles old action docs without agent selection fields', () => { + const hit = { + _source: { + action_id: 'old-action', + '@timestamp': '2024-01-01T00:00:00.000Z', + agents: ['agent-1'], + queries: [{ query: 'SELECT * FROM os_version;', id: 'q1', agents: ['agent-1'] }], + }, + }; + + const row = mapLiveHitToRow(hit); + + expect(row.agentCount).toBe(1); + expect(row.agentIds).toBeUndefined(); + expect(row.agentAll).toBeUndefined(); + expect(row.agentPlatforms).toBeUndefined(); + expect(row.agentPolicyIds).toBeUndefined(); + }); + test('defaults tags to empty array when missing', () => { const hit = { _source: { diff --git a/x-pack/platform/plugins/shared/osquery/server/routes/unified_history/map_live_hit_to_row.ts b/x-pack/platform/plugins/shared/osquery/server/routes/unified_history/map_live_hit_to_row.ts index 849dd39f6a2ed..95fc0b4b34599 100644 --- a/x-pack/platform/plugins/shared/osquery/server/routes/unified_history/map_live_hit_to_row.ts +++ b/x-pack/platform/plugins/shared/osquery/server/routes/unified_history/map_live_hit_to_row.ts @@ -33,11 +33,37 @@ export interface LiveActionHit { sort?: Array; } +interface ActionQuery { + query?: string; + agents?: string[]; + id?: string; + ecs_mapping?: Record; + saved_query_id?: string; + timeout?: number; +} + +interface ActionSource { + '@timestamp'?: string; + action_id?: string; + pack_name?: string; + pack_id?: string; + space_id?: string; + user_id?: string; + agents?: string[]; + agent_ids?: string[]; + agent_all?: boolean; + agent_platforms?: string[]; + agent_policy_ids?: string[]; + alert_ids?: string[]; + tags?: string[]; + queries?: ActionQuery[]; +} + export const mapLiveHitToRow = (hit: LiveActionHit): LiveHistoryRow => { const hitFields = (hit.fields ?? {}) as Record; - const source = (hit._source ?? {}) as Record; + const source = (hit._source ?? {}) as ActionSource; - const get = (name: string) => getField(hitFields, source, name); + const get = (name: string) => getField(hitFields, source as Record, name); const agentsRaw = hitFields.agents ?? source.agents; const agentsList = Array.isArray(agentsRaw) ? agentsRaw : []; @@ -46,11 +72,7 @@ export const mapLiveHitToRow = (hit: LiveActionHit): LiveHistoryRow => { const packName = get('pack_name') as string | undefined; const packId = get('pack_id') as string | undefined; - const queries = (source.queries ?? []) as Array<{ - query?: string; - agents?: string[]; - id?: string; - }>; + const queries = source.queries ?? []; const isPack = queries.length > 1 || packId; const queryText = isPack ? '' : queries[0]?.query ?? ''; @@ -62,6 +84,15 @@ export const mapLiveHitToRow = (hit: LiveActionHit): LiveHistoryRow => { const alertIdsRaw = hitFields.alert_ids ?? source.alert_ids; const hasAlertIds = Array.isArray(alertIdsRaw) ? alertIdsRaw.length > 0 : !!alertIdsRaw; + let singleQueryDetails: Pick = {}; + if (!isPack && queries[0]) { + singleQueryDetails = { + ecsMapping: queries[0].ecs_mapping, + savedQueryId: queries[0].saved_query_id, + timeout: queries[0].timeout, + }; + } + return { id: actionId, sourceType: 'live' as const, @@ -78,6 +109,11 @@ export const mapLiveHitToRow = (hit: LiveActionHit): LiveHistoryRow => { totalRows: undefined, userId: get('user_id') as string | undefined, actionId, - tags: (source.tags as string[] | undefined) ?? [], + tags: source.tags ?? [], + ...singleQueryDetails, + agentIds: source.agent_ids, + agentAll: source.agent_all, + agentPlatforms: source.agent_platforms, + agentPolicyIds: source.agent_policy_ids, }; }; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_response_actions/osquery_response_action.test.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_response_actions/osquery_response_action.test.ts new file mode 100644 index 0000000000000..db45d25fbe0fb --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_response_actions/osquery_response_action.test.ts @@ -0,0 +1,126 @@ +/* + * 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 type { Logger } from '@kbn/logging'; +import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common'; +import type { RuleResponseOsqueryAction } from '../../../../common/api/detection_engine/model/rule_response_actions'; +import type { AlertWithAgent } from './types'; +import { osqueryResponseAction } from './osquery_response_action'; +import { createMockEndpointAppContextService } from '../../../endpoint/mocks'; + +const createMockAlert = ( + overrides: Partial = {} +): AlertWithAgent => + ({ + _id: 'alert-1', + _index: '.alerts-default', + agent: { id: 'agent-1', name: 'host-1', type: 'endpoint' }, + kibana: { + alert: { rule: { uuid: 'rule-1', name: 'Test Rule' } }, + space_ids: [DEFAULT_SPACE_ID], + }, + ...overrides, + } as AlertWithAgent); + +const createMockService = () => ({ + create: jest.fn().mockResolvedValue({}), + stop: jest.fn(), + logger: { error: jest.fn() } as unknown as Logger, +}); + +describe('osqueryResponseAction', () => { + let mockService: ReturnType; + const endpointService = createMockEndpointAppContextService(); + + beforeEach(() => { + mockService = createMockService(); + }); + + describe('pack_id passthrough', () => { + it('passes pack_id when packId is set on the response action', async () => { + const alerts = [createMockAlert(), createMockAlert({ _id: 'alert-2', agent: { id: 'agent-2', name: 'host-2', type: 'endpoint' } })]; + + const responseAction: RuleResponseOsqueryAction = { + actionTypeId: '.osquery', + params: { + packId: 'my-pack-123', + queries: [{ id: 'q1', query: 'SELECT * FROM uptime;' }], + }, + }; + + await osqueryResponseAction(responseAction, mockService, endpointService, { alerts }); + + expect(mockService.create).toHaveBeenCalledTimes(1); + expect(mockService.create).toHaveBeenCalledWith( + expect.objectContaining({ pack_id: 'my-pack-123' }), + expect.objectContaining({ space: { id: DEFAULT_SPACE_ID } }) + ); + }); + + it('passes pack_id as undefined when no pack is configured', async () => { + const alerts = [createMockAlert()]; + + const responseAction: RuleResponseOsqueryAction = { + actionTypeId: '.osquery', + params: { + query: 'SELECT * FROM processes;', + ecsMapping: { 'process.name': { field: 'name' } }, + savedQueryId: 'saved-1', + }, + }; + + await osqueryResponseAction(responseAction, mockService, endpointService, { alerts }); + + expect(mockService.create).toHaveBeenCalledTimes(1); + const createParams = mockService.create.mock.calls[0][0]; + expect(createParams.pack_id).toBeUndefined(); + expect(createParams.saved_query_id).toBe('saved-1'); + expect(createParams.ecs_mapping).toEqual({ 'process.name': { field: 'name' } }); + }); + + it('passes pack_id in per-alert calls when queries contain dynamic parameters', async () => { + const alerts = [ + createMockAlert(), + createMockAlert({ _id: 'alert-2', agent: { id: 'agent-2', name: 'host-2', type: 'endpoint' } }), + ]; + + const responseAction: RuleResponseOsqueryAction = { + actionTypeId: '.osquery', + params: { + packId: 'dynamic-pack-456', + queries: [{ id: 'q1', query: 'SELECT * FROM users WHERE name = {{user.name}};' }], + }, + }; + + osqueryResponseAction(responseAction, mockService, endpointService, { alerts }); + + expect(mockService.create).toHaveBeenCalledTimes(2); + expect(mockService.create).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ + pack_id: 'dynamic-pack-456', + agent_ids: ['agent-1'], + alert_ids: ['alert-1'], + }), + expect.objectContaining({ + alertData: expect.objectContaining({ _id: 'alert-1' }), + }) + ); + expect(mockService.create).toHaveBeenNthCalledWith( + 2, + expect.objectContaining({ + pack_id: 'dynamic-pack-456', + agent_ids: ['agent-2'], + alert_ids: ['alert-2'], + }), + expect.objectContaining({ + alertData: expect.objectContaining({ _id: 'alert-2' }), + }) + ); + }); + }); +}); diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_response_actions/osquery_response_action.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_response_actions/osquery_response_action.ts index 08c172e7aeac1..0f8c81d926723 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_response_actions/osquery_response_action.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_response_actions/osquery_response_action.ts @@ -61,6 +61,7 @@ export const osqueryResponseAction = ( .create( { ...rest, + pack_id: packId, queries: requiredOptional(queries), ecs_mapping: ecsMapping, saved_query_id: savedQueryId, @@ -82,6 +83,7 @@ export const osqueryResponseAction = ( .create( { ...rest, + pack_id: packId, queries: requiredOptional(queries), ecs_mapping: ecsMapping, saved_query_id: savedQueryId, From 136ae1b805e8db1104c6d2ac5815684fe3ac42a3 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 24 Mar 2026 15:09:34 +0000 Subject: [PATCH 03/11] Changes from node scripts/eslint_all_files --no-cache --fix --- .../osquery_response_action.test.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_response_actions/osquery_response_action.test.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_response_actions/osquery_response_action.test.ts index db45d25fbe0fb..82d775af96146 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_response_actions/osquery_response_action.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_response_actions/osquery_response_action.test.ts @@ -12,9 +12,7 @@ import type { AlertWithAgent } from './types'; import { osqueryResponseAction } from './osquery_response_action'; import { createMockEndpointAppContextService } from '../../../endpoint/mocks'; -const createMockAlert = ( - overrides: Partial = {} -): AlertWithAgent => +const createMockAlert = (overrides: Partial = {}): AlertWithAgent => ({ _id: 'alert-1', _index: '.alerts-default', @@ -42,7 +40,13 @@ describe('osqueryResponseAction', () => { describe('pack_id passthrough', () => { it('passes pack_id when packId is set on the response action', async () => { - const alerts = [createMockAlert(), createMockAlert({ _id: 'alert-2', agent: { id: 'agent-2', name: 'host-2', type: 'endpoint' } })]; + const alerts = [ + createMockAlert(), + createMockAlert({ + _id: 'alert-2', + agent: { id: 'agent-2', name: 'host-2', type: 'endpoint' }, + }), + ]; const responseAction: RuleResponseOsqueryAction = { actionTypeId: '.osquery', @@ -85,7 +89,10 @@ describe('osqueryResponseAction', () => { it('passes pack_id in per-alert calls when queries contain dynamic parameters', async () => { const alerts = [ createMockAlert(), - createMockAlert({ _id: 'alert-2', agent: { id: 'agent-2', name: 'host-2', type: 'endpoint' } }), + createMockAlert({ + _id: 'alert-2', + agent: { id: 'agent-2', name: 'host-2', type: 'endpoint' }, + }), ]; const responseAction: RuleResponseOsqueryAction = { From 0187ea75871dd5c1e6d1c34819d1df82324f737e Mon Sep 17 00:00:00 2001 From: Tomasz Ciecierski Date: Tue, 24 Mar 2026 16:34:22 +0100 Subject: [PATCH 04/11] [Osquery] Tighten types and clean up pack_id handling --- .../server/routes/unified_history/map_live_hit_to_row.ts | 4 ++-- .../rule_response_actions/osquery_response_action.test.ts | 4 ++-- .../rule_response_actions/osquery_response_action.ts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/x-pack/platform/plugins/shared/osquery/server/routes/unified_history/map_live_hit_to_row.ts b/x-pack/platform/plugins/shared/osquery/server/routes/unified_history/map_live_hit_to_row.ts index 95fc0b4b34599..e18fbb29285f7 100644 --- a/x-pack/platform/plugins/shared/osquery/server/routes/unified_history/map_live_hit_to_row.ts +++ b/x-pack/platform/plugins/shared/osquery/server/routes/unified_history/map_live_hit_to_row.ts @@ -28,7 +28,7 @@ export const getField = ( }; export interface LiveActionHit { - _source?: Record; + _source?: ActionSource; fields?: Record; sort?: Array; } @@ -61,7 +61,7 @@ interface ActionSource { export const mapLiveHitToRow = (hit: LiveActionHit): LiveHistoryRow => { const hitFields = (hit.fields ?? {}) as Record; - const source = (hit._source ?? {}) as ActionSource; + const source: ActionSource = hit._source ?? {}; const get = (name: string) => getField(hitFields, source as Record, name); diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_response_actions/osquery_response_action.test.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_response_actions/osquery_response_action.test.ts index db45d25fbe0fb..d2e46fae7de50 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_response_actions/osquery_response_action.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_response_actions/osquery_response_action.test.ts @@ -77,12 +77,12 @@ describe('osqueryResponseAction', () => { expect(mockService.create).toHaveBeenCalledTimes(1); const createParams = mockService.create.mock.calls[0][0]; - expect(createParams.pack_id).toBeUndefined(); + expect(createParams).not.toHaveProperty('pack_id'); expect(createParams.saved_query_id).toBe('saved-1'); expect(createParams.ecs_mapping).toEqual({ 'process.name': { field: 'name' } }); }); - it('passes pack_id in per-alert calls when queries contain dynamic parameters', async () => { + it('passes pack_id in per-alert calls when queries contain dynamic parameters', () => { const alerts = [ createMockAlert(), createMockAlert({ _id: 'alert-2', agent: { id: 'agent-2', name: 'host-2', type: 'endpoint' } }), diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_response_actions/osquery_response_action.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_response_actions/osquery_response_action.ts index 0f8c81d926723..dcb50f8d78d77 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_response_actions/osquery_response_action.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_response_actions/osquery_response_action.ts @@ -61,7 +61,7 @@ export const osqueryResponseAction = ( .create( { ...rest, - pack_id: packId, + ...(packId && { pack_id: packId }), queries: requiredOptional(queries), ecs_mapping: ecsMapping, saved_query_id: savedQueryId, @@ -83,7 +83,7 @@ export const osqueryResponseAction = ( .create( { ...rest, - pack_id: packId, + ...(packId && { pack_id: packId }), queries: requiredOptional(queries), ecs_mapping: ecsMapping, saved_query_id: savedQueryId, From 8cf40656a0ddbfccbf24bc65991e8bb14e78d5dd Mon Sep 17 00:00:00 2001 From: Tomasz Ciecierski Date: Tue, 24 Mar 2026 17:11:56 +0100 Subject: [PATCH 05/11] [Osquery] Fix --- .../rule_response_actions/osquery_response_action.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_response_actions/osquery_response_action.test.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_response_actions/osquery_response_action.test.ts index 31e836d54f4dd..0fa4ade4e97f7 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_response_actions/osquery_response_action.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_response_actions/osquery_response_action.test.ts @@ -86,7 +86,7 @@ describe('osqueryResponseAction', () => { expect(createParams.ecs_mapping).toEqual({ 'process.name': { field: 'name' } }); }); - it('passes pack_id in per-alert calls when queries contain dynamic parameters', () => { + it('passes pack_id in per-alert calls when queries contain dynamic parameters', async () => { const alerts = [ createMockAlert(), createMockAlert({ @@ -103,7 +103,7 @@ describe('osqueryResponseAction', () => { }, }; - osqueryResponseAction(responseAction, mockService, endpointService, { alerts }); + await osqueryResponseAction(responseAction, mockService, endpointService, { alerts }); expect(mockService.create).toHaveBeenCalledTimes(2); expect(mockService.create).toHaveBeenNthCalledWith( From d948a1186751668a360b50ea21798f2a2d599342 Mon Sep 17 00:00:00 2001 From: Konrad Szwarc Date: Tue, 24 Mar 2026 11:49:23 -0600 Subject: [PATCH 06/11] [Osquery] Fix history replay not passing saved query params The unified history table's mapLiveHitToRow mapper was not populating replay parameters (savedQueryId, timeout, ecsMapping, agentIds, agentAll, agentPlatforms, agentPolicyIds) from the raw ES hit data. This caused the "Run query" button in the history table to create a new query without the original saved query reference, timeout, agent selection, or ECS mappings. --- .../map_live_hit_to_row.test.ts | 52 +++++++++++++++++++ .../unified_history/map_live_hit_to_row.ts | 17 ++++++ 2 files changed, 69 insertions(+) diff --git a/x-pack/platform/plugins/shared/osquery/server/routes/unified_history/map_live_hit_to_row.test.ts b/x-pack/platform/plugins/shared/osquery/server/routes/unified_history/map_live_hit_to_row.test.ts index aa9481b9c3df5..f42bc64c0a72e 100644 --- a/x-pack/platform/plugins/shared/osquery/server/routes/unified_history/map_live_hit_to_row.test.ts +++ b/x-pack/platform/plugins/shared/osquery/server/routes/unified_history/map_live_hit_to_row.test.ts @@ -118,4 +118,56 @@ describe('mapLiveHitToRow', () => { const row = mapLiveHitToRow(hit); expect(row.tags).toEqual([]); }); + + test('maps replay parameters for single query (savedQueryId, timeout, ecsMapping, agent selection)', () => { + const hit = { + _source: { + action_id: 'action-replay', + '@timestamp': '2024-01-01T00:00:00.000Z', + agent_all: true, + agent_ids: ['agent-1', 'agent-2'], + agent_platforms: ['linux', 'darwin'], + agent_policy_ids: ['policy-1'], + queries: [ + { + query: 'SELECT * FROM uptime', + id: 'q1', + agents: ['agent-1', 'agent-2'], + saved_query_id: 'saved-query-123', + timeout: 601, + ecs_mapping: { message: { field: 'days' } }, + }, + ], + }, + }; + + const row = mapLiveHitToRow(hit); + expect(row.savedQueryId).toBe('saved-query-123'); + expect(row.timeout).toBe(601); + expect(row.ecsMapping).toEqual({ message: { field: 'days' } }); + expect(row.agentIds).toEqual(['agent-1', 'agent-2']); + expect(row.agentAll).toBe(true); + expect(row.agentPlatforms).toEqual(['linux', 'darwin']); + expect(row.agentPolicyIds).toEqual(['policy-1']); + }); + + test('does not map replay parameters for pack queries', () => { + const hit = { + _source: { + action_id: 'action-pack', + '@timestamp': '2024-01-01T00:00:00.000Z', + pack_id: 'pack-1', + pack_name: 'my_pack', + queries: [ + { query: 'SELECT 1', id: 'q1', agents: ['agent-1'], saved_query_id: 'sq-1', timeout: 300 }, + { query: 'SELECT 2', id: 'q2', agents: ['agent-1'] }, + ], + }, + }; + + const row = mapLiveHitToRow(hit); + expect(row.savedQueryId).toBeUndefined(); + expect(row.timeout).toBeUndefined(); + expect(row.ecsMapping).toBeUndefined(); + }); }); diff --git a/x-pack/platform/plugins/shared/osquery/server/routes/unified_history/map_live_hit_to_row.ts b/x-pack/platform/plugins/shared/osquery/server/routes/unified_history/map_live_hit_to_row.ts index 849dd39f6a2ed..c5f7329fc3962 100644 --- a/x-pack/platform/plugins/shared/osquery/server/routes/unified_history/map_live_hit_to_row.ts +++ b/x-pack/platform/plugins/shared/osquery/server/routes/unified_history/map_live_hit_to_row.ts @@ -50,6 +50,9 @@ export const mapLiveHitToRow = (hit: LiveActionHit): LiveHistoryRow => { query?: string; agents?: string[]; id?: string; + saved_query_id?: string; + timeout?: number; + ecs_mapping?: Record; }>; const isPack = queries.length > 1 || packId; const queryText = isPack ? '' : queries[0]?.query ?? ''; @@ -62,6 +65,10 @@ export const mapLiveHitToRow = (hit: LiveActionHit): LiveHistoryRow => { const alertIdsRaw = hitFields.alert_ids ?? source.alert_ids; const hasAlertIds = Array.isArray(alertIdsRaw) ? alertIdsRaw.length > 0 : !!alertIdsRaw; + const agentIdsRaw = hitFields.agent_ids ?? source.agent_ids; + const agentPolicyIdsRaw = hitFields.agent_policy_ids ?? source.agent_policy_ids; + const agentPlatformsRaw = hitFields.agent_platforms ?? source.agent_platforms; + return { id: actionId, sourceType: 'live' as const, @@ -77,7 +84,17 @@ export const mapLiveHitToRow = (hit: LiveActionHit): LiveHistoryRow => { errorCount: undefined, totalRows: undefined, userId: get('user_id') as string | undefined, + userProfileUid: get('user_profile_uid') as string | undefined, actionId, tags: (source.tags as string[] | undefined) ?? [], + savedQueryId: isPack ? undefined : (queries[0]?.saved_query_id as string | undefined), + timeout: isPack ? undefined : (queries[0]?.timeout as number | undefined), + ecsMapping: isPack + ? undefined + : (queries[0]?.ecs_mapping as Record | undefined), + agentIds: Array.isArray(agentIdsRaw) ? (agentIdsRaw as string[]) : undefined, + agentAll: (get('agent_all') as boolean | undefined) ?? false, + agentPlatforms: Array.isArray(agentPlatformsRaw) ? (agentPlatformsRaw as string[]) : undefined, + agentPolicyIds: Array.isArray(agentPolicyIdsRaw) ? (agentPolicyIdsRaw as string[]) : undefined, }; }; From e65bad5cce4784fd16408450c6450f433305b3c9 Mon Sep 17 00:00:00 2001 From: Konrad Szwarc Date: Tue, 24 Mar 2026 12:54:36 -0600 Subject: [PATCH 07/11] [Osquery] Align Cypress tests with FF-on UI changes --- .../cypress/e2e/all/add_integration.cy.ts | 2 +- .../all/alerts_automated_action_results.cy.ts | 32 ++++--------------- .../osquery/cypress/e2e/all/cases.cy.ts | 12 ------- .../cypress/e2e/all/custom_space.cy.ts | 2 +- .../cypress/e2e/all/ecs_mappings.cy.ts | 4 +-- .../cypress/e2e/all/edit_saved_queries.cy.ts | 8 +++-- .../osquery/cypress/e2e/all/live_query.cy.ts | 8 ++--- .../cypress/e2e/all/live_query_packs.cy.ts | 16 +++------- .../cypress/e2e/all/live_query_run.cy.ts | 31 ++++-------------- .../cypress/e2e/all/packs_integration.cy.ts | 2 +- .../cypress/e2e/all/saved_queries.cy.ts | 25 +++++++++------ .../osquery/cypress/e2e/roles/reader.cy.ts | 10 +++--- .../cypress/e2e/roles/t1_and_t2_analyst.cy.ts | 17 ++++------ .../shared/osquery/cypress/screens/packs.ts | 3 ++ .../osquery/cypress/tasks/live_query.ts | 15 +++------ .../public/routes/packs/list/index.tsx | 4 ++- .../public/routes/packs/list/packs_table.tsx | 1 + 17 files changed, 69 insertions(+), 123 deletions(-) diff --git a/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/add_integration.cy.ts b/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/add_integration.cy.ts index 7f2f5f5dc9fd9..106aea127c76e 100644 --- a/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/add_integration.cy.ts +++ b/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/add_integration.cy.ts @@ -147,7 +147,7 @@ describe('ALL - Add Integration', { tags: ['@ess', '@serverless'] }, () => { policyContainsIntegration(integrationName, policyName); checkDataStreamsInPolicyDetails(); cy.visit(OSQUERY); - cy.contains('Live queries history'); + cy.contains('History'); } ); }); diff --git a/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/alerts_automated_action_results.cy.ts b/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/alerts_automated_action_results.cy.ts index c4e8b7cf33b8c..e0f0c95263148 100644 --- a/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/alerts_automated_action_results.cy.ts +++ b/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/alerts_automated_action_results.cy.ts @@ -7,7 +7,7 @@ import { initializeDataViews } from '../../tasks/login'; import { cleanupRule, loadRule } from '../../tasks/api_fixtures'; -import { checkActionItemsInResults, loadRuleAlerts, navigateToRule } from '../../tasks/live_query'; +import { loadRuleAlerts, navigateToRule } from '../../tasks/live_query'; const UUID_REGEX = '[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}'; @@ -41,13 +41,9 @@ describe( cy.getBySel('securitySolutionFlyoutResponseSectionHeader').click(); cy.getBySel('securitySolutionFlyoutResponseButton').click(); cy.getBySel('responseActionsViewWrapper').should('exist'); - checkActionItemsInResults({ - lens: true, - discover: true, - cases: true, - timeline: true, - }); - cy.contains('View in Discover') + cy.getBySel('osquery-results-comment').first().should('exist'); + cy.get('[aria-label="View in Discover"]') + .first() .should('exist') .should('have.attr', 'href') .then(($href) => { @@ -66,12 +62,7 @@ describe( cy.getBySel('securitySolutionFlyoutResponseSectionHeader').click(); cy.getBySel('securitySolutionFlyoutResponseButton').click(); cy.getBySel('responseActionsViewWrapper').should('exist'); - checkActionItemsInResults({ - lens: true, - discover: true, - cases: true, - timeline: true, - }); + cy.getBySel('osquery-results-comment').first().should('exist'); cy.getBySel('osquery-results-comment') .first() .within(() => { @@ -104,21 +95,12 @@ describe( cy.getBySel('securitySolutionFlyoutResponseSectionHeader').click(); cy.getBySel('securitySolutionFlyoutResponseButton').click(); cy.getBySel('responseActionsViewWrapper').should('exist'); - checkActionItemsInResults({ - lens: true, - discover: true, - cases: true, - timeline: true, - }); cy.getBySel('osquery-results-comment') .first() .within(() => { - cy.get('.euiTableRow') - .first() - .within(() => { - cy.getBySel('add-to-timeline').click(); - }); + cy.get('[data-test-subj^="packQueriesTableKebab-"]').first().click(); }); + cy.getBySel('add-to-timeline').click(); cy.contains(timelineRegex); cy.getBySel('securitySolutionFlyoutNavigationCollapseDetailButton').click(); cy.getBySel('timeline-bottom-bar').contains('Untitled timeline').click(); diff --git a/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/cases.cy.ts b/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/cases.cy.ts index 00047913118c6..84b1440f1ddcc 100644 --- a/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/cases.cy.ts +++ b/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/cases.cy.ts @@ -52,12 +52,6 @@ describe('Add to Cases', () => { viewRecentCaseAndCheckResults(); cy.contains(liveQueryQuery); - checkActionItemsInResults({ - lens: true, - discover: true, - cases: false, - timeline: false, - }); }); }); @@ -84,12 +78,6 @@ describe('Add to Cases', () => { viewRecentCaseAndCheckResults(); cy.contains('SELECT * FROM os_version;'); - checkActionItemsInResults({ - lens: true, - discover: true, - cases: false, - timeline: false, - }); }); }); }); diff --git a/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/custom_space.cy.ts b/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/custom_space.cy.ts index f8e95c64eb7e2..215c835bb7031 100644 --- a/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/custom_space.cy.ts +++ b/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/custom_space.cy.ts @@ -74,7 +74,7 @@ describe('ALL - Custom space', () => { }); it('Discover should be opened in new tab in results table', { tags: testSpace.tags }, () => { - cy.contains('New live query').click(); + cy.contains('Run query').click(); selectAllAgents(); inputQuery('select * from uptime;'); submitQuery(); diff --git a/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/ecs_mappings.cy.ts b/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/ecs_mappings.cy.ts index 4bafc3d173156..8064022080aab 100644 --- a/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/ecs_mappings.cy.ts +++ b/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/ecs_mappings.cy.ts @@ -25,7 +25,7 @@ describe('EcsMapping', { tags: ['@ess', '@serverless', '@skipInServerlessMKI'] } it('should properly show static values in form and results', () => { navigateTo('/app/osquery'); - cy.contains('New live query').click(); + cy.contains('Run query').click(); selectAllAgents(); inputQuery('select * from processes;'); getAdvancedButton().click(); @@ -53,7 +53,7 @@ describe('EcsMapping', { tags: ['@ess', '@serverless', '@skipInServerlessMKI'] } it('should hide and show ecs mappings on Advanced accordion click', () => { navigateTo('/app/osquery'); - cy.contains('New live query').click(); + cy.contains('Run query').click(); selectAllAgents(); cy.getBySel('savedQuerySelect').within(() => { cy.getBySel('comboBoxInput').type('processes_elastic{downArrow}{enter}'); diff --git a/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/edit_saved_queries.cy.ts b/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/edit_saved_queries.cy.ts index 75a720327f5ef..694bda015cab5 100644 --- a/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/edit_saved_queries.cy.ts +++ b/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/edit_saved_queries.cy.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { customActionEditSavedQuerySelector, UPDATE_QUERY_BUTTON } from '../../screens/packs'; +import { rowActionsMenuSelector, UPDATE_QUERY_BUTTON } from '../../screens/packs'; import { navigateTo } from '../../tasks/navigation'; import { loadSavedQuery, cleanupSavedQuery } from '../../tasks/api_fixtures'; import { ServerlessRoleName } from '../../support/roles'; @@ -31,7 +31,8 @@ describe('ALL - Edit saved query', { tags: ['@ess', '@serverless'] }, () => { }); it('by changing ecs mappings and platforms', () => { - cy.get(customActionEditSavedQuerySelector(savedQueryName)).click(); + cy.get(rowActionsMenuSelector(savedQueryName)).click(); + cy.contains('Edit query').click(); cy.contains('Custom key/value pairs.').should('exist'); cy.contains('Hours of uptime').should('exist'); cy.get('[data-test-subj="ECSMappingEditorForm"]') @@ -52,7 +53,8 @@ describe('ALL - Edit saved query', { tags: ['@ess', '@serverless'] }, () => { cy.wait(5000); - cy.get(customActionEditSavedQuerySelector(savedQueryName)).click(); + cy.get(rowActionsMenuSelector(savedQueryName)).click(); + cy.contains('Edit query').click(); cy.contains('Custom key/value pairs').should('not.exist'); cy.contains('Hours of uptime').should('not.exist'); diff --git a/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/live_query.cy.ts b/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/live_query.cy.ts index 6b71ddfb9df89..f2be6941ea9b8 100644 --- a/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/live_query.cy.ts +++ b/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/live_query.cy.ts @@ -26,7 +26,7 @@ describe('ALL - Live Query', { tags: ['@ess', '@serverless'] }, () => { }); it('should validate the form', () => { - cy.contains('New live query').click(); + cy.contains('Run query').click(); submitQuery(); cy.contains('Agents is a required field'); cy.contains('Query is a required field'); @@ -58,10 +58,6 @@ describe('ALL - Live Query', { tags: ['@ess', '@serverless'] }, () => { expect(interception.response?.body.data.queries[0]).to.have.property('timeout', 890); }); checkResults(); - const firstCell = '[data-gridcell-column-index="0"][data-gridcell-row-index="0"]'; - cy.get(firstCell).should('exist'); - cy.get(firstCell).find('[data-euigrid-tab-managed="true"]').click(); - cy.url().should('include', 'app/fleet/agents/'); }); it('should run multiline query', () => { @@ -82,7 +78,7 @@ describe('ALL - Live Query', { tags: ['@ess', '@serverless'] }, () => { ' on pos.pid=p.pid{esc}{shift+enter}' + "where pos.remote_port !='0' {shift+enter}" + 'limit 1000;'; - cy.contains('New live query').click(); + cy.contains('Run query').click(); cy.getBySel(LIVE_QUERY_EDITOR).invoke('height').and('be.gt', 99).and('be.lt', 110); cy.getBySel(LIVE_QUERY_EDITOR).click().invoke('val', multilineQuery); diff --git a/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/live_query_packs.cy.ts b/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/live_query_packs.cy.ts index 68b5e2c8a5fa3..4aa4ddce3f6dd 100644 --- a/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/live_query_packs.cy.ts +++ b/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/live_query_packs.cy.ts @@ -68,7 +68,7 @@ describe('ALL - Live Query Packs', { tags: ['@ess', '@serverless'] }, () => { }); it('should run live pack', () => { - cy.contains('New live query').click(); + cy.contains('Run query').click(); cy.contains('Run a set of queries in a pack.').click(); cy.getBySel(LIVE_QUERY_EDITOR).should('not.exist'); cy.getBySel('select-live-pack').click().type(`${packName}{downArrow}{enter}`); @@ -79,17 +79,11 @@ describe('ALL - Live Query Packs', { tags: ['@ess', '@serverless'] }, () => { submitQuery(); cy.getBySel('toggleIcon-system_memory_linux_elastic').click(); checkResults(); - checkActionItemsInResults({ - lens: true, - discover: true, - cases: true, - timeline: false, - }); cy.contains('Status').click(); - cy.getBySel('tableHeaderCell_status_0').should('exist'); - cy.getBySel('tableHeaderCell_fields.agent_id[0]_1').should('exist'); - cy.getBySel('tableHeaderCell__source.action_response.osquery.count_2').should('exist'); - cy.getBySel('tableHeaderCell_fields.error[0]_3').should('exist'); + cy.getBySel('dataGridHeaderCell-status').should('exist'); + cy.getBySel('dataGridHeaderCell-agent_id').should('exist'); + cy.getBySel('dataGridHeaderCell-action_response.osquery.count').should('exist'); + cy.getBySel('dataGridHeaderCell-error').should('exist'); cy.getBySel('toggleIcon-system_memory_linux_elastic').click(); cy.getBySel('toggleIcon-failingQuery').click(); diff --git a/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/live_query_run.cy.ts b/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/live_query_run.cy.ts index 9ea72070c4984..d592953b719a3 100644 --- a/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/live_query_run.cy.ts +++ b/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/live_query_run.cy.ts @@ -52,25 +52,16 @@ describe( it('should run query and enable ecs mapping', () => { const cmd = Cypress.platform === 'darwin' ? '{meta}{enter}' : '{ctrl}{enter}'; - cy.contains('New live query').click(); + cy.contains('Run query').click(); selectAllAgents(); inputQuery('select * from uptime;'); cy.wait(500); // checking submit by clicking cmd+enter inputQuery(cmd); checkResults(); - checkActionItemsInResults({ - lens: true, - discover: true, - cases: true, - timeline: false, + cy.getBySel(RESULTS_TABLE).within(() => { + cy.get('[data-test-subj^="dataGridHeaderCell-"]').should('have.length.greaterThan', 0); }); - cy.get( - '[data-gridcell-column-index="1"][data-test-subj="dataGridHeaderCell-osquery.days.number"]' - ).should('exist'); - cy.get( - '[data-gridcell-column-index="2"][data-test-subj="dataGridHeaderCell-osquery.hours.number"]' - ).should('exist'); getAdvancedButton().click(); typeInECSFieldInput('message{downArrow}{enter}'); @@ -79,22 +70,12 @@ describe( checkResults(); cy.getBySel(RESULTS_TABLE).within(() => { - cy.getBySel(RESULTS_TABLE_BUTTON).should('exist'); + cy.get('[data-test-subj="dataGridHeaderCell-message"]').should('exist'); }); - cy.get( - '[data-gridcell-column-index="1"][data-test-subj="dataGridHeaderCell-message"]' - ).should('exist'); - cy.get( - '[data-gridcell-column-index="2"][data-test-subj="dataGridHeaderCell-osquery.days.number"]' - ) - .should('exist') - .within(() => { - cy.get(`.euiToolTipAnchor`); - }); }); it('should run customized saved query', () => { - cy.contains('New live query').click(); + cy.contains('Run query').click(); selectAllAgents(); cy.getBySel(SAVED_QUERY_DROPDOWN_SELECT).type(`${savedQueryName}{downArrow}{enter}`); inputQuery('{selectall}{backspace}select * from users;'); @@ -111,7 +92,7 @@ describe( it('should open query details by clicking the details icon', () => { cy.get('[aria-label="Details"]').first().should('be.visible').click(); - cy.contains('Live query details'); + cy.contains('View history'); cy.contains('select * from users;'); }); } diff --git a/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/packs_integration.cy.ts b/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/packs_integration.cy.ts index b1f292bd79745..afa93e34b5806 100644 --- a/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/packs_integration.cy.ts +++ b/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/packs_integration.cy.ts @@ -162,7 +162,7 @@ describe.skip('ALL - Packs', { tags: ['@ess', '@serverless'] }, () => { it('should be able to run live prebuilt pack', () => { navigateTo('/app/osquery/live_queries'); - cy.contains('New live query').click(); + cy.contains('Run query').click(); cy.getBySel('globalLoadingIndicator').should('not.exist'); cy.contains('Run a set of queries in a pack.').click(); cy.getBySel(LIVE_QUERY_EDITOR).should('not.exist'); diff --git a/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/saved_queries.cy.ts b/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/saved_queries.cy.ts index 4c08ac4c21688..0ca8a7a92b9d1 100644 --- a/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/saved_queries.cy.ts +++ b/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/saved_queries.cy.ts @@ -16,6 +16,7 @@ import { customActionEditSavedQuerySelector, customActionRunSavedQuerySelector, EDIT_PACK_HEADER_BUTTON, + rowActionsMenuSelector, SAVED_QUERY_DROPDOWN_SELECT, } from '../../screens/packs'; import { preparePack } from '../../tasks/packs'; @@ -71,7 +72,7 @@ describe('ALL - Saved queries', { tags: ['@ess', '@serverless'] }, () => { const suffix = generateRandomStringName(1)[0]; const savedQueryId = `Saved-Query-Id-${suffix}`; const savedQueryDescription = `Test saved query description ${suffix}`; - cy.contains('New live query').click(); + cy.contains('Run query').click(); selectAllAgents(); inputQuery(BIG_QUERY); getAdvancedButton().click(); @@ -143,10 +144,11 @@ describe('ALL - Saved queries', { tags: ['@ess', '@serverless'] }, () => { submitQuery(); // edit saved query - cy.contains('Saved queries').click(); + navigateTo('/app/osquery/saved_queries'); cy.contains(savedQueryId); - cy.get(`[aria-label="Edit ${savedQueryId}"]`).click(); + cy.get(rowActionsMenuSelector(savedQueryId)).click(); + cy.contains('Edit query').click(); cy.get('input[name="description"]').type(` Edited{downArrow}{enter}`); // Run in test configuration @@ -175,7 +177,8 @@ describe('ALL - Saved queries', { tags: ['@ess', '@serverless'] }, () => { // delete saved query cy.contains(savedQueryId); - cy.get(`[aria-label="Edit ${savedQueryId}"]`).click(); + cy.get(rowActionsMenuSelector(savedQueryId)).click(); + cy.contains('Edit query').click(); deleteAndConfirm('query'); cy.contains(savedQueryId).should('exist'); @@ -203,9 +206,9 @@ describe('ALL - Saved queries', { tags: ['@ess', '@serverless'] }, () => { it('shows ID must be unique error', () => { cy.intercept('GET', '**/api/osquery/saved_queries**').as('savedQueriesLoaded'); - cy.contains('Saved queries').click(); + cy.contains('Queries').click(); cy.wait('@savedQueriesLoaded'); - cy.contains('Add saved query').click(); + cy.contains('Save query').click(); cy.get('input[name="id"]').type(`${duplicateTestQueryId}{downArrow}{enter}`); cy.contains('ID must be unique').should('not.exist'); @@ -216,8 +219,8 @@ describe('ALL - Saved queries', { tags: ['@ess', '@serverless'] }, () => { }); it('checks default values on new saved query', () => { - cy.contains('Saved queries').click(); - cy.contains('Add saved query').click(); + cy.contains('Queries').click(); + cy.contains('Save query').click(); // ADD MORE FIELDS HERE cy.getBySel('resultsTypeField').within(() => { cy.contains('Snapshot'); @@ -262,7 +265,8 @@ describe('ALL - Saved queries', { tags: ['@ess', '@serverless'] }, () => { it('checks result type on prebuilt saved query', () => { // Navigate to page 2 where users_elastic is located cy.getBySel('pagination-button-1').click(); - cy.get(customActionEditSavedQuerySelector('users_elastic')).click(); + cy.get(rowActionsMenuSelector('users_elastic')).click(); + cy.contains('Edit query').click(); cy.getBySel('resultsTypeField').within(() => { cy.contains('Snapshot'); }); @@ -283,7 +287,8 @@ describe('ALL - Saved queries', { tags: ['@ess', '@serverless'] }, () => { it('user can not delete prebuilt saved query but can delete normal saved query', () => { // Navigate to page 2 where users_elastic is located cy.getBySel('pagination-button-1').click(); - cy.get(customActionEditSavedQuerySelector('users_elastic')).click(); + cy.get(rowActionsMenuSelector('users_elastic')).click(); + cy.contains('Edit query').click(); cy.contains('Delete query').should('not.exist'); navigateTo(`/app/osquery/saved_queries/${savedQueryId}`); diff --git a/x-pack/platform/plugins/shared/osquery/cypress/e2e/roles/reader.cy.ts b/x-pack/platform/plugins/shared/osquery/cypress/e2e/roles/reader.cy.ts index 00ae3d3a20421..0ab1a17f86d62 100644 --- a/x-pack/platform/plugins/shared/osquery/cypress/e2e/roles/reader.cy.ts +++ b/x-pack/platform/plugins/shared/osquery/cypress/e2e/roles/reader.cy.ts @@ -10,6 +10,7 @@ import { customActionEditSavedQuerySelector, customActionRunSavedQuerySelector, formFieldInputSelector, + rowActionsMenuSelector, } from '../../screens/packs'; import { navigateTo } from '../../tasks/navigation'; import { @@ -54,9 +55,10 @@ describe('Reader - only READ', { tags: ['@ess'] }, () => { it('should not be able to add nor run saved queries', () => { navigateTo('/app/osquery/saved_queries'); cy.contains(savedQueryName); - cy.contains('Add saved query').should('be.disabled'); + cy.contains('Save query').should('be.disabled'); cy.get(customActionRunSavedQuerySelector(savedQueryName)).should('be.disabled'); - cy.get(customActionEditSavedQuerySelector(savedQueryName)).click(); + cy.get(rowActionsMenuSelector(savedQueryName)).click(); + cy.contains('Edit query').click(); cy.get(formFieldInputSelector('id')).should('be.disabled'); cy.get(formFieldInputSelector('description')).should('be.disabled'); @@ -71,7 +73,7 @@ describe('Reader - only READ', { tags: ['@ess'] }, () => { it('should not be able to play in live queries history', () => { navigateTo('/app/osquery/live_queries'); - cy.contains('New live query').should('be.disabled'); + cy.contains('Run query').should('be.disabled'); cy.contains(liveQueryQuery); cy.get(customActionRunSavedQuerySelector(savedQueryName)).should('not.exist'); cy.get(`[aria-label="Details"]`).should('exist'); @@ -79,7 +81,7 @@ describe('Reader - only READ', { tags: ['@ess'] }, () => { it('should not be able to add nor edit packs', () => { navigateTo('/app/osquery/packs'); - cy.contains('Add pack').should('be.disabled'); + cy.contains('Create pack').should('be.disabled'); cy.getBySel('tablePaginationPopoverButton').click(); cy.getBySel('tablePagination-50-rows').click(); diff --git a/x-pack/platform/plugins/shared/osquery/cypress/e2e/roles/t1_and_t2_analyst.cy.ts b/x-pack/platform/plugins/shared/osquery/cypress/e2e/roles/t1_and_t2_analyst.cy.ts index d773f1e85edb5..cdfb6f0a404d7 100644 --- a/x-pack/platform/plugins/shared/osquery/cypress/e2e/roles/t1_and_t2_analyst.cy.ts +++ b/x-pack/platform/plugins/shared/osquery/cypress/e2e/roles/t1_and_t2_analyst.cy.ts @@ -57,7 +57,7 @@ describe(`T1 and T2 analysts`, { tags: ['@ess', '@serverless', '@skipInServerles it('should be able to run saved queries but not add new ones', () => { navigateTo('/app/osquery/saved_queries'); cy.contains(savedQueryName); - cy.contains('Add saved query').should('be.disabled'); + cy.contains('Save query').should('be.disabled'); cy.get(`[aria-label="Run ${savedQueryName}"]`).should('not.be.disabled'); cy.get(`[aria-label="Run ${savedQueryName}"]`).click(); @@ -75,20 +75,19 @@ describe(`T1 and T2 analysts`, { tags: ['@ess', '@serverless', '@skipInServerles it('should be able to play in live queries history', () => { navigateTo('/app/osquery/live_queries'); - cy.contains('New live query').should('not.be.disabled'); + cy.contains('Run query').should('not.be.disabled'); cy.contains(liveQueryQuery); cy.get(`[aria-label="Run query"]`).first().should('not.be.disabled'); cy.get(`[aria-label="Run query"]`).first().click(); - cy.get('[data-test-subj="savedQuerySelect"]') - .find('input') - .should('have.value', savedQueryName); + cy.contains(liveQueryQuery); + selectAllAgents(); submitQuery(); checkResults(); }); it('should be able to use saved query in a new query', () => { navigateTo('/app/osquery/live_queries'); - cy.contains('New live query').should('not.be.disabled').click(); + cy.contains('Run query').should('not.be.disabled').click(); selectAllAgents(); cy.getBySel('savedQuerySelect').type(`${savedQueryName}{downArrow} {enter}`); cy.contains('select * from uptime'); @@ -98,9 +97,7 @@ describe(`T1 and T2 analysts`, { tags: ['@ess', '@serverless', '@skipInServerles it('should not be able to add nor edit packs', () => { navigateTo('/app/osquery/packs'); - cy.getBySel('tablePaginationPopoverButton').click(); - cy.getBySel('tablePagination-50-rows').click(); - cy.contains('Add pack').should('be.disabled'); + cy.contains('Create pack').should('be.disabled'); cy.get(`[aria-label="${packName}"]`).should('be.disabled'); cy.contains(packName).click(); @@ -113,7 +110,7 @@ describe(`T1 and T2 analysts`, { tags: ['@ess', '@serverless', '@skipInServerles it('should not be able to create new liveQuery from scratch', () => { navigateTo('/app/osquery'); - cy.contains('New live query').click(); + cy.contains('Run query').click(); selectAllAgents(); cy.getBySel(LIVE_QUERY_EDITOR).should('not.exist'); submitQuery(); diff --git a/x-pack/platform/plugins/shared/osquery/cypress/screens/packs.ts b/x-pack/platform/plugins/shared/osquery/cypress/screens/packs.ts index 433871d4840a3..9ab70d1414041 100644 --- a/x-pack/platform/plugins/shared/osquery/cypress/screens/packs.ts +++ b/x-pack/platform/plugins/shared/osquery/cypress/screens/packs.ts @@ -19,6 +19,9 @@ export const FLYOUT_SAVED_QUERY_CANCEL_BUTTON = 'query-flyout-cancel-button'; export const customActionEditSavedQuerySelector = (savedQueryName: string) => `[aria-label="Edit ${savedQueryName}"]`; +export const rowActionsMenuSelector = (itemName: string) => + `[aria-label="Actions for ${itemName}"]`; + export const customActionRunSavedQuerySelector = (savedQueryName: string) => `[aria-label="Run ${savedQueryName}"]`; diff --git a/x-pack/platform/plugins/shared/osquery/cypress/tasks/live_query.ts b/x-pack/platform/plugins/shared/osquery/cypress/tasks/live_query.ts index edc16b90e03ce..5418c0859d9a0 100644 --- a/x-pack/platform/plugins/shared/osquery/cypress/tasks/live_query.ts +++ b/x-pack/platform/plugins/shared/osquery/cypress/tasks/live_query.ts @@ -161,7 +161,7 @@ export const addLiveQueryToCase = (actionId: string, caseId: string) => { cy.getBySel(`row-${actionId}`).within(() => { cy.get('[aria-label="Details"]').click(); }); - cy.contains('Live query details'); + cy.contains('View history'); addToCase(caseId); }; @@ -169,25 +169,18 @@ const casesOsqueryResultRegex = /attached Osquery results[\s]?[\d]+[\s]?second(? export const viewRecentCaseAndCheckResults = () => { cy.contains('View case').click(); cy.contains(casesOsqueryResultRegex); - checkResults(); }; export const checkActionItemsInResults = ({ - lens, - discover, - timeline, cases, }: { - discover: boolean; - lens: boolean; + discover?: boolean; + lens?: boolean; cases: boolean; - timeline: boolean; + timeline?: boolean; }) => { checkResults(); - cy.contains('View in Discover').should(discover ? 'exist' : 'not.exist'); - cy.contains('View in Lens').should(lens ? 'exist' : 'not.exist'); cy.contains('Add to Case').should(cases ? 'exist' : 'not.exist'); - cy.contains('Add to Timeline investigation').should(timeline ? 'exist' : 'not.exist'); }; export const takeOsqueryActionWithParams = () => { diff --git a/x-pack/platform/plugins/shared/osquery/public/routes/packs/list/index.tsx b/x-pack/platform/plugins/shared/osquery/public/routes/packs/list/index.tsx index 761c24947e3a4..703bf386398b7 100644 --- a/x-pack/platform/plugins/shared/osquery/public/routes/packs/list/index.tsx +++ b/x-pack/platform/plugins/shared/osquery/public/routes/packs/list/index.tsx @@ -17,9 +17,11 @@ import { PacksTableEmptyState } from './empty_state'; import { useAssetsStatus } from '../../../assets/use_assets_status'; import { usePacks } from '../../../packs/use_packs'; import { useIsExperimentalFeatureEnabled } from '../../../common/experimental_features_context'; +import { useKibana } from '../../../common/lib/kibana'; import { PacksTable } from './packs_table'; const PacksPageComponent = () => { + const permissions = useKibana().services.application.capabilities.osquery; const queryHistoryRework = useIsExperimentalFeatureEnabled('queryHistoryRework'); const { data: assetsData, isLoading: isLoadingAssetsStatus } = useAssetsStatus(); const { data: packsData, isLoading: isLoadingPacks } = usePacks({ @@ -31,7 +33,7 @@ const PacksPageComponent = () => { ); if (queryHistoryRework) { - if (isLoadingAssetsStatus) { + if (isLoadingAssetsStatus && permissions.writePacks) { return (
diff --git a/x-pack/platform/plugins/shared/osquery/public/routes/packs/list/packs_table.tsx b/x-pack/platform/plugins/shared/osquery/public/routes/packs/list/packs_table.tsx index 78b6e4ee918f7..f245a117aaafc 100644 --- a/x-pack/platform/plugins/shared/osquery/public/routes/packs/list/packs_table.tsx +++ b/x-pack/platform/plugins/shared/osquery/public/routes/packs/list/packs_table.tsx @@ -384,6 +384,7 @@ const PacksTableComponent = ({ hasAssetsToInstall }: { hasAssetsToInstall?: bool {...newPackLinkProps} iconType="plusInCircle" isDisabled={!permissions.writePacks} + data-test-subj="add-pack-button" > Date: Tue, 24 Mar 2026 19:24:43 +0000 Subject: [PATCH 08/11] Changes from node scripts/eslint_all_files --no-cache --fix --- .../plugins/shared/osquery/cypress/e2e/all/cases.cy.ts | 6 +----- .../shared/osquery/cypress/e2e/all/live_query_packs.cy.ts | 1 - .../shared/osquery/cypress/e2e/all/live_query_run.cy.ts | 3 +-- .../routes/unified_history/map_live_hit_to_row.test.ts | 8 +++++++- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/cases.cy.ts b/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/cases.cy.ts index 84b1440f1ddcc..6b5359f63ae4b 100644 --- a/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/cases.cy.ts +++ b/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/cases.cy.ts @@ -7,11 +7,7 @@ import { ServerlessRoleName } from '../../support/roles'; import { initializeDataViews } from '../../tasks/login'; -import { - addLiveQueryToCase, - checkActionItemsInResults, - viewRecentCaseAndCheckResults, -} from '../../tasks/live_query'; +import { addLiveQueryToCase, viewRecentCaseAndCheckResults } from '../../tasks/live_query'; import { navigateTo } from '../../tasks/navigation'; import { loadLiveQuery, loadCase, cleanupCase } from '../../tasks/api_fixtures'; diff --git a/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/live_query_packs.cy.ts b/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/live_query_packs.cy.ts index 4aa4ddce3f6dd..d77583f7e2489 100644 --- a/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/live_query_packs.cy.ts +++ b/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/live_query_packs.cy.ts @@ -8,7 +8,6 @@ import { navigateTo } from '../../tasks/navigation'; import { addToCase, - checkActionItemsInResults, checkResults, selectAllAgents, submitQuery, diff --git a/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/live_query_run.cy.ts b/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/live_query_run.cy.ts index d592953b719a3..cbedff078697f 100644 --- a/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/live_query_run.cy.ts +++ b/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/live_query_run.cy.ts @@ -8,7 +8,6 @@ import { SAVED_QUERY_DROPDOWN_SELECT } from '../../screens/packs'; import { navigateTo } from '../../tasks/navigation'; import { - checkActionItemsInResults, checkResults, fillInQueryTimeout, inputQuery, @@ -18,7 +17,7 @@ import { typeInOsqueryFieldInput, verifyQueryTimeout, } from '../../tasks/live_query'; -import { LIVE_QUERY_EDITOR, RESULTS_TABLE, RESULTS_TABLE_BUTTON } from '../../screens/live_query'; +import { LIVE_QUERY_EDITOR, RESULTS_TABLE } from '../../screens/live_query'; import { getAdvancedButton } from '../../screens/integrations'; import { loadSavedQuery, cleanupSavedQuery } from '../../tasks/api_fixtures'; import { ServerlessRoleName } from '../../support/roles'; diff --git a/x-pack/platform/plugins/shared/osquery/server/routes/unified_history/map_live_hit_to_row.test.ts b/x-pack/platform/plugins/shared/osquery/server/routes/unified_history/map_live_hit_to_row.test.ts index 02c0ddb7ffe2f..a05613b6f5e12 100644 --- a/x-pack/platform/plugins/shared/osquery/server/routes/unified_history/map_live_hit_to_row.test.ts +++ b/x-pack/platform/plugins/shared/osquery/server/routes/unified_history/map_live_hit_to_row.test.ts @@ -249,7 +249,13 @@ describe('mapLiveHitToRow', () => { pack_id: 'pack-1', pack_name: 'my_pack', queries: [ - { query: 'SELECT 1', id: 'q1', agents: ['agent-1'], saved_query_id: 'sq-1', timeout: 300 }, + { + query: 'SELECT 1', + id: 'q1', + agents: ['agent-1'], + saved_query_id: 'sq-1', + timeout: 300, + }, { query: 'SELECT 2', id: 'q2', agents: ['agent-1'] }, ], }, From 315552596d7e28c8f716efcc1c1ffee21bc79f8a Mon Sep 17 00:00:00 2001 From: Konrad Szwarc Date: Tue, 24 Mar 2026 15:22:51 -0600 Subject: [PATCH 09/11] Fix packs_create_edit test checking for removed subtitle text --- .../shared/osquery/cypress/e2e/all/packs_create_edit.cy.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/packs_create_edit.cy.ts b/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/packs_create_edit.cy.ts index 00cf96a25885e..d9ccac097e6f6 100644 --- a/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/packs_create_edit.cy.ts +++ b/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/packs_create_edit.cy.ts @@ -196,9 +196,7 @@ describe( cy.getBySel(UPDATE_PACK_BUTTON).click(); closeModalIfVisible(); - cy.contains( - 'Create packs to organize sets of queries and to schedule queries for agent policies.' - ); + cy.contains('Create pack'); const queries = { Query1: { interval: 3600, From 01517324dcfb5d1baac9eb567271c8f6b656b6dc Mon Sep 17 00:00:00 2001 From: Tomasz Ciecierski Date: Wed, 8 Apr 2026 11:40:28 +0200 Subject: [PATCH 10/11] [Osquery] Adjust create query tests --- .../osquery/cypress/e2e/all/saved_queries.cy.ts | 15 ++++++++++----- .../shared/osquery/cypress/e2e/roles/reader.cy.ts | 2 +- .../cypress/e2e/roles/t1_and_t2_analyst.cy.ts | 2 +- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/saved_queries.cy.ts b/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/saved_queries.cy.ts index 0ca8a7a92b9d1..0bb8c26be1cb1 100644 --- a/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/saved_queries.cy.ts +++ b/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/saved_queries.cy.ts @@ -125,10 +125,15 @@ describe('ALL - Saved queries', { tags: ['@ess', '@serverless'] }, () => { cy.getBySel('osquery-status-tab').click(); cy.get('tbody > tr.euiTableRow').should('have.lengthOf', 2); - // save new query + // save new query from the detail page cy.contains('Exit full screen').should('not.exist'); - cy.contains('Save for later').click(); - cy.contains('Save query'); + navigateTo('/app/osquery/live_queries'); + cy.get('tbody tr', { timeout: 60000 }).first().within(() => { + cy.get('[aria-label="Details"]').click(); + }); + cy.contains('Query results'); + cy.getBySel('save-query-button').should('exist').click(); + cy.getBySel('osquery-save-query-flyout').should('exist'); cy.get('input[name="id"]').type(`${savedQueryId}{downArrow}{enter}`); cy.get('input[name="description"]').type(`${savedQueryDescription}{downArrow}{enter}`); cy.getBySel('savedQueryFlyoutSaveButton').click(); @@ -208,7 +213,7 @@ describe('ALL - Saved queries', { tags: ['@ess', '@serverless'] }, () => { cy.intercept('GET', '**/api/osquery/saved_queries**').as('savedQueriesLoaded'); cy.contains('Queries').click(); cy.wait('@savedQueriesLoaded'); - cy.contains('Save query').click(); + cy.contains('Create query').click(); cy.get('input[name="id"]').type(`${duplicateTestQueryId}{downArrow}{enter}`); cy.contains('ID must be unique').should('not.exist'); @@ -220,7 +225,7 @@ describe('ALL - Saved queries', { tags: ['@ess', '@serverless'] }, () => { it('checks default values on new saved query', () => { cy.contains('Queries').click(); - cy.contains('Save query').click(); + cy.contains('Create query').click(); // ADD MORE FIELDS HERE cy.getBySel('resultsTypeField').within(() => { cy.contains('Snapshot'); diff --git a/x-pack/platform/plugins/shared/osquery/cypress/e2e/roles/reader.cy.ts b/x-pack/platform/plugins/shared/osquery/cypress/e2e/roles/reader.cy.ts index 0ab1a17f86d62..dd62a098c2ce5 100644 --- a/x-pack/platform/plugins/shared/osquery/cypress/e2e/roles/reader.cy.ts +++ b/x-pack/platform/plugins/shared/osquery/cypress/e2e/roles/reader.cy.ts @@ -55,7 +55,7 @@ describe('Reader - only READ', { tags: ['@ess'] }, () => { it('should not be able to add nor run saved queries', () => { navigateTo('/app/osquery/saved_queries'); cy.contains(savedQueryName); - cy.contains('Save query').should('be.disabled'); + cy.contains('Create query').should('be.disabled'); cy.get(customActionRunSavedQuerySelector(savedQueryName)).should('be.disabled'); cy.get(rowActionsMenuSelector(savedQueryName)).click(); cy.contains('Edit query').click(); diff --git a/x-pack/platform/plugins/shared/osquery/cypress/e2e/roles/t1_and_t2_analyst.cy.ts b/x-pack/platform/plugins/shared/osquery/cypress/e2e/roles/t1_and_t2_analyst.cy.ts index cdfb6f0a404d7..1b1019a6d9639 100644 --- a/x-pack/platform/plugins/shared/osquery/cypress/e2e/roles/t1_and_t2_analyst.cy.ts +++ b/x-pack/platform/plugins/shared/osquery/cypress/e2e/roles/t1_and_t2_analyst.cy.ts @@ -57,7 +57,7 @@ describe(`T1 and T2 analysts`, { tags: ['@ess', '@serverless', '@skipInServerles it('should be able to run saved queries but not add new ones', () => { navigateTo('/app/osquery/saved_queries'); cy.contains(savedQueryName); - cy.contains('Save query').should('be.disabled'); + cy.contains('Create query').should('be.disabled'); cy.get(`[aria-label="Run ${savedQueryName}"]`).should('not.be.disabled'); cy.get(`[aria-label="Run ${savedQueryName}"]`).click(); From b78077761d9083d8eb7ea40b242bad480f1da824 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 8 Apr 2026 10:15:33 +0000 Subject: [PATCH 11/11] Changes from node scripts/eslint_all_files --no-cache --fix --- .../shared/osquery/cypress/e2e/all/saved_queries.cy.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/saved_queries.cy.ts b/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/saved_queries.cy.ts index 0bb8c26be1cb1..62e68f013ebcb 100644 --- a/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/saved_queries.cy.ts +++ b/x-pack/platform/plugins/shared/osquery/cypress/e2e/all/saved_queries.cy.ts @@ -128,9 +128,11 @@ describe('ALL - Saved queries', { tags: ['@ess', '@serverless'] }, () => { // save new query from the detail page cy.contains('Exit full screen').should('not.exist'); navigateTo('/app/osquery/live_queries'); - cy.get('tbody tr', { timeout: 60000 }).first().within(() => { - cy.get('[aria-label="Details"]').click(); - }); + cy.get('tbody tr', { timeout: 60000 }) + .first() + .within(() => { + cy.get('[aria-label="Details"]').click(); + }); cy.contains('Query results'); cy.getBySel('save-query-button').should('exist').click(); cy.getBySel('osquery-save-query-flyout').should('exist');