diff --git a/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts index 86198df9b1d27..124d8703d044d 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts @@ -134,7 +134,7 @@ describe('checking migration metadata changes on all registered SO types', () => "siem-ui-timeline": "e9d6b3a9fd7af6dc502293c21cbdb309409f3996", "siem-ui-timeline-note": "13c9d4c142f96624a93a623c6d7cba7e1ae9b5a6", "siem-ui-timeline-pinned-event": "96a43d59b9e2fc11f12255a0cb47ef0a3d83af4c", - "slo": "ee0e16abebba5779c37277bc3fe8da1fe1207b7a", + "slo": "aefffabdb35d15a6c388634af2cee1fa59ede83c", "space": "7fc578a1f9f7708cb07479f03953d664ad9f1dae", "spaces-usage-stats": "084bd0f080f94fb5735d7f3cf12f13ec92f36bad", "synthetics-monitor": "7136a2669a65323c56da849f26c369cdeeb3b381", diff --git a/src/core/server/integration_tests/saved_objects/migrations/group3/default_search_fields.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group3/default_search_fields.test.ts new file mode 100644 index 0000000000000..4101c22c23d50 --- /dev/null +++ b/src/core/server/integration_tests/saved_objects/migrations/group3/default_search_fields.test.ts @@ -0,0 +1,75 @@ +/* + * 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 { createRoot } from '@kbn/core-test-helpers-kbn-server'; + +describe('SO default search fields', () => { + let root: ReturnType; + + afterEach(() => { + try { + root?.shutdown(); + } catch (e) { + /* trap */ + } + }); + + interface InvalidMappingTuple { + type: string; + field: string; + } + + // identify / avoid scenarios of https://github.com/elastic/kibana/issues/130616 + it('make sure management types have the correct mappings for default search fields', async () => { + root = createRoot({}, { oss: false }); + await root.preboot(); + const setup = await root.setup(); + + const allTypes = setup.savedObjects.getTypeRegistry().getAllTypes(); + + const defaultSearchFields = [ + ...allTypes.reduce((fieldSet, type) => { + if (type.management?.defaultSearchField) { + fieldSet.add(type.management.defaultSearchField); + } + return fieldSet; + }, new Set()), + ]; + + const invalidMappings: InvalidMappingTuple[] = []; + + const managementTypes = setup.savedObjects + .getTypeRegistry() + .getImportableAndExportableTypes() + .filter((type) => type.management!.visibleInManagement ?? true); + + managementTypes.forEach((type) => { + const mappingProps = type.mappings.properties; + defaultSearchFields.forEach((searchField) => { + if (mappingProps[searchField]) { + const fieldDef = mappingProps[searchField]; + if (fieldDef.type !== 'text') { + invalidMappings.push({ + type: type.name, + field: searchField, + }); + } + } + }); + }); + + if (invalidMappings.length > 0) { + // `fail()` no longer exists... + expect( + `fields registered as defaultSearchField by any type must be registered as text. Invalid mappings found: ${JSON.stringify( + invalidMappings + )}` + ).toEqual(''); + } + }); +}); diff --git a/src/core/server/integration_tests/saved_objects/migrations/group3/type_registrations.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group3/type_registrations.test.ts index 914c825597774..dbcdbf2f9928c 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group3/type_registrations.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group3/type_registrations.test.ts @@ -132,8 +132,18 @@ const previouslyRegisteredTypes = [ ].sort(); describe('SO type registrations', () => { + let root: ReturnType; + + afterEach(() => { + try { + root?.shutdown(); + } catch (e) { + /* trap */ + } + }); + it('does not remove types from registrations without updating excludeOnUpgradeQuery', async () => { - const root = createRoot({}, { oss: false }); + root = createRoot({}, { oss: false }); await root.preboot(); const setup = await root.setup(); const currentlyRegisteredTypes = setup.savedObjects diff --git a/x-pack/plugins/asset_manager/server/routes/assets.ts b/x-pack/plugins/asset_manager/server/routes/assets.ts index 9050105380809..d1b4d839913e4 100644 --- a/x-pack/plugins/asset_manager/server/routes/assets.ts +++ b/x-pack/plugins/asset_manager/server/routes/assets.ts @@ -59,12 +59,6 @@ export function assetsRoutes({ router }: SetupR ); // GET /assets/diff - const assetType = schema.oneOf([ - schema.literal('k8s.pod'), - schema.literal('k8s.cluster'), - schema.literal('k8s.node'), - ]); - const getAssetsDiffQueryOptions = schema.object({ aFrom: schema.string(), aTo: schema.string(), diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/host_details_flyout/flyout.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/host_details_flyout/flyout.tsx index 5dadd7a5fb67b..98018550415c1 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/host_details_flyout/flyout.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/host_details_flyout/flyout.tsx @@ -6,13 +6,26 @@ */ import React from 'react'; -import { EuiFlyout, EuiFlyoutHeader, EuiTitle, EuiFlyoutBody } from '@elastic/eui'; -import { EuiSpacer, EuiTabs, EuiTab } from '@elastic/eui'; +import { + EuiFlyout, + EuiFlyoutHeader, + EuiTitle, + EuiFlyoutBody, + EuiFlexGroup, + EuiFlexItem, + EuiTab, + EuiSpacer, + EuiTabs, + useEuiTheme, +} from '@elastic/eui'; +import { css } from '@emotion/react'; +import { useUnifiedSearchContext } from '../../hooks/use_unified_search'; +import { LinkToUptime } from './links/link_to_uptime'; +import { LinkToApmServices } from './links/link_to_apm_services'; import { useLazyRef } from '../../../../../hooks/use_lazy_ref'; import { metadataTab } from './metadata'; import type { InventoryItemType } from '../../../../../../common/inventory_models/types'; import type { HostNodeRow } from '../../hooks/use_hosts_table'; -import { useUnifiedSearchContext } from '../../hooks/use_unified_search'; import { processesTab } from './processes'; import { Metadata } from './metadata/metadata'; import { Processes } from './processes/processes'; @@ -28,6 +41,7 @@ const NODE_TYPE = 'host' as InventoryItemType; export const Flyout = ({ node, closeFlyout }: Props) => { const { getDateRangeAsTimestamp } = useUnifiedSearchContext(); + const { euiTheme } = useEuiTheme(); const currentTimeRange = { ...getDateRangeAsTimestamp(), @@ -57,9 +71,24 @@ export const Flyout = ({ node, closeFlyout }: Props) => { return ( - -

