diff --git a/WORKSPACE.bazel b/WORKSPACE.bazel index d80ad948cbb55..acb62043a15ca 100644 --- a/WORKSPACE.bazel +++ b/WORKSPACE.bazel @@ -10,15 +10,15 @@ load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") # Fetch Node.js rules http_archive( name = "build_bazel_rules_nodejs", - sha256 = "10f534e1c80f795cffe1f2822becd4897754d18564612510c59b3c73544ae7c6", - urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/3.5.0/rules_nodejs-3.5.0.tar.gz"], + sha256 = "4a5d654a4ccd4a4c24eca5d319d85a88a650edf119601550c95bf400c8cc897e", + urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/3.5.1/rules_nodejs-3.5.1.tar.gz"], ) # Now that we have the rules let's import from them to complete the work load("@build_bazel_rules_nodejs//:index.bzl", "check_rules_nodejs_version", "node_repositories", "yarn_install") # Assure we have at least a given rules_nodejs version -check_rules_nodejs_version(minimum_version_string = "3.5.0") +check_rules_nodejs_version(minimum_version_string = "3.5.1") # Setup the Node.js toolchain for the architectures we want to support # diff --git a/dev_docs/tutorials/expressions.mdx b/dev_docs/tutorials/expressions.mdx index f0fc1dc595cfa..288fb9afdd722 100644 --- a/dev_docs/tutorials/expressions.mdx +++ b/dev_docs/tutorials/expressions.mdx @@ -9,23 +9,24 @@ tags: ['kibana', 'onboarding', 'dev', 'architecture'] ## Expressions service -Expression service exposes a registry of reusable functions primary used for fetching and transposing data and a registry of renderer functions that can render data into a DOM element. -Adding functions is easy and so is reusing them. An expression is a chain of functions with provided arguments, which given a single input translates to a single output. +Expression service exposes a registry of reusable functions primary used for fetching and transposing data and a registry of renderer functions that can render data into a DOM element. +Adding functions is easy and so is reusing them. An expression is a chain of functions with provided arguments, which given a single input translates to a single output. Each expression is representable by a human friendly string which a user can type. ### creating expressions Here is a very simple expression string: - essql 'select column1, column2 from myindex' | mapColumn name=column3 fn='{ column1 + 3 }' | table - +``` +essql 'select column1, column2 from myindex' | mapColumn name=column3 fn='{ column1 + 3 }' | table +``` It consists of 3 functions: - essql which runs given sql query against elasticsearch and returns the results - `mapColumn`, which computes a new column from existing ones; - `table`, which prepares the data for rendering in a tabular format. - + The same expression could also be constructed in the code: ```ts @@ -61,7 +62,7 @@ In addition, on the browser side, there are two additional ways to run expressio #### React expression renderer component -This is the easiest way to get expressions rendered inside your application. +This is the easiest way to get expressions rendered inside your application. ```ts diff --git a/docs/user/dashboard/timelion.asciidoc b/docs/user/dashboard/timelion.asciidoc index 675fd03df3648..ec9e8b56f9342 100644 --- a/docs/user/dashboard/timelion.asciidoc +++ b/docs/user/dashboard/timelion.asciidoc @@ -4,7 +4,7 @@ To use *Timelion*, you define a graph by chaining functions together, using the *Timelion*-specific syntax. The syntax enables some features that classical point series charts don't offer, such as pulling data from different indices or data sources into one graph. -deprecated::[7.0.0,"*Timelion* is still supported. The *Timelion app* is deprecated in 7.0, replaced by dashboard features. In the last 7.x minor version and later, the *Timelion app* is removed from {kib}. To prepare for the removal of *Timelion app*, you must migrate *Timelion app* worksheets to a dashboard. For information on how to migrate *Timelion app* worksheets, refer to the link:https://www.elastic.co/guide/en/kibana/7.10/release-notes-7.10.0.html#deprecation-v7.10.0[7.10.0 Release Notes]."] +deprecated::[7.0.0,"*Timelion* is still supported. The *Timelion app* is deprecated in 7.0, replaced by dashboard features. In 7.16 and later, the *Timelion app* is removed from {kib}. To prepare for the removal of *Timelion app*, you must migrate *Timelion app* worksheets to a dashboard. For information on how to migrate *Timelion app* worksheets, refer to the link:https://www.elastic.co/guide/en/kibana/7.10/release-notes-7.10.0.html#deprecation-v7.10.0[7.10.0 Release Notes]."] [float] ==== Timelion expressions diff --git a/package.json b/package.json index a2499d85247d7..65cb1e51866df 100644 --- a/package.json +++ b/package.json @@ -441,7 +441,7 @@ "@babel/traverse": "^7.12.12", "@babel/types": "^7.12.12", "@bazel/ibazel": "^0.15.10", - "@bazel/typescript": "^3.5.0", + "@bazel/typescript": "^3.5.1", "@cypress/snapshot": "^2.1.7", "@cypress/webpack-preprocessor": "^5.6.0", "@elastic/apm-rum": "^5.6.1", diff --git a/packages/kbn-apm-utils/src/index.ts b/packages/kbn-apm-utils/src/index.ts index f2f537138dad0..384b6683199e5 100644 --- a/packages/kbn-apm-utils/src/index.ts +++ b/packages/kbn-apm-utils/src/index.ts @@ -16,6 +16,8 @@ export interface SpanOptions { labels?: Record; } +type Span = Exclude; + export function parseSpanOptions(optionsOrName: SpanOptions | string) { const options = typeof optionsOrName === 'string' ? { name: optionsOrName } : optionsOrName; @@ -30,7 +32,7 @@ const runInNewContext = any>(cb: T): ReturnType( optionsOrName: SpanOptions | string, - cb: () => Promise + cb: (span?: Span) => Promise ): Promise { const options = parseSpanOptions(optionsOrName); @@ -71,13 +73,17 @@ export async function withSpan( span.addLabels(labels); } - return cb() + return cb(span) .then((res) => { - span.outcome = 'success'; + if (!span.outcome || span.outcome === 'unknown') { + span.outcome = 'success'; + } return res; }) .catch((err) => { - span.outcome = 'failure'; + if (!span.outcome || span.outcome === 'unknown') { + span.outcome = 'failure'; + } throw err; }) .finally(() => { diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 3427eee4b5c0b..455453464e957 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -34,7 +34,7 @@ pageLoadAssetSize: indexManagement: 140608 indexPatternManagement: 28222 infra: 184320 - fleet: 415829 + fleet: 450005 ingestPipelines: 58003 inputControlVis: 172675 inspector: 148711 @@ -87,7 +87,6 @@ pageLoadAssetSize: visDefaultEditor: 50178 visTypeMarkdown: 30896 visTypeMetric: 42790 - visTypePie: 34051 visTypeTable: 94934 visTypeTagcloud: 37575 visTypeTimelion: 68883 @@ -111,3 +110,4 @@ pageLoadAssetSize: timelines: 28613 cases: 162385 screenshotMode: 17856 + visTypePie: 35583 diff --git a/src/plugins/discover/public/application/components/discover_grid/discover_grid.scss b/src/plugins/discover/public/application/components/discover_grid/discover_grid.scss index 053b405b90acb..48b99458377ad 100644 --- a/src/plugins/discover/public/application/components/discover_grid/discover_grid.scss +++ b/src/plugins/discover/public/application/components/discover_grid/discover_grid.scss @@ -47,6 +47,9 @@ // We only truncate if the cell is not a control column. .euiDataGridHeader { + // This display property is temporary until https://github.com/elastic/eui/issues/4729 is resolved. + display: flex; + .euiDataGridHeaderCell__content { @include euiTextTruncate; overflow: hidden; diff --git a/src/plugins/discover/public/application/components/top_nav/open_options_popover.tsx b/src/plugins/discover/public/application/components/top_nav/open_options_popover.tsx index 280144d400216..e32ffa4a05de3 100644 --- a/src/plugins/discover/public/application/components/top_nav/open_options_popover.tsx +++ b/src/plugins/discover/public/application/components/top_nav/open_options_popover.tsx @@ -11,11 +11,21 @@ import ReactDOM from 'react-dom'; import { I18nStart } from 'kibana/public'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiSpacer, EuiButton, EuiText, EuiWrappingPopover, EuiCode } from '@elastic/eui'; +import { + EuiSpacer, + EuiButton, + EuiText, + EuiWrappingPopover, + EuiCode, + EuiHorizontalRule, + EuiButtonEmpty, + EuiTextAlign, +} from '@elastic/eui'; import { getServices } from '../../../kibana_services'; import './open_options_popover.scss'; import { DOC_TABLE_LEGACY } from '../../../../common'; +const container = document.createElement('div'); let isOpen = false; interface OptionsPopoverProps { @@ -77,11 +87,29 @@ export function OptionsPopover(props: OptionsPopoverProps) { defaultMessage: 'Get started', })} + + + + {i18n.translate('discover.openOptionsPopover.gotToAllSettings', { + defaultMessage: 'All Discover options', + })} + + ); } +function onClose() { + ReactDOM.unmountComponentAtNode(container); + document.body.removeChild(container); + isOpen = false; +} + export function openOptionsPopover({ I18nContext, anchorElement, @@ -90,17 +118,11 @@ export function openOptionsPopover({ anchorElement: HTMLElement; }) { if (isOpen) { + onClose(); return; } isOpen = true; - const container = document.createElement('div'); - const onClose = () => { - ReactDOM.unmountComponentAtNode(container); - document.body.removeChild(container); - isOpen = false; - }; - document.body.appendChild(container); const element = ( diff --git a/src/plugins/discover/public/url_generator.ts b/src/plugins/discover/public/url_generator.ts index 21bdbf225d6aa..63dea20fecc0a 100644 --- a/src/plugins/discover/public/url_generator.ts +++ b/src/plugins/discover/public/url_generator.ts @@ -6,16 +6,10 @@ * Side Public License, v 1. */ -import { - TimeRange, - Filter, - Query, - esFilters, - QueryState, - RefreshInterval, -} from '../../data/public'; +import type { UrlGeneratorsDefinition } from '../../share/public'; +import type { TimeRange, Filter, Query, QueryState, RefreshInterval } from '../../data/public'; +import { esFilters } from '../../data/public'; import { setStateToKbnUrl } from '../../kibana_utils/public'; -import { UrlGeneratorsDefinition } from '../../share/public'; export const DISCOVER_APP_URL_GENERATOR = 'DISCOVER_APP_URL_GENERATOR'; @@ -71,10 +65,12 @@ export interface DiscoverUrlGeneratorState { * Used interval of the histogram */ interval?: string; + /** * Array of the used sorting [[field,direction],...] */ sort?: string[][]; + /** * id of the used saved query */ diff --git a/src/plugins/kibana_react/public/app_links/redirect_app_link.tsx b/src/plugins/kibana_react/public/app_links/redirect_app_link.tsx index d9ea8be9f5cdd..744a186a201ca 100644 --- a/src/plugins/kibana_react/public/app_links/redirect_app_link.tsx +++ b/src/plugins/kibana_react/public/app_links/redirect_app_link.tsx @@ -12,7 +12,7 @@ import useObservable from 'react-use/lib/useObservable'; import { ApplicationStart } from 'src/core/public'; import { createNavigateToUrlClickHandler } from './click_handler'; -interface RedirectCrossAppLinksProps { +interface RedirectCrossAppLinksProps extends React.HTMLAttributes { application: ApplicationStart; className?: string; 'data-test-subj'?: string; diff --git a/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts b/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts index e033da875080f..1fa7d8e846c9d 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts @@ -142,6 +142,7 @@ export const applicationUsageSchema = { metrics: commonSchema, infra: commonSchema, // It's a forward app so we'll likely never report it fleet: commonSchema, + integrations: commonSchema, ingestManager: commonSchema, lens: commonSchema, maps: commonSchema, diff --git a/src/plugins/share/common/url_service/__tests__/locators.test.ts b/src/plugins/share/common/url_service/__tests__/locators.test.ts new file mode 100644 index 0000000000000..45d727df7de48 --- /dev/null +++ b/src/plugins/share/common/url_service/__tests__/locators.test.ts @@ -0,0 +1,165 @@ +/* + * 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 { of } from 'src/plugins/kibana_utils/common'; +import { testLocator, TestLocatorState, urlServiceTestSetup } from './setup'; + +describe('locators', () => { + test('can start locators service', () => { + const { + service: { locators }, + } = urlServiceTestSetup(); + + expect(typeof locators).toBe('object'); + expect(typeof locators.create).toBe('function'); + expect(typeof locators.get).toBe('function'); + }); + + test('returns "undefined" for unregistered locator', () => { + const { + service: { locators }, + } = urlServiceTestSetup(); + + expect(locators.get(testLocator.id)).toBe(undefined); + }); + + test('can register a locator', () => { + const { + service: { locators }, + } = urlServiceTestSetup(); + + locators.create(testLocator); + expect(typeof locators.get(testLocator.id)).toBe('object'); + }); + + test('getLocation() returns KibanaLocation generated by the locator', async () => { + const { + service: { locators }, + } = urlServiceTestSetup(); + + locators.create(testLocator); + + const locator = locators.get(testLocator.id); + const location = await locator?.getLocation({ + savedObjectId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', + pageNumber: 21, + showFlyout: true, + }); + + expect(location).toEqual({ + app: 'test_app', + route: '/my-object/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx?page=21', + state: { isFlyoutOpen: true }, + }); + }); + + describe('.navigate()', () => { + test('throws if navigation method is not implemented', async () => { + const { + service: { locators }, + } = urlServiceTestSetup(); + const locator = locators.create(testLocator); + const [, error] = await of( + locator.navigate({ + pageNumber: 1, + savedObjectId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', + showFlyout: false, + }) + ); + + expect(error).toBeInstanceOf(Error); + expect(error.message).toBe('not implemented'); + }); + + test('navigates user when .navigate() method is called', async () => { + const { + service: { locators }, + deps, + } = urlServiceTestSetup({ + navigate: jest.fn(async () => {}), + }); + const locator = locators.create(testLocator); + const [, error] = await of( + locator.navigate({ + pageNumber: 1, + savedObjectId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', + showFlyout: false, + }) + ); + + expect(error).toBe(undefined); + expect(deps.navigate).toHaveBeenCalledTimes(1); + expect(deps.navigate).toHaveBeenCalledWith( + { + app: 'test_app', + route: '/my-object/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx?page=1', + state: { + isFlyoutOpen: false, + }, + }, + { replace: false } + ); + }); + + test('can specify "replace" navigation parameter', async () => { + const { + service: { locators }, + deps, + } = urlServiceTestSetup({ + navigate: jest.fn(async () => {}), + }); + const locator = locators.create(testLocator); + + await locator.navigate( + { + pageNumber: 1, + savedObjectId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', + showFlyout: false, + }, + { + replace: false, + } + ); + + expect(deps.navigate).toHaveBeenCalledTimes(1); + expect(deps.navigate).toHaveBeenCalledWith( + { + app: 'test_app', + route: '/my-object/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx?page=1', + state: { + isFlyoutOpen: false, + }, + }, + { replace: false } + ); + + await locator.navigate( + { + pageNumber: 2, + savedObjectId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', + showFlyout: false, + }, + { + replace: true, + } + ); + + expect(deps.navigate).toHaveBeenCalledTimes(2); + expect(deps.navigate).toHaveBeenCalledWith( + { + app: 'test_app', + route: '/my-object/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx?page=2', + state: { + isFlyoutOpen: false, + }, + }, + { replace: true } + ); + }); + }); +}); diff --git a/src/plugins/share/common/url_service/__tests__/setup.ts b/src/plugins/share/common/url_service/__tests__/setup.ts new file mode 100644 index 0000000000000..ad13bb8d8d216 --- /dev/null +++ b/src/plugins/share/common/url_service/__tests__/setup.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { SerializableState } from 'src/plugins/kibana_utils/common'; +import { LocatorDefinition } from '../locators'; +import { UrlService, UrlServiceDependencies } from '../url_service'; + +export interface TestLocatorState extends SerializableState { + savedObjectId: string; + showFlyout: boolean; + pageNumber: number; +} + +export const testLocator: LocatorDefinition = { + id: 'TEST_LOCATOR', + getLocation: async ({ savedObjectId, pageNumber, showFlyout }) => { + return { + app: 'test_app', + route: `/my-object/${savedObjectId}?page=${pageNumber}`, + state: { + isFlyoutOpen: showFlyout, + }, + }; + }, +}; + +export const urlServiceTestSetup = (partialDeps: Partial = {}) => { + const deps: UrlServiceDependencies = { + navigate: async () => { + throw new Error('not implemented'); + }, + ...partialDeps, + }; + const service = new UrlService(deps); + + return { service, deps }; +}; diff --git a/src/plugins/share/common/url_service/index.ts b/src/plugins/share/common/url_service/index.ts new file mode 100644 index 0000000000000..84f74356bcf18 --- /dev/null +++ b/src/plugins/share/common/url_service/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './url_service'; +export * from './locators'; diff --git a/src/plugins/share/common/url_service/locators/index.ts b/src/plugins/share/common/url_service/locators/index.ts new file mode 100644 index 0000000000000..f9f87215eb4db --- /dev/null +++ b/src/plugins/share/common/url_service/locators/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './types'; +export * from './locator'; +export * from './locator_client'; diff --git a/src/plugins/share/common/url_service/locators/locator.ts b/src/plugins/share/common/url_service/locators/locator.ts new file mode 100644 index 0000000000000..68c3b05a7f411 --- /dev/null +++ b/src/plugins/share/common/url_service/locators/locator.ts @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { SavedObjectReference } from 'kibana/server'; +import type { PersistableState, SerializableState } from 'src/plugins/kibana_utils/common'; +import type { + LocatorDefinition, + LocatorPublic, + KibanaLocation, + LocatorNavigationParams, +} from './types'; + +export interface LocatorDependencies { + navigate: (location: KibanaLocation, params?: LocatorNavigationParams) => Promise; +} + +export class Locator

