diff --git a/.buildkite/ftr_base_serverless_configs.yml b/.buildkite/ftr_base_serverless_configs.yml index 5e7baeb3c3aea..2ff9ba6678462 100644 --- a/.buildkite/ftr_base_serverless_configs.yml +++ b/.buildkite/ftr_base_serverless_configs.yml @@ -1,6 +1,8 @@ disabled: # Base config files, only necessary to inform config finding script + # Serverless deployment-agnostic default config for api-integration tests + - x-pack/test/api_integration/deployment_agnostic/default_configs/serverless.config.base.ts # Serverless base config files - x-pack/test_serverless/api_integration/config.base.ts - x-pack/test_serverless/functional/config.base.ts diff --git a/.buildkite/ftr_oblt_serverless_configs.yml b/.buildkite/ftr_oblt_serverless_configs.yml index 085c25f2d80a6..8fe505ff0e93e 100644 --- a/.buildkite/ftr_oblt_serverless_configs.yml +++ b/.buildkite/ftr_oblt_serverless_configs.yml @@ -26,3 +26,5 @@ enabled: - x-pack/test_serverless/functional/test_suites/observability/common_configs/config.group5.ts - x-pack/test_serverless/functional/test_suites/observability/common_configs/config.group6.ts - x-pack/test_serverless/functional/test_suites/observability/config.screenshots.ts + # serverless config files that run deployment-agnostic tests + - x-pack/test/api_integration/deployment_agnostic/oblt.serverless.config.ts diff --git a/.buildkite/ftr_platform_stateful_configs.yml b/.buildkite/ftr_platform_stateful_configs.yml index 71ba8932271e8..a984afc263170 100644 --- a/.buildkite/ftr_platform_stateful_configs.yml +++ b/.buildkite/ftr_platform_stateful_configs.yml @@ -1,4 +1,6 @@ disabled: + # Stateful base config for deployment-agnostic tests + - x-pack/test/api_integration/deployment_agnostic/default_configs/stateful.config.base.ts # Base config files, only necessary to inform config finding script - test/functional/config.base.js - test/functional/firefox/config.base.ts @@ -155,7 +157,6 @@ enabled: - x-pack/test/api_integration/apis/monitoring/config.ts - x-pack/test/api_integration/apis/monitoring_collection/config.ts - x-pack/test/api_integration/apis/osquery/config.ts - - x-pack/test/api_integration/apis/painless_lab/config.ts - x-pack/test/api_integration/apis/search/config.ts - x-pack/test/api_integration/apis/searchprofiler/config.ts - x-pack/test/api_integration/apis/security/config.ts @@ -359,3 +360,5 @@ enabled: - x-pack/performance/journeys_e2e/apm_service_inventory.ts - x-pack/performance/journeys_e2e/infra_hosts_view.ts - x-pack/test/custom_branding/config.ts + # stateful config files that run deployment-agnostic tests + - x-pack/test/api_integration/deployment_agnostic/stateful.config.ts diff --git a/.buildkite/ftr_search_serverless_configs.yml b/.buildkite/ftr_search_serverless_configs.yml index 73b6238027bce..9a5ce6798dbae 100644 --- a/.buildkite/ftr_search_serverless_configs.yml +++ b/.buildkite/ftr_search_serverless_configs.yml @@ -16,3 +16,5 @@ enabled: - x-pack/test_serverless/functional/test_suites/search/common_configs/config.group4.ts - x-pack/test_serverless/functional/test_suites/search/common_configs/config.group5.ts - x-pack/test_serverless/functional/test_suites/search/common_configs/config.group6.ts + # serverless config files that run deployment-agnostic tests + - x-pack/test/api_integration/deployment_agnostic/search.serverless.config.ts diff --git a/.buildkite/ftr_security_serverless_configs.yml b/.buildkite/ftr_security_serverless_configs.yml index 3880175623fdd..4c3b037ce9f8a 100644 --- a/.buildkite/ftr_security_serverless_configs.yml +++ b/.buildkite/ftr_security_serverless_configs.yml @@ -97,3 +97,5 @@ enabled: - x-pack/test/security_solution_api_integration/test_suites/edr_workflows/response_actions/trial_license_complete_tier/configs/serverless.config.ts - x-pack/test/security_solution_endpoint/configs/serverless.endpoint.config.ts - x-pack/test/security_solution_endpoint/configs/serverless.integrations.config.ts + # serverless config files that run deployment-agnostic tests + - x-pack/test/api_integration/deployment_agnostic/security.serverless.config.ts diff --git a/.eslintrc.js b/.eslintrc.js index 853b1549d2b93..2b8c6c819bb3e 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -618,7 +618,7 @@ module.exports = { 'test/*/*.config.ts', 'test/*/{tests,test_suites,apis,apps}/**/*', 'test/server_integration/**/*.ts', - 'x-pack/test/*/{tests,test_suites,apis,apps}/**/*', + 'x-pack/test/*/{tests,test_suites,apis,apps,deployment_agnostic}/**/*', 'x-pack/test/*/*config.*ts', 'x-pack/test/saved_object_api_integration/*/apis/**/*', 'x-pack/test/ui_capabilities/*/tests/**/*', diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 8c044f50bfc7d..7a6d772a9a880 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1274,6 +1274,7 @@ x-pack/test/observability_ai_assistant_functional @elastic/obs-ai-assistant /x-pack/test_serverless/functional/test_suites/security/ftr/ @elastic/appex-qa /x-pack/test_serverless/functional/test_suites/common/home_page/ @elastic/appex-qa /x-pack/test_serverless/**/services/ @elastic/appex-qa +/packages/kbn-es/src/stateful_resources/roles.yml @elastic/appex-qa # Core /config/ @elastic/kibana-core diff --git a/packages/kbn-es/index.ts b/packages/kbn-es/index.ts index 2f241828f400d..12bb617f3ecfb 100644 --- a/packages/kbn-es/index.ts +++ b/packages/kbn-es/index.ts @@ -21,4 +21,4 @@ export { readRolesDescriptorsFromResource, } from './src/utils'; export type { ArtifactLicense } from './src/artifact'; -export { SERVERLESS_ROLES_ROOT_PATH } from './src/paths'; +export { SERVERLESS_ROLES_ROOT_PATH, STATEFUL_ROLES_ROOT_PATH } from './src/paths'; diff --git a/packages/kbn-es/src/paths.ts b/packages/kbn-es/src/paths.ts index 2ee3ea7c7c205..b407630333c7e 100644 --- a/packages/kbn-es/src/paths.ts +++ b/packages/kbn-es/src/paths.ts @@ -25,6 +25,8 @@ export const ES_CONFIG = 'config/elasticsearch.yml'; export const ES_KEYSTORE_BIN = maybeUseBat('./bin/elasticsearch-keystore'); +export const STATEFUL_ROLES_ROOT_PATH = resolve(__dirname, './stateful_resources'); + export const SERVERLESS_OPERATOR_USERS_PATH = resolve( __dirname, './serverless_resources/operator_users.yml' diff --git a/packages/kbn-es/src/stateful_resources/roles.yml b/packages/kbn-es/src/stateful_resources/roles.yml new file mode 100644 index 0000000000000..49ae1fafad958 --- /dev/null +++ b/packages/kbn-es/src/stateful_resources/roles.yml @@ -0,0 +1,130 @@ +# ----- +# This file is for information purpose only. 'viewer' and 'editor' roles are defined in stateful Elasticsearch by default +# Source: https://github.com/elastic/elasticsearch/blob/4272164530807787d4d8b991e3095a6e79176dbf/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStore.java#L861-L952 +# Note: inconsistency between these roles definition and the same roles of serverless project may break FTR deployment-agnostic tests +# ----- +viewer: + cluster: [] + indices: + - names: + - '.alerts*' + - '.preview.alerts*' + privileges: + - 'read' + - 'view_index_metadata' + allow_restricted_indices: false + - names: + - '.items-*' + - '.lists-*' + - '.siem-signals*' + privileges: + - 'read' + - 'view_index_metadata' + allow_restricted_indices: false + - names: + - '/~(([.]|ilm-history-).*)/' + privileges: + - 'read' + - 'view_index_metadata' + allow_restricted_indices: false + - names: + - '.profiling-*' + - 'profiling-*' + privileges: + - 'read' + - 'view_index_metadata' + allow_restricted_indices: false + applications: + - application: 'kibana-.kibana' + privileges: + - 'read' + resources: + - '*' + run_as: [] + +editor: + cluster: [] + indices: + - names: + - 'observability-annotations' + privileges: + - 'read' + - 'view_index_metadata' + - 'write' + allow_restricted_indices: false + - names: + - '.items-*' + - '.lists-*' + - '.siem-signals*' + privileges: + - 'maintenance' + - 'read' + - 'view_index_metadata' + - 'write' + allow_restricted_indices: false + - names: + - '/~(([.]|ilm-history-).*)/' + privileges: + - 'read' + - 'view_index_metadata' + allow_restricted_indices: false + - names: + - '.profiling-*' + - 'profiling-*' + privileges: + - 'read' + - 'view_index_metadata' + allow_restricted_indices: false + - names: + - '.alerts*' + - '.internal.alerts*' + - '.internal.preview.alerts*' + - '.preview.alerts*' + privileges: + - 'maintenance' + - 'read' + - 'view_index_metadata' + - 'write' + allow_restricted_indices: false + applications: + - application: 'kibana-.kibana' + privileges: + - 'all' + resources: + - '*' + run_as: [] + +# Admin role without 'remote_indices' access definition +# There is no such built-in role in stateful, and it's a role "similar" to the built-in 'admin' role in serverless +admin: + # TODO: 'all' should be replaced with explicit list both here and serverless for deployment-agnostic tests with 'admin' role to be compatible + cluster: ['all'] + indices: + - names: ['*'] + privileges: ['all'] + allow_restricted_indices: false + - names: ['*'] + privileges: + - 'monitor' + - 'read' + - 'read_cross_cluster' + - 'view_index_metadata' + allow_restricted_indices: true + applications: + - application: '*' + privileges: ['*'] + resources: ['*'] + run_as: ['*'] + +# temporarily added for testing purpose +system_indices_superuser: + cluster: ['all'] + indices: + - names: ['*'] + privileges: ['all'] + allow_restricted_indices: true + applications: + - application: '*' + privileges: ['*'] + resources: ['*'] + run_as: ['*'] diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/autocomplete.command.from.test.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/autocomplete.command.from.test.ts index 3752fad6c580f..911f8760f60b7 100644 --- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/autocomplete.command.from.test.ts +++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/autocomplete.command.from.test.ts @@ -14,6 +14,9 @@ const visibleIndices = indexes .map(({ name, suggestedAs }) => suggestedAs || name) .sort(); +const addTrailingSpace = (strings: string[], predicate: (s: string) => boolean = (_s) => true) => + strings.map((string) => (predicate(string) ? `${string} ` : string)); + const metadataFields = [...METADATA_FIELDS].sort(); describe('autocomplete.suggest', () => { @@ -33,17 +36,17 @@ describe('autocomplete.suggest', () => { test('suggests visible indices on space', async () => { const { assertSuggestions } = await setup(); - await assertSuggestions('from /', visibleIndices); - await assertSuggestions('FROM /', visibleIndices); - await assertSuggestions('from /index', visibleIndices); + await assertSuggestions('from /', addTrailingSpace(visibleIndices)); + await assertSuggestions('FROM /', addTrailingSpace(visibleIndices)); + await assertSuggestions('from /index', addTrailingSpace(visibleIndices)); }); test('suggests visible indices on comma', async () => { const { assertSuggestions } = await setup(); - await assertSuggestions('FROM a,/', visibleIndices); - await assertSuggestions('FROM a, /', visibleIndices); - await assertSuggestions('from *,/', visibleIndices); + await assertSuggestions('FROM a,/', addTrailingSpace(visibleIndices)); + await assertSuggestions('FROM a, /', addTrailingSpace(visibleIndices)); + await assertSuggestions('from *,/', addTrailingSpace(visibleIndices)); }); test('can suggest integration data sources', async () => { @@ -52,17 +55,21 @@ describe('autocomplete.suggest', () => { .filter(({ hidden }) => !hidden) .map(({ name, suggestedAs }) => suggestedAs || name) .sort(); + const expectedSuggestions = addTrailingSpace( + visibleDataSources, + (s) => !integrations.find(({ name }) => name === s) + ); const { assertSuggestions, callbacks } = await setup(); const cb = { ...callbacks, getSources: jest.fn().mockResolvedValue(dataSources), }; - assertSuggestions('from /', visibleDataSources, { callbacks: cb }); - assertSuggestions('FROM /', visibleDataSources, { callbacks: cb }); - assertSuggestions('FROM a,/', visibleDataSources, { callbacks: cb }); - assertSuggestions('from a, /', visibleDataSources, { callbacks: cb }); - assertSuggestions('from *,/', visibleDataSources, { callbacks: cb }); + await assertSuggestions('from /', expectedSuggestions, { callbacks: cb }); + await assertSuggestions('FROM /', expectedSuggestions, { callbacks: cb }); + await assertSuggestions('FROM a,/', expectedSuggestions, { callbacks: cb }); + await assertSuggestions('from a, /', expectedSuggestions, { callbacks: cb }); + await assertSuggestions('from *,/', expectedSuggestions, { callbacks: cb }); }); }); @@ -71,7 +78,7 @@ describe('autocomplete.suggest', () => { test('on SPACE without comma ",", suggests adding metadata', async () => { const { assertSuggestions } = await setup(); - const expected = ['METADATA $0', ',', '|'].sort(); + const expected = ['METADATA $0', ',', '| '].sort(); await assertSuggestions('from a, b /', expected); }); @@ -86,10 +93,10 @@ describe('autocomplete.suggest', () => { test('on SPACE after "METADATA" column suggests command and pipe operators', async () => { const { assertSuggestions } = await setup(); - await assertSuggestions('from a, b [metadata _index /]', [',', '|']); - await assertSuggestions('from a, b metadata _index /', [',', '|']); - await assertSuggestions('from a, b metadata _index, _source /', [',', '|']); - await assertSuggestions(`from a, b metadata ${METADATA_FIELDS.join(', ')} /`, ['|']); + await assertSuggestions('from a, b [metadata _index /]', [',', '| ']); + await assertSuggestions('from a, b metadata _index /', [',', '| ']); + await assertSuggestions('from a, b metadata _index, _source /', [',', '| ']); + await assertSuggestions(`from a, b metadata ${METADATA_FIELDS.join(', ')} /`, ['| ']); }); test('filters out already used metadata fields', async () => { diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/autocomplete.command.stats.test.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/autocomplete.command.stats.test.ts index 88fd654a83453..a94fb0b8bead9 100644 --- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/autocomplete.command.stats.test.ts +++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/autocomplete.command.stats.test.ts @@ -45,7 +45,7 @@ describe('autocomplete.suggest', () => { describe('... ...', () => { test('lists possible aggregations on space after command', async () => { const { assertSuggestions } = await setup(); - const expected = ['var0 =', ...allAggFunctions, ...allEvaFunctions]; + const expected = ['var0 = ', ...allAggFunctions, ...allEvaFunctions]; await assertSuggestions('from a | stats /', expected); await assertSuggestions('FROM a | STATS /', expected); @@ -60,14 +60,14 @@ describe('autocomplete.suggest', () => { test('on space after aggregate field', async () => { const { assertSuggestions } = await setup(); - await assertSuggestions('from a | stats a=min(b) /', ['BY $0', ',', '|']); + await assertSuggestions('from a | stats a=min(b) /', ['BY $0', ',', '| ']); }); test('on space after aggregate field with comma', async () => { const { assertSuggestions } = await setup(); await assertSuggestions('from a | stats a=max(b), /', [ - 'var0 =', + 'var0 = ', ...allAggFunctions, ...allEvaFunctions, ]); @@ -78,7 +78,7 @@ describe('autocomplete.suggest', () => { await assertSuggestions('from a | stats by bucket(/', [ ...getFieldNamesByType([...ESQL_COMMON_NUMERIC_TYPES, 'date']).map( - (field) => `${field},` + (field) => `${field}, ` ), ...getFunctionSignaturesByReturnType('eval', ['date', ...ESQL_COMMON_NUMERIC_TYPES], { scalar: true, @@ -172,21 +172,21 @@ describe('autocomplete.suggest', () => { test('when typing right paren', async () => { const { assertSuggestions } = await setup(); - await assertSuggestions('from a | stats a = min(b)/ | sort b', ['BY $0', ',', '|']); + await assertSuggestions('from a | stats a = min(b)/ | sort b', ['BY $0', ',', '| ']); }); test('increments suggested variable name counter', async () => { const { assertSuggestions } = await setup(); await assertSuggestions('from a | eval var0=round(b), var1=round(c) | stats /', [ - 'var2 =', + 'var2 = ', ...allAggFunctions, 'var0', 'var1', ...allEvaFunctions, ]); await assertSuggestions('from a | stats var0=min(b),var1=c,/', [ - 'var2 =', + 'var2 = ', ...allAggFunctions, ...allEvaFunctions, ]); @@ -197,8 +197,8 @@ describe('autocomplete.suggest', () => { test('on space after "BY" keyword', async () => { const { assertSuggestions } = await setup(); const expected = [ - 'var0 =', - ...getFieldNamesByType('any'), + 'var0 = ', + ...getFieldNamesByType('any').map((field) => `${field} `), ...allEvaFunctions, ...allGroupingFunctions, ]; @@ -211,26 +211,27 @@ describe('autocomplete.suggest', () => { test('on space after grouping field', async () => { const { assertSuggestions } = await setup(); - await assertSuggestions('from a | stats a=c by d /', [',', '|']); + await assertSuggestions('from a | stats a=c by d /', [',', '| ']); }); test('after comma "," in grouping fields', async () => { const { assertSuggestions } = await setup(); + const fields = getFieldNamesByType('any').map((field) => `${field} `); await assertSuggestions('from a | stats a=c by d, /', [ - 'var0 =', - ...getFieldNamesByType('any'), + 'var0 = ', + ...fields, ...allEvaFunctions, ...allGroupingFunctions, ]); await assertSuggestions('from a | stats a=min(b),/', [ - 'var0 =', + 'var0 = ', ...allAggFunctions, ...allEvaFunctions, ]); await assertSuggestions('from a | stats avg(b) by c, /', [ - 'var0 =', - ...getFieldNamesByType('any'), + 'var0 = ', + ...fields, ...getFunctionSignaturesByReturnType('eval', 'any', { scalar: true }), ...allGroupingFunctions, ]); @@ -251,12 +252,12 @@ describe('autocomplete.suggest', () => { ...allGroupingFunctions, ]); await assertSuggestions('from a | stats avg(b) by var0 = /', [ - ...getFieldNamesByType('any'), + ...getFieldNamesByType('any').map((field) => `${field} `), ...allEvaFunctions, ...allGroupingFunctions, ]); await assertSuggestions('from a | stats avg(b) by c, var0 = /', [ - ...getFieldNamesByType('any'), + ...getFieldNamesByType('any').map((field) => `${field} `), ...allEvaFunctions, ...allGroupingFunctions, ]); @@ -265,11 +266,11 @@ describe('autocomplete.suggest', () => { test('on space after expression right hand side operand', async () => { const { assertSuggestions } = await setup(); - await assertSuggestions('from a | stats avg(b) by doubleField % 2 /', [',', '|']); + await assertSuggestions('from a | stats avg(b) by doubleField % 2 /', [',', '| ']); await assertSuggestions( 'from a | stats var0 = AVG(doubleField) BY var1 = BUCKET(dateField, 1 day)/', - [',', '|', '+ $0', '- $0'] + [',', '| ', '+ $0', '- $0'] ); }); }); diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/helpers.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/helpers.ts index 6600ffdbaf1d8..464239d3ae960 100644 --- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/helpers.ts +++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/helpers.ts @@ -41,6 +41,7 @@ export const triggerCharacters = [',', '(', '=', ' ']; export const fields: Array<{ name: string; type: string; suggestedAs?: string }> = [ ...[ 'string', + 'keyword', 'double', 'date', 'boolean', diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.test.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.test.ts index d9c958b5bd4f7..a6c2756914ce2 100644 --- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.test.ts +++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.test.ts @@ -10,7 +10,12 @@ import { suggest } from './autocomplete'; import { evalFunctionDefinitions } from '../definitions/functions'; import { timeUnitsToSuggest } from '../definitions/literals'; import { commandDefinitions } from '../definitions/commands'; -import { getSafeInsertText, getUnitDuration, TRIGGER_SUGGESTION_COMMAND } from './factories'; +import { + getSafeInsertText, + getUnitDuration, + TIME_SYSTEM_PARAMS, + TRIGGER_SUGGESTION_COMMAND, +} from './factories'; import { camelCase, partition } from 'lodash'; import { getAstAndSyntaxErrors } from '@kbn/esql-ast'; import { FunctionParameter, FunctionReturnType } from '../definitions/types'; @@ -26,6 +31,7 @@ import { createCompletionContext, getPolicyFields, PartialSuggestionWithText, + TIME_PICKER_SUGGESTION, } from './__tests__/helpers'; import { METADATA_FIELDS } from '../shared/constants'; import { @@ -143,7 +149,7 @@ describe('autocomplete', () => { describe('show', () => { testSuggestions('show ', ['INFO']); for (const fn of ['info']) { - testSuggestions(`show ${fn} `, ['|']); + testSuggestions(`show ${fn} `, ['| ']); } }); @@ -151,9 +157,12 @@ describe('autocomplete', () => { const allEvalFns = getFunctionSignaturesByReturnType('where', 'any', { scalar: true, }); - testSuggestions('from a | where ', [...getFieldNamesByType('any'), ...allEvalFns]); + testSuggestions('from a | where ', [ + ...getFieldNamesByType('any').map((field) => `${field} `), + ...allEvalFns, + ]); testSuggestions('from a | eval var0 = 1 | where ', [ - ...getFieldNamesByType('any'), + ...getFieldNamesByType('any').map((name) => `${name} `), 'var0', ...allEvalFns, ]); @@ -172,6 +181,14 @@ describe('autocomplete', () => { ...getFieldNamesByType('any'), ...getFunctionSignaturesByReturnType('where', ['any'], { scalar: true }), ]); + + testSuggestions('from a | where dateField >= ', [ + TIME_PICKER_SUGGESTION, + ...TIME_SYSTEM_PARAMS, + ...getFieldNamesByType('date'), + ...getFunctionSignaturesByReturnType('where', ['date'], { scalar: true }), + ]); + // Skip these tests until the insensitive case equality gets restored back testSuggestions.skip('from a | where stringField =~ ', [ ...getFieldNamesByType('string'), @@ -182,7 +199,7 @@ describe('autocomplete', () => { ...getFunctionSignaturesByReturnType('where', 'any', { scalar: true }), ]); testSuggestions.skip('from a | where stringField =~ stringField ', [ - '|', + '| ', ...getFunctionSignaturesByReturnType( 'where', 'boolean', @@ -307,7 +324,7 @@ describe('autocomplete', () => { for (const subExpression of subExpressions) { testSuggestions(`from a | ${subExpression} grok `, getFieldNamesByType('string')); testSuggestions(`from a | ${subExpression} grok stringField `, [constantPattern], ' '); - testSuggestions(`from a | ${subExpression} grok stringField ${constantPattern} `, ['|']); + testSuggestions(`from a | ${subExpression} grok stringField ${constantPattern} `, ['| ']); } }); @@ -324,7 +341,7 @@ describe('autocomplete', () => { testSuggestions(`from a | ${subExpression} dissect stringField `, [constantPattern], ' '); testSuggestions( `from a | ${subExpression} dissect stringField ${constantPattern} `, - ['APPEND_SEPARATOR = $0', '|'], + ['APPEND_SEPARATOR = $0', '| '], ' ' ); testSuggestions( @@ -333,30 +350,30 @@ describe('autocomplete', () => { ); testSuggestions( `from a | ${subExpression} dissect stringField ${constantPattern} append_separator = ":" `, - ['|'] + ['| '] ); } }); describe('sort', () => { testSuggestions('from a | sort ', [ - ...getFieldNamesByType('any'), + ...getFieldNamesByType('any').map((name) => `${name} `), ...getFunctionSignaturesByReturnType('sort', 'any', { scalar: true }), ]); - testSuggestions('from a | sort stringField ', ['ASC', 'DESC', ',', '|']); - testSuggestions('from a | sort stringField desc ', ['NULLS FIRST', 'NULLS LAST', ',', '|']); + testSuggestions('from a | sort stringField ', ['ASC ', 'DESC ', ',', '| ']); + testSuggestions('from a | sort stringField desc ', ['NULLS FIRST ', 'NULLS LAST ', ',', '| ']); // @TODO: improve here // testSuggestions('from a | sort stringField desc ', ['first', 'last']); }); describe('limit', () => { - testSuggestions('from a | limit ', ['10', '100', '1000']); - testSuggestions('from a | limit 4 ', ['|']); + testSuggestions('from a | limit ', ['10 ', '100 ', '1000 ']); + testSuggestions('from a | limit 4 ', ['| ']); }); describe('mv_expand', () => { testSuggestions('from a | mv_expand ', getFieldNamesByType('any')); - testSuggestions('from a | mv_expand a ', ['|']); + testSuggestions('from a | mv_expand a ', ['| ']); }); describe('rename', () => { @@ -413,8 +430,9 @@ describe('autocomplete', () => { testSuggestions(`from a ${prevCommand}| enrich _${mode.toUpperCase()}:`, policyNames, ':'); testSuggestions(`from a ${prevCommand}| enrich _${camelCase(mode)}:`, policyNames, ':'); } - testSuggestions(`from a ${prevCommand}| enrich policy `, ['ON $0', 'WITH $0', '|']); + testSuggestions(`from a ${prevCommand}| enrich policy `, ['ON $0', 'WITH $0', '| ']); testSuggestions(`from a ${prevCommand}| enrich policy on `, [ + 'keywordField', 'stringField', 'doubleField', 'dateField', @@ -427,28 +445,28 @@ describe('autocomplete', () => { '`any#Char$Field`', 'kubernetes.something.something', ]); - testSuggestions(`from a ${prevCommand}| enrich policy on b `, ['WITH $0', ',', '|']); + testSuggestions(`from a ${prevCommand}| enrich policy on b `, ['WITH $0', ',', '| ']); testSuggestions( `from a ${prevCommand}| enrich policy on b with `, - ['var0 =', ...getPolicyFields('policy')], + ['var0 = ', ...getPolicyFields('policy')], ' ' ); - testSuggestions(`from a ${prevCommand}| enrich policy on b with var0 `, ['= $0', ',', '|']); + testSuggestions(`from a ${prevCommand}| enrich policy on b with var0 `, ['= $0', ',', '| ']); testSuggestions(`from a ${prevCommand}| enrich policy on b with var0 = `, [ ...getPolicyFields('policy'), ]); testSuggestions(`from a ${prevCommand}| enrich policy on b with var0 = stringField `, [ ',', - '|', + '| ', ]); testSuggestions(`from a ${prevCommand}| enrich policy on b with var0 = stringField, `, [ - 'var1 =', + 'var1 = ', ...getPolicyFields('policy'), ]); testSuggestions(`from a ${prevCommand}| enrich policy on b with var0 = stringField, var1 `, [ '= $0', ',', - '|', + '| ', ]); testSuggestions( `from a ${prevCommand}| enrich policy on b with var0 = stringField, var1 = `, @@ -456,16 +474,20 @@ describe('autocomplete', () => { ); testSuggestions( `from a ${prevCommand}| enrich policy with `, - ['var0 =', ...getPolicyFields('policy')], + ['var0 = ', ...getPolicyFields('policy')], ' ' ); - testSuggestions(`from a ${prevCommand}| enrich policy with stringField `, ['= $0', ',', '|']); + testSuggestions(`from a ${prevCommand}| enrich policy with stringField `, [ + '= $0', + ',', + '| ', + ]); } }); describe('eval', () => { testSuggestions('from a | eval ', [ - 'var0 =', + 'var0 = ', ...getFieldNamesByType('any'), ...getFunctionSignaturesByReturnType('eval', 'any', { scalar: true }), ]); @@ -474,7 +496,7 @@ describe('autocomplete', () => { 'double', ]), ',', - '|', + '| ', ]); testSuggestions('from index | EVAL stringField not ', ['LIKE $0', 'RLIKE $0', 'IN $0']); testSuggestions('from index | EVAL stringField NOT ', ['LIKE $0', 'RLIKE $0', 'IN $0']); @@ -499,7 +521,7 @@ describe('autocomplete', () => { ...getFunctionSignaturesByReturnType('eval', 'any', { scalar: true }), ]); testSuggestions('from a | eval a=doubleField, ', [ - 'var0 =', + 'var0 = ', ...getFieldNamesByType('any'), 'a', ...getFunctionSignaturesByReturnType('eval', 'any', { scalar: true }), @@ -548,7 +570,7 @@ describe('autocomplete', () => { ); testSuggestions('from a | eval a=round(doubleField) ', [ ',', - '|', + '| ', ...getFunctionSignaturesByReturnType('eval', 'any', { builtin: true, skipAssign: true }, [ 'double', ]), @@ -573,7 +595,7 @@ describe('autocomplete', () => { ' ' ); testSuggestions('from a | eval a=round(doubleField),', [ - 'var0 =', + 'var0 = ', ...getFieldNamesByType('any'), 'a', ...getFunctionSignaturesByReturnType('eval', 'any', { scalar: true }), @@ -597,7 +619,7 @@ describe('autocomplete', () => { testSuggestions( 'from a | stats avg(doubleField) by stringField | eval ', [ - 'var0 =', + 'var0 = ', '`avg(doubleField)`', ...getFunctionSignaturesByReturnType('eval', 'any', { scalar: true }), ], @@ -609,7 +631,7 @@ describe('autocomplete', () => { testSuggestions( 'from a | eval abs(doubleField) + 1 | eval ', [ - 'var0 =', + 'var0 = ', ...getFieldNamesByType('any'), '`abs(doubleField) + 1`', ...getFunctionSignaturesByReturnType('eval', 'any', { scalar: true }), @@ -619,7 +641,7 @@ describe('autocomplete', () => { testSuggestions( 'from a | stats avg(doubleField) by stringField | eval ', [ - 'var0 =', + 'var0 = ', '`avg(doubleField)`', ...getFunctionSignaturesByReturnType('eval', 'any', { scalar: true }), ], @@ -631,7 +653,7 @@ describe('autocomplete', () => { testSuggestions( 'from a | stats avg(doubleField), avg(kubernetes.something.something) by stringField | eval ', [ - 'var0 =', + 'var0 = ', '`avg(doubleField)`', '`avg(kubernetes.something.something)`', ...getFunctionSignaturesByReturnType('eval', 'any', { scalar: true }), @@ -664,7 +686,7 @@ describe('autocomplete', () => { ); // test that comma is correctly added to the suggestions if minParams is not reached yet testSuggestions('from a | eval a=concat( ', [ - ...getFieldNamesByType(['text', 'keyword']).map((v) => `${v},`), + ...getFieldNamesByType(['text', 'keyword']).map((v) => `${v}, `), ...getFunctionSignaturesByReturnType( 'eval', ['text', 'keyword'], @@ -691,7 +713,7 @@ describe('autocomplete', () => { testSuggestions( 'from a | eval a=cidr_match(ipField, textField, ', [ - ...getFieldNamesByType('text'), + ...getFieldNamesByType('keyword'), ...getFunctionSignaturesByReturnType( 'eval', ['text', 'keyword'], @@ -704,7 +726,7 @@ describe('autocomplete', () => { ); // test that comma is correctly added to the suggestions if minParams is not reached yet testSuggestions('from a | eval a=cidr_match( ', [ - ...getFieldNamesByType('ip').map((v) => `${v},`), + ...getFieldNamesByType('ip').map((v) => `${v}, `), ...getFunctionSignaturesByReturnType('eval', 'ip', { scalar: true }, undefined, [ 'cidr_match', ]).map((v) => ({ ...v, text: `${v.text},` })), @@ -749,7 +771,7 @@ describe('autocomplete', () => { 'from a | eval var0 = abs(doubleField) | eval abs(var0)', [ ',', - '|', + '| ', ...getFunctionSignaturesByReturnType('eval', 'any', { builtin: true, skipAssign: true }, [ 'double', ]), @@ -807,13 +829,13 @@ describe('autocomplete', () => { if (!requiresMoreArgs || s === '' || (typeof s === 'object' && s.text === '')) { return s; } - return typeof s === 'string' ? `${s},` : { ...s, text: `${s.text},` }; + return typeof s === 'string' ? `${s}, ` : { ...s, text: `${s.text},` }; }; testSuggestions( `from a | eval ${fn.name}(${Array(i).fill('field').join(', ')}${i ? ',' : ''} )`, suggestedConstants?.length - ? suggestedConstants.map((option) => `"${option}"${requiresMoreArgs ? ',' : ''}`) + ? suggestedConstants.map((option) => `"${option}"${requiresMoreArgs ? ', ' : ''}`) : [ ...getDateLiteralsByFieldType(getTypesFromParamDefs(acceptsFieldParamDefs)), ...getFieldNamesByType(getTypesFromParamDefs(acceptsFieldParamDefs)), @@ -833,7 +855,7 @@ describe('autocomplete', () => { i ? ',' : '' } )`, suggestedConstants?.length - ? suggestedConstants.map((option) => `"${option}"${requiresMoreArgs ? ',' : ''}`) + ? suggestedConstants.map((option) => `"${option}"${requiresMoreArgs ? ', ' : ''}`) : [ ...getDateLiteralsByFieldType(getTypesFromParamDefs(acceptsFieldParamDefs)), ...getFieldNamesByType(getTypesFromParamDefs(acceptsFieldParamDefs)), @@ -865,7 +887,7 @@ describe('autocomplete', () => { testSuggestions( `from a | eval ${fn.name}(`, suggestedConstants?.length - ? [...suggestedConstants.map((option) => `"${option}"${requiresMoreArgs ? ',' : ''}`)] + ? [...suggestedConstants.map((option) => `"${option}"${requiresMoreArgs ? ', ' : ''}`)] : [] ); } @@ -881,7 +903,7 @@ describe('autocomplete', () => { [ ...dateSuggestions, ',', - '|', + '| ', ...getFunctionSignaturesByReturnType('eval', 'any', { builtin: true, skipAssign: true }, [ 'integer', ]), @@ -890,12 +912,12 @@ describe('autocomplete', () => { ); testSuggestions('from a | eval a = 1 year ', [ ',', - '|', + '| ', ...getFunctionSignaturesByReturnType('eval', 'any', { builtin: true, skipAssign: true }, [ 'time_interval', ]), ]); - testSuggestions('from a | eval a = 1 day + 2 ', [',', '|']); + testSuggestions('from a | eval a = 1 day + 2 ', [',', '| ']); testSuggestions( 'from a | eval 1 day + 2 ', [ @@ -908,19 +930,19 @@ describe('autocomplete', () => { ); testSuggestions( 'from a | eval var0=date_trunc()', - [...getLiteralsByType('time_literal').map((t) => `${t},`)], + getLiteralsByType('time_literal').map((t) => `${t}, `), '(' ); testSuggestions( 'from a | eval var0=date_trunc(2 )', - [...dateSuggestions.map((t) => `${t},`), ','], + [...dateSuggestions.map((t) => `${t}, `), ','], ' ' ); }); }); describe('values suggestions', () => { - testSuggestions('FROM "a"', ['a', 'b'], undefined, 7, [ + testSuggestions('FROM "a"', ['a ', 'b '], undefined, 7, [ , [ { name: 'a', hidden: false }, @@ -975,50 +997,6 @@ describe('autocomplete', () => { }); }); - describe('auto triggers', () => { - function getSuggestionsFor(statement: string) { - const callbackMocks = createCustomCallbackMocks(undefined, undefined, undefined); - const triggerOffset = statement.lastIndexOf(' ') + 1; // drop - const context = createCompletionContext(statement[triggerOffset]); - return suggest( - statement, - triggerOffset + 1, - context, - async (text) => (text ? getAstAndSyntaxErrors(text) : { ast: [], errors: [] }), - callbackMocks - ); - } - it('should trigger further suggestions for functions', async () => { - const suggestions = await getSuggestionsFor('from a | eval '); - // test that all functions will retrigger suggestions - expect( - suggestions - .filter(({ kind }) => kind === 'Function') - .every(({ command }) => command === TRIGGER_SUGGESTION_COMMAND) - ).toBeTruthy(); - // now test that non-function won't retrigger - expect( - suggestions - .filter(({ kind }) => kind !== 'Function') - .every(({ command }) => command == null) - ).toBeTruthy(); - }); - it('should trigger further suggestions for commands', async () => { - const suggestions = await getSuggestionsFor('from a | '); - // test that all commands will retrigger suggestions - expect( - suggestions.every(({ command }) => command === TRIGGER_SUGGESTION_COMMAND) - ).toBeTruthy(); - }); - it('should trigger further suggestions after enrich mode', async () => { - const suggestions = await getSuggestionsFor('from a | enrich _any:'); - // test that all commands will retrigger suggestions - expect( - suggestions.every(({ command }) => command === TRIGGER_SUGGESTION_COMMAND) - ).toBeTruthy(); - }); - }); - /** * Monaco asks for suggestions in at least two different scenarios. * 1. When the user types a non-whitespace character (e.g. 'FROM k') - this is the Invoke trigger kind @@ -1049,24 +1027,47 @@ describe('autocomplete', () => { 10 ); - // function argument - testSuggestions( - 'FROM kibana_sample_data_logs | EVAL TRIM(e)', - [ - ...getFunctionSignaturesByReturnType( - 'eval', - ['text', 'keyword'], - { scalar: true }, - undefined, - ['trim'] - ), - ], - undefined, - 42 - ); + describe('function arguments', () => { + // function argument + testSuggestions( + 'FROM kibana_sample_data_logs | EVAL TRIM(e)', + [ + ...getFieldNamesByType(['text', 'keyword']), + ...getFunctionSignaturesByReturnType( + 'eval', + ['text', 'keyword'], + { scalar: true }, + undefined, + ['trim'] + ), + ], + undefined, + 42 + ); + + // subsequent function argument + const expectedDateDiff2ndArgSuggestions = [ + TIME_PICKER_SUGGESTION, + ...TIME_SYSTEM_PARAMS.map((t) => `${t}, `), + ...getFieldNamesByType('date').map((name) => `${name}, `), + ...getFunctionSignaturesByReturnType('eval', 'date', { scalar: true }).map((s) => ({ + ...s, + text: `${s.text},`, + })), + ]; + testSuggestions( + 'FROM a | EVAL DATE_DIFF("day", )', + expectedDateDiff2ndArgSuggestions, + undefined, + 31 + ); + + // trigger character case for comparison + testSuggestions('FROM a | EVAL DATE_DIFF("day", )', expectedDateDiff2ndArgSuggestions, ' '); + }); // FROM source - testSuggestions('FROM k', ['index1', 'index2'], undefined, 6, [ + testSuggestions('FROM k', ['index1 ', 'index2 '], undefined, 6, [ , [ { name: 'index1', hidden: false }, @@ -1075,7 +1076,7 @@ describe('autocomplete', () => { ]); // FROM source METADATA - testSuggestions('FROM index1 M', [',', 'METADATA $0', '|'], undefined, 13); + testSuggestions('FROM index1 M', [',', 'METADATA $0', '| '], undefined, 13); // FROM source METADATA field testSuggestions('FROM index1 METADATA _', METADATA_FIELDS, undefined, 22); @@ -1084,7 +1085,7 @@ describe('autocomplete', () => { testSuggestions( 'FROM index1 | EVAL b', [ - 'var0 =', + 'var0 = ', ...getFieldNamesByType('any'), ...getFunctionSignaturesByReturnType('eval', 'any', { scalar: true }), ], @@ -1117,7 +1118,7 @@ describe('autocomplete', () => { ); // ENRICH policy ON - testSuggestions('FROM index1 | ENRICH policy O', ['ON $0', 'WITH $0', '|'], undefined, 29); + testSuggestions('FROM index1 | ENRICH policy O', ['ON $0', 'WITH $0', '| '], undefined, 29); // ENRICH policy ON field testSuggestions('FROM index1 | ENRICH policy ON f', getFieldNamesByType('any'), undefined, 32); @@ -1125,14 +1126,14 @@ describe('autocomplete', () => { // ENRICH policy WITH policyfield testSuggestions( 'FROM index1 | ENRICH policy WITH v', - ['var0 =', ...getPolicyFields('policy')], + ['var0 = ', ...getPolicyFields('policy')], undefined, 34 ); testSuggestions( 'FROM index1 | ENRICH policy WITH \tv', - ['var0 =', ...getPolicyFields('policy')], + ['var0 = ', ...getPolicyFields('policy')], undefined, 34 ); @@ -1154,7 +1155,7 @@ describe('autocomplete', () => { // LIMIT argument // Here we actually test that the invoke trigger kind does not work // because it isn't very useful to see literal suggestions when typing a number - testSuggestions('FROM a | LIMIT 1', ['|'], undefined, 16); + testSuggestions('FROM a | LIMIT 1', ['| '], undefined, 16); // MV_EXPAND field testSuggestions('FROM index1 | MV_EXPAND f', getFieldNamesByType('any'), undefined, 25); @@ -1173,19 +1174,24 @@ describe('autocomplete', () => { 'FROM index1 | SORT f', [ ...getFunctionSignaturesByReturnType('sort', 'any', { scalar: true }), - ...getFieldNamesByType('any'), + ...getFieldNamesByType('any').map((field) => `${field} `), ], undefined, 20 ); // SORT field order - testSuggestions('FROM index1 | SORT stringField a', ['ASC', 'DESC', ',', '|'], undefined, 32); + testSuggestions( + 'FROM index1 | SORT stringField a', + ['ASC ', 'DESC ', ',', '| '], + undefined, + 32 + ); // SORT field order nulls testSuggestions( 'FROM index1 | SORT stringField ASC n', - ['NULLS FIRST', 'NULLS LAST', ',', '|'], + ['NULLS FIRST ', 'NULLS LAST ', ',', '| '], undefined, 36 ); @@ -1193,21 +1199,24 @@ describe('autocomplete', () => { // STATS argument testSuggestions( 'FROM index1 | STATS f', - ['var0 =', ...getFunctionSignaturesByReturnType('stats', 'any', { scalar: true, agg: true })], + [ + 'var0 = ', + ...getFunctionSignaturesByReturnType('stats', 'any', { scalar: true, agg: true }), + ], undefined, 21 ); // STATS argument BY - testSuggestions('FROM index1 | STATS AVG(booleanField) B', ['BY $0', ',', '|'], undefined, 39); + testSuggestions('FROM index1 | STATS AVG(booleanField) B', ['BY $0', ',', '| '], undefined, 39); // STATS argument BY expression testSuggestions( 'FROM index1 | STATS field BY f', [ - 'var0 =', + 'var0 = ', ...getFunctionSignaturesByReturnType('stats', 'any', { grouping: true, scalar: true }), - ...getFieldNamesByType('any'), + ...getFieldNamesByType('any').map((field) => `${field} `), ], undefined, 30 @@ -1217,7 +1226,7 @@ describe('autocomplete', () => { testSuggestions( 'FROM index1 | WHERE f', [ - ...getFieldNamesByType('any'), + ...getFieldNamesByType('any').map((field) => `${field} `), ...getFunctionSignaturesByReturnType('where', 'any', { scalar: true }), ], undefined, @@ -1239,4 +1248,264 @@ describe('autocomplete', () => { 33 ); }); + + describe('advancing the cursor and opening the suggestion menu automatically ✨', () => { + const attachTriggerCommand = ( + s: string | PartialSuggestionWithText + ): PartialSuggestionWithText => + typeof s === 'string' + ? { + text: s, + command: TRIGGER_SUGGESTION_COMMAND, + } + : { ...s, command: TRIGGER_SUGGESTION_COMMAND }; + + const attachAsSnippet = (s: PartialSuggestionWithText): PartialSuggestionWithText => ({ + ...s, + asSnippet: true, + }); + + // Source command + testSuggestions( + 'F', + ['FROM $0', 'ROW $0', 'SHOW $0'].map(attachTriggerCommand).map(attachAsSnippet), + undefined, + 1 + ); + + // Pipe command + testSuggestions( + 'FROM a | E', + commandDefinitions + .filter(({ name }) => !sourceCommands.includes(name)) + .map(({ name }) => attachTriggerCommand(name.toUpperCase() + ' $0')) + .map(attachAsSnippet), // TODO consider making this check more fundamental + undefined, + 10 + ); + + describe('function arguments', () => { + // literalSuggestions parameter + const dateDiffFirstParamSuggestions = + evalFunctionDefinitions.find(({ name }) => name === 'date_diff')?.signatures[0].params?.[0] + .literalSuggestions ?? []; + testSuggestions( + 'FROM a | EVAL DATE_DIFF()', + dateDiffFirstParamSuggestions.map((s) => `"${s}", `).map(attachTriggerCommand), + undefined, + 24 + ); + + // field parameter + + const expectedStringSuggestionsWhenMoreArgsAreNeeded = [ + ...getFieldNamesByType('keyword') + .map((field) => `${field}, `) + .map(attachTriggerCommand), + ...getFunctionSignaturesByReturnType('eval', 'keyword', { scalar: true }, undefined, [ + 'replace', + ]).map((s) => ({ + ...s, + text: `${s.text},`, + })), + ]; + + testSuggestions( + 'FROM a | EVAL REPLACE()', + expectedStringSuggestionsWhenMoreArgsAreNeeded, + undefined, + 22 + ); + + // subsequent parameter + testSuggestions( + 'FROM a | EVAL REPLACE(stringField, )', + expectedStringSuggestionsWhenMoreArgsAreNeeded, + undefined, + 35 + ); + + // final parameter — should not advance! + testSuggestions( + 'FROM a | EVAL REPLACE(stringField, stringField, )', + [ + ...getFieldNamesByType('keyword').map((field) => ({ text: field, command: undefined })), + ...getFunctionSignaturesByReturnType('eval', 'keyword', { scalar: true }, undefined, [ + 'replace', + ]), + ], + undefined, + 48 + ); + + // Trigger character because this is how it will actually be... the user will press + // space-bar... this may change if we fix the tokenization of timespan literals + // such that "2 days" is a single monaco token + testSuggestions( + 'FROM a | EVAL DATE_TRUNC(2 )', + [...timeUnitsToSuggest.map((s) => `${s.name}, `).map(attachTriggerCommand), ','], + ' ' + ); + }); + + // PIPE (|) + testSuggestions( + 'FROM a ', + [attachTriggerCommand('| '), ',', attachAsSnippet(attachTriggerCommand('METADATA $0'))], + undefined, + 7 + ); + + // Assignment + testSuggestions(`FROM a | ENRICH policy on b with `, [ + attachTriggerCommand('var0 = '), + ...getPolicyFields('policy'), + ]); + + // FROM source + // + // Using an Invoke trigger kind here because that's what Monaco uses when the show suggestions + // action is triggered (e.g. accepting the "FROM" suggestion) + testSuggestions( + 'FROM ', + [ + { text: 'index1 ', command: TRIGGER_SUGGESTION_COMMAND }, + { text: 'index2 ', command: TRIGGER_SUGGESTION_COMMAND }, + ], + undefined, + 5, + [ + , + [ + { name: 'index1', hidden: false }, + { name: 'index2', hidden: false }, + ], + ] + ); + + // FROM source METADATA + testSuggestions( + 'FROM index1 M', + [',', attachAsSnippet(attachTriggerCommand('METADATA $0')), '| '], + undefined, + 13 + ); + + // LIMIT number + testSuggestions('FROM a | LIMIT ', ['10 ', '100 ', '1000 '].map(attachTriggerCommand)); + + // SORT field + testSuggestions( + 'FROM a | SORT ', + [ + ...getFieldNamesByType('any').map((field) => `${field} `), + ...getFunctionSignaturesByReturnType('sort', 'any', { scalar: true }), + ].map(attachTriggerCommand), + undefined, + 14 + ); + + // SORT field order + testSuggestions( + 'FROM a | SORT field ', + [',', ...['ASC ', 'DESC ', '| '].map(attachTriggerCommand)], + undefined, + 20 + ); + + // SORT field order nulls + testSuggestions( + 'FROM a | SORT field ASC ', + [',', ...['NULLS FIRST ', 'NULLS LAST ', '| '].map(attachTriggerCommand)], + undefined, + 24 + ); + + // STATS argument + testSuggestions( + 'FROM a | STATS ', + [ + 'var0 = ', + ...getFunctionSignaturesByReturnType('stats', 'any', { scalar: true, agg: true }).map( + attachAsSnippet + ), + ].map(attachTriggerCommand), + undefined, + 15 + ); + + // STATS argument BY + testSuggestions( + 'FROM a | STATS AVG(numberField) ', + [',', attachAsSnippet(attachTriggerCommand('BY $0')), attachTriggerCommand('| ')], + undefined, + 32 + ); + + // STATS argument BY field + const allByCompatibleFunctions = getFunctionSignaturesByReturnType( + 'stats', + 'any', + { + scalar: true, + grouping: true, + }, + undefined, + undefined, + 'by' + ); + testSuggestions( + 'FROM a | STATS AVG(numberField) BY ', + [ + attachTriggerCommand('var0 = '), + ...getFieldNamesByType('any') + .map((field) => `${field} `) + .map(attachTriggerCommand), + ...allByCompatibleFunctions, + ], + undefined, + 35 + ); + + // STATS argument BY assignment (checking field suggestions) + testSuggestions( + 'FROM a | STATS AVG(numberField) BY var0 = ', + [ + ...getFieldNamesByType('any') + .map((field) => `${field} `) + .map(attachTriggerCommand), + ...allByCompatibleFunctions, + ], + undefined, + 41 + ); + + // WHERE argument (field suggestions) + testSuggestions( + 'FROM a | WHERE ', + [ + ...getFieldNamesByType('any') + .map((field) => `${field} `) + .map(attachTriggerCommand), + ...getFunctionSignaturesByReturnType('where', 'any', { scalar: true }).map(attachAsSnippet), + ], + undefined, + 15 + ); + + // WHERE argument comparison + testSuggestions( + 'FROM a | WHERE stringField ', + getFunctionSignaturesByReturnType( + 'where', + 'boolean', + { + builtin: true, + }, + ['string'] + ).map((s) => (s.text.toLowerCase().includes('null') ? s : attachTriggerCommand(s))), + undefined, + 27 + ); + }); }); diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.ts index d2d617aac4315..67af737d34da0 100644 --- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.ts +++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.ts @@ -13,10 +13,11 @@ import type { ESQLCommand, ESQLCommandOption, ESQLFunction, + ESQLLiteral, ESQLSingleAstItem, } from '@kbn/esql-ast'; import { partition } from 'lodash'; -import { ESQL_NUMBER_TYPES, isNumericType } from '../shared/esql_types'; +import { ESQL_NUMBER_TYPES, compareTypesWithLiterals, isNumericType } from '../shared/esql_types'; import type { EditorContext, SuggestionRawDefinition } from './types'; import { lookupColumn, @@ -44,6 +45,7 @@ import { nonNullable, getColumnExists, findPreviousWord, + noCaseCompare, } from '../shared/helpers'; import { collectVariables, excludeVariablesFromCurrentCommand } from '../shared/variables'; import type { ESQLPolicy, ESQLRealField, ESQLVariable, ReferenceMaps } from '../validation/types'; @@ -93,7 +95,7 @@ import { isAggFunctionUsedAlready, removeQuoteForSuggestedSources, } from './helper'; -import { FunctionParameter } from '../definitions/types'; +import { FunctionParameter, FunctionReturnType, SupportedFieldType } from '../definitions/types'; type GetSourceFn = () => Promise; type GetDataStreamsForIntegrationFn = ( @@ -101,7 +103,8 @@ type GetDataStreamsForIntegrationFn = ( ) => Promise | undefined>; type GetFieldsByTypeFn = ( type: string | string[], - ignored?: string[] + ignored?: string[], + options?: { advanceCursorAndOpenSuggestions?: boolean; addComma?: boolean } ) => Promise; type GetFieldsMapFn = () => Promise>; type GetPoliciesFn = () => Promise; @@ -185,8 +188,8 @@ function correctQuerySyntax(_query: string, context: EditorContext) { (context.triggerCharacter && charThatNeedMarkers.includes(context.triggerCharacter)) || // monaco.editor.CompletionTriggerKind['Invoke'] === 0 (context.triggerKind === 0 && unclosedRoundBrackets === 0) || - (context.triggerCharacter === ' ' && - (isMathFunction(query, query.length) || isComma(query.trimEnd()[query.trimEnd().length - 1]))) + (context.triggerCharacter === ' ' && isMathFunction(query, query.length)) || + isComma(query.trimEnd()[query.trimEnd().length - 1]) ) { query += EDITOR_MARKER; } @@ -308,12 +311,19 @@ export async function suggest( return []; } -function getFieldsByTypeRetriever(queryString: string, resourceRetriever?: ESQLCallbacks) { +function getFieldsByTypeRetriever( + queryString: string, + resourceRetriever?: ESQLCallbacks +): { getFieldsByType: GetFieldsByTypeFn; getFieldsMap: GetFieldsMapFn } { const helpers = getFieldsByTypeHelper(queryString, resourceRetriever); return { - getFieldsByType: async (expectedType: string | string[] = 'any', ignored: string[] = []) => { + getFieldsByType: async ( + expectedType: string | string[] = 'any', + ignored: string[] = [], + options + ) => { const fields = await helpers.getFieldsByType(expectedType, ignored); - return buildFieldsDefinitionsWithMetadata(fields); + return buildFieldsDefinitionsWithMetadata(fields, options); }, getFieldsMap: helpers.getFieldsMap, }; @@ -429,7 +439,13 @@ function areCurrentArgsValid( function extractFinalTypeFromArg( arg: ESQLAstItem, references: Pick -): string | undefined { +): + | ESQLLiteral['literalType'] + | SupportedFieldType + | FunctionReturnType + | 'timeInterval' + | string // @TODO remove this + | undefined { if (Array.isArray(arg)) { return extractFinalTypeFromArg(arg[0], references); } @@ -792,7 +808,11 @@ async function getExpressionSuggestionsByType( // if the definition includes a list of constants, suggest them if (argDef.values) { // ... | ... - suggestions.push(...buildConstantsDefinitions(argDef.values)); + suggestions.push( + ...buildConstantsDefinitions(argDef.values, undefined, undefined, { + advanceCursorAndOpenSuggestions: true, + }) + ); } // If the type is specified try to dig deeper in the definition to suggest the best candidate if ( @@ -815,6 +835,7 @@ async function getExpressionSuggestionsByType( // ... | // In this case start suggesting something not strictly based on type suggestions.push( + ...(await getFieldsByType('any', [], { advanceCursorAndOpenSuggestions: true })), ...(await getFieldsOrFunctionsSuggestions( ['any'], command.name, @@ -822,7 +843,7 @@ async function getExpressionSuggestionsByType( getFieldsByType, { functions: true, - fields: true, + fields: false, variables: anyVariables, } )) @@ -1087,7 +1108,11 @@ async function getFieldsOrFunctionsSuggestions( } = {} ): Promise { const filteredFieldsByType = pushItUpInTheList( - (await (fields ? getFieldsByType(types, ignoreFields) : [])) as SuggestionRawDefinition[], + (await (fields + ? getFieldsByType(types, ignoreFields, { + advanceCursorAndOpenSuggestions: commandName === 'sort', + }) + : [])) as SuggestionRawDefinition[], functions ); @@ -1187,6 +1212,8 @@ async function getFunctionArgsSuggestions( ? refSignature.minParams - 1 > argIndex : false); + const shouldAddComma = hasMoreMandatoryArgs && fnDefinition.type !== 'builtin'; + const suggestedConstants = Array.from( new Set( fnDefinition.signatures.reduce((acc, signature) => { @@ -1207,13 +1234,13 @@ async function getFunctionArgsSuggestions( ); if (suggestedConstants.length) { - return buildValueDefinitions(suggestedConstants).map((suggestion) => ({ - ...suggestion, - text: addCommaIf(hasMoreMandatoryArgs && fnDefinition.type !== 'builtin', suggestion.text), - })); + return buildValueDefinitions(suggestedConstants, { + addComma: hasMoreMandatoryArgs, + advanceCursorAndOpenSuggestions: hasMoreMandatoryArgs, + }); } - const suggestions = []; + const suggestions: SuggestionRawDefinition[] = []; const noArgDefined = !arg; const isUnknownColumn = arg && @@ -1267,7 +1294,9 @@ async function getFunctionArgsSuggestions( // if existing arguments are preset already, use them to filter out incompatible signatures .filter((signature) => { if (existingTypes.length) { - return existingTypes.every((type, index) => signature.params[index].type === type); + return existingTypes.every((type, index) => + compareTypesWithLiterals(signature.params[index].type, type) + ); } return true; }); @@ -1299,28 +1328,51 @@ async function getFunctionArgsSuggestions( return Array.from(new Set(paramDefs.map(({ type }) => type))); }; + // Literals suggestions.push( - ...getCompatibleLiterals(command.name, getTypesFromParamDefs(constantOnlyParamDefs)) + ...getCompatibleLiterals( + command.name, + getTypesFromParamDefs(constantOnlyParamDefs), + undefined, + { addComma: shouldAddComma, advanceCursorAndOpenSuggestions: hasMoreMandatoryArgs } + ) ); + // Fields suggestions.push( - ...(await getFieldsOrFunctionsSuggestions( - getTypesFromParamDefs(paramDefsWhichSupportFields), + ...pushItUpInTheList( + await getFieldsByType(getTypesFromParamDefs(paramDefsWhichSupportFields), [], { + addComma: shouldAddComma, + advanceCursorAndOpenSuggestions: hasMoreMandatoryArgs, + }), + true + ) + ); + + // Functions + suggestions.push( + ...getCompatibleFunctionDefinition( command.name, option?.name, - getFieldsByType, - { - functions: true, - fields: true, - variables: variablesExcludingCurrentCommandOnes, - }, - // do not repropose the same function as arg - // i.e. avoid cases like abs(abs(abs(...))) with suggestions - { - ignoreFn: fnToIgnore, - } - )) + getTypesFromParamDefs(paramDefsWhichSupportFields), + fnToIgnore + ).map((suggestion) => ({ + ...suggestion, + text: addCommaIf(shouldAddComma, suggestion.text), + })) ); + + // could also be in stats (bucket) but our autocomplete is not great yet + if ( + getTypesFromParamDefs(paramDefsWhichSupportFields).includes('date') && + ['where', 'eval'].includes(command.name) + ) + suggestions.push( + ...getDateLiterals({ + addComma: shouldAddComma, + advanceCursorAndOpenSuggestions: hasMoreMandatoryArgs, + }) + ); } // for eval and row commands try also to complete numeric literals with time intervals where possible @@ -1329,18 +1381,10 @@ async function getFunctionArgsSuggestions( if (isLiteralItem(arg) && isNumericType(arg.literalType)) { // ... | EVAL fn(2 ) suggestions.push( - ...(await getFieldsOrFunctionsSuggestions( - ['time_literal_unit'], - command.name, - option?.name, - getFieldsByType, - { - functions: false, - fields: false, - variables: variablesExcludingCurrentCommandOnes, - literals: true, - } - )) + ...getCompatibleLiterals(command.name, ['time_literal_unit'], undefined, { + addComma: shouldAddComma, + advanceCursorAndOpenSuggestions: hasMoreMandatoryArgs, + }) ); } } @@ -1349,24 +1393,9 @@ async function getFunctionArgsSuggestions( // suggest a comma if there's another argument for the function suggestions.push(commaCompleteItem); } - // if there are other arguments in the function, inject automatically a comma after each suggestion - return suggestions.map((suggestion) => - suggestion !== commaCompleteItem - ? { - ...suggestion, - text: - hasMoreMandatoryArgs && fnDefinition.type !== 'builtin' - ? `${suggestion.text},` - : suggestion.text, - } - : suggestion - ); } - return suggestions.map(({ text, ...rest }) => ({ - ...rest, - text: addCommaIf(hasMoreMandatoryArgs && fnDefinition.type !== 'builtin' && text !== '', text), - })); + return suggestions; } async function getListArgsSuggestions( @@ -1521,7 +1550,7 @@ async function getOptionArgsSuggestions( innerText ); - if (isNewExpression || findPreviousWord(innerText) === 'WITH') { + if (isNewExpression || noCaseCompare(findPreviousWord(innerText), 'WITH')) { suggestions.push(buildNewVarDefinition(findNewVariable(anyEnhancedVariables))); } @@ -1595,19 +1624,6 @@ async function getOptionArgsSuggestions( } if (command.name === 'stats') { - suggestions.push( - ...(await getFieldsOrFunctionsSuggestions( - ['column'], - command.name, - option.name, - getFieldsByType, - { - functions: false, - fields: true, - } - )) - ); - const argDef = optionDef?.signature.params[argIndex]; const nodeArgType = extractFinalTypeFromArg(nodeArg, references); @@ -1667,20 +1683,27 @@ async function getOptionArgsSuggestions( }) ); } else if (isNewExpression || (isAssignment(nodeArg) && !isAssignmentComplete(nodeArg))) { - // Otherwise try to complete the expression suggesting some columns suggestions.push( - ...(await getFieldsOrFunctionsSuggestions( - types[0] === 'column' ? ['any'] : types, - command.name, - option.name, - getFieldsByType, - { - functions: option.name === 'by', - fields: true, - } - )) + ...(await getFieldsByType(types[0] === 'column' ? ['any'] : types, [], { + advanceCursorAndOpenSuggestions: true, + })) ); + if (option.name === 'by') { + suggestions.push( + ...(await getFieldsOrFunctionsSuggestions( + types[0] === 'column' ? ['any'] : types, + command.name, + option.name, + getFieldsByType, + { + functions: true, + fields: false, + } + )) + ); + } + if (command.name === 'stats' && isNewExpression) { suggestions.push(buildNewVarDefinition(findNewVariable(anyVariables))); } diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/complete_items.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/complete_items.ts index e0ab600aa1382..88acf1c207f09 100644 --- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/complete_items.ts +++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/complete_items.ts @@ -98,13 +98,16 @@ function buildCharCompleteItem( sortText, }; } -export const pipeCompleteItem = buildCharCompleteItem( - '|', - i18n.translate('kbn-esql-validation-autocomplete.esql.autocomplete.pipeDoc', { +export const pipeCompleteItem: SuggestionRawDefinition = { + label: '|', + text: '| ', + kind: 'Keyword', + detail: i18n.translate('kbn-esql-validation-autocomplete.esql.autocomplete.pipeDoc', { defaultMessage: 'Pipe (|)', }), - { sortText: 'C', quoted: false } -); + sortText: 'C', + command: TRIGGER_SUGGESTION_COMMAND, +}; export const commaCompleteItem = buildCharCompleteItem( ',', diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/factories.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/factories.ts index cb56f40a6d6f4..13a3c76b389f0 100644 --- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/factories.ts +++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/factories.ts @@ -130,7 +130,8 @@ export function getSuggestionCommandDefinition( } export const buildFieldsDefinitionsWithMetadata = ( - fields: ESQLRealField[] + fields: ESQLRealField[], + options?: { advanceCursorAndOpenSuggestions?: boolean; addComma?: boolean } ): SuggestionRawDefinition[] => { return fields.map((field) => { const description = field.metadata?.description; @@ -138,7 +139,10 @@ export const buildFieldsDefinitionsWithMetadata = ( const titleCaseType = field.type.charAt(0).toUpperCase() + field.type.slice(1); return { label: field.name, - text: getSafeInsertText(field.name), + text: + getSafeInsertText(field.name) + + (options?.addComma ? ',' : '') + + (options?.advanceCursorAndOpenSuggestions ? ' ' : ''), kind: 'Variable', detail: titleCaseType, documentation: description @@ -151,6 +155,7 @@ ${description}`, : undefined, // If there is a description, it is a field from ECS, so it should be sorted to the top sortText: description ? '1D' : 'D', + command: options?.advanceCursorAndOpenSuggestions ? TRIGGER_SUGGESTION_COMMAND : undefined, }; }); }; @@ -185,9 +190,8 @@ export const buildSourcesDefinitions = ( ): SuggestionRawDefinition[] => sources.map(({ name, isIntegration, title }) => ({ label: title ?? name, - text: getSafeInsertSourceText(name), + text: getSafeInsertSourceText(name) + (!isIntegration ? ' ' : ''), isSnippet: isIntegration, - ...(isIntegration && { command: TRIGGER_SUGGESTION_COMMAND }), kind: isIntegration ? 'Class' : 'Issue', detail: isIntegration ? i18n.translate('kbn-esql-validation-autocomplete.esql.autocomplete.integrationDefinition', { @@ -197,16 +201,24 @@ export const buildSourcesDefinitions = ( defaultMessage: `Index`, }), sortText: 'A', + command: TRIGGER_SUGGESTION_COMMAND, })); export const buildConstantsDefinitions = ( userConstants: string[], detail?: string, - sortText?: string + sortText?: string, + /** + * Whether or not to advance the cursor and open the suggestions dialog after inserting the constant. + */ + options?: { advanceCursorAndOpenSuggestions?: boolean; addComma?: boolean } ): SuggestionRawDefinition[] => userConstants.map((label) => ({ label, - text: label, + text: + label + + (options?.addComma ? ',' : '') + + (options?.advanceCursorAndOpenSuggestions ? ' ' : ''), kind: 'Constant', detail: detail ?? @@ -214,32 +226,35 @@ export const buildConstantsDefinitions = ( defaultMessage: `Constant`, }), sortText: sortText ?? 'A', + command: options?.advanceCursorAndOpenSuggestions ? TRIGGER_SUGGESTION_COMMAND : undefined, })); export const buildValueDefinitions = ( values: string[], - detail?: string + options?: { advanceCursorAndOpenSuggestions?: boolean; addComma?: boolean } ): SuggestionRawDefinition[] => values.map((value) => ({ label: `"${value}"`, - text: `"${value}"`, - detail: - detail ?? - i18n.translate('kbn-esql-validation-autocomplete.esql.autocomplete.valueDefinition', { - defaultMessage: 'Literal value', - }), + text: `"${value}"${options?.addComma ? ',' : ''}${ + options?.advanceCursorAndOpenSuggestions ? ' ' : '' + }`, + detail: i18n.translate('kbn-esql-validation-autocomplete.esql.autocomplete.valueDefinition', { + defaultMessage: 'Literal value', + }), kind: 'Value', + command: options?.advanceCursorAndOpenSuggestions ? TRIGGER_SUGGESTION_COMMAND : undefined, })); export const buildNewVarDefinition = (label: string): SuggestionRawDefinition => { return { label, - text: `${label} =`, + text: `${label} = `, kind: 'Variable', detail: i18n.translate('kbn-esql-validation-autocomplete.esql.autocomplete.newVarDoc', { defaultMessage: 'Define a new variable', }), sortText: '1', + command: TRIGGER_SUGGESTION_COMMAND, }; }; @@ -358,21 +373,39 @@ export function getUnitDuration(unit: number = 1) { * "magical" logic. Maybe this is really the same thing as the literalOptions parameter * definition property... */ -export function getCompatibleLiterals(commandName: string, types: string[], names?: string[]) { +export function getCompatibleLiterals( + commandName: string, + types: string[], + names?: string[], + options?: { advanceCursorAndOpenSuggestions?: boolean; addComma?: boolean } +) { const suggestions: SuggestionRawDefinition[] = []; if (types.some(isNumericType)) { if (commandName === 'limit') { // suggest 10/100/1000 for limit - suggestions.push(...buildConstantsDefinitions(['10', '100', '1000'], '')); + suggestions.push( + ...buildConstantsDefinitions(['10', '100', '1000'], '', undefined, { + advanceCursorAndOpenSuggestions: true, + }) + ); } } if (types.includes('time_literal')) { // filter plural for now and suggest only unit + singular - suggestions.push(...buildConstantsDefinitions(getUnitDuration(1))); // i.e. 1 year + suggestions.push( + ...buildConstantsDefinitions(getUnitDuration(1), undefined, undefined, options) + ); // i.e. 1 year } // this is a special type built from the suggestion system, not inherited from the AST if (types.includes('time_literal_unit')) { - suggestions.push(...buildConstantsDefinitions(timeUnitsToSuggest.map(({ name }) => name))); // i.e. year, month, ... + suggestions.push( + ...buildConstantsDefinitions( + timeUnitsToSuggest.map(({ name }) => name), + undefined, + undefined, + options + ) + ); // i.e. year, month, ... } if (types.includes('string')) { if (names) { @@ -383,25 +416,31 @@ export function getCompatibleLiterals(commandName: string, types: string[], name [commandName === 'grok' ? '"%{WORD:firstWord}"' : '"%{firstWord}"'], i18n.translate('kbn-esql-validation-autocomplete.esql.autocomplete.aPatternString', { defaultMessage: 'A pattern string', - }) + }), + undefined, + options ) ); } else { - suggestions.push(...buildConstantsDefinitions(['string'], '')); + suggestions.push(...buildConstantsDefinitions(['string'], '', undefined, options)); } } } return suggestions; } -export function getDateLiterals() { +export function getDateLiterals(options?: { + advanceCursorAndOpenSuggestions?: boolean; + addComma?: boolean; +}) { return [ ...buildConstantsDefinitions( TIME_SYSTEM_PARAMS, i18n.translate('kbn-esql-validation-autocomplete.esql.autocomplete.namedParamDefinition', { defaultMessage: 'Named parameter', }), - '1A' + '1A', + options ), { label: i18n.translate( diff --git a/packages/kbn-esql-validation-autocomplete/src/shared/esql_types.ts b/packages/kbn-esql-validation-autocomplete/src/shared/esql_types.ts index dab8769f8477a..fada6bea88134 100644 --- a/packages/kbn-esql-validation-autocomplete/src/shared/esql_types.ts +++ b/packages/kbn-esql-validation-autocomplete/src/shared/esql_types.ts @@ -6,7 +6,8 @@ * Side Public License, v 1. */ -import { ESQLDecimalLiteral, ESQLNumericLiteralType } from '@kbn/esql-ast/src/types'; +import { ESQLDecimalLiteral, ESQLLiteral, ESQLNumericLiteralType } from '@kbn/esql-ast/src/types'; +import { FunctionParameterType } from '../definitions/types'; export const ESQL_COMMON_NUMERIC_TYPES = ['double', 'long', 'integer'] as const; export const ESQL_NUMERIC_DECIMAL_TYPES = [ @@ -47,3 +48,29 @@ export function isNumericDecimalType(type: unknown): type is ESQLDecimalLiteral ESQL_NUMERIC_DECIMAL_TYPES.includes(type as (typeof ESQL_NUMERIC_DECIMAL_TYPES)[number]) ); } + +/** + * Compares two types, taking into account literal types + * @TODO strengthen typing here (remove `string`) + */ +export const compareTypesWithLiterals = ( + a: ESQLLiteral['literalType'] | FunctionParameterType | string, + b: ESQLLiteral['literalType'] | FunctionParameterType | string +) => { + if (a === b) { + return true; + } + if (a === 'decimal') { + return isNumericDecimalType(b); + } + if (b === 'decimal') { + return isNumericDecimalType(a); + } + if (a === 'string') { + return isStringType(b); + } + if (b === 'string') { + return isStringType(a); + } + return false; +}; diff --git a/packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts b/packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts index e13326c2a9f43..1c9c82f676087 100644 --- a/packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts +++ b/packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts @@ -645,3 +645,8 @@ export const isParam = (x: unknown): x is ESQLParamLiteral => typeof x === 'object' && (x as ESQLParamLiteral).type === 'literal' && (x as ESQLParamLiteral).literalType === 'param'; + +/** + * Compares two strings in a case-insensitive manner + */ +export const noCaseCompare = (a: string, b: string) => a.toLowerCase() === b.toLowerCase(); diff --git a/packages/kbn-ftr-common-functional-services/index.ts b/packages/kbn-ftr-common-functional-services/index.ts index 2fabcc12227c5..4bd3eca34c45c 100644 --- a/packages/kbn-ftr-common-functional-services/index.ts +++ b/packages/kbn-ftr-common-functional-services/index.ts @@ -23,4 +23,6 @@ export type Es = ProvidedType; import { SupertestWithoutAuthProvider } from './services/supertest_without_auth'; export type SupertestWithoutAuthProviderType = ProvidedType; +export type { InternalRequestHeader, RoleCredentials } from './services/saml_auth'; + export type { FtrProviderContext } from './services/ftr_provider_context'; diff --git a/packages/kbn-ftr-common-functional-services/services/all.ts b/packages/kbn-ftr-common-functional-services/services/all.ts index 128d50731081d..49308faeb3dd0 100644 --- a/packages/kbn-ftr-common-functional-services/services/all.ts +++ b/packages/kbn-ftr-common-functional-services/services/all.ts @@ -11,6 +11,7 @@ import { EsProvider } from './es'; import { KibanaServerProvider } from './kibana_server'; import { RetryService } from './retry'; import { SupertestWithoutAuthProvider } from './supertest_without_auth'; +import { SamlAuthProvider } from './saml_auth'; export const services = { es: EsProvider, @@ -18,4 +19,5 @@ export const services = { esArchiver: EsArchiverProvider, retry: RetryService, supertestWithoutAuth: SupertestWithoutAuthProvider, + samlAuth: SamlAuthProvider, }; diff --git a/packages/kbn-ftr-common-functional-services/services/saml_auth/default_request_headers.ts b/packages/kbn-ftr-common-functional-services/services/saml_auth/default_request_headers.ts new file mode 100644 index 0000000000000..95983647647f3 --- /dev/null +++ b/packages/kbn-ftr-common-functional-services/services/saml_auth/default_request_headers.ts @@ -0,0 +1,33 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export const COMMON_REQUEST_HEADERS = { + 'kbn-xsrf': 'some-xsrf-token', +}; + +// possible change in 9.0 to match serverless +const STATEFUL_INTERNAL_REQUEST_HEADERS = { + ...COMMON_REQUEST_HEADERS, +}; + +const SERVERLESS_INTERNAL_REQUEST_HEADERS = { + ...COMMON_REQUEST_HEADERS, + 'x-elastic-internal-origin': 'kibana', +}; + +export type InternalRequestHeader = + | typeof STATEFUL_INTERNAL_REQUEST_HEADERS + | typeof SERVERLESS_INTERNAL_REQUEST_HEADERS; + +export const getServerlessInternalRequestHeaders = (): InternalRequestHeader => { + return SERVERLESS_INTERNAL_REQUEST_HEADERS; +}; + +export const getStatefulInternalRequestHeaders = (): InternalRequestHeader => { + return STATEFUL_INTERNAL_REQUEST_HEADERS; +}; diff --git a/packages/kbn-ftr-common-functional-services/services/saml_auth/get_auth_provider.ts b/packages/kbn-ftr-common-functional-services/services/saml_auth/get_auth_provider.ts new file mode 100644 index 0000000000000..3f1ec7af917ff --- /dev/null +++ b/packages/kbn-ftr-common-functional-services/services/saml_auth/get_auth_provider.ts @@ -0,0 +1,53 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import fs from 'fs'; +import { type Config } from '@kbn/test'; +import { ToolingLog } from '@kbn/tooling-log'; +import { MOCK_IDP_REALM_NAME } from '@kbn/mock-idp-utils'; +import { KibanaServer } from '../..'; + +import { ServerlessAuthProvider } from './serverless/auth_provider'; +import { StatefulAuthProvider } from './stateful/auth_provider'; +import { createRole, createRoleMapping } from './stateful/create_role_mapping'; + +const STATEFUL_ADMIN_ROLE_MAPPING_PATH = './stateful/admin_mapping'; + +export interface AuthProvider { + getSupportedRoleDescriptors(): any; + getDefaultRole(): string; + getRolesDefinitionPath(): string; + getCommonRequestHeader(): { [key: string]: string }; + getInternalRequestHeader(): { [key: string]: string }; +} + +export interface AuthProviderProps { + config: Config; + kibanaServer: KibanaServer; + log: ToolingLog; +} + +export const getAuthProvider = async (props: AuthProviderProps) => { + const { config, log, kibanaServer } = props; + const isServerless = !!props.config.get('serverless'); + if (isServerless) { + return new ServerlessAuthProvider(config); + } + + const provider = new StatefulAuthProvider(); + // TODO: Move it to @kbn-es package, so that roles and its mapping are created before FTR services loading starts. + // 'viewer' and 'editor' roles are available by default, but we have to create 'admin' role + const adminRoleMapping = JSON.parse( + fs.readFileSync(require.resolve(STATEFUL_ADMIN_ROLE_MAPPING_PATH), 'utf8') + ); + await createRole({ roleName: 'admin', roleMapping: adminRoleMapping, kibanaServer, log }); + const roles = Object.keys(provider.getSupportedRoleDescriptors()); + // Creating roles mapping for mock-idp + await createRoleMapping({ name: MOCK_IDP_REALM_NAME, roles, config, log }); + return provider; +}; diff --git a/packages/kbn-ftr-common-functional-services/services/saml_auth/index.ts b/packages/kbn-ftr-common-functional-services/services/saml_auth/index.ts new file mode 100644 index 0000000000000..15735d4bffcbb --- /dev/null +++ b/packages/kbn-ftr-common-functional-services/services/saml_auth/index.ts @@ -0,0 +1,10 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +export { SamlAuthProvider } from './saml_auth_provider'; +export type { RoleCredentials } from './saml_auth_provider'; +export type { InternalRequestHeader } from './default_request_headers'; diff --git a/x-pack/test_serverless/shared/services/svl_user_manager.ts b/packages/kbn-ftr-common-functional-services/services/saml_auth/saml_auth_provider.ts similarity index 61% rename from x-pack/test_serverless/shared/services/svl_user_manager.ts rename to packages/kbn-ftr-common-functional-services/services/saml_auth/saml_auth_provider.ts index 2bb8cbc5fab40..4e79cad656197 100644 --- a/x-pack/test_serverless/shared/services/svl_user_manager.ts +++ b/packages/kbn-ftr-common-functional-services/services/saml_auth/saml_auth_provider.ts @@ -1,18 +1,18 @@ /* * 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. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ -import { ServerlessProjectType, SERVERLESS_ROLES_ROOT_PATH } from '@kbn/es'; import { SamlSessionManager } from '@kbn/test'; -import { readRolesDescriptorsFromResource } from '@kbn/es'; -import { resolve } from 'path'; -import { Role } from '@kbn/test/src/auth/types'; -import { isServerlessProjectType } from '@kbn/es/src/utils'; import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../functional/ftr_provider_context'; +import { REPO_ROOT } from '@kbn/repo-info'; +import { resolve } from 'path'; +import { FtrProviderContext } from '../ftr_provider_context'; +import { getAuthProvider } from './get_auth_provider'; +import { InternalRequestHeader } from './default_request_headers'; export interface RoleCredentials { apiKey: { id: string; name: string }; @@ -20,63 +20,41 @@ export interface RoleCredentials { cookieHeader: { Cookie: string }; } -export function SvlUserManagerProvider({ getService }: FtrProviderContext) { +export async function SamlAuthProvider({ getService }: FtrProviderContext) { const config = getService('config'); const log = getService('log'); - const svlCommonApi = getService('svlCommonApi'); + const kibanaServer = getService('kibanaServer'); const supertestWithoutAuth = getService('supertestWithoutAuth'); const isCloud = !!process.env.TEST_CLOUD; - const kbnServerArgs = config.get('kbnTestServer.serverArgs') as string[]; - const projectType = kbnServerArgs - .filter((arg) => arg.startsWith('--serverless')) - .reduce((acc, arg) => { - const match = arg.match(/--serverless[=\s](\w+)/); - return acc + (match ? match[1] : ''); - }, '') as ServerlessProjectType; - - if (!isServerlessProjectType(projectType)) { - throw new Error(`Unsupported serverless projectType: ${projectType}`); - } - - const supportedRoleDescriptors = readRolesDescriptorsFromResource( - resolve(SERVERLESS_ROLES_ROOT_PATH, projectType, 'roles.yml') - ); + const authRoleProvider = await getAuthProvider({ config, kibanaServer, log }); + const supportedRoleDescriptors = authRoleProvider.getSupportedRoleDescriptors(); const supportedRoles = Object.keys(supportedRoleDescriptors); - const defaultRolesToMap = new Map([ - ['es', 'developer'], - ['security', 'editor'], - ['oblt', 'editor'], - ]); - - const getDefaultRole = () => { - if (defaultRolesToMap.has(projectType)) { - return defaultRolesToMap.get(projectType)!; - } else { - throw new Error(`Default role is not defined for ${projectType} project`); - } - }; - const customRolesFileName: string | undefined = process.env.ROLES_FILENAME_OVERRIDE; + const cloudUsersFilePath = resolve(REPO_ROOT, '.ftr', customRolesFileName ?? 'role_users.json'); + // Sharing the instance within FTR config run means cookies are persistent for each role between tests. - const sessionManager = new SamlSessionManager( - { - hostOptions: { - protocol: config.get('servers.kibana.protocol'), - hostname: config.get('servers.kibana.hostname'), - port: isCloud ? undefined : config.get('servers.kibana.port'), - username: config.get('servers.kibana.username'), - password: config.get('servers.kibana.password'), - }, - log, - isCloud, - supportedRoles, + const sessionManager = new SamlSessionManager({ + hostOptions: { + protocol: config.get('servers.kibana.protocol'), + hostname: config.get('servers.kibana.hostname'), + port: isCloud ? undefined : config.get('servers.kibana.port'), + username: config.get('servers.kibana.username'), + password: config.get('servers.kibana.password'), }, - customRolesFileName - ); + log, + isCloud, + supportedRoles: { + roles: supportedRoles, + sourcePath: authRoleProvider.getRolesDefinitionPath(), + }, + cloudUsersFilePath, + }); - const DEFAULT_ROLE = getDefaultRole(); + const DEFAULT_ROLE = authRoleProvider.getDefaultRole(); + const COMMON_REQUEST_HEADERS = authRoleProvider.getCommonRequestHeader(); + const INTERNAL_REQUEST_HEADERS = authRoleProvider.getInternalRequestHeader(); return { async getInteractiveUserSessionCookieWithRoleScope(role: string) { @@ -119,7 +97,7 @@ export function SvlUserManagerProvider({ getService }: FtrProviderContext) { const { body, status } = await supertestWithoutAuth .post('/internal/security/api_key') - .set(svlCommonApi.getInternalRequestHeader()) + .set(INTERNAL_REQUEST_HEADERS) .set(adminCookieHeader) .send({ name: 'myTestApiKey', @@ -147,12 +125,19 @@ export function SvlUserManagerProvider({ getService }: FtrProviderContext) { const { status } = await supertestWithoutAuth .post('/internal/security/api_key/invalidate') - .set(svlCommonApi.getInternalRequestHeader()) + .set(INTERNAL_REQUEST_HEADERS) .set(roleCredentials.cookieHeader) .send(requestBody); expect(status).to.be(200); }, + getCommonRequestHeader() { + return COMMON_REQUEST_HEADERS; + }, + + getInternalRequestHeader(): InternalRequestHeader { + return INTERNAL_REQUEST_HEADERS; + }, DEFAULT_ROLE, }; } diff --git a/packages/kbn-ftr-common-functional-services/services/saml_auth/serverless/auth_provider.ts b/packages/kbn-ftr-common-functional-services/services/saml_auth/serverless/auth_provider.ts new file mode 100644 index 0000000000000..eecbe3a5862f2 --- /dev/null +++ b/packages/kbn-ftr-common-functional-services/services/saml_auth/serverless/auth_provider.ts @@ -0,0 +1,67 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ServerlessProjectType, SERVERLESS_ROLES_ROOT_PATH } from '@kbn/es'; +import { type Config } from '@kbn/test'; +import { isServerlessProjectType, readRolesDescriptorsFromResource } from '@kbn/es/src/utils'; +import { resolve } from 'path'; +import { Role } from '@kbn/test/src/auth/types'; +import { + getServerlessInternalRequestHeaders, + COMMON_REQUEST_HEADERS, +} from '../default_request_headers'; +import { AuthProvider } from '../get_auth_provider'; + +const projectDefaultRoles = new Map([ + ['es', 'developer'], + ['security', 'editor'], + ['oblt', 'editor'], +]); + +const getDefaultServerlessRole = (projectType: string) => { + if (projectDefaultRoles.has(projectType)) { + return projectDefaultRoles.get(projectType)!; + } else { + throw new Error(`Default role is not defined for ${projectType} project`); + } +}; + +export class ServerlessAuthProvider implements AuthProvider { + private readonly projectType: string; + private readonly rolesDefinitionPath: string; + + constructor(config: Config) { + const kbnServerArgs = config.get('kbnTestServer.serverArgs') as string[]; + this.projectType = kbnServerArgs.reduce((acc, arg) => { + const match = arg.match(/--serverless[=\s](\w+)/); + return acc + (match ? match[1] : ''); + }, '') as ServerlessProjectType; + + if (!isServerlessProjectType(this.projectType)) { + throw new Error(`Unsupported serverless projectType: ${this.projectType}`); + } + + this.rolesDefinitionPath = resolve(SERVERLESS_ROLES_ROOT_PATH, this.projectType, 'roles.yml'); + } + + getSupportedRoleDescriptors(): any { + return readRolesDescriptorsFromResource(this.rolesDefinitionPath); + } + getDefaultRole(): string { + return getDefaultServerlessRole(this.projectType); + } + getRolesDefinitionPath(): string { + return this.rolesDefinitionPath; + } + getCommonRequestHeader() { + return COMMON_REQUEST_HEADERS; + } + getInternalRequestHeader() { + return getServerlessInternalRequestHeaders(); + } +} diff --git a/packages/kbn-ftr-common-functional-services/services/saml_auth/stateful/admin_mapping.json b/packages/kbn-ftr-common-functional-services/services/saml_auth/stateful/admin_mapping.json new file mode 100644 index 0000000000000..f8544f16bd239 --- /dev/null +++ b/packages/kbn-ftr-common-functional-services/services/saml_auth/stateful/admin_mapping.json @@ -0,0 +1,46 @@ +{ + "kibana":[ + { + "base":[ + "all" + ], + "feature":{ + + }, + "spaces":[ + "*" + ] + } + ], + "elasticsearch":{ + "cluster":[ + "all" + ], + "indices":[ + { + "names":[ + "*" + ], + "privileges":[ + "all" + ], + "allow_restricted_indices":false + }, + { + "names":[ + "*" + ], + "privileges":[ + "monitor", + "read", + "read_cross_cluster", + "view_index_metadata" + ], + "allow_restricted_indices":true + } + ], + "run_as":[ + + ] + } +} \ No newline at end of file diff --git a/packages/kbn-ftr-common-functional-services/services/saml_auth/stateful/auth_provider.ts b/packages/kbn-ftr-common-functional-services/services/saml_auth/stateful/auth_provider.ts new file mode 100644 index 0000000000000..cf27fd9d5d506 --- /dev/null +++ b/packages/kbn-ftr-common-functional-services/services/saml_auth/stateful/auth_provider.ts @@ -0,0 +1,37 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { readRolesDescriptorsFromResource, STATEFUL_ROLES_ROOT_PATH } from '@kbn/es'; +import { REPO_ROOT } from '@kbn/repo-info'; +import { resolve } from 'path'; +import { AuthProvider } from '../get_auth_provider'; +import { + getStatefulInternalRequestHeaders, + COMMON_REQUEST_HEADERS, +} from '../default_request_headers'; + +export class StatefulAuthProvider implements AuthProvider { + private readonly rolesDefinitionPath = resolve(REPO_ROOT, STATEFUL_ROLES_ROOT_PATH, 'roles.yml'); + getSupportedRoleDescriptors(): any { + return readRolesDescriptorsFromResource(this.rolesDefinitionPath); + } + getDefaultRole() { + return 'editor'; + } + getRolesDefinitionPath() { + return this.rolesDefinitionPath; + } + + getCommonRequestHeader() { + return COMMON_REQUEST_HEADERS; + } + + getInternalRequestHeader() { + return getStatefulInternalRequestHeaders(); + } +} diff --git a/packages/kbn-ftr-common-functional-services/services/saml_auth/stateful/create_role_mapping.ts b/packages/kbn-ftr-common-functional-services/services/saml_auth/stateful/create_role_mapping.ts new file mode 100644 index 0000000000000..1e9b897362dc6 --- /dev/null +++ b/packages/kbn-ftr-common-functional-services/services/saml_auth/stateful/create_role_mapping.ts @@ -0,0 +1,52 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { Config, createEsClientForFtrConfig } from '@kbn/test'; +import { ToolingLog } from '@kbn/tooling-log'; +import { KibanaServer } from '../../..'; + +export interface CreateRoleProps { + roleName: string; + roleMapping: string[]; + kibanaServer: KibanaServer; + log: ToolingLog; +} + +export interface CreateRoleMappingProps { + name: string; + roles: string[]; + config: Config; + log: ToolingLog; +} + +export async function createRole(props: CreateRoleProps) { + const { roleName, roleMapping, kibanaServer, log } = props; + log.debug(`Adding a role: ${roleName}`); + const { status, statusText } = await kibanaServer.request({ + path: `/api/security/role/${roleName}`, + method: 'PUT', + body: roleMapping, + retries: 0, + }); + if (status !== 204) { + throw new Error(`Expected status code of 204, received ${status} ${statusText}`); + } +} + +export async function createRoleMapping(props: CreateRoleMappingProps) { + const { name, roles, config, log } = props; + log.debug(`Creating a role mapping: {realm.name: ${name}, roles: ${roles}}`); + const esClient = createEsClientForFtrConfig(config); + await esClient.security.putRoleMapping({ + name, + roles, + enabled: true, + // @ts-ignore + rules: { field: { 'realm.name': name } }, + }); +} diff --git a/packages/kbn-ftr-common-functional-services/tsconfig.json b/packages/kbn-ftr-common-functional-services/tsconfig.json index 3641c807e4d6d..ff10ec6c9d5fb 100644 --- a/packages/kbn-ftr-common-functional-services/tsconfig.json +++ b/packages/kbn-ftr-common-functional-services/tsconfig.json @@ -14,7 +14,11 @@ "@kbn/core-saved-objects-server", "@kbn/tooling-log", "@kbn/es-archiver", - "@kbn/test" + "@kbn/test", + "@kbn/expect", + "@kbn/repo-info", + "@kbn/es", + "@kbn/mock-idp-utils" ], "exclude": [ "target/**/*", diff --git a/packages/kbn-test/src/auth/helper.ts b/packages/kbn-test/src/auth/helper.ts index fc32eab773c8e..3c8374911af8c 100644 --- a/packages/kbn-test/src/auth/helper.ts +++ b/packages/kbn-test/src/auth/helper.ts @@ -10,12 +10,13 @@ import * as fs from 'fs'; import { Role, User } from './types'; export const readCloudUsersFromFile = (filePath: string): Array<[Role, User]> => { + const defaultMessage = `Cannot read roles and email/password from ${filePath}`; if (!fs.existsSync(filePath)) { - throw new Error(`Please define user roles with email/password in ${filePath}`); + throw new Error(`${defaultMessage}: file does not exist`); } const data = fs.readFileSync(filePath, 'utf8'); if (data.length === 0) { - throw new Error(`'${filePath}' is empty: no roles are defined`); + throw new Error(`${defaultMessage}: file is empty`); } return Object.entries(JSON.parse(data)) as Array<[Role, User]>; diff --git a/packages/kbn-test/src/auth/session_manager.ts b/packages/kbn-test/src/auth/session_manager.ts index 40beccdfc3c6c..fb12e181e8a1e 100644 --- a/packages/kbn-test/src/auth/session_manager.ts +++ b/packages/kbn-test/src/auth/session_manager.ts @@ -6,10 +6,7 @@ * Side Public License, v 1. */ -import { SERVERLESS_ROLES_ROOT_PATH } from '@kbn/es'; -import { REPO_ROOT } from '@kbn/repo-info'; import { ToolingLog } from '@kbn/tooling-log'; -import { resolve } from 'path'; import Url from 'url'; import { KbnClient } from '../kbn_client'; import { readCloudUsersFromFile } from './helper'; @@ -32,31 +29,32 @@ export interface HostOptions { export interface SamlSessionManagerOptions { hostOptions: HostOptions; isCloud: boolean; - supportedRoles?: string[]; + supportedRoles?: SupportedRoles; + cloudUsersFilePath: string; log: ToolingLog; } +export interface SupportedRoles { + sourcePath: string; + roles: string[]; +} + /** * Manages cookies associated with user roles */ export class SamlSessionManager { - private readonly DEFAULT_ROLES_FILE_NAME: string = 'role_users.json'; private readonly isCloud: boolean; private readonly kbnHost: string; private readonly kbnClient: KbnClient; private readonly log: ToolingLog; private readonly roleToUserMap: Map; private readonly sessionCache: Map; - private readonly supportedRoles: string[]; - private readonly userRoleFilePath: string; + private readonly supportedRoles?: SupportedRoles; + private readonly cloudUsersFilePath: string; - constructor(options: SamlSessionManagerOptions, rolesFilename?: string) { + constructor(options: SamlSessionManagerOptions) { this.isCloud = options.isCloud; this.log = options.log; - // if the rolesFilename is provided, respect it. Otherwise use DEFAULT_ROLES_FILE_NAME. - const rolesFile = rolesFilename ? rolesFilename : this.DEFAULT_ROLES_FILE_NAME; - this.log.info(`Using the file ${rolesFile} for the role users`); - this.userRoleFilePath = resolve(REPO_ROOT, '.ftr', rolesFile); const hostOptionsWithoutAuth = { protocol: options.hostOptions.protocol, hostname: options.hostOptions.hostname, @@ -70,9 +68,10 @@ export class SamlSessionManager { auth: `${options.hostOptions.username}:${options.hostOptions.password}`, }), }); + this.cloudUsersFilePath = options.cloudUsersFilePath; this.sessionCache = new Map(); this.roleToUserMap = new Map(); - this.supportedRoles = options.supportedRoles ?? []; + this.supportedRoles = options.supportedRoles; } /** @@ -81,7 +80,8 @@ export class SamlSessionManager { */ private getCloudUsers = () => { if (this.roleToUserMap.size === 0) { - const data = readCloudUsersFromFile(this.userRoleFilePath); + this.log.info(`Reading cloud user credentials from ${this.cloudUsersFilePath}`); + const data = readCloudUsersFromFile(this.cloudUsersFilePath); for (const [roleName, user] of data) { this.roleToUserMap.set(roleName, user); } @@ -104,11 +104,11 @@ export class SamlSessionManager { } // Validate role before creating SAML session - if (this.supportedRoles.length && !this.supportedRoles.includes(role)) { + if (this.supportedRoles && !this.supportedRoles.roles.includes(role)) { throw new Error( - `Role '${role}' is not defined in the supported list: ${this.supportedRoles.join( + `Role '${role}' is not in the supported list: ${this.supportedRoles.roles.join( ', ' - )}. Update roles resource file in ${SERVERLESS_ROLES_ROOT_PATH} to enable it for testing` + )}. Add role descriptor in ${this.supportedRoles.sourcePath} to enable it for testing` ); } diff --git a/packages/kbn-test/src/auth/sesson_manager.test.ts b/packages/kbn-test/src/auth/sesson_manager.test.ts index 1f04620584507..929517e7c5a10 100644 --- a/packages/kbn-test/src/auth/sesson_manager.test.ts +++ b/packages/kbn-test/src/auth/sesson_manager.test.ts @@ -9,17 +9,23 @@ import { ToolingLog } from '@kbn/tooling-log'; import { Cookie } from 'tough-cookie'; import { Session } from './saml_auth'; -import { SamlSessionManager } from './session_manager'; +import { SamlSessionManager, SupportedRoles } from './session_manager'; import * as samlAuth from './saml_auth'; import * as helper from './helper'; import { Role, User, UserProfile } from './types'; import { SERVERLESS_ROLES_ROOT_PATH } from '@kbn/es'; +import { resolve } from 'path'; +import { REPO_ROOT } from '@kbn/repo-info'; const log = new ToolingLog(); -const supportedRoles = ['admin', 'editor', 'viewer']; +const supportedRoles: SupportedRoles = { + roles: ['admin', 'editor', 'viewer'], + sourcePath: 'test/roles.yml', +}; const roleViewer = 'viewer'; const roleEditor = 'editor'; +const cloudUsersFilePath = resolve(REPO_ROOT, SERVERLESS_ROLES_ROOT_PATH, 'role_users.json'); const createLocalSAMLSessionMock = jest.spyOn(samlAuth, 'createLocalSAMLSession'); const createCloudSAMLSessionMock = jest.spyOn(samlAuth, 'createCloudSAMLSession'); @@ -58,7 +64,7 @@ describe('SamlSessionManager', () => { hostOptions, isCloud, log, - supportedRoles, + cloudUsersFilePath, }; const testEmail = 'testuser@elastic.com'; const testFullname = 'Test User'; @@ -67,7 +73,7 @@ describe('SamlSessionManager', () => { )!; test('should create an instance of SamlSessionManager', () => { - const samlSessionManager = new SamlSessionManager({ hostOptions, log, isCloud }); + const samlSessionManager = new SamlSessionManager(samlSessionManagerOptions); expect(samlSessionManager).toBeInstanceOf(SamlSessionManager); }); @@ -118,10 +124,13 @@ describe('SamlSessionManager', () => { test(`throws error when role is not in 'supportedRoles'`, async () => { const nonExistingRole = 'tester'; - const expectedErrorMessage = `Role '${nonExistingRole}' is not defined in the supported list: ${supportedRoles.join( + const expectedErrorMessage = `Role '${nonExistingRole}' is not in the supported list: ${supportedRoles.roles.join( ', ' - )}. Update roles resource file in ${SERVERLESS_ROLES_ROOT_PATH} to enable it for testing`; - const samlSessionManager = new SamlSessionManager(samlSessionManagerOptions); + )}. Add role descriptor in ${supportedRoles.sourcePath} to enable it for testing`; + const samlSessionManager = new SamlSessionManager({ + ...samlSessionManagerOptions, + supportedRoles, + }); await expect( samlSessionManager.getInteractiveUserSessionCookieWithRoleScope(nonExistingRole) ).rejects.toThrow(expectedErrorMessage); @@ -145,11 +154,7 @@ describe('SamlSessionManager', () => { elastic_cloud_user: false, }; getSecurityProfileMock.mockResolvedValueOnce(testData); - const samlSessionManager = new SamlSessionManager({ - hostOptions, - log, - isCloud, - }); + const samlSessionManager = new SamlSessionManager(samlSessionManagerOptions); await samlSessionManager.getInteractiveUserSessionCookieWithRoleScope(nonExistingRole); await samlSessionManager.getApiCredentialsForRole(nonExistingRole); await samlSessionManager.getUserData(nonExistingRole); @@ -171,7 +176,7 @@ describe('SamlSessionManager', () => { hostOptions, isCloud, log, - supportedRoles, + cloudUsersFilePath, }; const cloudCookieInstance = Cookie.parse( 'sid=cloud_cookie_value; Path=/; Expires=Wed, 01 Oct 2023 07:00:00 GMT' @@ -195,11 +200,7 @@ describe('SamlSessionManager', () => { test('should throw error if TEST_CLOUD_HOST_NAME is not set', async () => { isValidHostnameMock.mockReturnValueOnce(false); - const samlSessionManager = new SamlSessionManager({ - hostOptions, - log, - isCloud, - }); + const samlSessionManager = new SamlSessionManager(samlSessionManagerOptions); await expect( samlSessionManager.getInteractiveUserSessionCookieWithRoleScope(roleViewer) ).rejects.toThrow( @@ -220,11 +221,7 @@ describe('SamlSessionManager', () => { }); test('should create an instance of SamlSessionManager', () => { - const samlSessionManager = new SamlSessionManager({ - hostOptions, - log, - isCloud, - }); + const samlSessionManager = new SamlSessionManager(samlSessionManagerOptions); expect(samlSessionManager).toBeInstanceOf(SamlSessionManager); }); @@ -276,10 +273,13 @@ describe('SamlSessionManager', () => { test(`throws error for non-existing role when 'supportedRoles' is defined`, async () => { const nonExistingRole = 'tester'; - const expectedErrorMessage = `Role '${nonExistingRole}' is not defined in the supported list: ${supportedRoles.join( + const expectedErrorMessage = `Role '${nonExistingRole}' is not in the supported list: ${supportedRoles.roles.join( ', ' - )}. Update roles resource file in ${SERVERLESS_ROLES_ROOT_PATH} to enable it for testing`; - const samlSessionManager = new SamlSessionManager(samlSessionManagerOptions); + )}. Add role descriptor in ${supportedRoles.sourcePath} to enable it for testing`; + const samlSessionManager = new SamlSessionManager({ + ...samlSessionManagerOptions, + supportedRoles, + }); await expect( samlSessionManager.getInteractiveUserSessionCookieWithRoleScope(nonExistingRole) ).rejects.toThrow(expectedErrorMessage); @@ -294,11 +294,7 @@ describe('SamlSessionManager', () => { test(`throws error for non-existing role when 'supportedRoles' is not defined`, async () => { const nonExistingRole = 'tester'; - const samlSessionManager = new SamlSessionManager({ - hostOptions, - log, - isCloud, - }); + const samlSessionManager = new SamlSessionManager(samlSessionManagerOptions); await expect( samlSessionManager.getInteractiveUserSessionCookieWithRoleScope(nonExistingRole) ).rejects.toThrow(`User with '${nonExistingRole}' role is not defined`); diff --git a/packages/kbn-test/src/functional_test_runner/lib/config/run_check_ftr_configs_cli.ts b/packages/kbn-test/src/functional_test_runner/lib/config/run_check_ftr_configs_cli.ts index 7176d21b8f0b3..309cac56ca30c 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/config/run_check_ftr_configs_cli.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/config/run_check_ftr_configs_cli.ts @@ -125,7 +125,8 @@ export async function runCheckFtrConfigsCli() { const invalid = possibleConfigs.filter((path) => !allFtrConfigs.includes(path)); if (invalid.length) { - const invalidList = invalid.map((path) => Path.relative(REPO_ROOT, path)).join('\n - '); + const invalidList = + ' - ' + invalid.map((path) => Path.relative(REPO_ROOT, path)).join('\n - '); log.error( `The following files look like FTR configs which are not listed in one of manifest files:\n${invalidList}\n Make sure to add your new FTR config to the correct manifest file.\n diff --git a/packages/kbn-text-based-editor/src/text_based_languages_editor.tsx b/packages/kbn-text-based-editor/src/text_based_languages_editor.tsx index 8b92bb213157e..e67ffeac3c177 100644 --- a/packages/kbn-text-based-editor/src/text_based_languages_editor.tsx +++ b/packages/kbn-text-based-editor/src/text_based_languages_editor.tsx @@ -763,8 +763,18 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ forceMoveMarkers: true, }, ]); + setPopoverPosition({}); + datePickerOpenStatusRef.current = false; + + // move the cursor past the date we just inserted + editor1.current?.setPosition({ + lineNumber: currentCursorPosition?.lineNumber ?? 0, + column: (currentCursorPosition?.column ?? 0) + addition.length - 1, + }); + // restore focus to the editor + editor1.current?.focus(); } }} inline diff --git a/packages/kbn-unified-data-table/README.md b/packages/kbn-unified-data-table/README.md index 576a676289d7a..7a2db17781ad1 100644 --- a/packages/kbn-unified-data-table/README.md +++ b/packages/kbn-unified-data-table/README.md @@ -41,13 +41,12 @@ Props description: | **configRowHeight** | (optional)number | Optional value for providing configuration setting for UnifiedDataTable rows height. | | **showMultiFields** | (optional)boolean | Optional value for providing configuration setting for enabling to display the complex fields in the table. Default is true. | | **maxDocFieldsDisplayed** | (optional)number | Optional value for providing configuration setting for maximum number of document fields to display in the table. Default is 50. | -| **externalControlColumns** | (optional)EuiDataGridControlColumn[] | Optional value for providing EuiDataGridControlColumn list of the additional leading control columns. UnifiedDataTable includes two control columns: Open Details and Select. | +| **rowAdditionalLeadingControls** | (optional)RowControlColumn[] | Optional value for providing an list of the additional leading control columns. UnifiedDataTable includes two control columns: Open Details and Select. | | **totalHits** | (optional)number | Number total hits from ES. | | **onFetchMoreRecords** | (optional)() => void | To fetch more. | | **externalAdditionalControls** | (optional)React.ReactNode | Optional value for providing the additional controls available in the UnifiedDataTable toolbar to manage it's records or state. UnifiedDataTable includes Columns, Sorting and Bulk Actions. | | **rowsPerPageOptions** | (optional)number[] | Optional list of number type values to set custom UnifiedDataTable paging options to display the records per page. | | **renderCustomGridBody** | (optional)(args: EuiDataGridCustomBodyProps) => React.ReactNode; | An optional function called to completely customize and control the rendering of EuiDataGrid's body and cell placement. | -| **trailingControlColumns** | (optional)EuiDataGridControlColumn[] | An optional list of the EuiDataGridControlColumn type for setting trailing control columns standard for EuiDataGrid. | | **visibleCellActions** | (optional)number | An optional value for a custom number of the visible cell actions in the table. By default is up to 3. | | **externalCustomRenderers** | (optional)Record React.ReactNode>; | An optional settings for a specified fields rendering like links. Applied only for the listed fields rendering. | | **consumer** | (optional)string | Name of the UnifiedDataTable consumer component or application. | @@ -141,9 +140,7 @@ Usage example: [browserFields, handleOnPanelClosed, runtimeMappings, timelineId] ); } - externalControlColumns={leadingControlColumns} externalAdditionalControls={additionalControls} - trailingControlColumns={trailingControlColumns} renderCustomGridBody={renderCustomGridBody} rowsPerPageOptions={[10, 30, 40, 100]} showFullScreenButton={false} diff --git a/packages/kbn-unified-data-table/__mocks__/external_control_columns.tsx b/packages/kbn-unified-data-table/__mocks__/external_control_columns.tsx index d67afccc01559..dce5e13a3c89d 100644 --- a/packages/kbn-unified-data-table/__mocks__/external_control_columns.tsx +++ b/packages/kbn-unified-data-table/__mocks__/external_control_columns.tsx @@ -17,6 +17,7 @@ import { EuiSpacer, EuiDataGridControlColumn, } from '@elastic/eui'; +import type { RowControlColumn } from '../src/types'; const SelectionHeaderCell = () => { return ( @@ -116,3 +117,22 @@ export const testLeadingControlColumn: EuiDataGridControlColumn = { rowCellRender: SelectionRowCell, width: 100, }; + +export const mockRowAdditionalLeadingControls = ['visBarVerticalStacked', 'heart', 'inspect'].map( + (iconType, index): RowControlColumn => ({ + id: `exampleControl_${iconType}`, + headerAriaLabel: `Example Row Control ${iconType}`, + renderControl: (Control, rowProps) => { + return ( + { + alert(`Example "${iconType}" control clicked. Row index: ${rowProps.rowIndex}`); + }} + /> + ); + }, + }) +); diff --git a/packages/kbn-unified-data-table/index.ts b/packages/kbn-unified-data-table/index.ts index 0929c33208fa0..7dace83c3774e 100644 --- a/packages/kbn-unified-data-table/index.ts +++ b/packages/kbn-unified-data-table/index.ts @@ -25,7 +25,7 @@ export { getRowsPerPageOptions } from './src/utils/rows_per_page'; export { popularizeField } from './src/utils/popularize_field'; export { useColumns } from './src/hooks/use_data_grid_columns'; -export { OPEN_DETAILS, SELECT_ROW } from './src/components/data_table_columns'; +export { OPEN_DETAILS, SELECT_ROW } from './src/components/data_table_columns'; // TODO: deprecate? export { DataTableRowControl } from './src/components/data_table_row_control'; export type { diff --git a/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/get_additional_row_control_columns.test.tsx b/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/get_additional_row_control_columns.test.tsx new file mode 100644 index 0000000000000..044b864213d82 --- /dev/null +++ b/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/get_additional_row_control_columns.test.tsx @@ -0,0 +1,51 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { getAdditionalRowControlColumns } from './get_additional_row_control_columns'; +import { mockRowAdditionalLeadingControls } from '../../../../__mocks__/external_control_columns'; + +describe('getAdditionalRowControlColumns', () => { + it('should work correctly for 0 controls', () => { + const columns = getAdditionalRowControlColumns([]); + + expect(columns).toHaveLength(0); + }); + + it('should work correctly for 1 control', () => { + const columns = getAdditionalRowControlColumns([mockRowAdditionalLeadingControls[0]]); + + expect(columns.map((column) => column.id)).toEqual([ + `additionalRowControl_${mockRowAdditionalLeadingControls[0].id}`, + ]); + }); + + it('should work correctly for 2 controls', () => { + const columns = getAdditionalRowControlColumns([ + mockRowAdditionalLeadingControls[0], + mockRowAdditionalLeadingControls[1], + ]); + + expect(columns.map((column) => column.id)).toEqual([ + `additionalRowControl_${mockRowAdditionalLeadingControls[0].id}`, + `additionalRowControl_${mockRowAdditionalLeadingControls[1].id}`, + ]); + }); + + it('should work correctly for 3 and more controls', () => { + const columns = getAdditionalRowControlColumns([ + mockRowAdditionalLeadingControls[0], + mockRowAdditionalLeadingControls[1], + mockRowAdditionalLeadingControls[2], + ]); + + expect(columns.map((column) => column.id)).toEqual([ + `additionalRowControl_${mockRowAdditionalLeadingControls[0].id}`, + `additionalRowControl_menuControl`, + ]); + }); +}); diff --git a/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/get_additional_row_control_columns.ts b/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/get_additional_row_control_columns.ts new file mode 100644 index 0000000000000..bd297b37bb5df --- /dev/null +++ b/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/get_additional_row_control_columns.ts @@ -0,0 +1,25 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { EuiDataGridControlColumn } from '@elastic/eui'; +import type { RowControlColumn } from '../../../types'; +import { getRowControlColumn } from './row_control_column'; +import { getRowMenuControlColumn } from './row_menu_control_column'; + +export const getAdditionalRowControlColumns = ( + rowControlColumns: RowControlColumn[] +): EuiDataGridControlColumn[] => { + if (rowControlColumns.length <= 2) { + return rowControlColumns.map(getRowControlColumn); + } + + return [ + getRowControlColumn(rowControlColumns[0]), + getRowMenuControlColumn(rowControlColumns.slice(1)), + ]; +}; diff --git a/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/index.ts b/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/index.ts new file mode 100644 index 0000000000000..d3a79fc3a0d50 --- /dev/null +++ b/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/index.ts @@ -0,0 +1,9 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { getAdditionalRowControlColumns } from './get_additional_row_control_columns'; diff --git a/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/row_control_column.test.tsx b/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/row_control_column.test.tsx new file mode 100644 index 0000000000000..360fa7bc235c4 --- /dev/null +++ b/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/row_control_column.test.tsx @@ -0,0 +1,53 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import type { EuiDataGridCellValueElementProps } from '@elastic/eui'; +import { render, screen } from '@testing-library/react'; +import { getRowControlColumn } from './row_control_column'; +import { dataTableContextMock } from '../../../../__mocks__/table_context'; +import { UnifiedDataTableContext } from '../../../table_context'; + +describe('getRowControlColumn', () => { + const contextMock = { + ...dataTableContextMock, + }; + + it('should render the component', () => { + const mockClick = jest.fn(); + const props = { + id: 'test_row_control', + headerAriaLabel: 'row control', + renderControl: jest.fn((Control, rowProps) => ( + + )), + }; + const rowControlColumn = getRowControlColumn(props); + const RowControlColumn = + rowControlColumn.rowCellRender as React.FC; + render( + + + + ); + const button = screen.getByTestId('unifiedDataTable_rowControl_test_row_control'); + expect(button).toBeInTheDocument(); + + button.click(); + + expect(mockClick).toHaveBeenCalledWith({ record: contextMock.rows[1], rowIndex: 1 }); + }); +}); diff --git a/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/row_control_column.tsx b/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/row_control_column.tsx new file mode 100644 index 0000000000000..f8d3ad063fb2d --- /dev/null +++ b/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/row_control_column.tsx @@ -0,0 +1,76 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useMemo } from 'react'; +import { + EuiButtonIcon, + EuiDataGridCellValueElementProps, + EuiDataGridControlColumn, + EuiScreenReaderOnly, + EuiToolTip, +} from '@elastic/eui'; +import { DataTableRowControl, Size } from '../../data_table_row_control'; +import type { RowControlColumn, RowControlProps } from '../../../types'; +import { DEFAULT_CONTROL_COLUMN_WIDTH } from '../../../constants'; +import { useControlColumn } from '../../../hooks/use_control_column'; + +export const RowControlCell = ({ + renderControl, + ...props +}: EuiDataGridCellValueElementProps & { + renderControl: RowControlColumn['renderControl']; +}) => { + const rowProps = useControlColumn(props); + + const Control: React.FC = useMemo( + () => + ({ 'data-test-subj': dataTestSubj, color, disabled, label, iconType, onClick }) => { + return ( + + + { + onClick?.(rowProps); + }} + /> + + + ); + }, + [props.columnId, rowProps] + ); + + return renderControl(Control, rowProps); +}; + +export const getRowControlColumn = ( + rowControlColumn: RowControlColumn +): EuiDataGridControlColumn => { + const { id, headerAriaLabel, headerCellRender, renderControl } = rowControlColumn; + + return { + id: `additionalRowControl_${id}`, + width: DEFAULT_CONTROL_COLUMN_WIDTH, + headerCellRender: + headerCellRender ?? + (() => ( + + {headerAriaLabel} + + )), + rowCellRender: (props) => { + return ; + }, + }; +}; diff --git a/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/row_menu_control_column.test.tsx b/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/row_menu_control_column.test.tsx new file mode 100644 index 0000000000000..8e26e3f01d062 --- /dev/null +++ b/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/row_menu_control_column.test.tsx @@ -0,0 +1,65 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import type { EuiDataGridCellValueElementProps } from '@elastic/eui'; +import { render, screen } from '@testing-library/react'; +import { getRowMenuControlColumn } from './row_menu_control_column'; +import { dataTableContextMock } from '../../../../__mocks__/table_context'; +import { mockRowAdditionalLeadingControls } from '../../../../__mocks__/external_control_columns'; +import { UnifiedDataTableContext } from '../../../table_context'; + +describe('getRowMenuControlColumn', () => { + const contextMock = { + ...dataTableContextMock, + }; + + it('should render the component', () => { + const mockClick = jest.fn(); + const props = { + id: 'test_row_menu_control', + headerAriaLabel: 'row control', + renderControl: jest.fn((Control, rowProps) => ( + + )), + }; + const rowMenuControlColumn = getRowMenuControlColumn([ + props, + mockRowAdditionalLeadingControls[0], + mockRowAdditionalLeadingControls[1], + ]); + const RowMenuControlColumn = + rowMenuControlColumn.rowCellRender as React.FC; + render( + + + + ); + const menuButton = screen.getByTestId('unifiedDataTable_test_row_menu_control'); + expect(menuButton).toBeInTheDocument(); + + menuButton.click(); + + expect(screen.getByTestId('exampleRowControl-visBarVerticalStacked')).toBeInTheDocument(); + expect(screen.getByTestId('exampleRowControl-heart')).toBeInTheDocument(); + + const button = screen.getByTestId('unifiedDataTable_rowMenu_test_row_menu_control'); + expect(button).toBeInTheDocument(); + + button.click(); + expect(mockClick).toHaveBeenCalledWith({ record: contextMock.rows[1], rowIndex: 1 }); + }); +}); diff --git a/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/row_menu_control_column.tsx b/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/row_menu_control_column.tsx new file mode 100644 index 0000000000000..917174618fa37 --- /dev/null +++ b/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/row_menu_control_column.tsx @@ -0,0 +1,130 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { Fragment, useCallback, useMemo, useState } from 'react'; +import { + EuiButtonIcon, + EuiContextMenuItem, + EuiContextMenuPanel, + EuiDataGridCellValueElementProps, + EuiDataGridControlColumn, + EuiPopover, + EuiScreenReaderOnly, + EuiToolTip, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { css } from '@emotion/react'; +import { DataTableRowControl, Size } from '../../data_table_row_control'; +import type { RowControlColumn, RowControlProps } from '../../../types'; +import { DEFAULT_CONTROL_COLUMN_WIDTH } from '../../../constants'; +import { useControlColumn } from '../../../hooks/use_control_column'; + +/** + * Menu button under which all other additional row controls would be placed + */ +export const RowMenuControlCell = ({ + rowControlColumns, + ...props +}: EuiDataGridCellValueElementProps & { + rowControlColumns: RowControlColumn[]; +}) => { + const rowProps = useControlColumn(props); + const [isMoreActionsPopoverOpen, setIsMoreActionsPopoverOpen] = useState(false); + + const buttonLabel = i18n.translate('unifiedDataTable.grid.additionalRowActions', { + defaultMessage: 'Additional actions', + }); + + const getControlComponent: (id: string) => React.FC = useCallback( + (id) => + ({ 'data-test-subj': dataTestSubj, color, disabled, label, iconType, onClick }) => { + return ( + { + onClick?.(rowProps); + setIsMoreActionsPopoverOpen(false); + }} + > + {label} + + ); + }, + [rowProps, setIsMoreActionsPopoverOpen] + ); + + const popoverMenuItems = useMemo( + () => + rowControlColumns.map((rowControlColumn) => { + const Control = getControlComponent(rowControlColumn.id); + return ( + + {rowControlColumn.renderControl(Control, rowProps)} + + ); + }), + [rowControlColumns, rowProps, getControlComponent] + ); + + return ( + + + { + setIsMoreActionsPopoverOpen(!isMoreActionsPopoverOpen); + }} + /> + + + } + isOpen={isMoreActionsPopoverOpen} + closePopover={() => setIsMoreActionsPopoverOpen(false)} + panelPaddingSize="none" + anchorPosition="downLeft" + > + + + ); +}; + +export const getRowMenuControlColumn = ( + rowControlColumns: RowControlColumn[] +): EuiDataGridControlColumn => { + return { + id: 'additionalRowControl_menuControl', + width: DEFAULT_CONTROL_COLUMN_WIDTH, + headerCellRender: () => ( + + + {i18n.translate('unifiedDataTable.additionalActionsColumnHeader', { + defaultMessage: 'Additional actions column', + })} + + + ), + rowCellRender: (props) => { + return ; + }, + }; +}; diff --git a/packages/kbn-unified-data-table/src/components/custom_control_columns/color_indicator/color_indicator_control_column.tsx b/packages/kbn-unified-data-table/src/components/custom_control_columns/color_indicator/color_indicator_control_column.tsx index dd9be4ab90d13..902667810e613 100644 --- a/packages/kbn-unified-data-table/src/components/custom_control_columns/color_indicator/color_indicator_control_column.tsx +++ b/packages/kbn-unified-data-table/src/components/custom_control_columns/color_indicator/color_indicator_control_column.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import React, { useContext, useEffect } from 'react'; +import React from 'react'; import { css } from '@emotion/react'; import { EuiDataGridControlColumn, @@ -15,7 +15,7 @@ import { EuiDataGridCellValueElementProps, } from '@elastic/eui'; import type { DataTableRecord } from '@kbn/discover-utils'; -import { UnifiedDataTableContext } from '../../../table_context'; +import { useControlColumn } from '../../../hooks/use_control_column'; const COLOR_INDICATOR_WIDTH = 4; @@ -28,32 +28,14 @@ interface ColorIndicatorCellParams { ) => { color: string; label: string } | undefined; } -const ColorIndicatorCell: React.FC = ({ - rowIndex, - setCellProps, - getRowIndicator, -}) => { +const ColorIndicatorCell: React.FC = ({ getRowIndicator, ...props }) => { + const { record } = useControlColumn(props); const { euiTheme } = useEuiTheme(); - const { rows, expanded } = useContext(UnifiedDataTableContext); - const row = rows[rowIndex]; - const configuration = row ? getRowIndicator(row, euiTheme) : undefined; + + const configuration = record ? getRowIndicator(record, euiTheme) : undefined; const color = configuration?.color || 'transparent'; const label = configuration?.label; - useEffect(() => { - if (row.isAnchor) { - setCellProps({ - className: 'unifiedDataTable__cell--highlight', - }); - } else if (expanded && row && expanded.id === row.id) { - setCellProps({ - className: 'unifiedDataTable__cell--expanded', - }); - } else { - setCellProps({ className: '' }); - } - }, [expanded, row, setCellProps]); - return (
{ }); }); - describe('customControlColumnsConfiguration', () => { - const customControlColumnsConfiguration = jest.fn(); - it('should be able to customise the leading control column', async () => { + describe('custom control columns', () => { + it('should be able to customise the leading controls', async () => { const component = await getComponent({ ...getProps(), expandedDoc: { @@ -467,23 +467,19 @@ describe('UnifiedDataTable', () => { setExpandedDoc: jest.fn(), renderDocumentView: jest.fn(), externalControlColumns: [testLeadingControlColumn], - customControlColumnsConfiguration: customControlColumnsConfiguration.mockImplementation( - () => { - return { - leadingControlColumns: [testLeadingControlColumn, testTrailingControlColumns[0]], - trailingControlColumns: [], - }; - } - ), + rowAdditionalLeadingControls: mockRowAdditionalLeadingControls, }); expect(findTestSubject(component, 'test-body-control-column-cell').exists()).toBeTruthy(); expect( - findTestSubject(component, 'test-trailing-column-popover-button').exists() + findTestSubject(component, 'exampleRowControl-visBarVerticalStacked').exists() + ).toBeTruthy(); + expect( + findTestSubject(component, 'unifiedDataTable_additionalRowControl_menuControl').exists() ).toBeTruthy(); }); - it('should be able to customise the trailing control column', async () => { + it('should be able to customise the trailing controls', async () => { const component = await getComponent({ ...getProps(), expandedDoc: { @@ -497,14 +493,7 @@ describe('UnifiedDataTable', () => { setExpandedDoc: jest.fn(), renderDocumentView: jest.fn(), externalControlColumns: [testLeadingControlColumn], - customControlColumnsConfiguration: customControlColumnsConfiguration.mockImplementation( - () => { - return { - leadingControlColumns: [], - trailingControlColumns: [testLeadingControlColumn, testTrailingControlColumns[0]], - }; - } - ), + trailingControlColumns: testTrailingControlColumns, }); expect(findTestSubject(component, 'test-body-control-column-cell').exists()).toBeTruthy(); diff --git a/packages/kbn-unified-data-table/src/components/data_table.tsx b/packages/kbn-unified-data-table/src/components/data_table.tsx index 1a12151c7c18a..243b86b540865 100644 --- a/packages/kbn-unified-data-table/src/components/data_table.tsx +++ b/packages/kbn-unified-data-table/src/components/data_table.tsx @@ -51,18 +51,19 @@ import { DataTableColumnsMeta, CustomCellRenderer, CustomGridColumnsConfiguration, - CustomControlColumnConfiguration, + RowControlColumn, } from '../types'; import { getDisplayedColumns } from '../utils/columns'; import { convertValueToString } from '../utils/convert_value_to_string'; import { getRowsPerPageOptions } from '../utils/rows_per_page'; import { getRenderCellValueFn } from '../utils/get_render_cell_value'; import { - getAllControlColumns, getEuiGridColumns, getLeadControlColumns, getVisibleColumns, canPrependTimeFieldColumn, + SELECT_ROW, + OPEN_DETAILS, } from './data_table_columns'; import { UnifiedDataTableContext } from '../table_context'; import { getSchemaDetectors } from './data_table_schema'; @@ -85,8 +86,11 @@ import { useSelectedDocs } from '../hooks/use_selected_docs'; import { getColorIndicatorControlColumn, type ColorIndicatorControlColumnParams, + getAdditionalRowControlColumns, } from './custom_control_columns'; +const CONTROL_COLUMN_IDS_DEFAULT = [SELECT_ROW, OPEN_DETAILS]; + export type SortOrder = [string, string]; export enum DataLoadingState { @@ -290,9 +294,20 @@ export interface UnifiedDataTableProps { */ maxDocFieldsDisplayed?: number; /** + * @deprecated Use only `rowAdditionalLeadingControls` instead * Optional value for providing EuiDataGridControlColumn list of the additional leading control columns. UnifiedDataTable includes two control columns: Open Details and Select. */ externalControlColumns?: EuiDataGridControlColumn[]; + /** + * An optional list of the EuiDataGridControlColumn type for setting trailing control columns standard for EuiDataGrid. + * We recommend to rather position all controls in the beginning of rows and use `rowAdditionalLeadingControls` for that + * as number of columns can be dynamically changed and we don't want the controls to become hidden due to horizontal scroll. + */ + trailingControlColumns?: EuiDataGridControlColumn[]; + /** + * Optional value to extend the list of default row actions + */ + rowAdditionalLeadingControls?: RowControlColumn[]; /** * Number total hits from ES */ @@ -327,10 +342,6 @@ export interface UnifiedDataTableProps { * @param gridProps */ renderCustomToolbar?: UnifiedDataTableRenderCustomToolbar; - /** - * An optional list of the EuiDataGridControlColumn type for setting trailing control columns standard for EuiDataGrid. - */ - trailingControlColumns?: EuiDataGridControlColumn[]; /** * An optional value for a custom number of the visible cell actions in the table. By default is up to 3. **/ @@ -347,10 +358,6 @@ export interface UnifiedDataTableProps { * An optional settings for customising the column */ customGridColumnsConfiguration?: CustomGridColumnsConfiguration; - /** - * An optional settings to control which columns to render as trailing and leading control columns - */ - customControlColumnsConfiguration?: CustomControlColumnConfiguration; /** * Name of the UnifiedDataTable consumer component or application */ @@ -396,8 +403,6 @@ export interface UnifiedDataTableProps { export const EuiDataGridMemoized = React.memo(EuiDataGrid); -const CONTROL_COLUMN_IDS_DEFAULT = ['openDetails', 'select']; - export const UnifiedDataTable = ({ ariaLabelledBy, columns, @@ -407,6 +412,7 @@ export const UnifiedDataTable = ({ headerRowHeightState, onUpdateHeaderRowHeight, controlColumnIds = CONTROL_COLUMN_IDS_DEFAULT, + rowAdditionalLeadingControls, dataView, loadingState, onFilter, @@ -437,7 +443,8 @@ export const UnifiedDataTable = ({ services, renderCustomGridBody, renderCustomToolbar, - trailingControlColumns, + externalControlColumns, // TODO: deprecate in favor of rowAdditionalLeadingControls + trailingControlColumns, // TODO: deprecate in favor of rowAdditionalLeadingControls totalHits, onFetchMoreRecords, renderDocumentView, @@ -446,7 +453,6 @@ export const UnifiedDataTable = ({ configRowHeight, showMultiFields = true, maxDocFieldsDisplayed = 50, - externalControlColumns, externalAdditionalControls, rowsPerPageOptions, visibleCellActions, @@ -458,7 +464,6 @@ export const UnifiedDataTable = ({ rowLineHeightOverride, cellActionsMetadata, customGridColumnsConfiguration, - customControlColumnsConfiguration, enableComparisonMode, cellContext, renderCellPopover, @@ -847,10 +852,19 @@ export const UnifiedDataTable = ({ const canSetExpandedDoc = Boolean(setExpandedDoc && !!renderDocumentView); const leadingControlColumns: EuiDataGridControlColumn[] = useMemo(() => { - const internalControlColumns = getLeadControlColumns(canSetExpandedDoc).filter(({ id }) => - controlColumnIds.includes(id) - ); - const leadingColumns = externalControlColumns + const defaultControlColumns = getLeadControlColumns(canSetExpandedDoc); + const internalControlColumns = controlColumnIds + ? // reorder the default controls as per controlColumnIds + controlColumnIds.reduce((acc, id) => { + const controlColumn = defaultControlColumns.find((col) => col.id === id); + if (controlColumn) { + acc.push(controlColumn); + } + return acc; + }, [] as EuiDataGridControlColumn[]) + : defaultControlColumns; + + const leadingColumns: EuiDataGridControlColumn[] = externalControlColumns ? [...internalControlColumns, ...externalControlColumns] : internalControlColumns; @@ -861,17 +875,18 @@ export const UnifiedDataTable = ({ leadingColumns.unshift(colorIndicatorControlColumn); } - return leadingColumns; - }, [canSetExpandedDoc, controlColumnIds, externalControlColumns, getRowIndicator]); - - const controlColumnsConfig = customControlColumnsConfiguration?.({ - controlColumns: getAllControlColumns(), - }); + if (rowAdditionalLeadingControls?.length) { + leadingColumns.push(...getAdditionalRowControlColumns(rowAdditionalLeadingControls)); + } - const customLeadingControlColumn = - controlColumnsConfig?.leadingControlColumns ?? leadingControlColumns; - const customTrailingControlColumn = - controlColumnsConfig?.trailingControlColumns ?? trailingControlColumns; + return leadingColumns; + }, [ + canSetExpandedDoc, + controlColumnIds, + externalControlColumns, + getRowIndicator, + rowAdditionalLeadingControls, + ]); const additionalControls = useMemo(() => { if (!externalAdditionalControls && !selectedDocIds.length) { @@ -1082,7 +1097,7 @@ export const UnifiedDataTable = ({ columns={euiGridColumns} columnVisibility={columnsVisibility} data-test-subj="docTable" - leadingControlColumns={customLeadingControlColumn} + leadingControlColumns={leadingControlColumns} onColumnResize={onResize} pagination={paginationObj} renderCellValue={renderCellValue} @@ -1096,7 +1111,7 @@ export const UnifiedDataTable = ({ gridStyle={gridStyleOverride ?? GRID_STYLE} renderCustomGridBody={renderCustomGridBody} renderCustomToolbar={renderCustomToolbarFn} - trailingControlColumns={customTrailingControlColumn} + trailingControlColumns={trailingControlColumns} cellContext={cellContext} renderCellPopover={renderCustomPopover} /> diff --git a/packages/kbn-unified-data-table/src/components/data_table_columns.tsx b/packages/kbn-unified-data-table/src/components/data_table_columns.tsx index 202a593018792..4528e323c2047 100644 --- a/packages/kbn-unified-data-table/src/components/data_table_columns.tsx +++ b/packages/kbn-unified-data-table/src/components/data_table_columns.tsx @@ -17,12 +17,16 @@ import { type DataView, DataViewField } from '@kbn/data-views-plugin/public'; import { ToastsStart, IUiSettingsClient } from '@kbn/core/public'; import { DocViewFilterFn } from '@kbn/unified-doc-viewer/types'; import { ExpandButton } from './data_table_expand_button'; -import { ControlColumns, CustomGridColumnsConfiguration, UnifiedDataTableSettings } from '../types'; +import { CustomGridColumnsConfiguration, UnifiedDataTableSettings } from '../types'; import type { ValueToStringConverter, DataTableColumnsMeta } from '../types'; import { buildCellActions } from './default_cell_actions'; import { getSchemaByKbnType } from './data_table_schema'; import { SelectButton, SelectAllButton } from './data_table_document_selection'; -import { defaultTimeColumnWidth, ROWS_HEIGHT_OPTIONS } from '../constants'; +import { + defaultTimeColumnWidth, + ROWS_HEIGHT_OPTIONS, + DEFAULT_CONTROL_COLUMN_WIDTH, +} from '../constants'; import { buildCopyColumnNameButton, buildCopyColumnValuesButton } from './build_copy_column_button'; import { buildEditFieldButton } from './build_edit_field_button'; import { DataTableColumnHeader, DataTableTimeColumnHeader } from './data_table_column_header'; @@ -53,7 +57,7 @@ export const SELECT_ROW = 'select'; const openDetails = { id: OPEN_DETAILS, - width: 26, + width: DEFAULT_CONTROL_COLUMN_WIDTH, headerCellRender: () => ( @@ -68,18 +72,11 @@ const openDetails = { const select = { id: SELECT_ROW, - width: 24, + width: DEFAULT_CONTROL_COLUMN_WIDTH, rowCellRender: SelectButton, headerCellRender: SelectAllButton, }; -export function getAllControlColumns(): ControlColumns { - return { - [SELECT_ROW]: select, - [OPEN_DETAILS]: openDetails, - }; -} - export function getLeadControlColumns(canSetExpandedDoc: boolean) { if (!canSetExpandedDoc) { return [select]; diff --git a/packages/kbn-unified-data-table/src/components/data_table_document_selection.tsx b/packages/kbn-unified-data-table/src/components/data_table_document_selection.tsx index b844a4bb537ca..2a34c4866cb86 100644 --- a/packages/kbn-unified-data-table/src/components/data_table_document_selection.tsx +++ b/packages/kbn-unified-data-table/src/components/data_table_document_selection.tsx @@ -5,7 +5,7 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react'; +import React, { useCallback, useContext, useMemo, useState } from 'react'; import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; import { ES_FIELD_TYPES, KBN_FIELD_TYPES } from '@kbn/field-types'; import { @@ -28,28 +28,19 @@ import { css } from '@emotion/react'; import type { DataTableRecord } from '@kbn/discover-utils/types'; import type { UseSelectedDocsState } from '../hooks/use_selected_docs'; import { UnifiedDataTableContext } from '../table_context'; +import { useControlColumn } from '../hooks/use_control_column'; -export const SelectButton = ({ rowIndex, setCellProps }: EuiDataGridCellValueElementProps) => { +export const SelectButton = (props: EuiDataGridCellValueElementProps) => { + const { record, rowIndex } = useControlColumn(props); const { euiTheme } = useEuiTheme(); - const { selectedDocsState, expanded, rows, isDarkMode } = useContext(UnifiedDataTableContext); + const { selectedDocsState } = useContext(UnifiedDataTableContext); const { isDocSelected, toggleDocSelection } = selectedDocsState; - const doc = useMemo(() => rows[rowIndex], [rows, rowIndex]); const toggleDocumentSelectionLabel = i18n.translate('unifiedDataTable.grid.selectDoc', { defaultMessage: `Select document ''{rowNumber}''`, values: { rowNumber: rowIndex + 1 }, }); - useEffect(() => { - if (expanded && doc && expanded.id === doc.id) { - setCellProps({ - className: 'unifiedDataTable__cell--selected', - }); - } else { - setCellProps({ className: '' }); - } - }, [expanded, doc, setCellProps, isDarkMode]); - return ( { - toggleDocSelection(doc.id); + toggleDocSelection(record.id); }} /> diff --git a/packages/kbn-unified-data-table/src/components/data_table_expand_button.tsx b/packages/kbn-unified-data-table/src/components/data_table_expand_button.tsx index da3c8a5026ea0..04ae49abec141 100644 --- a/packages/kbn-unified-data-table/src/components/data_table_expand_button.tsx +++ b/packages/kbn-unified-data-table/src/components/data_table_expand_button.tsx @@ -10,39 +10,27 @@ import React, { useContext, useEffect, useRef, useState } from 'react'; import { EuiButtonIcon, EuiDataGridCellValueElementProps, EuiToolTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { UnifiedDataTableContext } from '../table_context'; -import { DataTableRowControl } from './data_table_row_control'; +import { DataTableRowControl, Size } from './data_table_row_control'; +import { useControlColumn } from '../hooks/use_control_column'; /** * Button to expand a given row */ -export const ExpandButton = ({ rowIndex, setCellProps }: EuiDataGridCellValueElementProps) => { +export const ExpandButton = (props: EuiDataGridCellValueElementProps) => { + const { record, rowIndex } = useControlColumn(props); + const toolTipRef = useRef(null); const [pressed, setPressed] = useState(false); - const { expanded, setExpanded, rows, isDarkMode, componentsTourSteps } = - useContext(UnifiedDataTableContext); - const current = rows[rowIndex]; + const { expanded, setExpanded, componentsTourSteps } = useContext(UnifiedDataTableContext); const tourStep = componentsTourSteps ? componentsTourSteps.expandButton : undefined; - useEffect(() => { - if (current.isAnchor) { - setCellProps({ - className: 'unifiedDataTable__cell--highlight', - }); - } else if (expanded && current && expanded.id === current.id) { - setCellProps({ - className: 'unifiedDataTable__cell--expanded', - }); - } else { - setCellProps({ className: '' }); - } - }, [expanded, current, setCellProps, isDarkMode]); - const isCurrentRowExpanded = current === expanded; + const isCurrentRowExpanded = record === expanded; const buttonLabel = i18n.translate('unifiedDataTable.grid.viewDoc', { defaultMessage: 'Toggle dialog with details', }); - const testSubj = current.isAnchor + const testSubj = record.isAnchor ? 'docTableExpandToggleColumnAnchor' : 'docTableExpandToggleColumn'; @@ -60,7 +48,7 @@ export const ExpandButton = ({ rowIndex, setCellProps }: EuiDataGridCellValueEle } return ( - + { - const nextHit = isCurrentRowExpanded ? undefined : current; + const nextHit = isCurrentRowExpanded ? undefined : record; toolTipRef.current?.hideToolTip(); setPressed(Boolean(nextHit)); setExpanded?.(nextHit); diff --git a/packages/kbn-unified-data-table/src/components/data_table_row_control.tsx b/packages/kbn-unified-data-table/src/components/data_table_row_control.tsx index 4ceadea549dce..0ac0bbd4cb2f6 100644 --- a/packages/kbn-unified-data-table/src/components/data_table_row_control.tsx +++ b/packages/kbn-unified-data-table/src/components/data_table_row_control.tsx @@ -7,7 +7,16 @@ */ import React from 'react'; +import classnames from 'classnames'; -export const DataTableRowControl = ({ children }: { children: React.ReactNode }) => { - return {children}; +export enum Size { + normal = 'normal', +} + +export const DataTableRowControl: React.FC<{ size?: Size }> = ({ size, children }) => { + const classes = classnames('unifiedDataTable__rowControl', { + // normalize the size of the control + [`unifiedDataTable__rowControl--size-${size}`]: size, + }); + return {children}; }; diff --git a/packages/kbn-unified-data-table/src/constants.ts b/packages/kbn-unified-data-table/src/constants.ts index c2d5654c602c2..c7cf1793039a5 100644 --- a/packages/kbn-unified-data-table/src/constants.ts +++ b/packages/kbn-unified-data-table/src/constants.ts @@ -7,6 +7,8 @@ */ import { EuiDataGridStyle } from '@elastic/eui'; +export const DEFAULT_CONTROL_COLUMN_WIDTH = 24; + export const DEFAULT_ROWS_PER_PAGE = 100; export const MAX_LOADED_GRID_ROWS = 10000; diff --git a/packages/kbn-unified-data-table/src/hooks/use_control_column.ts b/packages/kbn-unified-data-table/src/hooks/use_control_column.ts new file mode 100644 index 0000000000000..e2bc05f668508 --- /dev/null +++ b/packages/kbn-unified-data-table/src/hooks/use_control_column.ts @@ -0,0 +1,41 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { useContext, useEffect, useMemo } from 'react'; +import type { EuiDataGridCellValueElementProps } from '@elastic/eui'; +import type { DataTableRecord } from '@kbn/discover-utils'; +import { UnifiedDataTableContext } from '../table_context'; + +export const useControlColumn = ({ + rowIndex, + setCellProps, +}: Pick): { + record: DataTableRecord; + rowIndex: number; +} => { + const { expanded, rows } = useContext(UnifiedDataTableContext); + const record = useMemo(() => rows[rowIndex], [rows, rowIndex]); + + useEffect(() => { + if (record.isAnchor) { + setCellProps({ + className: 'unifiedDataTable__cell--highlight', + }); + } else if (expanded && record && expanded.id === record.id) { + setCellProps({ + className: 'unifiedDataTable__cell--expanded', + }); + } else { + setCellProps({ + className: '', + }); + } + }, [expanded, record, setCellProps]); + + return useMemo(() => ({ record, rowIndex }), [record, rowIndex]); +}; diff --git a/packages/kbn-unified-data-table/src/types.ts b/packages/kbn-unified-data-table/src/types.ts index 5914fa03f8827..b08818e1a861a 100644 --- a/packages/kbn-unified-data-table/src/types.ts +++ b/packages/kbn-unified-data-table/src/types.ts @@ -6,8 +6,13 @@ * Side Public License, v 1. */ -import type { ReactElement } from 'react'; -import type { EuiDataGridCellValueElementProps, EuiDataGridColumn } from '@elastic/eui'; +import type { ReactElement, FC } from 'react'; +import type { + EuiDataGridCellValueElementProps, + EuiDataGridColumn, + IconType, + EuiButtonIconProps, +} from '@elastic/eui'; import type { DataTableRecord } from '@kbn/discover-utils/src/types'; import type { DataView } from '@kbn/data-views-plugin/common'; import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; @@ -70,16 +75,25 @@ export type CustomGridColumnsConfiguration = Record< (props: CustomGridColumnProps) => EuiDataGridColumn >; -export interface ControlColumns { - select: EuiDataGridControlColumn; - openDetails: EuiDataGridControlColumn; +export interface RowControlRowProps { + rowIndex: number; + record: DataTableRecord; } -export interface ControlColumnsProps { - controlColumns: ControlColumns; +export interface RowControlProps { + 'data-test-subj'?: string; + color?: EuiButtonIconProps['color']; + disabled?: boolean; + label: string; + iconType: IconType; + onClick: ((props: RowControlRowProps) => void) | undefined; } -export type CustomControlColumnConfiguration = (props: ControlColumnsProps) => { - leadingControlColumns: EuiDataGridControlColumn[]; - trailingControlColumns?: EuiDataGridControlColumn[]; -}; +export type RowControlComponent = FC; + +export interface RowControlColumn { + id: string; + headerAriaLabel: string; + headerCellRender?: EuiDataGridControlColumn['headerCellRender']; + renderControl: (Control: RowControlComponent, props: RowControlRowProps) => ReactElement; +} diff --git a/src/plugins/discover/public/application/main/components/layout/discover_documents.test.tsx b/src/plugins/discover/public/application/main/components/layout/discover_documents.test.tsx index 85c2dd581eecb..458d3dcb6318c 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_documents.test.tsx +++ b/src/plugins/discover/public/application/main/components/layout/discover_documents.test.tsx @@ -114,15 +114,10 @@ describe('Discover documents layout', () => { }); test('should render customisations', async () => { - const customControlColumnsConfiguration = () => ({ - leadingControlColumns: [], - trailingControlColumns: [], - }); - const customization: DiscoverCustomization = { id: 'data_table', logsEnabled: true, - customControlColumnsConfiguration, + rowAdditionalLeadingControls: [], }; customisationService.set(customization); @@ -130,8 +125,8 @@ describe('Discover documents layout', () => { const discoverGridComponent = component.find(DiscoverGrid); expect(discoverGridComponent.exists()).toBeTruthy(); - expect(discoverGridComponent.prop('customControlColumnsConfiguration')).toEqual( - customControlColumnsConfiguration + expect(discoverGridComponent.prop('rowAdditionalLeadingControls')).toBe( + customization.rowAdditionalLeadingControls ); expect(discoverGridComponent.prop('externalCustomRenderers')).toBeDefined(); expect(discoverGridComponent.prop('customGridColumnsConfiguration')).toBeDefined(); diff --git a/src/plugins/discover/public/application/main/components/layout/discover_documents.tsx b/src/plugins/discover/public/application/main/components/layout/discover_documents.tsx index e6fb6472397f3..e1b5636d010b1 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_documents.tsx +++ b/src/plugins/discover/public/application/main/components/layout/discover_documents.tsx @@ -259,7 +259,7 @@ function DiscoverDocumentsComponent({ [dataView, onAddColumn, onAddFilter, onRemoveColumn, query, savedSearch.id, setExpandedDoc] ); - const { customControlColumnsConfiguration } = useDiscoverCustomization('data_table') || {}; + const { rowAdditionalLeadingControls } = useDiscoverCustomization('data_table') || {}; const { customCellRenderer, customGridColumnsConfiguration } = useContextualGridCustomisations() || {}; const additionalFieldGroups = useAdditionalFieldGroups(); @@ -435,7 +435,7 @@ function DiscoverDocumentsComponent({ componentsTourSteps={TOUR_STEPS} externalCustomRenderers={cellRenderers} customGridColumnsConfiguration={customGridColumnsConfiguration} - customControlColumnsConfiguration={customControlColumnsConfiguration} + rowAdditionalLeadingControls={rowAdditionalLeadingControls} additionalFieldGroups={additionalFieldGroups} /> diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid.tsx b/src/plugins/discover/public/components/discover_grid/discover_grid.tsx index 79ca8afd005e3..986f66e9680c4 100644 --- a/src/plugins/discover/public/components/discover_grid/discover_grid.tsx +++ b/src/plugins/discover/public/components/discover_grid/discover_grid.tsx @@ -16,21 +16,33 @@ import { useProfileAccessor } from '../../context_awareness'; /** * Customized version of the UnifiedDataTable - * @param props * @constructor */ -export const DiscoverGrid: React.FC = (props) => { +export const DiscoverGrid: React.FC = ({ + rowAdditionalLeadingControls: customRowAdditionalLeadingControls, + ...props +}) => { const getRowIndicatorProvider = useProfileAccessor('getRowIndicatorProvider'); const getRowIndicator = useMemo(() => { return getRowIndicatorProvider(() => undefined)({ dataView: props.dataView }); }, [getRowIndicatorProvider, props.dataView]); + const getRowAdditionalLeadingControlsAccessor = useProfileAccessor( + 'getRowAdditionalLeadingControls' + ); + const rowAdditionalLeadingControls = useMemo(() => { + return getRowAdditionalLeadingControlsAccessor(() => customRowAdditionalLeadingControls)({ + dataView: props.dataView, + }); + }, [getRowAdditionalLeadingControlsAccessor, props.dataView, customRowAdditionalLeadingControls]); + return ( ); diff --git a/src/plugins/discover/public/context_awareness/profile_providers/example_data_source_profile/profile.tsx b/src/plugins/discover/public/context_awareness/profile_providers/example_data_source_profile/profile.tsx index 911e69e6d0d33..747bada5b0284 100644 --- a/src/plugins/discover/public/context_awareness/profile_providers/example_data_source_profile/profile.tsx +++ b/src/plugins/discover/public/context_awareness/profile_providers/example_data_source_profile/profile.tsx @@ -8,6 +8,7 @@ import { EuiBadge } from '@elastic/eui'; import type { DataTableRecord } from '@kbn/discover-utils'; +import type { RowControlColumn } from '@kbn/unified-data-table'; import { isOfAggregateQueryType } from '@kbn/es-query'; import { getIndexPatternFromESQLQuery } from '@kbn/esql-utils'; import { euiThemeVars } from '@kbn/ui-theme'; @@ -71,6 +72,31 @@ export const exampleDataSourceProfileProvider: DataSourceProfileProvider = { }, }; }, + getRowAdditionalLeadingControls: (prev) => (params) => { + const additionalControls = prev(params) || []; + + return [ + ...additionalControls, + ...['visBarVerticalStacked', 'heart', 'inspect'].map( + (iconType, index): RowControlColumn => ({ + id: `exampleControl_${iconType}`, + headerAriaLabel: `Example Row Control ${iconType}`, + renderControl: (Control, rowProps) => { + return ( + { + alert(`Example "${iconType}" control clicked. Row index: ${rowProps.rowIndex}`); + }} + /> + ); + }, + }) + ), + ]; + }, getDefaultAppState: () => () => ({ columns: [ { diff --git a/src/plugins/discover/public/context_awareness/types.ts b/src/plugins/discover/public/context_awareness/types.ts index 38c7116a765b1..b6e4d4558162d 100644 --- a/src/plugins/discover/public/context_awareness/types.ts +++ b/src/plugins/discover/public/context_awareness/types.ts @@ -38,11 +38,20 @@ export interface DefaultAppStateExtension { rowHeight?: number; } +export interface RowControlsExtensionParams { + dataView: DataView; +} + export interface Profile { - getCellRenderers: () => CustomCellRenderer; - getDocViewer: (params: DocViewerExtensionParams) => DocViewerExtension; getDefaultAppState: (params: DefaultAppStateExtensionParams) => DefaultAppStateExtension; + // Data grid + getCellRenderers: () => CustomCellRenderer; getRowIndicatorProvider: ( params: RowIndicatorExtensionParams ) => UnifiedDataTableProps['getRowIndicator'] | undefined; + getRowAdditionalLeadingControls: ( + params: RowControlsExtensionParams + ) => UnifiedDataTableProps['rowAdditionalLeadingControls'] | undefined; + // Doc viewer + getDocViewer: (params: DocViewerExtensionParams) => DocViewerExtension; } diff --git a/src/plugins/discover/public/customizations/customization_types/data_table_customisation.ts b/src/plugins/discover/public/customizations/customization_types/data_table_customisation.ts index 3e6e510488fbd..d53485911d12c 100644 --- a/src/plugins/discover/public/customizations/customization_types/data_table_customisation.ts +++ b/src/plugins/discover/public/customizations/customization_types/data_table_customisation.ts @@ -6,10 +6,10 @@ * Side Public License, v 1. */ -import { CustomControlColumnConfiguration } from '@kbn/unified-data-table'; +import type { UnifiedDataTableProps } from '@kbn/unified-data-table'; export interface DataTableCustomization { id: 'data_table'; logsEnabled: boolean; // TODO / NOTE: Just temporary until Discover's data type contextual awareness lands. - customControlColumnsConfiguration?: CustomControlColumnConfiguration; + rowAdditionalLeadingControls?: UnifiedDataTableProps['rowAdditionalLeadingControls']; } diff --git a/src/plugins/discover/public/embeddable/components/saved_search_grid.tsx b/src/plugins/discover/public/embeddable/components/saved_search_grid.tsx index 2c67595b846b6..77d6f44de126b 100644 --- a/src/plugins/discover/public/embeddable/components/saved_search_grid.tsx +++ b/src/plugins/discover/public/embeddable/components/saved_search_grid.tsx @@ -9,7 +9,7 @@ import React, { useCallback, useMemo, useState } from 'react'; import type { DataTableRecord } from '@kbn/discover-utils/types'; import { AggregateQuery, Query } from '@kbn/es-query'; import type { SearchResponseWarning } from '@kbn/search-response-warnings'; -import { MAX_DOC_FIELDS_DISPLAYED, ROW_HEIGHT_OPTION, SHOW_MULTIFIELDS } from '@kbn/discover-utils'; +import { MAX_DOC_FIELDS_DISPLAYED, SHOW_MULTIFIELDS } from '@kbn/discover-utils'; import { type UnifiedDataTableProps, type DataTableColumnsMeta, @@ -108,7 +108,6 @@ export function DiscoverGridEmbeddable(props: DiscoverGridEmbeddableProps) { totalHits={props.totalHitCount} setExpandedDoc={setExpandedDoc} expandedDoc={expandedDoc} - configRowHeight={props.services.uiSettings.get(ROW_HEIGHT_OPTION)} showMultiFields={props.services.uiSettings.get(SHOW_MULTIFIELDS)} maxDocFieldsDisplayed={props.services.uiSettings.get(MAX_DOC_FIELDS_DISPLAYED)} renderDocumentView={renderDocumentView} @@ -116,7 +115,6 @@ export function DiscoverGridEmbeddable(props: DiscoverGridEmbeddableProps) { externalCustomRenderers={cellRenderers} enableComparisonMode showColumnTokens - configHeaderRowHeight={3} showFullScreenButton={false} className="unifiedDataTable" /> diff --git a/src/plugins/discover/public/embeddable/components/search_embeddable_grid_component.tsx b/src/plugins/discover/public/embeddable/components/search_embeddable_grid_component.tsx index fe511f5887dd5..55ed59d30f1ae 100644 --- a/src/plugins/discover/public/embeddable/components/search_embeddable_grid_component.tsx +++ b/src/plugins/discover/public/embeddable/components/search_embeddable_grid_component.tsx @@ -12,8 +12,8 @@ import { BehaviorSubject } from 'rxjs'; import type { DataView } from '@kbn/data-views-plugin/common'; import { DOC_HIDE_TIME_COLUMN_SETTING, - isLegacyTableEnabled, SEARCH_FIELDS_FROM_SOURCE, + isLegacyTableEnabled, } from '@kbn/discover-utils'; import { Filter } from '@kbn/es-query'; import { @@ -33,6 +33,7 @@ import { SEARCH_EMBEDDABLE_CELL_ACTIONS_TRIGGER_ID } from '../constants'; import { isEsqlMode } from '../initialize_fetch'; import type { SearchEmbeddableApi, SearchEmbeddableStateManager } from '../types'; import { DiscoverGridEmbeddable } from './saved_search_grid'; +import { getSearchEmbeddableDefaults } from '../get_search_embeddable_defaults'; interface SavedSearchEmbeddableComponentProps { api: SearchEmbeddableApi & { fetchWarnings$: BehaviorSubject }; @@ -144,13 +145,15 @@ export function SearchEmbeddableGridComponent({ return getAllowedSampleSize(savedSearch.sampleSize, discoverServices.uiSettings); }, [savedSearch.sampleSize, discoverServices]); + const defaults = getSearchEmbeddableDefaults(discoverServices.uiSettings); + const sharedProps = { columns: savedSearch.columns ?? [], dataView, interceptedWarnings, onFilter: onAddFilter, rows, - rowsPerPageState: savedSearch.rowsPerPage, + rowsPerPageState: savedSearch.rowsPerPage ?? defaults.rowsPerPage, sampleSizeState: fetchedSampleSize, searchDescription: panelDescription || savedSearchDescription, sort, @@ -179,12 +182,14 @@ export function SearchEmbeddableGridComponent({ ariaLabelledBy={'documentsAriaLabel'} cellActionsTriggerId={SEARCH_EMBEDDABLE_CELL_ACTIONS_TRIGGER_ID} columnsMeta={columnsMeta} + configHeaderRowHeight={defaults.headerRowHeight} + configRowHeight={defaults.rowHeight} headerRowHeightState={savedSearch.headerRowHeight} + rowHeightState={savedSearch.rowHeight} isPlainRecord={isEsql} loadingState={Boolean(loading) ? DataLoadingState.loading : DataLoadingState.loaded} maxAllowedSampleSize={getMaxAllowedSampleSize(discoverServices.uiSettings)} query={savedSearch.searchSource.getField('query')} - rowHeightState={savedSearch.rowHeight} savedSearchId={savedSearchId} searchTitle={panelTitle || savedSearchTitle} services={discoverServices} diff --git a/src/plugins/discover/public/embeddable/get_search_embeddable_defaults.ts b/src/plugins/discover/public/embeddable/get_search_embeddable_defaults.ts new file mode 100644 index 0000000000000..e820f2ec38e21 --- /dev/null +++ b/src/plugins/discover/public/embeddable/get_search_embeddable_defaults.ts @@ -0,0 +1,30 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { IUiSettingsClient } from '@kbn/core/public'; +import { ROW_HEIGHT_OPTION, SAMPLE_SIZE_SETTING } from '@kbn/discover-utils'; +import { getDefaultRowsPerPage } from '../../common/constants'; +import { DEFAULT_HEADER_ROW_HEIGHT_LINES } from './constants'; + +export interface SearchEmbeddableDefaults { + rowHeight: number | undefined; + headerRowHeight: number | undefined; + rowsPerPage: number | undefined; + sampleSize: number | undefined; +} + +export const getSearchEmbeddableDefaults = ( + uiSettings: IUiSettingsClient +): SearchEmbeddableDefaults => { + return { + rowHeight: uiSettings.get(ROW_HEIGHT_OPTION), + headerRowHeight: DEFAULT_HEADER_ROW_HEIGHT_LINES, + rowsPerPage: getDefaultRowsPerPage(uiSettings), + sampleSize: uiSettings.get(SAMPLE_SIZE_SETTING), + }; +}; diff --git a/src/plugins/discover/public/embeddable/initialize_search_embeddable_api.tsx b/src/plugins/discover/public/embeddable/initialize_search_embeddable_api.tsx index 02f64d34fac2c..cc6711e3f5015 100644 --- a/src/plugins/discover/public/embeddable/initialize_search_embeddable_api.tsx +++ b/src/plugins/discover/public/embeddable/initialize_search_embeddable_api.tsx @@ -12,7 +12,6 @@ import { BehaviorSubject, combineLatest, map, Observable, skip } from 'rxjs'; import { ISearchSource, SerializedSearchSourceFields } from '@kbn/data-plugin/common'; import { DataView } from '@kbn/data-views-plugin/common'; -import { ROW_HEIGHT_OPTION, SAMPLE_SIZE_SETTING } from '@kbn/discover-utils'; import { DataTableRecord } from '@kbn/discover-utils/types'; import type { PublishesDataViews, @@ -24,9 +23,9 @@ import { SortOrder, VIEW_MODE } from '@kbn/saved-search-plugin/public'; import { DataTableColumnsMeta } from '@kbn/unified-data-table'; import { AggregateQuery, Filter, Query } from '@kbn/es-query'; -import { getDefaultRowsPerPage } from '../../common/constants'; import { DiscoverServices } from '../build_services'; -import { DEFAULT_HEADER_ROW_HEIGHT_LINES, EDITABLE_SAVED_SEARCH_KEYS } from './constants'; +import { EDITABLE_SAVED_SEARCH_KEYS } from './constants'; +import { getSearchEmbeddableDefaults } from './get_search_embeddable_defaults'; import { PublishesSavedSearch, SearchEmbeddableRuntimeState, @@ -85,14 +84,16 @@ export const initializeSearchEmbeddableApi = async ( const searchSource$ = new BehaviorSubject(searchSource); const dataViews = new BehaviorSubject(dataView ? [dataView] : undefined); + const defaults = getSearchEmbeddableDefaults(discoverServices.uiSettings); + /** This is the state that can be initialized from the saved initial state */ const columns$ = new BehaviorSubject(initialState.columns); const grid$ = new BehaviorSubject(initialState.grid); + const headerRowHeight$ = new BehaviorSubject(initialState.headerRowHeight); const rowHeight$ = new BehaviorSubject(initialState.rowHeight); const rowsPerPage$ = new BehaviorSubject(initialState.rowsPerPage); - const headerRowHeight$ = new BehaviorSubject(initialState.headerRowHeight); - const sort$ = new BehaviorSubject(initialState.sort); const sampleSize$ = new BehaviorSubject(initialState.sampleSize); + const sort$ = new BehaviorSubject(initialState.sort); const savedSearchViewMode$ = new BehaviorSubject(initialState.viewMode); /** @@ -112,10 +113,6 @@ export const initializeSearchEmbeddableApi = async ( const columnsMeta$ = new BehaviorSubject(undefined); const totalHitCount$ = new BehaviorSubject(undefined); - const defaultRowHeight = discoverServices.uiSettings.get(ROW_HEIGHT_OPTION); - const defaultRowsPerPage = getDefaultRowsPerPage(discoverServices.uiSettings); - const defaultSampleSize = discoverServices.uiSettings.get(SAMPLE_SIZE_SETTING); - /** * The state manager is used to modify the state of the saved search - this should never be * treated as the source of truth @@ -175,22 +172,22 @@ export const initializeSearchEmbeddableApi = async ( sampleSize: [ sampleSize$, (value) => sampleSize$.next(value), - (a, b) => (a ?? defaultSampleSize) === (b ?? defaultSampleSize), + (a, b) => (a ?? defaults.sampleSize) === (b ?? defaults.sampleSize), ], rowsPerPage: [ rowsPerPage$, (value) => rowsPerPage$.next(value), - (a, b) => (a ?? defaultRowsPerPage) === (b ?? defaultRowsPerPage), + (a, b) => (a ?? defaults.rowsPerPage) === (b ?? defaults.rowsPerPage), ], rowHeight: [ rowHeight$, (value) => rowHeight$.next(value), - (a, b) => (a ?? defaultRowHeight) === (b ?? defaultRowHeight), + (a, b) => (a ?? defaults.rowHeight) === (b ?? defaults.rowHeight), ], headerRowHeight: [ headerRowHeight$, (value) => headerRowHeight$.next(value), - (a, b) => (a ?? DEFAULT_HEADER_ROW_HEIGHT_LINES) === (b ?? DEFAULT_HEADER_ROW_HEIGHT_LINES), + (a, b) => (a ?? defaults.headerRowHeight) === (b ?? defaults.headerRowHeight), ], /** The following can't currently be changed from the dashboard */ diff --git a/test/api_integration/apis/console/index.ts b/test/api_integration/apis/console/index.ts index f39cf6cabb129..4e4d97b8204c8 100644 --- a/test/api_integration/apis/console/index.ts +++ b/test/api_integration/apis/console/index.ts @@ -13,6 +13,5 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./proxy_route')); loadTestFile(require.resolve('./autocomplete_entities')); loadTestFile(require.resolve('./es_config')); - loadTestFile(require.resolve('./spec_definitions')); }); } diff --git a/test/api_integration/apis/console/spec_definitions.ts b/test/api_integration/apis/console/spec_definitions.ts deleted file mode 100644 index f8e56354f6319..0000000000000 --- a/test/api_integration/apis/console/spec_definitions.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../ftr_provider_context'; - -export default function ({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - - describe('GET /api/console/api_server', () => { - it('returns autocomplete definitions', async () => { - const { body } = await supertest - .get('/api/console/api_server') - .set('kbn-xsrf', 'true') - .expect(200); - expect(body.es).to.be.ok(); - const { - es: { name, globals, endpoints }, - } = body; - expect(name).to.be.ok(); - expect(Object.keys(globals).length).to.be.above(0); - expect(Object.keys(endpoints).length).to.be.above(0); - }); - }); -} diff --git a/test/api_integration/apis/core/compression.ts b/test/api_integration/apis/core/compression.ts deleted file mode 100644 index c4b119692f4bb..0000000000000 --- a/test/api_integration/apis/core/compression.ts +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../ftr_provider_context'; - -export default function ({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - - const compressionSuite = (url: string) => { - it(`uses compression when there isn't a referer`, async () => { - await supertest - .get(url) - .set('accept-encoding', 'gzip') - .then((response) => { - expect(response.header).to.have.property('content-encoding', 'gzip'); - }); - }); - - it(`uses compression when there is a whitelisted referer`, async () => { - await supertest - .get(url) - .set('accept-encoding', 'gzip') - .set('referer', 'https://some-host.com') - .then((response) => { - expect(response.header).to.have.property('content-encoding', 'gzip'); - }); - }); - - it(`doesn't use compression when there is a non-whitelisted referer`, async () => { - await supertest - .get(url) - .set('accept-encoding', 'gzip') - .set('referer', 'https://other.some-host.com') - .then((response) => { - expect(response.header).not.to.have.property('content-encoding'); - }); - }); - - it(`supports brotli compression`, async () => { - await supertest - .get(url) - .set('accept-encoding', 'br') - .then((response) => { - expect(response.header).to.have.property('content-encoding', 'br'); - }); - }); - }; - - describe('compression', () => { - describe('against an application page', () => { - compressionSuite('/app/kibana'); - }); - }); -} diff --git a/test/api_integration/apis/core/index.ts b/test/api_integration/apis/core/index.ts index 65c8a5d5cb5c5..98010e01c8215 100644 --- a/test/api_integration/apis/core/index.ts +++ b/test/api_integration/apis/core/index.ts @@ -10,7 +10,6 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('core', () => { - loadTestFile(require.resolve('./compression')); loadTestFile(require.resolve('./translations')); loadTestFile(require.resolve('./capabilities')); }); diff --git a/test/common/services/index.ts b/test/common/services/index.ts index ad08829afd047..ccc786d4ccc6e 100644 --- a/test/common/services/index.ts +++ b/test/common/services/index.ts @@ -16,8 +16,15 @@ import { IndexPatternsService } from './index_patterns'; import { BsearchService } from './bsearch'; import { ConsoleProvider } from './console'; +// pick only services that work for any FTR config, e.g. 'samlAuth' requires SAML setup in config file +const { es, esArchiver, kibanaServer, retry, supertestWithoutAuth } = commonFunctionalServices; + export const services = { - ...commonFunctionalServices, + es, + esArchiver, + kibanaServer, + retry, + supertestWithoutAuth, deployment: DeploymentService, randomness: RandomnessService, security: SecurityServiceProvider, diff --git a/test/functional/apps/discover/context_awareness/extensions/_get_row_additional_leading_controls.ts b/test/functional/apps/discover/context_awareness/extensions/_get_row_additional_leading_controls.ts new file mode 100644 index 0000000000000..be536fd6cdbe9 --- /dev/null +++ b/test/functional/apps/discover/context_awareness/extensions/_get_row_additional_leading_controls.ts @@ -0,0 +1,64 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import kbnRison from '@kbn/rison'; +import type { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const PageObjects = getPageObjects(['common', 'discover']); + const testSubjects = getService('testSubjects'); + const dataViews = getService('dataViews'); + + describe('extension getRowAdditionalLeadingControls', () => { + describe('ES|QL mode', () => { + it('should render logs controls for logs data source', async () => { + const state = kbnRison.encode({ + dataSource: { type: 'esql' }, + query: { esql: 'from my-example-logs | sort @timestamp desc' }, + }); + await PageObjects.common.navigateToApp('discover', { + hash: `/?_a=${state}`, + }); + await PageObjects.discover.waitUntilSearchingHasFinished(); + await testSubjects.existOrFail('exampleLogsControl_visBarVerticalStacked'); + await testSubjects.existOrFail('unifiedDataTable_additionalRowControl_menuControl'); + }); + + it('should not render logs controls for non-logs data source', async () => { + const state = kbnRison.encode({ + dataSource: { type: 'esql' }, + query: { esql: 'from my-example-metrics | sort @timestamp desc' }, + }); + await PageObjects.common.navigateToApp('discover', { + hash: `/?_a=${state}`, + }); + await PageObjects.discover.waitUntilSearchingHasFinished(); + await testSubjects.missingOrFail('exampleLogsControl_visBarVerticalStacked'); + await testSubjects.missingOrFail('unifiedDataTable_additionalRowControl_menuControl'); + }); + }); + + describe('data view mode', () => { + it('should render logs controls for logs data source', async () => { + await PageObjects.common.navigateToApp('discover'); + await dataViews.switchTo('my-example-logs'); + await PageObjects.discover.waitUntilSearchingHasFinished(); + await testSubjects.existOrFail('exampleLogsControl_visBarVerticalStacked'); + await testSubjects.existOrFail('unifiedDataTable_additionalRowControl_menuControl'); + }); + + it('should not render logs controls for non-logs data source', async () => { + await PageObjects.common.navigateToApp('discover'); + await dataViews.switchTo('my-example-metrics'); + await PageObjects.discover.waitUntilSearchingHasFinished(); + await testSubjects.missingOrFail('exampleLogsControl_visBarVerticalStacked'); + await testSubjects.missingOrFail('unifiedDataTable_additionalRowControl_menuControl'); + }); + }); + }); +} diff --git a/test/functional/apps/discover/context_awareness/index.ts b/test/functional/apps/discover/context_awareness/index.ts index 82f03e7f54bbc..0bba18a339263 100644 --- a/test/functional/apps/discover/context_awareness/index.ts +++ b/test/functional/apps/discover/context_awareness/index.ts @@ -36,6 +36,7 @@ export default function ({ getService, getPageObjects, loadTestFile }: FtrProvid loadTestFile(require.resolve('./_root_profile')); loadTestFile(require.resolve('./_data_source_profile')); loadTestFile(require.resolve('./extensions/_get_row_indicator_provider')); + loadTestFile(require.resolve('./extensions/_get_row_additional_leading_controls')); loadTestFile(require.resolve('./extensions/_get_doc_viewer')); loadTestFile(require.resolve('./extensions/_get_cell_renderers')); loadTestFile(require.resolve('./extensions/_get_default_app_state')); diff --git a/test/functional/apps/discover/esql/_esql_view.ts b/test/functional/apps/discover/esql/_esql_view.ts index a66471b921528..0ab0e30a45eab 100644 --- a/test/functional/apps/discover/esql/_esql_view.ts +++ b/test/functional/apps/discover/esql/_esql_view.ts @@ -194,8 +194,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const cell = await dataGrid.getCellElementExcludingControlColumns(0, 1); expect(await cell.getVisibleText()).to.be(' - '); expect(await dataGrid.getHeaders()).to.eql([ - 'Control column', 'Select column', + 'Control column', 'Numberbytes', 'machine.ram_range', ]); diff --git a/test/functional/apps/discover/group2_data_grid3/_data_grid_pagination.ts b/test/functional/apps/discover/group2_data_grid3/_data_grid_pagination.ts index 193d3f4607987..55747eec93119 100644 --- a/test/functional/apps/discover/group2_data_grid3/_data_grid_pagination.ts +++ b/test/functional/apps/discover/group2_data_grid3/_data_grid_pagination.ts @@ -30,6 +30,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const retry = getService('retry'); const security = getService('security'); const dashboardAddPanel = getService('dashboardAddPanel'); + const dashboardPanelActions = getService('dashboardPanelActions'); describe('discover data grid pagination', function describeIndexTests() { before(async () => { @@ -126,6 +127,23 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.waitUntilSearchingHasFinished(); expect((await dataGrid.getDocTableRows()).length).to.be(10); // as in the saved search await dataGrid.checkCurrentRowsPerPageToBe(10); + + // should use "rowsPerPage" form the saved search on dashboard + await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.clickNewDashboard(); + await PageObjects.timePicker.setDefaultAbsoluteRange(); + await dashboardAddPanel.clickOpenAddPanel(); + await dashboardAddPanel.addSavedSearch(savedSearchTitle); + await PageObjects.header.waitUntilLoadingHasFinished(); + expect((await dataGrid.getDocTableRows()).length).to.be(10); // as in the saved search + await dataGrid.checkCurrentRowsPerPageToBe(10); + + // should use "rowsPerPage" form settings by default on dashboard + await dashboardPanelActions.removePanelByTitle(savedSearchTitle); + await dashboardAddPanel.addSavedSearch('A Saved Search'); + await PageObjects.header.waitUntilLoadingHasFinished(); + expect((await dataGrid.getDocTableRows()).length).to.be(6); // as in settings + await dataGrid.checkCurrentRowsPerPageToBe(6); }); it('should not split ES|QL results into pages', async () => { diff --git a/test/functional/screenshots/baseline/dashboard_embed_mode_scrolling.png b/test/functional/screenshots/baseline/dashboard_embed_mode_scrolling.png index 3fd3025ebb9a1..94de1a1c3cc4f 100644 Binary files a/test/functional/screenshots/baseline/dashboard_embed_mode_scrolling.png and b/test/functional/screenshots/baseline/dashboard_embed_mode_scrolling.png differ diff --git a/test/functional/services/data_grid.ts b/test/functional/services/data_grid.ts index 5014846a67407..70a67d33ffd00 100644 --- a/test/functional/services/data_grid.ts +++ b/test/functional/services/data_grid.ts @@ -139,6 +139,22 @@ export class DataGridService extends FtrService { 'euiDataGridCellExpandButton' ); await actionButton.click(); + await this.retry.waitFor('popover to be opened', async () => { + return await this.testSubjects.exists('euiDataGridExpansionPopover'); + }); + } + + /** + * Clicks grid cell 'expand' action button + * @param rowIndex data row index starting from 0 (0 means 1st row) + * @param columnIndex column index starting from 0 (0 means 1st column) + */ + public async clickCellExpandButtonExcludingControlColumns( + rowIndex: number = 0, + columnIndex: number = 0 + ) { + const controlsCount = await this.getControlColumnsCount(); + await this.clickCellExpandButton(rowIndex, controlsCount + columnIndex); } /** diff --git a/x-pack/plugins/cloud_security_posture/common/constants.ts b/x-pack/plugins/cloud_security_posture/common/constants.ts index 3c08632a262e5..053dcc4867bdd 100644 --- a/x-pack/plugins/cloud_security_posture/common/constants.ts +++ b/x-pack/plugins/cloud_security_posture/common/constants.ts @@ -85,8 +85,6 @@ export const LATEST_VULNERABILITIES_INDEX_DEFAULT_NS = 'logs-cloud_security_posture.vulnerabilities_latest-default'; export const LATEST_VULNERABILITIES_RETENTION_POLICY = '3d'; -export const DATA_VIEW_INDEX_PATTERN = 'logs-*'; - export const SECURITY_DEFAULT_DATA_VIEW_ID = 'security-solution-default'; export const ALERTS_INDEX_PATTERN = '.alerts-security.alerts-*'; @@ -157,9 +155,6 @@ export const POSTURE_TYPES: { [x: string]: PostureTypes } = { [POSTURE_TYPE_ALL]: POSTURE_TYPE_ALL, }; -export const VULNERABILITIES = 'vulnerabilities'; -export const CONFIGURATIONS = 'configurations'; - export const VULNERABILITIES_SEVERITY: Record = { LOW: 'LOW', MEDIUM: 'MEDIUM', @@ -168,8 +163,6 @@ export const VULNERABILITIES_SEVERITY: Record = { UNKNOWN: 'UNKNOWN', }; -export const VULNERABILITIES_ENUMERATION = 'CVE'; - export const AWS_CREDENTIALS_TYPE_TO_FIELDS_MAP: AwsCredentialsTypeFieldMap = { assume_role: ['role_arn'], direct_access_keys: ['access_key_id', 'secret_access_key'], @@ -209,7 +202,6 @@ export const AZURE_CREDENTIALS_TYPE_TO_FIELDS_MAP = { manual: [], }; -export const CLOUD_FORMATION_STACK_NAME = 'Elastic-Cloud-Security-Posture-Management'; export const TEMPLATE_URL_ACCOUNT_TYPE_ENV_VAR = 'ACCOUNT_TYPE'; export const ORGANIZATION_ACCOUNT = 'organization-account'; diff --git a/x-pack/plugins/cloud_security_posture/common/runtime_mappings/get_safe_kspm_cluster_id_runtime_mapping.ts b/x-pack/plugins/cloud_security_posture/common/runtime_mappings/get_safe_kspm_cluster_id_runtime_mapping.ts deleted file mode 100644 index eed3eba2f7220..0000000000000 --- a/x-pack/plugins/cloud_security_posture/common/runtime_mappings/get_safe_kspm_cluster_id_runtime_mapping.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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 { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/types'; - -/** - * Creates the `safe_posture_type` runtime field with the value of either - * `kspm` or `cspm` based on the value of `rule.benchmark.posture_type` - */ -export const getSafeKspmClusterIdRuntimeMapping = (): MappingRuntimeFields => ({ - safe_kspm_cluster_id: { - type: 'keyword', - script: { - source: ` - def orchestratorIdAvailable = doc.containsKey("orchestrator.cluster.id") && - !doc["orchestrator.cluster.id"].empty; - def clusterIdAvailable = doc.containsKey("cluster_id") && - !doc["cluster_id"].empty; - - if (orchestratorIdAvailable) { - emit(doc["orchestrator.cluster.id"].value); - } else if (clusterIdAvailable) { - emit(doc["cluster_id"].value); - } - `, - }, - }, -}); diff --git a/x-pack/plugins/cloud_security_posture/common/types/rules/v3.ts b/x-pack/plugins/cloud_security_posture/common/types/rules/v3.ts index 85c38c50022b8..a00bf1a8077e6 100644 --- a/x-pack/plugins/cloud_security_posture/common/types/rules/v3.ts +++ b/x-pack/plugins/cloud_security_posture/common/types/rules/v3.ts @@ -18,8 +18,6 @@ export type CspBenchmarkRuleMetadata = TypeOf; -export type PageUrlParams = Record<'policyId' | 'packagePolicyId', string>; - export const cspBenchmarkRuleMetadataSchema = schema.object({ audit: schema.string(), benchmark: schema.object({ diff --git a/x-pack/plugins/cloud_security_posture/common/types_old.ts b/x-pack/plugins/cloud_security_posture/common/types_old.ts index 6b290979a97f5..f77ac4678a526 100644 --- a/x-pack/plugins/cloud_security_posture/common/types_old.ts +++ b/x-pack/plugins/cloud_security_posture/common/types_old.ts @@ -36,10 +36,6 @@ export type AzureCredentialsType = | 'service_principal_with_client_username_and_password' | 'managed_identity'; -export type AzureCredentialsTypeFieldMap = { - [key in AzureCredentialsType]: string[]; -}; - export type Evaluation = 'passed' | 'failed' | 'NA'; export type PostureTypes = 'cspm' | 'kspm' | 'vuln_mgmt' | 'all'; diff --git a/x-pack/plugins/cloud_security_posture/public/common/component/multi_select_filter.tsx b/x-pack/plugins/cloud_security_posture/public/common/component/multi_select_filter.tsx index 6eece8a31c5e8..ecc5edcbf2e73 100644 --- a/x-pack/plugins/cloud_security_posture/public/common/component/multi_select_filter.tsx +++ b/x-pack/plugins/cloud_security_posture/public/common/component/multi_select_filter.tsx @@ -27,17 +27,6 @@ type FilterOption = EuiSelectableOp label: T; }>; -export type { FilterOption as MultiSelectFilterOption }; - -export const mapToMultiSelectOption = (options: T[]) => { - return options.map((option) => { - return { - key: option, - label: option, - }; - }); -}; - const fromRawOptionsToEuiSelectableOptions = ( options: Array>, selectedOptionKeys: string[] diff --git a/x-pack/plugins/cloud_security_posture/public/common/constants.ts b/x-pack/plugins/cloud_security_posture/public/common/constants.ts index 73934752e819a..8054917ba7462 100644 --- a/x-pack/plugins/cloud_security_posture/public/common/constants.ts +++ b/x-pack/plugins/cloud_security_posture/public/common/constants.ts @@ -40,11 +40,8 @@ export const DEFAULT_VISIBLE_ROWS_PER_PAGE = 25; export const LOCAL_STORAGE_DATA_TABLE_PAGE_SIZE_KEY = 'cloudPosture:dataTable:pageSize'; export const LOCAL_STORAGE_DATA_TABLE_COLUMNS_KEY = 'cloudPosture:dataTable:columns'; -export const LOCAL_STORAGE_PAGE_SIZE_FINDINGS_KEY = 'cloudPosture:findings:pageSize'; export const LOCAL_STORAGE_PAGE_SIZE_BENCHMARK_KEY = 'cloudPosture:benchmark:pageSize'; export const LOCAL_STORAGE_PAGE_SIZE_RULES_KEY = 'cloudPosture:rules:pageSize'; -export const LOCAL_STORAGE_DASHBOARD_CLUSTER_SORT_KEY = - 'cloudPosture:complianceDashboard:clusterSort'; export const LOCAL_STORAGE_DASHBOARD_BENCHMARK_SORT_KEY = 'cloudPosture:complianceDashboard:benchmarkSort'; export const LOCAL_STORAGE_FINDINGS_LAST_SELECTED_TAB_KEY = 'cloudPosture:findings:lastSelectedTab'; diff --git a/x-pack/plugins/cloud_security_posture/public/common/hooks/use_cloud_posture_data_table/utils.ts b/x-pack/plugins/cloud_security_posture/public/common/hooks/use_cloud_posture_data_table/utils.ts index 6628cc7711a82..98270ffee59bf 100644 --- a/x-pack/plugins/cloud_security_posture/public/common/hooks/use_cloud_posture_data_table/utils.ts +++ b/x-pack/plugins/cloud_security_posture/public/common/hooks/use_cloud_posture_data_table/utils.ts @@ -5,28 +5,6 @@ * 2.0. */ -import type { EuiBasicTableProps, Pagination } from '@elastic/eui'; - -type TablePagination = NonNullable['pagination']>; - -export const getPaginationTableParams = ( - params: TablePagination & Pick, 'pageIndex' | 'pageSize'>, - pageSizeOptions = [10, 25, 100], - showPerPageOptions = true -): Required => ({ - ...params, - pageSizeOptions, - showPerPageOptions, -}); - -export const getPaginationQuery = ({ - pageIndex, - pageSize, -}: Required>) => ({ - from: pageIndex * pageSize, - size: pageSize, -}); - export const getDefaultQuery = ({ query, filters }: any): any => ({ query, filters, diff --git a/x-pack/plugins/cloud_security_posture/public/common/hooks/use_page_slice.ts b/x-pack/plugins/cloud_security_posture/public/common/hooks/use_page_slice.ts deleted file mode 100644 index e089724b25909..0000000000000 --- a/x-pack/plugins/cloud_security_posture/public/common/hooks/use_page_slice.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * 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 { useMemo } from 'react'; - -/** - * @description given an array index and page size, returns a slice of said array. - */ -export const usePageSlice = (data: any[] | undefined, pageIndex: number, pageSize: number) => { - return useMemo(() => { - if (!data) { - return []; - } - - const cursor = pageIndex * pageSize; - return data.slice(cursor, cursor + pageSize); - }, [data, pageIndex, pageSize]); -}; diff --git a/x-pack/plugins/cloud_security_posture/public/common/types.ts b/x-pack/plugins/cloud_security_posture/public/common/types.ts index f2881c1798883..d0d491c256e0e 100644 --- a/x-pack/plugins/cloud_security_posture/public/common/types.ts +++ b/x-pack/plugins/cloud_security_posture/public/common/types.ts @@ -6,9 +6,6 @@ */ import type { Criteria } from '@elastic/eui'; import type { BoolQuery, Filter, Query, EsQueryConfig } from '@kbn/es-query'; -import { CspFinding } from '../../common/schemas/csp_finding'; - -export type FindingsGroupByKind = 'default' | 'resource'; export interface FindingsBaseURLQuery { query: Query; @@ -33,11 +30,6 @@ export interface FindingsBaseEsQuery { }; } -export interface CspFindingsQueryData { - page: CspFinding[]; - total: number; -} - export type Sort = NonNullable['sort']>; interface RuleSeverityMapping { diff --git a/x-pack/plugins/cloud_security_posture/public/common/utils/get_enabled_csp_integration_details.ts b/x-pack/plugins/cloud_security_posture/public/common/utils/get_enabled_csp_integration_details.ts deleted file mode 100644 index cd0a413648bed..0000000000000 --- a/x-pack/plugins/cloud_security_posture/public/common/utils/get_enabled_csp_integration_details.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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 { PackagePolicy } from '@kbn/fleet-plugin/common'; -import type { PostureInput } from '../../../common/types_old'; -import { SUPPORTED_CLOUDBEAT_INPUTS } from '../../../common/constants'; -import { cloudPostureIntegrations, type CloudPostureIntegrations } from '../constants'; - -const isPolicyTemplate = (name: unknown): name is keyof CloudPostureIntegrations => - typeof name === 'string' && name in cloudPostureIntegrations; - -export const getEnabledCspIntegrationDetails = (packageInfo?: PackagePolicy) => { - const enabledInput = packageInfo?.inputs.find((input) => input.enabled); - - // Check for valid and support input - if ( - !enabledInput || - !isPolicyTemplate(enabledInput.policy_template) || - !SUPPORTED_CLOUDBEAT_INPUTS.includes(enabledInput.type as PostureInput) - ) - return null; - - const integration = cloudPostureIntegrations[enabledInput.policy_template]; - const enabledIntegrationOption = integration.options.find( - (option) => option.type === enabledInput.type - ); - - return { integration, enabledIntegrationOption }; -}; diff --git a/x-pack/plugins/cloud_security_posture/public/common/utils/get_limit_properties.test.ts b/x-pack/plugins/cloud_security_posture/public/common/utils/get_limit_properties.test.ts deleted file mode 100644 index f62062ba37f4d..0000000000000 --- a/x-pack/plugins/cloud_security_posture/public/common/utils/get_limit_properties.test.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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 { getLimitProperties } from './get_limit_properties'; - -describe('getLimitProperties', () => { - it('less items than limit', () => { - const { limitedTotalItemCount, isLastLimitedPage } = getLimitProperties(200, 500, 100, 1); - - expect(limitedTotalItemCount).toBe(200); - expect(isLastLimitedPage).toBe(false); - }); - - it('more items than limit', () => { - const { limitedTotalItemCount, isLastLimitedPage } = getLimitProperties(600, 500, 100, 4); - - expect(limitedTotalItemCount).toBe(500); - expect(isLastLimitedPage).toBe(true); - }); - - it('per page calculations are correct', () => { - const { limitedTotalItemCount, isLastLimitedPage } = getLimitProperties(600, 500, 25, 19); - - expect(limitedTotalItemCount).toBe(500); - expect(isLastLimitedPage).toBe(true); - }); -}); diff --git a/x-pack/plugins/cloud_security_posture/public/common/utils/get_limit_properties.ts b/x-pack/plugins/cloud_security_posture/public/common/utils/get_limit_properties.ts deleted file mode 100644 index f09990bc3f854..0000000000000 --- a/x-pack/plugins/cloud_security_posture/public/common/utils/get_limit_properties.ts +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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 { useMemo } from 'react'; -import { MAX_FINDINGS_TO_LOAD } from '../constants'; - -export const getLimitProperties = ( - totalItems: number, - maxItems: number, - pageSize: number, - pageIndex: number -): { isLastLimitedPage: boolean; limitedTotalItemCount: number } => { - const limitItems = totalItems > maxItems; - const limitedTotalItemCount = limitItems ? maxItems : totalItems; - const lastLimitedPage = Math.ceil(limitedTotalItemCount / pageSize); - const isLastPage = lastLimitedPage === pageIndex + 1; - const isLastLimitedPage = limitItems && isLastPage; - - return { isLastLimitedPage, limitedTotalItemCount }; -}; - -export const useLimitProperties = ({ - total, - pageIndex, - pageSize, -}: { - total?: number; - pageSize: number; - pageIndex: number; -}) => - useMemo( - () => getLimitProperties(total || 0, MAX_FINDINGS_TO_LOAD, pageSize, pageIndex), - [total, pageIndex, pageSize] - ); diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/aws_credentials_form/aws_credentials_form_agentless.tsx b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/aws_credentials_form/aws_credentials_form_agentless.tsx index 867fc1af4d9ea..8a5587cf16622 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/aws_credentials_form/aws_credentials_form_agentless.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/aws_credentials_form/aws_credentials_form_agentless.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { EuiButton, EuiCallOut, EuiLink, EuiSpacer, EuiText } from '@elastic/eui'; +import { EuiButton, EuiCallOut, EuiLink, EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import semverCompare from 'semver/functions/compare'; import semverValid from 'semver/functions/valid'; @@ -36,106 +36,6 @@ import { AwsCredentialTypeSelector, } from './aws_credentials_form'; -const CLOUD_FORMATION_EXTERNAL_DOC_URL = - 'https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-whatis-howdoesitwork.html'; - -export const CloudFormationCloudCredentialsGuide = ({ - isOrganization, -}: { - isOrganization?: boolean; -}) => { - return ( - -

- - - - ), - }} - /> -

- -
    - {isOrganization ? ( -
  1. - -
  2. - ) : ( -
  3. - -
  4. - )} -
  5. - -
  6. -
  7. - -
  8. -
  9. - - - - ), - }} - /> -
  10. -
  11. - -
  12. -
  13. - -
  14. -
  15. - -
  16. -
-
-
- ); -}; - export const AwsCredentialsFormAgentless = ({ input, newPolicy, diff --git a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/test_subjects.ts b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/test_subjects.ts index 2a325ee225128..6c1a73ed735aa 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/test_subjects.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/test_subjects.ts @@ -5,7 +5,6 @@ * 2.0. */ -export const MISSING_FINDINGS_NO_DATA_CONFIG = 'missing-findings-no-data-config'; export const DASHBOARD_CONTAINER = 'dashboard-container'; export const DASHBOARD_SUMMARY_CONTAINER = 'dashboard-summary-section'; export const KUBERNETES_DASHBOARD_CONTAINER = 'kubernetes-dashboard-container'; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/latest_findings_group_renderer.tsx b/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/latest_findings_group_renderer.tsx index ddb3c757b420e..535a465917d44 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/latest_findings_group_renderer.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/latest_findings_group_renderer.tsx @@ -185,7 +185,7 @@ const FindingsCountComponent = ({ bucket }: { bucket: RawBucket ; type LatestFindingsResponse = IKibanaSearchResponse< estypes.SearchResponse diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/layout/findings_distribution_bar.tsx b/x-pack/plugins/cloud_security_posture/public/pages/configurations/layout/findings_distribution_bar.tsx index 56ca9687551d8..811abe1bd1ccc 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/configurations/layout/findings_distribution_bar.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/configurations/layout/findings_distribution_bar.tsx @@ -6,16 +6,8 @@ */ import React from 'react'; import { css } from '@emotion/react'; -import { - EuiHealth, - EuiBadge, - EuiSpacer, - EuiFlexGroup, - useEuiTheme, - EuiTextColor, -} from '@elastic/eui'; +import { EuiHealth, EuiBadge, EuiSpacer, EuiFlexGroup, useEuiTheme } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; import { getAbbreviatedNumber } from '../../../common/utils/get_abbreviated_number'; import { RULE_FAILED, RULE_PASSED } from '../../../../common/constants'; import { statusColors } from '../../../common/constants'; @@ -35,31 +27,6 @@ const I18N_FAILED_FINDINGS = i18n.translate('xpack.csp.findings.distributionBar. defaultMessage: 'Failed Findings', }); -export const CurrentPageOfTotal = ({ - pageEnd, - pageStart, - total, - type, -}: { - pageEnd: number; - pageStart: number; - total: number; - type: string; -}) => ( - - {pageStart}, - pageEnd: {pageEnd}, - total: {getAbbreviatedNumber(total)}, - type, - }} - /> - -); - export const FindingsDistributionBar = (props: Props) => (
diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/layout/findings_group_by_selector.tsx b/x-pack/plugins/cloud_security_posture/public/pages/configurations/layout/findings_group_by_selector.tsx deleted file mode 100644 index d8d8768df0cf1..0000000000000 --- a/x-pack/plugins/cloud_security_posture/public/pages/configurations/layout/findings_group_by_selector.tsx +++ /dev/null @@ -1,79 +0,0 @@ -/* - * 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 React, { useMemo } from 'react'; -import { EuiComboBox, EuiFormLabel, type EuiComboBoxOptionOption } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { useHistory } from 'react-router-dom'; -import { i18n } from '@kbn/i18n'; -import type { FindingsGroupByKind } from '../../../common/types'; -import { findingsNavigation } from '../../../common/navigation/constants'; -import * as TEST_SUBJECTS from '../test_subjects'; - -const getGroupByOptions = (): Array> => [ - { - value: 'default', - label: i18n.translate('xpack.csp.findings.groupBySelector.groupByNoneLabel', { - defaultMessage: 'None', - }), - }, - { - value: 'resource', - label: i18n.translate('xpack.csp.findings.groupBySelector.groupByResourceIdLabel', { - defaultMessage: 'Resource', - }), - }, -]; - -interface Props { - type: FindingsGroupByKind; - pathnameHandler?: (opts: Array>) => string; -} - -const getFindingsGroupPath = (opts: Array>) => { - const [firstOption] = opts; - - switch (firstOption?.value) { - case 'resource': - return findingsNavigation.findings_by_resource.path; - case 'default': - default: - return findingsNavigation.findings_default.path; - } -}; - -export const FindingsGroupBySelector = ({ - type, - pathnameHandler = getFindingsGroupPath, -}: Props) => { - const groupByOptions = useMemo(getGroupByOptions, []); - const history = useHistory(); - - const onChange = (options: Array>) => - history.push({ pathname: pathnameHandler(options) }); - - return ( - } - singleSelection={{ asPlainText: true }} - options={groupByOptions} - selectedOptions={groupByOptions.filter((o) => o.value === type)} - onChange={onChange} - isClearable={false} - compressed - /> - ); -}; - -const GroupByLabel = () => ( - - - -); diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/test_subjects.ts b/x-pack/plugins/cloud_security_posture/public/pages/configurations/test_subjects.ts index b8a670e8ba58b..d27a7739ab9a9 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/configurations/test_subjects.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/configurations/test_subjects.ts @@ -6,33 +6,11 @@ */ export const FINDINGS_FLYOUT = 'findings_flyout'; -export const FINDINGS_TABLE_EXPAND_COLUMN = 'findings_table_expand_column'; -export const FINDINGS_TABLE = 'findings_table'; -export const FINDINGS_CONTAINER = 'findings_container'; -export const FINDINGS_BY_RESOURCE_CONTAINER = 'findings_by_resource_container'; -export const FINDINGS_BY_RESOURCE_TABLE_RESOURCE_ID_COLUMN = - 'findings_by_resource_table_resource_id_column'; -export const FINDINGS_BY_RESOURCE_TABLE = 'findings_by_resource_table'; -export const getFindingsByResourceTableRowTestId = (id: string) => - `findings_resource_table_row_${id}`; export const LATEST_FINDINGS_CONTAINER = 'latest_findings_container'; export const LATEST_FINDINGS_TABLE = 'latest_findings_table'; -export const FINDINGS_GROUP_BY_SELECTOR = 'findings_group_by_selector'; export const FINDINGS_GROUPING_COUNTER = 'findings_grouping_counter'; -export const getFindingsTableRowTestId = (id: string) => `findings_table_row_${id}`; -export const getFindingsTableCellTestId = (columnId: string, rowId: string) => - `findings_table_cell_${columnId}_${rowId}`; - -export const FINDINGS_TABLE_CELL_ADD_FILTER = 'findings_table_cell_add_filter'; -export const FINDINGS_TABLE_CELL_ADD_NEGATED_FILTER = 'findings_table_cell_add_negated_filter'; - -export const RESOURCES_FINDINGS_CONTAINER = 'resources_findings_container'; -export const RESOURCES_FINDINGS_TABLE = 'resource_findings_table'; -export const getResourceFindingsTableRowTestId = (id: string) => - `resource_findings_table_row_${id}`; - export const FINDINGS_MISCONFIGS_FLYOUT_DESCRIPTION_LIST = 'misconfigs-findings-flyout-description-list'; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/utils/generate_findings_tags.ts b/x-pack/plugins/cloud_security_posture/public/pages/configurations/utils/generate_findings_tags.ts deleted file mode 100644 index 66da177e1cea8..0000000000000 --- a/x-pack/plugins/cloud_security_posture/public/pages/configurations/utils/generate_findings_tags.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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 { CspFinding } from '../../../../common/schemas/csp_finding'; - -const CSP_RULE_TAG = 'Cloud Security'; -const CNVM_RULE_TAG_USE_CASE = 'Use Case: Configuration Audit'; -const CNVM_RULE_TAG_DATA_SOURCE_PREFIX = 'Data Source: '; - -const STATIC_RULE_TAGS = [CSP_RULE_TAG, CNVM_RULE_TAG_USE_CASE]; - -export const generateFindingsTags = (finding: CspFinding) => { - return [STATIC_RULE_TAGS] - .concat(finding.rule.tags) - .concat( - finding.rule.benchmark.posture_type - ? [ - `${CNVM_RULE_TAG_DATA_SOURCE_PREFIX}${finding.rule.benchmark.posture_type.toUpperCase()}`, - ] - : [] - ) - .concat( - finding.rule.benchmark.posture_type === 'cspm' ? ['Domain: Cloud'] : ['Domain: Container'] - ) - .flat(); -}; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/utils/utils.ts b/x-pack/plugins/cloud_security_posture/public/pages/configurations/utils/utils.ts index 3e0277d7cd4c1..67b37ad1001fd 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/configurations/utils/utils.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/configurations/utils/utils.ts @@ -6,19 +6,8 @@ */ import type { estypes } from '@elastic/elasticsearch'; -import { EuiThemeComputed } from '@elastic/eui'; -import type { CspFinding } from '../../../../common/schemas/csp_finding'; export { getFilters } from './get_filters'; -export const getFindingsPageSizeInfo = ({ - currentPageSize, - pageIndex, - pageSize, -}: Record<'pageIndex' | 'pageSize' | 'currentPageSize', number>) => ({ - pageStart: pageIndex * pageSize + 1, - pageEnd: pageIndex * pageSize + currentPageSize, -}); - export const getFindingsCountAggQuery = () => ({ count: { terms: { field: 'result.evaluation' } }, }); @@ -34,14 +23,3 @@ export const getAggregationCount = ( failed: failed?.doc_count || 0, }; }; - -const isSelectedRow = (row: CspFinding, selected?: CspFinding) => - row.resource.id === selected?.resource.id && row.rule.id === selected?.rule.id; - -export const getSelectedRowStyle = ( - theme: EuiThemeComputed, - row: CspFinding, - selected?: CspFinding -): React.CSSProperties => ({ - background: isSelectedRow(row, selected) ? theme.colors.highlight : undefined, -}); diff --git a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules.test.tsx b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules.test.tsx index 7abcd0c37060b..66829ee739010 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules.test.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules.test.tsx @@ -22,9 +22,6 @@ import { useLicenseManagementLocatorApi } from '../../common/api/use_license_man import { useCspBenchmarkIntegrationsV2 } from '../benchmarks/use_csp_benchmark_integrations'; import * as TEST_SUBJECTS from './test_subjects'; -jest.mock('./use_csp_integration', () => ({ - useCspIntegrationInfo: jest.fn(), -})); jest.mock('../../common/api/use_setup_status_api'); jest.mock('../../common/api/use_license_management_locator_api'); jest.mock('../../common/hooks/use_subscription_status'); diff --git a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_table_header.tsx b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_table_header.tsx index 83745e5f5d113..fc714263f38be 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_table_header.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_table_header.tsx @@ -42,8 +42,6 @@ export const RULES_SELECT_ALL_RULES = 'select-all-rules-button'; export const RULES_CLEAR_ALL_RULES_SELECTION = 'clear-rules-selection-button'; export const RULES_DISABLED_FILTER = 'rules-disabled-filter'; export const RULES_ENABLED_FILTER = 'rules-enabled-filter'; -export const CIS_SECTION_FILTER = 'cis-section-filter'; -export const RULE_NUMBER_FILTER = 'rule-number-filter'; interface RulesTableToolbarProps { search: (value: string) => void; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/rules/test_subjects.ts b/x-pack/plugins/cloud_security_posture/public/pages/rules/test_subjects.ts index 43b209c6ce7d4..eb723c4e7894a 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/rules/test_subjects.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/rules/test_subjects.ts @@ -6,9 +6,7 @@ */ export const CSP_RULES_CONTAINER = 'csp_rules_container'; -export const CSP_RULES_SHARED_VALUES = 'csp_rules_shared_values'; -export const CSP_RULES_TABLE_ITEM_SWITCH = 'csp_rules_table_item_switch'; -export const CSP_RULES_SAVE_BUTTON = 'csp_rules_table_save_button'; + export const CSP_RULES_TABLE = 'csp_rules_table'; export const CSP_RULES_TABLE_ROW_ITEM_NAME = 'csp_rules_table_row_item_name'; export const CSP_RULES_FLYOUT_CONTAINER = 'csp_rules_flyout_container'; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/rules/use_csp_benchmark_rules.ts b/x-pack/plugins/cloud_security_posture/public/pages/rules/use_csp_benchmark_rules.ts index d580a7719ed0a..fd8c5bf794935 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/rules/use_csp_benchmark_rules.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/rules/use_csp_benchmark_rules.ts @@ -20,7 +20,6 @@ export type RulesQuery = Pick< FindCspBenchmarkRuleRequest, 'section' | 'search' | 'page' | 'perPage' | 'ruleNumber' | 'sortField' | 'sortOrder' >; -export type RulesQueryResult = ReturnType; export const useFindCspBenchmarkRule = ( { search, page, perPage, section, ruleNumber, sortField, sortOrder }: RulesQuery, diff --git a/x-pack/plugins/cloud_security_posture/public/pages/rules/use_csp_integration.tsx b/x-pack/plugins/cloud_security_posture/public/pages/rules/use_csp_integration.tsx deleted file mode 100644 index 45d4743490e3f..0000000000000 --- a/x-pack/plugins/cloud_security_posture/public/pages/rules/use_csp_integration.tsx +++ /dev/null @@ -1,35 +0,0 @@ -/* - * 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 { useQuery } from '@tanstack/react-query'; -import { - type CopyAgentPolicyResponse, - type GetOnePackagePolicyResponse, - packagePolicyRouteService, - agentPolicyRouteService, - API_VERSIONS, -} from '@kbn/fleet-plugin/common'; -import { useKibana } from '../../common/hooks/use_kibana'; -import { PageUrlParams } from '../../../common/types/rules/v3'; - -export const useCspIntegrationInfo = ({ packagePolicyId, policyId }: PageUrlParams) => { - const { http } = useKibana().services; - - return useQuery(['cspBenchmarkRuleInfo', { packagePolicyId, policyId }], () => - Promise.all([ - http - .get(packagePolicyRouteService.getInfoPath(packagePolicyId), { - version: API_VERSIONS.public.v1, - }) - .then((response) => response.item), - http - .get(agentPolicyRouteService.getInfoPath(policyId), { - version: API_VERSIONS.public.v1, - }) - .then((response) => response.item), - ]) - ); -}; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/latest_vulnerabilities_group_renderer.tsx b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/latest_vulnerabilities_group_renderer.tsx index 9c88ef2ab85d0..351cecd83d502 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/latest_vulnerabilities_group_renderer.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/latest_vulnerabilities_group_renderer.tsx @@ -150,7 +150,7 @@ const VulnerabilitiesCountComponent = ({ `vulnerability-finding-flyout-tab-${tabId}`; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/translations.ts b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/translations.ts index cc5392bd6da66..2864ff7f29005 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/translations.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/translations.ts @@ -9,18 +9,6 @@ import { i18n } from '@kbn/i18n'; import { VULNERABILITY_GROUPING_OPTIONS } from '../../common/constants'; -export const FILTER_IN = i18n.translate('xpack.csp.vulnerabilities.table.filterIn', { - defaultMessage: 'Filter in', -}); -export const FILTER_OUT = i18n.translate('xpack.csp.vulnerabilities.table.filterOut', { - defaultMessage: 'Filter out', -}); -export const SEARCH_BAR_PLACEHOLDER = i18n.translate( - 'xpack.csp.vulnerabilities.searchBar.placeholder', - { - defaultMessage: 'Search vulnerabilities (eg. vulnerability.severity : "CRITICAL" )', - } -); export const VULNERABILITIES = i18n.translate('xpack.csp.vulnerabilities', { defaultMessage: 'Vulnerabilities', }); diff --git a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/types.ts b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/types.ts index 14e9fbae28d41..e0c97ce6ff76d 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/types.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/types.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { VectorScoreBase, CspVulnerabilityFinding } from '../../../common/schemas'; +import { VectorScoreBase } from '../../../common/schemas'; export type Vendor = 'NVD' | 'Red Hat' | 'GHSA'; @@ -19,29 +19,3 @@ export interface Vector { vector: string; score: number | undefined; } - -export interface VulnerabilitiesQueryData { - page: CspVulnerabilityFinding[]; - total: number; -} - -export interface VulnerabilitiesByResourceQueryData { - page: Array<{ - resource: { - id: string; - name: string; - }; - cloud: { - region: string; - }; - vulnerabilities_count: number; - severity_map: { - critical: number; - high: number; - medium: number; - low: number; - }; - }>; - total: number; - total_vulnerabilities: number; -} diff --git a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/utils/custom_sort_script.ts b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/utils/custom_sort_script.ts index d184b0ed568a4..780cd539305b3 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/utils/custom_sort_script.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/utils/custom_sort_script.ts @@ -5,43 +5,6 @@ * 2.0. */ -import { i18n } from '@kbn/i18n'; - -export const severitySchemaConfig = { - type: 'severitySchema', - detector() { - return 0; // this schema is always explicitly defined - }, - sortTextAsc: i18n.translate('xpack.csp.vulnerabilityTable.column.sortAscending', { - defaultMessage: 'Low -> Critical', - }), - sortTextDesc: i18n.translate('xpack.csp.vulnerabilityTable.column.sortDescending', { - defaultMessage: 'Critical -> Low', - }), - icon: 'dot', - color: '', -}; - -export const severitySortScript = (direction: string) => ({ - _script: { - type: 'number', - script: { - lang: 'painless', - inline: - "if(doc.containsKey('vulnerability.severity') && !doc['vulnerability.severity'].empty && doc['vulnerability.severity'].size()!=0 && doc['vulnerability.severity'].value!=null && params.scores.containsKey(doc['vulnerability.severity'].value)) { return params.scores[doc['vulnerability.severity'].value];} return 0;", - params: { - scores: { - LOW: 1, - MEDIUM: 2, - HIGH: 3, - CRITICAL: 4, - }, - }, - }, - order: direction, - }, -}); - /** * Generates Painless sorting in case-insensitive manner */ diff --git a/x-pack/plugins/cloud_security_posture/public/test/fixtures/findings_fixture.ts b/x-pack/plugins/cloud_security_posture/public/test/fixtures/findings_fixture.ts index cb67ab59333e8..1fec1c76430eb 100644 --- a/x-pack/plugins/cloud_security_posture/public/test/fixtures/findings_fixture.ts +++ b/x-pack/plugins/cloud_security_posture/public/test/fixtures/findings_fixture.ts @@ -4,66 +4,3 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - -import { EcsEvent } from '@elastic/ecs'; -import Chance from 'chance'; -import { CspFinding } from '../../../common/schemas/csp_finding'; - -const chance = new Chance(); - -export const getFindingsFixture = (): CspFinding & { id: string } => ({ - cluster_id: chance.guid(), - id: chance.word(), - result: { - expected: { - source: {}, - }, - evaluation: chance.weighted(['passed', 'failed'], [0.5, 0.5]), - evidence: { - filemode: chance.word(), - }, - }, - rule: { - audit: chance.paragraph(), - benchmark: { - name: 'CIS Kubernetes', - version: '1.6.0', - id: 'cis_k8s', - rule_number: '1.1.1', - posture_type: 'kspm', - }, - default_value: chance.sentence(), - description: chance.paragraph(), - id: chance.guid(), - impact: chance.word(), - name: chance.string(), - profile_applicability: chance.sentence(), - rationale: chance.paragraph(), - references: chance.paragraph(), - rego_rule_id: 'cis_X_X_X', - remediation: chance.word(), - section: chance.sentence(), - tags: [], - version: '1.0', - }, - agent: { - id: chance.string(), - name: chance.string(), - type: chance.string(), - version: chance.string(), - }, - resource: { - name: chance.string(), - type: chance.string(), - raw: {} as any, - sub_type: chance.string(), - id: chance.string(), - }, - host: {} as any, - ecs: {} as any, - event: {} as EcsEvent, - '@timestamp': new Date().toISOString(), - data_stream: { - dataset: 'cloud_security_posture.findings', - }, -}); diff --git a/x-pack/plugins/cloud_security_posture/public/test/fixtures/navigation_item.ts b/x-pack/plugins/cloud_security_posture/public/test/fixtures/navigation_item.ts deleted file mode 100644 index 1edffe7af2988..0000000000000 --- a/x-pack/plugins/cloud_security_posture/public/test/fixtures/navigation_item.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * 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 Chance from 'chance'; -import type { CspPageNavigationItem } from '../../common/navigation/types'; - -type CreateNavigationItemFixtureInput = { chance?: Chance.Chance } & Partial; -export const createPageNavigationItemFixture = ({ - chance = new Chance(), - name = chance.word(), - path = `/${chance.word()}`, - disabled = undefined, - id = 'cloud_security_posture-dashboard', -}: CreateNavigationItemFixtureInput = {}): CspPageNavigationItem => ({ - name, - path, - disabled, - id, -}); diff --git a/x-pack/plugins/cloud_security_posture/server/create_indices/ingest_pipelines.ts b/x-pack/plugins/cloud_security_posture/server/create_indices/ingest_pipelines.ts index 2073f7b146275..7948fd9d90578 100644 --- a/x-pack/plugins/cloud_security_posture/server/create_indices/ingest_pipelines.ts +++ b/x-pack/plugins/cloud_security_posture/server/create_indices/ingest_pipelines.ts @@ -9,7 +9,6 @@ import type { IngestPutPipelineRequest } from '@elastic/elasticsearch/lib/api/ty import { CSP_INGEST_TIMESTAMP_PIPELINE, CSP_LATEST_FINDINGS_INGEST_TIMESTAMP_PIPELINE, - CSP_LATEST_VULNERABILITIES_INGEST_TIMESTAMP_PIPELINE, } from '../../common/constants'; export const scorePipelineIngestConfig: IngestPutPipelineRequest = { @@ -53,24 +52,3 @@ export const latestFindingsPipelineIngestConfig: IngestPutPipelineRequest = { }, ], }; - -export const latestVulnerabilitiesPipelineIngestConfig: IngestPutPipelineRequest = { - id: CSP_LATEST_VULNERABILITIES_INGEST_TIMESTAMP_PIPELINE, - description: 'Pipeline for cloudbeat latest vulnerabilities index', - processors: [ - { - set: { - field: 'event.ingested', - value: '{{_ingest.timestamp}}', - }, - }, - ], - on_failure: [ - { - set: { - field: 'error.message', - value: '{{ _ingest.on_failure_message }}', - }, - }, - ], -}; diff --git a/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/types.ts b/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/types.ts index 370c9676099d6..d5db37879554b 100644 --- a/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/types.ts +++ b/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/types.ts @@ -180,10 +180,6 @@ export interface KubernetesVersion { metrics: { 'cloudbeat.kubernetes.version': string }; } -export interface PackagePolicyId { - metrics: { 'cloud_security_posture.package_policy.id': string }; -} - export interface LatestDocTimestamp { metrics: { '@timestamp': string }; } diff --git a/x-pack/plugins/cloud_security_posture/server/routes/detection_engine/get_detection_engine_alerts_count_by_rule_tags.ts b/x-pack/plugins/cloud_security_posture/server/routes/detection_engine/get_detection_engine_alerts_count_by_rule_tags.ts index 026cf68819ab2..52ff94ad7086c 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/detection_engine/get_detection_engine_alerts_count_by_rule_tags.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/detection_engine/get_detection_engine_alerts_count_by_rule_tags.ts @@ -15,10 +15,6 @@ import { } from '../../../common/constants'; import { CspRouter } from '../../types'; -export interface VulnerabilitiesStatisticsQueryResult { - total: number; -} - const DEFAULT_ALERTS_INDEX = '.alerts-security.alerts-default' as const; export const getDetectionEngineAlertsCountByRuleTags = async ( diff --git a/x-pack/plugins/cloud_security_posture/server/types.ts b/x-pack/plugins/cloud_security_posture/server/types.ts index 82f80cd95f972..1190c0a1c5556 100644 --- a/x-pack/plugins/cloud_security_posture/server/types.ts +++ b/x-pack/plugins/cloud_security_posture/server/types.ts @@ -22,9 +22,6 @@ import type { Logger, SavedObjectsClientContract, IScopedClusterClient, - KibanaResponseFactory, - RequestHandler, - RouteMethod, } from '@kbn/core/server'; import type { AgentService, @@ -90,18 +87,6 @@ export type CspRequestHandlerContext = CustomRequestHandlerContext<{ alerting: AlertingApiRequestHandlerContext; }>; -/** - * Convenience type for request handlers in CSP that includes the CspRequestHandlerContext type - * @internal - */ -export type CspRequestHandler< - P = unknown, - Q = unknown, - B = unknown, - Method extends RouteMethod = any, - ResponseFactory extends KibanaResponseFactory = KibanaResponseFactory -> = RequestHandler; - /** * Convenience type for routers in Csp that includes the CspRequestHandlerContext type * @internal diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/components/common/translations.tsx b/x-pack/plugins/observability_solution/logs_explorer/public/components/common/translations.tsx index 577e068483427..826fcdab65915 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/public/components/common/translations.tsx +++ b/x-pack/plugins/observability_solution/logs_explorer/public/components/common/translations.tsx @@ -7,7 +7,6 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiCode } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; export const contentLabel = i18n.translate('xpack.logsExplorer.dataTable.header.popover.content', { @@ -21,17 +20,6 @@ export const resourceLabel = i18n.translate( } ); -export const actionsLabel = i18n.translate('xpack.logsExplorer.dataTable.header.popover.actions', { - defaultMessage: 'Actions', -}); - -export const actionsLabelLowerCase = i18n.translate( - 'xpack.logsExplorer.dataTable.header.popover.actions.lowercase', - { - defaultMessage: 'actions', - } -); - export const actionFilterForText = (text: string) => i18n.translate('xpack.logsExplorer.flyoutDetail.value.hover.filterFor', { defaultMessage: 'Filter for this {value}', @@ -109,35 +97,18 @@ export const resourceHeaderTooltipParagraph = i18n.translate( } ); -export const actionsHeaderTooltipParagraph = i18n.translate( - 'xpack.logsExplorer.dataTable.header.actions.tooltip.paragraph', +export const actionsHeaderAriaLabelDegradedAction = i18n.translate( + 'xpack.logsExplorer.dataTable.controlColumnHeader.degradedDocArialLabel', { - defaultMessage: 'Fields that provide actionable information, such as:', + defaultMessage: 'Access to degraded docs', } ); -export const actionsHeaderTooltipExpandAction = i18n.translate( - 'xpack.logsExplorer.dataTable.header.actions.tooltip.expand', - { defaultMessage: 'Expand log details' } -); - -export const actionsHeaderTooltipDegradedAction = ( - - _ignored - - ), - }} - /> -); - -export const actionsHeaderTooltipStacktraceAction = i18n.translate( - 'xpack.logsExplorer.dataTable.header.actions.tooltip.stacktrace', - { defaultMessage: 'Access to available stacktraces based on:' } +export const actionsHeaderAriaLabelStacktraceAction = i18n.translate( + 'xpack.logsExplorer.dataTable.controlColumnHeader.stacktraceArialLabel', + { + defaultMessage: 'Access to available stacktraces', + } ); export const degradedDocButtonLabelWhenPresent = i18n.translate( diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/components/virtual_columns/column_tooltips/actions_column_tooltip.tsx b/x-pack/plugins/observability_solution/logs_explorer/public/components/virtual_columns/column_tooltips/actions_column_tooltip.tsx deleted file mode 100644 index c33c514bf3789..0000000000000 --- a/x-pack/plugins/observability_solution/logs_explorer/public/components/virtual_columns/column_tooltips/actions_column_tooltip.tsx +++ /dev/null @@ -1,95 +0,0 @@ -/* - * 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 React from 'react'; -import { css } from '@emotion/react'; -import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiText } from '@elastic/eui'; -import { euiThemeVars } from '@kbn/ui-theme'; -import { - actionsHeaderTooltipExpandAction, - actionsHeaderTooltipDegradedAction, - actionsHeaderTooltipParagraph, - actionsHeaderTooltipStacktraceAction, - actionsLabel, - actionsLabelLowerCase, -} from '../../common/translations'; -import { TooltipButton } from './tooltip_button'; -import * as constants from '../../../../common/constants'; -import { FieldWithToken } from './field_with_token'; - -const spacingCSS = css` - margin-bottom: ${euiThemeVars.euiSizeS}; -`; - -export const ActionsColumnTooltip = () => { - return ( - -
- -

{actionsHeaderTooltipParagraph}

-
- - - - - - -

{actionsHeaderTooltipExpandAction}

-
-
-
- - - - - - -

{actionsHeaderTooltipDegradedAction}

-
-
-
- - - - - - -

{actionsHeaderTooltipStacktraceAction}

-
-
-
-
- {[ - constants.ERROR_STACK_TRACE, - constants.ERROR_EXCEPTION_STACKTRACE, - constants.ERROR_LOG_STACKTRACE, - ].map((field) => ( - - ))} -
-
-
- ); -}; diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/customizations/custom_control_column.tsx b/x-pack/plugins/observability_solution/logs_explorer/public/customizations/custom_control_column.tsx index 43edee4cf73af..89bc38482c803 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/public/customizations/custom_control_column.tsx +++ b/x-pack/plugins/observability_solution/logs_explorer/public/customizations/custom_control_column.tsx @@ -5,19 +5,16 @@ * 2.0. */ -import React, { ComponentClass } from 'react'; -import { - OPEN_DETAILS, - SELECT_ROW, - type ControlColumnsProps, - DataTableRowControl, -} from '@kbn/unified-data-table'; -import { EuiButtonIcon, EuiDataGridCellValueElementProps, EuiToolTip } from '@elastic/eui'; -import type { DataTableRecord } from '@kbn/discover-utils/src/types'; -import { useActor } from '@xstate/react'; +import React from 'react'; import { LogDocument } from '@kbn/discover-utils/src'; -import { LogsExplorerControllerStateService } from '../state_machines/logs_explorer_controller'; +import type { + UnifiedDataTableProps, + RowControlComponent, + RowControlRowProps, +} from '@kbn/unified-data-table'; import { + actionsHeaderAriaLabelDegradedAction, + actionsHeaderAriaLabelStacktraceAction, degradedDocButtonLabelWhenNotPresent, degradedDocButtonLabelWhenPresent, stacktraceAvailableControlButton, @@ -25,122 +22,79 @@ import { } from '../components/common/translations'; import * as constants from '../../common/constants'; import { getStacktraceFields } from '../utils/get_stack_trace'; -import { ActionsColumnTooltip } from '../components/virtual_columns/column_tooltips/actions_column_tooltip'; - -const ConnectedDegradedDocs = ({ - rowIndex, - service, -}: { - rowIndex: number; - service: LogsExplorerControllerStateService; -}) => { - const [state] = useActor(service); - if (state.matches('initialized') && state.context.rows[rowIndex]) { - return ; - } - - return null; -}; -const ConnectedStacktraceDocs = ({ - rowIndex, - service, +const DegradedDocs = ({ + Control, + rowProps: { record }, }: { - rowIndex: number; - service: LogsExplorerControllerStateService; + Control: RowControlComponent; + rowProps: RowControlRowProps; }) => { - const [state] = useActor(service); - if (state.matches('initialized') && state.context.rows[rowIndex]) { - return ; - } - - return null; -}; - -const DegradedDocs = ({ row, rowIndex }: { row: DataTableRecord; rowIndex: number }) => { - const isDegradedDocumentExists = constants.DEGRADED_DOCS_FIELD in row.raw; + const isDegradedDocumentExists = constants.DEGRADED_DOCS_FIELD in record.raw; return isDegradedDocumentExists ? ( - - - - - + ) : ( - - - - - + ); }; -const Stacktrace = ({ row, rowIndex }: { row: DataTableRecord; rowIndex: number }) => { - const stacktrace = getStacktraceFields(row as LogDocument); +const Stacktrace = ({ + Control, + rowProps: { record }, +}: { + Control: RowControlComponent; + rowProps: RowControlRowProps; +}) => { + const stacktrace = getStacktraceFields(record as LogDocument); const hasValue = Object.values(stacktrace).some((value) => value); - return ( - - - - - + return hasValue ? ( + + ) : ( + ); }; -export const createCustomControlColumnsConfiguration = - (service: LogsExplorerControllerStateService) => - ({ controlColumns }: ControlColumnsProps) => { - const checkBoxColumn = controlColumns[SELECT_ROW]; - const openDetails = controlColumns[OPEN_DETAILS]; - const ExpandButton = - openDetails.rowCellRender as ComponentClass; - const actionsColumn = { - id: 'actionsColumn', - width: constants.ACTIONS_COLUMN_WIDTH, - headerCellRender: ActionsColumnTooltip, - rowCellRender: ({ rowIndex, setCellProps, ...rest }: EuiDataGridCellValueElementProps) => { - return ( - - - - - - ); +export const getRowAdditionalControlColumns = + (): UnifiedDataTableProps['rowAdditionalLeadingControls'] => { + return [ + { + id: 'connectedDegradedDocs', + headerAriaLabel: actionsHeaderAriaLabelDegradedAction, + renderControl: (Control, rowProps) => { + return ; + }, }, - }; - - return { - leadingControlColumns: [checkBoxColumn, actionsColumn], - }; + { + id: 'connectedStacktraceDocs', + headerAriaLabel: actionsHeaderAriaLabelStacktraceAction, + renderControl: (Control, rowProps) => { + return ; + }, + }, + ]; }; diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/customizations/logs_explorer_profile.tsx b/x-pack/plugins/observability_solution/logs_explorer/public/customizations/logs_explorer_profile.tsx index 7b193f4aafff8..557cbe4dd2728 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/public/customizations/logs_explorer_profile.tsx +++ b/x-pack/plugins/observability_solution/logs_explorer/public/customizations/logs_explorer_profile.tsx @@ -82,8 +82,8 @@ export const createLogsExplorerProfileCustomizations = customizations.set({ id: 'data_table', logsEnabled: true, - customControlColumnsConfiguration: await import('./custom_control_column').then((module) => - module.createCustomControlColumnsConfiguration(service) + rowAdditionalLeadingControls: await import('./custom_control_column').then((module) => + module.getRowAdditionalControlColumns() ), }); diff --git a/x-pack/plugins/osquery/cypress/e2e/all/live_query_packs.cy.ts b/x-pack/plugins/osquery/cypress/e2e/all/live_query_packs.cy.ts index 85ae94dd3266d..a8461dd9742a8 100644 --- a/x-pack/plugins/osquery/cypress/e2e/all/live_query_packs.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/all/live_query_packs.cy.ts @@ -18,7 +18,8 @@ import { LIVE_QUERY_EDITOR } from '../../screens/live_query'; import { loadPack, cleanupPack, cleanupCase, loadCase } from '../../tasks/api_fixtures'; import { ServerlessRoleName } from '../../support/roles'; -describe('ALL - Live Query Packs', { tags: ['@ess', '@serverless'] }, () => { +// FLAKY: https://github.com/elastic/kibana/issues/169888 +describe.skip('ALL - Live Query Packs', { tags: ['@ess', '@serverless'] }, () => { let packName: string; let packId: string; let caseId: string; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/audit_log_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/action_log/action_log.ts similarity index 100% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/audit_log_route.ts rename to x-pack/plugins/security_solution/common/api/endpoint/actions/action_log/action_log.ts diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/audit_log.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/action_log/deprecated_action_log.gen.ts similarity index 59% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/audit_log.gen.ts rename to x-pack/plugins/security_solution/common/api/endpoint/actions/action_log/deprecated_action_log.gen.ts index bfbea46ed9c5c..e458ae1775479 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/audit_log.gen.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/action_log/deprecated_action_log.gen.ts @@ -10,23 +10,18 @@ * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. * * info: - * title: Audit Log Schema + * title: Action Log Schema * version: 2023-10-31 */ import { z } from 'zod'; -import { Page, PageSize, StartDate, EndDate, AgentId } from '../model/schema/common.gen'; +import { Page, PageSize, StartDate, EndDate } from '../../model/schema/common.gen'; -export type AuditLogRequestQuery = z.infer; -export const AuditLogRequestQuery = z.object({ +export type ActionLogRequestQuery = z.infer; +export const ActionLogRequestQuery = z.object({ page: Page.optional(), page_size: PageSize.optional(), start_date: StartDate.optional(), end_date: EndDate.optional(), }); - -export type AuditLogRequestParams = z.infer; -export const AuditLogRequestParams = z.object({ - agent_id: AgentId.optional(), -}); diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/action_log/deprecated_action_log.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/action_log/deprecated_action_log.schema.yaml new file mode 100644 index 0000000000000..d46a1cbe7d9ba --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/action_log/deprecated_action_log.schema.yaml @@ -0,0 +1,45 @@ +openapi: 3.0.0 +info: + title: Action Log Schema + version: '2023-10-31' +paths: + /api/endpoint/action_log/{agent_id}: + get: + summary: Get action requests log schema + operationId: EndpointGetActionLog + description: Get action requests log + deprecated: true + x-codegen-enabled: false + x-labels: [ess, serverless] + parameters: + - name: agent_id + in: path + required: true + schema: + $ref: '../../model/schema/common.schema.yaml#/components/schemas/AgentId' + - name: query + in: query + required: true + schema: + $ref: '#/components/schemas/ActionLogRequestQuery' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' + +components: + schemas: + ActionLogRequestQuery: + type: object + properties: + page: + $ref: '../../model/schema/common.schema.yaml#/components/schemas/Page' + page_size: + $ref: '../../model/schema/common.schema.yaml#/components/schemas/PageSize' + start_date: + $ref: '../../model/schema/common.schema.yaml#/components/schemas/StartDate' + end_date: + $ref: '../../model/schema/common.schema.yaml#/components/schemas/EndDate' diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/unisolate_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/action_log/index.ts similarity index 66% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/unisolate_route.ts rename to x-pack/plugins/security_solution/common/api/endpoint/actions/action_log/index.ts index 2d9f2d2f5725b..61a6f0bcbd5c9 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/unisolate_route.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/action_log/index.ts @@ -5,6 +5,5 @@ * 2.0. */ -import { NoParametersRequestSchema } from './common/base'; - -export const UnisolateRouteRequestSchema = NoParametersRequestSchema; +export * from './action_log'; +export * from './deprecated_action_log.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/actions.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/actions.schema.yaml deleted file mode 100644 index a64eb5c5cff3a..0000000000000 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/actions.schema.yaml +++ /dev/null @@ -1,131 +0,0 @@ -openapi: 3.0.0 -info: - title: Endpoint Actions Schema - version: '2023-10-31' -paths: - /api/endpoint/action/state: - get: - summary: Get Action State schema - operationId: EndpointGetActionsState - x-codegen-enabled: false - x-labels: - - ess - - serverless - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' - - /api/endpoint/action/running_procs: - post: - summary: Get Running Processes Action - operationId: EndpointGetRunningProcessesAction - x-codegen-enabled: false - x-labels: - - ess - - serverless - requestBody: - required: true - content: - application/json: - schema: - $ref: '../model/schema/common.schema.yaml#/components/schemas/BaseActionSchema' - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' - - /api/endpoint/action/isolate: - post: - summary: Isolate host Action - operationId: EndpointIsolateHostAction - x-codegen-enabled: false - x-labels: - - ess - - serverless - requestBody: - required: true - content: - application/json: - schema: - $ref: '../model/schema/common.schema.yaml#/components/schemas/BaseActionSchema' - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' - - /api/endpoint/action/unisolate: - post: - summary: Unisolate host Action - operationId: EndpointUnisolateHostAction - x-codegen-enabled: false - x-labels: - - ess - - serverless - requestBody: - required: true - content: - application/json: - schema: - $ref: '../model/schema/common.schema.yaml#/components/schemas/BaseActionSchema' - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' - - /api/endpoint/action/kill_process: - post: - summary: Kill process Action - operationId: EndpointKillProcessAction - x-codegen-enabled: false - x-labels: - - ess - - serverless - requestBody: - required: true - content: - application/json: - schema: - $ref: '../model/schema/common.schema.yaml#/components/schemas/ProcessActionSchemas' - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' - - - /api/endpoint/action/suspend_process: - post: - summary: Suspend process Action - operationId: EndpointSuspendProcessAction - x-codegen-enabled: false - x-labels: - - ess - - serverless - requestBody: - required: true - content: - application/json: - schema: - $ref: '../model/schema/common.schema.yaml#/components/schemas/ProcessActionSchemas' - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/audit_log.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/audit_log.schema.yaml deleted file mode 100644 index 5d0ed51ca339a..0000000000000 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/audit_log.schema.yaml +++ /dev/null @@ -1,51 +0,0 @@ -openapi: 3.0.0 -info: - title: Audit Log Schema - version: '2023-10-31' -paths: - /api/endpoint/action_log/{agent_id}: - get: - summary: Get action audit log schema - operationId: EndpointGetActionAuditLog - x-codegen-enabled: false - x-labels: - - ess - - serverless - parameters: - - name: query - in: query - required: true - schema: - $ref: '#/components/schemas/AuditLogRequestQuery' - - name: query - in: path - required: true - schema: - $ref: '#/components/schemas/AuditLogRequestParams' - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' - -components: - schemas: - AuditLogRequestQuery: - type: object - properties: - page: - $ref: '../model/schema/common.schema.yaml#/components/schemas/Page' - page_size: - $ref: '../model/schema/common.schema.yaml#/components/schemas/PageSize' - start_date: - $ref: '../model/schema/common.schema.yaml#/components/schemas/StartDate' - end_date: - $ref: '../model/schema/common.schema.yaml#/components/schemas/EndDate' - - AuditLogRequestParams: - type: object - properties: - agent_id: - $ref: '../model/schema/common.schema.yaml#/components/schemas/AgentId' diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/common/response_actions.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/common/response_actions.ts index ca6d9d5e91982..56b1fafdb5a71 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/common/response_actions.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/common/response_actions.ts @@ -7,24 +7,27 @@ import type { TypeOf } from '@kbn/config-schema'; import { schema } from '@kbn/config-schema'; -import { - KillProcessRouteRequestSchema, - SuspendProcessRouteRequestSchema, - UploadActionRequestSchema, -} from '../..'; -import { ExecuteActionRequestSchema } from '../execute_route'; -import { EndpointActionGetFileSchema } from '../get_file_route'; -import { ScanActionRequestSchema } from '../scan_route'; -import { NoParametersRequestSchema } from './base'; + +import { ExecuteActionRequestSchema } from '../response_actions/execute'; +import { EndpointActionGetFileSchema } from '../response_actions/get_file'; +import { ScanActionRequestSchema } from '../response_actions/scan'; +import { IsolateRouteRequestSchema } from '../response_actions/isolate'; +import { UnisolateRouteRequestSchema } from '../response_actions/unisolate'; +import { GetProcessesRouteRequestSchema } from '../response_actions/running_procs'; +import { KillProcessRouteRequestSchema } from '../response_actions/kill_process'; +import { SuspendProcessRouteRequestSchema } from '../response_actions/suspend_process'; +import { UploadActionRequestSchema } from '../response_actions/upload'; export const ResponseActionBodySchema = schema.oneOf([ - NoParametersRequestSchema.body, + IsolateRouteRequestSchema.body, + UnisolateRouteRequestSchema.body, + GetProcessesRouteRequestSchema.body, KillProcessRouteRequestSchema.body, SuspendProcessRouteRequestSchema.body, EndpointActionGetFileSchema.body, ExecuteActionRequestSchema.body, - ScanActionRequestSchema.body, UploadActionRequestSchema.body, + ScanActionRequestSchema.body, ]); export type ResponseActionsRequestBody = TypeOf; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/details/details.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/details/details.gen.ts new file mode 100644 index 0000000000000..3f5305dabd424 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/details/details.gen.ts @@ -0,0 +1,32 @@ +/* + * 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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Details Schema + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +import { SuccessResponse } from '../../model/schema/common.gen'; + +export type EndpointGetActionsDetailsRequestParams = z.infer< + typeof EndpointGetActionsDetailsRequestParams +>; +export const EndpointGetActionsDetailsRequestParams = z.object({ + action_id: z.string(), +}); +export type EndpointGetActionsDetailsRequestParamsInput = z.input< + typeof EndpointGetActionsDetailsRequestParams +>; + +export type EndpointGetActionsDetailsResponse = z.infer; +export const EndpointGetActionsDetailsResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/details.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/details/details.schema.yaml similarity index 51% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/details.schema.yaml rename to x-pack/plugins/security_solution/common/api/endpoint/actions/details/details.schema.yaml index 18a3ffa2c1fd2..ec3a184e6e9a9 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/details.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/details/details.schema.yaml @@ -7,28 +7,21 @@ paths: get: summary: Get Action details schema operationId: EndpointGetActionsDetails - x-codegen-enabled: false - x-labels: - - ess - - serverless + description: Get action details + x-codegen-enabled: true + x-labels: [ess, serverless] parameters: - - name: query + - name: action_id in: path required: true schema: - $ref: '#/components/schemas/DetailsRequestParams' + type: string responses: '200': description: OK content: application/json: schema: - $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' -components: - schemas: - DetailsRequestParams: - type: object - properties: - action_id: - type: string + $ref: '../../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' + diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/details_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/details/details.ts similarity index 100% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/details_route.ts rename to x-pack/plugins/security_solution/common/api/endpoint/actions/details/details.ts diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/constants.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/details/index.ts similarity index 52% rename from x-pack/plugins/cloud_security_posture/public/pages/configurations/constants.ts rename to x-pack/plugins/security_solution/common/api/endpoint/actions/details/index.ts index d4a14320fe225..0154d63be42a2 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/configurations/constants.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/details/index.ts @@ -5,6 +5,5 @@ * 2.0. */ -export const FINDINGS_PIT_KEEP_ALIVE = '2m'; -// Set to half of the PIT keep alive to make sure we keep the PIT window open as long as the components are mounted -export const FINDINGS_REFETCH_INTERVAL_MS = 1000 * 60; // One minute +export * from './details'; +export * from './details.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/execute.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/execute.gen.ts deleted file mode 100644 index 8afd62814dfb3..0000000000000 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/execute.gen.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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. - */ - -/* - * NOTICE: Do not edit this file manually. - * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. - * - * info: - * title: Execute Action Schema - * version: 2023-10-31 - */ - -import { z } from 'zod'; - -import { BaseActionSchema, Command, Timeout } from '../model/schema/common.gen'; - -export type ExecuteActionRequestBody = z.infer; -export const ExecuteActionRequestBody = BaseActionSchema.merge( - z.object({ - parameters: z.object({ - command: Command, - timeout: Timeout.optional(), - }), - }) -); diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_download.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_download.schema.yaml deleted file mode 100644 index 8eb02883965b1..0000000000000 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_download.schema.yaml +++ /dev/null @@ -1,39 +0,0 @@ -openapi: 3.0.0 -info: - title: File Download Schema - version: '2023-10-31' -paths: - /api/endpoint/action/{action_id}/file/{file_id}/download`: - get: - summary: File Download schema - operationId: EndpointFileDownload - x-codegen-enabled: false - x-labels: - - ess - - serverless - parameters: - - name: query - in: path - required: true - schema: - $ref: '#/components/schemas/FileDownloadRequestParams' - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' -components: - schemas: - FileDownloadRequestParams: - type: object - required: - - action_id - - file_id - properties: - action_id: - type: string - file_id: - type: string - diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_download.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_download/file_download.gen.ts similarity index 52% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/file_download.gen.ts rename to x-pack/plugins/security_solution/common/api/endpoint/actions/file_download/file_download.gen.ts index e670d2070d8ab..a32c1036e3fd9 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_download.gen.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_download/file_download.gen.ts @@ -16,8 +16,16 @@ import { z } from 'zod'; -export type FileDownloadRequestParams = z.infer; -export const FileDownloadRequestParams = z.object({ +import { SuccessResponse } from '../../model/schema/common.gen'; + +export type EndpointFileDownloadRequestParams = z.infer; +export const EndpointFileDownloadRequestParams = z.object({ action_id: z.string(), file_id: z.string(), }); +export type EndpointFileDownloadRequestParamsInput = z.input< + typeof EndpointFileDownloadRequestParams +>; + +export type EndpointFileDownloadResponse = z.infer; +export const EndpointFileDownloadResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_download/file_download.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_download/file_download.schema.yaml new file mode 100644 index 0000000000000..8842d1b6acc61 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_download/file_download.schema.yaml @@ -0,0 +1,30 @@ +openapi: 3.0.0 +info: + title: File Download Schema + version: '2023-10-31' +paths: + /api/endpoint/action/{action_id}/file/{file_id}/download`: + get: + summary: File Download schema + operationId: EndpointFileDownload + description: Download a file from an endpoint + x-codegen-enabled: true + x-labels: [ess, serverless] + parameters: + - name: action_id + in: path + required: true + schema: + type: string + - name: file_id + in: path + required: true + schema: + type: string + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_download_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_download/file_download.ts similarity index 100% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/file_download_route.ts rename to x-pack/plugins/security_solution/common/api/endpoint/actions/file_download/file_download.ts diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_download/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_download/index.ts new file mode 100644 index 0000000000000..f6b87f11df80e --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_download/index.ts @@ -0,0 +1,9 @@ +/* + * 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. + */ + +export * from './file_download'; +export * from './file_download.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_info.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_info.schema.yaml deleted file mode 100644 index 5351480738e23..0000000000000 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_info.schema.yaml +++ /dev/null @@ -1,40 +0,0 @@ -openapi: 3.0.0 -info: - title: File Info Schema - version: '2023-10-31' -paths: - /api/endpoint/action/{action_id}/file/{file_id}`: - get: - summary: File Info schema - operationId: EndpointFileInfo - x-codegen-enabled: false - x-labels: - - ess - - serverless - parameters: - - name: query - in: path - required: true - schema: - $ref: '#/components/schemas/FileInfoRequestParams' - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' - -components: - schemas: - FileInfoRequestParams: - type: object - required: - - action_id - - file_id - properties: - action_id: - type: string - file_id: - type: string - diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_info.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_info/file_info.gen.ts similarity index 54% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/file_info.gen.ts rename to x-pack/plugins/security_solution/common/api/endpoint/actions/file_info/file_info.gen.ts index d9737560e849c..1a706049b067a 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_info.gen.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_info/file_info.gen.ts @@ -16,8 +16,14 @@ import { z } from 'zod'; -export type FileInfoRequestParams = z.infer; -export const FileInfoRequestParams = z.object({ +import { SuccessResponse } from '../../model/schema/common.gen'; + +export type EndpointFileInfoRequestParams = z.infer; +export const EndpointFileInfoRequestParams = z.object({ action_id: z.string(), file_id: z.string(), }); +export type EndpointFileInfoRequestParamsInput = z.input; + +export type EndpointFileInfoResponse = z.infer; +export const EndpointFileInfoResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_info/file_info.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_info/file_info.schema.yaml new file mode 100644 index 0000000000000..6199dc56ed1b0 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_info/file_info.schema.yaml @@ -0,0 +1,30 @@ +openapi: 3.0.0 +info: + title: File Info Schema + version: '2023-10-31' +paths: + /api/endpoint/action/{action_id}/file/{file_id}`: + get: + summary: File Info schema + operationId: EndpointFileInfo + description: Get file info + x-codegen-enabled: true + x-labels: [ess, serverless] + parameters: + - name: action_id + in: path + required: true + schema: + type: string + - name: file_id + in: path + required: true + schema: + type: string + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_info_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_info/file_info.ts similarity index 100% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/file_info_route.ts rename to x-pack/plugins/security_solution/common/api/endpoint/actions/file_info/file_info.ts diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_info/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_info/index.ts new file mode 100644 index 0000000000000..db1f6c9ef2db3 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_info/index.ts @@ -0,0 +1,9 @@ +/* + * 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. + */ + +export * from './file_info'; +export * from './file_info.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_upload.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_upload.gen.ts deleted file mode 100644 index f376c6d81fc21..0000000000000 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_upload.gen.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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. - */ - -/* - * NOTICE: Do not edit this file manually. - * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. - * - * info: - * title: File Upload Schema - * version: 2023-10-31 - */ - -import { z } from 'zod'; - -import { BaseActionSchema } from '../model/schema/common.gen'; - -export type FileUploadActionRequestBody = z.infer; -export const FileUploadActionRequestBody = BaseActionSchema.merge( - z.object({ - parameters: z.object({ - overwrite: z.boolean().optional().default(false), - }), - file: z.string(), - }) -); diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/get_file.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/get_file.gen.ts deleted file mode 100644 index 22dc90c6cfd82..0000000000000 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/get_file.gen.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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. - */ - -/* - * NOTICE: Do not edit this file manually. - * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. - * - * info: - * title: Get File Schema - * version: 2023-10-31 - */ - -import { z } from 'zod'; - -import { BaseActionSchema } from '../model/schema/common.gen'; - -export type GetFileActionRequestBody = z.infer; -export const GetFileActionRequestBody = BaseActionSchema.merge( - z.object({ - parameters: z.object({ - path: z.string(), - }), - }) -); diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/list.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/list.schema.yaml deleted file mode 100644 index afa844135ecd5..0000000000000 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/list.schema.yaml +++ /dev/null @@ -1,53 +0,0 @@ -openapi: 3.0.0 -info: - title: Actions List Schema - version: '2023-10-31' -paths: - /api/endpoint/action: - get: - summary: Get Actions List schema - operationId: EndpointGetActionsList - x-codegen-enabled: false - x-labels: - - ess - - serverless - parameters: - - name: query - in: query - required: true - schema: - $ref: '#/components/schemas/EndpointActionListRequestQuery' - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' -components: - schemas: - EndpointActionListRequestQuery: - type: object - properties: - agentIds: - $ref: '../model/schema/common.schema.yaml#/components/schemas/AgentIds' - commands: - $ref: '../model/schema/common.schema.yaml#/components/schemas/Commands' - page: - $ref: '../model/schema/common.schema.yaml#/components/schemas/Page' - pageSize: - type: integer - default: 10 - minimum: 1 - maximum: 10000 - description: Number of items per page - startDate: - $ref: '../model/schema/common.schema.yaml#/components/schemas/StartDate' - endDate: - $ref: '../model/schema/common.schema.yaml#/components/schemas/EndDate' - userIds: - $ref: '../model/schema/common.schema.yaml#/components/schemas/UserIds' - types: - $ref: '../model/schema/common.schema.yaml#/components/schemas/Types' - withOutputs: - $ref: '../model/schema/common.schema.yaml#/components/schemas/WithOutputs' diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/list/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/list/index.ts new file mode 100644 index 0000000000000..9b1e437559465 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/list/index.ts @@ -0,0 +1,9 @@ +/* + * 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. + */ + +export * from './list'; +export * from './list.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/list.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/list/list.gen.ts similarity index 56% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/list.gen.ts rename to x-pack/plugins/security_solution/common/api/endpoint/actions/list/list.gen.ts index 771af7165f3bd..a63e011e1d2f5 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/list.gen.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/list/list.gen.ts @@ -17,7 +17,9 @@ import { z } from 'zod'; import { + SuccessResponse, AgentIds, + AgentTypes, Commands, Page, StartDate, @@ -25,11 +27,12 @@ import { UserIds, Types, WithOutputs, -} from '../model/schema/common.gen'; +} from '../../model/schema/common.gen'; -export type EndpointActionListRequestQuery = z.infer; -export const EndpointActionListRequestQuery = z.object({ +export type GetEndpointActionListRouteQuery = z.infer; +export const GetEndpointActionListRouteQuery = z.object({ agentIds: AgentIds.optional(), + agentTypes: AgentTypes.optional(), commands: Commands.optional(), page: Page.optional(), /** @@ -42,3 +45,14 @@ export const EndpointActionListRequestQuery = z.object({ types: Types.optional(), withOutputs: WithOutputs.optional(), }); + +export type EndpointGetActionsListRequestQuery = z.infer; +export const EndpointGetActionsListRequestQuery = z.object({ + query: GetEndpointActionListRouteQuery, +}); +export type EndpointGetActionsListRequestQueryInput = z.input< + typeof EndpointGetActionsListRequestQuery +>; + +export type EndpointGetActionsListResponse = z.infer; +export const EndpointGetActionsListResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/list/list.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/list/list.schema.yaml new file mode 100644 index 0000000000000..b91ba03c60b8d --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/list/list.schema.yaml @@ -0,0 +1,54 @@ +openapi: 3.0.0 +info: + title: Actions List Schema + version: '2023-10-31' +paths: + /api/endpoint/action: + get: + summary: Get Actions List schema + operationId: EndpointGetActionsList + description: Get a list of action requests and their responses + x-codegen-enabled: true + x-labels: [ess, serverless] + parameters: + - name: query + in: query + required: true + schema: + $ref: '#/components/schemas/GetEndpointActionListRouteQuery' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' +components: + schemas: + GetEndpointActionListRouteQuery: + type: object + properties: + agentIds: + $ref: '../../model/schema/common.schema.yaml#/components/schemas/AgentIds' + agentTypes: + $ref: '../../model/schema/common.schema.yaml#/components/schemas/AgentTypes' + commands: + $ref: '../../model/schema/common.schema.yaml#/components/schemas/Commands' + page: + $ref: '../../model/schema/common.schema.yaml#/components/schemas/Page' + pageSize: + type: integer + default: 10 + minimum: 1 + maximum: 10000 + description: Number of items per page + startDate: + $ref: '../../model/schema/common.schema.yaml#/components/schemas/StartDate' + endDate: + $ref: '../../model/schema/common.schema.yaml#/components/schemas/EndDate' + userIds: + $ref: '../../model/schema/common.schema.yaml#/components/schemas/UserIds' + types: + $ref: '../../model/schema/common.schema.yaml#/components/schemas/Types' + withOutputs: + $ref: '../../model/schema/common.schema.yaml#/components/schemas/WithOutputs' diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/list_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/list/list.ts similarity index 94% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/list_route.ts rename to x-pack/plugins/security_solution/common/api/endpoint/actions/list/list.ts index 720764d9cd03c..81cd32a045a7d 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/list_route.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/list/list.ts @@ -12,9 +12,9 @@ import { RESPONSE_ACTION_API_COMMANDS_NAMES, RESPONSE_ACTION_STATUS, RESPONSE_ACTION_TYPE, -} from '../../../endpoint/service/response_actions/constants'; -import { ENDPOINT_DEFAULT_PAGE_SIZE } from '../../../endpoint/constants'; -import { agentTypesSchema } from './common/base'; +} from '../../../../endpoint/service/response_actions/constants'; +import { ENDPOINT_DEFAULT_PAGE_SIZE } from '../../../../endpoint/constants'; +import { agentTypesSchema } from '../common/base'; const commandsSchema = schema.oneOf( // @ts-expect-error TS2769: No overload matches this call diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/execute/execute.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/execute/execute.gen.ts new file mode 100644 index 0000000000000..29c30de059fc7 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/execute/execute.gen.ts @@ -0,0 +1,43 @@ +/* + * 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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Execute Action Schema + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +import { + SuccessResponse, + BaseActionSchema, + Command, + Timeout, +} from '../../../model/schema/common.gen'; + +export type ExecuteRouteRequestBody = z.infer; +export const ExecuteRouteRequestBody = BaseActionSchema.merge( + z.object({ + parameters: z.object({ + command: Command, + timeout: Timeout.optional(), + }), + }) +); + +export type EndpointExecuteActionRequestBody = z.infer; +export const EndpointExecuteActionRequestBody = ExecuteRouteRequestBody; +export type EndpointExecuteActionRequestBodyInput = z.input< + typeof EndpointExecuteActionRequestBody +>; + +export type EndpointExecuteActionResponse = z.infer; +export const EndpointExecuteActionResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/execute.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/execute/execute.schema.yaml similarity index 54% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/execute.schema.yaml rename to x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/execute/execute.schema.yaml index ea9e7d7d98bf9..beafae76a4ba6 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/execute.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/execute/execute.schema.yaml @@ -7,29 +7,28 @@ paths: post: summary: Execute Action operationId: EndpointExecuteAction - x-codegen-enabled: false - x-labels: - - ess - - serverless + description: Execute a given command on an endpoint + x-codegen-enabled: true + x-labels: [ess, serverless] requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/ExecuteActionRequestBody' + $ref: '#/components/schemas/ExecuteRouteRequestBody' responses: '200': description: OK content: application/json: schema: - $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' + $ref: '../../../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' components: schemas: - ExecuteActionRequestBody: + ExecuteRouteRequestBody: allOf: - - $ref: '../model/schema/common.schema.yaml#/components/schemas/BaseActionSchema' + - $ref: '../../../model/schema/common.schema.yaml#/components/schemas/BaseActionSchema' - type: object required: - parameters @@ -40,6 +39,6 @@ components: type: object properties: command: - $ref: '../model/schema/common.schema.yaml#/components/schemas/Command' + $ref: '../../../model/schema/common.schema.yaml#/components/schemas/Command' timeout: - $ref: '../model/schema/common.schema.yaml#/components/schemas/Timeout' + $ref: '../../../model/schema/common.schema.yaml#/components/schemas/Timeout' diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/execute_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/execute/execute.ts similarity index 94% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/execute_route.ts rename to x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/execute/execute.ts index b8de2fc6874a0..f7f4394d66089 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/execute_route.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/execute/execute.ts @@ -7,7 +7,7 @@ import type { TypeOf } from '@kbn/config-schema'; import { schema } from '@kbn/config-schema'; -import { BaseActionRequestSchema } from './common/base'; +import { BaseActionRequestSchema } from '../../common/base'; export const ExecuteActionRequestSchema = { body: schema.object({ diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/execute/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/execute/index.ts new file mode 100644 index 0000000000000..be6732a4be40d --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/execute/index.ts @@ -0,0 +1,9 @@ +/* + * 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. + */ + +export * from './execute'; +export * from './execute.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/get_file/get_file.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/get_file/get_file.gen.ts new file mode 100644 index 0000000000000..e5c91527f5431 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/get_file/get_file.gen.ts @@ -0,0 +1,37 @@ +/* + * 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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Get File Schema + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +import { SuccessResponse, BaseActionSchema } from '../../../model/schema/common.gen'; + +export type GetFileRouteRequestBody = z.infer; +export const GetFileRouteRequestBody = BaseActionSchema.merge( + z.object({ + parameters: z.object({ + path: z.string(), + }), + }) +); + +export type EndpointGetFileActionRequestBody = z.infer; +export const EndpointGetFileActionRequestBody = GetFileRouteRequestBody; +export type EndpointGetFileActionRequestBodyInput = z.input< + typeof EndpointGetFileActionRequestBody +>; + +export type EndpointGetFileActionResponse = z.infer; +export const EndpointGetFileActionResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/get_file.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/get_file/get_file.schema.yaml similarity index 64% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/get_file.schema.yaml rename to x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/get_file/get_file.schema.yaml index d79c07556da82..a5211580d7e42 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/get_file.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/get_file/get_file.schema.yaml @@ -1,3 +1,4 @@ + openapi: 3.0.0 info: title: Get File Schema @@ -7,29 +8,28 @@ paths: post: summary: Get File Action operationId: EndpointGetFileAction - x-codegen-enabled: false - x-labels: - - ess - - serverless + description: Get a file from an endpoint + x-codegen-enabled: true + x-labels: [ess, serverless] requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/GetFileActionRequestBody' + $ref: '#/components/schemas/GetFileRouteRequestBody' responses: '200': description: OK content: application/json: schema: - $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' + $ref: '../../../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' components: schemas: - GetFileActionRequestBody: + GetFileRouteRequestBody: allOf: - - $ref: '../model/schema/common.schema.yaml#/components/schemas/BaseActionSchema' + - $ref: '../../../model/schema/common.schema.yaml#/components/schemas/BaseActionSchema' - type: object required: - parameters diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/get_file_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/get_file/get_file.ts similarity index 91% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/get_file_route.ts rename to x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/get_file/get_file.ts index 4378042f29403..0eb11cf538be5 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/get_file_route.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/get_file/get_file.ts @@ -7,7 +7,7 @@ import type { TypeOf } from '@kbn/config-schema'; import { schema } from '@kbn/config-schema'; -import { BaseActionRequestSchema } from './common/base'; +import { BaseActionRequestSchema } from '../../common/base'; export const EndpointActionGetFileSchema = { body: schema.object({ diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/get_file/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/get_file/index.ts new file mode 100644 index 0000000000000..b061771b5c3c2 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/get_file/index.ts @@ -0,0 +1,9 @@ +/* + * 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. + */ + +export * from './get_file'; +export * from './get_file.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/isolate_route.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/isolate/deprecated_isolate.gen.ts similarity index 91% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/isolate_route.gen.ts rename to x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/isolate/deprecated_isolate.gen.ts index 1318e25d827f8..4b179d47bea2a 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/isolate_route.gen.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/isolate/deprecated_isolate.gen.ts @@ -16,7 +16,7 @@ import type { z } from 'zod'; -import { BaseActionSchema, SuccessResponse } from '../model/schema/common.gen'; +import { BaseActionSchema, SuccessResponse } from '../../../model/schema/common.gen'; export type EndpointIsolateRedirectRequestBody = z.infer; export const EndpointIsolateRedirectRequestBody = BaseActionSchema; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/isolate_route.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/isolate/deprecated_isolate.schema.yaml similarity index 75% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/isolate_route.schema.yaml rename to x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/isolate/deprecated_isolate.schema.yaml index 4342dd85c7080..89d97c948c5d9 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/isolate_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/isolate/deprecated_isolate.schema.yaml @@ -7,15 +7,15 @@ paths: post: summary: Permanently redirects to a new location operationId: EndpointIsolateRedirect + deprecated: true x-codegen-enabled: true - x-labels: - - ess + x-labels: [ess] requestBody: required: true content: application/json: schema: - $ref: '../model/schema/common.schema.yaml#/components/schemas/BaseActionSchema' + $ref: '../../../model/schema/common.schema.yaml#/components/schemas/BaseActionSchema' responses: '308': description: Permanent Redirect @@ -30,4 +30,4 @@ paths: content: application/json: schema: - $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' + $ref: '../../../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/isolate/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/isolate/index.ts new file mode 100644 index 0000000000000..b4903efed2144 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/isolate/index.ts @@ -0,0 +1,9 @@ +/* + * 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. + */ + +export * from './isolate'; +export * from './isolate.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/isolate/isolate.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/isolate/isolate.gen.ts new file mode 100644 index 0000000000000..d3e9ee2f2588a --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/isolate/isolate.gen.ts @@ -0,0 +1,31 @@ +/* + * 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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Isolate Schema + * version: 2023-10-31 + */ + +import type { z } from 'zod'; + +import { SuccessResponse, NoParametersRequestSchema } from '../../../model/schema/common.gen'; + +export type IsolateRouteRequestBody = z.infer; +export const IsolateRouteRequestBody = NoParametersRequestSchema; + +export type EndpointIsolateActionRequestBody = z.infer; +export const EndpointIsolateActionRequestBody = IsolateRouteRequestBody; +export type EndpointIsolateActionRequestBodyInput = z.input< + typeof EndpointIsolateActionRequestBody +>; + +export type EndpointIsolateActionResponse = z.infer; +export const EndpointIsolateActionResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/isolate/isolate.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/isolate/isolate.schema.yaml new file mode 100644 index 0000000000000..f721c69efa570 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/isolate/isolate.schema.yaml @@ -0,0 +1,30 @@ +openapi: 3.0.0 +info: + title: Isolate Schema + version: '2023-10-31' +paths: + /api/endpoint/action/isolate: + post: + summary: Isolate Action + operationId: EndpointIsolateAction + description: Isolate an endpoint + x-codegen-enabled: true + x-labels: [ess, serverless] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/IsolateRouteRequestBody' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../../../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' + +components: + schemas: + IsolateRouteRequestBody: + $ref: '../../../model/schema/common.schema.yaml#/components/schemas/NoParametersRequestSchema' diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/isolate_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/isolate/isolate.ts similarity index 87% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/isolate_route.ts rename to x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/isolate/isolate.ts index 0df0d8d913457..787b2cb4362ec 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/isolate_route.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/isolate/isolate.ts @@ -6,7 +6,7 @@ */ import type { TypeOf } from '@kbn/config-schema'; -import { NoParametersRequestSchema } from './common/base'; +import { NoParametersRequestSchema } from '../../common/base'; export const IsolateRouteRequestSchema = NoParametersRequestSchema; export type IsolationRouteRequestBody = TypeOf; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/kill_process/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/kill_process/index.ts new file mode 100644 index 0000000000000..cd316877f542d --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/kill_process/index.ts @@ -0,0 +1,8 @@ +/* + * 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. + */ +export * from './kill_process'; +export * from './kill_process.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/kill_process/kill_process.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/kill_process/kill_process.gen.ts new file mode 100644 index 0000000000000..dc24dacf3e120 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/kill_process/kill_process.gen.ts @@ -0,0 +1,30 @@ +/* + * 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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Kill Process Schema + * version: 2023-10-31 + */ + +import type { z } from 'zod'; + +import { KillOrSuspendActionSchema, SuccessResponse } from '../../../model/schema/common.gen'; + +export type EndpointKillProcessActionRequestBody = z.infer< + typeof EndpointKillProcessActionRequestBody +>; +export const EndpointKillProcessActionRequestBody = KillOrSuspendActionSchema; +export type EndpointKillProcessActionRequestBodyInput = z.input< + typeof EndpointKillProcessActionRequestBody +>; + +export type EndpointKillProcessActionResponse = z.infer; +export const EndpointKillProcessActionResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/kill_process/kill_process.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/kill_process/kill_process.schema.yaml new file mode 100644 index 0000000000000..0014026664fe2 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/kill_process/kill_process.schema.yaml @@ -0,0 +1,25 @@ +openapi: 3.0.0 +info: + title: Kill Process Schema + version: '2023-10-31' +paths: + /api/endpoint/action/kill_process: + post: + summary: Kill process Action + operationId: EndpointKillProcessAction + description: Kill a running process on an endpoint + x-codegen-enabled: true + x-labels: [ess, serverless] + requestBody: + required: true + content: + application/json: + schema: + $ref: '../../../model/schema/common.schema.yaml#/components/schemas/KillOrSuspendActionSchema' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../../../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/kill_process_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/kill_process/kill_process.ts similarity index 88% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/kill_process_route.ts rename to x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/kill_process/kill_process.ts index f3c0d4e8f12be..0d42cb8badda1 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/kill_process_route.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/kill_process/kill_process.ts @@ -5,8 +5,9 @@ * 2.0. */ +import type { TypeOf } from '@kbn/config-schema'; import { schema } from '@kbn/config-schema'; -import { BaseActionRequestSchema } from './common/base'; +import { BaseActionRequestSchema } from '../../common/base'; // -------------------------------------------------- // Tests for this module are at: @@ -41,3 +42,5 @@ export const KillProcessRouteRequestSchema = { } ), }; + +export type KillProcessRequestBody = TypeOf; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/running_procs/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/running_procs/index.ts new file mode 100644 index 0000000000000..d44f9455c9807 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/running_procs/index.ts @@ -0,0 +1,9 @@ +/* + * 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. + */ + +export * from './running_procs'; +export * from './running_procs.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/running_procs/running_procs.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/running_procs/running_procs.gen.ts new file mode 100644 index 0000000000000..8050f78c1f72c --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/running_procs/running_procs.gen.ts @@ -0,0 +1,33 @@ +/* + * 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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Get Running Processes Schema + * version: 2023-10-31 + */ + +import type { z } from 'zod'; + +import { SuccessResponse, NoParametersRequestSchema } from '../../../model/schema/common.gen'; + +export type GetProcessesRouteRequestBody = z.infer; +export const GetProcessesRouteRequestBody = NoParametersRequestSchema; + +export type EndpointGetProcessesActionRequestBody = z.infer< + typeof EndpointGetProcessesActionRequestBody +>; +export const EndpointGetProcessesActionRequestBody = GetProcessesRouteRequestBody; +export type EndpointGetProcessesActionRequestBodyInput = z.input< + typeof EndpointGetProcessesActionRequestBody +>; + +export type EndpointGetProcessesActionResponse = z.infer; +export const EndpointGetProcessesActionResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/running_procs/running_procs.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/running_procs/running_procs.schema.yaml new file mode 100644 index 0000000000000..0d5ced3b205f4 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/running_procs/running_procs.schema.yaml @@ -0,0 +1,31 @@ +openapi: 3.0.0 +info: + title: Get Running Processes Schema + version: '2023-10-31' +paths: + /api/endpoint/action/running_procs: + post: + summary: Get Running Processes Action + operationId: EndpointGetProcessesAction + description: Get list of running processes on an endpoint + x-codegen-enabled: true + x-labels: [ess, serverless] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/GetProcessesRouteRequestBody' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../../../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' + +components: + schemas: + GetProcessesRouteRequestBody: + allOf: + - $ref: '../../../model/schema/common.schema.yaml#/components/schemas/NoParametersRequestSchema' diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/get_processes_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/running_procs/running_procs.ts similarity index 88% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/get_processes_route.ts rename to x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/running_procs/running_procs.ts index a9e56e52ba292..e9caec65e2aa3 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/get_processes_route.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/running_procs/running_procs.ts @@ -6,7 +6,7 @@ */ import type { TypeOf } from '@kbn/config-schema'; -import { NoParametersRequestSchema } from './common/base'; +import { NoParametersRequestSchema } from '../../common/base'; export const GetProcessesRouteRequestSchema = NoParametersRequestSchema; export type GetProcessesRequestBody = TypeOf; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/scan/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/scan/index.ts new file mode 100644 index 0000000000000..b7170f14f6d48 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/scan/index.ts @@ -0,0 +1,9 @@ +/* + * 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. + */ + +export * from './scan'; +export * from './scan.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/scan/scan.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/scan/scan.gen.ts new file mode 100644 index 0000000000000..302f5de95eaa0 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/scan/scan.gen.ts @@ -0,0 +1,35 @@ +/* + * 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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Scan Schema + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +import { SuccessResponse, BaseActionSchema } from '../../../model/schema/common.gen'; + +export type ScanRouteRequestBody = z.infer; +export const ScanRouteRequestBody = BaseActionSchema.merge( + z.object({ + parameters: z.object({ + path: z.string(), + }), + }) +); + +export type EndpointScanActionRequestBody = z.infer; +export const EndpointScanActionRequestBody = ScanRouteRequestBody; +export type EndpointScanActionRequestBodyInput = z.input; + +export type EndpointScanActionResponse = z.infer; +export const EndpointScanActionResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/scan.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/scan/scan.schema.yaml similarity index 64% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/scan.schema.yaml rename to x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/scan/scan.schema.yaml index ee7e9c1a9a4a7..beea986e4546c 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/scan.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/scan/scan.schema.yaml @@ -7,29 +7,28 @@ paths: post: summary: Scan Action operationId: EndpointScanAction - x-codegen-enabled: false - x-labels: - - ess - - serverless + description: Scan a file or directory + x-codegen-enabled: true + x-labels: [ess, serverless] requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/ScanActionRequestBody' + $ref: '#/components/schemas/ScanRouteRequestBody' responses: '200': description: OK content: application/json: schema: - $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' + $ref: '../../../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' components: schemas: - ScanActionRequestBody: + ScanRouteRequestBody: allOf: - - $ref: '../model/schema/common.schema.yaml#/components/schemas/BaseActionSchema' + - $ref: '../../../model/schema/common.schema.yaml#/components/schemas/BaseActionSchema' - type: object required: - parameters diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/scan_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/scan/scan.ts similarity index 92% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/scan_route.ts rename to x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/scan/scan.ts index 1c9b3c6980d90..3cbc04fdf4555 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/scan_route.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/scan/scan.ts @@ -7,7 +7,7 @@ import type { TypeOf } from '@kbn/config-schema'; import { schema } from '@kbn/config-schema'; -import { BaseActionRequestSchema } from './common/base'; +import { BaseActionRequestSchema } from '../../common/base'; export const ScanActionRequestSchema = { body: schema.object({ diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/suspend_process/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/suspend_process/index.ts new file mode 100644 index 0000000000000..ff4d08a07f23f --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/suspend_process/index.ts @@ -0,0 +1,9 @@ +/* + * 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. + */ + +export * from './suspend_process'; +export * from './suspend_process.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/suspend_process/suspend_process.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/suspend_process/suspend_process.gen.ts new file mode 100644 index 0000000000000..6c5fb52525628 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/suspend_process/suspend_process.gen.ts @@ -0,0 +1,32 @@ +/* + * 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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Suspend Process Schema + * version: 2023-10-31 + */ + +import type { z } from 'zod'; + +import { KillOrSuspendActionSchema, SuccessResponse } from '../../../model/schema/common.gen'; + +export type EndpointSuspendProcessActionRequestBody = z.infer< + typeof EndpointSuspendProcessActionRequestBody +>; +export const EndpointSuspendProcessActionRequestBody = KillOrSuspendActionSchema; +export type EndpointSuspendProcessActionRequestBodyInput = z.input< + typeof EndpointSuspendProcessActionRequestBody +>; + +export type EndpointSuspendProcessActionResponse = z.infer< + typeof EndpointSuspendProcessActionResponse +>; +export const EndpointSuspendProcessActionResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/suspend_process/suspend_process.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/suspend_process/suspend_process.schema.yaml new file mode 100644 index 0000000000000..f5f5b1e46ed2d --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/suspend_process/suspend_process.schema.yaml @@ -0,0 +1,25 @@ +openapi: 3.0.0 +info: + title: Suspend Process Schema + version: '2023-10-31' +paths: + /api/endpoint/action/suspend_process: + post: + summary: Suspend process Action + operationId: EndpointSuspendProcessAction + description: Suspend a running process on an endpoint + x-codegen-enabled: true + x-labels: [ess, serverless] + requestBody: + required: true + content: + application/json: + schema: + $ref: '../../../model/schema/common.schema.yaml#/components/schemas/KillOrSuspendActionSchema' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../../../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/suspend_process_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/suspend_process/suspend_process.ts similarity index 73% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/suspend_process_route.ts rename to x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/suspend_process/suspend_process.ts index 81edb01197c69..d7cbbdc2b21e6 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/suspend_process_route.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/suspend_process/suspend_process.ts @@ -5,8 +5,9 @@ * 2.0. */ +import type { TypeOf } from '@kbn/config-schema'; import { schema } from '@kbn/config-schema'; -import { BaseActionRequestSchema } from './common/base'; +import { BaseActionRequestSchema } from '../../common/base'; export const SuspendProcessRouteRequestSchema = { body: schema.object({ @@ -17,3 +18,5 @@ export const SuspendProcessRouteRequestSchema = { ]), }), }; + +export type SuspendProcessRequestBody = TypeOf; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/unisolate_route.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/unisolate/deprecated_unisolate.gen.ts similarity index 91% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/unisolate_route.gen.ts rename to x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/unisolate/deprecated_unisolate.gen.ts index d4c68a439c172..42ed374c24fce 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/unisolate_route.gen.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/unisolate/deprecated_unisolate.gen.ts @@ -16,7 +16,7 @@ import type { z } from 'zod'; -import { BaseActionSchema, SuccessResponse } from '../model/schema/common.gen'; +import { BaseActionSchema, SuccessResponse } from '../../../model/schema/common.gen'; export type EndpointUnisolateRedirectRequestBody = z.infer< typeof EndpointUnisolateRedirectRequestBody diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/unisolate_route.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/unisolate/deprecated_unisolate.schema.yaml similarity index 76% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/unisolate_route.schema.yaml rename to x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/unisolate/deprecated_unisolate.schema.yaml index 570854a054799..1d347f90fed44 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/unisolate_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/unisolate/deprecated_unisolate.schema.yaml @@ -7,15 +7,15 @@ paths: post: summary: Permanently redirects to a new location operationId: EndpointUnisolateRedirect + deprecated: true x-codegen-enabled: true - x-labels: - - ess + x-labels: [ess] requestBody: required: true content: application/json: schema: - $ref: '../model/schema/common.schema.yaml#/components/schemas/BaseActionSchema' + $ref: '../../../model/schema/common.schema.yaml#/components/schemas/BaseActionSchema' responses: '308': description: Permanent Redirect @@ -30,4 +30,4 @@ paths: content: application/json: schema: - $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' + $ref: '../../../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/unisolate/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/unisolate/index.ts new file mode 100644 index 0000000000000..46e542a8d1ef1 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/unisolate/index.ts @@ -0,0 +1,10 @@ +/* + * 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. + */ + +export * from './unisolate'; +export * from './unisolate.gen'; +export * from './deprecated_unisolate.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/unisolate/unisolate.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/unisolate/unisolate.gen.ts new file mode 100644 index 0000000000000..bf0fd2da1a605 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/unisolate/unisolate.gen.ts @@ -0,0 +1,31 @@ +/* + * 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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Unisolate Schema + * version: 2023-10-31 + */ + +import type { z } from 'zod'; + +import { SuccessResponse, NoParametersRequestSchema } from '../../../model/schema/common.gen'; + +export type UnisolateRouteRequestBody = z.infer; +export const UnisolateRouteRequestBody = NoParametersRequestSchema; + +export type EndpointUnisolateActionRequestBody = z.infer; +export const EndpointUnisolateActionRequestBody = UnisolateRouteRequestBody; +export type EndpointUnisolateActionRequestBodyInput = z.input< + typeof EndpointUnisolateActionRequestBody +>; + +export type EndpointUnisolateActionResponse = z.infer; +export const EndpointUnisolateActionResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/unisolate/unisolate.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/unisolate/unisolate.schema.yaml new file mode 100644 index 0000000000000..6c12a21f3241a --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/unisolate/unisolate.schema.yaml @@ -0,0 +1,30 @@ +openapi: 3.0.0 +info: + title: Unisolate Schema + version: '2023-10-31' +paths: + /api/endpoint/action/unisolate: + post: + summary: Unisolate Action + operationId: EndpointUnisolateAction + description: Release an endpoint + x-codegen-enabled: true + x-labels: [ess, serverless] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UnisolateRouteRequestBody' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../../../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' + +components: + schemas: + UnisolateRouteRequestBody: + $ref: '../../../model/schema/common.schema.yaml#/components/schemas/NoParametersRequestSchema' diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/unisolate/unisolate.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/unisolate/unisolate.ts new file mode 100644 index 0000000000000..84d63886bb8fa --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/unisolate/unisolate.ts @@ -0,0 +1,12 @@ +/* + * 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 { TypeOf } from '@kbn/config-schema'; +import { NoParametersRequestSchema } from '../../common/base'; + +export const UnisolateRouteRequestSchema = NoParametersRequestSchema; +export type UnisolationRouteRequestBody = TypeOf; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/upload/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/upload/index.ts new file mode 100644 index 0000000000000..34071a503e98f --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/upload/index.ts @@ -0,0 +1,9 @@ +/* + * 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. + */ + +export * from './upload'; +export * from './upload.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/upload/upload.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/upload/upload.gen.ts new file mode 100644 index 0000000000000..81a908d0b8728 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/upload/upload.gen.ts @@ -0,0 +1,36 @@ +/* + * 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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: File Upload Schema + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +import { SuccessResponse, BaseActionSchema } from '../../../model/schema/common.gen'; + +export type UploadRouteRequestBody = z.infer; +export const UploadRouteRequestBody = BaseActionSchema.merge( + z.object({ + parameters: z.object({ + overwrite: z.boolean().optional().default(false), + }), + file: z.string(), + }) +); + +export type EndpointUploadActionRequestBody = z.infer; +export const EndpointUploadActionRequestBody = UploadRouteRequestBody; +export type EndpointUploadActionRequestBodyInput = z.input; + +export type EndpointUploadActionResponse = z.infer; +export const EndpointUploadActionResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_upload.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/upload/upload.schema.yaml similarity index 68% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/file_upload.schema.yaml rename to x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/upload/upload.schema.yaml index fa9f0da1b1203..ff62065ae5403 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_upload.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/upload/upload.schema.yaml @@ -7,29 +7,28 @@ paths: post: summary: Upload Action operationId: EndpointUploadAction - x-codegen-enabled: false - x-labels: - - ess - - serverless + description: Upload a file to an endpoint + x-codegen-enabled: true + x-labels: [ess, serverless] requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/FileUploadActionRequestBody' + $ref: '#/components/schemas/UploadRouteRequestBody' responses: '200': description: OK content: application/json: schema: - $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' + $ref: '../../../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' components: schemas: - FileUploadActionRequestBody: + UploadRouteRequestBody: allOf: - - $ref: '../model/schema/common.schema.yaml#/components/schemas/BaseActionSchema' + - $ref: '../../../model/schema/common.schema.yaml#/components/schemas/BaseActionSchema' - type: object required: - parameters diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_upload_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/upload/upload.ts similarity index 94% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/file_upload_route.ts rename to x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/upload/upload.ts index b5850a63ca8e0..5bebdfa58c0b0 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_upload_route.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/upload/upload.ts @@ -7,7 +7,7 @@ import type { TypeOf } from '@kbn/config-schema'; import { schema } from '@kbn/config-schema'; -import { BaseActionRequestSchema } from './common/base'; +import { BaseActionRequestSchema } from '../../common/base'; export const UploadActionRequestSchema = { body: schema.object({ diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/scan.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/scan.gen.ts deleted file mode 100644 index 0aa58c33b8ebf..0000000000000 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/scan.gen.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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. - */ - -/* - * NOTICE: Do not edit this file manually. - * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. - * - * info: - * title: Scan Schema - * version: 2023-10-31 - */ - -import { z } from 'zod'; - -import { BaseActionSchema } from '../model/schema/common.gen'; - -export type ScanActionRequestBody = z.infer; -export const ScanActionRequestBody = BaseActionSchema.merge( - z.object({ - parameters: z.object({ - path: z.string(), - }), - }) -); diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/state/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/state/index.ts new file mode 100644 index 0000000000000..38db865a0e2c7 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/state/index.ts @@ -0,0 +1,8 @@ +/* + * 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. + */ + +export * from './state.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/details.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/state/state.gen.ts similarity index 59% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/details.gen.ts rename to x-pack/plugins/security_solution/common/api/endpoint/actions/state/state.gen.ts index dcceb64d44a6b..758835f484034 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/details.gen.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/state/state.gen.ts @@ -10,13 +10,13 @@ * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. * * info: - * title: Details Schema + * title: Endpoint Action State Schema * version: 2023-10-31 */ -import { z } from 'zod'; +import type { z } from 'zod'; -export type DetailsRequestParams = z.infer; -export const DetailsRequestParams = z.object({ - action_id: z.string().optional(), -}); +import { SuccessResponse } from '../../model/schema/common.gen'; + +export type EndpointGetActionsStateResponse = z.infer; +export const EndpointGetActionsStateResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/state/state.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/state/state.schema.yaml new file mode 100644 index 0000000000000..a8d9187107cbe --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/state/state.schema.yaml @@ -0,0 +1,19 @@ +openapi: 3.0.0 +info: + title: Endpoint Action State Schema + version: '2023-10-31' +paths: + /api/endpoint/action/state: + get: + summary: Get Action State schema + operationId: EndpointGetActionsState + x-codegen-enabled: true + x-labels: [ess, serverless] + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' + diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/status/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/status/index.ts new file mode 100644 index 0000000000000..bcbee2a39039b --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/status/index.ts @@ -0,0 +1,9 @@ +/* + * 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. + */ + +export * from './status'; +export * from './status.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/status/status.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/status/status.gen.ts new file mode 100644 index 0000000000000..69918d650e4a0 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/status/status.gen.ts @@ -0,0 +1,34 @@ +/* + * 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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Get Action status schema + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +import { AgentIds, SuccessResponse } from '../../model/schema/common.gen'; + +export type EndpointGetActionsStatusRequestQuery = z.infer< + typeof EndpointGetActionsStatusRequestQuery +>; +export const EndpointGetActionsStatusRequestQuery = z.object({ + query: z.object({ + agent_ids: AgentIds.optional(), + }), +}); +export type EndpointGetActionsStatusRequestQueryInput = z.input< + typeof EndpointGetActionsStatusRequestQuery +>; + +export type EndpointGetActionsStatusResponse = z.infer; +export const EndpointGetActionsStatusResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/actions_status.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/status/status.schema.yaml similarity index 64% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/actions_status.schema.yaml rename to x-pack/plugins/security_solution/common/api/endpoint/actions/status/status.schema.yaml index 92438f6d1a9fc..ec8e3d386bcd2 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/actions_status.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/status/status.schema.yaml @@ -7,10 +7,9 @@ paths: get: summary: Get Actions status schema operationId: EndpointGetActionsStatus - x-codegen-enabled: false - x-labels: - - ess - - serverless + description: Get action status + x-codegen-enabled: true + x-labels: [ess, serverless] parameters: - name: query in: query @@ -19,12 +18,12 @@ paths: type: object properties: agent_ids: - $ref: '../model/schema/common.schema.yaml#/components/schemas/AgentIds' + $ref: '../../model/schema/common.schema.yaml#/components/schemas/AgentIds' responses: '200': description: OK content: application/json: schema: - $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' + $ref: '../../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/action_status_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/status/status.ts similarity index 100% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/action_status_route.ts rename to x-pack/plugins/security_solution/common/api/endpoint/actions/status/status.ts diff --git a/x-pack/plugins/security_solution/common/api/endpoint/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/index.ts index 6dfbcd20d12fb..5917101be93a1 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/index.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/index.ts @@ -5,28 +5,28 @@ * 2.0. */ -export * from './actions/audit_log_route'; -export * from './actions/action_status_route'; -export * from './actions/details_route'; -export * from './actions/file_download_route'; -export * from './actions/file_info_route'; -export * from './actions/file_upload_route'; -export * from './actions/list_route'; -export * from './actions/isolate_route'; -export * from './actions/unisolate_route'; -export * from './actions/kill_process_route'; -export * from './actions/suspend_process_route'; -export * from './actions/get_processes_route'; -export * from './actions/get_file_route'; -export * from './actions/execute_route'; -export * from './actions/scan_route'; export * from './actions/common/base'; export * from './actions/common/response_actions'; -export * from './metadata/list_metadata_route'; -export * from './metadata/get_metadata_route'; +export * from './actions/action_log'; +export * from './actions/status'; +export * from './actions/details'; +export * from './actions/file_download'; +export * from './actions/file_info'; +export * from './actions/list'; -export * from './policy/get_policy_response_route'; -export * from './policy/get_agent_policy_summary_route'; +export * from './actions/response_actions/isolate'; +export * from './actions/response_actions/unisolate'; +export * from './actions/response_actions/kill_process'; +export * from './actions/response_actions/suspend_process'; +export * from './actions/response_actions/running_procs'; +export * from './actions/response_actions/get_file'; +export * from './actions/response_actions/execute'; +export * from './actions/response_actions/upload'; +export * from './actions/response_actions/scan'; -export * from './suggestions/get_suggestions_route'; +export * from './metadata'; + +export * from './policy'; + +export * from './suggestions'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/metadata/get_metadata.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/metadata/get_metadata.gen.ts new file mode 100644 index 0000000000000..a520b56c07195 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/metadata/get_metadata.gen.ts @@ -0,0 +1,33 @@ +/* + * 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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Endpoint Metadata Schema + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +import { ListRequestQuery } from './list_metadata.gen'; +import { SuccessResponse } from '../model/schema/common.gen'; + +export type GetEndpointMetadataListRequestQuery = z.infer< + typeof GetEndpointMetadataListRequestQuery +>; +export const GetEndpointMetadataListRequestQuery = z.object({ + query: ListRequestQuery, +}); +export type GetEndpointMetadataListRequestQueryInput = z.input< + typeof GetEndpointMetadataListRequestQuery +>; + +export type GetEndpointMetadataListResponse = z.infer; +export const GetEndpointMetadataListResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/metadata/metadata.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/metadata/get_metadata.schema.yaml similarity index 82% rename from x-pack/plugins/security_solution/common/api/endpoint/metadata/metadata.schema.yaml rename to x-pack/plugins/security_solution/common/api/endpoint/metadata/get_metadata.schema.yaml index a8af6101472d3..5ecea044053d2 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/metadata/metadata.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/endpoint/metadata/get_metadata.schema.yaml @@ -7,10 +7,8 @@ paths: get: summary: Get Metadata List schema operationId: GetEndpointMetadataList - x-codegen-enabled: false - x-labels: - - ess - - serverless + x-codegen-enabled: true + x-labels: [ess, serverless] parameters: - name: query in: query @@ -30,9 +28,7 @@ paths: summary: Get Metadata Transform schema operationId: GetEndpointMetadataTransform x-codegen-enabled: false - x-labels: - - ess - - serverless + x-labels: [ess, serverless] responses: '200': description: OK @@ -46,18 +42,13 @@ paths: summary: Get Metadata schema operationId: GetEndpointMetadata x-codegen-enabled: false - x-labels: - - ess - - serverless + x-labels: [ess, serverless] parameters: - - name: query + - name: id in: path required: true schema: - type: object - properties: - id: - type: string + type: string responses: '200': description: OK diff --git a/x-pack/plugins/security_solution/common/api/endpoint/metadata/get_metadata_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/metadata/get_metadata.ts similarity index 100% rename from x-pack/plugins/security_solution/common/api/endpoint/metadata/get_metadata_route.ts rename to x-pack/plugins/security_solution/common/api/endpoint/metadata/get_metadata.ts diff --git a/x-pack/plugins/security_solution/common/api/endpoint/metadata/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/metadata/index.ts new file mode 100644 index 0000000000000..d95135ef8aa90 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/metadata/index.ts @@ -0,0 +1,12 @@ +/* + * 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. + */ + +export * from './get_metadata'; +export * from './get_metadata.gen'; + +export * from './list_metadata'; +export * from './list_metadata.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/metadata/list_metadata_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/metadata/list_metadata.ts similarity index 100% rename from x-pack/plugins/security_solution/common/api/endpoint/metadata/list_metadata_route.ts rename to x-pack/plugins/security_solution/common/api/endpoint/metadata/list_metadata.ts diff --git a/x-pack/plugins/security_solution/common/api/endpoint/model/schema/common.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/model/schema/common.gen.ts index 4e3885391f241..57af5c334f4f0 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/model/schema/common.gen.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/model/schema/common.gen.ts @@ -70,6 +70,7 @@ export const Command = z.enum([ 'get-file', 'execute', 'upload', + 'scan', ]); export type CommandEnum = typeof Command.enum; export const CommandEnum = Command.enum; @@ -98,16 +99,22 @@ export type UserIds = z.infer; export const UserIds = z.union([z.array(z.string().min(1)).min(1), z.string().min(1)]); /** - * With Outputs + * Shows detailed outputs for an action response */ export type WithOutputs = z.infer; export const WithOutputs = z.union([z.array(z.string().min(1)).min(1), z.string().min(1)]); +/** + * Type of response action + */ export type Type = z.infer; export const Type = z.enum(['automated', 'manual']); export type TypeEnum = typeof Type.enum; export const TypeEnum = Type.enum; +/** + * List of types of response actions + */ export type Types = z.infer; export const Types = z.array(Type); @@ -135,17 +142,28 @@ export const Comment = z.string(); export type Parameters = z.infer; export const Parameters = z.object({}); +export type AgentTypes = z.infer; +export const AgentTypes = z.enum(['endpoint', 'sentinel_one', 'crowdstrike']); +export type AgentTypesEnum = typeof AgentTypes.enum; +export const AgentTypesEnum = AgentTypes.enum; + export type BaseActionSchema = z.infer; export const BaseActionSchema = z.object({ - endpoint_ids: EndpointIds.optional(), + endpoint_ids: EndpointIds, alert_ids: AlertIds.optional(), case_ids: CaseIds.optional(), comment: Comment.optional(), parameters: Parameters.optional(), + agent_type: AgentTypes.optional(), +}); + +export type NoParametersRequestSchema = z.infer; +export const NoParametersRequestSchema = z.object({ + body: BaseActionSchema, }); -export type ProcessActionSchemas = z.infer; -export const ProcessActionSchemas = BaseActionSchema.merge( +export type KillOrSuspendActionSchema = z.infer; +export const KillOrSuspendActionSchema = BaseActionSchema.merge( z.object({ parameters: z.union([ z.object({ diff --git a/x-pack/plugins/security_solution/common/api/endpoint/model/schema/common.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/model/schema/common.schema.yaml index 306bd31f4886b..00cf557b98496 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/model/schema/common.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/endpoint/model/schema/common.schema.yaml @@ -54,6 +54,7 @@ components: - get-file - execute - upload + - scan minLength: 1 description: The command to be executed (cannot be an empty string) @@ -101,16 +102,18 @@ components: minItems: 1 - type: string minLength: 1 - description: With Outputs + description: Shows detailed outputs for an action response Type: type: string + description: Type of response action enum: - automated - manual Types: type: array + description: List of types of response actions items: $ref: '#/components/schemas/Type' minLength: 1 @@ -136,6 +139,12 @@ components: Parameters: type: object description: Optional parameters object + AgentTypes: + type: string + enum: + - endpoint + - sentinel_one + - crowdstrike BaseActionSchema: x-inline: true @@ -151,8 +160,20 @@ components: $ref: '#/components/schemas/Comment' parameters: $ref: '#/components/schemas/Parameters' + agent_type: + $ref: '#/components/schemas/AgentTypes' + required: + - endpoint_ids + + NoParametersRequestSchema: + type: object + required: + - body + properties: + body: + $ref: '#/components/schemas/BaseActionSchema' - ProcessActionSchemas: + KillOrSuspendActionSchema: allOf: - $ref: '#/components/schemas/BaseActionSchema' - type: object diff --git a/x-pack/plugins/security_solution/common/api/endpoint/policy/policy.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/policy/deprecated_agent_policy_summary.gen.ts similarity index 66% rename from x-pack/plugins/security_solution/common/api/endpoint/policy/policy.gen.ts rename to x-pack/plugins/security_solution/common/api/endpoint/policy/deprecated_agent_policy_summary.gen.ts index 9cc37e874f6a2..9a20dcfc3b310 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/policy/policy.gen.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/policy/deprecated_agent_policy_summary.gen.ts @@ -16,7 +16,7 @@ import { z } from 'zod'; -import { SuccessResponse, AgentId } from '../model/schema/common.gen'; +import { SuccessResponse } from '../model/schema/common.gen'; export type GetAgentPolicySummaryRequestQuery = z.infer; export const GetAgentPolicySummaryRequestQuery = z.object({ @@ -31,13 +31,3 @@ export type GetAgentPolicySummaryRequestQueryInput = z.input< export type GetAgentPolicySummaryResponse = z.infer; export const GetAgentPolicySummaryResponse = SuccessResponse; -export type GetPolicyResponseRequestQuery = z.infer; -export const GetPolicyResponseRequestQuery = z.object({ - query: z.object({ - agentId: AgentId.optional(), - }), -}); -export type GetPolicyResponseRequestQueryInput = z.input; - -export type GetPolicyResponseResponse = z.infer; -export const GetPolicyResponseResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/policy/policy.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/policy/deprecated_agent_policy_summary.schema.yaml similarity index 50% rename from x-pack/plugins/security_solution/common/api/endpoint/policy/policy.schema.yaml rename to x-pack/plugins/security_solution/common/api/endpoint/policy/deprecated_agent_policy_summary.schema.yaml index 6084ae851bc17..803010a4e8268 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/policy/policy.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/endpoint/policy/deprecated_agent_policy_summary.schema.yaml @@ -7,10 +7,9 @@ paths: get: summary: Get Agent Policy Summary schema operationId: GetAgentPolicySummary + deprecated: true x-codegen-enabled: true - x-labels: - - ess - - serverless + x-labels: [ess, serverless] parameters: - name: query in: query @@ -31,28 +30,3 @@ paths: application/json: schema: $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' - - /api/endpoint/policy_response: - get: - summary: Get Policy Response schema - operationId: GetPolicyResponse - x-codegen-enabled: true - x-labels: - - ess - - serverless - parameters: - - name: query - in: query - required: true - schema: - type: object - properties: - agentId: - $ref: '../model/schema/common.schema.yaml#/components/schemas/AgentId' - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' diff --git a/x-pack/plugins/security_solution/common/api/endpoint/policy/get_agent_policy_summary_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/policy/deprecated_agent_policy_summary.ts similarity index 100% rename from x-pack/plugins/security_solution/common/api/endpoint/policy/get_agent_policy_summary_route.ts rename to x-pack/plugins/security_solution/common/api/endpoint/policy/deprecated_agent_policy_summary.ts diff --git a/x-pack/plugins/security_solution/common/api/endpoint/policy/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/policy/index.ts new file mode 100644 index 0000000000000..e27dc83a3b993 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/policy/index.ts @@ -0,0 +1,11 @@ +/* + * 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. + */ + +export * from './policy_response'; +export * from './deprecated_agent_policy_summary'; +export * from './policy_response.gen'; +export * from './deprecated_agent_policy_summary.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/policy/policy_response.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/policy/policy_response.gen.ts new file mode 100644 index 0000000000000..e2eb2595be662 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/policy/policy_response.gen.ts @@ -0,0 +1,30 @@ +/* + * 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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Endpoint Policy Schema + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +import { AgentId, SuccessResponse } from '../model/schema/common.gen'; + +export type GetPolicyResponseRequestQuery = z.infer; +export const GetPolicyResponseRequestQuery = z.object({ + query: z.object({ + agentId: AgentId.optional(), + }), +}); +export type GetPolicyResponseRequestQueryInput = z.input; + +export type GetPolicyResponseResponse = z.infer; +export const GetPolicyResponseResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/policy/policy_response.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/policy/policy_response.schema.yaml new file mode 100644 index 0000000000000..7acc39013da85 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/policy/policy_response.schema.yaml @@ -0,0 +1,27 @@ +openapi: 3.0.0 +info: + title: Endpoint Policy Schema + version: '2023-10-31' +paths: + /api/endpoint/policy_response: + get: + summary: Get Policy Response schema + operationId: GetPolicyResponse + x-codegen-enabled: true + x-labels: [ess, serverless] + parameters: + - name: query + in: query + required: true + schema: + type: object + properties: + agentId: + $ref: '../model/schema/common.schema.yaml#/components/schemas/AgentId' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' diff --git a/x-pack/plugins/security_solution/common/api/endpoint/policy/get_policy_response_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/policy/policy_response.ts similarity index 100% rename from x-pack/plugins/security_solution/common/api/endpoint/policy/get_policy_response_route.ts rename to x-pack/plugins/security_solution/common/api/endpoint/policy/policy_response.ts diff --git a/x-pack/plugins/security_solution/common/api/endpoint/protection_updates_note/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/protection_updates_note/index.ts new file mode 100644 index 0000000000000..c49eecd853e2c --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/protection_updates_note/index.ts @@ -0,0 +1,9 @@ +/* + * 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. + */ + +export * from './protection_updates_note'; +export * from './protection_updates_note.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/protection_updates_note/protection_updates_note.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/protection_updates_note/protection_updates_note.schema.yaml index ce2760780b627..44c02e417b185 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/protection_updates_note/protection_updates_note.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/endpoint/protection_updates_note/protection_updates_note.schema.yaml @@ -8,9 +8,7 @@ paths: summary: Get Protection Updates Note schema operationId: GetProtectionUpdatesNote x-codegen-enabled: true - x-labels: - - ess - - serverless + x-labels: [ess, serverless] parameters: - name: package_policy_id in: path @@ -28,9 +26,7 @@ paths: summary: Create Update Protection Updates Note schema operationId: CreateUpdateProtectionUpdatesNote x-codegen-enabled: true - x-labels: - - ess - - serverless + x-labels: [ess, serverless] requestBody: required: true content: diff --git a/x-pack/plugins/security_solution/common/api/endpoint/protection_updates_note/protection_updates_note_schema.ts b/x-pack/plugins/security_solution/common/api/endpoint/protection_updates_note/protection_updates_note.ts similarity index 100% rename from x-pack/plugins/security_solution/common/api/endpoint/protection_updates_note/protection_updates_note_schema.ts rename to x-pack/plugins/security_solution/common/api/endpoint/protection_updates_note/protection_updates_note.ts diff --git a/x-pack/plugins/security_solution/common/api/endpoint/suggestions/get_suggestions.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/suggestions/get_suggestions.schema.yaml index b7e1b0f34780e..573f9c0e3992f 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/suggestions/get_suggestions.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/endpoint/suggestions/get_suggestions.schema.yaml @@ -8,9 +8,7 @@ paths: summary: Get suggestions operationId: GetEndpointSuggestions x-codegen-enabled: true - x-labels: - - ess - - serverless + x-labels: [ess, serverless] requestBody: required: true content: diff --git a/x-pack/plugins/security_solution/common/api/endpoint/suggestions/get_suggestions_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/suggestions/get_suggestions.ts similarity index 100% rename from x-pack/plugins/security_solution/common/api/endpoint/suggestions/get_suggestions_route.ts rename to x-pack/plugins/security_solution/common/api/endpoint/suggestions/get_suggestions.ts diff --git a/x-pack/plugins/security_solution/common/api/endpoint/suggestions/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/suggestions/index.ts new file mode 100644 index 0000000000000..a4b3e85842ae9 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/suggestions/index.ts @@ -0,0 +1,9 @@ +/* + * 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. + */ + +export * from './get_suggestions'; +export * from './get_suggestions.gen'; diff --git a/x-pack/plugins/security_solution/common/endpoint/schema/actions.test.ts b/x-pack/plugins/security_solution/common/endpoint/schema/actions.test.ts index 4ea9f59ea179d..c7ce775bb2129 100644 --- a/x-pack/plugins/security_solution/common/endpoint/schema/actions.test.ts +++ b/x-pack/plugins/security_solution/common/endpoint/schema/actions.test.ts @@ -19,10 +19,10 @@ import { KillProcessRouteRequestSchema, SuspendProcessRouteRequestSchema, UploadActionRequestSchema, + ExecuteActionRequestSchema, + ScanActionRequestSchema, + NoParametersRequestSchema, } from '../../api/endpoint'; -import { NoParametersRequestSchema } from '../../api/endpoint/actions/common/base'; -import { ExecuteActionRequestSchema } from '../../api/endpoint/actions/execute_route'; -import { ScanActionRequestSchema } from '../../api/endpoint/actions/scan_route'; // NOTE: Even though schemas are kept in common/api/endpoint - we keep tests here, because common/api should import from outside describe('actions schemas', () => { diff --git a/x-pack/plugins/security_solution/common/endpoint/types/actions.ts b/x-pack/plugins/security_solution/common/endpoint/types/actions.ts index 47556a966280a..061182d5075ac 100644 --- a/x-pack/plugins/security_solution/common/endpoint/types/actions.ts +++ b/x-pack/plugins/security_solution/common/endpoint/types/actions.ts @@ -9,13 +9,12 @@ import type { TypeOf } from '@kbn/config-schema'; import type { EcsError } from '@elastic/ecs'; import type { BaseFileMetadata, FileCompression, FileJSON } from '@kbn/files-plugin/common'; import type { - KillProcessRouteRequestSchema, - ResponseActionBodySchema, - SuspendProcessRouteRequestSchema, UploadActionApiRequestBody, + ActionStatusRequestSchema, + KillProcessRequestBody, + SuspendProcessRequestBody, } from '../../api/endpoint'; -import type { ActionStatusRequestSchema } from '../../api/endpoint/actions/action_status_route'; -import type { NoParametersRequestSchema } from '../../api/endpoint/actions/common/base'; + import type { ResponseActionAgentType, ResponseActionsApiCommandNames, @@ -358,14 +357,6 @@ export interface ActivityLog { data: ActivityLogEntry[]; } -export type HostIsolationRequestBody = TypeOf; - -export type ResponseActionRequestBody = TypeOf; - -export type KillProcessRequestBody = TypeOf; - -export type SuspendProcessRequestBody = TypeOf; - /** Note: this type should almost never be used. Use instead the response action specific types above */ export type KillOrSuspendProcessRequestBody = KillProcessRequestBody & SuspendProcessRequestBody; @@ -373,8 +364,6 @@ export interface HostIsolationResponse { action: string; } -export type ProcessesRequestBody = TypeOf; - export interface ResponseActionApiResponse< TOutputContent extends EndpointActionResponseDataOutput = EndpointActionResponseDataOutput > { diff --git a/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_endpoint_management_api_2023_10_31.bundled.schema.yaml b/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_endpoint_management_api_2023_10_31.bundled.schema.yaml index 93e0b5298c4aa..f291b2db216c0 100644 --- a/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_endpoint_management_api_2023_10_31.bundled.schema.yaml +++ b/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_endpoint_management_api_2023_10_31.bundled.schema.yaml @@ -13,13 +13,14 @@ servers: paths: /api/endpoint/action: get: + description: Get a list of action requests and their responses operationId: EndpointGetActionsList parameters: - in: query name: query required: true schema: - $ref: '#/components/schemas/EndpointActionListRequestQuery' + $ref: '#/components/schemas/GetEndpointActionListRouteQuery' responses: '200': content: @@ -32,18 +33,20 @@ paths: - Security Solution Endpoint Management API '/api/endpoint/action_log/{agent_id}': get: - operationId: EndpointGetActionAuditLog + deprecated: true + description: Get action requests log + operationId: EndpointGetActionLog parameters: - - in: query - name: query + - in: path + name: agent_id required: true schema: - $ref: '#/components/schemas/AuditLogRequestQuery' - - in: path + $ref: '#/components/schemas/AgentId' + - in: query name: query required: true schema: - $ref: '#/components/schemas/AuditLogRequestParams' + $ref: '#/components/schemas/ActionLogRequestQuery' responses: '200': content: @@ -51,11 +54,12 @@ paths: schema: $ref: '#/components/schemas/SuccessResponse' description: OK - summary: Get action audit log schema + summary: Get action requests log schema tags: - Security Solution Endpoint Management API /api/endpoint/action_status: get: + description: Get action status operationId: EndpointGetActionsStatus parameters: - in: query @@ -78,13 +82,14 @@ paths: - Security Solution Endpoint Management API '/api/endpoint/action/{action_id}': get: + description: Get action details operationId: EndpointGetActionsDetails parameters: - in: path - name: query + name: action_id required: true schema: - $ref: '#/components/schemas/DetailsRequestParams' + type: string responses: '200': content: @@ -97,13 +102,19 @@ paths: - Security Solution Endpoint Management API '/api/endpoint/action/{action_id}/file/{file_id}/download`': get: + description: Download a file from an endpoint operationId: EndpointFileDownload parameters: - in: path - name: query + name: action_id required: true schema: - $ref: '#/components/schemas/FileDownloadRequestParams' + type: string + - in: path + name: file_id + required: true + schema: + type: string responses: '200': content: @@ -116,13 +127,19 @@ paths: - Security Solution Endpoint Management API '/api/endpoint/action/{action_id}/file/{file_id}`': get: + description: Get file info operationId: EndpointFileInfo parameters: - in: path - name: query + name: action_id required: true schema: - $ref: '#/components/schemas/FileInfoRequestParams' + type: string + - in: path + name: file_id + required: true + schema: + type: string responses: '200': content: @@ -135,12 +152,13 @@ paths: - Security Solution Endpoint Management API /api/endpoint/action/execute: post: + description: Execute a given command on an endpoint operationId: EndpointExecuteAction requestBody: content: application/json: schema: - $ref: '#/components/schemas/ExecuteActionRequestBody' + $ref: '#/components/schemas/ExecuteRouteRequestBody' required: true responses: '200': @@ -154,12 +172,13 @@ paths: - Security Solution Endpoint Management API /api/endpoint/action/get_file: post: + description: Get a file from an endpoint operationId: EndpointGetFileAction requestBody: content: application/json: schema: - $ref: '#/components/schemas/GetFileActionRequestBody' + $ref: '#/components/schemas/GetFileRouteRequestBody' required: true responses: '200': @@ -173,23 +192,13 @@ paths: - Security Solution Endpoint Management API /api/endpoint/action/isolate: post: - operationId: EndpointIsolateHostAction + description: Isolate an endpoint + operationId: EndpointIsolateAction requestBody: content: application/json: schema: - type: object - properties: - alert_ids: - $ref: '#/components/schemas/AlertIds' - case_ids: - $ref: '#/components/schemas/CaseIds' - comment: - $ref: '#/components/schemas/Comment' - endpoint_ids: - $ref: '#/components/schemas/EndpointIds' - parameters: - $ref: '#/components/schemas/Parameters' + $ref: '#/components/schemas/IsolateRouteRequestBody' required: true responses: '200': @@ -198,17 +207,18 @@ paths: schema: $ref: '#/components/schemas/SuccessResponse' description: OK - summary: Isolate host Action + summary: Isolate Action tags: - Security Solution Endpoint Management API /api/endpoint/action/kill_process: post: + description: Kill a running process on an endpoint operationId: EndpointKillProcessAction requestBody: content: application/json: schema: - $ref: '#/components/schemas/ProcessActionSchemas' + $ref: '#/components/schemas/KillOrSuspendActionSchema' required: true responses: '200': @@ -222,23 +232,13 @@ paths: - Security Solution Endpoint Management API /api/endpoint/action/running_procs: post: - operationId: EndpointGetRunningProcessesAction + description: Get list of running processes on an endpoint + operationId: EndpointGetProcessesAction requestBody: content: application/json: schema: - type: object - properties: - alert_ids: - $ref: '#/components/schemas/AlertIds' - case_ids: - $ref: '#/components/schemas/CaseIds' - comment: - $ref: '#/components/schemas/Comment' - endpoint_ids: - $ref: '#/components/schemas/EndpointIds' - parameters: - $ref: '#/components/schemas/Parameters' + $ref: '#/components/schemas/GetProcessesRouteRequestBody' required: true responses: '200': @@ -252,12 +252,13 @@ paths: - Security Solution Endpoint Management API /api/endpoint/action/scan: post: + description: Scan a file or directory operationId: EndpointScanAction requestBody: content: application/json: schema: - $ref: '#/components/schemas/ScanActionRequestBody' + $ref: '#/components/schemas/ScanRouteRequestBody' required: true responses: '200': @@ -284,12 +285,13 @@ paths: - Security Solution Endpoint Management API /api/endpoint/action/suspend_process: post: + description: Suspend a running process on an endpoint operationId: EndpointSuspendProcessAction requestBody: content: application/json: schema: - $ref: '#/components/schemas/ProcessActionSchemas' + $ref: '#/components/schemas/KillOrSuspendActionSchema' required: true responses: '200': @@ -303,23 +305,13 @@ paths: - Security Solution Endpoint Management API /api/endpoint/action/unisolate: post: - operationId: EndpointUnisolateHostAction + description: Release an endpoint + operationId: EndpointUnisolateAction requestBody: content: application/json: schema: - type: object - properties: - alert_ids: - $ref: '#/components/schemas/AlertIds' - case_ids: - $ref: '#/components/schemas/CaseIds' - comment: - $ref: '#/components/schemas/Comment' - endpoint_ids: - $ref: '#/components/schemas/EndpointIds' - parameters: - $ref: '#/components/schemas/Parameters' + $ref: '#/components/schemas/UnisolateRouteRequestBody' required: true responses: '200': @@ -328,17 +320,18 @@ paths: schema: $ref: '#/components/schemas/SuccessResponse' description: OK - summary: Unisolate host Action + summary: Unisolate Action tags: - Security Solution Endpoint Management API /api/endpoint/action/upload: post: + description: Upload a file to an endpoint operationId: EndpointUploadAction requestBody: content: application/json: schema: - $ref: '#/components/schemas/FileUploadActionRequestBody' + $ref: '#/components/schemas/UploadRouteRequestBody' required: true responses: '200': @@ -352,6 +345,7 @@ paths: - Security Solution Endpoint Management API /api/endpoint/isolate: post: + deprecated: true operationId: EndpointIsolateRedirect requestBody: content: @@ -359,6 +353,8 @@ paths: schema: type: object properties: + agent_type: + $ref: '#/components/schemas/AgentTypes' alert_ids: $ref: '#/components/schemas/AlertIds' case_ids: @@ -369,6 +365,8 @@ paths: $ref: '#/components/schemas/EndpointIds' parameters: $ref: '#/components/schemas/Parameters' + required: + - endpoint_ids required: true responses: '200': @@ -412,13 +410,10 @@ paths: operationId: GetEndpointMetadata parameters: - in: path - name: query + name: id required: true schema: - type: object - properties: - id: - type: string + type: string responses: '200': content: @@ -466,6 +461,7 @@ paths: - Security Solution Endpoint Management API /api/endpoint/policy/summaries: get: + deprecated: true operationId: GetAgentPolicySummary parameters: - in: query @@ -573,6 +569,7 @@ paths: - Security Solution Endpoint Management API /api/endpoint/unisolate: post: + deprecated: true operationId: EndpointUnisolateRedirect requestBody: content: @@ -580,6 +577,8 @@ paths: schema: type: object properties: + agent_type: + $ref: '#/components/schemas/AgentTypes' alert_ids: $ref: '#/components/schemas/AlertIds' case_ids: @@ -590,6 +589,8 @@ paths: $ref: '#/components/schemas/EndpointIds' parameters: $ref: '#/components/schemas/Parameters' + required: + - endpoint_ids required: true responses: '200': @@ -611,6 +612,17 @@ paths: - Security Solution Endpoint Management API components: schemas: + ActionLogRequestQuery: + type: object + properties: + end_date: + $ref: '#/components/schemas/EndDate' + page: + $ref: '#/components/schemas/Page' + page_size: + $ref: '#/components/schemas/PageSize' + start_date: + $ref: '#/components/schemas/StartDate' AgentId: description: Agent ID type: string @@ -625,28 +637,18 @@ components: type: array - minLength: 1 type: string + AgentTypes: + enum: + - endpoint + - sentinel_one + - crowdstrike + type: string AlertIds: description: A list of alerts ids. items: $ref: '#/components/schemas/NonEmptyString' minItems: 1 type: array - AuditLogRequestParams: - type: object - properties: - agent_id: - $ref: '#/components/schemas/AgentId' - AuditLogRequestQuery: - type: object - properties: - end_date: - $ref: '#/components/schemas/EndDate' - page: - $ref: '#/components/schemas/Page' - page_size: - $ref: '#/components/schemas/PageSize' - start_date: - $ref: '#/components/schemas/StartDate' CaseIds: description: Case IDs to be updated (cannot contain empty strings) items: @@ -665,6 +667,7 @@ components: - get-file - execute - upload + - scan minLength: 1 type: string Commands: @@ -674,39 +677,9 @@ components: Comment: description: Optional comment type: string - DetailsRequestParams: - type: object - properties: - action_id: - type: string EndDate: description: End date type: string - EndpointActionListRequestQuery: - type: object - properties: - agentIds: - $ref: '#/components/schemas/AgentIds' - commands: - $ref: '#/components/schemas/Commands' - endDate: - $ref: '#/components/schemas/EndDate' - page: - $ref: '#/components/schemas/Page' - pageSize: - default: 10 - description: Number of items per page - maximum: 10000 - minimum: 1 - type: integer - startDate: - $ref: '#/components/schemas/StartDate' - types: - $ref: '#/components/schemas/Types' - userIds: - $ref: '#/components/schemas/UserIds' - withOutputs: - $ref: '#/components/schemas/WithOutputs' EndpointIds: description: List of endpoint IDs (cannot contain empty strings) items: @@ -714,10 +687,12 @@ components: type: string minItems: 1 type: array - ExecuteActionRequestBody: + ExecuteRouteRequestBody: allOf: - type: object properties: + agent_type: + $ref: '#/components/schemas/AgentTypes' alert_ids: $ref: '#/components/schemas/AlertIds' case_ids: @@ -728,6 +703,8 @@ components: $ref: '#/components/schemas/EndpointIds' parameters: $ref: '#/components/schemas/Parameters' + required: + - endpoint_ids - type: object properties: parameters: @@ -741,30 +718,39 @@ components: - command required: - parameters - FileDownloadRequestParams: + GetEndpointActionListRouteQuery: type: object properties: - action_id: - type: string - file_id: - type: string - required: - - action_id - - file_id - FileInfoRequestParams: - type: object - properties: - action_id: - type: string - file_id: - type: string - required: - - action_id - - file_id - FileUploadActionRequestBody: + agentIds: + $ref: '#/components/schemas/AgentIds' + agentTypes: + $ref: '#/components/schemas/AgentTypes' + commands: + $ref: '#/components/schemas/Commands' + endDate: + $ref: '#/components/schemas/EndDate' + page: + $ref: '#/components/schemas/Page' + pageSize: + default: 10 + description: Number of items per page + maximum: 10000 + minimum: 1 + type: integer + startDate: + $ref: '#/components/schemas/StartDate' + types: + $ref: '#/components/schemas/Types' + userIds: + $ref: '#/components/schemas/UserIds' + withOutputs: + $ref: '#/components/schemas/WithOutputs' + GetFileRouteRequestBody: allOf: - type: object properties: + agent_type: + $ref: '#/components/schemas/AgentTypes' alert_ids: $ref: '#/components/schemas/AlertIds' case_ids: @@ -775,24 +761,29 @@ components: $ref: '#/components/schemas/EndpointIds' parameters: $ref: '#/components/schemas/Parameters' + required: + - endpoint_ids - type: object properties: - file: - format: binary - type: string parameters: type: object properties: - overwrite: - default: false - type: boolean + path: + type: string + required: + - path required: - parameters - - file - GetFileActionRequestBody: + GetProcessesRouteRequestBody: + $ref: '#/components/schemas/NoParametersRequestSchema' + IsolateRouteRequestBody: + $ref: '#/components/schemas/NoParametersRequestSchema' + KillOrSuspendActionSchema: allOf: - type: object properties: + agent_type: + $ref: '#/components/schemas/AgentTypes' alert_ids: $ref: '#/components/schemas/AlertIds' case_ids: @@ -803,15 +794,22 @@ components: $ref: '#/components/schemas/EndpointIds' parameters: $ref: '#/components/schemas/Parameters' + required: + - endpoint_ids - type: object properties: parameters: - type: object - properties: - path: - type: string - required: - - path + oneOf: + - type: object + properties: + pid: + minimum: 1 + type: integer + - type: object + properties: + entity_id: + minLength: 1 + type: string required: - parameters ListRequestQuery: @@ -866,6 +864,28 @@ components: minLength: 1 pattern: ^(?! *$).+$ type: string + NoParametersRequestSchema: + type: object + properties: + body: + type: object + properties: + agent_type: + $ref: '#/components/schemas/AgentTypes' + alert_ids: + $ref: '#/components/schemas/AlertIds' + case_ids: + $ref: '#/components/schemas/CaseIds' + comment: + $ref: '#/components/schemas/Comment' + endpoint_ids: + $ref: '#/components/schemas/EndpointIds' + parameters: + $ref: '#/components/schemas/Parameters' + required: + - endpoint_ids + required: + - body Page: default: 1 description: Page number @@ -880,45 +900,17 @@ components: Parameters: description: Optional parameters object type: object - ProcessActionSchemas: - allOf: - - type: object - properties: - alert_ids: - $ref: '#/components/schemas/AlertIds' - case_ids: - $ref: '#/components/schemas/CaseIds' - comment: - $ref: '#/components/schemas/Comment' - endpoint_ids: - $ref: '#/components/schemas/EndpointIds' - parameters: - $ref: '#/components/schemas/Parameters' - - type: object - properties: - parameters: - oneOf: - - type: object - properties: - pid: - minimum: 1 - type: integer - - type: object - properties: - entity_id: - minLength: 1 - type: string - required: - - parameters ProtectionUpdatesNoteResponse: type: object properties: note: type: string - ScanActionRequestBody: + ScanRouteRequestBody: allOf: - type: object properties: + agent_type: + $ref: '#/components/schemas/AgentTypes' alert_ids: $ref: '#/components/schemas/AlertIds' case_ids: @@ -929,6 +921,8 @@ components: $ref: '#/components/schemas/EndpointIds' parameters: $ref: '#/components/schemas/Parameters' + required: + - endpoint_ids - type: object properties: parameters: @@ -951,16 +945,52 @@ components: minimum: 1 type: integer Type: + description: Type of response action enum: - automated - manual type: string Types: + description: List of types of response actions items: $ref: '#/components/schemas/Type' maxLength: 2 minLength: 1 type: array + UnisolateRouteRequestBody: + $ref: '#/components/schemas/NoParametersRequestSchema' + UploadRouteRequestBody: + allOf: + - type: object + properties: + agent_type: + $ref: '#/components/schemas/AgentTypes' + alert_ids: + $ref: '#/components/schemas/AlertIds' + case_ids: + $ref: '#/components/schemas/CaseIds' + comment: + $ref: '#/components/schemas/Comment' + endpoint_ids: + $ref: '#/components/schemas/EndpointIds' + parameters: + $ref: '#/components/schemas/Parameters' + required: + - endpoint_ids + - type: object + properties: + file: + format: binary + type: string + parameters: + type: object + properties: + overwrite: + default: false + type: boolean + required: + - parameters + - file UserIds: description: User IDs oneOf: @@ -972,7 +1002,7 @@ components: - minLength: 1 type: string WithOutputs: - description: With Outputs + description: Shows detailed outputs for an action response oneOf: - items: minLength: 1 diff --git a/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_endpoint_management_api_2023_10_31.bundled.schema.yaml b/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_endpoint_management_api_2023_10_31.bundled.schema.yaml index 76bf0dede41b7..82af84f6d4253 100644 --- a/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_endpoint_management_api_2023_10_31.bundled.schema.yaml +++ b/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_endpoint_management_api_2023_10_31.bundled.schema.yaml @@ -13,13 +13,14 @@ servers: paths: /api/endpoint/action: get: + description: Get a list of action requests and their responses operationId: EndpointGetActionsList parameters: - in: query name: query required: true schema: - $ref: '#/components/schemas/EndpointActionListRequestQuery' + $ref: '#/components/schemas/GetEndpointActionListRouteQuery' responses: '200': content: @@ -32,18 +33,20 @@ paths: - Security Solution Endpoint Management API '/api/endpoint/action_log/{agent_id}': get: - operationId: EndpointGetActionAuditLog + deprecated: true + description: Get action requests log + operationId: EndpointGetActionLog parameters: - - in: query - name: query + - in: path + name: agent_id required: true schema: - $ref: '#/components/schemas/AuditLogRequestQuery' - - in: path + $ref: '#/components/schemas/AgentId' + - in: query name: query required: true schema: - $ref: '#/components/schemas/AuditLogRequestParams' + $ref: '#/components/schemas/ActionLogRequestQuery' responses: '200': content: @@ -51,11 +54,12 @@ paths: schema: $ref: '#/components/schemas/SuccessResponse' description: OK - summary: Get action audit log schema + summary: Get action requests log schema tags: - Security Solution Endpoint Management API /api/endpoint/action_status: get: + description: Get action status operationId: EndpointGetActionsStatus parameters: - in: query @@ -78,13 +82,14 @@ paths: - Security Solution Endpoint Management API '/api/endpoint/action/{action_id}': get: + description: Get action details operationId: EndpointGetActionsDetails parameters: - in: path - name: query + name: action_id required: true schema: - $ref: '#/components/schemas/DetailsRequestParams' + type: string responses: '200': content: @@ -97,13 +102,19 @@ paths: - Security Solution Endpoint Management API '/api/endpoint/action/{action_id}/file/{file_id}/download`': get: + description: Download a file from an endpoint operationId: EndpointFileDownload parameters: - in: path - name: query + name: action_id required: true schema: - $ref: '#/components/schemas/FileDownloadRequestParams' + type: string + - in: path + name: file_id + required: true + schema: + type: string responses: '200': content: @@ -116,13 +127,19 @@ paths: - Security Solution Endpoint Management API '/api/endpoint/action/{action_id}/file/{file_id}`': get: + description: Get file info operationId: EndpointFileInfo parameters: - in: path - name: query + name: action_id required: true schema: - $ref: '#/components/schemas/FileInfoRequestParams' + type: string + - in: path + name: file_id + required: true + schema: + type: string responses: '200': content: @@ -135,12 +152,13 @@ paths: - Security Solution Endpoint Management API /api/endpoint/action/execute: post: + description: Execute a given command on an endpoint operationId: EndpointExecuteAction requestBody: content: application/json: schema: - $ref: '#/components/schemas/ExecuteActionRequestBody' + $ref: '#/components/schemas/ExecuteRouteRequestBody' required: true responses: '200': @@ -154,12 +172,13 @@ paths: - Security Solution Endpoint Management API /api/endpoint/action/get_file: post: + description: Get a file from an endpoint operationId: EndpointGetFileAction requestBody: content: application/json: schema: - $ref: '#/components/schemas/GetFileActionRequestBody' + $ref: '#/components/schemas/GetFileRouteRequestBody' required: true responses: '200': @@ -173,23 +192,13 @@ paths: - Security Solution Endpoint Management API /api/endpoint/action/isolate: post: - operationId: EndpointIsolateHostAction + description: Isolate an endpoint + operationId: EndpointIsolateAction requestBody: content: application/json: schema: - type: object - properties: - alert_ids: - $ref: '#/components/schemas/AlertIds' - case_ids: - $ref: '#/components/schemas/CaseIds' - comment: - $ref: '#/components/schemas/Comment' - endpoint_ids: - $ref: '#/components/schemas/EndpointIds' - parameters: - $ref: '#/components/schemas/Parameters' + $ref: '#/components/schemas/IsolateRouteRequestBody' required: true responses: '200': @@ -198,17 +207,18 @@ paths: schema: $ref: '#/components/schemas/SuccessResponse' description: OK - summary: Isolate host Action + summary: Isolate Action tags: - Security Solution Endpoint Management API /api/endpoint/action/kill_process: post: + description: Kill a running process on an endpoint operationId: EndpointKillProcessAction requestBody: content: application/json: schema: - $ref: '#/components/schemas/ProcessActionSchemas' + $ref: '#/components/schemas/KillOrSuspendActionSchema' required: true responses: '200': @@ -222,23 +232,13 @@ paths: - Security Solution Endpoint Management API /api/endpoint/action/running_procs: post: - operationId: EndpointGetRunningProcessesAction + description: Get list of running processes on an endpoint + operationId: EndpointGetProcessesAction requestBody: content: application/json: schema: - type: object - properties: - alert_ids: - $ref: '#/components/schemas/AlertIds' - case_ids: - $ref: '#/components/schemas/CaseIds' - comment: - $ref: '#/components/schemas/Comment' - endpoint_ids: - $ref: '#/components/schemas/EndpointIds' - parameters: - $ref: '#/components/schemas/Parameters' + $ref: '#/components/schemas/GetProcessesRouteRequestBody' required: true responses: '200': @@ -252,12 +252,13 @@ paths: - Security Solution Endpoint Management API /api/endpoint/action/scan: post: + description: Scan a file or directory operationId: EndpointScanAction requestBody: content: application/json: schema: - $ref: '#/components/schemas/ScanActionRequestBody' + $ref: '#/components/schemas/ScanRouteRequestBody' required: true responses: '200': @@ -284,12 +285,13 @@ paths: - Security Solution Endpoint Management API /api/endpoint/action/suspend_process: post: + description: Suspend a running process on an endpoint operationId: EndpointSuspendProcessAction requestBody: content: application/json: schema: - $ref: '#/components/schemas/ProcessActionSchemas' + $ref: '#/components/schemas/KillOrSuspendActionSchema' required: true responses: '200': @@ -303,23 +305,13 @@ paths: - Security Solution Endpoint Management API /api/endpoint/action/unisolate: post: - operationId: EndpointUnisolateHostAction + description: Release an endpoint + operationId: EndpointUnisolateAction requestBody: content: application/json: schema: - type: object - properties: - alert_ids: - $ref: '#/components/schemas/AlertIds' - case_ids: - $ref: '#/components/schemas/CaseIds' - comment: - $ref: '#/components/schemas/Comment' - endpoint_ids: - $ref: '#/components/schemas/EndpointIds' - parameters: - $ref: '#/components/schemas/Parameters' + $ref: '#/components/schemas/UnisolateRouteRequestBody' required: true responses: '200': @@ -328,17 +320,18 @@ paths: schema: $ref: '#/components/schemas/SuccessResponse' description: OK - summary: Unisolate host Action + summary: Unisolate Action tags: - Security Solution Endpoint Management API /api/endpoint/action/upload: post: + description: Upload a file to an endpoint operationId: EndpointUploadAction requestBody: content: application/json: schema: - $ref: '#/components/schemas/FileUploadActionRequestBody' + $ref: '#/components/schemas/UploadRouteRequestBody' required: true responses: '200': @@ -374,13 +367,10 @@ paths: operationId: GetEndpointMetadata parameters: - in: path - name: query + name: id required: true schema: - type: object - properties: - id: - type: string + type: string responses: '200': content: @@ -428,6 +418,7 @@ paths: - Security Solution Endpoint Management API /api/endpoint/policy/summaries: get: + deprecated: true operationId: GetAgentPolicySummary parameters: - in: query @@ -535,6 +526,17 @@ paths: - Security Solution Endpoint Management API components: schemas: + ActionLogRequestQuery: + type: object + properties: + end_date: + $ref: '#/components/schemas/EndDate' + page: + $ref: '#/components/schemas/Page' + page_size: + $ref: '#/components/schemas/PageSize' + start_date: + $ref: '#/components/schemas/StartDate' AgentId: description: Agent ID type: string @@ -549,28 +551,18 @@ components: type: array - minLength: 1 type: string + AgentTypes: + enum: + - endpoint + - sentinel_one + - crowdstrike + type: string AlertIds: description: A list of alerts ids. items: $ref: '#/components/schemas/NonEmptyString' minItems: 1 type: array - AuditLogRequestParams: - type: object - properties: - agent_id: - $ref: '#/components/schemas/AgentId' - AuditLogRequestQuery: - type: object - properties: - end_date: - $ref: '#/components/schemas/EndDate' - page: - $ref: '#/components/schemas/Page' - page_size: - $ref: '#/components/schemas/PageSize' - start_date: - $ref: '#/components/schemas/StartDate' CaseIds: description: Case IDs to be updated (cannot contain empty strings) items: @@ -589,6 +581,7 @@ components: - get-file - execute - upload + - scan minLength: 1 type: string Commands: @@ -598,39 +591,9 @@ components: Comment: description: Optional comment type: string - DetailsRequestParams: - type: object - properties: - action_id: - type: string EndDate: description: End date type: string - EndpointActionListRequestQuery: - type: object - properties: - agentIds: - $ref: '#/components/schemas/AgentIds' - commands: - $ref: '#/components/schemas/Commands' - endDate: - $ref: '#/components/schemas/EndDate' - page: - $ref: '#/components/schemas/Page' - pageSize: - default: 10 - description: Number of items per page - maximum: 10000 - minimum: 1 - type: integer - startDate: - $ref: '#/components/schemas/StartDate' - types: - $ref: '#/components/schemas/Types' - userIds: - $ref: '#/components/schemas/UserIds' - withOutputs: - $ref: '#/components/schemas/WithOutputs' EndpointIds: description: List of endpoint IDs (cannot contain empty strings) items: @@ -638,10 +601,12 @@ components: type: string minItems: 1 type: array - ExecuteActionRequestBody: + ExecuteRouteRequestBody: allOf: - type: object properties: + agent_type: + $ref: '#/components/schemas/AgentTypes' alert_ids: $ref: '#/components/schemas/AlertIds' case_ids: @@ -652,6 +617,8 @@ components: $ref: '#/components/schemas/EndpointIds' parameters: $ref: '#/components/schemas/Parameters' + required: + - endpoint_ids - type: object properties: parameters: @@ -665,30 +632,39 @@ components: - command required: - parameters - FileDownloadRequestParams: + GetEndpointActionListRouteQuery: type: object properties: - action_id: - type: string - file_id: - type: string - required: - - action_id - - file_id - FileInfoRequestParams: - type: object - properties: - action_id: - type: string - file_id: - type: string - required: - - action_id - - file_id - FileUploadActionRequestBody: + agentIds: + $ref: '#/components/schemas/AgentIds' + agentTypes: + $ref: '#/components/schemas/AgentTypes' + commands: + $ref: '#/components/schemas/Commands' + endDate: + $ref: '#/components/schemas/EndDate' + page: + $ref: '#/components/schemas/Page' + pageSize: + default: 10 + description: Number of items per page + maximum: 10000 + minimum: 1 + type: integer + startDate: + $ref: '#/components/schemas/StartDate' + types: + $ref: '#/components/schemas/Types' + userIds: + $ref: '#/components/schemas/UserIds' + withOutputs: + $ref: '#/components/schemas/WithOutputs' + GetFileRouteRequestBody: allOf: - type: object properties: + agent_type: + $ref: '#/components/schemas/AgentTypes' alert_ids: $ref: '#/components/schemas/AlertIds' case_ids: @@ -699,24 +675,29 @@ components: $ref: '#/components/schemas/EndpointIds' parameters: $ref: '#/components/schemas/Parameters' + required: + - endpoint_ids - type: object properties: - file: - format: binary - type: string parameters: type: object properties: - overwrite: - default: false - type: boolean + path: + type: string + required: + - path required: - parameters - - file - GetFileActionRequestBody: + GetProcessesRouteRequestBody: + $ref: '#/components/schemas/NoParametersRequestSchema' + IsolateRouteRequestBody: + $ref: '#/components/schemas/NoParametersRequestSchema' + KillOrSuspendActionSchema: allOf: - type: object properties: + agent_type: + $ref: '#/components/schemas/AgentTypes' alert_ids: $ref: '#/components/schemas/AlertIds' case_ids: @@ -727,15 +708,22 @@ components: $ref: '#/components/schemas/EndpointIds' parameters: $ref: '#/components/schemas/Parameters' + required: + - endpoint_ids - type: object properties: parameters: - type: object - properties: - path: - type: string - required: - - path + oneOf: + - type: object + properties: + pid: + minimum: 1 + type: integer + - type: object + properties: + entity_id: + minLength: 1 + type: string required: - parameters ListRequestQuery: @@ -790,6 +778,28 @@ components: minLength: 1 pattern: ^(?! *$).+$ type: string + NoParametersRequestSchema: + type: object + properties: + body: + type: object + properties: + agent_type: + $ref: '#/components/schemas/AgentTypes' + alert_ids: + $ref: '#/components/schemas/AlertIds' + case_ids: + $ref: '#/components/schemas/CaseIds' + comment: + $ref: '#/components/schemas/Comment' + endpoint_ids: + $ref: '#/components/schemas/EndpointIds' + parameters: + $ref: '#/components/schemas/Parameters' + required: + - endpoint_ids + required: + - body Page: default: 1 description: Page number @@ -804,45 +814,17 @@ components: Parameters: description: Optional parameters object type: object - ProcessActionSchemas: - allOf: - - type: object - properties: - alert_ids: - $ref: '#/components/schemas/AlertIds' - case_ids: - $ref: '#/components/schemas/CaseIds' - comment: - $ref: '#/components/schemas/Comment' - endpoint_ids: - $ref: '#/components/schemas/EndpointIds' - parameters: - $ref: '#/components/schemas/Parameters' - - type: object - properties: - parameters: - oneOf: - - type: object - properties: - pid: - minimum: 1 - type: integer - - type: object - properties: - entity_id: - minLength: 1 - type: string - required: - - parameters ProtectionUpdatesNoteResponse: type: object properties: note: type: string - ScanActionRequestBody: + ScanRouteRequestBody: allOf: - type: object properties: + agent_type: + $ref: '#/components/schemas/AgentTypes' alert_ids: $ref: '#/components/schemas/AlertIds' case_ids: @@ -853,6 +835,8 @@ components: $ref: '#/components/schemas/EndpointIds' parameters: $ref: '#/components/schemas/Parameters' + required: + - endpoint_ids - type: object properties: parameters: @@ -875,16 +859,52 @@ components: minimum: 1 type: integer Type: + description: Type of response action enum: - automated - manual type: string Types: + description: List of types of response actions items: $ref: '#/components/schemas/Type' maxLength: 2 minLength: 1 type: array + UnisolateRouteRequestBody: + $ref: '#/components/schemas/NoParametersRequestSchema' + UploadRouteRequestBody: + allOf: + - type: object + properties: + agent_type: + $ref: '#/components/schemas/AgentTypes' + alert_ids: + $ref: '#/components/schemas/AlertIds' + case_ids: + $ref: '#/components/schemas/CaseIds' + comment: + $ref: '#/components/schemas/Comment' + endpoint_ids: + $ref: '#/components/schemas/EndpointIds' + parameters: + $ref: '#/components/schemas/Parameters' + required: + - endpoint_ids + - type: object + properties: + file: + format: binary + type: string + parameters: + type: object + properties: + overwrite: + default: false + type: boolean + required: + - parameters + - file UserIds: description: User IDs oneOf: @@ -896,7 +916,7 @@ components: - minLength: 1 type: string WithOutputs: - description: With Outputs + description: Shows detailed outputs for an action response oneOf: - items: minLength: 1 diff --git a/x-pack/plugins/security_solution/public/common/lib/endpoint/endpoint_isolation/index.ts b/x-pack/plugins/security_solution/public/common/lib/endpoint/endpoint_isolation/index.ts index dc119de848dec..678c0d63e4be6 100644 --- a/x-pack/plugins/security_solution/public/common/lib/endpoint/endpoint_isolation/index.ts +++ b/x-pack/plugins/security_solution/public/common/lib/endpoint/endpoint_isolation/index.ts @@ -6,9 +6,10 @@ */ import type { - HostIsolationRequestBody, - ResponseActionApiResponse, -} from '../../../../../common/endpoint/types'; + IsolationRouteRequestBody, + UnisolationRouteRequestBody, +} from '../../../../../common/api/endpoint'; +import type { ResponseActionApiResponse } from '../../../../../common/endpoint/types'; import { KibanaServices } from '../../kibana'; import { ISOLATE_HOST_ROUTE_V2, @@ -19,7 +20,7 @@ import { /** Isolates a Host running either elastic endpoint or fleet agent */ export const isolateHost = async ( - params: HostIsolationRequestBody + params: IsolationRouteRequestBody ): Promise => { return KibanaServices.get().http.post(ISOLATE_HOST_ROUTE_V2, { body: JSON.stringify(params), @@ -29,7 +30,7 @@ export const isolateHost = async ( /** Un-isolates a Host running either elastic endpoint or fleet agent */ export const unIsolateHost = async ( - params: HostIsolationRequestBody + params: UnisolationRouteRequestBody ): Promise => { return KibanaServices.get().http.post(UNISOLATE_HOST_ROUTE_V2, { body: JSON.stringify(params), diff --git a/x-pack/plugins/security_solution/public/common/lib/endpoint/endpoint_isolation/mocks.ts b/x-pack/plugins/security_solution/public/common/lib/endpoint/endpoint_isolation/mocks.ts index 4881fc3f1569f..bd65a1de60612 100644 --- a/x-pack/plugins/security_solution/public/common/lib/endpoint/endpoint_isolation/mocks.ts +++ b/x-pack/plugins/security_solution/public/common/lib/endpoint/endpoint_isolation/mocks.ts @@ -5,10 +5,8 @@ * 2.0. */ -import type { - HostIsolationRequestBody, - HostIsolationResponse, -} from '../../../../../common/endpoint/types'; +import type { IsolationRouteRequestBody } from '../../../../../common/api/endpoint'; +import type { HostIsolationResponse } from '../../../../../common/endpoint/types'; import type { ResponseProvidersInterface } from '../../../mock/endpoint/http_handler_mock_factory'; import { httpHandlerMockFactory } from '../../../mock/endpoint/http_handler_mock_factory'; import { @@ -16,7 +14,7 @@ import { UNISOLATE_HOST_ROUTE_V2, } from '../../../../../common/endpoint/constants'; -export const hostIsolationRequestBodyMock = (): HostIsolationRequestBody => { +export const hostIsolationRequestBodyMock = (): IsolationRouteRequestBody => { return { endpoint_ids: ['88c04a90-b19c-11eb-b838-222'], alert_ids: ['88c04a90-b19c-11eb-b838-333'], diff --git a/x-pack/plugins/security_solution/public/common/lib/process_actions/index.ts b/x-pack/plugins/security_solution/public/common/lib/process_actions/index.ts index b8cb7c04f469c..960ec21f41ef4 100644 --- a/x-pack/plugins/security_solution/public/common/lib/process_actions/index.ts +++ b/x-pack/plugins/security_solution/public/common/lib/process_actions/index.ts @@ -6,10 +6,10 @@ */ import type { - ResponseActionApiResponse, KillProcessRequestBody, SuspendProcessRequestBody, -} from '../../../../common/endpoint/types'; +} from '../../../../common/api/endpoint'; +import type { ResponseActionApiResponse } from '../../../../common/endpoint/types'; import { KibanaServices } from '../kibana'; import { KILL_PROCESS_ROUTE, SUSPEND_PROCESS_ROUTE } from '../../../../common/endpoint/constants'; diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/get_processes_action.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/get_processes_action.tsx index 54dce8a6e4add..5086e1ac1fada 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/get_processes_action.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/get_processes_action.tsx @@ -6,12 +6,10 @@ */ import React, { memo, useMemo } from 'react'; +import type { GetProcessesRequestBody } from '../../../../../common/api/endpoint'; import { RunningProcessesActionResults } from '../../running_processes_action_results'; import { useConsoleActionSubmitter } from '../hooks/use_console_action_submitter'; -import type { - GetProcessesActionOutputContent, - ProcessesRequestBody, -} from '../../../../../common/endpoint/types'; +import type { GetProcessesActionOutputContent } from '../../../../../common/endpoint/types'; import { useSendGetEndpointProcessesRequest } from '../../../hooks/response_actions/use_send_get_endpoint_processes_request'; import type { ActionRequestComponentProps } from '../types'; @@ -32,7 +30,7 @@ export const GetProcessesActionResult = memo( }, [endpointId, comment, agentType]); const { result, actionDetails: completedActionDetails } = useConsoleActionSubmitter< - ProcessesRequestBody, + GetProcessesRequestBody, GetProcessesActionOutputContent >({ ResultComponent, diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/kill_process_action.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/kill_process_action.tsx index abfc48b78c791..e96f1f0028b7d 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/kill_process_action.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/kill_process_action.tsx @@ -6,11 +6,11 @@ */ import { memo, useMemo } from 'react'; +import type { KillProcessRequestBody } from '../../../../../common/api/endpoint'; import { parsedKillOrSuspendParameter } from '../lib/utils'; import { useSendKillProcessRequest } from '../../../hooks/response_actions/use_send_kill_process_endpoint_request'; import type { ActionRequestComponentProps } from '../types'; import { useConsoleActionSubmitter } from '../hooks/use_console_action_submitter'; -import type { KillProcessRequestBody } from '../../../../../common/endpoint/types'; export const KillProcessActionResult = memo< ActionRequestComponentProps<{ pid?: string[]; entityId?: string[]; processName?: string[] }> diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/suspend_process_action.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/suspend_process_action.tsx index 344ebe5da626e..b2a73b426aa27 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/suspend_process_action.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/suspend_process_action.tsx @@ -6,10 +6,10 @@ */ import { memo, useMemo } from 'react'; +import type { SuspendProcessRequestBody } from '../../../../../common/api/endpoint'; import { parsedKillOrSuspendParameter } from '../lib/utils'; import type { SuspendProcessActionOutputContent, - SuspendProcessRequestBody, ResponseActionParametersWithEntityId, ResponseActionParametersWithPid, } from '../../../../../common/endpoint/types'; diff --git a/x-pack/plugins/security_solution/public/management/cypress/support/saml_authentication.ts b/x-pack/plugins/security_solution/public/management/cypress/support/saml_authentication.ts index a7085494ab6e8..526c430920ea6 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/support/saml_authentication.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/support/saml_authentication.ts @@ -10,6 +10,7 @@ import { ToolingLog } from '@kbn/tooling-log'; import type { HostOptions } from '@kbn/test'; import { SamlSessionManager } from '@kbn/test'; import type { SecurityRoleName } from '../../../../common/test'; +import { resolveCloudUsersFilePath } from '../../../../scripts/endpoint/common/roles_users/serverless'; export const samlAuthentication = async ( on: Cypress.PluginEvents, @@ -34,15 +35,15 @@ export const samlAuthentication = async ( role: string | SecurityRoleName ): Promise<{ cookie: string; username: string; password: string }> => { // If config.env.PROXY_ORG is set, it means that proxy service is used to create projects. Define the proxy org filename to override the roles. - const rolesFilename = config.env.PROXY_ORG ? `${config.env.PROXY_ORG}.json` : undefined; - const sessionManager = new SamlSessionManager( - { - hostOptions, - log, - isCloud: config.env.CLOUD_SERVERLESS, - }, - rolesFilename - ); + const rolesFilename = config.env.PROXY_ORG + ? `${config.env.PROXY_ORG}.json` + : 'role_users.json'; + const sessionManager = new SamlSessionManager({ + hostOptions, + log, + isCloud: config.env.CLOUD_SERVERLESS, + cloudUsersFilePath: resolveCloudUsersFilePath(rolesFilename), + }); return sessionManager.getInteractiveUserSessionCookieWithRoleScope(role).then((cookie) => { return { cookie, diff --git a/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_get_endpoint_processes_request.ts b/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_get_endpoint_processes_request.ts index cf946db04ca37..849380714cd94 100644 --- a/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_get_endpoint_processes_request.ts +++ b/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_get_endpoint_processes_request.ts @@ -8,8 +8,8 @@ import type { UseMutationOptions, UseMutationResult } from '@tanstack/react-query'; import { useMutation } from '@tanstack/react-query'; import type { IHttpFetchError } from '@kbn/core-http-browser'; +import type { GetProcessesRequestBody } from '../../../../common/api/endpoint'; import type { - ProcessesRequestBody, ResponseActionApiResponse, GetProcessesActionOutputContent, } from '../../../../common/endpoint/types/actions'; @@ -24,18 +24,18 @@ export const useSendGetEndpointProcessesRequest = ( customOptions?: UseMutationOptions< ResponseActionApiResponse, IHttpFetchError, - ProcessesRequestBody + GetProcessesRequestBody > ): UseMutationResult< ResponseActionApiResponse, IHttpFetchError, - ProcessesRequestBody + GetProcessesRequestBody > => { return useMutation< ResponseActionApiResponse, IHttpFetchError, - ProcessesRequestBody - >((getRunningProcessesData: ProcessesRequestBody) => { + GetProcessesRequestBody + >((getRunningProcessesData: GetProcessesRequestBody) => { return KibanaServices.get().http.post< ResponseActionApiResponse >(GET_PROCESSES_ROUTE, { diff --git a/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_isolate_endpoint_request.ts b/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_isolate_endpoint_request.ts index b4cb131f6ae9c..a9efa834e97e5 100644 --- a/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_isolate_endpoint_request.ts +++ b/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_isolate_endpoint_request.ts @@ -8,11 +8,9 @@ import type { UseMutationOptions, UseMutationResult } from '@tanstack/react-query'; import { useMutation } from '@tanstack/react-query'; import type { IHttpFetchError } from '@kbn/core-http-browser'; +import type { IsolationRouteRequestBody } from '../../../../common/api/endpoint'; import { isolateHost } from '../../../common/lib/endpoint/endpoint_isolation'; -import type { - HostIsolationRequestBody, - ResponseActionApiResponse, -} from '../../../../common/endpoint/types'; +import type { ResponseActionApiResponse } from '../../../../common/endpoint/types'; /** * Create host isolation requests @@ -22,11 +20,11 @@ export const useSendIsolateEndpointRequest = ( customOptions?: UseMutationOptions< ResponseActionApiResponse, IHttpFetchError, - HostIsolationRequestBody + IsolationRouteRequestBody > -): UseMutationResult => { - return useMutation( - (isolateData: HostIsolationRequestBody) => { +): UseMutationResult => { + return useMutation( + (isolateData: IsolationRouteRequestBody) => { return isolateHost(isolateData); }, customOptions diff --git a/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_kill_process_endpoint_request.ts b/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_kill_process_endpoint_request.ts index b17d34ab4e463..1dd2b1ae2a8c7 100644 --- a/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_kill_process_endpoint_request.ts +++ b/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_kill_process_endpoint_request.ts @@ -8,10 +8,8 @@ import type { UseMutationOptions, UseMutationResult } from '@tanstack/react-query'; import { useMutation } from '@tanstack/react-query'; import type { IHttpFetchError } from '@kbn/core-http-browser'; -import type { - ResponseActionApiResponse, - KillProcessRequestBody, -} from '../../../../common/endpoint/types'; +import type { KillProcessRequestBody } from '../../../../common/api/endpoint'; +import type { ResponseActionApiResponse } from '../../../../common/endpoint/types'; import { killProcess } from '../../../common/lib/process_actions'; /** diff --git a/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_release_endpoint_request.ts b/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_release_endpoint_request.ts index b9e140dc3ba9d..ed0eaf68f90d4 100644 --- a/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_release_endpoint_request.ts +++ b/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_release_endpoint_request.ts @@ -8,10 +8,8 @@ import type { UseMutationOptions, UseMutationResult } from '@tanstack/react-query'; import { useMutation } from '@tanstack/react-query'; import type { IHttpFetchError } from '@kbn/core-http-browser'; -import type { - HostIsolationRequestBody, - ResponseActionApiResponse, -} from '../../../../common/endpoint/types'; +import type { UnisolationRouteRequestBody } from '../../../../common/api/endpoint'; +import type { ResponseActionApiResponse } from '../../../../common/endpoint/types'; import { unIsolateHost } from '../../../common/lib/endpoint/endpoint_isolation'; /** @@ -22,11 +20,11 @@ export const useSendReleaseEndpointRequest = ( customOptions?: UseMutationOptions< ResponseActionApiResponse, IHttpFetchError, - HostIsolationRequestBody + UnisolationRouteRequestBody > -): UseMutationResult => { - return useMutation( - (releaseData: HostIsolationRequestBody) => { +): UseMutationResult => { + return useMutation( + (releaseData: UnisolationRouteRequestBody) => { return unIsolateHost(releaseData); }, customOptions diff --git a/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_suspend_process_endpoint_request.ts b/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_suspend_process_endpoint_request.ts index 787344ffd54dc..f2e467e36073e 100644 --- a/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_suspend_process_endpoint_request.ts +++ b/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_suspend_process_endpoint_request.ts @@ -8,10 +8,8 @@ import { useMutation } from '@tanstack/react-query'; import type { UseMutationOptions, UseMutationResult } from '@tanstack/react-query'; import type { IHttpFetchError } from '@kbn/core-http-browser'; -import type { - ResponseActionApiResponse, - SuspendProcessRequestBody, -} from '../../../../common/endpoint/types'; +import type { SuspendProcessRequestBody } from '../../../../common/api/endpoint'; +import type { ResponseActionApiResponse } from '../../../../common/endpoint/types'; import { suspendProcess } from '../../../common/lib/process_actions'; /** diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts index 01b9b3455dea9..43db8f92929e8 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts @@ -7,9 +7,9 @@ import type { Action } from 'redux'; import type { DataViewBase } from '@kbn/es-query'; +import type { IsolationRouteRequestBody } from '../../../../../common/api/endpoint'; import type { GetHostPolicyResponse, - HostIsolationRequestBody, ISOLATION_ACTIONS, MetadataListResponse, } from '../../../../../common/endpoint/types'; @@ -133,7 +133,7 @@ export interface ServerFailedToReturnEndpointsTotal { export type EndpointIsolationRequest = Action<'endpointIsolationRequest'> & { payload: { type: ISOLATION_ACTIONS; - data: HostIsolationRequestBody; + data: IsolationRouteRequestBody; }; }; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts index fce262052220e..ec27500a45e12 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts @@ -14,6 +14,10 @@ import type { IndexFieldsStrategyRequest, IndexFieldsStrategyResponse, } from '@kbn/timelines-plugin/common'; +import type { + IsolationRouteRequestBody, + UnisolationRouteRequestBody, +} from '../../../../../common/api/endpoint'; import { ENDPOINT_FIELDS_SEARCH_STRATEGY, HOST_METADATA_LIST_ROUTE, @@ -22,7 +26,6 @@ import { metadataCurrentIndexPattern, } from '../../../../../common/endpoint/constants'; import type { - HostIsolationRequestBody, HostResultList, Immutable, ImmutableObject, @@ -246,9 +249,9 @@ const handleIsolateEndpointHost = async ( let response: ResponseActionApiResponse; if (action.payload.type === 'unisolate') { - response = await unIsolateHost(action.payload.data as HostIsolationRequestBody); + response = await unIsolateHost(action.payload.data as UnisolationRouteRequestBody); } else { - response = await isolateHost(action.payload.data as HostIsolationRequestBody); + response = await isolateHost(action.payload.data as IsolationRouteRequestBody); } dispatch({ diff --git a/x-pack/plugins/security_solution/scripts/endpoint/common/roles_users/serverless/index.ts b/x-pack/plugins/security_solution/scripts/endpoint/common/roles_users/serverless/index.ts index 23a44df2d0808..f743f4e3db10c 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/common/roles_users/serverless/index.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/common/roles_users/serverless/index.ts @@ -7,6 +7,7 @@ import { resolve, join } from 'path'; import { readFileSync } from 'fs'; +import { REPO_ROOT } from '@kbn/repo-info'; const ES_RESOURCES_DIR = resolve(__dirname, 'es_serverless_resources'); @@ -16,6 +17,8 @@ export const ES_RESOURCES = Object.freeze({ users_roles: join(ES_RESOURCES_DIR, 'users_roles'), }); +export const resolveCloudUsersFilePath = (filename: string) => resolve(REPO_ROOT, '.ftr', filename); + export const ES_LOADED_USERS = readFileSync(ES_RESOURCES.users) .toString() .split(/\n/) diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.test.ts index 47099ae060efa..2fdfa5143cfe2 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.test.ts @@ -42,7 +42,6 @@ import type { HostMetadata, LogsEndpointAction, ResponseActionApiResponse, - ResponseActionRequestBody, } from '../../../../common/endpoint/types'; import { EndpointDocGenerator } from '../../../../common/endpoint/generate_data'; import type { EndpointAuthz } from '../../../../common/endpoint/types/authz'; @@ -63,7 +62,10 @@ import * as ActionDetailsService from '../../services/actions/action_details_by_ import { CaseStatuses } from '@kbn/cases-components'; import { getEndpointAuthzInitialStateMock } from '../../../../common/endpoint/service/authz/mocks'; import { getResponseActionsClient as _getResponseActionsClient } from '../../services'; -import type { UploadActionApiRequestBody } from '../../../../common/api/endpoint'; +import type { + ResponseActionsRequestBody, + UploadActionApiRequestBody, +} from '../../../../common/api/endpoint'; import type { FleetToHostFileClientInterface } from '@kbn/fleet-plugin/server'; import type { HapiReadableStream, SecuritySolutionRequestHandlerContext } from '../../../types'; import { createHapiReadableStreamMock } from '../../services/actions/mocks'; @@ -92,7 +94,7 @@ jest.mock('../../services', () => { const getResponseActionsClientMock = _getResponseActionsClient; interface CallRouteInterface { - body?: ResponseActionRequestBody; + body?: ResponseActionsRequestBody; indexErrorResponse?: any; searchResponse?: HostMetadata; mockUser?: any; diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.ts index 723daf576c38e..9f40852fec380 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.ts @@ -13,6 +13,10 @@ import { stringify } from '../../utils/stringify'; import { getResponseActionsClient, NormalizedExternalConnectorClient } from '../../services'; import type { ResponseActionsClient } from '../../services/actions/clients/lib/types'; import { CustomHttpRequestError } from '../../../utils/custom_http_request_error'; +import type { + KillProcessRequestBody, + SuspendProcessRequestBody, +} from '../../../../common/api/endpoint'; import { EndpointActionGetFileSchema, type ExecuteActionRequestBody, @@ -50,8 +54,6 @@ import type { ResponseActionParametersWithProcessData, ResponseActionsExecuteParameters, ResponseActionScanParameters, - KillProcessRequestBody, - SuspendProcessRequestBody, } from '../../../../common/endpoint/types'; import type { ResponseActionsApiCommandNames } from '../../../../common/endpoint/service/response_actions/constants'; import type { diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/protection_updates_note/handlers.ts b/x-pack/plugins/security_solution/server/endpoint/routes/protection_updates_note/handlers.ts index e1677451ff577..79eb38211387e 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/protection_updates_note/handlers.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/protection_updates_note/handlers.ts @@ -16,7 +16,7 @@ import { protectionUpdatesNoteSavedObjectType } from '../../lib/protection_updat import type { CreateUpdateProtectionUpdatesNoteSchema, GetProtectionUpdatesNoteSchema, -} from '../../../../common/api/endpoint/protection_updates_note/protection_updates_note_schema'; +} from '../../../../common/api/endpoint/protection_updates_note'; const getProtectionNote = async (SOClient: SavedObjectsClientContract, packagePolicyId: string) => { return SOClient.find<{ note: string }>({ diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/protection_updates_note/index.ts b/x-pack/plugins/security_solution/server/endpoint/routes/protection_updates_note/index.ts index 4d398bbe14e6e..7b28ccfcf9fe7 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/protection_updates_note/index.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/protection_updates_note/index.ts @@ -10,7 +10,7 @@ import { getProtectionUpdatesNoteHandler, postProtectionUpdatesNoteHandler } fro import { GetProtectionUpdatesNoteSchema, CreateUpdateProtectionUpdatesNoteSchema, -} from '../../../../common/api/endpoint/protection_updates_note/protection_updates_note_schema'; +} from '../../../../common/api/endpoint/protection_updates_note'; import { withEndpointAuthz } from '../with_endpoint_authz'; import { PROTECTION_UPDATES_NOTE_ROUTE } from '../../../../common/endpoint/constants'; import type { EndpointAppContext } from '../../types'; diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/crowdstrike/crowdstrike_actions_client.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/crowdstrike/crowdstrike_actions_client.ts index 958c51014c6a0..b39c85726651f 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/crowdstrike/crowdstrike_actions_client.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/crowdstrike/crowdstrike_actions_client.ts @@ -27,7 +27,10 @@ import type { EndpointActionResponseDataOutput, LogsEndpointAction, } from '../../../../../../common/endpoint/types'; -import type { IsolationRouteRequestBody } from '../../../../../../common/api/endpoint'; +import type { + IsolationRouteRequestBody, + UnisolationRouteRequestBody, +} from '../../../../../../common/api/endpoint'; import type { ResponseActionsClientOptions, ResponseActionsClientWriteActionRequestToEndpointIndexOptions, @@ -238,7 +241,7 @@ export class CrowdstrikeActionsClient extends ResponseActionsClientImpl { } async release( - actionRequest: IsolationRouteRequestBody, + actionRequest: UnisolationRouteRequestBody, options: CommonResponseActionMethodOptions = {} ): Promise { const reqIndexOptions: ResponseActionsClientWriteActionRequestToEndpointIndexOptions = { diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/endpoint/endpoint_actions_client.test.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/endpoint/endpoint_actions_client.test.ts index eaaa5fa259927..a406397c2be19 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/endpoint/endpoint_actions_client.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/endpoint/endpoint_actions_client.test.ts @@ -11,20 +11,21 @@ import { EndpointActionsClient } from '../../..'; import { endpointActionClientMock } from './mocks'; import { responseActionsClientMock } from '../mocks'; import { ENDPOINT_ACTIONS_INDEX } from '../../../../../../common/endpoint/constants'; -import type { ResponseActionRequestBody } from '../../../../../../common/endpoint/types'; + import { DEFAULT_EXECUTE_ACTION_TIMEOUT } from '../../../../../../common/endpoint/service/response_actions/constants'; import { applyEsClientSearchMock } from '../../../../mocks/utils.mock'; import type { ElasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; import { BaseDataGenerator } from '../../../../../../common/endpoint/data_generators/base_data_generator'; import { Readable } from 'stream'; import { EndpointActionGenerator } from '../../../../../../common/endpoint/data_generators/endpoint_action_generator'; +import type { ResponseActionsRequestBody } from '../../../../../../common/api/endpoint'; describe('EndpointActionsClient', () => { let classConstructorOptions: ResponseActionsClientOptions; let endpointActionsClient: ResponseActionsClient; const getCommonResponseActionOptions = (): Pick< - ResponseActionRequestBody, + ResponseActionsRequestBody, 'endpoint_ids' | 'case_ids' > => { return { diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/endpoint/endpoint_actions_client.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/endpoint/endpoint_actions_client.ts index 59328beb46c12..df2b3c323ee08 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/endpoint/endpoint_actions_client.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/endpoint/endpoint_actions_client.ts @@ -24,6 +24,9 @@ import type { UploadActionApiRequestBody, ResponseActionsRequestBody, ScanActionRequestBody, + SuspendProcessRequestBody, + KillProcessRequestBody, + UnisolationRouteRequestBody, } from '../../../../../../common/api/endpoint'; import { ResponseActionsClientImpl } from '../lib/base_response_actions_client'; import type { @@ -44,8 +47,6 @@ import type { UploadedFileInfo, ResponseActionScanParameters, ResponseActionScanOutputContent, - KillProcessRequestBody, - SuspendProcessRequestBody, } from '../../../../../../common/endpoint/types'; import type { CommonResponseActionMethodOptions, @@ -236,10 +237,10 @@ export class EndpointActionsClient extends ResponseActionsClientImpl { } async release( - actionRequest: IsolationRouteRequestBody, + actionRequest: UnisolationRouteRequestBody, options: CommonResponseActionMethodOptions = {} ): Promise { - return this.handleResponseAction( + return this.handleResponseAction( 'unisolate', actionRequest, options diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/lib/base_response_actions_client.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/lib/base_response_actions_client.ts index 67aec8d861ce4..07ab63b77a312 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/lib/base_response_actions_client.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/lib/base_response_actions_client.ts @@ -66,16 +66,17 @@ import type { SuspendProcessActionOutputContent, UploadedFileInfo, WithAllKeys, - KillProcessRequestBody, - SuspendProcessRequestBody, } from '../../../../../../common/endpoint/types'; import type { ExecuteActionRequestBody, GetProcessesRequestBody, IsolationRouteRequestBody, + KillProcessRequestBody, ResponseActionGetFileRequestBody, ResponseActionsRequestBody, ScanActionRequestBody, + SuspendProcessRequestBody, + UnisolationRouteRequestBody, UploadActionApiRequestBody, } from '../../../../../../common/api/endpoint'; import { stringify } from '../../../../utils/stringify'; @@ -716,7 +717,7 @@ export abstract class ResponseActionsClientImpl implements ResponseActionsClient } public async release( - actionRequest: IsolationRouteRequestBody, + actionRequest: UnisolationRouteRequestBody, options?: CommonResponseActionMethodOptions ): Promise { throw new ResponseActionsNotSupportedError('unisolate'); diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/lib/types.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/lib/types.ts index 4a7b7efd4d4a5..e3407b5ba959a 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/lib/types.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/lib/types.ts @@ -23,17 +23,18 @@ import type { UploadedFileInfo, ResponseActionScanOutputContent, ResponseActionScanParameters, - KillProcessRequestBody, - SuspendProcessRequestBody, } from '../../../../../../common/endpoint/types'; import type { IsolationRouteRequestBody, + UnisolationRouteRequestBody, GetProcessesRequestBody, ResponseActionGetFileRequestBody, ExecuteActionRequestBody, UploadActionApiRequestBody, BaseActionRequestBody, ScanActionRequestBody, + KillProcessRequestBody, + SuspendProcessRequestBody, } from '../../../../../../common/api/endpoint'; type OmitUnsupportedAttributes = Omit< @@ -84,7 +85,7 @@ export interface ResponseActionsClient { ) => Promise; release: ( - actionRequest: OmitUnsupportedAttributes, + actionRequest: OmitUnsupportedAttributes, options?: CommonResponseActionMethodOptions ) => Promise; diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/sentinelone/sentinel_one_actions_client.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/sentinelone/sentinel_one_actions_client.ts index 906125df4c2b8..2933f25cfd0ad 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/sentinelone/sentinel_one_actions_client.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/sentinelone/sentinel_one_actions_client.ts @@ -57,7 +57,6 @@ import type { EndpointActionResponseDataOutput, GetProcessesActionOutputContent, KillProcessActionOutputContent, - KillProcessRequestBody, LogsEndpointAction, LogsEndpointActionResponse, ResponseActionGetFileOutputContent, @@ -81,6 +80,8 @@ import type { GetProcessesRequestBody, IsolationRouteRequestBody, ResponseActionGetFileRequestBody, + KillProcessRequestBody, + UnisolationRouteRequestBody, } from '../../../../../../common/api/endpoint'; import type { ResponseActionsClientOptions, @@ -361,7 +362,7 @@ export class SentinelOneActionsClient extends ResponseActionsClientImpl { } async release( - actionRequest: IsolationRouteRequestBody, + actionRequest: UnisolationRouteRequestBody, options: CommonResponseActionMethodOptions = {} ): Promise { const reqIndexOptions: ResponseActionsClientWriteActionRequestToEndpointIndexOptions< diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 9abcbd4c40cde..a423b5e64a1d9 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -13210,7 +13210,6 @@ "xpack.csp.emptyState.resetFiltersButton": "Réinitialiser les filtres", "xpack.csp.emptyState.title": "Aucun résultat ne correspond à vos critères de recherche.", "xpack.csp.enableBenchmarkRuleButton": "Activer la règle", - "xpack.csp.findings.distributionBar.showingPageOfTotalLabel": "Affichage de {pageStart}-{pageEnd} sur {total} {type}", "xpack.csp.findings.distributionBar.totalFailedLabel": "Échec des résultats", "xpack.csp.findings.distributionBar.totalPassedLabel": "Réussite des résultats", "xpack.csp.findings.errorCallout.pageSearchErrorTitle": "Une erreur s’est produite lors de la récupération des résultats de recherche.", @@ -13249,9 +13248,6 @@ "xpack.csp.findings.gcpIntegration.gcpInputText.credentialFileText": "Chemin vers le fichier JSON qui contient les informations d'identification et la clé utilisés pour souscrire", "xpack.csp.findings.gcpIntegration.gcpInputText.credentialJSONText": "Blob JSON qui contient les informations d'identification et la clé utilisées pour souscrire", "xpack.csp.findings.gcpIntegration.gcpInputText.credentialSelectBoxTitle": "Informations d'identification", - "xpack.csp.findings.groupBySelector.groupByLabel": "Regrouper par", - "xpack.csp.findings.groupBySelector.groupByNoneLabel": "Aucun", - "xpack.csp.findings.groupBySelector.groupByResourceIdLabel": "Ressource", "xpack.csp.findings.grouping.cloudAccount.nullGroupTitle": "Aucun compte cloud", "xpack.csp.findings.grouping.default.nullGroupTitle": "Aucun regroupement", "xpack.csp.findings.grouping.kubernetes.nullGroupTitle": "Aucun cluster Kubernetes", @@ -13441,9 +13437,6 @@ "xpack.csp.vulnerabilities.grouping.nullGroupUnit": "vulnérabilités", "xpack.csp.vulnerabilities.grouping.resource.nullGroupTitle": "Aucune ressource", "xpack.csp.vulnerabilities.grouping.severity": "Sévérité", - "xpack.csp.vulnerabilities.searchBar.placeholder": "Rechercher des vulnérabilités (par exemple vulnerability.severity : \"CRITICAL\" )", - "xpack.csp.vulnerabilities.table.filterIn": "Inclure", - "xpack.csp.vulnerabilities.table.filterOut": "Exclure", "xpack.csp.vulnerabilities.unit": "{totalCount, plural, =1 {vulnérabilité} other {vulnérabilités}}", "xpack.csp.vulnerabilities.vulnerabilitiesFindingFlyout.flyoutDescriptionList.packageTitle": "Pack", "xpack.csp.vulnerabilities.vulnerabilitiesFindingFlyout.flyoutDescriptionList.resourceId": "ID ressource", @@ -13468,8 +13461,6 @@ "xpack.csp.vulnerabilityDashboard.trendGraphChart.accountsDropDown.prepend.accountsTitle": "Comptes", "xpack.csp.vulnerabilityDashboard.trendGraphChart.trendBySeverityTitle": "Tendance par degré de gravité", "xpack.csp.vulnerabilityDashboard.viewAllButton.buttonTitle": "Tout afficher", - "xpack.csp.vulnerabilityTable.column.sortAscending": "Basse -> Critique", - "xpack.csp.vulnerabilityTable.column.sortDescending": "Critique -> Basse", "xpack.csp.vulnerabilityTable.panel.buttonText": "Afficher toutes les vulnérabilités", "xpack.csp.vulnMgmtIntegration.awsOption.nameTitle": "Amazon Web Services", "xpack.csp.vulnMgmtIntegration.azureOption.nameTitle": "Azure", @@ -24325,13 +24316,8 @@ "xpack.lists.services.items.fileUploadFromFileSystem": "Fichier chargé depuis le système de fichiers de {fileName}", "xpack.logsExplorer.dataTable.controlColumn.actions.button.stacktrace.available": "Traces d'appel disponibles", "xpack.logsExplorer.dataTable.controlColumn.actions.button.stacktrace.notAvailable": "Traces d'appel indisponibles", - "xpack.logsExplorer.dataTable.header.actions.tooltip.expand": "Développer les détails du log", - "xpack.logsExplorer.dataTable.header.actions.tooltip.paragraph": "Les champs fournissant des informations exploitables, comme :", - "xpack.logsExplorer.dataTable.header.actions.tooltip.stacktrace": "L'accès aux traces d'appel disponibles est basé sur :", "xpack.logsExplorer.dataTable.header.content.tooltip.paragraph1": "Affiche le {logLevel} du document et les champs {message}.", "xpack.logsExplorer.dataTable.header.content.tooltip.paragraph2": "Lorsque le champ de message est vide, l'une des informations suivantes s'affiche :", - "xpack.logsExplorer.dataTable.header.popover.actions": "Actions", - "xpack.logsExplorer.dataTable.header.popover.actions.lowercase": "actions", "xpack.logsExplorer.dataTable.header.popover.content": "Contenu", "xpack.logsExplorer.dataTable.header.popover.resource": "Ressource", "xpack.logsExplorer.dataTable.header.resource.tooltip.paragraph": "Les champs fournissant des informations sur la source du document, comme :", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index c5be020320a04..ae244e5fd110b 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -13170,7 +13170,6 @@ "xpack.csp.emptyState.resetFiltersButton": "フィルターをリセット", "xpack.csp.emptyState.title": "検索条件と一致する結果がありません。", "xpack.csp.enableBenchmarkRuleButton": "ルールを有効にする", - "xpack.csp.findings.distributionBar.showingPageOfTotalLabel": "{total}件中{pageStart}-{pageEnd}件の{type}を表示しています", "xpack.csp.findings.distributionBar.totalFailedLabel": "失敗した調査結果", "xpack.csp.findings.distributionBar.totalPassedLabel": "合格した調査結果", "xpack.csp.findings.errorCallout.pageSearchErrorTitle": "検索結果の取得中にエラーが発生しました", @@ -13209,9 +13208,6 @@ "xpack.csp.findings.gcpIntegration.gcpInputText.credentialFileText": "サブスクライブに使用される資格情報とキーを含むJSONファイルへのパス", "xpack.csp.findings.gcpIntegration.gcpInputText.credentialJSONText": "サブスクライブに使用される資格情報とキーを含むJSON blob", "xpack.csp.findings.gcpIntegration.gcpInputText.credentialSelectBoxTitle": "資格情報", - "xpack.csp.findings.groupBySelector.groupByLabel": "グループ分けの条件", - "xpack.csp.findings.groupBySelector.groupByNoneLabel": "なし", - "xpack.csp.findings.groupBySelector.groupByResourceIdLabel": "リソース", "xpack.csp.findings.grouping.cloudAccount.nullGroupTitle": "クラウドアカウントなし", "xpack.csp.findings.grouping.default.nullGroupTitle": "グループ分けなし", "xpack.csp.findings.grouping.kubernetes.nullGroupTitle": "Kubernetesクラスターなし", @@ -13400,9 +13396,6 @@ "xpack.csp.vulnerabilities.grouping.nullGroupUnit": "脆弱性", "xpack.csp.vulnerabilities.grouping.resource.nullGroupTitle": "リソースなし", "xpack.csp.vulnerabilities.grouping.severity": "深刻度", - "xpack.csp.vulnerabilities.searchBar.placeholder": "脆弱性を検索(例:vulnerability.severity :\"CRITICAL\")", - "xpack.csp.vulnerabilities.table.filterIn": "フィルタリング", - "xpack.csp.vulnerabilities.table.filterOut": "除外", "xpack.csp.vulnerabilities.unit": "{totalCount, plural, other {脆弱性}}", "xpack.csp.vulnerabilities.vulnerabilitiesFindingFlyout.flyoutDescriptionList.packageTitle": "パッケージ", "xpack.csp.vulnerabilities.vulnerabilitiesFindingFlyout.flyoutDescriptionList.resourceId": "リソースID", @@ -13427,8 +13420,6 @@ "xpack.csp.vulnerabilityDashboard.trendGraphChart.accountsDropDown.prepend.accountsTitle": "アカウント", "xpack.csp.vulnerabilityDashboard.trendGraphChart.trendBySeverityTitle": "重要度別傾向", "xpack.csp.vulnerabilityDashboard.viewAllButton.buttonTitle": "すべて表示", - "xpack.csp.vulnerabilityTable.column.sortAscending": "低 -> 重大", - "xpack.csp.vulnerabilityTable.column.sortDescending": "重大 -> 低", "xpack.csp.vulnerabilityTable.panel.buttonText": "すべての脆弱性を表示", "xpack.csp.vulnMgmtIntegration.awsOption.nameTitle": "Amazon Web Services", "xpack.csp.vulnMgmtIntegration.azureOption.nameTitle": "Azure", @@ -24250,13 +24241,8 @@ "xpack.lists.services.items.fileUploadFromFileSystem": "ファイルは{fileName}のファイルシステムからアップロードされました", "xpack.logsExplorer.dataTable.controlColumn.actions.button.stacktrace.available": "スタックトレースがあります", "xpack.logsExplorer.dataTable.controlColumn.actions.button.stacktrace.notAvailable": "スタックトレースがありません", - "xpack.logsExplorer.dataTable.header.actions.tooltip.expand": "ログの詳細を展開", - "xpack.logsExplorer.dataTable.header.actions.tooltip.paragraph": "次のようなアクショナブルな情報を提供するフィールド:", - "xpack.logsExplorer.dataTable.header.actions.tooltip.stacktrace": "次に基づいて使用可能なスタックトレースにアクセス:", "xpack.logsExplorer.dataTable.header.content.tooltip.paragraph1": "ドキュメントの{logLevel}と{message}フィールドを表示します。", "xpack.logsExplorer.dataTable.header.content.tooltip.paragraph2": "メッセージフィールドが空のときには、次のいずれかが表示されます。", - "xpack.logsExplorer.dataTable.header.popover.actions": "アクション", - "xpack.logsExplorer.dataTable.header.popover.actions.lowercase": "アクション", "xpack.logsExplorer.dataTable.header.popover.content": "コンテンツ", "xpack.logsExplorer.dataTable.header.popover.resource": "リソース", "xpack.logsExplorer.dataTable.header.resource.tooltip.paragraph": "次のようなドキュメントのソースに関する情報を提供するフィールド:", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 0497f0f96fe31..2e0d57450749f 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -13231,7 +13231,6 @@ "xpack.csp.emptyState.resetFiltersButton": "重置筛选", "xpack.csp.emptyState.title": "没有任何结果匹配您的搜索条件", "xpack.csp.enableBenchmarkRuleButton": "启用规则", - "xpack.csp.findings.distributionBar.showingPageOfTotalLabel": "正在显示第 {pageStart}-{pageEnd} 个(共 {total} 个){type}", "xpack.csp.findings.distributionBar.totalFailedLabel": "失败的结果", "xpack.csp.findings.distributionBar.totalPassedLabel": "通过的结果", "xpack.csp.findings.errorCallout.pageSearchErrorTitle": "检索搜索结果时遇到问题", @@ -13270,9 +13269,6 @@ "xpack.csp.findings.gcpIntegration.gcpInputText.credentialFileText": "包含用于订阅的凭据和密钥的 JSON 文件的路径", "xpack.csp.findings.gcpIntegration.gcpInputText.credentialJSONText": "包含用于订阅的凭据和密钥的 JSON Blob", "xpack.csp.findings.gcpIntegration.gcpInputText.credentialSelectBoxTitle": "凭据", - "xpack.csp.findings.groupBySelector.groupByLabel": "分组依据", - "xpack.csp.findings.groupBySelector.groupByNoneLabel": "无", - "xpack.csp.findings.groupBySelector.groupByResourceIdLabel": "资源", "xpack.csp.findings.grouping.cloudAccount.nullGroupTitle": "无云帐户", "xpack.csp.findings.grouping.default.nullGroupTitle": "无分组", "xpack.csp.findings.grouping.kubernetes.nullGroupTitle": "无 Kubernetes 集群", @@ -13462,9 +13458,6 @@ "xpack.csp.vulnerabilities.grouping.nullGroupUnit": "漏洞", "xpack.csp.vulnerabilities.grouping.resource.nullGroupTitle": "无资源", "xpack.csp.vulnerabilities.grouping.severity": "严重性", - "xpack.csp.vulnerabilities.searchBar.placeholder": "搜索漏洞(例如,vulnerability.severity:“CRITICAL”)", - "xpack.csp.vulnerabilities.table.filterIn": "筛选范围", - "xpack.csp.vulnerabilities.table.filterOut": "筛除", "xpack.csp.vulnerabilities.unit": "{totalCount, plural, other {个漏洞}}", "xpack.csp.vulnerabilities.vulnerabilitiesFindingFlyout.flyoutDescriptionList.packageTitle": "软件包", "xpack.csp.vulnerabilities.vulnerabilitiesFindingFlyout.flyoutDescriptionList.resourceId": "资源 ID", @@ -13489,8 +13482,6 @@ "xpack.csp.vulnerabilityDashboard.trendGraphChart.accountsDropDown.prepend.accountsTitle": "帐户", "xpack.csp.vulnerabilityDashboard.trendGraphChart.trendBySeverityTitle": "趋势(按严重性)", "xpack.csp.vulnerabilityDashboard.viewAllButton.buttonTitle": "查看全部", - "xpack.csp.vulnerabilityTable.column.sortAscending": "低 -> 严重", - "xpack.csp.vulnerabilityTable.column.sortDescending": "严重 -> 低", "xpack.csp.vulnerabilityTable.panel.buttonText": "查看所有漏洞", "xpack.csp.vulnMgmtIntegration.awsOption.nameTitle": "Amazon Web Services", "xpack.csp.vulnMgmtIntegration.azureOption.nameTitle": "Azure", @@ -24360,13 +24351,8 @@ "xpack.lists.services.items.fileUploadFromFileSystem": "从 {fileName} 的文件系统上传的文件", "xpack.logsExplorer.dataTable.controlColumn.actions.button.stacktrace.available": "堆栈跟踪可用", "xpack.logsExplorer.dataTable.controlColumn.actions.button.stacktrace.notAvailable": "堆栈跟踪不可用", - "xpack.logsExplorer.dataTable.header.actions.tooltip.expand": "展开日志详情", - "xpack.logsExplorer.dataTable.header.actions.tooltip.paragraph": "提供可操作信息的字段,例如:", - "xpack.logsExplorer.dataTable.header.actions.tooltip.stacktrace": "基于以下项访问可用堆栈跟踪:", "xpack.logsExplorer.dataTable.header.content.tooltip.paragraph1": "显示该文档的 {logLevel} 和 {message} 字段。", "xpack.logsExplorer.dataTable.header.content.tooltip.paragraph2": "消息字段为空时,将显示以下项之一:", - "xpack.logsExplorer.dataTable.header.popover.actions": "操作", - "xpack.logsExplorer.dataTable.header.popover.actions.lowercase": "操作", "xpack.logsExplorer.dataTable.header.popover.content": "内容", "xpack.logsExplorer.dataTable.header.popover.resource": "资源", "xpack.logsExplorer.dataTable.header.resource.tooltip.paragraph": "提供有关文档来源信息的字段,例如:", diff --git a/x-pack/test/api_integration/apis/painless_lab/config.ts b/x-pack/test/api_integration/apis/painless_lab/config.ts deleted file mode 100644 index 5f335f116fefe..0000000000000 --- a/x-pack/test/api_integration/apis/painless_lab/config.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * 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 { FtrConfigProviderContext } from '@kbn/test'; - -export default async function ({ readConfigFile }: FtrConfigProviderContext) { - const baseIntegrationTestsConfig = await readConfigFile(require.resolve('../../config.ts')); - - return { - ...baseIntegrationTestsConfig.getAll(), - testFiles: [require.resolve('.')], - }; -} diff --git a/x-pack/test/api_integration/deployment_agnostic/README.md b/x-pack/test/api_integration/deployment_agnostic/README.md new file mode 100644 index 0000000000000..eff834b7b1db9 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/README.md @@ -0,0 +1,183 @@ +# Deployment-Agnostic Tests Guidelines + +## Definition +A deployment-agnostic API integration test is a test suite that fulfills the following criteria: + +**Functionality**: It tests Kibana APIs that are logically identical in both stateful and serverless environments for the same roles. + +**Design**: The test design is clean and does not require additional logic to execute in either stateful or serverless environments. + +## Tests Design Requirements +A deployment-agnostic test is contained within a single test file and always utilizes the [DeploymentAgnosticFtrProviderContext](https://github.com/elastic/kibana/blob/main/x-pack/test/api_integration/deployment_agnostic/ftr_provider_context.d.ts) to load compatible FTR services. A compatible FTR service must support: + +- **Serverless**: Both local environments and MKI (Managed Kubernetes Infrastructure). +- **Stateful**: Both local environments and Cloud deployments. + +To achieve this, services cannot use `supertest`, which employs an operator user for serverless and a system index superuser for stateful setups. Instead, services should use a combination of `supertestWithoutAuth` and `samlAuth` to generate an API key for user roles and make API calls. For example, see the [data_view_api.ts](https://github.com/elastic/kibana/blob/main/x-pack/test/api_integration/deployment_agnostic/services/data_view_api.ts) service. + +### How It Works +Most existing stateful tests use basic authentication for API testing. In contrast, serverless tests use SAML authentication with project-specific role mapping. + +Since both Elastic Cloud (ESS) and Serverless rely on SAML authentication by default, and stateful deployments also support SAML, *deployment-agnostic tests configure Elasticsearch and Kibana with SAML authentication to use the same authentication approach in all cases*. For roles, stateful deployments define 'viewer', 'editor', and 'admin' roles with serverless-alike permissions. + +### When to Create Separate Tests +While the deployment-agnostic testing approach is beneficial, it should not compromise the quality and simplicity of the tests. Here are some scenarios where separate test files are recommended: + +- **Role-Specific Logic**: If API access or logic depends on roles that differ across deployments. +- **Environment Constraints**: If a test can only run locally and not on MKI or Cloud deployments. +- **Complex Logic**: If the test logic requires splitting across multiple locations. + +## File Structure +We recommend following this structure to simplify maintenance and allow other teams to reuse code (e.g., FTR services) created by different teams: + +``` +x-pack/test/ +├─ deployment_agnostic +│ ├─ apis +│ │ ├─ +│ │ │ ├─ +│ │ │ ├─ +│ │ ├─ +│ │ │ ├─ +│ │ │ ├─ +│ ├─ services +│ │ ├─ index.ts // only services from 'x-pack/test/api_integration/deployment_agnostic/services' +│ │ ├─ .ts +│ │ ├─ .ts +│ ├─ ftr_provider_context.d.ts // with types of services from './services' +├─ stateful.index.ts +├─ stateful.config.ts +├─ .index.ts // e.g., oblt.index.ts +├─ .serverless.config.ts // e.g., oblt.serverless.config.ts +``` + +## Step-by-Step Guide +1. Define Deployment-Agnostic Services + +Under `x-pack/test//deployment_agnostic/services`, create `index.ts` and load default services like `samlAuth` and `superuserWithoutAuth`: + +```ts +import { commonFunctionalServices } from '@kbn/ftr-common-functional-services'; +import { services as deploymentAgnosticServices } from './../../api_integration/deployment_agnostic/services'; + +export type { + InternalRequestHeader, + RoleCredentials, + SupertestWithoutAuthProviderType, +} from '@kbn/ftr-common-functional-services'; + +export const services = { + ...deploymentAgnosticServices, + // create a new deployment-agnostic service and load here +}; +``` + +We suggest adding new services to `x-pack/test/api_integration/deployment_agnostic/services` so other teams can benefit from them. + +2. Create `DeploymentAgnosticFtrProviderContext` with Services Defined in Step 2 + +Create `ftr_provider_context.d.ts` and export `DeploymentAgnosticFtrProviderContext`: +```ts +import { GenericFtrProviderContext } from '@kbn/test'; +import { services } from './services'; + +export type DeploymentAgnosticFtrProviderContext = GenericFtrProviderContext; +``` + +3. Add Tests + +Add test files to `x-pack/test//deployment_agnostic/apis/`: + +test example +```ts +export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { + const samlAuth = getService('samlAuth'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + let roleAuthc: RoleCredentials; + let internalHeaders: InternalRequestHeader; + + describe('compression', () => { + before(async () => { + roleAuthc = await samlAuth.createM2mApiKeyWithRoleScope('admin'); + internalHeaders = samlAuth.getInternalRequestHeader(); + }); + after(async () => { + await samlAuth.invalidateM2mApiKeyWithRoleScope(roleAuthc); + }); + describe('against an application page', () => { + it(`uses compression when there isn't a referer`, async () => { + const response = await supertestWithoutAuth + .get('/app/kibana') + .set('accept-encoding', 'gzip') + .set(internalHeaders) + .set(roleAuthc.apiKeyHeader); + expect(response.header).to.have.property('content-encoding', 'gzip'); + }); + }); + }); +} +``` +Load all test files in `index.ts` under the same folder. + +4. Add Tests Entry File and FTR Config File for **Stateful** Deployment + +Create `stateful.index.ts` tests entry file and load tests: + +```ts +import { DeploymentAgnosticFtrProviderContext } from './ftr_provider_context'; + +export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) { + describe('apis', () => { + loadTestFile(require.resolve('./apis/')); + }); +} +``` + +Create `stateful.config.ts` and link tests entry file: + +```ts +import { createStatefulTestConfig } from './../../api_integration/deployment_agnostic/default_configs/stateful.config.base'; + +export default createStatefulTestConfig({ + testFiles: [require.resolve('./stateful.index.ts')], + junit: { + reportName: 'Stateful - Deployment-agnostic API Integration Tests', + }, + // extra arguments + esServerArgs: [], + kbnServerArgs: [], +}); +``` +5. Add Tests Entry File and FTR Config File for Specific **Serverless** Project + +Example for Observability project: + +oblt.index.ts +```ts +import { DeploymentAgnosticFtrProviderContext } from './ftr_provider_context'; + +export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) { + describe('Serverless Observability - Deployment-agnostic api integration tests', () => { + loadTestFile(require.resolve('./apis/')); + }); +} +``` + +oblt.serverless.config.ts +```ts +import { createServerlessTestConfig } from './../../api_integration/deployment_agnostic/default_configs/serverless.config.base'; + +export default createServerlessTestConfig({ + serverlessProject: 'oblt', + testFiles: [require.resolve('./oblt.index.ts')], + junit: { + reportName: 'Serverless Observability - Deployment-agnostic API Integration Tests', + }, +}); +``` + +ES and Kibana project-specific arguments are defined and loaded from `serverless.config.base`. These arguments are copied from the Elasticsearch and Kibana controller repositories. + +Note: The FTR (Functional Test Runner) does not have the capability to provision custom ES/Kibana server arguments into the serverless project. Any custom arguments listed explicitly in this config file will apply **only to a local environment**. + +6. Add FTR Configs Path to FTR Manifest Files Located in `.buildkite/` diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/console/index.ts b/x-pack/test/api_integration/deployment_agnostic/apis/console/index.ts new file mode 100644 index 0000000000000..4558f6818542f --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/apis/console/index.ts @@ -0,0 +1,14 @@ +/* + * 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 { DeploymentAgnosticFtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) { + describe('console', () => { + loadTestFile(require.resolve('./spec_definitions')); + }); +} diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/console/spec_definitions.ts b/x-pack/test/api_integration/deployment_agnostic/apis/console/spec_definitions.ts new file mode 100644 index 0000000000000..a2c8115e4ea0d --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/apis/console/spec_definitions.ts @@ -0,0 +1,42 @@ +/* + * 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 expect from '@kbn/expect'; +import { RoleCredentials, InternalRequestHeader } from '@kbn/ftr-common-functional-services'; +import { DeploymentAgnosticFtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { + const samlAuth = getService('samlAuth'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + let roleAuthc: RoleCredentials; + let internalHeaders: InternalRequestHeader; + + describe('GET /api/console/api_server', () => { + before(async () => { + roleAuthc = await samlAuth.createM2mApiKeyWithRoleScope('admin'); + internalHeaders = samlAuth.getInternalRequestHeader(); + }); + after(async () => { + await samlAuth.invalidateM2mApiKeyWithRoleScope(roleAuthc); + }); + it('returns autocomplete definitions', async () => { + const { body } = await supertestWithoutAuth + .get('/api/console/api_server') + .set(roleAuthc.apiKeyHeader) + .set(internalHeaders) + .set('kbn-xsrf', 'true') + .expect(200); + expect(body.es).to.be.ok(); + const { + es: { name, globals, endpoints }, + } = body; + expect(name).to.be.ok(); + expect(Object.keys(globals).length).to.be.above(0); + expect(Object.keys(endpoints).length).to.be.above(0); + }); + }); +} diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/core/compression.ts b/x-pack/test/api_integration/deployment_agnostic/apis/core/compression.ts new file mode 100644 index 0000000000000..d1aa1cfb45153 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/apis/core/compression.ts @@ -0,0 +1,47 @@ +/* + * 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 expect from '@kbn/expect'; +import { RoleCredentials, InternalRequestHeader } from '@kbn/ftr-common-functional-services'; +import { DeploymentAgnosticFtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { + const samlAuth = getService('samlAuth'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + let roleAuthc: RoleCredentials; + let internalHeaders: InternalRequestHeader; + + describe('compression', () => { + before(async () => { + roleAuthc = await samlAuth.createM2mApiKeyWithRoleScope('admin'); + internalHeaders = samlAuth.getInternalRequestHeader(); + }); + after(async () => { + await samlAuth.invalidateM2mApiKeyWithRoleScope(roleAuthc); + }); + describe('against an application page', () => { + it(`uses compression when there isn't a referer`, async () => { + const response = await supertestWithoutAuth + .get('/app/kibana') + .set('accept-encoding', 'gzip') + .set(internalHeaders) + .set(roleAuthc.apiKeyHeader); + expect(response.header).to.have.property('content-encoding', 'gzip'); + }); + + it(`uses compression when there is a whitelisted referer`, async () => { + const response = await supertestWithoutAuth + .get('/app/kibana') + .set('accept-encoding', 'gzip') + .set(internalHeaders) + .set('referer', 'https://some-host.com') + .set(roleAuthc.apiKeyHeader); + expect(response.header).to.have.property('content-encoding', 'gzip'); + }); + }); + }); +} diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/core/index.ts b/x-pack/test/api_integration/deployment_agnostic/apis/core/index.ts new file mode 100644 index 0000000000000..93e40d3d5b914 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/apis/core/index.ts @@ -0,0 +1,14 @@ +/* + * 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 { DeploymentAgnosticFtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) { + describe('core', () => { + loadTestFile(require.resolve('./compression')); + }); +} diff --git a/x-pack/test/api_integration/apis/painless_lab/index.ts b/x-pack/test/api_integration/deployment_agnostic/apis/painless_lab/index.ts similarity index 67% rename from x-pack/test/api_integration/apis/painless_lab/index.ts rename to x-pack/test/api_integration/deployment_agnostic/apis/painless_lab/index.ts index 63744b77312d7..ff59037fc1f06 100644 --- a/x-pack/test/api_integration/apis/painless_lab/index.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/painless_lab/index.ts @@ -5,9 +5,9 @@ * 2.0. */ -import { FtrProviderContext } from '../../ftr_provider_context'; +import { DeploymentAgnosticFtrProviderContext } from '../../ftr_provider_context'; -export default function ({ loadTestFile }: FtrProviderContext) { +export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) { describe('Painless Lab', () => { loadTestFile(require.resolve('./painless_lab')); }); diff --git a/x-pack/test/api_integration/apis/painless_lab/painless_lab.ts b/x-pack/test/api_integration/deployment_agnostic/apis/painless_lab/painless_lab.ts similarity index 60% rename from x-pack/test/api_integration/apis/painless_lab/painless_lab.ts rename to x-pack/test/api_integration/deployment_agnostic/apis/painless_lab/painless_lab.ts index b594220634d1d..7e7047ac9cb57 100644 --- a/x-pack/test/api_integration/apis/painless_lab/painless_lab.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/painless_lab/painless_lab.ts @@ -6,22 +6,34 @@ */ import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../ftr_provider_context'; +import { RoleCredentials, InternalRequestHeader } from '@kbn/ftr-common-functional-services'; +import { DeploymentAgnosticFtrProviderContext } from '../../ftr_provider_context'; const API_BASE_PATH = '/api/painless_lab'; -export default function ({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); +export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { + const samlAuth = getService('samlAuth'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + let roleAuthc: RoleCredentials; + let internalHeaders: InternalRequestHeader; - describe('Painless Lab', function () { + describe('Painless Lab Routes', function () { + before(async () => { + roleAuthc = await samlAuth.createM2mApiKeyWithRoleScope('admin'); + internalHeaders = samlAuth.getInternalRequestHeader(); + }); + after(async () => { + await samlAuth.invalidateM2mApiKeyWithRoleScope(roleAuthc); + }); describe('Execute', () => { it('should execute a valid painless script', async () => { const script = '"{\\n \\"script\\": {\\n \\"source\\": \\"return true;\\",\\n \\"params\\": {\\n \\"string_parameter\\": \\"string value\\",\\n \\"number_parameter\\": 1.5,\\n \\"boolean_parameter\\": true\\n}\\n }\\n}"'; - const { body } = await supertest + const { body } = await supertestWithoutAuth .post(`${API_BASE_PATH}/execute`) - .set('kbn-xsrf', 'xxx') + .set(internalHeaders) + .set(roleAuthc.apiKeyHeader) .set('Content-Type', 'application/json;charset=UTF-8') .send(script) .expect(200); @@ -35,10 +47,11 @@ export default function ({ getService }: FtrProviderContext) { const invalidScript = '"{\\n \\"script\\": {\\n \\"source\\": \\"foobar\\",\\n \\"params\\": {\\n \\"string_parameter\\": \\"string value\\",\\n \\"number_parameter\\": 1.5,\\n \\"boolean_parameter\\": true\\n}\\n }\\n}"'; - const { body } = await supertest + const { body } = await supertestWithoutAuth .post(`${API_BASE_PATH}/execute`) - .set('kbn-xsrf', 'xxx') + .set(internalHeaders) .set('Content-Type', 'application/json;charset=UTF-8') + .set(roleAuthc.apiKeyHeader) .send(invalidScript) .expect(200); diff --git a/x-pack/test/api_integration/deployment_agnostic/default_configs/serverless.config.base.ts b/x-pack/test/api_integration/deployment_agnostic/default_configs/serverless.config.base.ts new file mode 100644 index 0000000000000..606a71835b317 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/default_configs/serverless.config.base.ts @@ -0,0 +1,90 @@ +/* + * 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 { FtrConfigProviderContext, Config } from '@kbn/test'; + +import { ServerlessProjectType } from '@kbn/es'; +import { services } from '../services'; + +interface CreateTestConfigOptions { + serverlessProject: ServerlessProjectType; + esServerArgs?: string[]; + kbnServerArgs?: string[]; + testFiles: string[]; + junit: { reportName: string }; + suiteTags?: { include?: string[]; exclude?: string[] }; +} + +// include settings from elasticsearch controller +// https://github.com/elastic/elasticsearch-controller/blob/main/helm/values.yaml +const esServerArgsFromController = { + es: [], + oblt: [ + 'xpack.apm_data.enabled=true', + // for ML, data frame analytics are not part of this project type + 'xpack.ml.dfa.enabled=false', + ], + security: [ + 'xpack.security.authc.api_key.cache.max_keys=70000', + 'data_streams.lifecycle.retention.factory_default=365d', + 'data_streams.lifecycle.retention.factory_max=365d', + ], +}; + +// include settings from kibana controller +// https://github.com/elastic/kibana-controller/blob/main/internal/controllers/kibana/config/config_settings.go +const kbnServerArgsFromController = { + es: [ + // useful for testing (also enabled in MKI QA) + '--coreApp.allowDynamicConfigOverrides=true', + ], + oblt: [ + '--coreApp.allowDynamicConfigOverrides=true', + // defined in MKI control plane + '--xpack.uptime.service.manifestUrl=mockDevUrl', + ], + security: [ + '--coreApp.allowDynamicConfigOverrides=true', + // disable fleet task that writes to metrics.fleet_server.* data streams, impacting functional tests + `--xpack.task_manager.unsafe.exclude_task_types=${JSON.stringify(['Fleet-Metrics-Task'])}`, + ], +}; + +export function createServerlessTestConfig(options: CreateTestConfigOptions) { + return async ({ readConfigFile }: FtrConfigProviderContext): Promise => { + const svlSharedConfig = await readConfigFile( + require.resolve('@kbn/test-suites-serverless/shared/config.base') + ); + + return { + ...svlSharedConfig.getAll(), + + services: { + ...services, + }, + esTestCluster: { + ...svlSharedConfig.get('esTestCluster'), + serverArgs: [ + ...svlSharedConfig.get('esTestCluster.serverArgs'), + ...esServerArgsFromController[options.serverlessProject], + ...(options.esServerArgs ?? []), + ], + }, + kbnTestServer: { + ...svlSharedConfig.get('kbnTestServer'), + serverArgs: [ + ...svlSharedConfig.get('kbnTestServer.serverArgs'), + ...kbnServerArgsFromController[options.serverlessProject], + `--serverless=${options.serverlessProject}`, + ...(options.kbnServerArgs || []), + ], + }, + testFiles: options.testFiles, + junit: options.junit, + suiteTags: options.suiteTags, + }; + }; +} diff --git a/x-pack/test/api_integration/deployment_agnostic/default_configs/stateful.config.base.ts b/x-pack/test/api_integration/deployment_agnostic/default_configs/stateful.config.base.ts new file mode 100644 index 0000000000000..c784ff071895b --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/default_configs/stateful.config.base.ts @@ -0,0 +1,95 @@ +/* + * 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 { + MOCK_IDP_REALM_NAME, + MOCK_IDP_ENTITY_ID, + MOCK_IDP_ATTRIBUTE_PRINCIPAL, + MOCK_IDP_ATTRIBUTE_ROLES, + MOCK_IDP_ATTRIBUTE_EMAIL, + MOCK_IDP_ATTRIBUTE_NAME, +} from '@kbn/mock-idp-utils'; +import { + esTestConfig, + kbnTestConfig, + systemIndicesSuperuser, + FtrConfigProviderContext, +} from '@kbn/test'; +import { services } from '../services'; + +interface CreateTestConfigOptions { + esServerArgs?: string[]; + kbnServerArgs?: string[]; + testFiles: string[]; + junit: { reportName: string }; + suiteTags?: { include?: string[]; exclude?: string[] }; +} + +export function createStatefulTestConfig(options: CreateTestConfigOptions) { + return async ({ readConfigFile }: FtrConfigProviderContext) => { + const xPackAPITestsConfig = await readConfigFile(require.resolve('../../config.ts')); + + // TODO: move to kbn-es because currently metadata file has hardcoded entityID and Location + const idpPath = require.resolve( + '@kbn/security-api-integration-helpers/saml/idp_metadata_mock_idp.xml' + ); + + const servers = { + kibana: { + ...kbnTestConfig.getUrlParts(systemIndicesSuperuser), + protocol: process.env.TEST_CLOUD ? 'https' : 'http', + }, + elasticsearch: { + ...esTestConfig.getUrlParts(), + protocol: process.env.TEST_CLOUD ? 'https' : 'http', + }, + }; + + const kbnUrl = `${servers.kibana.protocol}://${servers.kibana.hostname}:${servers.kibana.port}`; + + return { + servers, + testFiles: options.testFiles, + security: { disableTestUser: true }, + services, + junit: options.junit, + suiteTags: options.suiteTags, + + esTestCluster: { + ...xPackAPITestsConfig.get('esTestCluster'), + serverArgs: [ + ...xPackAPITestsConfig.get('esTestCluster.serverArgs'), + ...(options.esServerArgs ?? []), + 'xpack.security.authc.token.enabled=true', + `xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.order=0`, + `xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.idp.metadata.path=${idpPath}`, + `xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.idp.entity_id=${MOCK_IDP_ENTITY_ID}`, + `xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.sp.entity_id=${kbnUrl}`, + `xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.sp.acs=${kbnUrl}/api/security/saml/callback`, + `xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.sp.logout=${kbnUrl}/logout`, + `xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.attributes.principal=${MOCK_IDP_ATTRIBUTE_PRINCIPAL}`, + `xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.attributes.groups=${MOCK_IDP_ATTRIBUTE_ROLES}`, + `xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.attributes.name=${MOCK_IDP_ATTRIBUTE_NAME}`, + `xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.attributes.mail=${MOCK_IDP_ATTRIBUTE_EMAIL}`, + ], + }, + + kbnTestServer: { + ...xPackAPITestsConfig.get('kbnTestServer'), + serverArgs: [ + ...xPackAPITestsConfig.get('kbnTestServer.serverArgs'), + ...(options.kbnServerArgs || []), + '--xpack.security.authc.selector.enabled=false', + `--xpack.security.authc.providers=${JSON.stringify({ + saml: { 'cloud-saml-kibana': { order: 0, realm: MOCK_IDP_REALM_NAME } }, + basic: { 'cloud-basic': { order: 1 } }, + })}`, + `--server.publicBaseUrl=${servers.kibana.protocol}://${servers.kibana.hostname}:${servers.kibana.port}`, + ], + }, + }; + }; +} diff --git a/x-pack/test/api_integration/deployment_agnostic/ftr_provider_context.d.ts b/x-pack/test/api_integration/deployment_agnostic/ftr_provider_context.d.ts new file mode 100644 index 0000000000000..81df490d79428 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/ftr_provider_context.d.ts @@ -0,0 +1,12 @@ +/* + * 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 { GenericFtrProviderContext } from '@kbn/test'; + +import { services } from './services'; + +export type DeploymentAgnosticFtrProviderContext = GenericFtrProviderContext; diff --git a/x-pack/test/api_integration/deployment_agnostic/oblt.index.ts b/x-pack/test/api_integration/deployment_agnostic/oblt.index.ts new file mode 100644 index 0000000000000..d81415e0554dd --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/oblt.index.ts @@ -0,0 +1,15 @@ +/* + * 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 { DeploymentAgnosticFtrProviderContext } from './ftr_provider_context'; + +export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) { + describe('Serverless Observability - Deployment-agnostic api integration tests', () => { + loadTestFile(require.resolve('./apis/console')); + loadTestFile(require.resolve('./apis/core')); + loadTestFile(require.resolve('./apis/painless_lab')); + }); +} diff --git a/x-pack/test/api_integration/deployment_agnostic/oblt.serverless.config.ts b/x-pack/test/api_integration/deployment_agnostic/oblt.serverless.config.ts new file mode 100644 index 0000000000000..52e1ba2d431ac --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/oblt.serverless.config.ts @@ -0,0 +1,16 @@ +/* + * 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 { createServerlessTestConfig } from './default_configs/serverless.config.base'; + +export default createServerlessTestConfig({ + serverlessProject: 'oblt', + testFiles: [require.resolve('./oblt.index.ts')], + junit: { + reportName: 'Serverless Observability - Deployment-agnostic API Integration Tests', + }, +}); diff --git a/x-pack/test/api_integration/deployment_agnostic/search.index.ts b/x-pack/test/api_integration/deployment_agnostic/search.index.ts new file mode 100644 index 0000000000000..740520f032f0f --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/search.index.ts @@ -0,0 +1,14 @@ +/* + * 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 { DeploymentAgnosticFtrProviderContext } from './ftr_provider_context'; + +export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) { + describe('Serverless Search - Deployment-agnostic api integration tests', () => { + loadTestFile(require.resolve('./apis/console')); + loadTestFile(require.resolve('./apis/core')); + }); +} diff --git a/x-pack/test/api_integration/deployment_agnostic/search.serverless.config.ts b/x-pack/test/api_integration/deployment_agnostic/search.serverless.config.ts new file mode 100644 index 0000000000000..5e90b71d69550 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/search.serverless.config.ts @@ -0,0 +1,16 @@ +/* + * 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 { createServerlessTestConfig } from './default_configs/serverless.config.base'; + +export default createServerlessTestConfig({ + serverlessProject: 'es', + testFiles: [require.resolve('./search.index.ts')], + junit: { + reportName: 'Serverless Search - Deployment-agnostic API Integration Tests', + }, +}); diff --git a/x-pack/test/api_integration/deployment_agnostic/security.index.ts b/x-pack/test/api_integration/deployment_agnostic/security.index.ts new file mode 100644 index 0000000000000..9a9db972bcd5d --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/security.index.ts @@ -0,0 +1,15 @@ +/* + * 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 { DeploymentAgnosticFtrProviderContext } from './ftr_provider_context'; + +export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) { + describe('Security Search - Deployment-agnostic api integration tests', () => { + loadTestFile(require.resolve('./apis/console')); + loadTestFile(require.resolve('./apis/core')); + loadTestFile(require.resolve('./apis/painless_lab')); + }); +} diff --git a/x-pack/test/api_integration/deployment_agnostic/security.serverless.config.ts b/x-pack/test/api_integration/deployment_agnostic/security.serverless.config.ts new file mode 100644 index 0000000000000..d3a30cc95d820 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/security.serverless.config.ts @@ -0,0 +1,16 @@ +/* + * 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 { createServerlessTestConfig } from './default_configs/serverless.config.base'; + +export default createServerlessTestConfig({ + serverlessProject: 'security', + testFiles: [require.resolve('./security.index.ts')], + junit: { + reportName: 'Serverless Security - Deployment-agnostic API Integration Tests', + }, +}); diff --git a/x-pack/test/api_integration/deployment_agnostic/services/data_view_api.ts b/x-pack/test/api_integration/deployment_agnostic/services/data_view_api.ts new file mode 100644 index 0000000000000..c22db40882b60 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/services/data_view_api.ts @@ -0,0 +1,66 @@ +/* + * 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 { RoleCredentials } from '@kbn/ftr-common-functional-services'; +import { DeploymentAgnosticFtrProviderContext } from '../ftr_provider_context'; + +export function DataViewApiProvider({ getService }: DeploymentAgnosticFtrProviderContext) { + const supertestWithoutAuth = getService('supertestWithoutAuth'); + const samlAuth = getService('samlAuth'); + + return { + async create({ + roleAuthc, + id, + name, + title, + }: { + roleAuthc: RoleCredentials; + id: string; + name: string; + title: string; + }) { + const { body } = await supertestWithoutAuth + .post(`/api/content_management/rpc/create`) + .set(roleAuthc.apiKeyHeader) + .set(samlAuth.getInternalRequestHeader()) + .set(samlAuth.getCommonRequestHeader()) + .send({ + contentTypeId: 'index-pattern', + data: { + fieldAttrs: '{}', + title, + timeFieldName: '@timestamp', + sourceFilters: '[]', + fields: '[]', + fieldFormatMap: '{}', + typeMeta: '{}', + runtimeFieldMap: '{}', + name, + }, + options: { id }, + version: 1, + }); + return body; + }, + + async delete({ roleAuthc, id }: { roleAuthc: RoleCredentials; id: string }) { + const { body } = await supertestWithoutAuth + .post(`/api/content_management/rpc/create`) + .set(roleAuthc.apiKeyHeader) + .set(samlAuth.getInternalRequestHeader()) + .set(samlAuth.getCommonRequestHeader()) + .send({ + contentTypeId: 'index-pattern', + id, + options: { force: true }, + version: 1, + }); + return body; + }, + }; +} diff --git a/x-pack/test/api_integration/deployment_agnostic/services/deployment_agnostic_services.ts b/x-pack/test/api_integration/deployment_agnostic/services/deployment_agnostic_services.ts new file mode 100644 index 0000000000000..38222c096bed0 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/services/deployment_agnostic_services.ts @@ -0,0 +1,28 @@ +/* + * 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 _ from 'lodash'; +import { services as apiIntegrationServices } from '../../services'; + +/** + * Load only services that support both stateful & serverless deployments (including Cloud/MKI), + * e.g. `randomness` or `retry` are deployment agnostic + */ +export const deploymentAgnosticServices = _.pick(apiIntegrationServices, [ + 'supertest', // TODO: review its behaviour + 'es', + 'esArchiver', + 'esSupertest', // TODO: review its behaviour + 'indexPatterns', + 'ingestPipelines', + 'kibanaServer', + // 'ml', depends on 'esDeleteAllIndices', can we make it deployment agnostic? + 'randomness', + 'retry', + 'security', + 'usageAPI', +]); diff --git a/x-pack/test/api_integration/deployment_agnostic/services/index.ts b/x-pack/test/api_integration/deployment_agnostic/services/index.ts new file mode 100644 index 0000000000000..c1e70466f1b3c --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/services/index.ts @@ -0,0 +1,26 @@ +/* + * 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 { commonFunctionalServices } from '@kbn/ftr-common-functional-services'; +import { deploymentAgnosticServices } from './deployment_agnostic_services'; +import { DataViewApiProvider } from './data_view_api'; +import { SloApiProvider } from './slo_api'; + +export type { + InternalRequestHeader, + RoleCredentials, + SupertestWithoutAuthProviderType, +} from '@kbn/ftr-common-functional-services'; + +export const services = { + ...deploymentAgnosticServices, + supertestWithoutAuth: commonFunctionalServices.supertestWithoutAuth, + samlAuth: commonFunctionalServices.samlAuth, + dataViewApi: DataViewApiProvider, + sloApi: SloApiProvider, + // create a new deployment-agnostic service and load here +}; diff --git a/x-pack/test/api_integration/deployment_agnostic/services/slo_api.ts b/x-pack/test/api_integration/deployment_agnostic/services/slo_api.ts new file mode 100644 index 0000000000000..4c83a536ffb36 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/services/slo_api.ts @@ -0,0 +1,203 @@ +/* + * 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 { + fetchHistoricalSummaryParamsSchema, + FetchHistoricalSummaryResponse, +} from '@kbn/slo-schema'; +import * as t from 'io-ts'; +import { RoleCredentials } from '@kbn/ftr-common-functional-services'; +import { DeploymentAgnosticFtrProviderContext } from '../ftr_provider_context'; + +type DurationUnit = 'm' | 'h' | 'd' | 'w' | 'M'; + +interface Duration { + value: number; + unit: DurationUnit; +} + +interface WindowSchema { + id: string; + burnRateThreshold: number; + maxBurnRateThreshold: number; + longWindow: Duration; + shortWindow: Duration; + actionGroup: string; +} + +interface Dependency { + ruleId: string; + actionGroupsToSuppressOn: string[]; +} + +export interface SloBurnRateRuleParams { + sloId: string; + windows: WindowSchema[]; + dependencies?: Dependency[]; +} + +interface SloParams { + id?: string; + name: string; + description: string; + indicator: { + type: 'sli.kql.custom'; + params: { + index: string; + good: string; + total: string; + timestampField: string; + }; + }; + timeWindow: { + duration: string; + type: string; + }; + budgetingMethod: string; + objective: { + target: number; + }; + groupBy: string; +} + +type FetchHistoricalSummaryParams = t.OutputOf< + typeof fetchHistoricalSummaryParamsSchema.props.body +>; + +interface SloRequestParams { + id: string; + roleAuthc: RoleCredentials; +} + +export function SloApiProvider({ getService }: DeploymentAgnosticFtrProviderContext) { + const es = getService('es'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + const samlAuth = getService('samlAuth'); + const retry = getService('retry'); + const config = getService('config'); + const retryTimeout = config.get('timeouts.try'); + const requestTimeout = 30 * 1000; + + return { + async create(slo: SloParams, roleAuthc: RoleCredentials) { + const { body } = await supertestWithoutAuth + .post(`/api/observability/slos`) + .set(roleAuthc.apiKeyHeader) + .set(samlAuth.getInternalRequestHeader()) + .send(slo); + return body; + }, + + async delete({ id, roleAuthc }: SloRequestParams) { + const response = await supertestWithoutAuth + .delete(`/api/observability/slos/${id}`) + .set(roleAuthc.apiKeyHeader) + .set(samlAuth.getInternalRequestHeader()); + return response; + }, + + async fetchHistoricalSummary( + params: FetchHistoricalSummaryParams, + roleAuthc: RoleCredentials + ): Promise { + const { body } = await supertestWithoutAuth + .post(`/internal/observability/slos/_historical_summary`) + .set(roleAuthc.apiKeyHeader) + .set(samlAuth.getInternalRequestHeader()) + .send(params); + return body; + }, + + async waitForSloToBeDeleted({ id, roleAuthc }: SloRequestParams) { + return await retry.tryForTime(retryTimeout, async () => { + const response = await supertestWithoutAuth + .delete(`/api/observability/slos/${id}`) + .set(roleAuthc.apiKeyHeader) + .set(samlAuth.getInternalRequestHeader()) + .timeout(requestTimeout); + if (!response.ok) { + throw new Error(`SLO with id '${id}' was not deleted`); + } + return response; + }); + }, + + async waitForSloCreated({ id, roleAuthc }: SloRequestParams) { + return await retry.tryForTime(retryTimeout, async () => { + const response = await supertestWithoutAuth + .get(`/api/observability/slos/${id}`) + .set(roleAuthc.apiKeyHeader) + .set(samlAuth.getInternalRequestHeader()) + .timeout(requestTimeout); + if (response.body.id === undefined) { + throw new Error(`No SLO with id '${id}' found`); + } + return response.body; + }); + }, + + async waitForSloSummaryTempIndexToExist(index: string) { + return await retry.tryForTime(retryTimeout, async () => { + const indexExists = await es.indices.exists({ index, allow_no_indices: false }); + if (!indexExists) { + throw new Error(`SLO summary index '${index}' should exist`); + } + return indexExists; + }); + }, + + async getSloData({ sloId, indexName }: { sloId: string; indexName: string }) { + const response = await es.search({ + index: indexName, + body: { + query: { + bool: { + filter: [{ term: { 'slo.id': sloId } }], + }, + }, + }, + }); + return response; + }, + async waitForSloData({ id, indexName }: { id: string; indexName: string }) { + return await retry.tryForTime(retryTimeout, async () => { + const response = await es.search({ + index: indexName, + body: { + query: { + bool: { + filter: [{ term: { 'slo.id': id } }], + }, + }, + }, + }); + if (response.hits.hits.length === 0) { + throw new Error(`No hits found at index '${indexName}' for slo id='${id}'`); + } + return response; + }); + }, + async deleteAllSLOs(roleAuthc: RoleCredentials) { + const response = await supertestWithoutAuth + .get(`/api/observability/slos/_definitions`) + .set(roleAuthc.apiKeyHeader) + .set(samlAuth.getInternalRequestHeader()) + .send() + .expect(200); + await Promise.all( + response.body.results.map(({ id }: { id: string }) => { + return supertestWithoutAuth + .delete(`/api/observability/slos/${id}`) + .set(roleAuthc.apiKeyHeader) + .set(samlAuth.getInternalRequestHeader()) + .send() + .expect(204); + }) + ); + }, + }; +} diff --git a/x-pack/test/api_integration/deployment_agnostic/stateful.config.ts b/x-pack/test/api_integration/deployment_agnostic/stateful.config.ts new file mode 100644 index 0000000000000..b4c22e735925c --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/stateful.config.ts @@ -0,0 +1,18 @@ +/* + * 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 { createStatefulTestConfig } from './default_configs/stateful.config.base'; + +export default createStatefulTestConfig({ + testFiles: [require.resolve('./stateful.index.ts')], + junit: { + reportName: 'Stateful - Deployment-agnostic API Integration Tests', + }, + // extra arguments + esServerArgs: [], + kbnServerArgs: [], +}); diff --git a/x-pack/test/api_integration/deployment_agnostic/stateful.index.ts b/x-pack/test/api_integration/deployment_agnostic/stateful.index.ts new file mode 100644 index 0000000000000..3e46c4d5b0472 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/stateful.index.ts @@ -0,0 +1,16 @@ +/* + * 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 { DeploymentAgnosticFtrProviderContext } from './ftr_provider_context'; + +export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) { + describe('apis', () => { + loadTestFile(require.resolve('./apis/console')); + loadTestFile(require.resolve('./apis/core')); + loadTestFile(require.resolve('./apis/painless_lab')); + }); +} diff --git a/x-pack/test/api_integration/services/security_solution_api.gen.ts b/x-pack/test/api_integration/services/security_solution_api.gen.ts index 582b7feea4d55..ef706d2aca2cd 100644 --- a/x-pack/test/api_integration/services/security_solution_api.gen.ts +++ b/x-pack/test/api_integration/services/security_solution_api.gen.ts @@ -41,8 +41,22 @@ import { DeleteNoteRequestBodyInput } from '@kbn/security-solution-plugin/common import { DeleteRuleRequestQueryInput } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management/crud/delete_rule/delete_rule_route.gen'; import { DeleteTimelinesRequestBodyInput } from '@kbn/security-solution-plugin/common/api/timeline/delete_timelines/delete_timelines_route.gen'; import { DeprecatedTriggerRiskScoreCalculationRequestBodyInput } from '@kbn/security-solution-plugin/common/api/entity_analytics/risk_engine/entity_calculation_route.gen'; -import { EndpointIsolateRedirectRequestBodyInput } from '@kbn/security-solution-plugin/common/api/endpoint/actions/isolate_route.gen'; -import { EndpointUnisolateRedirectRequestBodyInput } from '@kbn/security-solution-plugin/common/api/endpoint/actions/unisolate_route.gen'; +import { EndpointExecuteActionRequestBodyInput } from '@kbn/security-solution-plugin/common/api/endpoint/actions/response_actions/execute/execute.gen'; +import { EndpointFileDownloadRequestParamsInput } from '@kbn/security-solution-plugin/common/api/endpoint/actions/file_download/file_download.gen'; +import { EndpointFileInfoRequestParamsInput } from '@kbn/security-solution-plugin/common/api/endpoint/actions/file_info/file_info.gen'; +import { EndpointGetActionsDetailsRequestParamsInput } from '@kbn/security-solution-plugin/common/api/endpoint/actions/details/details.gen'; +import { EndpointGetActionsListRequestQueryInput } from '@kbn/security-solution-plugin/common/api/endpoint/actions/list/list.gen'; +import { EndpointGetActionsStatusRequestQueryInput } from '@kbn/security-solution-plugin/common/api/endpoint/actions/status/status.gen'; +import { EndpointGetFileActionRequestBodyInput } from '@kbn/security-solution-plugin/common/api/endpoint/actions/response_actions/get_file/get_file.gen'; +import { EndpointGetProcessesActionRequestBodyInput } from '@kbn/security-solution-plugin/common/api/endpoint/actions/response_actions/running_procs/running_procs.gen'; +import { EndpointIsolateActionRequestBodyInput } from '@kbn/security-solution-plugin/common/api/endpoint/actions/response_actions/isolate/isolate.gen'; +import { EndpointIsolateRedirectRequestBodyInput } from '@kbn/security-solution-plugin/common/api/endpoint/actions/response_actions/isolate/deprecated_isolate.gen'; +import { EndpointKillProcessActionRequestBodyInput } from '@kbn/security-solution-plugin/common/api/endpoint/actions/response_actions/kill_process/kill_process.gen'; +import { EndpointScanActionRequestBodyInput } from '@kbn/security-solution-plugin/common/api/endpoint/actions/response_actions/scan/scan.gen'; +import { EndpointSuspendProcessActionRequestBodyInput } from '@kbn/security-solution-plugin/common/api/endpoint/actions/response_actions/suspend_process/suspend_process.gen'; +import { EndpointUnisolateActionRequestBodyInput } from '@kbn/security-solution-plugin/common/api/endpoint/actions/response_actions/unisolate/unisolate.gen'; +import { EndpointUnisolateRedirectRequestBodyInput } from '@kbn/security-solution-plugin/common/api/endpoint/actions/response_actions/unisolate/deprecated_unisolate.gen'; +import { EndpointUploadActionRequestBodyInput } from '@kbn/security-solution-plugin/common/api/endpoint/actions/response_actions/upload/upload.gen'; import { ExportRulesRequestQueryInput, ExportRulesRequestBodyInput, @@ -54,15 +68,16 @@ import { import { FinalizeAlertsMigrationRequestBodyInput } from '@kbn/security-solution-plugin/common/api/detection_engine/signals_migration/finalize_signals_migration/finalize_signals_migration.gen'; import { FindAssetCriticalityRecordsRequestQueryInput } from '@kbn/security-solution-plugin/common/api/entity_analytics/asset_criticality/list_asset_criticality.gen'; import { FindRulesRequestQueryInput } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management/find_rules/find_rules_route.gen'; -import { GetAgentPolicySummaryRequestQueryInput } from '@kbn/security-solution-plugin/common/api/endpoint/policy/policy.gen'; +import { GetAgentPolicySummaryRequestQueryInput } from '@kbn/security-solution-plugin/common/api/endpoint/policy/deprecated_agent_policy_summary.gen'; import { GetAssetCriticalityRecordRequestQueryInput } from '@kbn/security-solution-plugin/common/api/entity_analytics/asset_criticality/get_asset_criticality.gen'; import { GetDraftTimelinesRequestQueryInput } from '@kbn/security-solution-plugin/common/api/timeline/get_draft_timelines/get_draft_timelines_route.gen'; +import { GetEndpointMetadataListRequestQueryInput } from '@kbn/security-solution-plugin/common/api/endpoint/metadata/get_metadata.gen'; import { GetEndpointSuggestionsRequestParamsInput, GetEndpointSuggestionsRequestBodyInput, } from '@kbn/security-solution-plugin/common/api/endpoint/suggestions/get_suggestions.gen'; import { GetNotesRequestQueryInput } from '@kbn/security-solution-plugin/common/api/timeline/get_notes/get_notes_route.gen'; -import { GetPolicyResponseRequestQueryInput } from '@kbn/security-solution-plugin/common/api/endpoint/policy/policy.gen'; +import { GetPolicyResponseRequestQueryInput } from '@kbn/security-solution-plugin/common/api/endpoint/policy/policy_response.gen'; import { GetProtectionUpdatesNoteRequestParamsInput } from '@kbn/security-solution-plugin/common/api/endpoint/protection_updates_note/protection_updates_note.gen'; import { GetRuleExecutionEventsRequestQueryInput, @@ -341,6 +356,114 @@ Migrations are initiated per index. While the process is neither destructive nor .set(ELASTIC_HTTP_VERSION_HEADER, '1') .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana'); }, + /** + * Execute a given command on an endpoint + */ + endpointExecuteAction(props: EndpointExecuteActionProps) { + return supertest + .post('/api/endpoint/action/execute') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .send(props.body as object); + }, + /** + * Download a file from an endpoint + */ + endpointFileDownload(props: EndpointFileDownloadProps) { + return supertest + .get( + replaceParams( + '/api/endpoint/action/{action_id}/file/{file_id}/download`', + props.params + ) + ) + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana'); + }, + /** + * Get file info + */ + endpointFileInfo(props: EndpointFileInfoProps) { + return supertest + .get(replaceParams('/api/endpoint/action/{action_id}/file/{file_id}`', props.params)) + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana'); + }, + /** + * Get action details + */ + endpointGetActionsDetails(props: EndpointGetActionsDetailsProps) { + return supertest + .get(replaceParams('/api/endpoint/action/{action_id}', props.params)) + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana'); + }, + /** + * Get a list of action requests and their responses + */ + endpointGetActionsList(props: EndpointGetActionsListProps) { + return supertest + .get('/api/endpoint/action') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .query(props.query); + }, + endpointGetActionsState() { + return supertest + .get('/api/endpoint/action/state') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana'); + }, + /** + * Get action status + */ + endpointGetActionsStatus(props: EndpointGetActionsStatusProps) { + return supertest + .get('/api/endpoint/action_status') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .query(props.query); + }, + /** + * Get a file from an endpoint + */ + endpointGetFileAction(props: EndpointGetFileActionProps) { + return supertest + .post('/api/endpoint/action/get_file') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .send(props.body as object); + }, + /** + * Get list of running processes on an endpoint + */ + endpointGetProcessesAction(props: EndpointGetProcessesActionProps) { + return supertest + .post('/api/endpoint/action/running_procs') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .send(props.body as object); + }, + /** + * Isolate an endpoint + */ + endpointIsolateAction(props: EndpointIsolateActionProps) { + return supertest + .post('/api/endpoint/action/isolate') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .send(props.body as object); + }, endpointIsolateRedirect(props: EndpointIsolateRedirectProps) { return supertest .post('/api/endpoint/isolate') @@ -349,6 +472,50 @@ Migrations are initiated per index. While the process is neither destructive nor .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .send(props.body as object); }, + /** + * Kill a running process on an endpoint + */ + endpointKillProcessAction(props: EndpointKillProcessActionProps) { + return supertest + .post('/api/endpoint/action/kill_process') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .send(props.body as object); + }, + /** + * Scan a file or directory + */ + endpointScanAction(props: EndpointScanActionProps) { + return supertest + .post('/api/endpoint/action/scan') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .send(props.body as object); + }, + /** + * Suspend a running process on an endpoint + */ + endpointSuspendProcessAction(props: EndpointSuspendProcessActionProps) { + return supertest + .post('/api/endpoint/action/suspend_process') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .send(props.body as object); + }, + /** + * Release an endpoint + */ + endpointUnisolateAction(props: EndpointUnisolateActionProps) { + return supertest + .post('/api/endpoint/action/unisolate') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .send(props.body as object); + }, endpointUnisolateRedirect(props: EndpointUnisolateRedirectProps) { return supertest .post('/api/endpoint/unisolate') @@ -357,6 +524,17 @@ Migrations are initiated per index. While the process is neither destructive nor .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .send(props.body as object); }, + /** + * Upload a file to an endpoint + */ + endpointUploadAction(props: EndpointUploadActionProps) { + return supertest + .post('/api/endpoint/action/upload') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .send(props.body as object); + }, /** * Export detection rules to an `.ndjson` file. The following configuration items are also included in the `.ndjson` file: - Actions @@ -447,6 +625,14 @@ finalize it. .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .query(props.query); }, + getEndpointMetadataList(props: GetEndpointMetadataListProps) { + return supertest + .get('/api/endpoint/metadata') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .query(props.query); + }, getEndpointSuggestions(props: GetEndpointSuggestionsProps) { return supertest .post(replaceParams('/api/endpoint/suggestions/{suggestion_type}', props.params)) @@ -899,12 +1085,54 @@ export interface DeleteTimelinesProps { export interface DeprecatedTriggerRiskScoreCalculationProps { body: DeprecatedTriggerRiskScoreCalculationRequestBodyInput; } +export interface EndpointExecuteActionProps { + body: EndpointExecuteActionRequestBodyInput; +} +export interface EndpointFileDownloadProps { + params: EndpointFileDownloadRequestParamsInput; +} +export interface EndpointFileInfoProps { + params: EndpointFileInfoRequestParamsInput; +} +export interface EndpointGetActionsDetailsProps { + params: EndpointGetActionsDetailsRequestParamsInput; +} +export interface EndpointGetActionsListProps { + query: EndpointGetActionsListRequestQueryInput; +} +export interface EndpointGetActionsStatusProps { + query: EndpointGetActionsStatusRequestQueryInput; +} +export interface EndpointGetFileActionProps { + body: EndpointGetFileActionRequestBodyInput; +} +export interface EndpointGetProcessesActionProps { + body: EndpointGetProcessesActionRequestBodyInput; +} +export interface EndpointIsolateActionProps { + body: EndpointIsolateActionRequestBodyInput; +} export interface EndpointIsolateRedirectProps { body: EndpointIsolateRedirectRequestBodyInput; } +export interface EndpointKillProcessActionProps { + body: EndpointKillProcessActionRequestBodyInput; +} +export interface EndpointScanActionProps { + body: EndpointScanActionRequestBodyInput; +} +export interface EndpointSuspendProcessActionProps { + body: EndpointSuspendProcessActionRequestBodyInput; +} +export interface EndpointUnisolateActionProps { + body: EndpointUnisolateActionRequestBodyInput; +} export interface EndpointUnisolateRedirectProps { body: EndpointUnisolateRedirectRequestBodyInput; } +export interface EndpointUploadActionProps { + body: EndpointUploadActionRequestBodyInput; +} export interface ExportRulesProps { query: ExportRulesRequestQueryInput; body: ExportRulesRequestBodyInput; @@ -931,6 +1159,9 @@ export interface GetAssetCriticalityRecordProps { export interface GetDraftTimelinesProps { query: GetDraftTimelinesRequestQueryInput; } +export interface GetEndpointMetadataListProps { + query: GetEndpointMetadataListRequestQueryInput; +} export interface GetEndpointSuggestionsProps { params: GetEndpointSuggestionsRequestParamsInput; body: GetEndpointSuggestionsRequestBodyInput; diff --git a/x-pack/test/common/utils/security_solution/detections_response/rules/get_rule_for_alert_testing.ts b/x-pack/test/common/utils/security_solution/detections_response/rules/get_rule_for_alert_testing.ts index 5649031185feb..5c0500f89ef51 100644 --- a/x-pack/test/common/utils/security_solution/detections_response/rules/get_rule_for_alert_testing.ts +++ b/x-pack/test/common/utils/security_solution/detections_response/rules/get_rule_for_alert_testing.ts @@ -30,3 +30,18 @@ export const getRuleForAlertTesting = ( query: '*:*', from: '1900-01-01T00:00:00.000Z', }); + +export const getLuceneRuleForTesting = (): QueryRuleCreateProps => ({ + rule_id: 'lucene-rule-1', + enabled: true, + name: 'Incident 496 test rule', + description: 'Ensures lucene rules generate alerts', + risk_score: 1, + severity: 'high', + type: 'query', + index: ['auditbeat-*'], + query: + '((event.category: (network OR network_traffic) AND type: (tls OR http)) OR event.dataset: (network_traffic.tls OR network_traffic.http)) AND destination.domain:/[a-z]{3}.stage.[0-9]{8}..*/', + language: 'lucene', + from: '1900-01-01T00:00:00.000Z', +}); diff --git a/x-pack/test/functional/apps/observability_logs_explorer/columns_selection.ts b/x-pack/test/functional/apps/observability_logs_explorer/columns_selection.ts index b944ebd9b8306..3c87d53031aa4 100644 --- a/x-pack/test/functional/apps/observability_logs_explorer/columns_selection.ts +++ b/x-pack/test/functional/apps/observability_logs_explorer/columns_selection.ts @@ -85,7 +85,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('render content virtual column properly', async () => { it('should render log level and log message when present', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(0, 4); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(0, 2); const cellValue = await cellElement.getVisibleText(); expect(cellValue.includes('info')).to.be(true); expect(cellValue.includes('A sample log')).to.be(true); @@ -94,7 +94,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render log message when present and skip log level when missing', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(1, 4); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(1, 2); const cellValue = await cellElement.getVisibleText(); expect(cellValue.includes('info')).to.be(false); expect(cellValue.includes('A sample log')).to.be(true); @@ -103,7 +103,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render message from error object when top level message not present', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(2, 4); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(2, 2); const cellValue = await cellElement.getVisibleText(); expect(cellValue.includes('info')).to.be(true); expect(cellValue.includes('error.message')).to.be(true); @@ -113,7 +113,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render message from event.original when top level message and error.message not present', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(3, 4); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(3, 2); const cellValue = await cellElement.getVisibleText(); expect(cellValue.includes('info')).to.be(true); expect(cellValue.includes('event.original')).to.be(true); @@ -123,7 +123,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render the whole JSON when neither message, error.message and event.original are present', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(4, 4); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(4, 2); const cellValue = await cellElement.getVisibleText(); expect(cellValue.includes('info')).to.be(true); @@ -137,7 +137,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('on cell expansion with no message field should open JSON Viewer', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - await dataGrid.clickCellExpandButton(4, 4); + await dataGrid.clickCellExpandButtonExcludingControlColumns(4, 2); await testSubjects.existOrFail('dataTableExpandCellActionJsonPopover'); }); }); @@ -145,7 +145,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('on cell expansion with message field should open regular popover', async () => { await navigateToLogsExplorer(); await retry.tryForTime(TEST_TIMEOUT, async () => { - await dataGrid.clickCellExpandButton(3, 4); + await dataGrid.clickCellExpandButtonExcludingControlColumns(3, 2); await testSubjects.existOrFail('euiDataGridExpansionPopover'); }); }); @@ -154,7 +154,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('render resource virtual column properly', async () => { it('should render service name and host name when present', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(0, 3); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(0, 1); const cellValue = await cellElement.getVisibleText(); expect(cellValue.includes('synth-service')).to.be(true); expect(cellValue.includes('synth-host')).to.be(true); @@ -168,7 +168,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should render a popover with cell actions when a chip on content column is clicked', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(0, 4); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(0, 2); const logLevelChip = await cellElement.findByTestSubject('*logLevelBadge-'); await logLevelChip.click(); // Check Filter In button is present @@ -182,7 +182,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render the table filtered where log.level value is info when filter in action is clicked', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(0, 4); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(0, 2); const logLevelChip = await cellElement.findByTestSubject('*logLevelBadge-'); const actionSelector = 'dataTableCellAction_addToFilterAction_log.level'; @@ -203,7 +203,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render the table filtered where log.level value is not info when filter out action is clicked', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(0, 4); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(0, 2); const logLevelChip = await cellElement.findByTestSubject('*logLevelBadge-'); const actionSelector = 'dataTableCellAction_removeFromFilterAction_log.level'; @@ -222,7 +222,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render the table filtered where service.name value is selected', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(0, 3); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(0, 1); const serviceNameChip = await cellElement.findByTestSubject( 'dataTablePopoverChip_service.name' ); diff --git a/x-pack/test/functional/apps/observability_logs_explorer/custom_control_columns.ts b/x-pack/test/functional/apps/observability_logs_explorer/custom_control_columns.ts index 21dd8a6772bb7..58b123d08cdaf 100644 --- a/x-pack/test/functional/apps/observability_logs_explorer/custom_control_columns.ts +++ b/x-pack/test/functional/apps/observability_logs_explorer/custom_control_columns.ts @@ -47,7 +47,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render control column with proper header', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { // First control column has no title, so empty string, leading control column has title - expect(await dataGrid.getControlColumnHeaderFields()).to.eql(['', 'actions']); + expect(await dataGrid.getControlColumnHeaderFields()).to.eql(['', '', '', '']); }); }); @@ -61,7 +61,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render the degraded icon in the leading control column if degraded doc exists', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(1, 1); + const cellElement = await dataGrid.getCellElement(1, 2); const degradedButton = await cellElement.findByTestSubject('docTableDegradedDocExist'); expect(degradedButton).to.not.be.empty(); }); @@ -69,7 +69,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render the disabled degraded icon in the leading control column when degraded doc does not exists', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(0, 1); + const cellElement = await dataGrid.getCellElement(0, 2); const degradedDisableButton = await cellElement.findByTestSubject( 'docTableDegradedDocDoesNotExist' ); @@ -79,7 +79,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render the stacktrace icon in the leading control column when stacktrace exists', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(4, 1); + const cellElement = await dataGrid.getCellElement(4, 3); const stacktraceButton = await cellElement.findByTestSubject('docTableStacktraceExist'); expect(stacktraceButton).to.not.be.empty(); }); @@ -87,7 +87,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render the stacktrace icon disabled in the leading control column when stacktrace does not exists', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(1, 1); + const cellElement = await dataGrid.getCellElement(1, 3); const stacktraceButton = await cellElement.findByTestSubject( 'docTableStacktraceDoesNotExist' ); diff --git a/x-pack/test/functional/es_archives/auditbeat/hosts/data.json.gz b/x-pack/test/functional/es_archives/auditbeat/hosts/data.json.gz index 00c6963b16937..b400db7f4540d 100644 Binary files a/x-pack/test/functional/es_archives/auditbeat/hosts/data.json.gz and b/x-pack/test/functional/es_archives/auditbeat/hosts/data.json.gz differ diff --git a/x-pack/test/scalability/config.ts b/x-pack/test/scalability/config.ts index ebfa91d4ce1e2..3315276a688ca 100644 --- a/x-pack/test/scalability/config.ts +++ b/x-pack/test/scalability/config.ts @@ -11,8 +11,8 @@ import path from 'path'; // @ts-expect-error we have to check types with "allowJs: false" for now, causing this import to fail import { REPO_ROOT } from '@kbn/repo-info'; import { createFlagError } from '@kbn/dev-cli-errors'; -import { commonFunctionalServices } from '@kbn/ftr-common-functional-services'; import { v4 as uuidV4 } from 'uuid'; +import { services } from './services'; import { ScalabilityTestRunner } from './runner'; import { FtrProviderContext } from './ftr_provider_context'; import { ScalabilityJourney } from './types'; @@ -49,7 +49,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { return { ...baseConfig, - services: commonFunctionalServices, + services, pageObjects: {}, testRunner: (context: FtrProviderContext) => diff --git a/x-pack/test/scalability/ftr_provider_context.ts b/x-pack/test/scalability/ftr_provider_context.ts index 19c510a9ec8e7..82dba4367104b 100644 --- a/x-pack/test/scalability/ftr_provider_context.ts +++ b/x-pack/test/scalability/ftr_provider_context.ts @@ -5,8 +5,8 @@ * 2.0. */ -import { commonFunctionalServices } from '@kbn/ftr-common-functional-services'; import { GenericFtrProviderContext, GenericFtrService } from '@kbn/test'; +import { services } from './services'; -export type FtrProviderContext = GenericFtrProviderContext; +export type FtrProviderContext = GenericFtrProviderContext; export class FtrService extends GenericFtrService {} diff --git a/x-pack/test/scalability/services.ts b/x-pack/test/scalability/services.ts new file mode 100644 index 0000000000000..5708fb19f7e57 --- /dev/null +++ b/x-pack/test/scalability/services.ts @@ -0,0 +1,17 @@ +/* + * 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 { commonFunctionalServices } from '@kbn/ftr-common-functional-services'; + +const { es, esArchiver, kibanaServer, retry, supertestWithoutAuth } = commonFunctionalServices; +export const services = { + es, + esArchiver, + kibanaServer, + retry, + supertestWithoutAuth, +}; diff --git a/x-pack/test/security_api_integration/packages/helpers/saml/idp_metadata_mock_idp.xml b/x-pack/test/security_api_integration/packages/helpers/saml/idp_metadata_mock_idp.xml new file mode 100644 index 0000000000000..1049f4392d9ba --- /dev/null +++ b/x-pack/test/security_api_integration/packages/helpers/saml/idp_metadata_mock_idp.xml @@ -0,0 +1,41 @@ + + + + + + + + MIIDYjCCAkqgAwIBAgIUZ2p8K7GMXGk6xwCS9S91BUl1JnAwDQYJKoZIhvcNAQEL +BQAwNDEyMDAGA1UEAxMpRWxhc3RpYyBDZXJ0aWZpY2F0ZSBUb29sIEF1dG9nZW5l +cmF0ZWQgQ0EwIBcNMjMwOTIzMTUyMDE0WhgPMjA3MzA5MTAxNTIwMTRaMBExDzAN +BgNVBAMTBmtpYmFuYTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMOU +r52dbZ5dY0BoP2p7CEnOpG+qHTNrOAqZO/OJfniPMtpGmwAMl3WZDca6u2XkV2KE +qQyevQ2ADk6G3o8S2RU8mO/+UweuCDF7LHuSdxEGTpucidZErmVhEGUOFosL5UeB +AtIDWxvWwgK+W9Yzt5IEN2HzNCZ6h0dOSk2r9EjVMG5yF4Q6kuqOYxBT7jxoaOtO +OCrgBRummtUga4T13WZ/ZIyyHpXj2+JD4YEmrDyoTa7NLaphv0hnVhHXYoYBI/c6 +2SwwAoBlmtDmlinwSACQ3o/8eLWk0tqkIP14rc3oFh3m7D2c3c2m2HXuyoSDMfGW +beG2IE1Q3idcGmeG3qsCAwEAAaOBjDCBiTAdBgNVHQ4EFgQUMOUM7w5jmIozDvnq +RpM779m5GigwHwYDVR0jBBgwFoAUMEwqwI5b0MYpNxwaHJ9Tw1Lp3p4wPAYDVR0R +BDUwM4IUaG9zdC5kb2NrZXIuaW50ZXJuYWyCCWxvY2FsaG9zdIIEZXMwM4IEZXMw +MoIEZXMwMTAJBgNVHRMEAjAAMA0GCSqGSIb3DQEBCwUAA4IBAQCxqvQYXSKqgpdl +SP4gXgwipAnYsoW9qkgWQODTvSBEzUdOWme0d3j7i2l6Ur/nVSv5YjkqAv1hf/yJ +Hrk9h+j29ZO/aQ/KDh5i/gTEUnPw3Bxbw47dfn23tjMWO7NCU1fr5HNztRsa/gQr +e9s07g25u/gTfTi9Fyu0lcRe3bXOLS/mFVcuC5oxuS65R9OlbIsiORkZ2EfwuNUf +wAAYOGPIjM2VlQCvBitefsd/SzRKHdxSPy6KSjkO6MGEGo87fr7B7Nx1qp1DVrK7 +q9XeP1Cuygjg9WTcnsvWvNw8CssyuFM6X/3tGjpPasXwLvNUoG2AairK2AYTWhvS +foE31cFg + + + + + + + + + diff --git a/x-pack/test/security_solution_api_integration/config/serverless/services_edr_workflows.ts b/x-pack/test/security_solution_api_integration/config/serverless/services_edr_workflows.ts index 299d1a509f55e..6c50ff3500050 100644 --- a/x-pack/test/security_solution_api_integration/config/serverless/services_edr_workflows.ts +++ b/x-pack/test/security_solution_api_integration/config/serverless/services_edr_workflows.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { SvlUserManagerProvider } from '@kbn/test-suites-serverless/shared/services/svl_user_manager'; +import { commonFunctionalServices } from '@kbn/ftr-common-functional-services'; import { SvlCommonApiServiceProvider } from '@kbn/test-suites-serverless/shared/services/svl_common_api'; import { services as essServices } from '../ess/services_edr_workflows'; import { SecuritySolutionServerlessSuperTest } from '../services/security_solution_serverless_supertest'; @@ -15,6 +15,6 @@ export const svlServices = { ...essServices, supertest: SecuritySolutionServerlessSuperTest, securitySolutionUtils: SecuritySolutionServerlessUtils, - svlUserManager: SvlUserManagerProvider, + svlUserManager: commonFunctionalServices.samlAuth, svlCommonApi: SvlCommonApiServiceProvider, }; diff --git a/x-pack/test/security_solution_api_integration/config/services/security_solution_serverless_utils.ts b/x-pack/test/security_solution_api_integration/config/services/security_solution_serverless_utils.ts index da57ccf64860e..15b2699acbedb 100644 --- a/x-pack/test/security_solution_api_integration/config/services/security_solution_serverless_utils.ts +++ b/x-pack/test/security_solution_api_integration/config/services/security_solution_serverless_utils.ts @@ -32,6 +32,8 @@ export function SecuritySolutionServerlessUtils({ }); async function invalidateApiKey(credentials: RoleCredentials) { + // load service to call it outside mocha context + await svlUserManager.init(); await svlUserManager.invalidateM2mApiKeyWithRoleScope(credentials); } @@ -53,6 +55,8 @@ export function SecuritySolutionServerlessUtils({ const createSuperTest = async (role = 'admin') => { cleanCredentials(role); + // load service to call it outside mocha context + await svlUserManager.init(); const credentials = await svlUserManager.createM2mApiKeyWithRoleScope(role); rolesCredentials.set(role, credentials); @@ -62,6 +66,8 @@ export function SecuritySolutionServerlessUtils({ return { getUsername: async (role = 'admin') => { + // load service to call it outside mocha context + await svlUserManager.init(); const { username } = await svlUserManager.getUserData(role); return username; diff --git a/x-pack/test/security_solution_api_integration/es_archive/serverless/auditbeat/hosts/data.json.gz b/x-pack/test/security_solution_api_integration/es_archive/serverless/auditbeat/hosts/data.json.gz index 00c6963b16937..1bb35f37877b6 100644 Binary files a/x-pack/test/security_solution_api_integration/es_archive/serverless/auditbeat/hosts/data.json.gz and b/x-pack/test/security_solution_api_integration/es_archive/serverless/auditbeat/hosts/data.json.gz differ diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/custom_query.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/custom_query.ts index 2b66bc6ca49bb..0fd9938dd1999 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/custom_query.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/custom_query.ts @@ -69,6 +69,7 @@ import { deleteAllRules, deleteAllAlerts, getRuleForAlertTesting, + getLuceneRuleForTesting, } from '../../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../../ftr_provider_context'; @@ -117,7 +118,10 @@ export default ({ getService }: FtrProviderContext) => { after(async () => { await esArchiver.unload(auditbeatPath); await esArchiver.unload('x-pack/test/functional/es_archives/signals/severity_risk_overrides'); - await deleteAllAlerts(supertest, log, es, ['.preview.alerts-security.alerts-*']); + await deleteAllAlerts(supertest, log, es, [ + '.preview.alerts-security.alerts-*', + '.alerts-security.alerts-*', + ]); await deleteAllRules(supertest, log); }); @@ -2750,5 +2754,18 @@ export default ({ getService }: FtrProviderContext) => { }); }); }); + + describe('with a Lucene query rule', () => { + it('should run successfully and generate an alert that matches the lucene query', async () => { + const luceneQueryRule = getLuceneRuleForTesting(); + const { previewId } = await previewRule({ supertest, rule: luceneQueryRule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).toBeGreaterThan(0); + expect(previewAlerts[0]?._source?.destination).toEqual( + expect.objectContaining({ domain: 'aaa.stage.11111111.hello' }) + ); + expect(previewAlerts[0]?._source?.['event.dataset']).toEqual('network_traffic.tls'); + }); + }); }); }; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/threshold.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/threshold.ts index c1681d65c05f5..0ea928d67b491 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/threshold.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/threshold.ts @@ -133,7 +133,7 @@ export default ({ getService }: FtrProviderContext) => { field: 'host.id', value: 1, // This value generates 7 alerts with the current esArchive }, - max_signals: 7, + max_signals: 8, }; const { logs } = await previewRule({ supertest, rule }); expect(logs[0].warnings).not.toContain(getMaxAlertsWarning()); diff --git a/x-pack/test/security_solution_cypress/cypress/support/saml_auth.ts b/x-pack/test/security_solution_cypress/cypress/support/saml_auth.ts index b6701a8b4b553..1b5c2adcae455 100644 --- a/x-pack/test/security_solution_cypress/cypress/support/saml_auth.ts +++ b/x-pack/test/security_solution_cypress/cypress/support/saml_auth.ts @@ -9,6 +9,8 @@ import { ToolingLog } from '@kbn/tooling-log'; import { SecurityRoleName } from '@kbn/security-solution-plugin/common/test'; import { HostOptions, SamlSessionManager } from '@kbn/test'; +import { REPO_ROOT } from '@kbn/repo-info'; +import { resolve } from 'path'; import { DEFAULT_SERVERLESS_ROLE } from '../env_var_names_constants'; export const samlAuthentication = async ( @@ -31,30 +33,27 @@ export const samlAuthentication = async ( // If config.env.PROXY_ORG is set, it means that proxy service is used to create projects. Define the proxy org filename to override the roles. const rolesFilename = config.env.PROXY_ORG ? `${config.env.PROXY_ORG}.json` : undefined; + const cloudUsersFilePath = resolve(REPO_ROOT, '.ftr', rolesFilename ?? 'role_users.json'); on('task', { getSessionCookie: async (role: string | SecurityRoleName): Promise => { - const sessionManager = new SamlSessionManager( - { - hostOptions, - log, - isCloud: config.env.CLOUD_SERVERLESS, - }, - rolesFilename - ); + const sessionManager = new SamlSessionManager({ + hostOptions, + log, + isCloud: config.env.CLOUD_SERVERLESS, + cloudUsersFilePath, + }); return sessionManager.getInteractiveUserSessionCookieWithRoleScope(role); }, getFullname: async ( role: string | SecurityRoleName = DEFAULT_SERVERLESS_ROLE ): Promise => { - const sessionManager = new SamlSessionManager( - { - hostOptions, - log, - isCloud: config.env.CLOUD_SERVERLESS, - }, - rolesFilename - ); + const sessionManager = new SamlSessionManager({ + hostOptions, + log, + isCloud: config.env.CLOUD_SERVERLESS, + cloudUsersFilePath, + }); const { full_name: fullName } = await sessionManager.getUserData(role); return fullName; }, diff --git a/x-pack/test/security_solution_cypress/cypress/tsconfig.json b/x-pack/test/security_solution_cypress/cypress/tsconfig.json index d07d03536f7f4..b7223115bbcb6 100644 --- a/x-pack/test/security_solution_cypress/cypress/tsconfig.json +++ b/x-pack/test/security_solution_cypress/cypress/tsconfig.json @@ -42,5 +42,6 @@ "@kbn/actions-plugin", "@kbn/alerts-ui-shared", "@kbn/securitysolution-endpoint-exceptions-common", + "@kbn/repo-info", ] } diff --git a/x-pack/test/security_solution_endpoint/services/index.ts b/x-pack/test/security_solution_endpoint/services/index.ts index c9b9d59fa56f6..666378e4492c5 100644 --- a/x-pack/test/security_solution_endpoint/services/index.ts +++ b/x-pack/test/security_solution_endpoint/services/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { SvlUserManagerProvider } from '@kbn/test-suites-serverless/shared/services/svl_user_manager'; +import { commonFunctionalServices } from '@kbn/ftr-common-functional-services'; import { SvlCommonApiServiceProvider } from '@kbn/test-suites-serverless/shared/services/svl_common_api'; import { services as xPackFunctionalServices } from '../../functional/services'; import { IngestManagerProvider } from '../../common/services/ingest_manager'; @@ -43,7 +43,7 @@ export const svlServices = { supertestWithoutAuth: KibanaSupertestWithCertWithoutAuthProvider, svlCommonApi: SvlCommonApiServiceProvider, - svlUserManager: SvlUserManagerProvider, + svlUserManager: commonFunctionalServices.samlAuth, }; export type Services = typeof services | typeof svlServices; diff --git a/x-pack/test/security_solution_endpoint/tsconfig.json b/x-pack/test/security_solution_endpoint/tsconfig.json index 6b35306a0d693..e4ce04de12a59 100644 --- a/x-pack/test/security_solution_endpoint/tsconfig.json +++ b/x-pack/test/security_solution_endpoint/tsconfig.json @@ -27,5 +27,6 @@ "@kbn/ftr-common-functional-ui-services", "@kbn/test", "@kbn/test-subj-selector", + "@kbn/ftr-common-functional-services", ] } diff --git a/x-pack/test/tsconfig.json b/x-pack/test/tsconfig.json index 3449df8d6f23c..974a206d71e3f 100644 --- a/x-pack/test/tsconfig.json +++ b/x-pack/test/tsconfig.json @@ -176,6 +176,7 @@ "@kbn/osquery-plugin", "@kbn/entities-schema", "@kbn/actions-simulators-plugin", - "@kbn/cases-api-integration-test-plugin" + "@kbn/cases-api-integration-test-plugin", + "@kbn/mock-idp-utils" ] } diff --git a/x-pack/test_serverless/api_integration/test_suites/common/console/autocomplete_entities.ts b/x-pack/test_serverless/api_integration/test_suites/common/console/autocomplete_entities.ts index 64adeea57a9be..72640103c0ef9 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/console/autocomplete_entities.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/console/autocomplete_entities.ts @@ -7,8 +7,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; -import { RoleCredentials } from '../../../../shared/services'; -import { InternalRequestHeader } from '../../../../shared/services/svl_common_api'; +import { InternalRequestHeader, RoleCredentials } from '../../../../shared/services'; export default ({ getService }: FtrProviderContext) => { const svlCommonApi = getService('svlCommonApi'); diff --git a/x-pack/test_serverless/api_integration/test_suites/common/platform_security/response_headers.ts b/x-pack/test_serverless/api_integration/test_suites/common/platform_security/response_headers.ts index 97afa8213aa4f..562e98d33866a 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/platform_security/response_headers.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/platform_security/response_headers.ts @@ -17,9 +17,6 @@ export default function ({ getService }: FtrProviderContext) { let roleAuthc: RoleCredentials; describe('security/response_headers', function () { - // fails on MKI, see https://github.com/elastic/kibana/issues/188714 - this.tags(['failsOnMKI']); - const baseCSP = `script-src 'report-sample' 'self'; worker-src 'report-sample' 'self' blob:; style-src 'report-sample' 'self' 'unsafe-inline'; frame-ancestors 'self'`; const defaultCOOP = 'same-origin'; const defaultPermissionsPolicy = diff --git a/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/extensions/_get_row_additional_leading_controls.ts b/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/extensions/_get_row_additional_leading_controls.ts new file mode 100644 index 0000000000000..c91dae10bc4ea --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/extensions/_get_row_additional_leading_controls.ts @@ -0,0 +1,66 @@ +/* + * 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 kbnRison from '@kbn/rison'; +import type { FtrProviderContext } from '../../../../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const PageObjects = getPageObjects(['common', 'discover', 'svlCommonPage']); + const testSubjects = getService('testSubjects'); + const dataViews = getService('dataViews'); + + describe('extension getRowAdditionalLeadingControls', () => { + before(async () => { + await PageObjects.svlCommonPage.loginAsAdmin(); + }); + describe('ES|QL mode', () => { + it('should render logs controls for logs data source', async () => { + const state = kbnRison.encode({ + dataSource: { type: 'esql' }, + query: { esql: 'from my-example-logs | sort @timestamp desc' }, + }); + await PageObjects.common.navigateToApp('discover', { + hash: `/?_a=${state}`, + }); + await PageObjects.discover.waitUntilSearchingHasFinished(); + await testSubjects.existOrFail('exampleLogsControl_visBarVerticalStacked'); + await testSubjects.existOrFail('unifiedDataTable_additionalRowControl_menuControl'); + }); + + it('should not render logs controls for non-logs data source', async () => { + const state = kbnRison.encode({ + dataSource: { type: 'esql' }, + query: { esql: 'from my-example-metrics | sort @timestamp desc' }, + }); + await PageObjects.common.navigateToApp('discover', { + hash: `/?_a=${state}`, + }); + await PageObjects.discover.waitUntilSearchingHasFinished(); + await testSubjects.missingOrFail('exampleLogsControl_visBarVerticalStacked'); + await testSubjects.missingOrFail('unifiedDataTable_additionalRowControl_menuControl'); + }); + }); + + describe('data view mode', () => { + it('should render logs controls for logs data source', async () => { + await PageObjects.common.navigateToApp('discover'); + await dataViews.switchTo('my-example-logs'); + await PageObjects.discover.waitUntilSearchingHasFinished(); + await testSubjects.existOrFail('exampleLogsControl_visBarVerticalStacked'); + await testSubjects.existOrFail('unifiedDataTable_additionalRowControl_menuControl'); + }); + + it('should not render logs controls for non-logs data source', async () => { + await PageObjects.common.navigateToApp('discover'); + await dataViews.switchTo('my-example-metrics'); + await PageObjects.discover.waitUntilSearchingHasFinished(); + await testSubjects.missingOrFail('exampleLogsControl_visBarVerticalStacked'); + await testSubjects.missingOrFail('unifiedDataTable_additionalRowControl_menuControl'); + }); + }); + }); +} diff --git a/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/index.ts b/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/index.ts index e8c8f1234aab5..d0e23c825870b 100644 --- a/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/index.ts +++ b/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/index.ts @@ -38,6 +38,7 @@ export default function ({ getService, getPageObjects, loadTestFile }: FtrProvid loadTestFile(require.resolve('./_root_profile')); loadTestFile(require.resolve('./_data_source_profile')); loadTestFile(require.resolve('./extensions/_get_row_indicator_provider')); + loadTestFile(require.resolve('./extensions/_get_row_additional_leading_controls')); loadTestFile(require.resolve('./extensions/_get_doc_viewer')); loadTestFile(require.resolve('./extensions/_get_cell_renderers')); loadTestFile(require.resolve('./extensions/_get_default_app_state')); diff --git a/x-pack/test_serverless/functional/test_suites/common/discover/esql/_esql_view.ts b/x-pack/test_serverless/functional/test_suites/common/discover/esql/_esql_view.ts index 80fff5ef76014..0d1e2b3de6a02 100644 --- a/x-pack/test_serverless/functional/test_suites/common/discover/esql/_esql_view.ts +++ b/x-pack/test_serverless/functional/test_suites/common/discover/esql/_esql_view.ts @@ -198,8 +198,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const cell = await dataGrid.getCellElementExcludingControlColumns(0, 1); expect(await cell.getVisibleText()).to.be(' - '); expect(await dataGrid.getHeaders()).to.eql([ - 'Control column', 'Select column', + 'Control column', 'Numberbytes', 'machine.ram_range', ]); diff --git a/x-pack/test_serverless/functional/test_suites/common/saved_objects_management/export_transform.ts b/x-pack/test_serverless/functional/test_suites/common/saved_objects_management/export_transform.ts index 80de679ca7b49..fa7de0335ff8d 100644 --- a/x-pack/test_serverless/functional/test_suites/common/saved_objects_management/export_transform.ts +++ b/x-pack/test_serverless/functional/test_suites/common/saved_objects_management/export_transform.ts @@ -42,7 +42,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); it('allows to mutate the objects during an export', async () => { - await supertest + const resp = await supertest .post('/api/saved_objects/_export') .set(svlCommonApi.getCommonRequestHeader()) .set(svlCommonApi.getInternalRequestHeader()) @@ -50,24 +50,23 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { type: ['test-export-transform'], excludeExportDetails: true, }) - .expect(200) - .then((resp) => { - const objects = parseNdJson(resp.text); - expect(objects.map((obj) => ({ id: obj.id, enabled: obj.attributes.enabled }))).to.eql([ - { - id: 'type_1-obj_1', - enabled: false, - }, - { - id: 'type_1-obj_2', - enabled: false, - }, - ]); - }); + .expect(200); + + const objects = parseNdJson(resp.text); + expect(objects.map((obj) => ({ id: obj.id, enabled: obj.attributes.enabled }))).to.eql([ + { + id: 'type_1-obj_1', + enabled: false, + }, + { + id: 'type_1-obj_2', + enabled: false, + }, + ]); }); it('allows to add additional objects to an export', async () => { - await supertest + const resp = await supertest .post('/api/saved_objects/_export') .set(svlCommonApi.getCommonRequestHeader()) .set(svlCommonApi.getInternalRequestHeader()) @@ -80,15 +79,13 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { ], excludeExportDetails: true, }) - .expect(200) - .then((resp) => { - const objects = parseNdJson(resp.text); - expect(objects.map((obj) => obj.id)).to.eql(['type_2-obj_1', 'type_dep-obj_1']); - }); + .expect(200); + const objects = parseNdJson(resp.text); + expect(objects.map((obj) => obj.id)).to.eql(['type_2-obj_1', 'type_dep-obj_1']); }); it('allows to add additional objects to an export when exporting by type', async () => { - await supertest + const resp = await supertest .post('/api/saved_objects/_export') .set(svlCommonApi.getCommonRequestHeader()) .set(svlCommonApi.getInternalRequestHeader()) @@ -96,20 +93,18 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { type: ['test-export-add'], excludeExportDetails: true, }) - .expect(200) - .then((resp) => { - const objects = parseNdJson(resp.text); - expect(objects.map((obj) => obj.id)).to.eql([ - 'type_2-obj_1', - 'type_2-obj_2', - 'type_dep-obj_1', - 'type_dep-obj_2', - ]); - }); + .expect(200); + const objects = parseNdJson(resp.text); + expect(objects.map((obj) => obj.id)).to.eql([ + 'type_2-obj_1', + 'type_2-obj_2', + 'type_dep-obj_1', + 'type_dep-obj_2', + ]); }); it('returns a 400 when the type causes a transform error', async () => { - await supertest + const resp = await supertest .post('/api/saved_objects/_export') .set(svlCommonApi.getCommonRequestHeader()) .set(svlCommonApi.getInternalRequestHeader()) @@ -117,21 +112,19 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { type: ['test-export-transform-error'], excludeExportDetails: true, }) - .expect(400) - .then((resp) => { - const { attributes, ...error } = resp.body; - expect(error).to.eql({ - error: 'Bad Request', - message: 'Error transforming objects to export', - statusCode: 400, - }); - expect(attributes.cause).to.eql('Error during transform'); - expect(attributes.objects.map((obj: any) => obj.id)).to.eql(['type_4-obj_1']); - }); + .expect(400); + const { attributes, ...error } = resp.body; + expect(error).to.eql({ + error: 'Bad Request', + message: 'Error transforming objects to export', + statusCode: 400, + }); + expect(attributes.cause).to.eql('Error during transform'); + expect(attributes.objects.map((obj: any) => obj.id)).to.eql(['type_4-obj_1']); }); it('returns a 400 when the type causes an invalid transform', async () => { - await supertest + const resp = await supertest .post('/api/saved_objects/_export') .set(svlCommonApi.getCommonRequestHeader()) .set(svlCommonApi.getInternalRequestHeader()) @@ -139,17 +132,15 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { type: ['test-export-invalid-transform'], excludeExportDetails: true, }) - .expect(400) - .then((resp) => { - expect(resp.body).to.eql({ - error: 'Bad Request', - message: 'Invalid transform performed on objects to export', - statusCode: 400, - attributes: { - objectKeys: ['test-export-invalid-transform|type_3-obj_1'], - }, - }); - }); + .expect(400); + expect(resp.body).to.eql({ + error: 'Bad Request', + message: 'Invalid transform performed on objects to export', + statusCode: 400, + attributes: { + objectKeys: ['test-export-invalid-transform|type_3-obj_1'], + }, + }); }); }); @@ -172,7 +163,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); it('execute export transforms for reference objects', async () => { - await supertest + const resp = await supertest .post('/api/saved_objects/_export') .set(svlCommonApi.getCommonRequestHeader()) .set(svlCommonApi.getInternalRequestHeader()) @@ -186,21 +177,17 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { includeReferencesDeep: true, excludeExportDetails: true, }) - .expect(200) - .then((resp) => { - const objects = parseNdJson(resp.text).sort((obj1, obj2) => - obj1.id.localeCompare(obj2.id) - ); - expect(objects.map((obj) => obj.id)).to.eql([ - 'type_1-obj_1', - 'type_1-obj_2', - 'type_2-obj_1', - 'type_dep-obj_1', - ]); + .expect(200); + const objects = parseNdJson(resp.text).sort((obj1, obj2) => obj1.id.localeCompare(obj2.id)); + expect(objects.map((obj) => obj.id)).to.eql([ + 'type_1-obj_1', + 'type_1-obj_2', + 'type_2-obj_1', + 'type_dep-obj_1', + ]); - expect(objects[0].attributes.enabled).to.eql(false); - expect(objects[1].attributes.enabled).to.eql(false); - }); + expect(objects[0].attributes.enabled).to.eql(false); + expect(objects[1].attributes.enabled).to.eql(false); }); }); @@ -223,7 +210,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); it('should only export objects returning `true` for `isExportable`', async () => { - await supertest + const resp = await supertest .post('/api/saved_objects/_export') .set(svlCommonApi.getCommonRequestHeader()) .set(svlCommonApi.getInternalRequestHeader()) @@ -237,21 +224,17 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { includeReferencesDeep: true, excludeExportDetails: true, }) - .expect(200) - .then((resp) => { - const objects = parseNdJson(resp.text).sort((obj1, obj2) => - obj1.id.localeCompare(obj2.id) - ); - expect(objects.map((obj) => `${obj.type}:${obj.id}`)).to.eql([ - 'test-is-exportable:1', - 'test-is-exportable:3', - 'test-is-exportable:5', - ]); - }); + .expect(200); + const objects = parseNdJson(resp.text).sort((obj1, obj2) => obj1.id.localeCompare(obj2.id)); + expect(objects.map((obj) => `${obj.type}:${obj.id}`)).to.eql([ + 'test-is-exportable:1', + 'test-is-exportable:3', + 'test-is-exportable:5', + ]); }); it('lists objects that got filtered', async () => { - await supertest + const resp = await supertest .post('/api/saved_objects/_export') .set(svlCommonApi.getCommonRequestHeader()) .set(svlCommonApi.getInternalRequestHeader()) @@ -265,31 +248,29 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { includeReferencesDeep: true, excludeExportDetails: false, }) - .expect(200) - .then((resp) => { - const objects = parseNdJson(resp.text); - const exportDetails = objects[ - objects.length - 1 - ] as unknown as SavedObjectsExportResultDetails; + .expect(200); + const objects = parseNdJson(resp.text); + const exportDetails = objects[ + objects.length - 1 + ] as unknown as SavedObjectsExportResultDetails; - expect(exportDetails.excludedObjectsCount).to.eql(2); - expect(exportDetails.excludedObjects).to.eql([ - { - type: 'test-is-exportable', - id: '2', - reason: 'excluded', - }, - { - type: 'test-is-exportable', - id: '4', - reason: 'excluded', - }, - ]); - }); + expect(exportDetails.excludedObjectsCount).to.eql(2); + expect(exportDetails.excludedObjects).to.eql([ + { + type: 'test-is-exportable', + id: '2', + reason: 'excluded', + }, + { + type: 'test-is-exportable', + id: '4', + reason: 'excluded', + }, + ]); }); it('excludes objects if `isExportable` throws', async () => { - await supertest + const resp = await supertest .post('/api/saved_objects/_export') .set(svlCommonApi.getCommonRequestHeader()) .set(svlCommonApi.getInternalRequestHeader()) @@ -307,24 +288,20 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { includeReferencesDeep: true, excludeExportDetails: false, }) - .expect(200) - .then((resp) => { - const objects = parseNdJson(resp.text); - expect(objects.length).to.eql(2); - expect([objects[0]].map((obj) => `${obj.type}:${obj.id}`)).to.eql([ - 'test-is-exportable:5', - ]); - const exportDetails = objects[ - objects.length - 1 - ] as unknown as SavedObjectsExportResultDetails; - expect(exportDetails.excludedObjects).to.eql([ - { - type: 'test-is-exportable', - id: 'error', - reason: 'predicate_error', - }, - ]); - }); + .expect(200); + const objects = parseNdJson(resp.text); + expect(objects.length).to.eql(2); + expect([objects[0]].map((obj) => `${obj.type}:${obj.id}`)).to.eql(['test-is-exportable:5']); + const exportDetails = objects[ + objects.length - 1 + ] as unknown as SavedObjectsExportResultDetails; + expect(exportDetails.excludedObjects).to.eql([ + { + type: 'test-is-exportable', + id: 'error', + reason: 'predicate_error', + }, + ]); }); }); }); diff --git a/x-pack/test_serverless/functional/test_suites/common/saved_objects_management/find.ts b/x-pack/test_serverless/functional/test_suites/common/saved_objects_management/find.ts index 986479518bfd4..c792d0f7fdbc1 100644 --- a/x-pack/test_serverless/functional/test_suites/common/saved_objects_management/find.ts +++ b/x-pack/test_serverless/functional/test_suites/common/saved_objects_management/find.ts @@ -44,37 +44,35 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await kibanaServer.savedObjects.cleanStandardList(); }); - it('returns saved objects with importableAndExportable types', async () => - await supertest + it('returns saved objects with importableAndExportable types', async () => { + const resp = await supertest .get('/api/kibana/management/saved_objects/_find?type=test-hidden-importable-exportable') .set(svlCommonApi.getCommonRequestHeader()) .set(svlCommonApi.getInternalRequestHeader()) - .expect(200) - .then((resp) => { - expect( - resp.body.saved_objects.map((so: { id: string; type: string }) => ({ - id: so.id, - type: so.type, - })) - ).to.eql([ - { - type: 'test-hidden-importable-exportable', - id: 'ff3733a0-9fty-11e7-ahb3-3dcb94193fab', - }, - ]); - })); + .expect(200); + expect( + resp.body.saved_objects.map((so: { id: string; type: string }) => ({ + id: so.id, + type: so.type, + })) + ).to.eql([ + { + type: 'test-hidden-importable-exportable', + id: 'ff3733a0-9fty-11e7-ahb3-3dcb94193fab', + }, + ]); + }); - it('returns empty response for non importableAndExportable types', async () => - await supertest + it('returns empty response for non importableAndExportable types', async () => { + const resp = await supertest .get( '/api/kibana/management/saved_objects/_find?type=test-hidden-non-importable-exportable' ) .set(svlCommonApi.getCommonRequestHeader()) .set(svlCommonApi.getInternalRequestHeader()) - .expect(200) - .then((resp) => { - expect(resp.body.saved_objects).to.eql([]); - })); + .expect(200); + expect(resp.body.saved_objects).to.eql([]); + }); }); }); } diff --git a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/columns_selection.ts b/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/columns_selection.ts index 8e7294d35f1b8..6fcfea151db12 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/columns_selection.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/columns_selection.ts @@ -86,7 +86,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('render content virtual column properly', async () => { it('should render log level and log message when present', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(0, 4); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(0, 2); const cellValue = await cellElement.getVisibleText(); expect(cellValue.includes('info')).to.be(true); expect(cellValue.includes('A sample log')).to.be(true); @@ -95,7 +95,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render log message when present and skip log level when missing', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(1, 4); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(1, 2); const cellValue = await cellElement.getVisibleText(); expect(cellValue.includes('info')).to.be(false); expect(cellValue.includes('A sample log')).to.be(true); @@ -104,7 +104,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render message from error object when top level message not present', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(2, 4); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(2, 2); const cellValue = await cellElement.getVisibleText(); expect(cellValue.includes('info')).to.be(true); expect(cellValue.includes('error.message')).to.be(true); @@ -114,7 +114,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render message from event.original when top level message and error.message not present', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(3, 4); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(3, 2); const cellValue = await cellElement.getVisibleText(); expect(cellValue.includes('info')).to.be(true); expect(cellValue.includes('event.original')).to.be(true); @@ -124,7 +124,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render the whole JSON when neither message, error.message and event.original are present', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(4, 4); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(4, 2); const cellValue = await cellElement.getVisibleText(); expect(cellValue.includes('info')).to.be(true); @@ -138,7 +138,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('on cell expansion with no message field should open JSON Viewer', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - await dataGrid.clickCellExpandButton(4, 4); + await dataGrid.clickCellExpandButtonExcludingControlColumns(4, 2); await testSubjects.existOrFail('dataTableExpandCellActionJsonPopover'); }); }); @@ -146,7 +146,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('on cell expansion with message field should open regular popover', async () => { await navigateToLogsExplorer(); await retry.tryForTime(TEST_TIMEOUT, async () => { - await dataGrid.clickCellExpandButton(3, 4); + await dataGrid.clickCellExpandButtonExcludingControlColumns(3, 2); await testSubjects.existOrFail('euiDataGridExpansionPopover'); }); }); @@ -155,7 +155,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('render resource virtual column properly', async () => { it('should render service name and host name when present', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(0, 3); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(0, 1); const cellValue = await cellElement.getVisibleText(); expect(cellValue.includes('synth-service')).to.be(true); expect(cellValue.includes('synth-host')).to.be(true); @@ -169,7 +169,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should render a popover with cell actions when a chip on content column is clicked', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(0, 4); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(0, 2); const logLevelChip = await cellElement.findByTestSubject('*logLevelBadge-'); await logLevelChip.click(); // Check Filter In button is present @@ -183,7 +183,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render the table filtered where log.level value is info when filter in action is clicked', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(0, 4); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(0, 2); const logLevelChip = await cellElement.findByTestSubject('*logLevelBadge-'); const actionSelector = 'dataTableCellAction_addToFilterAction_log.level'; @@ -204,7 +204,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render the table filtered where log.level value is not info when filter out action is clicked', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(0, 4); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(0, 2); const logLevelChip = await cellElement.findByTestSubject('*logLevelBadge-'); const actionSelector = 'dataTableCellAction_removeFromFilterAction_log.level'; @@ -223,7 +223,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render the table filtered where service.name value is selected', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(0, 3); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(0, 1); const serviceNameChip = await cellElement.findByTestSubject( 'dataTablePopoverChip_service.name' ); diff --git a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/custom_control_columns.ts b/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/custom_control_columns.ts index fea974fd0096e..c1f4692f19fdf 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/custom_control_columns.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/custom_control_columns.ts @@ -48,7 +48,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render control column with proper header', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { // First control column has no title, so empty string, leading control column has title - expect(await dataGrid.getControlColumnHeaderFields()).to.eql(['', 'actions']); + expect(await dataGrid.getControlColumnHeaderFields()).to.eql(['', '', '', '']); }); }); @@ -60,27 +60,27 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); }); - it('should render the malformed icon in the leading control column if malformed doc exists', async () => { + it('should render the degraded icon in the leading control column if degraded doc exists', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(1, 1); - const malformedButton = await cellElement.findByTestSubject('docTableDegradedDocExist'); - expect(malformedButton).to.not.be.empty(); + const cellElement = await dataGrid.getCellElement(1, 2); + const degradedButton = await cellElement.findByTestSubject('docTableDegradedDocExist'); + expect(degradedButton).to.not.be.empty(); }); }); - it('should render the disabled malformed icon in the leading control column when malformed doc does not exists', async () => { + it('should render the disabled degraded icon in the leading control column when degraded doc does not exists', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(0, 1); - const malformedDisableButton = await cellElement.findByTestSubject( + const cellElement = await dataGrid.getCellElement(0, 2); + const degradedDisableButton = await cellElement.findByTestSubject( 'docTableDegradedDocDoesNotExist' ); - expect(malformedDisableButton).to.not.be.empty(); + expect(degradedDisableButton).to.not.be.empty(); }); }); it('should render the stacktrace icon in the leading control column when stacktrace exists', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(4, 1); + const cellElement = await dataGrid.getCellElement(4, 3); const stacktraceButton = await cellElement.findByTestSubject('docTableStacktraceExist'); expect(stacktraceButton).to.not.be.empty(); }); @@ -88,7 +88,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render the stacktrace icon disabled in the leading control column when stacktrace does not exists', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(1, 1); + const cellElement = await dataGrid.getCellElement(1, 3); const stacktraceButton = await cellElement.findByTestSubject( 'docTableStacktraceDoesNotExist' ); @@ -116,10 +116,7 @@ function generateLogsData({ to, count = 1 }: { to: string; count?: number }) { }) ); - const malformedDocs = timerange( - moment(to).subtract(2, 'second'), - moment(to).subtract(1, 'second') - ) + const degradedDocs = timerange(moment(to).subtract(2, 'second'), moment(to).subtract(1, 'second')) .interval('1m') .rate(1) .generator((timestamp) => @@ -128,7 +125,7 @@ function generateLogsData({ to, count = 1 }: { to: string; count?: number }) { .map(() => { return log .create() - .message('A malformed doc') + .message('A degraded doc') .logLevel(MORE_THAN_1024_CHARS) .timestamp(timestamp) .defaults({ 'service.name': 'synth-service' }); @@ -186,5 +183,5 @@ function generateLogsData({ to, count = 1 }: { to: string; count?: number }) { }) ); - return [logs, malformedDocs, logsWithErrorMessage, logsWithErrorException, logsWithErrorInLog]; + return [logs, degradedDocs, logsWithErrorMessage, logsWithErrorException, logsWithErrorInLog]; } diff --git a/x-pack/test_serverless/shared/services/index.ts b/x-pack/test_serverless/shared/services/index.ts index 71c048cdf772b..631d047d8fcfb 100644 --- a/x-pack/test_serverless/shared/services/index.ts +++ b/x-pack/test_serverless/shared/services/index.ts @@ -9,12 +9,13 @@ import { commonFunctionalServices } from '@kbn/ftr-common-functional-services'; import { SupertestProvider } from './supertest'; import { SvlCommonApiServiceProvider } from './svl_common_api'; import { SvlReportingServiceProvider } from './svl_reporting'; -import { SvlUserManagerProvider } from './svl_user_manager'; import { DataViewApiProvider } from './data_view_api'; -export type { RoleCredentials } from './svl_user_manager'; -export type { InternalRequestHeader } from './svl_common_api'; -export type { SupertestWithoutAuthProviderType } from '@kbn/ftr-common-functional-services'; +export type { + InternalRequestHeader, + RoleCredentials, + SupertestWithoutAuthProviderType, +} from '@kbn/ftr-common-functional-services'; const SupertestWithoutAuthProvider = commonFunctionalServices.supertestWithoutAuth; @@ -23,6 +24,6 @@ export const services = { supertestWithoutAuth: SupertestWithoutAuthProvider, svlCommonApi: SvlCommonApiServiceProvider, svlReportingApi: SvlReportingServiceProvider, - svlUserManager: SvlUserManagerProvider, + svlUserManager: commonFunctionalServices.samlAuth, dataViewApi: DataViewApiProvider, }; diff --git a/x-pack/test_serverless/shared/services/svl_common_api.ts b/x-pack/test_serverless/shared/services/svl_common_api.ts index 99ffe486dd4d7..f207c0ed3bb0e 100644 --- a/x-pack/test_serverless/shared/services/svl_common_api.ts +++ b/x-pack/test_serverless/shared/services/svl_common_api.ts @@ -22,10 +22,11 @@ export type InternalRequestHeader = typeof INTERNAL_REQUEST_HEADERS; export function SvlCommonApiServiceProvider({}: FtrProviderContext) { return { + // call it from 'samlAuth' service when tests are migrated to deployment-agnostic getCommonRequestHeader() { return COMMON_REQUEST_HEADERS; }, - + // call it from 'samlAuth' service when tests are migrated to deployment-agnostic getInternalRequestHeader(): InternalRequestHeader { return INTERNAL_REQUEST_HEADERS; }, diff --git a/x-pack/test_serverless/shared/services/svl_reporting.ts b/x-pack/test_serverless/shared/services/svl_reporting.ts index cd231ee8e77e1..9d3d7941ec503 100644 --- a/x-pack/test_serverless/shared/services/svl_reporting.ts +++ b/x-pack/test_serverless/shared/services/svl_reporting.ts @@ -11,8 +11,8 @@ import type { ReportingJobResponse } from '@kbn/reporting-plugin/server/types'; import { REPORTING_DATA_STREAM_WILDCARD_WITH_LEGACY } from '@kbn/reporting-server'; import rison from '@kbn/rison'; import { FtrProviderContext } from '../../functional/ftr_provider_context'; -import { RoleCredentials } from './svl_user_manager'; -import { InternalRequestHeader } from './svl_common_api'; +import { RoleCredentials } from '.'; +import { InternalRequestHeader } from '.'; const API_HEADER: [string, string] = ['kbn-xsrf', 'reporting'];