{node.name}

-
+ + + +

{node.name}

+
+
+ + + + + + +
{tabEntries} diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/host_details_flyout/links/link_to_apm_services.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/host_details_flyout/links/link_to_apm_services.tsx new file mode 100644 index 0000000000000..18fc83004dc13 --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/host_details_flyout/links/link_to_apm_services.tsx @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import { stringify } from 'querystring'; +import { encode } from '@kbn/rison'; +import { css } from '@emotion/react'; +import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app'; +import { EuiIcon, EuiLink, useEuiTheme } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { useKibanaContextForPlugin } from '../../../../../../hooks/use_kibana'; + +interface LinkToApmServicesProps { + hostName: string; + apmField: string; +} + +export const LinkToApmServices = ({ hostName, apmField }: LinkToApmServicesProps) => { + const { services } = useKibanaContextForPlugin(); + const { http } = services; + const { euiTheme } = useEuiTheme(); + + const queryString = new URLSearchParams( + encode( + stringify({ + kuery: `${apmField}:"${hostName}"`, + }) + ) + ); + + const linkToApmServices = http.basePath.prepend(`/app/apm/services?${queryString}`); + + return ( + + + + + + + ); +}; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/host_details_flyout/links/link_to_uptime.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/host_details_flyout/links/link_to_uptime.tsx new file mode 100644 index 0000000000000..02043eb21417d --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/host_details_flyout/links/link_to_uptime.tsx @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiLink, EuiIcon, useEuiTheme } from '@elastic/eui'; +import { css } from '@emotion/react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { uptimeOverviewLocatorID } from '@kbn/observability-plugin/public'; +import { useKibanaContextForPlugin } from '../../../../../../hooks/use_kibana'; +import type { InventoryItemType } from '../../../../../../../common/inventory_models/types'; +import type { HostNodeRow } from '../../../hooks/use_hosts_table'; + +interface LinkTUptimeProps { + nodeType: InventoryItemType; + node: HostNodeRow; +} + +export const LinkToUptime = ({ nodeType, node }: LinkTUptimeProps) => { + const { share } = useKibanaContextForPlugin().services; + const { euiTheme } = useEuiTheme(); + + return ( + + share.url.locators + .get(uptimeOverviewLocatorID)! + .navigate({ [nodeType]: node.name, ip: node.ip }) + } + > + + + + ); +}; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.test.ts b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.test.ts index 26622f57fd581..0085b9b923140 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.test.ts +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.test.ts @@ -73,6 +73,7 @@ describe('useHostTable hook', () => { { name: 'host-0', os: '-', + ip: '', id: 'host-0-0', title: { cloudProvider: 'aws', @@ -103,6 +104,7 @@ describe('useHostTable hook', () => { { name: 'host-1', os: 'macOS', + ip: '243.86.94.22', id: 'host-1-1', title: { cloudProvider: null, diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.tsx index 5127c4f9251e8..a898c46316648 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.tsx @@ -31,6 +31,7 @@ type HostMetrics = Record; export interface HostNodeRow extends HostMetrics { os?: string | null; + ip?: string | null; servicesOnHost?: number | null; title: { name: string; cloudProvider?: CloudProvider | null }; name: string; @@ -53,6 +54,7 @@ const buildItemsList = (nodes: SnapshotNode[]) => { id: `${name}-${index}`, name, os: path.at(-1)?.os ?? '-', + ip: path.at(-1)?.ip ?? '', title: { name, cloudProvider: path.at(-1)?.cloudProvider ?? null, diff --git a/x-pack/plugins/observability/server/saved_objects/slo.ts b/x-pack/plugins/observability/server/saved_objects/slo.ts index e2c8fe22336a8..1e6d108088b8e 100644 --- a/x-pack/plugins/observability/server/saved_objects/slo.ts +++ b/x-pack/plugins/observability/server/saved_objects/slo.ts @@ -20,7 +20,7 @@ export const slo: SavedObjectsType = { dynamic: false, properties: { id: { type: 'keyword' }, - name: { type: 'keyword' }, + name: { type: 'text' }, description: { type: 'text' }, indicator: { properties: { diff --git a/x-pack/test/functional/apps/infra/hosts_view.ts b/x-pack/test/functional/apps/infra/hosts_view.ts index 28686f9b3d7fe..9ef3909691172 100644 --- a/x-pack/test/functional/apps/infra/hosts_view.ts +++ b/x-pack/test/functional/apps/infra/hosts_view.ts @@ -244,6 +244,26 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { expect(metadataTab).to.contain('Metadata'); }); + it('should navigate to Uptime after click', async () => { + await pageObjects.infraHostsView.clickFlyoutUptimeLink(); + await pageObjects.infraHome.waitForLoading(); + const url = await browser.getCurrentUrl(); + expect(url).to.contain( + 'app/uptime/?search=host.name%3A%20%22Jennys-MBP.fritz.box%22%20OR%20host.ip%3A%20%22192.168.1.79%22' + ); + await browser.goBack(); + await pageObjects.infraHome.waitForLoading(); + }); + + it('should navigate to APM services after click', async () => { + await pageObjects.infraHostsView.clickFlyoutApmServicesLink(); + await pageObjects.infraHome.waitForLoading(); + const url = await browser.getCurrentUrl(); + expect(url).to.contain('app/apm/services?kuery=host.hostname%3A%22Jennys-MBP.fritz.box%22'); + await browser.goBack(); + await pageObjects.infraHome.waitForLoading(); + }); + describe('should render processes tab', async () => { const processTitles = [ 'Total processes', 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 d608a7a98a335..cc8582e43d08b 100644 --- a/x-pack/test/functional/page_objects/infra_hosts_view.ts +++ b/x-pack/test/functional/page_objects/infra_hosts_view.ts @@ -36,6 +36,14 @@ export function InfraHostsViewProvider({ getService }: FtrProviderContext) { return testSubjects.click('infraProcessRowButton'); }, + async clickFlyoutUptimeLink() { + return testSubjects.click('hostsView-flyout-uptime-link'); + }, + + async clickFlyoutApmServicesLink() { + return testSubjects.click('hostsView-flyout-apm-services-link'); + }, + async getHostsLandingPageDisabled() { const container = await testSubjects.find('hostView-no-enable-access'); const containerText = await container.getVisibleText();