implements PersistableState

, LocatorPublic

{ + public readonly migrations: PersistableState

['migrations']; + + constructor( + public readonly definition: LocatorDefinition

, + protected readonly deps: LocatorDependencies + ) { + this.migrations = definition.migrations || {}; + } + + // PersistableState

------------------------------------------------------- + + public readonly telemetry: PersistableState

['telemetry'] = ( + state: P, + stats: Record + ): Record => { + return this.definition.telemetry ? this.definition.telemetry(state, stats) : stats; + }; + + public readonly inject: PersistableState

['inject'] = ( + state: P, + references: SavedObjectReference[] + ): P => { + return this.definition.inject ? this.definition.inject(state, references) : state; + }; + + public readonly extract: PersistableState

['extract'] = ( + state: P + ): { state: P; references: SavedObjectReference[] } => { + return this.definition.extract ? this.definition.extract(state) : { state, references: [] }; + }; + + // LocatorPublic

---------------------------------------------------------- + + public async getLocation(params: P): Promise { + return await this.definition.getLocation(params); + } + + public async navigate( + params: P, + { replace = false }: LocatorNavigationParams = {} + ): Promise { + const location = await this.getLocation(params); + await this.deps.navigate(location, { + replace, + }); + } +} diff --git a/src/plugins/share/common/url_service/locators/locator_client.ts b/src/plugins/share/common/url_service/locators/locator_client.ts new file mode 100644 index 0000000000000..168cc02d03ff1 --- /dev/null +++ b/src/plugins/share/common/url_service/locators/locator_client.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { SerializableState } from 'src/plugins/kibana_utils/common'; +import type { LocatorDependencies } from './locator'; +import type { LocatorDefinition, LocatorPublic, ILocatorClient } from './types'; +import { Locator } from './locator'; + +export type LocatorClientDependencies = LocatorDependencies; + +export class LocatorClient implements ILocatorClient { + /** + * Collection of registered locators. + */ + protected locators: Map> = new Map(); + + constructor(protected readonly deps: LocatorClientDependencies) {} + + /** + * Creates and register a URL locator. + * + * @param definition A definition of URL locator. + * @returns A public interface of URL locator. + */ + public create

(definition: LocatorDefinition

): LocatorPublic

{ + const locator = new Locator

(definition, this.deps); + + this.locators.set(definition.id, locator); + + return locator; + } + + /** + * Returns a previously registered URL locator. + * + * @param id ID of a URL locator. + * @returns A public interface of a registered URL locator. + */ + public get

(id: string): undefined | LocatorPublic

{ + return this.locators.get(id); + } +} diff --git a/src/plugins/share/common/url_service/locators/types.ts b/src/plugins/share/common/url_service/locators/types.ts new file mode 100644 index 0000000000000..d811ae0fd4aa2 --- /dev/null +++ b/src/plugins/share/common/url_service/locators/types.ts @@ -0,0 +1,91 @@ +/* + * 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 { PersistableState, SerializableState } from 'src/plugins/kibana_utils/common'; + +/** + * URL locator registry. + */ +export interface ILocatorClient { + /** + * Create and register a new locator. + * + * @param urlGenerator Definition of the new locator. + */ + create

(locatorDefinition: LocatorDefinition

): LocatorPublic

; + + /** + * Retrieve a previously registered locator. + * + * @param id Unique ID of the locator. + */ + get

(id: string): undefined | LocatorPublic

; +} + +/** + * A convenience interface used to define and register a locator. + */ +export interface LocatorDefinition

