From 595ba8f15cd35f7faae34718a5e3d74f34108be1 Mon Sep 17 00:00:00 2001 From: Ido Cohen <90558359+CohenIdo@users.noreply.github.com> Date: Tue, 3 Jan 2023 12:15:59 +0200 Subject: [PATCH 1/9] [Cloud Posture] CSPM telemetry accounts stats --- .../collectors/accounts_stats_collector.ts | 241 ++++++++++++++++++ .../collectors/indices_stats_collector.ts | 70 ++--- .../lib/telemetry/collectors/register.ts | 7 +- .../collectors/resources_stats_collector.ts | 9 +- .../server/lib/telemetry/collectors/schema.ts | 16 ++ .../server/lib/telemetry/collectors/types.ts | 14 + .../schema/xpack_plugins.json | 40 +++ 7 files changed, 358 insertions(+), 39 deletions(-) create mode 100644 x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/accounts_stats_collector.ts diff --git a/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/accounts_stats_collector.ts b/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/accounts_stats_collector.ts new file mode 100644 index 0000000000000..01637f43a5dea --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/accounts_stats_collector.ts @@ -0,0 +1,241 @@ +/* + * 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 { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import type { Logger } from '@kbn/core/server'; +import type { SearchRequest } from '@elastic/elasticsearch/lib/api/types'; +import { calculatePostureScore } from '../../../routes/compliance_dashboard/get_stats'; +import type { CspmAccountsStats } from './types'; +import { LATEST_FINDINGS_INDEX_DEFAULT_NS } from '../../../../common/constants'; + +interface Value { + value: number; +} + +interface DocCount { + doc_count: number; +} + +interface BenchmarkName { + metrics: { 'rule.benchmark.name': string }; +} + +interface BenchmarkId { + metrics: { 'rule.benchmark.id': string }; +} + +interface BenchmarkVersion { + metrics: { 'rule.benchmark.version': string }; +} + +interface AccountsStats { + accounts: { + buckets: AccountEntity[]; + }; +} +interface AccountEntity { + key: string; // account_id + doc_count: number; // latest findings doc count + passed_findings_count: DocCount; + failed_findings_count: DocCount; + benchmark_name: { top: BenchmarkName[] }; + benchmark_id: { top: BenchmarkId[] }; + benchmark_version: { top: BenchmarkVersion[] }; + agents_count: Value; + nodes_count: Value; + pods_count: Value; + resources: { + pods_count: Value; + }; +} + +const getAccountsStatsQuery = (index: string): SearchRequest => ({ + index, + query: { + match_all: {}, + }, + aggs: { + accounts: { + terms: { + field: 'cluster_id', + order: { + _count: 'desc', + }, + size: 100, + }, + aggs: { + nodes_count: { + cardinality: { + field: 'host.name', + }, + }, + agents_count: { + cardinality: { + field: 'agent.id', + }, + }, + benchmark_id: { + top_metrics: { + metrics: { + field: 'rule.benchmark.id', + }, + size: 1, + sort: { + '@timestamp': 'desc', + }, + }, + }, + benchmark_version: { + top_metrics: { + metrics: { + field: 'rule.benchmark.version', + }, + size: 1, + sort: { + '@timestamp': 'desc', + }, + }, + }, + benchmark_name: { + top_metrics: { + metrics: { + field: 'rule.benchmark.name', + }, + size: 1, + sort: { + '@timestamp': 'desc', + }, + }, + }, + passed_findings_count: { + filter: { + bool: { + filter: [ + { + bool: { + should: [ + { + term: { + 'result.evaluation': 'passed', + }, + }, + ], + minimum_should_match: 1, + }, + }, + ], + }, + }, + }, + failed_findings_count: { + filter: { + bool: { + filter: [ + { + bool: { + should: [ + { + term: { + 'result.evaluation': 'failed', + }, + }, + ], + minimum_should_match: 1, + }, + }, + ], + }, + }, + }, + resources: { + filter: { + bool: { + filter: [ + { + bool: { + should: [ + { + term: { + 'resource.sub_type': 'Pod', + }, + }, + ], + minimum_should_match: 1, + }, + }, + ], + }, + }, + aggs: { + pods_count: { + cardinality: { + field: 'resource.id', + }, + }, + }, + }, + }, + }, + }, + + size: 0, + _source: false, +}); + +const getCspmAccountsStats = ( + aggregatedResourcesStats: AccountsStats, + logger: Logger +): CspmAccountsStats[] => { + const accounts = aggregatedResourcesStats.accounts.buckets; + + const cspmAccountsStats = accounts.map((account) => ({ + account_id: account.key, + latest_findings_doc_count: account.doc_count, + posture_score: calculatePostureScore( + account.passed_findings_count.doc_count, + account.failed_findings_count.doc_count + ), + passed_findings_count: account.passed_findings_count.doc_count, + failed_findings_count: account.failed_findings_count.doc_count, + benchmark_name: account.benchmark_name.top[0].metrics['rule.benchmark.name'], + benchmark_id: account.benchmark_id.top[0].metrics['rule.benchmark.id'], + benchmark_version: account.benchmark_version.top[0].metrics['rule.benchmark.version'], + agents_count: account.agents_count.value, + nodes_count: account.nodes_count.value, + pods_count: account.resources.pods_count.value, + })); + logger.info('CSPM telemetry: accounts stats was sent'); + + return cspmAccountsStats; +}; + +export const getAccountsStats = async ( + esClient: ElasticsearchClient, + logger: Logger +): Promise => { + try { + const isIndexExists = await esClient.indices.exists({ + index: LATEST_FINDINGS_INDEX_DEFAULT_NS, + }); + + if (isIndexExists) { + const accountsStatsResponse = await esClient.search( + getAccountsStatsQuery(LATEST_FINDINGS_INDEX_DEFAULT_NS) + ); + + const cspmAccountsStats = accountsStatsResponse.aggregations + ? getCspmAccountsStats(accountsStatsResponse.aggregations, logger) + : []; + + return cspmAccountsStats; + } + + return []; + } catch (e) { + logger.error(`Failed to get resources stats ${e}`); + return []; + } +}; diff --git a/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/indices_stats_collector.ts b/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/indices_stats_collector.ts index a1bb49216262f..0feacc82c42a2 100644 --- a/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/indices_stats_collector.ts +++ b/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/indices_stats_collector.ts @@ -4,29 +4,36 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import type { Logger } from '@kbn/core/server'; +import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import type { CspmIndicesStats, IndexStats } from './types'; import { BENCHMARK_SCORE_INDEX_DEFAULT_NS, FINDINGS_INDEX_DEFAULT_NS, LATEST_FINDINGS_INDEX_DEFAULT_NS, } from '../../../../common/constants'; -import type { CspmIndicesStats, IndexStats } from './types'; -export const getIndicesStats = async ( +const getIndexDocCount = (esClient: ElasticsearchClient, index: string) => + esClient.indices.stats({ index }); + +const getLatestDocTimestamp = async ( esClient: ElasticsearchClient, - logger: Logger -): Promise => { - const [findings, latestFindings, score] = await Promise.all([ - getIndexStats(esClient, FINDINGS_INDEX_DEFAULT_NS, logger), - getIndexStats(esClient, LATEST_FINDINGS_INDEX_DEFAULT_NS, logger), - getIndexStats(esClient, BENCHMARK_SCORE_INDEX_DEFAULT_NS, logger), - ]); - return { - findings, - latest_findings: latestFindings, - score, - }; + index: string +): Promise => { + const latestTimestamp = await esClient.search({ + index, + query: { + match_all: {}, + }, + sort: '@timestamp:desc', + size: 1, + fields: ['@timestamp'], + _source: false, + }); + + const latestEventTimestamp = latestTimestamp.hits?.hits[0]?.fields; + + return latestEventTimestamp ? latestEventTimestamp['@timestamp'][0] : null; }; const getIndexStats = async ( @@ -60,25 +67,18 @@ const getIndexStats = async ( } }; -const getIndexDocCount = (esClient: ElasticsearchClient, index: string) => - esClient.indices.stats({ index }); - -const getLatestDocTimestamp = async ( +export const getIndicesStats = async ( esClient: ElasticsearchClient, - index: string -): Promise => { - const latestTimestamp = await esClient.search({ - index, - query: { - match_all: {}, - }, - sort: '@timestamp:desc', - size: 1, - fields: ['@timestamp'], - _source: false, - }); - - const latestEventTimestamp = latestTimestamp.hits?.hits[0]?.fields; - - return latestEventTimestamp ? latestEventTimestamp['@timestamp'][0] : null; + logger: Logger +): Promise => { + const [findings, latestFindings, score] = await Promise.all([ + getIndexStats(esClient, FINDINGS_INDEX_DEFAULT_NS, logger), + getIndexStats(esClient, LATEST_FINDINGS_INDEX_DEFAULT_NS, logger), + getIndexStats(esClient, BENCHMARK_SCORE_INDEX_DEFAULT_NS, logger), + ]); + return { + findings, + latest_findings: latestFindings, + score, + }; }; diff --git a/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/register.ts b/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/register.ts index 5f5de23434145..9eeb3338a9035 100644 --- a/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/register.ts +++ b/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/register.ts @@ -11,6 +11,7 @@ import { getIndicesStats } from './indices_stats_collector'; import { getResourcesStats } from './resources_stats_collector'; import { cspmUsageSchema } from './schema'; import { CspmUsage } from './types'; +import { getAccountsStats } from './accounts_stats_collector'; export function registerCspmUsageCollector( logger: Logger, @@ -26,13 +27,15 @@ export function registerCspmUsageCollector( type: 'cloud_security_posture', isReady: () => true, fetch: async (collectorFetchContext: CollectorFetchContext) => { - const [indicesStats, resourcesStats] = await Promise.all([ + const [indicesStats, accountsStats, resourcesStats] = await Promise.all([ getIndicesStats(collectorFetchContext.esClient, logger), - await getResourcesStats(collectorFetchContext.esClient, logger), + getAccountsStats(collectorFetchContext.esClient, logger), + getResourcesStats(collectorFetchContext.esClient, logger), ]); return { indices: indicesStats, + accounts_stats: accountsStats, resources_stats: resourcesStats, }; }, diff --git a/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/resources_stats_collector.ts b/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/resources_stats_collector.ts index 53b7f2af26c8d..3802f6651cd19 100644 --- a/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/resources_stats_collector.ts +++ b/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/resources_stats_collector.ts @@ -110,7 +110,10 @@ const getEvaluationStats = (resourceSubType: ResourceSubType) => { return { passed_findings_count: passed, failed_findings_count: failed }; }; -const getCspmResourcesStats = (aggregatedResourcesStats: ResourcesStats): CspmResourcesStats[] => { +const getCspmResourcesStats = ( + aggregatedResourcesStats: ResourcesStats, + logger: Logger +): CspmResourcesStats[] => { const accounts = aggregatedResourcesStats.accounts.buckets; const resourcesStats = accounts.map((account) => { @@ -129,6 +132,8 @@ const getCspmResourcesStats = (aggregatedResourcesStats: ResourcesStats): CspmRe }); }); }); + logger.info('CSPM telemetry: resources stats was sent'); + return resourcesStats.flat(2); }; @@ -147,7 +152,7 @@ export const getResourcesStats = async ( ); const cspmResourcesStats = resourcesStatsResponse.aggregations - ? getCspmResourcesStats(resourcesStatsResponse.aggregations) + ? getCspmResourcesStats(resourcesStatsResponse.aggregations, logger) : []; return cspmResourcesStats; diff --git a/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/schema.ts b/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/schema.ts index 9e25309369934..b7ed05f4532ce 100644 --- a/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/schema.ts +++ b/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/schema.ts @@ -65,4 +65,20 @@ export const cspmUsageSchema: MakeSchemaFrom = { failed_findings_count: { type: 'long' }, }, }, + accounts_stats: { + type: 'array', + items: { + account_id: { type: 'keyword' }, + posture_score: { type: 'long' }, + latest_findings_doc_count: { type: 'long' }, + benchmark_id: { type: 'keyword' }, + benchmark_name: { type: 'keyword' }, + benchmark_version: { type: 'keyword' }, + passed_findings_count: { type: 'long' }, + failed_findings_count: { type: 'long' }, + agents_count: { type: 'short' }, + nodes_count: { type: 'short' }, + pods_count: { type: 'short' }, + }, + }, }; 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 c17f07cb6cdc1..58e2932c231bb 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 @@ -8,6 +8,7 @@ export interface CspmUsage { indices: CspmIndicesStats; resources_stats: CspmResourcesStats[]; + accounts_stats: CspmAccountsStats[]; } export interface CspmIndicesStats { @@ -32,3 +33,16 @@ export interface CspmResourcesStats { passed_findings_count: number; failed_findings_count: number; } +export interface CspmAccountsStats { + account_id: string; + posture_score: number; + latest_findings_doc_count: number; + benchmark_id: string; + benchmark_name: string; + benchmark_version: string; + passed_findings_count: number; + failed_findings_count: number; + agents_count: number; + nodes_count: number; + pods_count: number; +} diff --git a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json index 18ab743d599d0..cb3e798e4a6e7 100644 --- a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json +++ b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @@ -5143,6 +5143,46 @@ } } } + }, + "accounts_stats": { + "type": "array", + "items": { + "properties": { + "account_id": { + "type": "keyword" + }, + "posture_score": { + "type": "long" + }, + "latest_findings_doc_count": { + "type": "long" + }, + "benchmark_id": { + "type": "keyword" + }, + "benchmark_name": { + "type": "keyword" + }, + "benchmark_version": { + "type": "keyword" + }, + "passed_findings_count": { + "type": "long" + }, + "failed_findings_count": { + "type": "long" + }, + "agents_count": { + "type": "short" + }, + "nodes_count": { + "type": "short" + }, + "pods_count": { + "type": "short" + } + } + } } } }, From fa3972b50e85813cd790c4258291ce582f181ec2 Mon Sep 17 00:00:00 2001 From: Yaroslav Kuznietsov Date: Tue, 3 Jan 2023 12:17:31 +0200 Subject: [PATCH 2/9] [Discover][Unified Search] Clicking on an individual error should focus the error on the monaco editor. (#148171) ## Summary Completes part of https://github.com/elastic/kibana/issues/136950. Closes https://github.com/elastic/kibana/issues/147986 ### Basic case: https://user-images.githubusercontent.com/22456368/209937498-c007be4e-4ced-46c4-bac5-5786b3d88074.mov ### Case with updating query, not running it, and clicking on errors: https://user-images.githubusercontent.com/22456368/209937718-a55e13fd-5a7c-4ec2-ac6f-bfce9578d75a.mov ### Fixed bug related to clearing the previous errors: #### Before: https://user-images.githubusercontent.com/22456368/209938380-3fcd04a6-8c49-4bc7-82f8-9e2cccf7e369.mov #### After https://user-images.githubusercontent.com/22456368/209938517-53101589-206e-4c26-be59-6ddb499c6183.mov Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../application/main/hooks/use_data_state.ts | 2 +- .../editor_footer.tsx | 24 ++++++++++++++-- .../text_based_languages_editor/helpers.ts | 11 +++++++- .../text_based_languages_editor/index.tsx | 28 ++++++++++++++++--- 4 files changed, 56 insertions(+), 9 deletions(-) diff --git a/src/plugins/discover/public/application/main/hooks/use_data_state.ts b/src/plugins/discover/public/application/main/hooks/use_data_state.ts index fe512e747b4a1..e27e31f147671 100644 --- a/src/plugins/discover/public/application/main/hooks/use_data_state.ts +++ b/src/plugins/discover/public/application/main/hooks/use_data_state.ts @@ -15,7 +15,7 @@ export function useDataState(data$: BehaviorSubject) { useEffect(() => { const subscription = data$.subscribe((next) => { if (next.fetchStatus !== fetchState.fetchStatus) { - setFetchState({ ...fetchState, ...next }); + setFetchState({ ...fetchState, ...next, ...(next.error ? {} : { error: undefined }) }); } }); return () => subscription.unsubscribe(); diff --git a/src/plugins/unified_search/public/query_string_input/text_based_languages_editor/editor_footer.tsx b/src/plugins/unified_search/public/query_string_input/text_based_languages_editor/editor_footer.tsx index c9baf17eeec8d..ff7237e059925 100644 --- a/src/plugins/unified_search/public/query_string_input/text_based_languages_editor/editor_footer.tsx +++ b/src/plugins/unified_search/public/query_string_input/text_based_languages_editor/editor_footer.tsx @@ -21,6 +21,9 @@ import { EuiDescriptionListDescription, } from '@elastic/eui'; import { Interpolation, Theme, css } from '@emotion/react'; +import { css as classNameCss } from '@emotion/css'; + +import type { MonacoError } from './helpers'; const isMac = navigator.platform.toLowerCase().indexOf('mac') >= 0; const COMMAND_KEY = isMac ? '⌘' : '^'; @@ -28,13 +31,17 @@ const COMMAND_KEY = isMac ? '⌘' : '^'; interface EditorFooterProps { lines: number; containerCSS: Interpolation; - errors?: Array<{ startLineNumber: number; message: string }>; + errors?: MonacoError[]; + onErrorClick: (error: MonacoError) => void; + refreshErrors: () => void; } export const EditorFooter = memo(function EditorFooter({ lines, containerCSS, errors, + onErrorClick, + refreshErrors, }: EditorFooterProps) { const [isPopoverOpen, setIsPopoverOpen] = useState(false); return ( @@ -75,7 +82,10 @@ export const EditorFooter = memo(function EditorFooter({ text-decoration: underline; } `} - onClick={() => setIsPopoverOpen(!isPopoverOpen)} + onClick={() => { + refreshErrors(); + setIsPopoverOpen(!isPopoverOpen); + }} >

{i18n.translate( @@ -104,7 +114,15 @@ export const EditorFooter = memo(function EditorFooter({ {errors.map((error, index) => { return ( - + onErrorClick(error)} + > diff --git a/src/plugins/unified_search/public/query_string_input/text_based_languages_editor/helpers.ts b/src/plugins/unified_search/public/query_string_input/text_based_languages_editor/helpers.ts index 191b82f5817a3..58e603fa62d4f 100644 --- a/src/plugins/unified_search/public/query_string_input/text_based_languages_editor/helpers.ts +++ b/src/plugins/unified_search/public/query_string_input/text_based_languages_editor/helpers.ts @@ -11,6 +11,15 @@ import useDebounce from 'react-use/lib/useDebounce'; import { monaco } from '@kbn/monaco'; import { i18n } from '@kbn/i18n'; +export interface MonacoError { + message: string; + startColumn: number; + startLineNumber: number; + endColumn: number; + endLineNumber: number; + severity: monaco.MarkerSeverity; +} + export const useDebounceWithOptions = ( fn: Function, { skipFirstRender }: { skipFirstRender: boolean } = { skipFirstRender: false }, @@ -33,7 +42,7 @@ export const useDebounceWithOptions = ( ); }; -export const parseErrors = (errors: Error[], code: string) => { +export const parseErrors = (errors: Error[], code: string): MonacoError[] => { return errors.map((error) => { if (error.message.includes('line')) { const text = error.message.split('line')[1]; diff --git a/src/plugins/unified_search/public/query_string_input/text_based_languages_editor/index.tsx b/src/plugins/unified_search/public/query_string_input/text_based_languages_editor/index.tsx index 34e1eab4bd55f..6ea8724923352 100644 --- a/src/plugins/unified_search/public/query_string_input/text_based_languages_editor/index.tsx +++ b/src/plugins/unified_search/public/query_string_input/text_based_languages_editor/index.tsx @@ -43,6 +43,7 @@ import { parseErrors, getInlineEditorText, getDocumentationSections, + MonacoError, } from './helpers'; import { EditorFooter } from './editor_footer'; import { ResizableButton } from './resizable_button'; @@ -103,9 +104,7 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ const [isCompactFocused, setIsCompactFocused] = useState(isCodeEditorExpanded); const [isCodeEditorExpandedFocused, setIsCodeEditorExpandedFocused] = useState(false); const [isWordWrapped, setIsWordWrapped] = useState(true); - const [editorErrors, setEditorErrors] = useState< - Array<{ startLineNumber: number; message: string }> - >([]); + const [editorErrors, setEditorErrors] = useState([]); const [documentationSections, setDocumentationSections] = useState(); const kibana = useKibana(); @@ -241,6 +240,19 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ [errors] ); + const onErrorClick = useCallback(({ startLineNumber, startColumn }: MonacoError) => { + if (!editor1.current) { + return; + } + + editor1.current.focus(); + editor1.current.setPosition({ + lineNumber: startLineNumber, + column: startColumn, + }); + editor1.current.revealLine(startLineNumber); + }, []); + // Clean up the monaco editor and DOM on unmount useEffect(() => { const model = editorModel; @@ -512,6 +524,8 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ lines={lines} containerCSS={styles.bottomContainer} errors={editorErrors} + onErrorClick={onErrorClick} + refreshErrors={onTextLangQuerySubmit} /> )} @@ -577,7 +591,13 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ )} {isCodeEditorExpanded && ( - + )} {isCodeEditorExpanded && ( Date: Tue, 3 Jan 2023 12:07:17 +0100 Subject: [PATCH 3/9] [Fleet] Fix crashes when installing a package with a lot of saved objects (#148141) **Resolves: https://github.com/elastic/kibana/issues/147695, https://github.com/elastic/kibana/issues/148174** **Related to: https://github.com/elastic/kibana/pull/145851, https://github.com/elastic/kibana/issues/137420** ## Summary This PR improves the stability of the Fleet packages installation process with many saved objects. 1. Changed mappings of the `installed_kibana` and `package_assets` fields from `nested` to `object` with `enabled: false`. Values of those fields were retrieved from `_source`, and no queries or aggregations were performed against them. So the mappings were unused, while during the installation of packages containing more than 10,000 saved objects, an error was thrown due to the nested field limitations: ``` Error installing security_detection_engine 8.4.1: The number of nested documents has exceeded the allowed limit of [10000]. This limit can be set by changing the [index.mapping.nested_objects.limit] index level setting. ``` 2. Improved the deletion of previous package assets by switching from sending multiple `savedObjectsClient.delete` requests in parallel to a single `savedObjectsClient.bulkDelete` request. Multiple parallel requests were causing the Elasticsearch cluster to stop responding for some time; see [this ticket](https://github.com/elastic/kibana/issues/147695) for more info. **Before** ![Screenshot 2022-12-28 at 11 09 35](https://user-images.githubusercontent.com/1938181/209816219-ade6dd0a-0d56-4acc-929e-b88571f0fe81.png) **After** ![Screenshot 2022-12-28 at 13 56 44](https://user-images.githubusercontent.com/1938181/209816209-16c69922-4ae2-4589-9aa4-5a28050037f4.png) --- .../migrations/check_registered_types.test.ts | 2 +- x-pack/plugins/fleet/server/saved_objects/index.ts | 14 ++++---------- .../fleet/server/services/epm/archive/storage.ts | 5 ++--- .../fleet/server/services/epm/packages/remove.ts | 5 +---- 4 files changed, 8 insertions(+), 18 deletions(-) diff --git a/src/core/server/integration_tests/saved_objects/migrations/check_registered_types.test.ts b/src/core/server/integration_tests/saved_objects/migrations/check_registered_types.test.ts index 2c6fee861ce7d..048efa5008533 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/check_registered_types.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/check_registered_types.test.ts @@ -85,7 +85,7 @@ describe('checking migration metadata changes on all registered SO types', () => "endpoint:user-artifact": "f94c250a52b30d0a2d32635f8b4c5bdabd1e25c0", "endpoint:user-artifact-manifest": "8c14d49a385d5d1307d956aa743ec78de0b2be88", "enterprise_search_telemetry": "fafcc8318528d34f721c42d1270787c52565bad5", - "epm-packages": "2915aee4302d4b00472ed05c21f59b7d498b5206", + "epm-packages": "7d80ba3f1fcd80316aa0b112657272034b66d5a8", "epm-packages-assets": "9fd3d6726ac77369249e9a973902c2cd615fc771", "event_loop_delays_daily": "d2ed39cf669577d90921c176499908b4943fb7bd", "exception-list": "fe8cc004fd2742177cdb9300f4a67689463faf9c", diff --git a/x-pack/plugins/fleet/server/saved_objects/index.ts b/x-pack/plugins/fleet/server/saved_objects/index.ts index 6c87e894e5777..03f072747a33d 100644 --- a/x-pack/plugins/fleet/server/saved_objects/index.ts +++ b/x-pack/plugins/fleet/server/saved_objects/index.ts @@ -266,19 +266,13 @@ const getSavedObjectTypes = ( }, }, installed_kibana: { - type: 'nested', - properties: { - id: { type: 'keyword' }, - type: { type: 'keyword' }, - }, + type: 'object', + enabled: false, }, installed_kibana_space_id: { type: 'keyword' }, package_assets: { - type: 'nested', - properties: { - id: { type: 'keyword' }, - type: { type: 'keyword' }, - }, + type: 'object', + enabled: false, }, install_started_at: { type: 'date' }, install_version: { type: 'keyword' }, diff --git a/x-pack/plugins/fleet/server/services/epm/archive/storage.ts b/x-pack/plugins/fleet/server/services/epm/archive/storage.ts index c6176b94f5fd2..3b6f38971eff7 100644 --- a/x-pack/plugins/fleet/server/services/epm/archive/storage.ts +++ b/x-pack/plugins/fleet/server/services/epm/archive/storage.ts @@ -102,10 +102,9 @@ export async function removeArchiveEntries(opts: { }) { const { savedObjectsClient, refs } = opts; if (!refs) return; - const results = await Promise.all( - refs.map((ref) => savedObjectsClient.delete(ASSETS_SAVED_OBJECT_TYPE, ref.id)) + return savedObjectsClient.bulkDelete( + refs.map((ref) => ({ id: ref.id, type: ASSETS_SAVED_OBJECT_TYPE })) ); - return results; } export async function saveArchiveEntries(opts: { diff --git a/x-pack/plugins/fleet/server/services/epm/packages/remove.ts b/x-pack/plugins/fleet/server/services/epm/packages/remove.ts index 61780c7977166..4a4183965913a 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/remove.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/remove.ts @@ -125,11 +125,8 @@ async function deleteKibanaAssets( // in the case of a partial install, it is expected that some assets will be not found // we filter these out before calling delete const assetsToDelete = foundObjects.map(({ saved_object: { id, type } }) => ({ id, type })); - const promises = assetsToDelete.map(async ({ id, type }) => { - return savedObjectsClient.delete(type, id, { namespace }); - }); - return Promise.all(promises); + return savedObjectsClient.bulkDelete(assetsToDelete, { namespace }); } function deleteESAssets( From a4c13e446809e4425f2981487f6e6711a6ebc478 Mon Sep 17 00:00:00 2001 From: Yaroslav Kuznietsov Date: Tue, 3 Jan 2023 13:23:14 +0200 Subject: [PATCH 4/9] [Unified Search] Sql editor border fix on resize. (#148198) ## Summary Completes part of https://github.com/elastic/kibana/issues/136950. https://user-images.githubusercontent.com/22456368/209969988-0138355b-8ffe-418e-ae04-a415e6b5e304.mov https://user-images.githubusercontent.com/22456368/209970036-407276ed-1532-4bbc-9224-461bb960c910.mov Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../text_based_languages_editor/index.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/plugins/unified_search/public/query_string_input/text_based_languages_editor/index.tsx b/src/plugins/unified_search/public/query_string_input/text_based_languages_editor/index.tsx index 6ea8724923352..18f08bfa750eb 100644 --- a/src/plugins/unified_search/public/query_string_input/text_based_languages_editor/index.tsx +++ b/src/plugins/unified_search/public/query_string_input/text_based_languages_editor/index.tsx @@ -468,7 +468,12 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ )} - + {(resizeRef) => ( Date: Tue, 3 Jan 2023 13:42:28 +0200 Subject: [PATCH 5/9] [Cloud Posture] replace integration extension form (#147751) --- .../common/constants.ts | 5 - .../cloud_security_posture/common/types.ts | 5 + .../public/common/constants.ts | 3 +- .../get_enabled_csp_integration_details.ts | 7 +- .../fleet_extensions/aws_credentials_form.tsx | 12 +- .../csp_boxed_radio_group.tsx | 108 +++++++++++++ .../deployment_type_select.tsx | 68 -------- .../components/fleet_extensions/eks_form.tsx | 153 ------------------ .../fleet_extensions/inline_radio_group.tsx | 83 ---------- .../components/fleet_extensions/mocks.ts | 2 +- .../policy_extension_create.test.tsx | 93 ----------- .../policy_extension_create.tsx | 42 +---- .../policy_extension_edit.test.tsx | 80 --------- .../policy_extension_edit.tsx | 35 +--- .../policy_template_form.test.tsx | 5 +- .../fleet_extensions/policy_template_form.tsx | 3 +- .../policy_template_input_selector.tsx | 76 +++++---- .../components/fleet_extensions/utils.ts | 93 ++--------- .../translations/translations/fr-FR.json | 13 -- .../translations/translations/ja-JP.json | 13 -- .../translations/translations/zh-CN.json | 13 -- 21 files changed, 186 insertions(+), 726 deletions(-) create mode 100644 x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/csp_boxed_radio_group.tsx delete mode 100644 x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/deployment_type_select.tsx delete mode 100644 x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/eks_form.tsx delete mode 100644 x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/inline_radio_group.tsx delete mode 100644 x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_extension_create.test.tsx delete mode 100644 x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_extension_edit.test.tsx diff --git a/x-pack/plugins/cloud_security_posture/common/constants.ts b/x-pack/plugins/cloud_security_posture/common/constants.ts index 644c6bb5a8c42..797b29ad2be32 100644 --- a/x-pack/plugins/cloud_security_posture/common/constants.ts +++ b/x-pack/plugins/cloud_security_posture/common/constants.ts @@ -59,8 +59,3 @@ export const SUPPORTED_CLOUDBEAT_INPUTS = [ CLOUDBEAT_GCP, CLOUDBEAT_AZURE, ] as const; - -export type CLOUDBEAT_INTEGRATION = typeof SUPPORTED_CLOUDBEAT_INPUTS[number]; -export type POLICY_TEMPLATE = typeof SUPPORTED_POLICY_TEMPLATES[number]; -export type PostureInput = typeof SUPPORTED_CLOUDBEAT_INPUTS[number]; -export type PosturePolicyTemplate = typeof SUPPORTED_POLICY_TEMPLATES[number]; diff --git a/x-pack/plugins/cloud_security_posture/common/types.ts b/x-pack/plugins/cloud_security_posture/common/types.ts index 29525fa95e19b..757ec5ebb0eb5 100644 --- a/x-pack/plugins/cloud_security_posture/common/types.ts +++ b/x-pack/plugins/cloud_security_posture/common/types.ts @@ -6,6 +6,7 @@ */ import type { PackagePolicy, AgentPolicy } from '@kbn/fleet-plugin/common'; +import { SUPPORTED_CLOUDBEAT_INPUTS, SUPPORTED_POLICY_TEMPLATES } from './constants'; import type { CspRuleMetadata } from './schemas/csp_rule_metadata'; export type Evaluation = 'passed' | 'failed' | 'NA'; @@ -100,3 +101,7 @@ export interface Benchmark { export type BenchmarkId = CspRuleMetadata['benchmark']['id']; export type BenchmarkName = CspRuleMetadata['benchmark']['name']; + +// Fleet Integration types +export type PostureInput = typeof SUPPORTED_CLOUDBEAT_INPUTS[number]; +export type PosturePolicyTemplate = typeof SUPPORTED_POLICY_TEMPLATES[number]; 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 1a96b8b095541..521885044f480 100644 --- a/x-pack/plugins/cloud_security_posture/public/common/constants.ts +++ b/x-pack/plugins/cloud_security_posture/public/common/constants.ts @@ -7,14 +7,13 @@ import { i18n } from '@kbn/i18n'; import { euiThemeVars } from '@kbn/ui-theme'; +import type { PosturePolicyTemplate, PostureInput } from '../../common/types'; import { CLOUDBEAT_EKS, CLOUDBEAT_VANILLA, CLOUDBEAT_AWS, CLOUDBEAT_GCP, CLOUDBEAT_AZURE, - type PostureInput, - type PosturePolicyTemplate, } from '../../common/constants'; import eksLogo from '../assets/icons/cis_eks_logo.svg'; 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 index eff420fd94af4..4cf3d7d129f0c 100644 --- 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 @@ -5,9 +5,10 @@ * 2.0. */ -import { PackagePolicy } from '@kbn/fleet-plugin/common'; -import { PostureInput, SUPPORTED_CLOUDBEAT_INPUTS } from '../../../common/constants'; -import { cloudPostureIntegrations, CloudPostureIntegrations } from '../constants'; +import type { PackagePolicy } from '@kbn/fleet-plugin/common'; +import type { PostureInput } from '../../../common/types'; +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; diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/aws_credentials_form.tsx b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/aws_credentials_form.tsx index e3cae37d18600..3585e63cf5143 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/aws_credentials_form.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/aws_credentials_form.tsx @@ -17,7 +17,7 @@ import type { NewPackagePolicy } from '@kbn/fleet-plugin/public'; import { NewPackagePolicyInput } from '@kbn/fleet-plugin/common'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; -import { InlineRadioGroup } from './inline_radio_group'; +import { RadioGroup } from './csp_boxed_radio_group'; import { getPosturePolicy, NewPackagePolicyPostureInput } from './utils'; const DocsLink = ( @@ -197,11 +197,13 @@ const getInputVarsFields = ( } as const; }); -const getDefaultAwsType = (input: Props['input']): AwsCredentialsType => - input.streams[0].vars['aws.credentials.type'].value; +const getAwsCredentialsType = (input: Props['input']): AwsCredentialsType | undefined => + input.streams[0].vars?.['aws.credentials.type'].value; export const AwsCredentialsForm = ({ input, newPolicy, updatePolicy }: Props) => { - const awsCredentialsType = getDefaultAwsType(input); + // We only have a value for 'aws.credentials.type' once the form has mounted. + // On initial render we don't have that value so we default to the first option. + const awsCredentialsType = getAwsCredentialsType(input) || AWS_CREDENTIALS_OPTIONS[0].id; const group = options[awsCredentialsType]; const fields = getInputVarsFields(input, group.fields); @@ -240,7 +242,7 @@ const AwsCredentialTypeSelector = ({ onChange(type: AwsCredentialsType): void; type: AwsCredentialsType; }) => ( - { + const { euiTheme } = useEuiTheme(); + + return ( +

+ {options.map((option) => { + const isChecked = option.id === idSelected; + return ( + + onChange(option.id)} + iconType={option.icon} + iconSide="right" + contentProps={{ + style: { + justifyContent: 'flex-start', + }, + }} + css={css` + width: 100%; + height: ${size === 's' ? euiTheme.size.xxl : euiTheme.size.xxxl}; + svg, + img { + margin-left: auto; + } + + &&, + &&:hover { + text-decoration: none; + } + &:disabled { + svg, + img { + filter: grayscale(1); + } + } + `} + > + {}} + /> + + + ); + })} +
+ ); +}; diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/deployment_type_select.tsx b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/deployment_type_select.tsx deleted file mode 100644 index bc5c61fa0370b..0000000000000 --- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/deployment_type_select.tsx +++ /dev/null @@ -1,68 +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 { - EuiFlexGroup, - EuiFlexItem, - EuiComboBox, - EuiToolTip, - EuiFormRow, - EuiIcon, - type EuiComboBoxOptionOption, - EuiDescribedFormGroup, -} from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { cloudPostureIntegrations } from '../../common/constants'; -import { CLOUDBEAT_INTEGRATION, POLICY_TEMPLATE } from '../../../common/constants'; - -interface Props { - policyTemplate: POLICY_TEMPLATE; - type: CLOUDBEAT_INTEGRATION; - onChange?: (type: CLOUDBEAT_INTEGRATION) => void; - isDisabled?: boolean; -} - -const kubeDeployOptions = ( - policyTemplate: POLICY_TEMPLATE -): Array> => - cloudPostureIntegrations[policyTemplate].options.map((o) => ({ value: o.type, label: o.name })); - -const KubernetesDeploymentFieldLabel = () => ( - - } - > - - - -   - - - - -); - -export const DeploymentTypeSelect = ({ policyTemplate, type, isDisabled, onChange }: Props) => ( - }> - }> - o.value === type)} - isDisabled={isDisabled} - onChange={(options) => !isDisabled && onChange?.(options[0].value!)} - /> - - -); diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/eks_form.tsx b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/eks_form.tsx deleted file mode 100644 index b160561807d64..0000000000000 --- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/eks_form.tsx +++ /dev/null @@ -1,153 +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 { - EuiFormRow, - EuiFieldText, - EuiDescribedFormGroup, - EuiText, - EuiSpacer, - EuiLink, -} from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n-react'; -import type { NewPackagePolicyInput } from '@kbn/fleet-plugin/common'; -import { i18n } from '@kbn/i18n'; -import { isEksInput } from './utils'; - -export const eksVars = [ - { - id: 'access_key_id', - label: i18n.translate( - 'xpack.csp.createPackagePolicy.eksIntegrationSettingsSection.accessKeyIdFieldLabel', - { defaultMessage: 'Access key ID' } - ), - }, - { - id: 'secret_access_key', - label: i18n.translate( - 'xpack.csp.createPackagePolicy.eksIntegrationSettingsSection.secretAccessKeyFieldLabel', - { defaultMessage: 'Secret Access Key' } - ), - }, - { - id: 'session_token', - label: i18n.translate( - 'xpack.csp.createPackagePolicy.eksIntegrationSettingsSection.sessionTokenFieldLabel', - { defaultMessage: 'Session Token' } - ), - }, - { - id: 'shared_credential_file', - label: i18n.translate( - 'xpack.csp.createPackagePolicy.eksIntegrationSettingsSection.sharedCredentialsFileFieldLabel', - { defaultMessage: 'Shared Credential File' } - ), - }, - { - id: 'credential_profile_name', - label: i18n.translate( - 'xpack.csp.createPackagePolicy.eksIntegrationSettingsSection.sharedCredentialFileFieldLabel', - { defaultMessage: 'Credential Profile Name' } - ), - }, - { - id: 'role_arn', - label: i18n.translate( - 'xpack.csp.createPackagePolicy.eksIntegrationSettingsSection.roleARNFieldLabel', - { defaultMessage: 'ARN Role' } - ), - }, -] as const; - -type EksVars = typeof eksVars; -type EksVarId = EksVars[number]['id']; -type EksFormVars = { [K in EksVarId]: string }; - -interface Props { - onChange(key: EksVarId, value: string): void; - inputs: NewPackagePolicyInput[]; -} - -const getEksVars = (input?: NewPackagePolicyInput): EksFormVars => { - const vars = input?.streams?.[0]?.vars; - return { - access_key_id: vars?.access_key_id.value || '', - secret_access_key: vars?.secret_access_key.value || '', - session_token: vars?.session_token.value || '', - shared_credential_file: vars?.shared_credential_file.value || '', - credential_profile_name: vars?.credential_profile_name.value || '', - role_arn: vars?.role_arn.value || '', - }; -}; - -export const EksFormWrapper = ({ onChange, inputs }: Props) => ( - <> - - - -); - -const EksForm = ({ onChange, inputs }: Props) => { - const values = getEksVars(inputs.find(isEksInput)); - - const eksFormTitle = ( -

