({ rawResponse: { @@ -35,9 +36,13 @@ jest.spyOn(RxApi, 'lastValueFrom').mockImplementation(async () => ({ })); async function mountAndFindSubjects( - props: Omit + props: Omit< + DiscoverNoResultsProps, + 'onDisableFilters' | 'data' | 'isTimeBased' | 'stateContainer' + > ) { const services = createDiscoverServicesMock(); + const isTimeBased = props.dataView.isTimeBased(); let component: ReactWrapper; @@ -45,7 +50,8 @@ async function mountAndFindSubjects( component = await mountWithIntl( {}} {...props} /> diff --git a/src/plugins/discover/public/application/main/components/no_results/no_results.tsx b/src/plugins/discover/public/application/main/components/no_results/no_results.tsx index bd010502df149..86f73e18ca4d0 100644 --- a/src/plugins/discover/public/application/main/components/no_results/no_results.tsx +++ b/src/plugins/discover/public/application/main/components/no_results/no_results.tsx @@ -10,10 +10,14 @@ import React from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import type { DataView } from '@kbn/data-views-plugin/common'; import type { AggregateQuery, Filter, Query } from '@kbn/es-query'; +import { SearchResponseWarnings } from '@kbn/search-response-warnings'; import { NoResultsSuggestions } from './no_results_suggestions'; +import type { DiscoverStateContainer } from '../../services/discover_state'; +import { useDataState } from '../../hooks/use_data_state'; import './_no_results.scss'; export interface DiscoverNoResultsProps { + stateContainer: DiscoverStateContainer; isTimeBased?: boolean; query: Query | AggregateQuery | undefined; filters: Filter[] | undefined; @@ -22,12 +26,26 @@ export interface DiscoverNoResultsProps { } export function DiscoverNoResults({ + stateContainer, isTimeBased, query, filters, dataView, onDisableFilters, }: DiscoverNoResultsProps) { + const { documents$ } = stateContainer.dataState.data$; + const interceptedWarnings = useDataState(documents$).interceptedWarnings; + + if (interceptedWarnings?.length) { + return ( + + ); + } + return ( diff --git a/src/plugins/discover/public/application/main/components/no_results/no_results_suggestions/no_results_suggestions.tsx b/src/plugins/discover/public/application/main/components/no_results/no_results_suggestions/no_results_suggestions.tsx index c55e8de773942..633f082c4792b 100644 --- a/src/plugins/discover/public/application/main/components/no_results/no_results_suggestions/no_results_suggestions.tsx +++ b/src/plugins/discover/public/application/main/components/no_results/no_results_suggestions/no_results_suggestions.tsx @@ -121,6 +121,7 @@ export const NoResultsSuggestions: React.FC = ({ layout="horizontal" color="plain" icon={} + hasBorder title={

{ + .then(({ records, textBasedQueryColumns, interceptedWarnings }) => { if (services.analytics) { const duration = window.performance.now() - startTime; reportPerformanceMetricEvent(services.analytics, { @@ -131,6 +131,7 @@ export function fetchAll( fetchStatus, result: records, textBasedQueryColumns, + interceptedWarnings, recordRawType, query, }); diff --git a/src/plugins/discover/public/application/main/utils/fetch_documents.ts b/src/plugins/discover/public/application/main/utils/fetch_documents.ts index 4a4e388a27367..bce5f266d6def 100644 --- a/src/plugins/discover/public/application/main/utils/fetch_documents.ts +++ b/src/plugins/discover/public/application/main/utils/fetch_documents.ts @@ -11,7 +11,9 @@ import { lastValueFrom } from 'rxjs'; import { isCompleteResponse, ISearchSource } from '@kbn/data-plugin/public'; import { SAMPLE_SIZE_SETTING, buildDataTableRecordList } from '@kbn/discover-utils'; import type { EsHitRecord } from '@kbn/discover-utils/types'; +import { getSearchResponseInterceptedWarnings } from '@kbn/search-response-warnings'; import type { RecordsFetchResponse } from '../../../types'; +import { DISABLE_SHARD_FAILURE_WARNING } from '../../../../common/constants'; import { FetchDeps } from './fetch_all'; /** @@ -53,6 +55,7 @@ export const fetchDocuments = ( }), }, executionContext, + disableShardFailureWarning: DISABLE_SHARD_FAILURE_WARNING, }) .pipe( filter((res) => isCompleteResponse(res)), @@ -61,5 +64,21 @@ export const fetchDocuments = ( }) ); - return lastValueFrom(fetch$).then((records) => ({ records })); + return lastValueFrom(fetch$).then((records) => { + const adapter = inspectorAdapters.requests; + const interceptedWarnings = adapter + ? getSearchResponseInterceptedWarnings({ + services, + adapter, + options: { + disableShardFailureWarning: DISABLE_SHARD_FAILURE_WARNING, + }, + }) + : []; + + return { + records, + interceptedWarnings, + }; + }); }; diff --git a/src/plugins/discover/public/components/common/error_callout.tsx b/src/plugins/discover/public/components/common/error_callout.tsx index 0f1fbb722bf82..d0e914a81e851 100644 --- a/src/plugins/discover/public/components/common/error_callout.tsx +++ b/src/plugins/discover/public/components/common/error_callout.tsx @@ -10,9 +10,6 @@ import { EuiButton, EuiCallOut, EuiEmptyPrompt, - EuiFlexGroup, - EuiFlexItem, - EuiIcon, EuiLink, EuiModal, EuiModalBody, @@ -104,36 +101,31 @@ export const ErrorCallout = ({ /> ) : ( - - - - -

{formattedTitle}

-
- - } + title={

{formattedTitle}

} body={ - overrideDisplay?.body ?? ( - <> -

- {error.message} -

- {showErrorMessage} - - ) +
+ {overrideDisplay?.body ?? ( + <> +

+ {error.message} +

+ {showErrorMessage} + + )} +
} - css={css` - text-align: left; - `} data-test-subj={dataTestSubj} /> )} diff --git a/src/plugins/discover/public/components/doc_table/create_doc_table_embeddable.tsx b/src/plugins/discover/public/components/doc_table/create_doc_table_embeddable.tsx index e45faec8cbaa1..570c980e649e5 100644 --- a/src/plugins/discover/public/components/doc_table/create_doc_table_embeddable.tsx +++ b/src/plugins/discover/public/components/doc_table/create_doc_table_embeddable.tsx @@ -33,6 +33,7 @@ export function DiscoverDocTableEmbeddable(renderProps: DocTableEmbeddableProps) sharedItemTitle={renderProps.sharedItemTitle} isLoading={renderProps.isLoading} isPlainRecord={renderProps.isPlainRecord} + interceptedWarnings={renderProps.interceptedWarnings} dataTestSubj="embeddedSavedSearchDocTable" DocViewer={DocViewer} /> diff --git a/src/plugins/discover/public/components/doc_table/doc_table_embeddable.tsx b/src/plugins/discover/public/components/doc_table/doc_table_embeddable.tsx index 97ed5f3af9d14..6901df855984e 100644 --- a/src/plugins/discover/public/components/doc_table/doc_table_embeddable.tsx +++ b/src/plugins/discover/public/components/doc_table/doc_table_embeddable.tsx @@ -11,6 +11,7 @@ import './index.scss'; import { FormattedMessage } from '@kbn/i18n-react'; import { EuiText } from '@elastic/eui'; import { SAMPLE_SIZE_SETTING, usePager } from '@kbn/discover-utils'; +import type { SearchResponseInterceptedWarning } from '@kbn/search-response-warnings'; import { ToolBarPagination, MAX_ROWS_PER_PAGE_OPTION, @@ -22,6 +23,7 @@ import { SavedSearchEmbeddableBase } from '../../embeddable/saved_search_embedda export interface DocTableEmbeddableProps extends DocTableProps { totalHitCount: number; rowsPerPageState?: number; + interceptedWarnings?: SearchResponseInterceptedWarning[]; onUpdateRowsPerPage?: (rowsPerPage?: number) => void; } @@ -101,6 +103,7 @@ export const DocTableEmbeddable = (props: DocTableEmbeddableProps) => { return ( & filter?: (field: DataViewField, value: string[], operator: string) => void; hits?: DataTableRecord[]; totalHitCount?: number; + interceptedWarnings?: SearchResponseInterceptedWarning[]; onMoveColumn?: (column: string, index: number) => void; onUpdateRowHeight?: (rowHeight?: number) => void; onUpdateRowsPerPage?: (rowsPerPage?: number) => void; @@ -279,6 +284,7 @@ export class SavedSearchEmbeddable this.inspectorAdapters.requests!.reset(); searchProps.isLoading = true; + searchProps.interceptedWarnings = undefined; const wasAlreadyRendered = this.getOutput().rendered; @@ -357,9 +363,20 @@ export class SavedSearchEmbeddable }), }, executionContext, + disableShardFailureWarning: DISABLE_SHARD_FAILURE_WARNING, }) ); + if (this.inspectorAdapters.requests) { + searchProps.interceptedWarnings = getSearchResponseInterceptedWarnings({ + services: this.services, + adapter: this.inspectorAdapters.requests, + options: { + disableShardFailureWarning: DISABLE_SHARD_FAILURE_WARNING, + }, + }); + } + this.updateOutput({ ...this.getOutput(), loading: false, diff --git a/src/plugins/discover/public/embeddable/saved_search_embeddable_badge.tsx b/src/plugins/discover/public/embeddable/saved_search_embeddable_badge.tsx new file mode 100644 index 0000000000000..9944adb4be33c --- /dev/null +++ b/src/plugins/discover/public/embeddable/saved_search_embeddable_badge.tsx @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { + SearchResponseWarnings, + type SearchResponseInterceptedWarning, +} from '@kbn/search-response-warnings'; + +export interface SavedSearchEmbeddableBadgeProps { + interceptedWarnings: SearchResponseInterceptedWarning[] | undefined; +} + +export const SavedSearchEmbeddableBadge: React.FC = ({ + interceptedWarnings, +}) => { + return interceptedWarnings?.length ? ( + + ) : null; +}; diff --git a/src/plugins/discover/public/embeddable/saved_search_embeddable_base.tsx b/src/plugins/discover/public/embeddable/saved_search_embeddable_base.tsx index 17785570b9487..b41c70676c754 100644 --- a/src/plugins/discover/public/embeddable/saved_search_embeddable_base.tsx +++ b/src/plugins/discover/public/embeddable/saved_search_embeddable_base.tsx @@ -9,7 +9,9 @@ import React from 'react'; import { css } from '@emotion/react'; import { EuiFlexGroup, EuiFlexItem, EuiProgress } from '@elastic/eui'; +import type { SearchResponseInterceptedWarning } from '@kbn/search-response-warnings'; import { TotalDocuments } from '../application/main/components/total_documents/total_documents'; +import { SavedSearchEmbeddableBadge } from './saved_search_embeddable_badge'; const containerStyles = css` width: 100%; @@ -22,6 +24,7 @@ export interface SavedSearchEmbeddableBaseProps { prepend?: React.ReactElement; append?: React.ReactElement; dataTestSubj?: string; + interceptedWarnings?: SearchResponseInterceptedWarning[]; } export const SavedSearchEmbeddableBase: React.FC = ({ @@ -30,6 +33,7 @@ export const SavedSearchEmbeddableBase: React.FC prepend, append, dataTestSubj, + interceptedWarnings, children, }) => { return ( @@ -62,6 +66,12 @@ export const SavedSearchEmbeddableBase: React.FC {children} {Boolean(append) && {append}} + + {Boolean(interceptedWarnings?.length) && ( +
+ +
+ )} ); }; diff --git a/src/plugins/discover/public/embeddable/saved_search_grid.tsx b/src/plugins/discover/public/embeddable/saved_search_grid.tsx index 075a3ca930235..87258347b474e 100644 --- a/src/plugins/discover/public/embeddable/saved_search_grid.tsx +++ b/src/plugins/discover/public/embeddable/saved_search_grid.tsx @@ -7,6 +7,7 @@ */ import React, { useState, memo } from 'react'; import type { DataTableRecord } from '@kbn/discover-utils/types'; +import type { SearchResponseInterceptedWarning } from '@kbn/search-response-warnings'; import { DiscoverGrid, DiscoverGridProps } from '../components/discover_grid/discover_grid'; import './saved_search_grid.scss'; import { DiscoverGridFlyout } from '../components/discover_grid/discover_grid_flyout'; @@ -14,11 +15,13 @@ import { SavedSearchEmbeddableBase } from './saved_search_embeddable_base'; export interface DiscoverGridEmbeddableProps extends DiscoverGridProps { totalHitCount: number; + interceptedWarnings?: SearchResponseInterceptedWarning[]; } export const DataGridMemoized = memo(DiscoverGrid); export function DiscoverGridEmbeddable(props: DiscoverGridEmbeddableProps) { + const { interceptedWarnings, ...gridProps } = props; const [expandedDoc, setExpandedDoc] = useState(undefined); return ( @@ -26,9 +29,10 @@ export function DiscoverGridEmbeddable(props: DiscoverGridEmbeddableProps) { totalHitCount={props.totalHitCount} isLoading={props.isLoading} dataTestSubj="embeddedSavedSearchDocTable" + interceptedWarnings={props.interceptedWarnings} > { const embeddable = unifiedHistogramServicesMock.lens.EmbeddableComponent; const onLoad = component.find(embeddable).props().onLoad; const adapters = createDefaultInspectorAdapters(); + adapters.tables.tables.unifiedHistogram = { meta: { statistics: { totalCount: 100 } } } as any; const rawResponse = { _shards: { total: 1, @@ -215,14 +216,21 @@ describe('Histogram', () => { failed: 1, failures: [], }, + hits: { + total: 100, + max_score: null, + hits: [], + }, }; jest .spyOn(adapters.requests, 'getRequests') .mockReturnValue([{ response: { json: { rawResponse } } } as any]); - onLoad(false, adapters); + act(() => { + onLoad(false, adapters); + }); expect(props.onTotalHitsChange).toHaveBeenLastCalledWith( - UnifiedHistogramFetchStatus.error, - undefined + UnifiedHistogramFetchStatus.complete, + 100 ); expect(props.onChartLoad).toHaveBeenLastCalledWith({ adapters }); }); diff --git a/src/plugins/unified_histogram/public/chart/histogram.tsx b/src/plugins/unified_histogram/public/chart/histogram.tsx index 9983f2e0841dd..761e701e8f9a6 100644 --- a/src/plugins/unified_histogram/public/chart/histogram.tsx +++ b/src/plugins/unified_histogram/public/chart/histogram.tsx @@ -101,10 +101,10 @@ export function Histogram({ | undefined; const response = json?.rawResponse; - // Lens will swallow shard failures and return `isLoading: false` because it displays - // its own errors, but this causes us to emit onTotalHitsChange(UnifiedHistogramFetchStatus.complete, 0). - // This is incorrect, so we check for request failures and shard failures here, and emit an error instead. - if (requestFailed || response?._shards.failed) { + // The response can have `response?._shards.failed` but we should still be able to show hits number + // TODO: show shards warnings as a badge next to the total hits number + + if (requestFailed) { onTotalHitsChange?.(UnifiedHistogramFetchStatus.error, undefined); onChartLoad?.({ adapters: adapters ?? {} }); return; diff --git a/src/plugins/unified_histogram/public/chart/hooks/use_total_hits.ts b/src/plugins/unified_histogram/public/chart/hooks/use_total_hits.ts index 6903cdf6b4256..c260d3171697b 100644 --- a/src/plugins/unified_histogram/public/chart/hooks/use_total_hits.ts +++ b/src/plugins/unified_histogram/public/chart/hooks/use_total_hits.ts @@ -206,6 +206,7 @@ const fetchTotalHitsSearchSource = async ({ executionContext: { description: 'fetch total hits', }, + disableShardFailureWarning: true, // TODO: show warnings as a badge next to total hits number }) .pipe( filter((res) => isCompleteResponse(res)), diff --git a/tsconfig.base.json b/tsconfig.base.json index 12504320663e3..8efe5c62421de 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -1172,6 +1172,8 @@ "@kbn/screenshotting-plugin/*": ["x-pack/plugins/screenshotting/*"], "@kbn/search-examples-plugin": ["examples/search_examples"], "@kbn/search-examples-plugin/*": ["examples/search_examples/*"], + "@kbn/search-response-warnings": ["packages/kbn-search-response-warnings"], + "@kbn/search-response-warnings/*": ["packages/kbn-search-response-warnings/*"], "@kbn/searchprofiler-plugin": ["x-pack/plugins/searchprofiler"], "@kbn/searchprofiler-plugin/*": ["x-pack/plugins/searchprofiler/*"], "@kbn/security-api-integration-helpers": ["x-pack/test/security_api_integration/packages/helpers"], diff --git a/x-pack/test/functional/apps/discover/async_scripted_fields.js b/x-pack/test/functional/apps/discover/async_scripted_fields.js index 9a9d5e0d450f2..0d48f42c5ba1e 100644 --- a/x-pack/test/functional/apps/discover/async_scripted_fields.js +++ b/x-pack/test/functional/apps/discover/async_scripted_fields.js @@ -15,9 +15,17 @@ export default function ({ getService, getPageObjects }) { const esArchiver = getService('esArchiver'); const log = getService('log'); const testSubjects = getService('testSubjects'); - const PageObjects = getPageObjects(['common', 'settings', 'discover', 'timePicker']); + const PageObjects = getPageObjects([ + 'common', + 'settings', + 'discover', + 'timePicker', + 'header', + 'dashboard', + ]); const queryBar = getService('queryBar'); const security = getService('security'); + const dashboardAddPanel = getService('dashboardAddPanel'); describe('async search with scripted fields', function () { this.tags(['skipFirefox']); @@ -43,7 +51,7 @@ export default function ({ getService, getPageObjects }) { await security.testUser.restoreDefaults(); }); - it('query should show failed shards pop up', async function () { + it('query should show failed shards callout', async function () { if (false) { /* If you had to modify the scripted fields, you could un-comment all this, run it, use es_archiver to update 'kibana_scripted_fields_on_logstash' */ @@ -69,12 +77,39 @@ export default function ({ getService, getPageObjects }) { await retry.tryForTime(20000, async function () { // wait for shards failed message - const shardMessage = await testSubjects.getVisibleText('euiToastHeader'); + const shardMessage = await testSubjects.getVisibleText( + 'dscNoResultsInterceptedWarningsCallout_warningTitle' + ); log.debug(shardMessage); expect(shardMessage).to.be('1 of 3 shards failed'); }); }); + it('query should show failed shards badge on dashboard', async function () { + await security.testUser.setRoles([ + 'test_logstash_reader', + 'global_discover_all', + 'global_dashboard_all', + ]); + await PageObjects.common.navigateToApp('discover'); + await PageObjects.discover.selectIndexPattern('logsta*'); + + await PageObjects.discover.saveSearch('search with warning'); + await PageObjects.header.waitUntilLoadingHasFinished(); + + await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.gotoDashboardLandingPage(); + await PageObjects.dashboard.clickNewDashboard(); + + await dashboardAddPanel.addSavedSearch('search with warning'); + await PageObjects.header.waitUntilLoadingHasFinished(); + + await retry.tryForTime(20000, async function () { + // wait for shards failed message + await testSubjects.existOrFail('savedSearchEmbeddableWarningsCallout_trigger'); + }); + }); + it('query return results with valid scripted field', async function () { if (false) { /* the skipped steps below were used to create the scripted fields in the logstash-* index pattern diff --git a/yarn.lock b/yarn.lock index 39981aa19923e..a7e1003917f3d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5220,6 +5220,10 @@ version "0.0.0" uid "" +"@kbn/search-response-warnings@link:packages/kbn-search-response-warnings": + version "0.0.0" + uid "" + "@kbn/searchprofiler-plugin@link:x-pack/plugins/searchprofiler": version "0.0.0" uid "" From 39e11ffbac4bbeba5548300f292ff17fdf4a2fe3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yulia=20=C4=8Cech?= <6585477+yuliacech@users.noreply.github.com> Date: Thu, 10 Aug 2023 14:42:49 +0200 Subject: [PATCH 48/53] [Index Management] Disable index actions using contextRef (#163475) ## Summary Follow up to https://github.com/elastic/kibana/pull/161528 This PR leverages the [schema.contextRef('serverless') check](https://www.elastic.co/guide/en/kibana/master/configuration-service.html#validating-your-configuration-based-on-context-references) to prevent the config `enableIndexActions` from leaking to self-managed. ### Screenshots Stateful (no changes), index actions enabled Screenshot 2023-08-09 at 12 15 31 Serverless (no changes), index actions disabled Screenshot 2023-08-09 at 12 09 45 --- .../test_suites/core_plugins/rendering.ts | 2 +- .../public/application/mount_management_section.ts | 2 +- x-pack/plugins/index_management/public/types.ts | 2 +- x-pack/plugins/index_management/server/config.ts | 9 ++++++++- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/test/plugin_functional/test_suites/core_plugins/rendering.ts b/test/plugin_functional/test_suites/core_plugins/rendering.ts index f03af110fd866..66a2e385d3e6c 100644 --- a/test/plugin_functional/test_suites/core_plugins/rendering.ts +++ b/test/plugin_functional/test_suites/core_plugins/rendering.ts @@ -239,7 +239,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) { 'xpack.graph.savePolicy (alternatives)', 'xpack.ilm.ui.enabled (boolean)', 'xpack.index_management.ui.enabled (boolean)', - 'xpack.index_management.enableIndexActions (boolean)', + 'xpack.index_management.enableIndexActions (any)', 'xpack.infra.sources.default.fields.message (array)', /** * xpack.infra.logs is conditional and will resolve to an object of properties diff --git a/x-pack/plugins/index_management/public/application/mount_management_section.ts b/x-pack/plugins/index_management/public/application/mount_management_section.ts index d00aa6ff1f0e6..6bb3b834ce85f 100644 --- a/x-pack/plugins/index_management/public/application/mount_management_section.ts +++ b/x-pack/plugins/index_management/public/application/mount_management_section.ts @@ -53,7 +53,7 @@ export async function mountManagementSection( extensionsService: ExtensionsService, isFleetEnabled: boolean, kibanaVersion: SemVer, - enableIndexActions: boolean + enableIndexActions: boolean = true ) { const { element, setBreadcrumbs, history, theme$ } = params; const [core, startDependencies] = await coreSetup.getStartServices(); diff --git a/x-pack/plugins/index_management/public/types.ts b/x-pack/plugins/index_management/public/types.ts index 59954c6659494..20d2405a0fa4b 100644 --- a/x-pack/plugins/index_management/public/types.ts +++ b/x-pack/plugins/index_management/public/types.ts @@ -28,5 +28,5 @@ export interface ClientConfigType { ui: { enabled: boolean; }; - enableIndexActions: boolean; + enableIndexActions?: boolean; } diff --git a/x-pack/plugins/index_management/server/config.ts b/x-pack/plugins/index_management/server/config.ts index 4fd24bf3fcdf7..c5d459486a8ef 100644 --- a/x-pack/plugins/index_management/server/config.ts +++ b/x-pack/plugins/index_management/server/config.ts @@ -22,7 +22,14 @@ const schemaLatest = schema.object( ui: schema.object({ enabled: schema.boolean({ defaultValue: true }), }), - enableIndexActions: schema.boolean({ defaultValue: true }), + enableIndexActions: schema.conditional( + schema.contextRef('serverless'), + true, + // Index actions are disabled in serverless; refer to the serverless.yml file as the source of truth + // We take this approach in order to have a central place (serverless.yml) for serverless config across Kibana + schema.boolean({ defaultValue: true }), + schema.never() + ), }, { defaultValue: undefined } ); From f99be4ede4c61af920b3b217cf8ea1c57c03c3b5 Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Thu, 10 Aug 2023 08:43:41 -0400 Subject: [PATCH 49/53] [Fleet] add managed to imported saved object (#163526) --- .../server/services/epm/kibana/assets/install.ts | 2 ++ .../apis/epm/install_remove_assets.ts | 14 ++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/x-pack/plugins/fleet/server/services/epm/kibana/assets/install.ts b/x-pack/plugins/fleet/server/services/epm/kibana/assets/install.ts index e9a4e255e9ea1..ec0cbab539bcd 100644 --- a/x-pack/plugins/fleet/server/services/epm/kibana/assets/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/kibana/assets/install.ts @@ -320,6 +320,7 @@ export async function installKibanaSavedObjects({ readStream: createListStream(toBeSavedObjects), createNewCopies: false, refresh: false, + managed: true, }) ); @@ -371,6 +372,7 @@ export async function installKibanaSavedObjects({ await savedObjectsImporter.resolveImportErrors({ readStream: createListStream(toBeSavedObjects), createNewCopies: false, + managed: true, retries, }); diff --git a/x-pack/test/fleet_api_integration/apis/epm/install_remove_assets.ts b/x-pack/test/fleet_api_integration/apis/epm/install_remove_assets.ts index 3744c2aa9d2f0..aaf31e54798db 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/install_remove_assets.ts +++ b/x-pack/test/fleet_api_integration/apis/epm/install_remove_assets.ts @@ -437,61 +437,75 @@ const expectAssetsInstalled = ({ id: 'sample_dashboard', }); expect(resDashboard.id).equal('sample_dashboard'); + expect(resDashboard.managed).be(true); expect(resDashboard.references.map((ref: any) => ref.id).includes('sample_tag')).equal(true); const resDashboard2 = await kibanaServer.savedObjects.get({ type: 'dashboard', id: 'sample_dashboard2', }); expect(resDashboard2.id).equal('sample_dashboard2'); + expect(resDashboard2.managed).be(true); const resVis = await kibanaServer.savedObjects.get({ type: 'visualization', id: 'sample_visualization', }); + expect(resVis.id).equal('sample_visualization'); + expect(resVis.managed).be(true); const resSearch = await kibanaServer.savedObjects.get({ type: 'search', id: 'sample_search', }); expect(resSearch.id).equal('sample_search'); + expect(resSearch.managed).be(true); const resLens = await kibanaServer.savedObjects.get({ type: 'lens', id: 'sample_lens', }); + expect(resLens.id).equal('sample_lens'); + expect(resLens.managed).be(true); const resMlModule = await kibanaServer.savedObjects.get({ type: 'ml-module', id: 'sample_ml_module', }); expect(resMlModule.id).equal('sample_ml_module'); + expect(resMlModule.managed).be(true); const resSecurityRule = await kibanaServer.savedObjects.get({ type: 'security-rule', id: 'sample_security_rule', }); expect(resSecurityRule.id).equal('sample_security_rule'); + expect(resSecurityRule.managed).be(true); const resOsqueryPackAsset = await kibanaServer.savedObjects.get({ type: 'osquery-pack-asset', id: 'sample_osquery_pack_asset', }); expect(resOsqueryPackAsset.id).equal('sample_osquery_pack_asset'); + expect(resOsqueryPackAsset.managed).be(true); const resOsquerySavedObject = await kibanaServer.savedObjects.get({ type: 'osquery-saved-query', id: 'sample_osquery_saved_query', }); expect(resOsquerySavedObject.id).equal('sample_osquery_saved_query'); + expect(resOsquerySavedObject.managed).be(true); const resCloudSecurityPostureRuleTemplate = await kibanaServer.savedObjects.get({ type: 'csp-rule-template', id: 'sample_csp_rule_template', }); expect(resCloudSecurityPostureRuleTemplate.id).equal('sample_csp_rule_template'); + expect(resCloudSecurityPostureRuleTemplate.managed).be(true); const resTag = await kibanaServer.savedObjects.get({ type: 'tag', id: 'sample_tag', }); + expect(resTag.managed).be(true); expect(resTag.id).equal('sample_tag'); const resIndexPattern = await kibanaServer.savedObjects.get({ type: 'index-pattern', id: 'test-*', }); + expect(resIndexPattern.managed).be(true); expect(resIndexPattern.id).equal('test-*'); let resInvalidTypeIndexPattern; From 94432d81e62eccfcbe872e559f882e5ef75f4ed6 Mon Sep 17 00:00:00 2001 From: Mark Hopkin Date: Thu, 10 Aug 2023 13:52:26 +0100 Subject: [PATCH 50/53] [Fleet] Re-enable and fix Fleet policy secret integration tests (#163428) ## Summary Closes #162732 Closes #157503 Wanted to sneak this in before we move over to the internal index, I have tidied the tests a bit to make that transition easier. Since we restricted the fleet service account permissions, we can no longer use a test index for the secret tests. The test index was added while .fleet-secrets didn't exist so I have switched to using the real index. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/plugins/fleet/server/config.ts | 1 - .../apis/policy_secrets.ts | 122 ++++++------------ .../test/fleet_api_integration/config.base.ts | 1 - 3 files changed, 43 insertions(+), 81 deletions(-) diff --git a/x-pack/plugins/fleet/server/config.ts b/x-pack/plugins/fleet/server/config.ts index 14e5a86aa73ad..9726837375eed 100644 --- a/x-pack/plugins/fleet/server/config.ts +++ b/x-pack/plugins/fleet/server/config.ts @@ -139,7 +139,6 @@ export const config: PluginConfigDescriptor = { disableRegistryVersionCheck: schema.boolean({ defaultValue: false }), allowAgentUpgradeSourceUri: schema.boolean({ defaultValue: false }), bundledPackageLocation: schema.string({ defaultValue: DEFAULT_BUNDLED_PACKAGE_LOCATION }), - testSecretsIndex: schema.maybe(schema.string()), }), packageVerification: schema.object({ gpgKeyPath: schema.string({ defaultValue: DEFAULT_GPG_KEY_PATH }), diff --git a/x-pack/test/fleet_api_integration/apis/policy_secrets.ts b/x-pack/test/fleet_api_integration/apis/policy_secrets.ts index 34f20e88b0a81..52b614f389ba9 100644 --- a/x-pack/test/fleet_api_integration/apis/policy_secrets.ts +++ b/x-pack/test/fleet_api_integration/apis/policy_secrets.ts @@ -41,37 +41,43 @@ function createdPolicyToUpdatePolicy(policy: any) { return updatedPolicy; } +const SECRETS_INDEX_NAME = '.fleet-secrets'; export default function (providerContext: FtrProviderContext) { - // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/162732 - describe.skip('fleet policy secrets', () => { + describe('fleet policy secrets', () => { const { getService } = providerContext; const es: Client = getService('es'); const supertest = getService('supertest'); const kibanaServer = getService('kibanaServer'); - const getPackagePolicyById = async (id: string) => { - const { body } = await supertest.get(`/api/fleet/package_policies/${id}`); - return body.item; + const getSecrets = async (ids?: string[]) => { + const query = ids ? { terms: { _id: ids } } : { match_all: {} }; + return es.search({ + index: SECRETS_INDEX_NAME, + body: { + query, + }, + }); }; - const maybeCreateSecretsIndex = async () => { - // create mock .secrets index for testing - if (await es.indices.exists({ index: '.fleet-test-secrets' })) { - await es.indices.delete({ index: '.fleet-test-secrets' }); - } - await es.indices.create({ - index: '.fleet-test-secrets', - body: { - mappings: { - properties: { - value: { - type: 'keyword', - }, + const deleteAllSecrets = async () => { + try { + await es.deleteByQuery({ + index: SECRETS_INDEX_NAME, + body: { + query: { + match_all: {}, }, }, - }, - }); + }); + } catch (err) { + // index doesnt exis + } + }; + + const getPackagePolicyById = async (id: string) => { + const { body } = await supertest.get(`/api/fleet/package_policies/${id}`); + return body.item; }; const getFullAgentPolicyById = async (id: string) => { @@ -137,10 +143,8 @@ export default function (providerContext: FtrProviderContext) { let agentPolicyId: string; before(async () => { await kibanaServer.savedObjects.cleanStandardList(); - await getService('esArchiver').load( - 'x-pack/test/functional/es_archives/fleet/empty_fleet_server' - ); - await maybeCreateSecretsIndex(); + + await deleteAllSecrets(); }); setupFleetAndAgents(providerContext); @@ -261,16 +265,7 @@ export default function (providerContext: FtrProviderContext) { }); it('should have correctly created the secrets', async () => { - const searchRes = await es.search({ - index: '.fleet-test-secrets', - body: { - query: { - ids: { - values: [packageVarId, inputVarId, streamVarId], - }, - }, - }, - }); + const searchRes = await getSecrets([packageVarId, inputVarId, streamVarId]); expect(searchRes.hits.hits.length).to.eql(3); @@ -337,14 +332,7 @@ export default function (providerContext: FtrProviderContext) { }); it('should have correctly deleted unused secrets after update', async () => { - const searchRes = await es.search({ - index: '.fleet-test-secrets', - body: { - query: { - match_all: {}, - }, - }, - }); + const searchRes = await getSecrets(); expect(searchRes.hits.hits.length).to.eql(3); // should have created 1 and deleted 1 doc @@ -374,14 +362,7 @@ export default function (providerContext: FtrProviderContext) { expectCompiledPolicyVars(policyDoc, updatedPackageVarId); - const searchRes = await es.search({ - index: '.fleet-test-secrets', - body: { - query: { - match_all: {}, - }, - }, - }); + const searchRes = await getSecrets(); expect(searchRes.hits.hits.length).to.eql(3); @@ -413,53 +394,36 @@ export default function (providerContext: FtrProviderContext) { updatedPackagePolicy.vars.package_var_secret.value.id, updatedPackageVarId, ]; - - const searchRes = await es.search({ - index: '.fleet-test-secrets', - body: { - query: { - terms: { - _id: packageVarSecretIds, - }, - }, - }, - }); + const searchRes = await getSecrets(packageVarSecretIds); expect(searchRes.hits.hits.length).to.eql(2); }); it('should not delete used secrets on package policy delete', async () => { - return supertest + await supertest .delete(`/api/fleet/package_policies/${duplicatedPackagePolicyId}`) .set('kbn-xsrf', 'xxxx') .expect(200); - const searchRes = await es.search({ - index: '.fleet-test-secrets', - body: { - query: { - match_all: {}, - }, - }, - }); + // sleep to allow for secrets to be deleted + await new Promise((resolve) => setTimeout(resolve, 1000)); + + const searchRes = await getSecrets(); + // should have deleted new_package_secret_val_2 expect(searchRes.hits.hits.length).to.eql(3); }); it('should delete all secrets on package policy delete', async () => { - return supertest + await supertest .delete(`/api/fleet/package_policies/${createdPackagePolicyId}`) .set('kbn-xsrf', 'xxxx') .expect(200); - const searchRes = await es.search({ - index: '.fleet-test-secrets', - body: { - query: { - match_all: {}, - }, - }, - }); + // sleep to allow for secrets to be deleted + await new Promise((resolve) => setTimeout(resolve, 1000)); + + const searchRes = await getSecrets(); expect(searchRes.hits.hits.length).to.eql(0); }); diff --git a/x-pack/test/fleet_api_integration/config.base.ts b/x-pack/test/fleet_api_integration/config.base.ts index e5746278a26f9..3e4b35988efba 100644 --- a/x-pack/test/fleet_api_integration/config.base.ts +++ b/x-pack/test/fleet_api_integration/config.base.ts @@ -74,7 +74,6 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { 'secretsStorage', 'agentTamperProtectionEnabled', ])}`, - `--xpack.fleet.developer.testSecretsIndex=.fleet-test-secrets`, `--logging.loggers=${JSON.stringify([ ...getKibanaCliLoggers(xPackAPITestsConfig.get('kbnTestServer.serverArgs')), From 7353dc66902fcff9e11ac88bc87be6244234a149 Mon Sep 17 00:00:00 2001 From: Konrad Szwarc Date: Thu, 10 Aug 2023 14:56:48 +0200 Subject: [PATCH 51/53] [Fleet] Add a banner to the top of the Kafka Output UI to say that Elastic Defend integration is not supported (#163579) Closes https://github.com/elastic/security-team/issues/7309 ![test](https://github.com/elastic/kibana/assets/29123534/8ce752d5-9340-4cdf-a4ec-7a9ae195a3d2) --- x-pack/plugins/fleet/cypress/screens/fleet.ts | 2 + .../fleet/cypress/screens/fleet_outputs.ts | 1 + .../components/edit_output_flyout/index.tsx | 57 ++++++++++++------- 3 files changed, 41 insertions(+), 19 deletions(-) diff --git a/x-pack/plugins/fleet/cypress/screens/fleet.ts b/x-pack/plugins/fleet/cypress/screens/fleet.ts index e6b6ac1f47008..3b8ffcc63b6f6 100644 --- a/x-pack/plugins/fleet/cypress/screens/fleet.ts +++ b/x-pack/plugins/fleet/cypress/screens/fleet.ts @@ -121,6 +121,8 @@ export const SETTINGS_OUTPUTS = { NAME_INPUT: 'settingsOutputsFlyout.nameInput', TYPE_INPUT: 'settingsOutputsFlyout.typeInput', ADD_HOST_ROW_BTN: 'fleetServerHosts.multiRowInput.addRowButton', + WARNING_KAFKA_CALLOUT: 'settingsOutputsFlyout.kafkaOutputTypeCallout', + WARNING_ELASTICSEARCH_CALLOUT: 'settingsOutputsFlyout.elasticsearchOutputTypeCallout', }; export const getSpecificSelectorId = (selector: string, id: number) => { diff --git a/x-pack/plugins/fleet/cypress/screens/fleet_outputs.ts b/x-pack/plugins/fleet/cypress/screens/fleet_outputs.ts index de6ef1097b74a..0e018cd301d1b 100644 --- a/x-pack/plugins/fleet/cypress/screens/fleet_outputs.ts +++ b/x-pack/plugins/fleet/cypress/screens/fleet_outputs.ts @@ -21,6 +21,7 @@ export const selectKafkaOutput = () => { visit('/app/fleet/settings'); cy.getBySel(SETTINGS_OUTPUTS.ADD_BTN).click(); cy.getBySel(SETTINGS_OUTPUTS.TYPE_INPUT).select('kafka'); + cy.getBySel(SETTINGS_OUTPUTS.WARNING_KAFKA_CALLOUT); cy.getBySel(SETTINGS_OUTPUTS_KAFKA.AUTHENTICATION_USERNAME_PASSWORD_OPTION).click(); }; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/index.tsx index 3f2055e999914..e764e93527b34 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/index.tsx @@ -73,7 +73,6 @@ export const EditOutputFlyout: React.FunctionComponent = [proxies] ); - const isESOutput = inputs.typeInput.value === outputType.Elasticsearch; const { kafkaOutput: isKafkaOutputEnabled } = ExperimentalFeaturesService.get(); const OUTPUT_TYPE_OPTIONS = [ @@ -249,6 +248,43 @@ export const EditOutputFlyout: React.FunctionComponent = } }; + const renderTypeSpecificWarning = () => { + const isESOutput = inputs.typeInput.value === outputType.Elasticsearch; + const isKafkaOutput = inputs.typeInput.value === outputType.Kafka; + if (!isKafkaOutput && !isESOutput) { + return null; + } + + const generateWarningMessage = () => { + switch (inputs.typeInput.value) { + case outputType.Kafka: + return i18n.translate('xpack.fleet.settings.editOutputFlyout.kafkaOutputTypeCallout', { + defaultMessage: + 'Kafka output is currently not supported on Agents using the Elastic Defend integration.', + }); + default: + case outputType.Elasticsearch: + return i18n.translate('xpack.fleet.settings.editOutputFlyout.esOutputTypeCallout', { + defaultMessage: + 'This output type currently does not support connectivity to a remote Elasticsearch cluster.', + }); + } + }; + return ( + <> + + + + ); + }; + return ( @@ -350,24 +386,7 @@ export const EditOutputFlyout: React.FunctionComponent = } )} /> - {isESOutput && ( - <> - - - - )} + {renderTypeSpecificWarning()} From 4a56d2009629db15d886087fadf681bc094e0c0a Mon Sep 17 00:00:00 2001 From: Carlos Crespo Date: Thu, 10 Aug 2023 15:05:50 +0200 Subject: [PATCH 52/53] [Infra UI] Implement telemetry for the asset details flyout (#163078) ## Summary This PR adds proper `data-test-subj` attributes to the Asset Details component and adds a new custom telemetry event image image Besides that, I've renamed 2 props,`node` -> `asset` and `nodeType` -> `assetType`, to make naming consistent with what we're naming these attributes across asset-related stuff. ### How to test - Setup a local Kibana instance - Navigate to `Infrastructure` > `Hosts` - Open the flyout - Upon opening, an event called `Asset Details Flyout Viewed` should be present in `kibana-browser` request - Click through the flyout and check the `kibana-browser` requests - All automatic events in the flyout should contain the `data-component-name` and `data-asset-type` attributes --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../context/fixtures/asset_details_state.ts | 4 +- .../asset_details/asset_details.stories.tsx | 10 +- .../asset_details/asset_details.tsx | 45 +++++++-- .../asset_details_embeddable.tsx | 4 +- .../components/alerts_tooltip_content.tsx | 2 +- .../components/expandable_content.tsx | 5 +- .../components/asset_details/constants.ts | 1 + .../asset_details/header/header.tsx | 16 ++-- .../hooks/use_asset_details_state.ts | 8 +- .../asset_details/hooks/use_tab_switcher.tsx | 1 + .../asset_details/links/link_to_alerts.tsx | 2 +- .../links/link_to_alerts_page.tsx | 10 +- .../links/link_to_apm_services.tsx | 8 +- .../links/link_to_node_details.tsx | 16 ++-- .../asset_details/links/tab_to_apm_traces.tsx | 8 +- .../asset_details/links/tab_to_uptime.tsx | 15 ++- .../tabs/anomalies/anomalies.tsx | 4 +- .../asset_details/tabs/logs/logs.tsx | 19 ++-- .../metadata/add_metadata_filter_button.tsx | 4 +- .../tabs/metadata/add_pin_to_row.tsx | 6 +- .../tabs/metadata/metadata.test.tsx | 19 ++-- .../asset_details/tabs/metadata/metadata.tsx | 12 +-- .../asset_details/tabs/metadata/table.tsx | 8 +- .../asset_details/tabs/osquery/osquery.tsx | 8 +- .../asset_details/tabs/overview/alerts.tsx | 24 ++--- .../tabs/overview/kpis/kpi_grid.tsx | 2 +- .../metadata_summary/metadata_header.tsx | 4 +- .../metadata_summary_list.tsx | 2 +- .../tabs/overview/metrics/metrics_grid.tsx | 6 +- .../asset_details/tabs/overview/overview.tsx | 16 ++-- .../tabs/processes/processes.tsx | 10 +- .../tabs/processes/processes_table.tsx | 2 +- .../tabs/processes/summary_table.tsx | 5 +- .../public/components/asset_details/types.ts | 11 +-- .../host_details_flyout/flyout_wrapper.tsx | 9 +- .../components/host_details_flyout/tabs.ts | 6 -- .../telemetry/telemetry_client.mock.ts | 1 + .../services/telemetry/telemetry_client.ts | 5 + .../services/telemetry/telemetry_events.ts | 28 ++++++ .../telemetry/telemetry_service.test.ts | 24 +++++ .../infra/public/services/telemetry/types.ts | 15 ++- .../page_objects/infra_hosts_view.ts | 95 +++++++++---------- 42 files changed, 304 insertions(+), 196 deletions(-) diff --git a/x-pack/plugins/infra/public/components/asset_details/__stories__/context/fixtures/asset_details_state.ts b/x-pack/plugins/infra/public/components/asset_details/__stories__/context/fixtures/asset_details_state.ts index edcd1d0627d3d..4e88dc368ca0a 100644 --- a/x-pack/plugins/infra/public/components/asset_details/__stories__/context/fixtures/asset_details_state.ts +++ b/x-pack/plugins/infra/public/components/asset_details/__stories__/context/fixtures/asset_details_state.ts @@ -9,7 +9,7 @@ import type { DataViewField, DataView } from '@kbn/data-views-plugin/common'; import { UseAssetDetailsStateProps } from '../../../hooks/use_asset_details_state'; export const assetDetailsState: UseAssetDetailsStateProps['state'] = { - node: { + asset: { name: 'host1', id: 'host1-macOS', ip: '192.168.0.1', @@ -29,7 +29,7 @@ export const assetDetailsState: UseAssetDetailsStateProps['state'] = { showActionsColumn: true, }, }, - nodeType: 'host', + assetType: 'host', dateRange: { from: '2023-04-09T11:07:49Z', to: '2023-04-09T11:23:49Z', diff --git a/x-pack/plugins/infra/public/components/asset_details/asset_details.stories.tsx b/x-pack/plugins/infra/public/components/asset_details/asset_details.stories.tsx index a90fe5764f531..824a4e5f65ef0 100644 --- a/x-pack/plugins/infra/public/components/asset_details/asset_details.stories.tsx +++ b/x-pack/plugins/infra/public/components/asset_details/asset_details.stories.tsx @@ -22,42 +22,36 @@ const tabs: Tab[] = [ name: i18n.translate('xpack.infra.nodeDetails.tabs.overview.title', { defaultMessage: 'Overview', }), - 'data-test-subj': 'hostsView-flyout-tabs-overview', }, { id: FlyoutTabIds.LOGS, name: i18n.translate('xpack.infra.nodeDetails.tabs.logs', { defaultMessage: 'Logs', }), - 'data-test-subj': 'hostsView-flyout-tabs-logs', }, { id: FlyoutTabIds.METADATA, name: i18n.translate('xpack.infra.metrics.nodeDetails.tabs.metadata', { defaultMessage: 'Metadata', }), - 'data-test-subj': 'hostsView-flyout-tabs-metadata', }, { id: FlyoutTabIds.PROCESSES, name: i18n.translate('xpack.infra.metrics.nodeDetails.tabs.processes', { defaultMessage: 'Processes', }), - 'data-test-subj': 'hostsView-flyout-tabs-processes', }, { id: FlyoutTabIds.ANOMALIES, name: i18n.translate('xpack.infra.nodeDetails.tabs.anomalies', { defaultMessage: 'Anomalies', }), - 'data-test-subj': 'hostsView-flyout-tabs-anomalies', }, { id: FlyoutTabIds.LINK_TO_APM, name: i18n.translate('xpack.infra.infra.nodeDetails.apmTabLabel', { defaultMessage: 'APM', }), - 'data-test-subj': 'hostsView-flyout-apm-link', }, ]; @@ -96,7 +90,7 @@ const FlyoutTemplate: Story = (args) => { Open flyout