+ extends Partial> { + /** + * Unique ID of the locator. Should be constant and unique across Kibana. + */ + id: string; + + /** + * Returns a deep link, including location state, which can be used for + * navigation in Kibana. + * + * @param params Parameters from which to generate a Kibana location. + */ + getLocation(params: P): Promise; +} + +/** + * Public interface of a registered locator. + */ +export interface LocatorPublic

{ + /** + * Returns a relative URL to the client-side redirect endpoint using this + * locator. (This method is necessary for compatibility with URL generators.) + */ + getLocation(params: P): Promise; + + /** + * Navigate using the `core.application.navigateToApp()` method to a Kibana + * location generated by this locator. This method is available only on the + * browser. + */ + navigate(params: P, navigationParams?: LocatorNavigationParams): Promise; +} + +export interface LocatorNavigationParams { + replace?: boolean; +} + +/** + * This interface represents a location in Kibana to which one can navigate + * using the `core.application.navigateToApp()` method. + */ +export interface KibanaLocation { + /** + * Kibana application ID. + */ + app: string; + + /** + * A URL route within a Kibana application. + */ + route: string; + + /** + * A serializable location state object, which the app can use to determine + * what should be displayed on the screen. + */ + state: S; +} diff --git a/src/plugins/share/common/url_service/url_service.ts b/src/plugins/share/common/url_service/url_service.ts new file mode 100644 index 0000000000000..0c3a0aabb750b --- /dev/null +++ b/src/plugins/share/common/url_service/url_service.ts @@ -0,0 +1,23 @@ +/* + * 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 { LocatorClient, LocatorClientDependencies } from './locators'; + +export type UrlServiceDependencies = LocatorClientDependencies; + +/** + * Common URL Service client interface for server-side and client-side. + */ +export class UrlService { + /** + * Client to work with locators. + */ + locators: LocatorClient = new LocatorClient(this.deps); + + constructor(protected readonly deps: UrlServiceDependencies) {} +} diff --git a/src/plugins/share/public/plugin.ts b/src/plugins/share/public/plugin.ts index 14d74e055cbd9..eb7c46cdaef86 100644 --- a/src/plugins/share/public/plugin.ts +++ b/src/plugins/share/public/plugin.ts @@ -18,6 +18,7 @@ import { UrlGeneratorsSetup, UrlGeneratorsStart, } from './url_generators/url_generator_service'; +import { UrlService } from '../common/url_service'; export interface ShareSetupDependencies { securityOss?: SecurityOssPluginSetup; @@ -27,16 +28,60 @@ export interface ShareStartDependencies { securityOss?: SecurityOssPluginStart; } +/** @public */ +export type SharePluginSetup = ShareMenuRegistrySetup & { + /** + * @deprecated + * + * URL Generators are deprecated use UrlService instead. + */ + urlGenerators: UrlGeneratorsSetup; + + /** + * Utilities to work with URL locators and short URLs. + */ + url: UrlService; +}; + +/** @public */ +export type SharePluginStart = ShareMenuManagerStart & { + /** + * @deprecated + * + * URL Generators are deprecated use UrlService instead. + */ + urlGenerators: UrlGeneratorsStart; + + /** + * Utilities to work with URL locators and short URLs. + */ + url: UrlService; +}; + export class SharePlugin implements Plugin { private readonly shareMenuRegistry = new ShareMenuRegistry(); private readonly shareContextMenu = new ShareMenuManager(); private readonly urlGeneratorsService = new UrlGeneratorsService(); + private url?: UrlService; public setup(core: CoreSetup, plugins: ShareSetupDependencies): SharePluginSetup { core.application.register(createShortUrlRedirectApp(core, window.location)); + + this.url = new UrlService({ + navigate: async (location, { replace = false } = {}) => { + const [start] = await core.getStartServices(); + await start.application.navigateToApp(location.app, { + path: location.route, + state: location.state, + replace, + }); + }, + }); + return { ...this.shareMenuRegistry.setup(), urlGenerators: this.urlGeneratorsService.setup(core), + url: this.url, }; } @@ -48,16 +93,7 @@ export class SharePlugin implements Plugin { plugins.securityOss?.anonymousAccess ), urlGenerators: this.urlGeneratorsService.start(core), + url: this.url!, }; } } - -/** @public */ -export type SharePluginSetup = ShareMenuRegistrySetup & { - urlGenerators: UrlGeneratorsSetup; -}; - -/** @public */ -export type SharePluginStart = ShareMenuManagerStart & { - urlGenerators: UrlGeneratorsStart; -}; diff --git a/src/plugins/share/public/url_generators/README.md b/src/plugins/share/public/url_generators/README.md index 39ee5f2901e91..f948354aad959 100644 --- a/src/plugins/share/public/url_generators/README.md +++ b/src/plugins/share/public/url_generators/README.md @@ -1,3 +1,9 @@ +# URL Generators are deprecated + +__Below is documentation of URL Generators, which are now deprecated and will be removed in favor of URL locators in 7.14.__ + +--- + ## URL Generator Services Developers who maintain pages in Kibana that other developers may want to link to diff --git a/src/plugins/share/public/url_generators/url_generator_service.ts b/src/plugins/share/public/url_generators/url_generator_service.ts index 982f0692102df..5a8e7a1b5c17a 100644 --- a/src/plugins/share/public/url_generators/url_generator_service.ts +++ b/src/plugins/share/public/url_generators/url_generator_service.ts @@ -13,10 +13,20 @@ import { UrlGeneratorInternal } from './url_generator_internal'; import { UrlGeneratorContract } from './url_generator_contract'; export interface UrlGeneratorsStart { + /** + * @deprecated + * + * URL Generators are deprecated, use URL locators in UrlService instead. + */ getUrlGenerator: (urlGeneratorId: T) => UrlGeneratorContract; } export interface UrlGeneratorsSetup { + /** + * @deprecated + * + * URL Generators are deprecated, use URL locators in UrlService instead. + */ registerUrlGenerator: ( generator: UrlGeneratorsDefinition ) => UrlGeneratorContract; diff --git a/src/plugins/share/server/plugin.ts b/src/plugins/share/server/plugin.ts index 744a4148215c3..6e3c68935f77b 100644 --- a/src/plugins/share/server/plugin.ts +++ b/src/plugins/share/server/plugin.ts @@ -12,11 +12,30 @@ import { CoreSetup, Plugin, PluginInitializerContext } from 'kibana/server'; import { createRoutes } from './routes/create_routes'; import { url } from './saved_objects'; import { CSV_SEPARATOR_SETTING, CSV_QUOTE_VALUES_SETTING } from '../common/constants'; +import { UrlService } from '../common/url_service'; + +/** @public */ +export interface SharePluginSetup { + url: UrlService; +} + +/** @public */ +export interface SharePluginStart { + url: UrlService; +} + +export class SharePlugin implements Plugin { + private url?: UrlService; -export class SharePlugin implements Plugin { constructor(private readonly initializerContext: PluginInitializerContext) {} public setup(core: CoreSetup) { + this.url = new UrlService({ + navigate: async () => { + throw new Error('Locator .navigate() does not work on server.'); + }, + }); + createRoutes(core, this.initializerContext.logger.get()); core.savedObjects.registerType(url); core.uiSettings.register({ @@ -41,10 +60,18 @@ export class SharePlugin implements Plugin { schema: schema.boolean(), }, }); + + return { + url: this.url, + }; } public start() { this.initializerContext.logger.get().debug('Starting plugin'); + + return { + url: this.url!, + }; } public stop() { diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json index 1d37c25f52fd4..7b6c4ba9788f1 100644 --- a/src/plugins/telemetry/schema/oss_plugins.json +++ b/src/plugins/telemetry/schema/oss_plugins.json @@ -3184,6 +3184,137 @@ } } }, + "integrations": { + "properties": { + "appId": { + "type": "keyword", + "_meta": { + "description": "The application being tracked" + } + }, + "viewId": { + "type": "keyword", + "_meta": { + "description": "Always `main`" + } + }, + "clicks_total": { + "type": "long", + "_meta": { + "description": "General number of clicks in the application since we started counting them" + } + }, + "clicks_7_days": { + "type": "long", + "_meta": { + "description": "General number of clicks in the application over the last 7 days" + } + }, + "clicks_30_days": { + "type": "long", + "_meta": { + "description": "General number of clicks in the application over the last 30 days" + } + }, + "clicks_90_days": { + "type": "long", + "_meta": { + "description": "General number of clicks in the application over the last 90 days" + } + }, + "minutes_on_screen_total": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen since we started counting them." + } + }, + "minutes_on_screen_7_days": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen over the last 7 days" + } + }, + "minutes_on_screen_30_days": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen over the last 30 days" + } + }, + "minutes_on_screen_90_days": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen over the last 90 days" + } + }, + "views": { + "type": "array", + "items": { + "properties": { + "appId": { + "type": "keyword", + "_meta": { + "description": "The application being tracked" + } + }, + "viewId": { + "type": "keyword", + "_meta": { + "description": "The application view being tracked" + } + }, + "clicks_total": { + "type": "long", + "_meta": { + "description": "General number of clicks in the application sub view since we started counting them" + } + }, + "clicks_7_days": { + "type": "long", + "_meta": { + "description": "General number of clicks in the active application sub view over the last 7 days" + } + }, + "clicks_30_days": { + "type": "long", + "_meta": { + "description": "General number of clicks in the active application sub view over the last 30 days" + } + }, + "clicks_90_days": { + "type": "long", + "_meta": { + "description": "General number of clicks in the active application sub view over the last 90 days" + } + }, + "minutes_on_screen_total": { + "type": "float", + "_meta": { + "description": "Minutes the application sub view is active and on-screen since we started counting them." + } + }, + "minutes_on_screen_7_days": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen active application sub view over the last 7 days" + } + }, + "minutes_on_screen_30_days": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen active application sub view over the last 30 days" + } + }, + "minutes_on_screen_90_days": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen active application sub view over the last 90 days" + } + } + } + } + } + } + }, "ingestManager": { "properties": { "appId": { diff --git a/src/plugins/timelion/public/components/timelion_deprecation.tsx b/src/plugins/timelion/public/components/timelion_deprecation.tsx index efcef88b3d0a2..117aabed6773c 100644 --- a/src/plugins/timelion/public/components/timelion_deprecation.tsx +++ b/src/plugins/timelion/public/components/timelion_deprecation.tsx @@ -19,7 +19,7 @@ export const TimelionDeprecation = ({ links }: DocLinksStart) => { title={ diff --git a/src/plugins/timelion/server/deprecations.ts b/src/plugins/timelion/server/deprecations.ts index e65d72cb460df..3c344e1d4a8d8 100644 --- a/src/plugins/timelion/server/deprecations.ts +++ b/src/plugins/timelion/server/deprecations.ts @@ -30,7 +30,7 @@ export const showWarningMessageIfTimelionSheetWasFound = async ( const count = await getTimelionSheetsCount(savedObjectsClient); if (count > 0) { logger.warn( - 'Deprecated since 7.0, the Timelion app will be removed in the last 7.x minor version. To continue using your Timelion worksheets, migrate them to a dashboard. See https://www.elastic.co/guide/en/kibana/current/create-panels-with-timelion.html.' + 'Deprecated since 7.0, the Timelion app will be removed in 7.16. To continue using your Timelion worksheets, migrate them to a dashboard. See https://www.elastic.co/guide/en/kibana/current/create-panels-with-timelion.html.' ); } }; @@ -49,7 +49,7 @@ export async function getDeprecations({ if (count > 0) { deprecations.push({ - message: `You have ${count} Timelion worksheets. The Timelion app will be removed in the last 7.x minor version. To continue using your Timelion worksheets, migrate them to a dashboard.`, + message: `You have ${count} Timelion worksheets. The Timelion app will be removed in 7.16. To continue using your Timelion worksheets, migrate them to a dashboard.`, documentationUrl: 'https://www.elastic.co/guide/en/kibana/current/create-panels-with-timelion.html', level: 'warning', diff --git a/x-pack/build_chromium/README.md b/x-pack/build_chromium/README.md index efa4d8eaa5cfc..267de376c68ff 100644 --- a/x-pack/build_chromium/README.md +++ b/x-pack/build_chromium/README.md @@ -1,26 +1,29 @@ # Chromium build -We ship our own headless build of Chromium which is significantly smaller than -the standard binaries shipped by Google. The scripts in this folder can be used -to accept a commit hash from the Chromium repository, and initialize the build -on Ubuntu Linux. +We ship our own headless build of Chromium for Linux and Mac OS, using a +version of the source that corresponds to the requirements of the Puppeteer +node module. The scripts in this folder can be used to accept a commit hash +from the Chromium repository, and initialize the build in a workspace. ## Why do we do this -By default, Puppeteer will download a zip file containing the Chromium browser for any -OS. This creates problems on Linux, because Chromium has a dependency on X11, which -is often not installed for a server environment. We don't want to make a requirement -for Linux that you need X11 to run Kibana. To work around this, we create our own Chromium -build, using the +**Linux**: By default, Puppeteer will download a zip file containing the +Chromium browser for any OS. This creates problems on Linux, because Chromium +has a dependency on X11, which is often not installed for a server environment. +We don't want to make a requirement for Linux that you need X11 to run Kibana. +To work around this, we create our own Chromium build, using the [`headless_shell`](https://chromium.googlesource.com/chromium/src/+/5cf4b8b13ed518472038170f8de9db2f6c258fe4/headless) -build target. There are no (trustworthy) sources of these builds available elsewhere. - -Fortunately, creating the custom builds is only necessary for Linux. When you have a build -of Kibana for Linux, or if you use a Linux desktop to develop Kibana, you have a copy of -`headless_shell` bundled inside. When you have a Windows or Mac build of Kibana, or use -either of those for development, you have a copy of the full build of Chromium, which -was downloaded from the main [Chromium download -location](https://commondatastorage.googleapis.com/chromium-browser-snapshots/index.html). +build target. There are no (trustworthy) sources of these builds available +elsewhere. + +**Mac**: We do this on Mac because Elastic signs the Kibanna release artifact +with Apple to work with Gatekeeper on Mac OS. Having our own binary of Chromium +and bundling it with Kibana is integral to the artifact signing process. + +**Windows**: No custom build is necessary for Windows. We are able to use the +full build of Chromium, downloaded from the main [Chromium download +location](https://commondatastorage.googleapis.com/chromium-browser-snapshots/index.html), +using the revision that corresponds with the Puppeteer dependency. ## Build Script Usage diff --git a/x-pack/build_chromium/build.py b/x-pack/build_chromium/build.py index 2f85e95b337d5..8f6c6c79e2d8c 100644 --- a/x-pack/build_chromium/build.py +++ b/x-pack/build_chromium/build.py @@ -6,7 +6,7 @@ md5_file, ) -# This file builds Chromium headless on Linux. +# This file builds Chromium headless on Mac and Linux. # Verify that we have an argument, and if not print instructions if (len(sys.argv) < 2): @@ -68,7 +68,6 @@ print('Running install-build-deps...') runcmd(src_path + '/build/install-build-deps.sh') - print('Updating all modules') runcmd('gclient sync -D') @@ -89,7 +88,7 @@ print('Compiling... this will take a while') runcmd('autoninja -C out/headless headless_shell') -# Optimize the output on Linux x64 by stripping inessentials from the binary +# Optimize the output on Linux x64 and Mac by stripping inessentials from the binary # ARM must be cross-compiled from Linux and can not read the ARM binary in order to strip if platform.system() != 'Windows' and arch_name != 'arm64': print('Optimizing headless_shell') @@ -112,10 +111,18 @@ def archive_file(name): archive.write(from_path, to_path) return to_path -# Add dependencies that must be bundled with the Chromium executable. +# Each platform has slightly different requirements for what dependencies +# must be bundled with the Chromium executable. archive_file('headless_shell') -archive_file(path.join('swiftshader', 'libEGL.so')) -archive_file(path.join('swiftshader', 'libGLESv2.so')) +if platform.system() == 'Linux': + archive_file(path.join('swiftshader', 'libEGL.so')) + archive_file(path.join('swiftshader', 'libGLESv2.so')) + +elif platform.system() == 'Darwin': + archive_file('headless_shell') + archive_file('libswiftshader_libEGL.dylib') + archive_file('libswiftshader_libGLESv2.dylib') + archive_file(path.join('Helpers', 'chrome_crashpad_handler')) archive.close() diff --git a/x-pack/build_chromium/darwin/args.gn b/x-pack/build_chromium/darwin/args.gn new file mode 100644 index 0000000000000..605778a6a737c --- /dev/null +++ b/x-pack/build_chromium/darwin/args.gn @@ -0,0 +1,30 @@ +# Based on //build/headless.gn + +# Embed resource.pak into binary to simplify deployment. +headless_use_embedded_resources = true + +# In order to simplify deployment we build ICU data file +# into binary. +icu_use_data_file = false + +# Use embedded data instead external files for headless in order +# to simplify deployment. +v8_use_external_startup_data = false + +enable_nacl = false +enable_print_preview = false +enable_basic_printing = false +enable_remoting = false +use_alsa = false +use_cups = false +use_dbus = false +use_gio = false +# Please, consult @elastic/kibana-security before changing/removing this option. +use_kerberos = false +use_libpci = false +use_pulseaudio = false +use_udev = false + +is_debug = false +symbol_level = 0 +is_component_build = false diff --git a/x-pack/plugins/actions/server/lib/action_executor.ts b/x-pack/plugins/actions/server/lib/action_executor.ts index b08985e516f84..0737e0ce3f071 100644 --- a/x-pack/plugins/actions/server/lib/action_executor.ts +++ b/x-pack/plugins/actions/server/lib/action_executor.ts @@ -7,6 +7,7 @@ import type { PublicMethodsOf } from '@kbn/utility-types'; import { Logger, KibanaRequest } from 'src/core/server'; +import { withSpan } from '@kbn/apm-utils'; import { validateParams, validateConfig, validateSecrets } from './validate_with_schema'; import { ActionTypeExecutorResult, @@ -78,113 +79,136 @@ export class ActionExecutor { ); } - const { - logger, - spaces, - getServices, - encryptedSavedObjectsClient, - actionTypeRegistry, - eventLogger, - preconfiguredActions, - getActionsClientWithRequest, - } = this.actionExecutorContext!; - - const services = getServices(request); - const spaceId = spaces && spaces.getSpaceId(request); - const namespace = spaceId && spaceId !== 'default' ? { namespace: spaceId } : {}; - - const { actionTypeId, name, config, secrets } = await getActionInfo( - await getActionsClientWithRequest(request, source), - encryptedSavedObjectsClient, - preconfiguredActions, - actionId, - namespace.namespace - ); + return withSpan( + { + name: `execute_action`, + type: 'actions', + labels: { + actionId, + }, + }, + async (span) => { + const { + logger, + spaces, + getServices, + encryptedSavedObjectsClient, + actionTypeRegistry, + eventLogger, + preconfiguredActions, + getActionsClientWithRequest, + } = this.actionExecutorContext!; - if (!actionTypeRegistry.isActionExecutable(actionId, actionTypeId, { notifyUsage: true })) { - actionTypeRegistry.ensureActionTypeEnabled(actionTypeId); - } - const actionType = actionTypeRegistry.get(actionTypeId); - - let validatedParams: Record; - let validatedConfig: Record; - let validatedSecrets: Record; - - try { - validatedParams = validateParams(actionType, params); - validatedConfig = validateConfig(actionType, config); - validatedSecrets = validateSecrets(actionType, secrets); - } catch (err) { - return { status: 'error', actionId, message: err.message, retry: false }; - } + const services = getServices(request); + const spaceId = spaces && spaces.getSpaceId(request); + const namespace = spaceId && spaceId !== 'default' ? { namespace: spaceId } : {}; + + const { actionTypeId, name, config, secrets } = await getActionInfo( + await getActionsClientWithRequest(request, source), + encryptedSavedObjectsClient, + preconfiguredActions, + actionId, + namespace.namespace + ); + + if (span) { + span.name = `execute_action ${actionTypeId}`; + span.addLabels({ + actionTypeId, + }); + } + + if (!actionTypeRegistry.isActionExecutable(actionId, actionTypeId, { notifyUsage: true })) { + actionTypeRegistry.ensureActionTypeEnabled(actionTypeId); + } + const actionType = actionTypeRegistry.get(actionTypeId); + + let validatedParams: Record; + let validatedConfig: Record; + let validatedSecrets: Record; + + try { + validatedParams = validateParams(actionType, params); + validatedConfig = validateConfig(actionType, config); + validatedSecrets = validateSecrets(actionType, secrets); + } catch (err) { + span?.setOutcome('failure'); + return { status: 'error', actionId, message: err.message, retry: false }; + } - const actionLabel = `${actionTypeId}:${actionId}: ${name}`; - logger.debug(`executing action ${actionLabel}`); - - const event: IEvent = { - event: { action: EVENT_LOG_ACTIONS.execute }, - kibana: { - saved_objects: [ - { - rel: SAVED_OBJECT_REL_PRIMARY, - type: 'action', - id: actionId, - ...namespace, + const actionLabel = `${actionTypeId}:${actionId}: ${name}`; + logger.debug(`executing action ${actionLabel}`); + + const event: IEvent = { + event: { action: EVENT_LOG_ACTIONS.execute }, + kibana: { + saved_objects: [ + { + rel: SAVED_OBJECT_REL_PRIMARY, + type: 'action', + id: actionId, + type_id: actionTypeId, + ...namespace, + }, + ], }, - ], - }, - }; + }; - eventLogger.startTiming(event); - let rawResult: ActionTypeExecutorResult; - try { - rawResult = await actionType.executor({ - actionId, - services, - params: validatedParams, - config: validatedConfig, - secrets: validatedSecrets, - }); - } catch (err) { - rawResult = { - actionId, - status: 'error', - message: 'an error occurred while running the action executor', - serviceMessage: err.message, - retry: false, - }; - } - eventLogger.stopTiming(event); + eventLogger.startTiming(event); + let rawResult: ActionTypeExecutorResult; + try { + rawResult = await actionType.executor({ + actionId, + services, + params: validatedParams, + config: validatedConfig, + secrets: validatedSecrets, + }); + } catch (err) { + rawResult = { + actionId, + status: 'error', + message: 'an error occurred while running the action executor', + serviceMessage: err.message, + retry: false, + }; + } + eventLogger.stopTiming(event); - // allow null-ish return to indicate success - const result = rawResult || { - actionId, - status: 'ok', - }; + // allow null-ish return to indicate success + const result = rawResult || { + actionId, + status: 'ok', + }; - event.event = event.event || {}; - - if (result.status === 'ok') { - event.event.outcome = 'success'; - event.message = `action executed: ${actionLabel}`; - } else if (result.status === 'error') { - event.event.outcome = 'failure'; - event.message = `action execution failure: ${actionLabel}`; - event.error = event.error || {}; - event.error.message = actionErrorToMessage(result); - logger.warn(`action execution failure: ${actionLabel}: ${event.error.message}`); - } else { - event.event.outcome = 'failure'; - event.message = `action execution returned unexpected result: ${actionLabel}: "${result.status}"`; - event.error = event.error || {}; - event.error.message = 'action execution returned unexpected result'; - logger.warn( - `action execution failure: ${actionLabel}: returned unexpected result "${result.status}"` - ); - } + event.event = event.event || {}; - eventLogger.logEvent(event); - return result; + if (result.status === 'ok') { + span?.setOutcome('success'); + event.event.outcome = 'success'; + event.message = `action executed: ${actionLabel}`; + } else if (result.status === 'error') { + span?.setOutcome('failure'); + event.event.outcome = 'failure'; + event.message = `action execution failure: ${actionLabel}`; + event.error = event.error || {}; + event.error.message = actionErrorToMessage(result); + logger.warn(`action execution failure: ${actionLabel}: ${event.error.message}`); + } else { + span?.setOutcome('failure'); + event.event.outcome = 'failure'; + event.message = `action execution returned unexpected result: ${actionLabel}: "${result.status}"`; + event.error = event.error || {}; + event.error.message = 'action execution returned unexpected result'; + logger.warn( + `action execution failure: ${actionLabel}: returned unexpected result "${result.status}"` + ); + } + + eventLogger.logEvent(event); + return result; + } + ); } } diff --git a/x-pack/plugins/alerting/server/task_runner/create_execution_handler.test.ts b/x-pack/plugins/alerting/server/task_runner/create_execution_handler.test.ts index 78d74b78c99ba..5ab25fbfa39e7 100644 --- a/x-pack/plugins/alerting/server/task_runner/create_execution_handler.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/create_execution_handler.test.ts @@ -166,10 +166,12 @@ test('enqueues execution per selected action', async () => { "id": "1", "rel": "primary", "type": "alert", + "type_id": "test", }, Object { "id": "1", "type": "action", + "type_id": "test", }, ], }, diff --git a/x-pack/plugins/alerting/server/task_runner/create_execution_handler.ts b/x-pack/plugins/alerting/server/task_runner/create_execution_handler.ts index 93cced2043d5e..ef93179bdaba1 100644 --- a/x-pack/plugins/alerting/server/task_runner/create_execution_handler.ts +++ b/x-pack/plugins/alerting/server/task_runner/create_execution_handler.ts @@ -182,8 +182,14 @@ export function createExecutionHandler< action_subgroup: actionSubgroup, }, saved_objects: [ - { rel: SAVED_OBJECT_REL_PRIMARY, type: 'alert', id: alertId, ...namespace }, - { type: 'action', id: action.id, ...namespace }, + { + rel: SAVED_OBJECT_REL_PRIMARY, + type: 'alert', + id: alertId, + type_id: alertType.id, + ...namespace, + }, + { type: 'action', id: action.id, type_id: action.actionTypeId, ...namespace }, ], }, }; diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts index 8b720203b2533..74d4bbe19b6d0 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts @@ -280,6 +280,7 @@ describe('Task Runner', () => { "namespace": undefined, "rel": "primary", "type": "alert", + "type_id": "test", }, ], }, @@ -390,6 +391,7 @@ describe('Task Runner', () => { namespace: undefined, rel: 'primary', type: 'alert', + type_id: 'test', }, ], }, @@ -414,6 +416,7 @@ describe('Task Runner', () => { namespace: undefined, rel: 'primary', type: 'alert', + type_id: 'test', }, ], }, @@ -436,11 +439,13 @@ describe('Task Runner', () => { namespace: undefined, rel: 'primary', type: 'alert', + type_id: 'test', }, { id: '1', namespace: undefined, type: 'action', + type_id: 'action', }, ], }, @@ -463,6 +468,7 @@ describe('Task Runner', () => { namespace: undefined, rel: 'primary', type: 'alert', + type_id: 'test', }, ], }, @@ -542,6 +548,7 @@ describe('Task Runner', () => { namespace: undefined, rel: 'primary', type: 'alert', + type_id: 'test', }, ], }, @@ -565,6 +572,7 @@ describe('Task Runner', () => { namespace: undefined, rel: 'primary', type: 'alert', + type_id: 'test', }, ], }, @@ -586,6 +594,7 @@ describe('Task Runner', () => { namespace: undefined, rel: 'primary', type: 'alert', + type_id: 'test', }, ], }, @@ -724,6 +733,7 @@ describe('Task Runner', () => { "namespace": undefined, "rel": "primary", "type": "alert", + "type_id": "test", }, ], }, @@ -747,6 +757,7 @@ describe('Task Runner', () => { "namespace": undefined, "rel": "primary", "type": "alert", + "type_id": "test", }, ], }, @@ -1158,6 +1169,7 @@ describe('Task Runner', () => { "namespace": undefined, "rel": "primary", "type": "alert", + "type_id": "test", }, ], }, @@ -1183,6 +1195,7 @@ describe('Task Runner', () => { "namespace": undefined, "rel": "primary", "type": "alert", + "type_id": "test", }, ], }, @@ -1234,11 +1247,13 @@ describe('Task Runner', () => { "namespace": undefined, "rel": "primary", "type": "alert", + "type_id": "test", }, Object { "id": "1", "namespace": undefined, "type": "action", + "type_id": "action", }, ], }, @@ -1262,6 +1277,7 @@ describe('Task Runner', () => { "namespace": undefined, "rel": "primary", "type": "alert", + "type_id": "test", }, ], }, @@ -1584,6 +1600,7 @@ describe('Task Runner', () => { "namespace": undefined, "rel": "primary", "type": "alert", + "type_id": "test", }, ], }, @@ -1609,6 +1626,7 @@ describe('Task Runner', () => { "namespace": undefined, "rel": "primary", "type": "alert", + "type_id": "test", }, ], }, @@ -1632,6 +1650,7 @@ describe('Task Runner', () => { "namespace": undefined, "rel": "primary", "type": "alert", + "type_id": "test", }, ], }, @@ -1842,6 +1861,7 @@ describe('Task Runner', () => { "namespace": undefined, "rel": "primary", "type": "alert", + "type_id": "test", }, ], }, @@ -1901,6 +1921,7 @@ describe('Task Runner', () => { "namespace": undefined, "rel": "primary", "type": "alert", + "type_id": "test", }, ], }, @@ -1968,6 +1989,7 @@ describe('Task Runner', () => { "namespace": undefined, "rel": "primary", "type": "alert", + "type_id": "test", }, ], }, @@ -2035,6 +2057,7 @@ describe('Task Runner', () => { "namespace": undefined, "rel": "primary", "type": "alert", + "type_id": "test", }, ], }, @@ -2101,6 +2124,7 @@ describe('Task Runner', () => { "namespace": undefined, "rel": "primary", "type": "alert", + "type_id": "test", }, ], }, diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.ts index 6e4ae1295eb33..bfb698c878124 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.ts @@ -338,6 +338,7 @@ export class TaskRunner< alertId, alertLabel, namespace, + ruleTypeId: alert.alertTypeId, }); if (!muteAll) { @@ -500,6 +501,7 @@ export class TaskRunner< rel: SAVED_OBJECT_REL_PRIMARY, type: 'alert', id: alertId, + type_id: this.alertType.id, namespace, }, ], @@ -661,6 +663,7 @@ interface GenerateNewAndRecoveredInstanceEventsParams< alertId: string; alertLabel: string; namespace: string | undefined; + ruleTypeId: string; } function generateNewAndRecoveredInstanceEvents< @@ -674,6 +677,7 @@ function generateNewAndRecoveredInstanceEvents< currentAlertInstances, originalAlertInstances, recoveredAlertInstances, + ruleTypeId, } = params; const originalAlertInstanceIds = Object.keys(originalAlertInstances); const currentAlertInstanceIds = Object.keys(currentAlertInstances); @@ -756,6 +760,7 @@ function generateNewAndRecoveredInstanceEvents< rel: SAVED_OBJECT_REL_PRIMARY, type: 'alert', id: alertId, + type_id: ruleTypeId, namespace, }, ], diff --git a/x-pack/plugins/apm/public/application/index.tsx b/x-pack/plugins/apm/public/application/index.tsx index d5d77eea8c9c0..6924a8b9161b2 100644 --- a/x-pack/plugins/apm/public/application/index.tsx +++ b/x-pack/plugins/apm/public/application/index.tsx @@ -10,7 +10,11 @@ import ReactDOM from 'react-dom'; import 'react-vis/dist/style.css'; import type { ObservabilityRuleTypeRegistry } from '../../../observability/public'; import { ConfigSchema } from '../'; -import { AppMountParameters, CoreStart } from '../../../../../src/core/public'; +import { + AppMountParameters, + CoreStart, + APP_WRAPPER_CLASS, +} from '../../../../../src/core/public'; import { ApmPluginSetupDeps, ApmPluginStartDeps } from '../plugin'; import { createCallApmApi } from '../services/rest/createCallApmApi'; import { createStaticIndexPattern } from '../services/rest/index_pattern'; @@ -58,6 +62,9 @@ export const renderApp = ({ console.log('Error creating static index pattern', e); }); + // add .kbnAppWrappers class to root element + element.classList.add(APP_WRAPPER_CLASS); + ReactDOM.render( )} - - - } - /> - + + } + /> diff --git a/x-pack/plugins/apm/public/components/app/service_map/index.tsx b/x-pack/plugins/apm/public/components/app/service_map/index.tsx index 714228d58f962..df8438c5c80a4 100644 --- a/x-pack/plugins/apm/public/components/app/service_map/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_map/index.tsx @@ -5,7 +5,12 @@ * 2.0. */ -import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiLoadingSpinner, + EuiPanel, +} from '@elastic/eui'; import React, { PropsWithChildren, ReactNode } from 'react'; import { isActivePlatinumLicense } from '../../../../common/license_check'; import { useTrackPageview } from '../../../../../observability/public'; @@ -97,6 +102,10 @@ export function ServiceMap({ const { ref, height } = useRefDimensions(); + // Temporary hack to work around bottom padding introduced by EuiPage + const PADDING_BOTTOM = 24; + const heightWithPadding = height - PADDING_BOTTOM; + useTrackPageview({ app: 'apm', path: 'service_map' }); useTrackPageview({ app: 'apm', path: 'service_map', delay: 15000 }); @@ -137,20 +146,25 @@ export function ServiceMap({ return ( <> - -