- -

- ); - - const eksFormDescription = ( - <> - - - - ), - }} - /> - - - - ); - - return ( - - {eksVars.map((field) => ( - - - - } - > - onChange(field.id, event.target.value)} - /> - - ))} - - ); -}; diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/inline_radio_group.tsx b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/inline_radio_group.tsx deleted file mode 100644 index 6aba7275a79dd..0000000000000 --- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/inline_radio_group.tsx +++ /dev/null @@ -1,83 +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 { useEuiTheme, EuiRadioGroup, type EuiRadioGroupProps } from '@elastic/eui'; -import { css } from '@emotion/react'; - -type RadioGroupProps = Pick; - -type Props = RadioGroupProps & { - size?: 's' | 'm'; -}; - -export const InlineRadioGroup = ({ idSelected, size, options, disabled, onChange }: Props) => { - const { euiTheme } = useEuiTheme(); - - return ( - ({ - id: o.id, - label: o.label, - disabled: o.disabled, - ['data-enabled']: idSelected === o.id, - ['data-disabled']: o.disabled, - className: '__extendedRadioOption', - }))} - onChange={onChange} - css={css` - display: grid; - grid-template-columns: repeat(${options.length}, 1fr); - grid-template-rows: ${size === 's' ? euiTheme.size.xxl : euiTheme.size.xxxl}; - column-gap: ${euiTheme.size.s}; - align-items: center; - - > .__extendedRadioOption { - margin-top: 0; - height: 100%; - padding-left: ${euiTheme.size.m}; - padding-right: ${euiTheme.size.m}; - - display: grid; - grid-template-columns: auto 1fr; - column-gap: ${euiTheme.size.s}; - align-items: center; - - border: 1px solid ${euiTheme.colors.lightShade}; - border-radius: ${euiTheme.border.radius.medium}; - background: ${euiTheme.colors.emptyShade}; - - &[data-enabled='true'] { - border-color: ${euiTheme.colors.primary}; - background: ${euiTheme.colors.lightestShade}; - } - - &[data-disabled='true'] { - border-color: ${euiTheme.colors.disabled}; - background: ${euiTheme.colors.emptyShade}; - } - - // EuiRadio shows an absolute positioned div as a circle instead of input[type=radio] which is hidden - // removing the absolute position to make it part of document flow, set by css grid - &.__extendedRadioOption { - & > *:not(label):not(input) { - position: inherit; - top: 0; - left: 0; - } - - & > label { - padding-left: 0; - } - } - } - `} - /> - ); -}; diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/mocks.ts b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/mocks.ts index bf8616aaeeb3b..ecc435aa5961d 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/mocks.ts +++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/mocks.ts @@ -9,7 +9,7 @@ import type { PackagePolicy } from '@kbn/fleet-plugin/common'; import { createNewPackagePolicyMock } from '@kbn/fleet-plugin/common/mocks'; import { BenchmarkId } from '../../../common/types'; import { CLOUDBEAT_EKS, CLOUDBEAT_VANILLA } from '../../../common/constants'; -import { PostureInput } from '../../../common/constants'; +import type { PostureInput } from '../../../common/types'; export const getCspNewPolicyMock = (type: BenchmarkId = 'cis_k8s'): NewPackagePolicy => ({ name: 'some-cloud_security_posture-policy', diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_extension_create.test.tsx b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_extension_create.test.tsx deleted file mode 100644 index 29916477837f1..0000000000000 --- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_extension_create.test.tsx +++ /dev/null @@ -1,93 +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 { fireEvent, render } from '@testing-library/react'; -import CspCreatePolicyExtension from './policy_extension_create'; -import { eksVars } from './eks_form'; -import Chance from 'chance'; -import { TestProvider } from '../../test/test_provider'; -import userEvent from '@testing-library/user-event'; -import { getCspNewPolicyMock } from './mocks'; - -// ensures that fields appropriately match to their label -jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({ - ...jest.requireActual('@elastic/eui/lib/services/accessibility/html_id_generator'), - htmlIdGenerator: () => () => `id-${Math.random()}`, -})); - -// ensures that fields appropriately match to their label -jest.mock('@elastic/eui/lib/services/accessibility', () => ({ - ...jest.requireActual('@elastic/eui/lib/services/accessibility'), - useGeneratedHtmlId: () => `id-${Math.random()}`, -})); - -const chance = new Chance(); - -describe('', () => { - const onChange = jest.fn(); - - const WrappedComponent = ({ newPolicy = getCspNewPolicyMock() }) => ( - - - - ); - - beforeEach(() => { - onChange.mockClear(); - }); - - it('renders non-disabled ', () => { - const { getByLabelText } = render(); - const input = getByLabelText('Kubernetes Deployment') as HTMLInputElement; - expect(input).toBeInTheDocument(); - expect(input).not.toBeDisabled(); - }); - - it('renders non-disabled ', () => { - const { getByLabelText } = render( - - ); - - eksVars.forEach((eksVar) => { - expect(getByLabelText(eksVar.label)).toBeInTheDocument(); - expect(getByLabelText(eksVar.label)).not.toBeDisabled(); - }); - }); - - it('handles updating deployment type', () => { - const { getByLabelText } = render(); - const input = getByLabelText('Kubernetes Deployment') as HTMLInputElement; - - userEvent.type(input, 'EKS (Elastic Kubernetes Service){enter}'); - - expect(onChange).toBeCalledWith({ - isValid: true, - updatedPolicy: getCspNewPolicyMock('cis_eks'), - }); - }); - - it('handles updating EKS vars', () => { - const { getByLabelText } = render( - - ); - - const randomValues = chance.unique(chance.string, eksVars.length); - - eksVars.forEach((eksVar, i) => { - const eksVarInput = getByLabelText(eksVar.label) as HTMLInputElement; - fireEvent.change(eksVarInput, { target: { value: randomValues[i] } }); - - const policy = getCspNewPolicyMock('cis_eks'); - policy.inputs[1].streams[0].vars![eksVar.id].value = randomValues[i]; - - expect(onChange).toBeCalledWith({ - isValid: true, - updatedPolicy: policy, - }); - }); - }); -}); diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_extension_create.tsx b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_extension_create.tsx index 790cd8978725c..b4d3828dd97b8 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_extension_create.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_extension_create.tsx @@ -5,47 +5,13 @@ * 2.0. */ import React, { memo } from 'react'; -import { EuiForm } from '@elastic/eui'; import type { PackagePolicyCreateExtensionComponentProps } from '@kbn/fleet-plugin/public'; -import { CLOUDBEAT_AWS, CLOUDBEAT_EKS, CLOUDBEAT_INTEGRATION } from '../../../common/constants'; -import { DeploymentTypeSelect } from './deployment_type_select'; -import { EksFormWrapper } from './eks_form'; -import { - getEnabledInput, - getEnabledInputType, - getUpdatedDeploymentType, - getUpdatedEksVar, -} from './utils'; +import { CspPolicyTemplateForm } from './policy_template_form'; export const CspCreatePolicyExtension = memo( - ({ newPolicy, onChange }) => { - const selectedDeploymentType = getEnabledInputType(newPolicy.inputs); - const selectedInput = getEnabledInput(newPolicy.inputs); - const policyTemplate = selectedInput?.policy_template; - const updateDeploymentType = (inputType: CLOUDBEAT_INTEGRATION) => - onChange(getUpdatedDeploymentType(newPolicy, inputType)); - - const updateEksVar = (key: string, value: string) => - onChange(getUpdatedEksVar(newPolicy, key, value)); - - return ( - - {selectedInput && (policyTemplate === 'kspm' || policyTemplate === 'cspm') && ( - <> - - {(selectedDeploymentType === CLOUDBEAT_EKS || - selectedDeploymentType === CLOUDBEAT_AWS) && ( - - )} - - )} - - ); - } + ({ newPolicy, onChange }) => ( + + ) ); CspCreatePolicyExtension.displayName = 'CspCreatePolicyExtension'; diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_extension_edit.test.tsx b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_extension_edit.test.tsx deleted file mode 100644 index 856e4650ff045..0000000000000 --- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_extension_edit.test.tsx +++ /dev/null @@ -1,80 +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 { fireEvent, render } from '@testing-library/react'; -import CspEditPolicyExtension from './policy_extension_edit'; -import { TestProvider } from '../../test/test_provider'; -import { getCspNewPolicyMock, getCspPolicyMock } from './mocks'; -import Chance from 'chance'; -import { eksVars } from './eks_form'; - -const chance = new Chance(); - -// ensures that fields appropriately match to their label -jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({ - ...jest.requireActual('@elastic/eui/lib/services/accessibility/html_id_generator'), - htmlIdGenerator: () => () => `id-${Math.random()}`, -})); - -// ensures that fields appropriately match to their label -jest.mock('@elastic/eui/lib/services/accessibility', () => ({ - ...jest.requireActual('@elastic/eui/lib/services/accessibility'), - useGeneratedHtmlId: () => `id-${Math.random()}`, -})); - -describe('', () => { - const onChange = jest.fn(); - - const WrappedComponent = ({ policy = getCspPolicyMock(), newPolicy = getCspNewPolicyMock() }) => ( - - - - ); - - beforeEach(() => { - onChange.mockClear(); - }); - - it('renders disabled ', () => { - const { getByLabelText } = render(); - const input = getByLabelText('Kubernetes Deployment') as HTMLInputElement; - expect(input).toBeInTheDocument(); - expect(input).toBeDisabled(); - }); - - it('renders non-disabled ', () => { - const { getByLabelText } = render( - - ); - - eksVars.forEach((eksVar) => { - expect(getByLabelText(eksVar.label)).toBeInTheDocument(); - expect(getByLabelText(eksVar.label)).not.toBeDisabled(); - }); - }); - - it('handles updating EKS vars', () => { - const { getByLabelText } = render( - - ); - - const randomValues = chance.unique(chance.string, eksVars.length); - - eksVars.forEach((eksVar, i) => { - const eksVarInput = getByLabelText(eksVar.label) as HTMLInputElement; - fireEvent.change(eksVarInput, { target: { value: randomValues[i] } }); - - const policy = getCspNewPolicyMock('cis_eks'); - policy.inputs[1].streams[0].vars![eksVar.id].value = randomValues[i]; - - expect(onChange).toBeCalledWith({ - isValid: true, - updatedPolicy: policy, - }); - }); - }); -}); diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_extension_edit.tsx b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_extension_edit.tsx index e268dac8cd14e..b2be1b0c0d7cc 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_extension_edit.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_extension_edit.tsx @@ -5,40 +5,13 @@ * 2.0. */ import React, { memo } from 'react'; -import { EuiForm } from '@elastic/eui'; import type { PackagePolicyEditExtensionComponentProps } from '@kbn/fleet-plugin/public'; -import { CLOUDBEAT_EKS, CLOUDBEAT_AWS } from '../../../common/constants'; -import { DeploymentTypeSelect } from './deployment_type_select'; -import { EksFormWrapper } from './eks_form'; -import { getEnabledInput, getEnabledInputType, getUpdatedEksVar } from './utils'; +import { CspPolicyTemplateForm } from './policy_template_form'; export const CspEditPolicyExtension = memo( - ({ newPolicy, onChange }) => { - const selectedDeploymentType = getEnabledInputType(newPolicy.inputs); - const selectedInput = getEnabledInput(newPolicy.inputs); - const policyTemplate = selectedInput?.policy_template; - - const updateEksVar = (key: string, value: string) => - onChange(getUpdatedEksVar(newPolicy, key, value)); - - return ( - - {(policyTemplate === 'kspm' || policyTemplate === 'cspm') && ( - <> - - {(selectedDeploymentType === CLOUDBEAT_EKS || - selectedDeploymentType === CLOUDBEAT_AWS) && ( - - )} - - )} - - ); - } + ({ newPolicy, onChange }) => ( + + ) ); CspEditPolicyExtension.displayName = 'CspEditPolicyExtension'; diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.test.tsx b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.test.tsx index a43a9fd26fb10..0d4ab59bd31b9 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.test.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.test.tsx @@ -12,6 +12,7 @@ import { getMockPolicyAWS, getMockPolicyEKS, getMockPolicyK8s } from './mocks'; import type { NewPackagePolicy } from '@kbn/fleet-plugin/common'; import userEvent from '@testing-library/user-event'; import { getPosturePolicy } from './utils'; +import { CLOUDBEAT_AWS, CLOUDBEAT_EKS } from '../../../common/constants'; describe('', () => { const onChange = jest.fn(); @@ -148,8 +149,8 @@ describe('', () => { * AWS Credentials input fields tests for KSPM/CSPM integrations */ const awsInputs = { - 'cloudbeat/cis_eks': getMockPolicyEKS, - 'cloudbeat/cis_aws': getMockPolicyAWS, + [CLOUDBEAT_EKS]: getMockPolicyEKS, + [CLOUDBEAT_AWS]: getMockPolicyAWS, }; for (const [inputKey, getPolicy] of Object.entries(awsInputs) as Array< diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.tsx b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.tsx index 3298d368bf7d9..eee8f86ef6b48 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.tsx @@ -10,7 +10,8 @@ import type { NewPackagePolicy, PackagePolicyCreateExtensionComponentProps, } from '@kbn/fleet-plugin/public'; -import { CLOUDBEAT_AWS, CLOUDBEAT_VANILLA, PostureInput } from '../../../common/constants'; +import type { PostureInput } from '../../../common/types'; +import { CLOUDBEAT_AWS, CLOUDBEAT_VANILLA } from '../../../common/constants'; import { getPosturePolicy, INPUTS_WITH_AWS_VARS, diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_input_selector.tsx b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_input_selector.tsx index be3ba857e9691..34d00a95d6899 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_input_selector.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_input_selector.tsx @@ -5,20 +5,11 @@ * 2.0. */ import React from 'react'; -import { - EuiFlexGroup, - EuiToolTip, - EuiFlexItem, - EuiIcon, - EuiSpacer, - EuiText, - EuiTitle, -} from '@elastic/eui'; +import { EuiSpacer, EuiText, EuiTitle } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; -import { css } from '@emotion/react'; -import type { PostureInput } from '../../../common/constants'; +import type { PostureInput, PosturePolicyTemplate } from '../../../common/types'; import { getPolicyTemplateInputOptions, type NewPackagePolicyPostureInput } from './utils'; -import { InlineRadioGroup } from './inline_radio_group'; +import { RadioGroup } from './csp_boxed_radio_group'; interface Props { disabled: boolean; @@ -26,43 +17,19 @@ interface Props { setInput: (inputType: PostureInput) => void; } -const RadioLabel = ({ - label, - icon, - disabled, - tooltip, -}: ReturnType[number]) => ( - - - {label} - {icon && ( - - - - )} - - -); - export const PolicyInputSelector = ({ input, disabled, setInput }: Props) => { const baseOptions = getPolicyTemplateInputOptions(input.policy_template); const options = baseOptions.map((option) => ({ ...option, disabled: option.disabled || disabled, - label: , + label: option.label, + icon: option.icon, })); return (
- + ( ); + +const ConfigureIntegrationInfo = ({ type }: { type: PosturePolicyTemplate }) => ( + <> + +

+ +

+
+ + + {type === 'kspm' && ( + + )} + {type === 'cspm' && ( + + )} + + + +); diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/utils.ts b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/utils.ts index e6fe0e09e44f6..dde25b7477543 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/utils.ts +++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/utils.ts @@ -7,7 +7,6 @@ import type { NewPackagePolicy, NewPackagePolicyInput, - NewPackagePolicyInputStream, PackagePolicyConfigRecordEntry, } from '@kbn/fleet-plugin/common'; import merge from 'lodash/merge'; @@ -15,96 +14,26 @@ import { CLOUDBEAT_AWS, CLOUDBEAT_EKS, CLOUDBEAT_VANILLA, + CLOUDBEAT_GCP, + CLOUDBEAT_AZURE, SUPPORTED_POLICY_TEMPLATES, SUPPORTED_CLOUDBEAT_INPUTS, - type PostureInput, - type PosturePolicyTemplate, } from '../../../common/constants'; +import { type PostureInput, type PosturePolicyTemplate } from '../../../common/types'; import { assert } from '../../../common/utils/helpers'; import { cloudPostureIntegrations } from '../../common/constants'; -export const isEksInput = (input: NewPackagePolicyInput) => input.type === CLOUDBEAT_EKS; export const INPUTS_WITH_AWS_VARS = [CLOUDBEAT_EKS, CLOUDBEAT_AWS]; -const defaultInputType: Record = { - kspm: CLOUDBEAT_VANILLA, - cspm: CLOUDBEAT_AWS, -}; -export const getEnabledInputType = (inputs: NewPackagePolicy['inputs']): PostureInput => { - const enabledInput = getEnabledInput(inputs); - - if (enabledInput) return enabledInput.type as PostureInput; - - const policyTemplate = inputs[0].policy_template as PosturePolicyTemplate | undefined; - - if (policyTemplate && SUPPORTED_POLICY_TEMPLATES.includes(policyTemplate)) - return defaultInputType[policyTemplate]; - - throw new Error('unsupported policy template'); -}; - -export const getEnabledInput = ( - inputs: NewPackagePolicy['inputs'] -): NewPackagePolicyInput | undefined => inputs.find((input) => input.enabled); - -export const getUpdatedDeploymentType = (newPolicy: NewPackagePolicy, inputType: PostureInput) => ({ - isValid: true, // TODO: add validations - updatedPolicy: { - ...newPolicy, - inputs: newPolicy.inputs.map((item) => ({ - ...item, - enabled: item.type === inputType, - streams: item.streams.map((stream) => ({ - ...stream, - enabled: item.type === inputType, - })), - })), - }, -}); - -export const getUpdatedEksVar = (newPolicy: NewPackagePolicy, key: string, value: string) => ({ - isValid: true, // TODO: add validations - updatedPolicy: { - ...newPolicy, - inputs: newPolicy.inputs.map((item) => - INPUTS_WITH_AWS_VARS.includes(item.type) ? getUpdatedStreamVars(item, key, value) : item - ), - }, -}); - -// TODO: remove access to first stream -const getUpdatedStreamVars = (item: NewPackagePolicyInput, key: string, value: string) => { - if (!item.streams[0]) return item; - - return { - ...item, - streams: [ - { - ...item.streams[0], - vars: { - ...item.streams[0]?.vars, - [key]: { - ...item.streams[0]?.vars?.[key], - value, - }, - }, - }, - ], - }; -}; -type StreamWithRequiredVars = Array< - NewPackagePolicyInputStream & Required> ->; +type PosturePolicyInput = + | { type: typeof CLOUDBEAT_AZURE; policy_template: 'cspm' } + | { type: typeof CLOUDBEAT_GCP; policy_template: 'cspm' } + | { type: typeof CLOUDBEAT_AWS; policy_template: 'cspm' } + | { type: typeof CLOUDBEAT_VANILLA; policy_template: 'kspm' } + | { type: typeof CLOUDBEAT_EKS; policy_template: 'kspm' }; -// Extend NewPackagePolicyInput with known string literals for input type, policy template and streams -export type NewPackagePolicyPostureInput = NewPackagePolicyInput & - ( - | { type: 'cloudbeat/cis_azure'; policy_template: 'cspm' } - | { type: 'cloudbeat/cis_gcp'; policy_template: 'cspm' } - | { type: 'cloudbeat/cis_k8s'; policy_template: 'kspm' } - | { type: 'cloudbeat/cis_aws'; policy_template: 'cspm'; streams: StreamWithRequiredVars } - | { type: 'cloudbeat/cis_eks'; policy_template: 'kspm'; streams: StreamWithRequiredVars } - ); +// Extend NewPackagePolicyInput with known string literals for input type and policy template +export type NewPackagePolicyPostureInput = NewPackagePolicyInput & PosturePolicyInput; export const isPostureInput = ( input: NewPackagePolicyInput diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index a2bd70f068552..b5484f5a56e83 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -10171,7 +10171,6 @@ "xpack.csp.cloudPosturePage.errorRenderer.errorDescription": "{error} {statusCode} : {body}", "xpack.csp.cloudPosturePage.packageNotInstalled.description": "Utilisez notre intégration {integrationFullName} (KSPM) pour mesurer votre configuration de cluster Kubernetes par rapport aux recommandations du CIS.", "xpack.csp.complianceDashboard.complianceByCisSection.complianceColumnTooltip": "{passed}/{total}", - "xpack.csp.createPackagePolicy.eksIntegrationSettingsSection.awsCredentialsDescription": "L'intégration nécessite des droits d'accès supérieurs pour l'exécution de règles CIS Benchmarks. Vous pouvez suivre {link} pour générer les informations d'identification nécessaires.", "xpack.csp.dashboard.benchmarkSection.clusterTitle": "{title} - {shortId}", "xpack.csp.dashboard.benchmarkSection.clusterTitleTooltip.clusterTitle": "{title} - {shortId}", "xpack.csp.dashboard.benchmarkSection.lastEvaluatedTitle": "Dernière évaluation {dateFromNow}", @@ -10210,18 +10209,6 @@ "xpack.csp.createPackagePolicy.customAssetsTab.dashboardViewLabel": "Afficher le tableau de bord CSP", "xpack.csp.createPackagePolicy.customAssetsTab.findingsViewLabel": "Afficher les résultats CSP ", "xpack.csp.createPackagePolicy.customAssetsTab.rulesViewLabel": "Afficher les règles CSP ", - "xpack.csp.createPackagePolicy.eksIntegrationSettingsSection.accessKeyIdFieldLabel": "ID de clé d'accès", - "xpack.csp.createPackagePolicy.eksIntegrationSettingsSection.awsCredentialsInstructionsLink": "ces instructions", - "xpack.csp.createPackagePolicy.eksIntegrationSettingsSection.awsCredentialsNote": "Si vous choisissez de ne pas fournir vos informations d'identification, seul un sous-ensemble des règles de benchmark sera évalué avec vos clusters.", - "xpack.csp.createPackagePolicy.eksIntegrationSettingsSection.awsCredentialsTitle": "Informations d'identification AWS", - "xpack.csp.createPackagePolicy.eksIntegrationSettingsSection.optionalField": "Facultatif", - "xpack.csp.createPackagePolicy.eksIntegrationSettingsSection.roleARNFieldLabel": "Rôle ARN", - "xpack.csp.createPackagePolicy.eksIntegrationSettingsSection.secretAccessKeyFieldLabel": "Clé d'accès secrète", - "xpack.csp.createPackagePolicy.eksIntegrationSettingsSection.sessionTokenFieldLabel": "Token de session", - "xpack.csp.createPackagePolicy.eksIntegrationSettingsSection.sharedCredentialFileFieldLabel": "Nom de profil des informations d'identification", - "xpack.csp.createPackagePolicy.eksIntegrationSettingsSection.sharedCredentialsFileFieldLabel": "Fichier d'informations d'identification partagé", - "xpack.csp.createPackagePolicy.stepConfigure.integrationSettingsSection.kubernetesDeploymentLabel": "Déploiement Kubernetes", - "xpack.csp.createPackagePolicy.stepConfigure.integrationSettingsSection.kubernetesDeploymentLabelTooltip": "Sélectionner votre type de déploiement Kubernetes", "xpack.csp.cspEvaluationBadge.failLabel": "Échec", "xpack.csp.cspEvaluationBadge.passLabel": "Réussite", "xpack.csp.cspSettings.rules": "Règles de sécurité du CSP - ", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index fc998e22ac922..95ff0e1bcd852 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -10160,7 +10160,6 @@ "xpack.csp.cloudPosturePage.errorRenderer.errorDescription": "{error} {statusCode}: {body}", "xpack.csp.cloudPosturePage.packageNotInstalled.description": "{integrationFullName}(KSPM)統合は、CISの推奨事項に照らしてKubernetesクラスター設定を測定します。", "xpack.csp.complianceDashboard.complianceByCisSection.complianceColumnTooltip": "{passed}/{total}", - "xpack.csp.createPackagePolicy.eksIntegrationSettingsSection.awsCredentialsDescription": "この統合では、一部のCISベンチマークルールを実行するために昇格されたアクセス権が必要です。必要な資格情報を生成するには、{link}に従ってください。", "xpack.csp.dashboard.benchmarkSection.clusterTitle": "{title} - {shortId}", "xpack.csp.dashboard.benchmarkSection.clusterTitleTooltip.clusterTitle": "{title} - {shortId}", "xpack.csp.dashboard.benchmarkSection.lastEvaluatedTitle": "前回の評価{dateFromNow}", @@ -10199,18 +10198,6 @@ "xpack.csp.createPackagePolicy.customAssetsTab.dashboardViewLabel": "CSPダッシュボードを表示", "xpack.csp.createPackagePolicy.customAssetsTab.findingsViewLabel": "CSP調査結果を表示 ", "xpack.csp.createPackagePolicy.customAssetsTab.rulesViewLabel": "CSPルールを表示 ", - "xpack.csp.createPackagePolicy.eksIntegrationSettingsSection.accessKeyIdFieldLabel": "アクセスキーID", - "xpack.csp.createPackagePolicy.eksIntegrationSettingsSection.awsCredentialsInstructionsLink": "これらの手順", - "xpack.csp.createPackagePolicy.eksIntegrationSettingsSection.awsCredentialsNote": "資格情報を指定しない場合は、ベンチマークルールのサブネットのみがクラスターに対して評価されます。", - "xpack.csp.createPackagePolicy.eksIntegrationSettingsSection.awsCredentialsTitle": "AWS認証情報", - "xpack.csp.createPackagePolicy.eksIntegrationSettingsSection.optionalField": "オプション", - "xpack.csp.createPackagePolicy.eksIntegrationSettingsSection.roleARNFieldLabel": "ARNロール", - "xpack.csp.createPackagePolicy.eksIntegrationSettingsSection.secretAccessKeyFieldLabel": "シークレットアクセスキー", - "xpack.csp.createPackagePolicy.eksIntegrationSettingsSection.sessionTokenFieldLabel": "セッショントークン", - "xpack.csp.createPackagePolicy.eksIntegrationSettingsSection.sharedCredentialFileFieldLabel": "資格情報プロファイル名", - "xpack.csp.createPackagePolicy.eksIntegrationSettingsSection.sharedCredentialsFileFieldLabel": "共有資格情報ファイル", - "xpack.csp.createPackagePolicy.stepConfigure.integrationSettingsSection.kubernetesDeploymentLabel": "Kubernetesデプロイ", - "xpack.csp.createPackagePolicy.stepConfigure.integrationSettingsSection.kubernetesDeploymentLabelTooltip": "Kubernetesデプロイタイプを選択", "xpack.csp.cspEvaluationBadge.failLabel": "失敗", "xpack.csp.cspEvaluationBadge.passLabel": "合格", "xpack.csp.cspSettings.rules": "CSPセキュリティルール - ", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index c063e7e87fe79..4b4d96d9e049d 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -10175,7 +10175,6 @@ "xpack.csp.cloudPosturePage.errorRenderer.errorDescription": "{error} {statusCode}:{body}", "xpack.csp.cloudPosturePage.packageNotInstalled.description": "使用我们的 {integrationFullName} (KSPM) 集成根据 CIS 建议衡量 Kubernetes 集群设置。", "xpack.csp.complianceDashboard.complianceByCisSection.complianceColumnTooltip": "{passed}/{total}", - "xpack.csp.createPackagePolicy.eksIntegrationSettingsSection.awsCredentialsDescription": "该集成需要提升访问权限才能运行某些 CIS 基准规则。您可以访问 {link} 以生成必要的凭据。", "xpack.csp.dashboard.benchmarkSection.clusterTitle": "{title} - {shortId}", "xpack.csp.dashboard.benchmarkSection.clusterTitleTooltip.clusterTitle": "{title} - {shortId}", "xpack.csp.dashboard.benchmarkSection.lastEvaluatedTitle": "上次评估于 {dateFromNow}", @@ -10214,18 +10213,6 @@ "xpack.csp.createPackagePolicy.customAssetsTab.dashboardViewLabel": "查看 CSP 仪表板", "xpack.csp.createPackagePolicy.customAssetsTab.findingsViewLabel": "查看 CSP 结果 ", "xpack.csp.createPackagePolicy.customAssetsTab.rulesViewLabel": "查看 CSP 规则 ", - "xpack.csp.createPackagePolicy.eksIntegrationSettingsSection.accessKeyIdFieldLabel": "访问密钥 ID", - "xpack.csp.createPackagePolicy.eksIntegrationSettingsSection.awsCredentialsInstructionsLink": "以下说明", - "xpack.csp.createPackagePolicy.eksIntegrationSettingsSection.awsCredentialsNote": "如果选择不提供凭据,将仅根据您的集群评估基准规则的子集。", - "xpack.csp.createPackagePolicy.eksIntegrationSettingsSection.awsCredentialsTitle": "AWS 凭据", - "xpack.csp.createPackagePolicy.eksIntegrationSettingsSection.optionalField": "可选", - "xpack.csp.createPackagePolicy.eksIntegrationSettingsSection.roleARNFieldLabel": "ARN 角色", - "xpack.csp.createPackagePolicy.eksIntegrationSettingsSection.secretAccessKeyFieldLabel": "机密访问密钥", - "xpack.csp.createPackagePolicy.eksIntegrationSettingsSection.sessionTokenFieldLabel": "会话令牌", - "xpack.csp.createPackagePolicy.eksIntegrationSettingsSection.sharedCredentialFileFieldLabel": "凭据配置文件名", - "xpack.csp.createPackagePolicy.eksIntegrationSettingsSection.sharedCredentialsFileFieldLabel": "共享凭据文件", - "xpack.csp.createPackagePolicy.stepConfigure.integrationSettingsSection.kubernetesDeploymentLabel": "Kubernetes 部署", - "xpack.csp.createPackagePolicy.stepConfigure.integrationSettingsSection.kubernetesDeploymentLabelTooltip": "选择 Kubernetes 部署类型", "xpack.csp.cspEvaluationBadge.failLabel": "失败", "xpack.csp.cspEvaluationBadge.passLabel": "通过", "xpack.csp.cspSettings.rules": "CSP 安全规则 - ", From 5fddc7da8171dadc71b6931e69ab29921c50f8e7 Mon Sep 17 00:00:00 2001 From: Jatin Kathuria Date: Tue, 3 Jan 2023 13:43:21 +0100 Subject: [PATCH 6/9] [Security Solution][feat] Alert filters for Detection Page (#146989) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary This PR adds feature according to https://github.com/elastic/security-team/issues/4128. ### Capabilities Added - 💚 Page Filters are dynamic component that have been added on Detection Alerts Page. - 🌀 The status of the page filters is propagated to the URL and any external page can direct users to Detection Alert Page with custom filters populated. - 🌴 Filters are `hierarchical` in nature, what it means is that they should be arranged from lowers cardinality to highest from left to right. Any change in the filter on the left side, will update all the filters on the right hand side. - 💊 Existing alerts status change pill ( open/acknowledged/closed) has been removed from the alerts page but ❗it still exists on the rule details page. - 📄 Filters are persisted during user's browser session through local storage but they are not shared across spaces. 📔 Here is how the page looks when alert index exists v/s if there is some error in fetching the index. | Working Page Filters | When Index is unavailable | |--|--| |
diff --git a/x-pack/plugins/security_solution/public/detections/components/detection_page_filters/index.tsx b/x-pack/plugins/security_solution/public/detections/components/detection_page_filters/index.tsx new file mode 100644 index 0000000000000..e4b1850025d0a --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/components/detection_page_filters/index.tsx @@ -0,0 +1,55 @@ +/* + * 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 { ComponentProps } from 'react'; +import React, { useState, useCallback } from 'react'; +import type { Filter } from '@kbn/es-query'; +import { isEqual } from 'lodash'; +import { DEFAULT_DETECTION_PAGE_FILTERS } from '../../../../common/constants'; +import { FilterGroup } from '../../../common/components/filter_group'; + +type FilterItemSetProps = Omit, 'initialControls'>; + +const FilterItemSetComponent = (props: FilterItemSetProps) => { + const { dataViewId, onFilterChange, ...restFilterItemGroupProps } = props; + + const [initialFilterControls] = useState(DEFAULT_DETECTION_PAGE_FILTERS); + + const filterChangesHandler = useCallback( + (newFilters: Filter[]) => { + if (!onFilterChange) { + return; + } + const updatedFilters = newFilters.map((filter) => { + return { + ...filter, + meta: { + ...filter.meta, + disabled: false, + }, + }; + }); + onFilterChange(updatedFilters); + }, + [onFilterChange] + ); + return ( + + ); +}; + +const arePropsEqual = (prevProps: FilterItemSetProps, newProps: FilterItemSetProps) => { + const _isEqual = isEqual(prevProps, newProps); + return _isEqual; +}; + +export const DetectionPageFilterSet = React.memo(FilterItemSetComponent, arePropsEqual); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/chart_panels/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/chart_panels/index.tsx index 83df1b0018b1f..ddfe14d51201f 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/chart_panels/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/chart_panels/index.tsx @@ -7,7 +7,7 @@ import type { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { Filter, Query } from '@kbn/es-query'; -import { EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui'; +import { EuiFlexItem, EuiLoadingContent, EuiLoadingSpinner } from '@elastic/eui'; import React, { useCallback, useMemo } from 'react'; import styled from 'styled-components'; @@ -143,7 +143,7 @@ const ChartPanelsComponent: React.FC = ({ {alertViewSelection === 'trend' && ( {isLoadingIndexPattern ? ( - + ) : ( = ({ {alertViewSelection === 'table' && ( {isLoadingIndexPattern ? ( - + ) : ( = ({ {alertViewSelection === 'treemap' && ( {isLoadingIndexPattern ? ( - + ) : ( = ({ - clearEventsDeleted, - clearEventsLoading, -}) => { +const DetectionEnginePageComponent: React.FC = () => { const dispatch = useDispatch(); const containerElement = useRef(null); const getTable = useMemo(() => dataTableSelectors.getTableByIdSelector(), []); const graphEventId = useShallowEqualSelector( (state) => (getTable(state, TableId.alertsOnAlertsPage) ?? tableDefaults).graphEventId ); - const updatedAt = useShallowEqualSelector( - (state) => (getTable(state, TableId.alertsOnAlertsPage) ?? tableDefaults).updated - ); - const isAlertsLoading = useShallowEqualSelector( - (state) => (getTable(state, TableId.alertsOnAlertsPage) ?? tableDefaults).isLoading - ); const getGlobalFiltersQuerySelector = useMemo( () => inputsSelectors.globalFiltersQuerySelector(), [] @@ -128,9 +109,12 @@ const DetectionEnginePageComponent: React.FC = ({ const { loading: listsConfigLoading, needsConfiguration: needsListsConfiguration } = useListsConfig(); + const [detectionPageFilters, setDetectionPageFilters] = useState(); + const { indexPattern, runtimeMappings, + dataViewId, loading: isLoadingIndexPattern, } = useSourcererDataView(SourcererScopeName.detections); @@ -140,10 +124,8 @@ const DetectionEnginePageComponent: React.FC = ({ const loading = userInfoLoading || listsConfigLoading; const { application: { navigateToUrl }, - timelines: timelinesUi, data, } = useKibana().services; - const [filterGroup, setFilterGroup] = useState(FILTER_OPEN); const { filterManager } = data.query; @@ -163,8 +145,6 @@ const DetectionEnginePageComponent: React.FC = ({ [filterManager] ); - const showUpdating = useMemo(() => isAlertsLoading || loading, [isAlertsLoading, loading]); - const updateDateRangeCallback = useCallback( ({ x }) => { if (!x) { @@ -190,25 +170,14 @@ const DetectionEnginePageComponent: React.FC = ({ [formatUrl, navigateToUrl] ); - // Callback for when open/closed filter changes - const onFilterGroupChangedCallback = useCallback( - (newFilterGroup: Status) => { - const timelineId = TableId.alertsOnAlertsPage; - clearEventsLoading({ id: timelineId }); - clearEventsDeleted({ id: timelineId }); - setFilterGroup(newFilterGroup); - }, - [clearEventsLoading, clearEventsDeleted, setFilterGroup] - ); - const alertsHistogramDefaultFilters = useMemo( () => [ ...filters, ...buildShowBuildingBlockFilter(showBuildingBlockAlerts), - ...buildAlertStatusFilter(filterGroup), ...buildThreatMatchFilter(showOnlyThreatIndicatorAlerts), + ...(detectionPageFilters ?? []), ], - [filters, showBuildingBlockAlerts, showOnlyThreatIndicatorAlerts, filterGroup] + [filters, showBuildingBlockAlerts, showOnlyThreatIndicatorAlerts, detectionPageFilters] ); // AlertsTable manages global filters itself, so not including `filters` @@ -216,8 +185,9 @@ const DetectionEnginePageComponent: React.FC = ({ () => [ ...buildShowBuildingBlockFilter(showBuildingBlockAlerts), ...buildThreatMatchFilter(showOnlyThreatIndicatorAlerts), + ...(detectionPageFilters ?? []), ], - [showBuildingBlockAlerts, showOnlyThreatIndicatorAlerts] + [showBuildingBlockAlerts, showOnlyThreatIndicatorAlerts, detectionPageFilters] ); const onShowBuildingBlockAlertsChangedCallback = useCallback( @@ -258,6 +228,19 @@ const DetectionEnginePageComponent: React.FC = ({ [containerElement, onSkipFocusBeforeEventsTable, onSkipFocusAfterEventsTable] ); + const pageFiltersUpdateHandler = useCallback((newFilters: Filter[]) => { + setDetectionPageFilters(newFilters); + }, []); + + const isAlertTableLoading = useMemo( + () => loading || !Array.isArray(detectionPageFilters), + [loading, detectionPageFilters] + ); + const isChartPanelLoading = useMemo( + () => isLoadingIndexPattern || !Array.isArray(detectionPageFilters), + [isLoadingIndexPattern, detectionPageFilters] + ); + if (loading) { return ( @@ -324,33 +307,24 @@ const DetectionEnginePageComponent: React.FC = ({ {i18n.BUTTON_MANAGE_RULES} - - - - - - - - - - {updatedAt && - timelinesUi.getLastUpdated({ - updatedAt: updatedAt || Date.now(), - showUpdating, - })} - - - - - + + = ({ = ({ showOnlyThreatIndicatorAlerts={showOnlyThreatIndicatorAlerts} onShowOnlyThreatIndicatorAlertsChanged={onShowOnlyThreatIndicatorAlertsCallback} to={to} - filterGroup={filterGroup} /> diff --git a/x-pack/plugins/security_solution/tsconfig.json b/x-pack/plugins/security_solution/tsconfig.json index f508a920fa8bd..4ea871a5d8433 100644 --- a/x-pack/plugins/security_solution/tsconfig.json +++ b/x-pack/plugins/security_solution/tsconfig.json @@ -125,6 +125,8 @@ "@kbn/core-status-common-internal", "@kbn/repo-info", "@kbn/storybook", + "@kbn/controls-plugin", + "@kbn/shared-ux-utility", ], "exclude": [ "target/**/*", From 0b1ecc9844565188113a60f8f267068f6ce30a54 Mon Sep 17 00:00:00 2001 From: Antonio Date: Tue, 3 Jan 2023 14:09:50 +0100 Subject: [PATCH 7/9] [ResponseOps] [Cases] Allow sorting cases list by status, severity and title (#148193) Connected to #132041 ## Summary Sorting by status, severity, and title is now allowed in the all-cases list. ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [x] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [x] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [x] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) --- ## Release notes Sorting by status, severity, and title is now allowed in the all-cases list. Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/plugins/cases/common/ui/types.ts | 5 +- .../all_cases/all_cases_list.test.tsx | 73 ++++++++++++++++++- .../components/all_cases/all_cases_list.tsx | 3 +- .../all_cases/use_cases_columns.test.tsx | 54 ++++++++++++++ .../all_cases/use_cases_columns.tsx | 24 +++--- 5 files changed, 144 insertions(+), 15 deletions(-) diff --git a/x-pack/plugins/cases/common/ui/types.ts b/x-pack/plugins/cases/common/ui/types.ts index dc8ccf55c9f5d..bc385b68aeeaf 100644 --- a/x-pack/plugins/cases/common/ui/types.ts +++ b/x-pack/plugins/cases/common/ui/types.ts @@ -127,8 +127,11 @@ export type SingleCaseMetricsFeature = | 'lifespan'; export enum SortFieldCase { - createdAt = 'createdAt', closedAt = 'closedAt', + createdAt = 'createdAt', + severity = 'severity', + status = 'status', + title = 'title', } export type ElasticUser = SnakeToCamelCase; diff --git a/x-pack/plugins/cases/public/components/all_cases/all_cases_list.test.tsx b/x-pack/plugins/cases/public/components/all_cases/all_cases_list.test.tsx index 838ebeea59030..3450313114a0b 100644 --- a/x-pack/plugins/cases/public/components/all_cases/all_cases_list.test.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/all_cases_list.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { mount } from 'enzyme'; import moment from 'moment-timezone'; -import { render, waitFor, screen, act } from '@testing-library/react'; +import { render, waitFor, screen, act, within } from '@testing-library/react'; import { renderHook } from '@testing-library/react-hooks'; import userEvent from '@testing-library/user-event'; import { waitForEuiPopoverOpen } from '@elastic/eui/lib/test/rtl'; @@ -257,23 +257,28 @@ describe('AllCasesListGeneric', () => { expect.objectContaining({ queryParams: { ...DEFAULT_QUERY_PARAMS, - sortOrder: 'asc', }, }) ); }); }); + it('renders the title column', async () => { + const res = appMockRenderer.render(); + + expect(res.getByTestId('tableHeaderCell_title_0')).toBeInTheDocument(); + }); + it('renders the status column', async () => { const res = appMockRenderer.render(); - expect(res.getByTestId('tableHeaderCell_Status_6')).toBeInTheDocument(); + expect(res.getByTestId('tableHeaderCell_status_6')).toBeInTheDocument(); }); it('renders the severity column', async () => { const res = appMockRenderer.render(); - expect(res.getByTestId('tableHeaderCell_Severity_7')).toBeInTheDocument(); + expect(res.getByTestId('tableHeaderCell_severity_7')).toBeInTheDocument(); }); it('should render the case stats', () => { @@ -393,6 +398,66 @@ describe('AllCasesListGeneric', () => { }); }); + it('should sort by status', async () => { + const result = appMockRenderer.render(); + + userEvent.click( + within(result.getByTestId('tableHeaderCell_status_6')).getByTestId('tableHeaderSortButton') + ); + + await waitFor(() => { + expect(useGetCasesMock).toHaveBeenLastCalledWith( + expect.objectContaining({ + queryParams: { + ...DEFAULT_QUERY_PARAMS, + sortField: SortFieldCase.status, + sortOrder: 'asc', + }, + }) + ); + }); + }); + + it('should sort by severity', async () => { + const result = appMockRenderer.render(); + + userEvent.click( + within(result.getByTestId('tableHeaderCell_severity_7')).getByTestId('tableHeaderSortButton') + ); + + await waitFor(() => { + expect(useGetCasesMock).toHaveBeenLastCalledWith( + expect.objectContaining({ + queryParams: { + ...DEFAULT_QUERY_PARAMS, + sortField: SortFieldCase.severity, + sortOrder: 'asc', + }, + }) + ); + }); + }); + + it('should sort by title', async () => { + const result = appMockRenderer.render(); + + userEvent.click( + within(result.getByTestId('tableHeaderCell_title_0')).getByTestId('tableHeaderSortButton') + ); + + await waitFor(() => { + expect(useGetCasesMock).toHaveBeenLastCalledWith( + expect.objectContaining({ + queryParams: { + ...DEFAULT_QUERY_PARAMS, + sortField: SortFieldCase.title, + sortOrder: 'asc', + }, + }) + ); + }); + }); + it('should filter by status: closed', async () => { const result = appMockRenderer.render(); userEvent.click(result.getByTestId('case-status-filter')); diff --git a/x-pack/plugins/cases/public/components/all_cases/all_cases_list.tsx b/x-pack/plugins/cases/public/components/all_cases/all_cases_list.tsx index d4ba5692a0b72..3d786b787369a 100644 --- a/x-pack/plugins/cases/public/components/all_cases/all_cases_list.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/all_cases_list.tsx @@ -45,7 +45,8 @@ const ProgressLoader = styled(EuiProgress)` `; const getSortField = (field: string): SortFieldCase => - field === SortFieldCase.closedAt ? SortFieldCase.closedAt : SortFieldCase.createdAt; + // @ts-ignore + SortFieldCase[field] ?? SortFieldCase.title; export interface AllCasesListProps { hiddenStatuses?: CaseStatusWithAllStatus[]; diff --git a/x-pack/plugins/cases/public/components/all_cases/use_cases_columns.test.tsx b/x-pack/plugins/cases/public/components/all_cases/use_cases_columns.test.tsx index 0647bdd25e382..0b8777465ff2d 100644 --- a/x-pack/plugins/cases/public/components/all_cases/use_cases_columns.test.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/use_cases_columns.test.tsx @@ -49,8 +49,10 @@ describe('useCasesColumns ', () => { Object { "columns": Array [ Object { + "field": "title", "name": "Name", "render": [Function], + "sortable": true, "width": "20%", }, Object { @@ -95,12 +97,16 @@ describe('useCasesColumns ', () => { "render": [Function], }, Object { + "field": "status", "name": "Status", "render": [Function], + "sortable": true, }, Object { + "field": "severity", "name": "Severity", "render": [Function], + "sortable": true, }, Object { "align": "right", @@ -130,8 +136,10 @@ describe('useCasesColumns ', () => { Object { "columns": Array [ Object { + "field": "title", "name": "Name", "render": [Function], + "sortable": true, "width": "20%", }, Object { @@ -176,12 +184,16 @@ describe('useCasesColumns ', () => { "render": [Function], }, Object { + "field": "status", "name": "Status", "render": [Function], + "sortable": true, }, Object { + "field": "severity", "name": "Severity", "render": [Function], + "sortable": true, }, Object { "align": "right", @@ -210,8 +222,10 @@ describe('useCasesColumns ', () => { Object { "columns": Array [ Object { + "field": "title", "name": "Name", "render": [Function], + "sortable": true, "width": "20%", }, Object { @@ -250,12 +264,16 @@ describe('useCasesColumns ', () => { "render": [Function], }, Object { + "field": "status", "name": "Status", "render": [Function], + "sortable": true, }, Object { + "field": "severity", "name": "Severity", "render": [Function], + "sortable": true, }, Object { "align": "right", @@ -282,8 +300,10 @@ describe('useCasesColumns ', () => { Object { "columns": Array [ Object { + "field": "title", "name": "Name", "render": [Function], + "sortable": true, "width": "20%", }, Object { @@ -321,12 +341,16 @@ describe('useCasesColumns ', () => { "render": [Function], }, Object { + "field": "status", "name": "Status", "render": [Function], + "sortable": true, }, Object { + "field": "severity", "name": "Severity", "render": [Function], + "sortable": true, }, Object { "align": "right", @@ -347,8 +371,10 @@ describe('useCasesColumns ', () => { Object { "columns": Array [ Object { + "field": "title", "name": "Name", "render": [Function], + "sortable": true, "width": "20%", }, Object { @@ -387,12 +413,16 @@ describe('useCasesColumns ', () => { "render": [Function], }, Object { + "field": "status", "name": "Status", "render": [Function], + "sortable": true, }, Object { + "field": "severity", "name": "Severity", "render": [Function], + "sortable": true, }, Object { "align": "right", @@ -418,8 +448,10 @@ describe('useCasesColumns ', () => { Object { "columns": Array [ Object { + "field": "title", "name": "Name", "render": [Function], + "sortable": true, "width": "20%", }, Object { @@ -458,12 +490,16 @@ describe('useCasesColumns ', () => { "render": [Function], }, Object { + "field": "status", "name": "Status", "render": [Function], + "sortable": true, }, Object { + "field": "severity", "name": "Severity", "render": [Function], + "sortable": true, }, Object { "align": "right", @@ -487,8 +523,10 @@ describe('useCasesColumns ', () => { Object { "columns": Array [ Object { + "field": "title", "name": "Name", "render": [Function], + "sortable": true, "width": "20%", }, Object { @@ -527,12 +565,16 @@ describe('useCasesColumns ', () => { "render": [Function], }, Object { + "field": "status", "name": "Status", "render": [Function], + "sortable": true, }, Object { + "field": "severity", "name": "Severity", "render": [Function], + "sortable": true, }, Object { "align": "right", @@ -555,8 +597,10 @@ describe('useCasesColumns ', () => { Object { "columns": Array [ Object { + "field": "title", "name": "Name", "render": [Function], + "sortable": true, "width": "20%", }, Object { @@ -595,12 +639,16 @@ describe('useCasesColumns ', () => { "render": [Function], }, Object { + "field": "status", "name": "Status", "render": [Function], + "sortable": true, }, Object { + "field": "severity", "name": "Severity", "render": [Function], + "sortable": true, }, Object { "align": "right", @@ -622,8 +670,10 @@ describe('useCasesColumns ', () => { Object { "columns": Array [ Object { + "field": "title", "name": "Name", "render": [Function], + "sortable": true, "width": "20%", }, Object { @@ -662,12 +712,16 @@ describe('useCasesColumns ', () => { "render": [Function], }, Object { + "field": "status", "name": "Status", "render": [Function], + "sortable": true, }, Object { + "field": "severity", "name": "Severity", "render": [Function], + "sortable": true, }, ], } diff --git a/x-pack/plugins/cases/public/components/all_cases/use_cases_columns.tsx b/x-pack/plugins/cases/public/components/all_cases/use_cases_columns.tsx index df261b613b0b2..601e60ae9a5c5 100644 --- a/x-pack/plugins/cases/public/components/all_cases/use_cases_columns.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/use_cases_columns.tsx @@ -112,8 +112,10 @@ export const useCasesColumns = ({ const columns: CasesColumns[] = [ { + field: 'title', name: i18n.NAME, - render: (theCase: Case) => { + sortable: true, + render: (title: string, theCase: Case) => { if (theCase.id != null && theCase.title != null) { const caseDetailsLinkComponent = isSelectorView ? ( @@ -287,23 +289,27 @@ export const useCasesColumns = ({ }, }, { + field: 'status', name: i18n.STATUS, - render: (theCase: Case) => { - if (theCase.status === null || theCase.status === undefined) { - return getEmptyTagValue(); + sortable: true, + render: (status: Case['status']) => { + if (status != null) { + return ; } - return ; + return getEmptyTagValue(); }, }, { + field: 'severity', name: i18n.SEVERITY, - render: (theCase: Case) => { - if (theCase.severity != null) { - const severityData = severities[theCase.severity ?? CaseSeverity.LOW]; + sortable: true, + render: (severity: Case['severity']) => { + if (severity != null) { + const severityData = severities[severity ?? CaseSeverity.LOW]; return ( {severityData.label} From 1c3dc4a15164c12d428158a279d73e7ca850134a Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski Date: Tue, 3 Jan 2023 07:36:24 -0600 Subject: [PATCH 8/9] skip flaky suite (#148263) --- .../saved_objects/migrations/multiple_kibana_nodes.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/core/server/integration_tests/saved_objects/migrations/multiple_kibana_nodes.test.ts b/src/core/server/integration_tests/saved_objects/migrations/multiple_kibana_nodes.test.ts index a947854e9249b..34df1d484b92b 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/multiple_kibana_nodes.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/multiple_kibana_nodes.test.ts @@ -106,7 +106,8 @@ async function createRoot({ logFileName }: CreateRootConfig) { // suite is very long, the 10mins default can cause timeouts jest.setTimeout(15 * 60 * 1000); -describe('migration v2', () => { +// FLAKY: https://github.com/elastic/kibana/issues/148263 +describe.skip('migration v2', () => { let esServer: TestElasticsearchUtils; let rootA: Root; let rootB: Root; From 9111a2ebfc10813b018c61ab249e099f90f7582a Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Tue, 3 Jan 2023 15:58:15 +0100 Subject: [PATCH 9/9] [ML] AIOps: Replace useMount with useEffect to consider return cleanup callback. (#148229) Replace `useMount` (just the usages with return callbacks) with `useEffect` to consider return cleanup callback. --- .../change_point_detection_context.tsx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/aiops/public/components/change_point_detection/change_point_detection_context.tsx b/x-pack/plugins/aiops/public/components/change_point_detection/change_point_detection_context.tsx index 600a68ff1a498..8d67a28c3055f 100644 --- a/x-pack/plugins/aiops/public/components/change_point_detection/change_point_detection_context.tsx +++ b/x-pack/plugins/aiops/public/components/change_point_detection/change_point_detection_context.tsx @@ -16,7 +16,6 @@ import React, { } from 'react'; import { type DataViewField } from '@kbn/data-views-plugin/public'; import { startWith } from 'rxjs'; -import useMount from 'react-use/lib/useMount'; import type { Query, Filter } from '@kbn/es-query'; import { usePageUrlState } from '@kbn/ml-url-state'; import { @@ -129,7 +128,7 @@ export const ChangePointDetectionContextProvider: FC = ({ children }) => { const timeRange = useTimeRangeUpdates(); - useMount(function updateIntervalOnTimeBoundsChange() { + useEffect(function updateIntervalOnTimeBoundsChange() { const timeUpdateSubscription = timefilter .getTimeUpdate$() .pipe(startWith(timefilter.getTime())) @@ -145,7 +144,8 @@ export const ChangePointDetectionContextProvider: FC = ({ children }) => { return () => { timeUpdateSubscription.unsubscribe(); }; - }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); const metricFieldOptions = useMemo(() => { return dataView.fields.filter(({ aggregatable, type }) => aggregatable && type === 'number'); @@ -195,7 +195,7 @@ export const ChangePointDetectionContextProvider: FC = ({ children }) => { [filterManager] ); - useMount(() => { + useEffect(() => { setResultFilter(filterManager.getFilters()); const sub = filterManager.getUpdates$().subscribe(() => { setResultFilter(filterManager.getFilters()); @@ -203,7 +203,8 @@ export const ChangePointDetectionContextProvider: FC = ({ children }) => { return () => { sub.unsubscribe(); }; - }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); useEffect( function syncFilters() {