From df9fd9d0304a6d7c44c6b8542fb9943e29a3d0d2 Mon Sep 17 00:00:00 2001 From: Jordan <51442161+JordanSh@users.noreply.github.com> Date: Thu, 28 Sep 2023 11:33:24 +0300 Subject: [PATCH 1/4] [Cloud Security] Azure support for dashboard and flyout and PLG for dashboard (#167422) --- .../common/constants.ts | 3 + .../components/accounts_evaluated_widget.tsx | 132 +++++++++++------- .../public/components/cis_benchmark_icon.tsx | 2 + .../public/components/csp_counter_card.tsx | 39 +----- .../summary_section.test.tsx | 14 +- .../dashboard_sections/summary_section.tsx | 65 +++++---- .../translations/translations/fr-FR.json | 1 - .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - 9 files changed, 131 insertions(+), 127 deletions(-) diff --git a/x-pack/plugins/cloud_security_posture/common/constants.ts b/x-pack/plugins/cloud_security_posture/common/constants.ts index 23de4b6d02e36..1cc356cbfd5e3 100644 --- a/x-pack/plugins/cloud_security_posture/common/constants.ts +++ b/x-pack/plugins/cloud_security_posture/common/constants.ts @@ -100,6 +100,9 @@ export const CLOUDBEAT_VULN_MGMT_GCP = 'cloudbeat/vuln_mgmt_gcp'; export const CLOUDBEAT_VULN_MGMT_AZURE = 'cloudbeat/vuln_mgmt_azure'; export const CIS_AWS = 'cis_aws'; export const CIS_GCP = 'cis_gcp'; +export const CIS_K8S = 'cis_k8s'; +export const CIS_EKS = 'cis_eks'; +export const CIS_AZURE = 'cis_azure'; export const KSPM_POLICY_TEMPLATE = 'kspm'; export const CSPM_POLICY_TEMPLATE = 'cspm'; export const VULN_MGMT_POLICY_TEMPLATE = 'vuln_mgmt'; diff --git a/x-pack/plugins/cloud_security_posture/public/components/accounts_evaluated_widget.tsx b/x-pack/plugins/cloud_security_posture/public/components/accounts_evaluated_widget.tsx index 39b37c85aee39..418b1c37a1bdd 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/accounts_evaluated_widget.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/accounts_evaluated_widget.tsx @@ -5,13 +5,43 @@ * 2.0. */ import React from 'react'; -import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { CIS_AWS, CIS_GCP } from '../../common/constants'; +import { EuiFlexGroup, EuiFlexItem, useEuiTheme } from '@elastic/eui'; +import { css } from '@emotion/react'; +import { CIS_AWS, CIS_GCP, CIS_AZURE, CIS_K8S, CIS_EKS } from '../../common/constants'; import { Cluster } from '../../common/types'; import { CISBenchmarkIcon } from './cis_benchmark_icon'; import { CompactFormattedNumber } from './compact_formatted_number'; import { useNavigateFindings } from '../common/hooks/use_navigate_findings'; +// order in array will determine order of appearance in the dashboard +const benchmarks = [ + { + type: CIS_AWS, + name: 'Amazon Web Services (AWS)', + provider: 'aws', + }, + { + type: CIS_GCP, + name: 'Google Cloud Platform (GCP)', + provider: 'gcp', + }, + { + type: CIS_AZURE, + name: 'Azure', + provider: 'azure', + }, + { + type: CIS_K8S, + name: 'Kubernetes', + benchmarkId: 'cis_k8s', + }, + { + type: CIS_EKS, + name: 'Amazon Elastic Kubernetes Service (EKS)', + benchmarkId: 'cis_eks', + }, +]; + export const AccountsEvaluatedWidget = ({ clusters, benchmarkAbbreviateAbove = 999, @@ -20,6 +50,8 @@ export const AccountsEvaluatedWidget = ({ /** numbers higher than the value of this field will be abbreviated using compact notation and have a tooltip displaying the full value */ benchmarkAbbreviateAbove?: number; }) => { + const { euiTheme } = useEuiTheme(); + const filterClustersById = (benchmarkId: string) => { return clusters?.filter((obj) => obj?.meta.benchmark.id === benchmarkId) || []; }; @@ -30,56 +62,52 @@ export const AccountsEvaluatedWidget = ({ navToFindings({ 'cloud.provider': provider }); }; - const cisAwsClusterAmount = filterClustersById(CIS_AWS).length; - const cisGcpClusterAmount = filterClustersById(CIS_GCP).length; + const navToFindingsByCisBenchmark = (cisBenchmark: string) => { + navToFindings({ 'rule.benchmark.id': cisBenchmark }); + }; + + const benchmarkElements = benchmarks.map((benchmark) => { + const clusterAmount = filterClustersById(benchmark.type).length; + + return ( + clusterAmount > 0 && ( + { + if (benchmark.provider) { + navToFindingsByCloudProvider(benchmark.provider); + } + if (benchmark.benchmarkId) { + navToFindingsByCisBenchmark(benchmark.benchmarkId); + } + }} + css={css` + transition: ${euiTheme.animation.normal} ease-in; + border-bottom: ${euiTheme.border.thick}; + border-color: transparent; - const cisAwsBenchmarkName = 'Amazon Web Services (AWS)'; - const cisGcpBenchmarkName = 'Google Cloud Platform (GCP)'; + :hover { + cursor: pointer; + border-color: ${euiTheme.colors.darkestShade}; + } + `} + > + + + + + + + + + + ) + ); + }); - return ( - <> - - {cisAwsClusterAmount > 0 && ( - - - - - - { - navToFindingsByCloudProvider('aws'); - }} - > - - - - - )} - {cisGcpClusterAmount > 0 && ( - - - - - - { - navToFindingsByCloudProvider('gcp'); - }} - > - - - - - )} - - - ); + // Render the benchmark elements + return {benchmarkElements}; }; diff --git a/x-pack/plugins/cloud_security_posture/public/components/cis_benchmark_icon.tsx b/x-pack/plugins/cloud_security_posture/public/components/cis_benchmark_icon.tsx index a0048b21fb92f..50f9ca1b15d9d 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/cis_benchmark_icon.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/cis_benchmark_icon.tsx @@ -23,6 +23,8 @@ const getBenchmarkIdIconType = (props: Props): string => { switch (props.type) { case 'cis_eks': return cisEksIcon; + case 'cis_azure': + return 'logoAzure'; case 'cis_aws': return 'logoAWS'; case 'cis_gcp': diff --git a/x-pack/plugins/cloud_security_posture/public/components/csp_counter_card.tsx b/x-pack/plugins/cloud_security_posture/public/components/csp_counter_card.tsx index def19c1c871ec..9b07c0f3edded 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/csp_counter_card.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/csp_counter_card.tsx @@ -5,14 +5,13 @@ * 2.0. */ -import React, { MouseEventHandler } from 'react'; -import { css } from '@emotion/react'; -import { EuiIcon, EuiPanel, EuiStat, useEuiTheme } from '@elastic/eui'; +import React, { ReactNode } from 'react'; +import { EuiPanel, EuiStat, useEuiTheme, EuiHorizontalRule } from '@elastic/eui'; import type { EuiStatProps } from '@elastic/eui'; export interface CspCounterCardProps { id: string; - onClick?: MouseEventHandler; + button?: ReactNode; title: EuiStatProps['title']; titleColor?: EuiStatProps['titleColor']; description: EuiStatProps['description']; @@ -22,25 +21,10 @@ export const CspCounterCard = (counter: CspCounterCardProps) => { const { euiTheme } = useEuiTheme(); return ( - + { descriptionElement="h6" description={counter.description} /> - {counter.onClick && ( - - )} + + {counter.button} ); }; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/summary_section.test.tsx b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/summary_section.test.tsx index f78b596074349..0545d4f3bb429 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/summary_section.test.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/summary_section.test.tsx @@ -31,11 +31,7 @@ describe('', () => { renderCloudSummarySection(); expectIdsInDoc({ - be: [ - DASHBOARD_COUNTER_CARDS.CLUSTERS_EVALUATED, - DASHBOARD_COUNTER_CARDS.RESOURCES_EVALUATED, - DASHBOARD_COUNTER_CARDS.FAILING_FINDINGS, - ], + be: [DASHBOARD_COUNTER_CARDS.CLUSTERS_EVALUATED, DASHBOARD_COUNTER_CARDS.RESOURCES_EVALUATED], }); }); @@ -46,7 +42,6 @@ describe('', () => { expect(screen.getByTestId(DASHBOARD_COUNTER_CARDS.RESOURCES_EVALUATED)).toHaveTextContent( '162' ); - expect(screen.getByTestId(DASHBOARD_COUNTER_CARDS.FAILING_FINDINGS)).toHaveTextContent('17'); }); it('renders counters value in compact abbreviation if its above one million', () => { @@ -55,12 +50,5 @@ describe('', () => { expect(screen.getByTestId(DASHBOARD_COUNTER_CARDS.RESOURCES_EVALUATED)).toHaveTextContent( '999,999' ); - expect(screen.getByTestId(DASHBOARD_COUNTER_CARDS.FAILING_FINDINGS)).toHaveTextContent('1M'); - }); - - it('renders N/A as an empty state', () => { - renderCloudSummarySection({ stats: { totalFailed: undefined } }); - - expect(screen.getByTestId(DASHBOARD_COUNTER_CARDS.FAILING_FINDINGS)).toHaveTextContent('N/A'); }); }); diff --git a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/summary_section.tsx b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/summary_section.tsx index 9837c531021f2..50d5493387466 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/summary_section.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/summary_section.tsx @@ -6,10 +6,10 @@ */ import React, { useMemo } from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiFlexItemProps } from '@elastic/eui'; +import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiFlexItemProps } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { css } from '@emotion/react'; -import { statusColors } from '../../../common/constants'; +import { useCspIntegrationLink } from '../../../common/navigation/use_csp_integration_link'; import { DASHBOARD_COUNTER_CARDS, DASHBOARD_SUMMARY_CONTAINER } from '../test_subjects'; import { CspCounterCard, CspCounterCardProps } from '../../../components/csp_counter_card'; import { CompactFormattedNumber } from '../../../components/compact_formatted_number'; @@ -56,6 +56,8 @@ export const SummarySection = ({ }) => { const navToFindings = useNavigateFindings(); const navToFindingsByResource = useNavigateFindingsByResource(); + const cspmIntegrationLink = useCspIntegrationLink(CSPM_POLICY_TEMPLATE); + const kspmIntegrationLink = useCspIntegrationLink(KSPM_POLICY_TEMPLATE); const handleEvalCounterClick = (evaluation: Evaluation) => { navToFindings({ 'result.evaluation': evaluation, ...getPolicyTemplateQuery(dashboardType) }); @@ -87,12 +89,26 @@ export const SummarySection = ({ 'xpack.csp.dashboard.summarySection.counterCard.accountsEvaluatedDescription', { defaultMessage: 'Accounts Evaluated' } ), - title: - dashboardType === KSPM_POLICY_TEMPLATE ? ( - - ) : ( - - ), + title: , + button: ( + + {dashboardType === KSPM_POLICY_TEMPLATE + ? i18n.translate( + 'xpack.csp.dashboard.summarySection.counterCard.clustersEvaluatedButtonTitle', + { defaultMessage: 'Enroll more clusters' } + ) + : i18n.translate( + 'xpack.csp.dashboard.summarySection.counterCard.accountsEvaluatedButtonTitle', + { defaultMessage: 'Enroll more accounts' } + )} + + ), }, { id: DASHBOARD_COUNTER_CARDS.RESOURCES_EVALUATED, @@ -101,32 +117,27 @@ export const SummarySection = ({ { defaultMessage: 'Resources Evaluated' } ), title: , - onClick: () => { - navToFindingsByResource(getPolicyTemplateQuery(dashboardType)); - }, - }, - { - id: DASHBOARD_COUNTER_CARDS.FAILING_FINDINGS, - description: i18n.translate( - 'xpack.csp.dashboard.summarySection.counterCard.failingFindingsDescription', - { defaultMessage: 'Failing Findings' } + button: ( + { + navToFindingsByResource(getPolicyTemplateQuery(dashboardType)); + }} + > + {i18n.translate( + 'xpack.csp.dashboard.summarySection.counterCard.resourcesEvaluatedButtonTitle', + { defaultMessage: 'View all resources' } + )} + ), - title: , - titleColor: complianceData.stats.totalFailed > 0 ? statusColors.failed : 'text', - onClick: () => { - navToFindings({ - 'result.evaluation': RULE_FAILED, - ...getPolicyTemplateQuery(dashboardType), - }); - }, }, ], [ complianceData.clusters, complianceData.stats.resourcesEvaluated, - complianceData.stats.totalFailed, + cspmIntegrationLink, dashboardType, - navToFindings, + kspmIntegrationLink, navToFindingsByResource, ] ); diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index aa95ebb6e2aef..260328513d6c1 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -11377,7 +11377,6 @@ "xpack.csp.dashboard.summarySection.complianceByCisSectionPanelTitle": "Conformité par section CIS", "xpack.csp.dashboard.summarySection.counterCard.accountsEvaluatedDescription": "Comptes évalués", "xpack.csp.dashboard.summarySection.counterCard.clustersEvaluatedDescription": "Clusters évalués", - "xpack.csp.dashboard.summarySection.counterCard.failingFindingsDescription": "Résultats en échec", "xpack.csp.dashboard.summarySection.counterCard.resourcesEvaluatedDescription": "Ressources évaluées", "xpack.csp.dashboardTabs.cloudTab.tabTitle": "Cloud", "xpack.csp.dashboardTabs.kubernetesTab.tabTitle": "Kubernetes", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 0c085d75d7902..db4e8cc674281 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -11392,7 +11392,6 @@ "xpack.csp.dashboard.summarySection.complianceByCisSectionPanelTitle": "CISセクション別のコンプライアンス", "xpack.csp.dashboard.summarySection.counterCard.accountsEvaluatedDescription": "評価されたアカウント", "xpack.csp.dashboard.summarySection.counterCard.clustersEvaluatedDescription": "評価されたクラスター", - "xpack.csp.dashboard.summarySection.counterCard.failingFindingsDescription": "失敗した調査結果", "xpack.csp.dashboard.summarySection.counterCard.resourcesEvaluatedDescription": "評価されたリソース", "xpack.csp.dashboardTabs.cloudTab.tabTitle": "クラウド", "xpack.csp.dashboardTabs.kubernetesTab.tabTitle": "Kubernetes", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 4cc2eb901cbbd..68e8ae1196d60 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -11392,7 +11392,6 @@ "xpack.csp.dashboard.summarySection.complianceByCisSectionPanelTitle": "合规性(按 CIS 部分)", "xpack.csp.dashboard.summarySection.counterCard.accountsEvaluatedDescription": "已评估帐户", "xpack.csp.dashboard.summarySection.counterCard.clustersEvaluatedDescription": "集群已评估", - "xpack.csp.dashboard.summarySection.counterCard.failingFindingsDescription": "失败的结果", "xpack.csp.dashboard.summarySection.counterCard.resourcesEvaluatedDescription": "资源已评估", "xpack.csp.dashboardTabs.cloudTab.tabTitle": "云", "xpack.csp.dashboardTabs.kubernetesTab.tabTitle": "Kubernetes", From 0d97cc71fcb138252cd965f5d772c3ce337e2625 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Thu, 28 Sep 2023 11:00:05 +0200 Subject: [PATCH 2/4] [ML] AIOps: Move log pattern analysis fetch code to `common` to be available via `server` (#167465) Follow up to #167237. Part of #167467. We plan to reuse some of the queries log pattern analysis does to use via log rate analysis too. Log pattern analysis mostly does queries from the client side, late rate analysis has its own API endpoint and does ES queries via Kibana server. In preparation for the use via log rate analysis, this moves the code we need to have available server side for log rate analysis to the `common` area of the plugin so it can be used both on server/client. --- .../create_categorize_query.ts | 48 +++++ .../create_category_request.ts | 66 +++++++ .../log_categorization/get_category_query.ts | 33 ++++ .../process_category_results.ts | 51 +++++ .../common/api/log_categorization/types.ts | 38 ++++ .../category_table/category_table.tsx | 20 +- .../document_count_chart.tsx | 8 +- .../log_categorization_for_flyout.tsx | 28 +-- .../log_categorization_page.tsx | 6 +- .../use_categorize_request.ts | 177 ++---------------- .../log_categorization/use_discover_links.ts | 22 +-- .../use_validate_category_field.ts | 5 +- 12 files changed, 300 insertions(+), 202 deletions(-) create mode 100644 x-pack/plugins/aiops/common/api/log_categorization/create_categorize_query.ts create mode 100644 x-pack/plugins/aiops/common/api/log_categorization/create_category_request.ts create mode 100644 x-pack/plugins/aiops/common/api/log_categorization/get_category_query.ts create mode 100644 x-pack/plugins/aiops/common/api/log_categorization/process_category_results.ts create mode 100644 x-pack/plugins/aiops/common/api/log_categorization/types.ts diff --git a/x-pack/plugins/aiops/common/api/log_categorization/create_categorize_query.ts b/x-pack/plugins/aiops/common/api/log_categorization/create_categorize_query.ts new file mode 100644 index 0000000000000..8b9e3a95c653f --- /dev/null +++ b/x-pack/plugins/aiops/common/api/log_categorization/create_categorize_query.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { cloneDeep } from 'lodash'; + +import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; + +export function createCategorizeQuery( + queryIn: QueryDslQueryContainer, + timeField: string, + from: number | undefined, + to: number | undefined +) { + const query = cloneDeep(queryIn); + + if (query.bool === undefined) { + query.bool = {}; + } + if (query.bool.must === undefined) { + query.bool.must = []; + if (query.match_all !== undefined) { + query.bool.must.push({ match_all: query.match_all }); + delete query.match_all; + } + } + if (query.multi_match !== undefined) { + query.bool.should = { + multi_match: query.multi_match, + }; + delete query.multi_match; + } + + (query.bool.must as QueryDslQueryContainer[]).push({ + range: { + [timeField]: { + gte: from, + lte: to, + format: 'epoch_millis', + }, + }, + }); + + return query; +} diff --git a/x-pack/plugins/aiops/common/api/log_categorization/create_category_request.ts b/x-pack/plugins/aiops/common/api/log_categorization/create_category_request.ts new file mode 100644 index 0000000000000..38acb5029e830 --- /dev/null +++ b/x-pack/plugins/aiops/common/api/log_categorization/create_category_request.ts @@ -0,0 +1,66 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; + +import { createRandomSamplerWrapper } from '@kbn/ml-random-sampler-utils'; + +import { createCategorizeQuery } from './create_categorize_query'; + +const CATEGORY_LIMIT = 1000; +const EXAMPLE_LIMIT = 1; + +export function createCategoryRequest( + index: string, + field: string, + timeField: string, + from: number | undefined, + to: number | undefined, + queryIn: QueryDslQueryContainer, + wrap: ReturnType['wrap'], + intervalMs?: number +) { + const query = createCategorizeQuery(queryIn, timeField, from, to); + const aggs = { + categories: { + categorize_text: { + field, + size: CATEGORY_LIMIT, + }, + aggs: { + hit: { + top_hits: { + size: EXAMPLE_LIMIT, + sort: [timeField], + _source: field, + }, + }, + ...(intervalMs + ? { + sparkline: { + date_histogram: { + field: timeField, + fixed_interval: `${intervalMs}ms`, + }, + }, + } + : {}), + }, + }, + }; + + return { + params: { + index, + size: 0, + body: { + query, + aggs: wrap(aggs), + }, + }, + }; +} diff --git a/x-pack/plugins/aiops/common/api/log_categorization/get_category_query.ts b/x-pack/plugins/aiops/common/api/log_categorization/get_category_query.ts new file mode 100644 index 0000000000000..ba6e2886f6a9e --- /dev/null +++ b/x-pack/plugins/aiops/common/api/log_categorization/get_category_query.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Category } from './types'; + +export const QUERY_MODE = { + INCLUDE: 'should', + EXCLUDE: 'must_not', +} as const; +export type QueryMode = typeof QUERY_MODE[keyof typeof QUERY_MODE]; + +export const getCategoryQuery = ( + field: string, + categories: Category[], + mode: QueryMode = QUERY_MODE.INCLUDE +) => ({ + bool: { + [mode]: categories.map(({ key: query }) => ({ + match: { + [field]: { + auto_generate_synonyms_phrase_query: false, + fuzziness: 0, + operator: 'and', + query, + }, + }, + })), + }, +}); diff --git a/x-pack/plugins/aiops/common/api/log_categorization/process_category_results.ts b/x-pack/plugins/aiops/common/api/log_categorization/process_category_results.ts new file mode 100644 index 0000000000000..347ba5711719d --- /dev/null +++ b/x-pack/plugins/aiops/common/api/log_categorization/process_category_results.ts @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { get } from 'lodash'; + +import { estypes } from '@elastic/elasticsearch'; + +import { createRandomSamplerWrapper } from '@kbn/ml-random-sampler-utils'; + +import type { Category, CategoriesAgg, CatResponse, SparkLinesPerCategory } from './types'; + +export function processCategoryResults( + result: CatResponse, + field: string, + unwrap: ReturnType['unwrap'] +) { + const sparkLinesPerCategory: SparkLinesPerCategory = {}; + const { aggregations } = result.rawResponse; + if (aggregations === undefined) { + throw new Error('processCategoryResults failed, did not return aggregations.'); + } + const { + categories: { buckets }, + } = unwrap( + aggregations as unknown as Record + ) as CategoriesAgg; + + const categories: Category[] = buckets.map((b) => { + sparkLinesPerCategory[b.key] = + b.sparkline === undefined + ? {} + : b.sparkline.buckets.reduce>((acc2, cur2) => { + acc2[cur2.key] = cur2.doc_count; + return acc2; + }, {}); + + return { + key: b.key, + count: b.doc_count, + examples: b.hit.hits.hits.map((h) => get(h._source, field)), + }; + }); + return { + categories, + sparkLinesPerCategory, + }; +} diff --git a/x-pack/plugins/aiops/common/api/log_categorization/types.ts b/x-pack/plugins/aiops/common/api/log_categorization/types.ts new file mode 100644 index 0000000000000..83e16d8ada7d0 --- /dev/null +++ b/x-pack/plugins/aiops/common/api/log_categorization/types.ts @@ -0,0 +1,38 @@ +/* + * 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 { estypes } from '@elastic/elasticsearch'; + +export interface Category { + key: string; + count: number; + examples: string[]; + sparkline?: Array<{ doc_count: number; key: number; key_as_string: string }>; +} + +export interface CategoriesAgg { + categories: { + buckets: Array<{ + key: string; + doc_count: number; + hit: { hits: { hits: Array<{ _source: { message: string } }> } }; + sparkline: { + buckets: Array<{ key_as_string: string; key: number; doc_count: number }>; + }; + }>; + }; +} + +interface CategoriesSampleAgg { + sample: CategoriesAgg; +} + +export interface CatResponse { + rawResponse: estypes.SearchResponseBody; +} + +export type SparkLinesPerCategory = Record>; diff --git a/x-pack/plugins/aiops/public/components/log_categorization/category_table/category_table.tsx b/x-pack/plugins/aiops/public/components/log_categorization/category_table/category_table.tsx index 1dc30c04253bc..346fc7f5b3562 100644 --- a/x-pack/plugins/aiops/public/components/log_categorization/category_table/category_table.tsx +++ b/x-pack/plugins/aiops/public/components/log_categorization/category_table/category_table.tsx @@ -6,8 +6,7 @@ */ import React, { FC, useMemo, useState } from 'react'; -import { i18n } from '@kbn/i18n'; -import type { TimefilterContract } from '@kbn/data-plugin/public'; + import { useEuiBackgroundColor, EuiInMemoryTable, @@ -19,14 +18,25 @@ import { EuiSpacer, } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import type { TimefilterContract } from '@kbn/data-plugin/public'; import { DataViewField } from '@kbn/data-views-plugin/common'; import { Filter } from '@kbn/es-query'; import { useTableState } from '@kbn/ml-in-memory-table'; -import { useDiscoverLinks, createFilter, QueryMode, QUERY_MODE } from '../use_discover_links'; -import { MiniHistogram } from '../../mini_histogram'; + +import type { + Category, + SparkLinesPerCategory, +} from '../../../../common/api/log_categorization/types'; + import { useEuiTheme } from '../../../hooks/use_eui_theme'; import type { LogCategorizationAppState } from '../../../application/utils/url_state'; -import type { EventRate, Category, SparkLinesPerCategory } from '../use_categorize_request'; + +import { MiniHistogram } from '../../mini_histogram'; + +import { useDiscoverLinks, createFilter, QueryMode, QUERY_MODE } from '../use_discover_links'; +import type { EventRate } from '../use_categorize_request'; + import { getLabels } from './labels'; import { TableHeader } from './table_header'; diff --git a/x-pack/plugins/aiops/public/components/log_categorization/document_count_chart.tsx b/x-pack/plugins/aiops/public/components/log_categorization/document_count_chart.tsx index d34861b924ccb..859eaed0fec57 100644 --- a/x-pack/plugins/aiops/public/components/log_categorization/document_count_chart.tsx +++ b/x-pack/plugins/aiops/public/components/log_categorization/document_count_chart.tsx @@ -9,11 +9,15 @@ import React, { FC, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { DocumentCountChart as DocumentCountChartRoot } from '@kbn/aiops-components'; + +import type { Category, SparkLinesPerCategory } from '../../../common/api/log_categorization/types'; + import { useAiopsAppContext } from '../../hooks/use_aiops_app_context'; +import { DocumentCountStats } from '../../get_document_stats'; + import { TotalCountHeader } from '../document_count_content/total_count_header'; -import type { Category, SparkLinesPerCategory } from './use_categorize_request'; + import type { EventRate } from './use_categorize_request'; -import { DocumentCountStats } from '../../get_document_stats'; interface Props { totalCount: number; diff --git a/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_for_flyout.tsx b/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_for_flyout.tsx index 599a669197ee5..28c1350a88141 100644 --- a/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_for_flyout.tsx +++ b/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_for_flyout.tsx @@ -5,10 +5,7 @@ * 2.0. */ import React, { FC, useState, useEffect, useCallback, useRef, useMemo } from 'react'; -import type { SavedSearch } from '@kbn/saved-search-plugin/public'; -import type { DataView, DataViewField } from '@kbn/data-views-plugin/public'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; + import { EuiTitle, EuiFlyoutHeader, @@ -18,26 +15,33 @@ import { useEuiTheme, } from '@elastic/eui'; +import type { SavedSearch } from '@kbn/saved-search-plugin/public'; +import type { DataView, DataViewField } from '@kbn/data-views-plugin/public'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; import { buildEmptyFilter, Filter } from '@kbn/es-query'; - import { usePageUrlState } from '@kbn/ml-url-state'; import type { FieldValidationResults } from '@kbn/ml-category-validator'; + +import type { Category, SparkLinesPerCategory } from '../../../common/api/log_categorization/types'; + +import { + type LogCategorizationPageUrlState, + getDefaultLogCategorizationAppState, +} from '../../application/utils/url_state'; +import { createMergedEsQuery } from '../../application/utils/search_utils'; import { useData } from '../../hooks/use_data'; import { useSearch } from '../../hooks/use_search'; +import { useAiopsAppContext } from '../../hooks/use_aiops_app_context'; + import { useCategorizeRequest } from './use_categorize_request'; -import type { EventRate, Category, SparkLinesPerCategory } from './use_categorize_request'; +import type { EventRate } from './use_categorize_request'; import { CategoryTable } from './category_table'; -import { useAiopsAppContext } from '../../hooks/use_aiops_app_context'; import { InformationText } from './information_text'; -import { createMergedEsQuery } from '../../application/utils/search_utils'; import { SamplingMenu } from './sampling_menu'; import { TechnicalPreviewBadge } from './technical_preview_badge'; import { LoadingCategorization } from './loading_categorization'; import { useValidateFieldRequest } from './use_validate_category_field'; -import { - type LogCategorizationPageUrlState, - getDefaultLogCategorizationAppState, -} from '../../application/utils/url_state'; import { FieldValidationCallout } from './category_validation_callout'; export interface LogCategorizationPageProps { diff --git a/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx b/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx index 9539ff607e05e..5ccdca64d1036 100644 --- a/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx +++ b/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx @@ -24,9 +24,11 @@ import { Filter, Query } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { usePageUrlState, useUrlState } from '@kbn/ml-url-state'; - import type { FieldValidationResults } from '@kbn/ml-category-validator'; import type { SearchQueryLanguage } from '@kbn/ml-query-utils'; + +import type { Category, SparkLinesPerCategory } from '../../../common/api/log_categorization/types'; + import { useDataSource } from '../../hooks/use_data_source'; import { useData } from '../../hooks/use_data'; import { useSearch } from '../../hooks/use_search'; @@ -39,7 +41,7 @@ import { import { SearchPanel } from '../search_panel'; import { PageHeader } from '../page_header'; -import type { EventRate, Category, SparkLinesPerCategory } from './use_categorize_request'; +import type { EventRate } from './use_categorize_request'; import { useCategorizeRequest } from './use_categorize_request'; import { CategoryTable } from './category_table'; import { DocumentCountChart } from './document_count_chart'; diff --git a/x-pack/plugins/aiops/public/components/log_categorization/use_categorize_request.ts b/x-pack/plugins/aiops/public/components/log_categorization/use_categorize_request.ts index 3108ec9391cef..8179751266e6e 100644 --- a/x-pack/plugins/aiops/public/components/log_categorization/use_categorize_request.ts +++ b/x-pack/plugins/aiops/public/components/log_categorization/use_categorize_request.ts @@ -5,62 +5,37 @@ * 2.0. */ -import { cloneDeep, get } from 'lodash'; import { useRef, useCallback, useMemo } from 'react'; -import { isCompleteResponse } from '@kbn/data-plugin/public'; + import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; -import { createRandomSamplerWrapper } from '@kbn/ml-random-sampler-utils'; -import { estypes } from '@elastic/elasticsearch'; +import { isCompleteResponse } from '@kbn/data-plugin/public'; import { useStorage } from '@kbn/ml-local-storage'; + +import { createCategoryRequest } from '../../../common/api/log_categorization/create_category_request'; +import { processCategoryResults } from '../../../common/api/log_categorization/process_category_results'; +import type { + Category, + CatResponse, + SparkLinesPerCategory, +} from '../../../common/api/log_categorization/types'; + import { useAiopsAppContext } from '../../hooks/use_aiops_app_context'; -import { RandomSampler } from './sampling_menu'; import { type AiOpsKey, type AiOpsStorageMapped, AIOPS_RANDOM_SAMPLING_MODE_PREFERENCE, AIOPS_RANDOM_SAMPLING_PROBABILITY_PREFERENCE, } from '../../types/storage'; -import { RANDOM_SAMPLER_OPTION, DEFAULT_PROBABILITY } from './sampling_menu/random_sampler'; -const CATEGORY_LIMIT = 1000; -const EXAMPLE_LIMIT = 1; - -interface CategoriesAgg { - categories: { - buckets: Array<{ - key: string; - doc_count: number; - hit: { hits: { hits: Array<{ _source: { message: string } }> } }; - sparkline: { - buckets: Array<{ key_as_string: string; key: number; doc_count: number }>; - }; - }>; - }; -} - -interface CategoriesSampleAgg { - sample: CategoriesAgg; -} - -interface CatResponse { - rawResponse: estypes.SearchResponseBody; -} - -export interface Category { - key: string; - count: number; - examples: string[]; - sparkline?: Array<{ doc_count: number; key: number; key_as_string: string }>; -} +import { RandomSampler } from './sampling_menu'; +import { RANDOM_SAMPLER_OPTION, DEFAULT_PROBABILITY } from './sampling_menu/random_sampler'; export type EventRate = Array<{ key: number; docCount: number; }>; -export type SparkLinesPerCategory = Record>; - export function useCategorizeRequest() { const [randomSamplerMode, setRandomSamplerMode] = useStorage< AiOpsKey, @@ -135,129 +110,3 @@ export function useCategorizeRequest() { return { runCategorizeRequest, cancelRequest, randomSampler }; } - -function createCategoryRequest( - index: string, - field: string, - timeField: string, - from: number | undefined, - to: number | undefined, - queryIn: QueryDslQueryContainer, - wrap: ReturnType['wrap'], - intervalMs?: number -) { - const query = createCategorizeQuery(queryIn, timeField, from, to); - const aggs = { - categories: { - categorize_text: { - field, - size: CATEGORY_LIMIT, - }, - aggs: { - hit: { - top_hits: { - size: EXAMPLE_LIMIT, - sort: [timeField], - _source: field, - }, - }, - ...(intervalMs - ? { - sparkline: { - date_histogram: { - field: timeField, - fixed_interval: `${intervalMs}ms`, - }, - }, - } - : {}), - }, - }, - }; - - return { - params: { - index, - size: 0, - body: { - query, - aggs: wrap(aggs), - }, - }, - }; -} - -export function createCategorizeQuery( - queryIn: QueryDslQueryContainer, - timeField: string, - from: number | undefined, - to: number | undefined -) { - const query = cloneDeep(queryIn); - - if (query.bool === undefined) { - query.bool = {}; - } - if (query.bool.must === undefined) { - query.bool.must = []; - if (query.match_all !== undefined) { - query.bool.must.push({ match_all: query.match_all }); - delete query.match_all; - } - } - if (query.multi_match !== undefined) { - query.bool.should = { - multi_match: query.multi_match, - }; - delete query.multi_match; - } - - (query.bool.must as QueryDslQueryContainer[]).push({ - range: { - [timeField]: { - gte: from, - lte: to, - format: 'epoch_millis', - }, - }, - }); - - return query; -} - -function processCategoryResults( - result: CatResponse, - field: string, - unwrap: ReturnType['unwrap'] -) { - const sparkLinesPerCategory: SparkLinesPerCategory = {}; - const { aggregations } = result.rawResponse; - if (aggregations === undefined) { - throw new Error('processCategoryResults failed, did not return aggregations.'); - } - const { - categories: { buckets }, - } = unwrap( - aggregations as unknown as Record - ) as CategoriesAgg; - - const categories: Category[] = buckets.map((b) => { - sparkLinesPerCategory[b.key] = - b.sparkline === undefined - ? {} - : b.sparkline.buckets.reduce>((acc2, cur2) => { - acc2[cur2.key] = cur2.doc_count; - return acc2; - }, {}); - - return { - key: b.key, - count: b.doc_count, - examples: b.hit.hits.hits.map((h) => get(h._source, field)), - }; - }); - return { - categories, - sparkLinesPerCategory, - }; -} diff --git a/x-pack/plugins/aiops/public/components/log_categorization/use_discover_links.ts b/x-pack/plugins/aiops/public/components/log_categorization/use_discover_links.ts index cb3b60268f438..22e8e50ebcf19 100644 --- a/x-pack/plugins/aiops/public/components/log_categorization/use_discover_links.ts +++ b/x-pack/plugins/aiops/public/components/log_categorization/use_discover_links.ts @@ -5,15 +5,18 @@ * 2.0. */ -import rison from '@kbn/rison'; import moment from 'moment'; +import rison from '@kbn/rison'; import type { TimeRangeBounds } from '@kbn/data-plugin/common'; import { i18n } from '@kbn/i18n'; import type { Filter } from '@kbn/es-query'; + +import { getCategoryQuery } from '../../../common/api/log_categorization/get_category_query'; +import type { Category } from '../../../common/api/log_categorization/types'; + import type { AiOpsIndexBasedAppState } from '../../application/utils/url_state'; import { useAiopsAppContext } from '../../hooks/use_aiops_app_context'; -import type { Category } from './use_categorize_request'; export const QUERY_MODE = { INCLUDE: 'should', @@ -71,20 +74,7 @@ export function createFilter( ): Filter { const selectedRows = category === undefined ? selection : [category]; return { - query: { - bool: { - [mode]: selectedRows.map(({ key: query }) => ({ - match: { - [field]: { - auto_generate_synonyms_phrase_query: false, - fuzziness: 0, - operator: 'and', - query, - }, - }, - })), - }, - }, + query: getCategoryQuery(field, selectedRows, mode), meta: { alias: i18n.translate('xpack.aiops.logCategorization.filterAliasLabel', { defaultMessage: 'Categorization - {field}', diff --git a/x-pack/plugins/aiops/public/components/log_categorization/use_validate_category_field.ts b/x-pack/plugins/aiops/public/components/log_categorization/use_validate_category_field.ts index d354ed5f3fbb6..8e0850aa1daa8 100644 --- a/x-pack/plugins/aiops/public/components/log_categorization/use_validate_category_field.ts +++ b/x-pack/plugins/aiops/public/components/log_categorization/use_validate_category_field.ts @@ -8,10 +8,13 @@ import { useRef, useCallback } from 'react'; import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; + import type { FieldValidationResults } from '@kbn/ml-category-validator'; + import { AIOPS_API_ENDPOINT } from '../../../common/api'; +import { createCategorizeQuery } from '../../../common/api/log_categorization/create_categorize_query'; + import { useAiopsAppContext } from '../../hooks/use_aiops_app_context'; -import { createCategorizeQuery } from './use_categorize_request'; export function useValidateFieldRequest() { const { http } = useAiopsAppContext(); From 2eddd54cdd4098c9a298e66e1ef0df3bde08bb24 Mon Sep 17 00:00:00 2001 From: Dzmitry Lemechko Date: Thu, 28 Sep 2023 11:00:22 +0200 Subject: [PATCH 3/4] [ftr] pass password for UI login + improve login/logout steps with proper wait logic (#166936) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Hopefully closes #167104 closes #167130 closes #167100 closes #167013 closes #166964 Fixing a few issues with login/logout: 1. Failed to login in "before" hook Screenshot 2023-09-25 at 12 37 45 My theory is that we are loading `/login` route too soon while log out was not completed yet. When we navigate to `https://localhost:5620/logout` there are multiple url re-directions with final page being Cloud login form. This PR makes sure we wait for this form to be displayed + 2500 ms extra to avoid "immediate" /login navigation 2. Failed login on MKI: Updating login via UI for serverless to pass password valid for deployment: currently FTR uses `changeme` for both Kibana CI & MKI. 3. ES activate user profile call returning 500 We saw some login failures that are preceded with the following logs: ``` [00:03:27] │ debg Find.clickByCssSelector('[data-test-subj="loginSubmit"]') with timeout=10000 [00:03:27] │ debg Find.findByCssSelector('[data-test-subj="loginSubmit"]') with timeout=10000 [00:03:27] │ debg Find.waitForDeletedByCssSelector('.kibanaWelcomeLogo') with timeout=10000 [00:03:27] │ proc [kibana] [2023-09-19T07:08:26.126+00:00][INFO ][plugins.security.routes] Logging in with provider "basic" (basic) [00:03:27] │ info [o.e.x.s.s.SecurityIndexManager] [ftr] security index does not exist, creating [.security-profile-8] with alias [.security-profile] [00:03:27] │ proc [kibana] [2023-09-19T07:08:26.140+00:00][ERROR][plugins.security.user-profile] Failed to activate user profile: {"error":{"root_cause":[{"type":"validation_exception","reason":"Validation Failed: 1: this action would add [1] shards, but this cluster currently has [27]/[27] maximum normal shards open;"}],"type":"validation_exception","reason":"Validation Failed: 1: this action would add [1] shards, but this cluster currently has [27]/[27] maximum normal shards open;"},"status":400}. [00:03:27] │ proc [kibana] [2023-09-19T07:08:26.140+00:00][ERROR][http] 500 Server Error [00:03:27] │ warn browser[SEVERE] http://localhost:5620/internal/security/login - Failed to load resource: the server responded with a status of 500 (Internal Server Error) ``` User activation happens during `POST internal/security/login` call to Kibana server. ~~The only improvement that we can do from FTR perspective is to call this end-point via API to makes sure user is activated and only after proceed with UI login.~~ While working on issue #4 and talking to @jeramysoucy I believe retrying login via UI will work here as well. We are checking if we are still on login page (similar to incorrect password login), waiting 2500 ms and pressing login button again. 4. Failed to login with Kibana reporting UNEXPECTED_SESSION_ERROR and been re-directed to Cloud login page ``` proc [kibana] [2023-09-25T11:35:12.794+00:00][INFO ][plugins.security.authentication] Authentication attempt failed: UNEXPECTED_SESSION_ERROR ``` Temporary solution is to retry login from scratch (navigation to Kibana login page & re-login ) Flaky-test-runner for functional obtl tests 50x https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/3215 This PR is not fixing random 401 response when user navigates to some apps with direct url --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../functional/page_objects/security_page.ts | 11 ++- .../page_objects/svl_common_page.ts | 78 ++++++++++++++++++- 2 files changed, 85 insertions(+), 4 deletions(-) diff --git a/x-pack/test/functional/page_objects/security_page.ts b/x-pack/test/functional/page_objects/security_page.ts index 081bd9f297503..5b0a9a679840a 100644 --- a/x-pack/test/functional/page_objects/security_page.ts +++ b/x-pack/test/functional/page_objects/security_page.ts @@ -168,7 +168,7 @@ export class SecurityPageObject extends FtrService { ); } - private async isLoginFormVisible() { + public async isLoginFormVisible() { return await this.testSubjects.exists('loginForm'); } @@ -323,7 +323,14 @@ export class SecurityPageObject extends FtrService { if (alert?.accept) { await alert.accept(); } - return !(await this.browser.getCurrentUrl()).includes('/logout'); + + if (this.config.get('serverless')) { + // Logout might trigger multiple redirects, but in the end we expect the Cloud login page + this.log.debug('Wait 5 sec for Cloud login page to be displayed'); + return await this.find.existsByDisplayedByCssSelector('.login-form-password', 5000); + } else { + return !(await this.browser.getCurrentUrl()).includes('/logout'); + } }); } } diff --git a/x-pack/test_serverless/functional/page_objects/svl_common_page.ts b/x-pack/test_serverless/functional/page_objects/svl_common_page.ts index cf96bfd274eb9..7762bf92d046a 100644 --- a/x-pack/test_serverless/functional/page_objects/svl_common_page.ts +++ b/x-pack/test_serverless/functional/page_objects/svl_common_page.ts @@ -10,16 +10,90 @@ import { FtrProviderContext } from '../ftr_provider_context'; export function SvlCommonPageProvider({ getService, getPageObjects }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const config = getService('config'); - const pageObjects = getPageObjects(['security']); + const pageObjects = getPageObjects(['security', 'common']); + const retry = getService('retry'); + const deployment = getService('deployment'); + const log = getService('log'); + const browser = getService('browser'); + + const delay = (ms: number) => + new Promise((resolve) => { + setTimeout(resolve, ms); + }); return { + async navigateToLoginForm() { + const url = deployment.getHostPort() + '/login'; + await browser.get(url); + // ensure welcome screen won't be shown. This is relevant for environments which don't allow + // to use the yml setting, e.g. cloud + await browser.setLocalStorageItem('home:welcome:show', 'false'); + + log.debug('Waiting for Login Form to appear.'); + await retry.waitForWithTimeout('login form', 10_000, async () => { + return await pageObjects.security.isLoginFormVisible(); + }); + }, + async login() { await pageObjects.security.forceLogout({ waitForLoginPage: false }); - return await pageObjects.security.login(config.get('servers.kibana.username')); + + // adding sleep to settle down logout + await pageObjects.common.sleep(2500); + + await retry.waitForWithTimeout( + 'Waiting for successful authentication', + 90_000, + async () => { + if (!(await testSubjects.exists('loginUsername', { timeout: 1000 }))) { + await this.navigateToLoginForm(); + + await testSubjects.setValue('loginUsername', config.get('servers.kibana.username')); + await testSubjects.setValue('loginPassword', config.get('servers.kibana.password')); + await testSubjects.click('loginSubmit'); + } + + if (await testSubjects.exists('userMenuButton', { timeout: 10_000 })) { + log.debug('userMenuButton is found, logged in passed'); + return true; + } else { + throw new Error(`Failed to login to Kibana via UI`); + } + }, + async () => { + // Sometimes authentication fails and user is redirected to Cloud login page + // [plugins.security.authentication] Authentication attempt failed: UNEXPECTED_SESSION_ERROR + const currentUrl = await browser.getCurrentUrl(); + if (currentUrl.startsWith('https://cloud.elastic.co')) { + log.debug( + 'Probably authentication attempt failed, we are at Cloud login page. Retrying from scratch' + ); + } else { + const authError = await testSubjects.exists('promptPage', { timeout: 2500 }); + if (authError) { + log.debug('Probably SAML callback page, doing logout again'); + await pageObjects.security.forceLogout({ waitForLoginPage: false }); + } else { + const isOnLoginPage = await testSubjects.exists('loginUsername', { timeout: 1000 }); + if (isOnLoginPage) { + log.debug( + 'Probably ES user profile activation failed, waiting 2 seconds and pressing Login button again' + ); + await delay(2000); + await testSubjects.click('loginSubmit'); + } else { + log.debug('New behaviour, trying to navigate and login again'); + } + } + } + } + ); + log.debug('Logged in successfully'); }, async forceLogout() { await pageObjects.security.forceLogout({ waitForLoginPage: false }); + log.debug('Logged out successfully'); }, async assertProjectHeaderExists() { From 8a2701cf211eaafad423512c56afde26eaacf804 Mon Sep 17 00:00:00 2001 From: Carlos Crespo Date: Thu, 28 Sep 2023 11:12:46 +0200 Subject: [PATCH 4/4] [Type Check] Add newly included AlertStatus item to the infra tests (#167470) ## Summary This PR fix type check problems caused by the inclusion of a new item in the AlertStatus union type --- x-pack/test/functional/page_objects/infra_hosts_view.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/x-pack/test/functional/page_objects/infra_hosts_view.ts b/x-pack/test/functional/page_objects/infra_hosts_view.ts index 1cd0cf15996ec..4dda165ea96a7 100644 --- a/x-pack/test/functional/page_objects/infra_hosts_view.ts +++ b/x-pack/test/functional/page_objects/infra_hosts_view.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { AlertStatus, ALERT_STATUS_ACTIVE, ALERT_STATUS_RECOVERED } from '@kbn/rule-data-utils'; +import { AlertStatus } from '@kbn/rule-data-utils'; import { WebElementWrapper } from '../../../../test/functional/services/lib/web_element_wrapper'; import { FtrProviderContext } from '../ftr_provider_context'; @@ -201,9 +201,10 @@ export function InfraHostsViewProvider({ getService }: FtrProviderContext) { }, setAlertStatusFilter(alertStatus?: AlertStatus) { - const buttons = { - [ALERT_STATUS_ACTIVE]: 'hostsView-alert-status-filter-active-button', - [ALERT_STATUS_RECOVERED]: 'hostsView-alert-status-filter-recovered-button', + const buttons: Record = { + active: 'hostsView-alert-status-filter-active-button', + recovered: 'hostsView-alert-status-filter-recovered-button', + untracked: 'hostsView-alert-status-filter-untracked-button', all: 'hostsView-alert-status-filter-show-all-button', };