- +
- - {serviceName && } - {status === FETCH_STATUS.LOADING && } - - -
+ + + {serviceName && } + {status === FETCH_STATUS.LOADING && } + + +
+ ); } diff --git a/x-pack/plugins/apm/public/components/app/service_node_metrics/index.tsx b/x-pack/plugins/apm/public/components/app/service_node_metrics/index.tsx index 20b78b90e0378..5528bdaa40c41 100644 --- a/x-pack/plugins/apm/public/components/app/service_node_metrics/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_node_metrics/index.tsx @@ -39,13 +39,6 @@ const Truncate = euiStyled.span` ${truncate(px(unit * 12))} `; -const MetadataFlexGroup = euiStyled(EuiFlexGroup)` - border-bottom: ${({ theme }) => theme.eui.euiBorderThin}; - margin-bottom: ${({ theme }) => theme.eui.paddingSizes.m}; - padding: ${({ theme }) => - `${theme.eui.paddingSizes.m} 0 0 ${theme.eui.paddingSizes.m}`}; -`; - interface ServiceNodeMetricsProps { serviceName: string; serviceNodeName: string; @@ -117,55 +110,60 @@ export function ServiceNodeMetrics({ /> ) : ( - - - + + + + {serviceName} + + } + /> + + + + {host} + } - )} - title={ - - {serviceName} - - } - /> - - - - {host} - - } - /> - - - + + + + {containerId} + } - )} - title={ - - {containerId} - - } - /> - - + /> + + + )} {agentName && ( diff --git a/x-pack/plugins/apm/public/components/app/service_node_overview/index.tsx b/x-pack/plugins/apm/public/components/app/service_node_overview/index.tsx index 69e5ea5a78ea1..1a432f90f1e3a 100644 --- a/x-pack/plugins/apm/public/components/app/service_node_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_node_overview/index.tsx @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { EuiPanel, EuiToolTip } from '@elastic/eui'; +import { EuiToolTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common'; @@ -142,18 +142,16 @@ function ServiceNodeOverview({ serviceName }: ServiceNodeOverviewProps) { ]; return ( - - - + ); } diff --git a/x-pack/plugins/apm/public/components/app/trace_overview/index.tsx b/x-pack/plugins/apm/public/components/app/trace_overview/index.tsx index 0938456193dc0..bf60463255d64 100644 --- a/x-pack/plugins/apm/public/components/app/trace_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/trace_overview/index.tsx @@ -5,7 +5,6 @@ * 2.0. */ -import { EuiPanel } from '@elastic/eui'; import React from 'react'; import { useTrackPageview } from '../../../../../observability/public'; import { useUrlParams } from '../../../context/url_params_context/use_url_params'; @@ -51,12 +50,10 @@ export function TraceOverview() { <> - - - + ); } diff --git a/x-pack/plugins/apm/public/components/routing/apm_route_config.tsx b/x-pack/plugins/apm/public/components/routing/apm_route_config.tsx index af62f4f235af7..09c25ee4557c5 100644 --- a/x-pack/plugins/apm/public/components/routing/apm_route_config.tsx +++ b/x-pack/plugins/apm/public/components/routing/apm_route_config.tsx @@ -14,17 +14,13 @@ import { toQuery } from '../shared/Links/url_helpers'; import { ErrorGroupDetails } from '../app/ErrorGroupDetails'; import { useApmPluginContext } from '../../context/apm_plugin/use_apm_plugin_context'; import { ServiceNodeMetrics } from '../app/service_node_metrics'; -import { Settings } from '../app/Settings'; +import { SettingsTemplate } from './templates/settings_template'; import { AgentConfigurations } from '../app/Settings/AgentConfigurations'; import { AnomalyDetection } from '../app/Settings/anomaly_detection'; import { ApmIndices } from '../app/Settings/ApmIndices'; import { CustomizeUI } from '../app/Settings/CustomizeUI'; import { TraceLink } from '../app/TraceLink'; import { TransactionDetails } from '../app/transaction_details'; -import { - CreateAgentConfigurationRouteHandler, - EditAgentConfigurationRouteHandler, -} from './route_handlers/agent_configuration'; import { enableServiceOverview } from '../../../common/ui_settings_keys'; import { redirectTo } from './redirect_to'; import { ApmMainTemplate } from './templates/apm_main_template'; @@ -38,6 +34,8 @@ import { ServiceOverview } from '../app/service_overview'; import { TransactionOverview } from '../app/transaction_overview'; import { ServiceInventory } from '../app/service_inventory'; import { TraceOverview } from '../app/trace_overview'; +import { useFetcher } from '../../hooks/use_fetcher'; +import { AgentConfigurationCreateEdit } from '../app/Settings/AgentConfigurations/AgentConfigurationCreateEdit'; // These component function definitions are used below with the `component` // property of the route definitions. @@ -222,9 +220,9 @@ function TransactionDetailsRouteView( function SettingsAgentConfigurationRouteView() { return ( - + - + ); } @@ -232,9 +230,9 @@ function SettingsAgentConfigurationRouteView() { function SettingsAnomalyDetectionRouteView() { return ( - + - + ); } @@ -242,9 +240,9 @@ function SettingsAnomalyDetectionRouteView() { function SettingsApmIndicesRouteView() { return ( - + - + ); } @@ -252,9 +250,57 @@ function SettingsApmIndicesRouteView() { function SettingsCustomizeUI() { return ( - + - + + + ); +} + +export function EditAgentConfigurationRouteView(props: RouteComponentProps) { + const { search } = props.history.location; + + // typescript complains because `pageStop` does not exist in `APMQueryParams` + // Going forward we should move away from globally declared query params and this is a first step + // @ts-expect-error + const { name, environment, pageStep } = toQuery(search); + + const res = useFetcher( + (callApmApi) => { + return callApmApi({ + endpoint: 'GET /api/apm/settings/agent-configuration/view', + params: { query: { name, environment } }, + }); + }, + [name, environment] + ); + + return ( + + + + + + ); +} + +export function CreateAgentConfigurationRouteView(props: RouteComponentProps) { + const { search } = props.history.location; + + // Ignoring here because we specifically DO NOT want to add the query params to the global route handler + // @ts-expect-error + const { pageStep } = toQuery(search); + + return ( + + + + ); } @@ -339,14 +385,14 @@ export const apmRouteConfig: APMRouteDefinition[] = [ { exact: true, path: '/settings/agent-configuration/create', - component: CreateAgentConfigurationRouteHandler, + component: CreateAgentConfigurationRouteView, breadcrumb: CreateAgentConfigurationTitle, }, { exact: true, path: '/settings/agent-configuration/edit', breadcrumb: EditAgentConfigurationTitle, - component: EditAgentConfigurationRouteHandler, + component: EditAgentConfigurationRouteView, }, { exact: true, diff --git a/x-pack/plugins/apm/public/components/routing/app_root.tsx b/x-pack/plugins/apm/public/components/routing/app_root.tsx index 9529a67210748..2bb387ae315ff 100644 --- a/x-pack/plugins/apm/public/components/routing/app_root.tsx +++ b/x-pack/plugins/apm/public/components/routing/app_root.tsx @@ -11,7 +11,7 @@ import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; import React from 'react'; import { Route, Router, Switch } from 'react-router-dom'; import { DefaultTheme, ThemeProvider } from 'styled-components'; -import { euiStyled } from '../../../../../../src/plugins/kibana_react/common'; +import { APP_WRAPPER_CLASS } from '../../../../../../src/core/public'; import { KibanaContextProvider, RedirectAppLinks, @@ -32,10 +32,6 @@ import { useApmPluginContext } from '../../context/apm_plugin/use_apm_plugin_con import { AnomalyDetectionJobsContextProvider } from '../../context/anomaly_detection_jobs/anomaly_detection_jobs_context'; import { apmRouteConfig } from './apm_route_config'; -const MainContainer = euiStyled.div` - height: 100%; -`; - export function ApmAppRoot({ apmPluginContextValue, pluginsStart, @@ -48,7 +44,12 @@ export function ApmAppRoot({ const i18nCore = core.i18n; return ( - + @@ -57,19 +58,14 @@ export function ApmAppRoot({ - - + - - - {apmRouteConfig.map((route, i) => ( - - ))} - - + + + {apmRouteConfig.map((route, i) => ( + + ))} + diff --git a/x-pack/plugins/apm/public/components/routing/route_handlers/agent_configuration.tsx b/x-pack/plugins/apm/public/components/routing/route_handlers/agent_configuration.tsx deleted file mode 100644 index 8e0a08603bc76..0000000000000 --- a/x-pack/plugins/apm/public/components/routing/route_handlers/agent_configuration.tsx +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { RouteComponentProps } from 'react-router-dom'; -import { useFetcher } from '../../../hooks/use_fetcher'; -import { toQuery } from '../../shared/Links/url_helpers'; -import { Settings } from '../../app/Settings'; -import { AgentConfigurationCreateEdit } from '../../app/Settings/AgentConfigurations/AgentConfigurationCreateEdit'; - -type EditAgentConfigurationRouteHandler = RouteComponentProps<{}>; - -export function EditAgentConfigurationRouteHandler( - props: EditAgentConfigurationRouteHandler -) { - const { search } = props.history.location; - - // typescript complains because `pageStop` does not exist in `APMQueryParams` - // Going forward we should move away from globally declared query params and this is a first step - // @ts-expect-error - const { name, environment, pageStep } = toQuery(search); - - const res = useFetcher( - (callApmApi) => { - return callApmApi({ - endpoint: 'GET /api/apm/settings/agent-configuration/view', - params: { query: { name, environment } }, - }); - }, - [name, environment] - ); - - return ( - - - - ); -} - -type CreateAgentConfigurationRouteHandlerProps = RouteComponentProps<{}>; - -export function CreateAgentConfigurationRouteHandler( - props: CreateAgentConfigurationRouteHandlerProps -) { - const { search } = props.history.location; - - // Ignoring here because we specifically DO NOT want to add the query params to the global route handler - // @ts-expect-error - const { pageStep } = toQuery(search); - - return ( - - - - ); -} diff --git a/x-pack/plugins/apm/public/components/routing/templates/apm_main_template.tsx b/x-pack/plugins/apm/public/components/routing/templates/apm_main_template.tsx index 0473e88c23d12..e917350f6024b 100644 --- a/x-pack/plugins/apm/public/components/routing/templates/apm_main_template.tsx +++ b/x-pack/plugins/apm/public/components/routing/templates/apm_main_template.tsx @@ -5,6 +5,7 @@ * 2.0. */ +import { EuiPageHeaderProps, EuiPageTemplateProps } from '@elastic/eui'; import React from 'react'; import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; import { ApmPluginStartDeps } from '../../../plugin'; @@ -21,11 +22,14 @@ import { EnvironmentFilter } from '../../shared/EnvironmentFilter'; */ export function ApmMainTemplate({ pageTitle, + pageHeader, children, + ...pageTemplateProps }: { - pageTitle: React.ReactNode; + pageTitle?: React.ReactNode; + pageHeader?: EuiPageHeaderProps; children: React.ReactNode; -}) { +} & EuiPageTemplateProps) { const { services } = useKibana(); const ObservabilityPageTemplate = services.observability.navigation.PageTemplate; @@ -35,7 +39,9 @@ export function ApmMainTemplate({ pageHeader={{ pageTitle, rightSideItems: [], + ...pageHeader, }} + {...pageTemplateProps} > {children} diff --git a/x-pack/plugins/apm/public/components/routing/templates/apm_service_template.tsx b/x-pack/plugins/apm/public/components/routing/templates/apm_service_template.tsx index 526d9eb3551d0..ab53052780ea9 100644 --- a/x-pack/plugins/apm/public/components/routing/templates/apm_service_template.tsx +++ b/x-pack/plugins/apm/public/components/routing/templates/apm_service_template.tsx @@ -10,9 +10,8 @@ import { i18n } from '@kbn/i18n'; import { EuiFlexGroup, EuiFlexItem, + EuiPageHeaderProps, EuiTitle, - EuiTabs, - EuiTab, EuiBetaBadge, } from '@elastic/eui'; import { ApmMainTemplate } from './apm_main_template'; @@ -33,12 +32,10 @@ import { useUrlParams } from '../../../context/url_params_context/use_url_params import { Correlations } from '../../app/correlations'; import { SearchBar } from '../../shared/search_bar'; -interface Tab { - key: TabKey; - href: string; - text: React.ReactNode; +type Tab = NonNullable[0] & { + key: string; hidden?: boolean; -} +}; type TabKey = | 'errors' @@ -49,60 +46,63 @@ type TabKey = | 'profiling' | 'transactions'; -export function ApmServiceTemplate({ +interface Props { + children: React.ReactNode; + serviceName: string; + selectedTab: TabKey; + searchBarOptions?: React.ComponentProps; +} + +export function ApmServiceTemplate(props: Props) { + return ( + +