diff --git a/docs/settings/apm-settings.asciidoc b/docs/settings/apm-settings.asciidoc index f78b0642f7fa34..b396c40aa21f96 100644 --- a/docs/settings/apm-settings.asciidoc +++ b/docs/settings/apm-settings.asciidoc @@ -47,7 +47,7 @@ Changing these settings may disable features of the APM App. | Set to `false` to hide the APM app from the menu. Defaults to `true`. | `xpack.apm.ui.transactionGroupBucketSize` - | Number of top transaction groups displayed in the APM app. Defaults to `100`. + | Number of top transaction groups displayed in the APM app. Defaults to `1000`. | `xpack.apm.ui.maxTraceItems` {ess-icon} | Maximum number of child items displayed when viewing trace details. Defaults to `1000`. diff --git a/test/functional/apps/saved_objects_management/edit_saved_object.ts b/test/functional/apps/saved_objects_management/edit_saved_object.ts index 0e2ff44ff62ef0..aac6178b34e1d5 100644 --- a/test/functional/apps/saved_objects_management/edit_saved_object.ts +++ b/test/functional/apps/saved_objects_management/edit_saved_object.ts @@ -67,7 +67,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }; // Flaky: https://github.com/elastic/kibana/issues/68400 - describe('saved objects edition page', () => { + describe.skip('saved objects edition page', () => { beforeEach(async () => { await esArchiver.load('saved_objects_management/edit_saved_object'); }); diff --git a/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/WatcherFlyout.tsx b/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/WatcherFlyout.tsx deleted file mode 100644 index 26cff5e71b610f..00000000000000 --- a/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/WatcherFlyout.tsx +++ /dev/null @@ -1,635 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { - EuiButton, - EuiFieldNumber, - EuiFieldText, - EuiFlexGroup, - EuiFlexItem, - EuiFlyout, - EuiFlyoutBody, - EuiFlyoutFooter, - EuiFlyoutHeader, - EuiForm, - EuiFormRow, - EuiLink, - EuiRadio, - EuiSelect, - EuiSpacer, - EuiSwitch, - EuiText, - EuiTitle, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { padStart, range } from 'lodash'; -import moment from 'moment-timezone'; -import React, { Component } from 'react'; -import styled from 'styled-components'; -import { toMountPoint } from '../../../../../../../../src/plugins/kibana_react/public'; -import { IUrlParams } from '../../../../context/UrlParamsContext/types'; -import { KibanaLink } from '../../../shared/Links/KibanaLink'; -import { createErrorGroupWatch, Schedule } from './createErrorGroupWatch'; -import { ElasticDocsLink } from '../../../shared/Links/ElasticDocsLink'; -import { ApmPluginContext } from '../../../../context/ApmPluginContext'; -import { getApmIndexPatternTitle } from '../../../../services/rest/index_pattern'; - -type ScheduleKey = keyof Schedule; - -const SmallInput = styled.div` - .euiFormRow { - max-width: 85px; - } - .euiFormHelpText { - width: 200px; - } -`; - -interface WatcherFlyoutProps { - urlParams: IUrlParams; - onClose: () => void; - isOpen: boolean; -} - -type IntervalUnit = 'm' | 'h'; - -interface WatcherFlyoutState { - schedule: ScheduleKey; - threshold: number; - actions: { - slack: boolean; - email: boolean; - }; - interval: { - value: number; - unit: IntervalUnit; - }; - daily: string; - emails: string; - slackUrl: string; -} - -export class WatcherFlyout extends Component< - WatcherFlyoutProps, - WatcherFlyoutState -> { - static contextType = ApmPluginContext; - context!: React.ContextType; - public state: WatcherFlyoutState = { - schedule: 'daily', - threshold: 10, - actions: { - slack: false, - email: false, - }, - interval: { - value: 10, - unit: 'm', - }, - daily: '08:00', - emails: '', - slackUrl: '', - }; - - public onChangeSchedule = (schedule: ScheduleKey) => { - this.setState({ schedule }); - }; - - public onChangeThreshold = (event: React.ChangeEvent) => { - this.setState({ - threshold: parseInt(event.target.value, 10), - }); - }; - - public onChangeDailyUnit = (event: React.ChangeEvent) => { - this.setState({ - daily: event.target.value, - }); - }; - - public onChangeIntervalValue = ( - event: React.ChangeEvent - ) => { - this.setState({ - interval: { - value: parseInt(event.target.value, 10), - unit: this.state.interval.unit, - }, - }); - }; - - public onChangeIntervalUnit = ( - event: React.ChangeEvent - ) => { - this.setState({ - interval: { - value: this.state.interval.value, - unit: event.target.value as IntervalUnit, - }, - }); - }; - - public onChangeAction = (actionName: 'slack' | 'email') => { - this.setState({ - actions: { - ...this.state.actions, - [actionName]: !this.state.actions[actionName], - }, - }); - }; - - public onChangeEmails = (event: React.ChangeEvent) => { - this.setState({ emails: event.target.value }); - }; - - public onChangeSlackUrl = (event: React.ChangeEvent) => { - this.setState({ slackUrl: event.target.value }); - }; - - public createWatch = () => { - const { serviceName } = this.props.urlParams; - const { core } = this.context; - - if (!serviceName) { - return; - } - - const emails = this.state.actions.email - ? this.state.emails - .split(',') - .map((email) => email.trim()) - .filter((email) => !!email) - : []; - - const slackUrl = this.state.actions.slack ? this.state.slackUrl : ''; - - const schedule = - this.state.schedule === 'interval' - ? { - interval: `${this.state.interval.value}${this.state.interval.unit}`, - } - : { - daily: { at: `${this.state.daily}` }, - }; - - const timeRange = - this.state.schedule === 'interval' - ? { - value: this.state.interval.value, - unit: this.state.interval.unit, - } - : { - value: 24, - unit: 'h', - }; - - return getApmIndexPatternTitle() - .then((indexPatternTitle) => { - return createErrorGroupWatch({ - http: core.http, - emails, - schedule, - serviceName, - slackUrl, - threshold: this.state.threshold, - timeRange, - apmIndexPatternTitle: indexPatternTitle, - }).then((id: string) => { - this.props.onClose(); - this.addSuccessToast(id); - }); - }) - .catch((e) => { - // eslint-disable-next-line - console.error(e); - this.addErrorToast(); - }); - }; - - public addErrorToast = () => { - const { core } = this.context; - - core.notifications.toasts.addWarning({ - title: i18n.translate( - 'xpack.apm.serviceDetails.enableErrorReportsPanel.watchCreationFailedNotificationTitle', - { - defaultMessage: 'Watch creation failed', - } - ), - text: toMountPoint( -

- {i18n.translate( - 'xpack.apm.serviceDetails.enableErrorReportsPanel.watchCreationFailedNotificationText', - { - defaultMessage: - 'Make sure your user has permission to create watches.', - } - )} -

- ), - }); - }; - - public addSuccessToast = (id: string) => { - const { core } = this.context; - - core.notifications.toasts.addSuccess({ - title: i18n.translate( - 'xpack.apm.serviceDetails.enableErrorReportsPanel.watchCreatedNotificationTitle', - { - defaultMessage: 'New watch created!', - } - ), - text: toMountPoint( -

- {i18n.translate( - 'xpack.apm.serviceDetails.enableErrorReportsPanel.watchCreatedNotificationText', - { - defaultMessage: - 'The watch is now ready and will send error reports for {serviceName}.', - values: { - serviceName: this.props.urlParams.serviceName, - }, - } - )}{' '} - - - {i18n.translate( - 'xpack.apm.serviceDetails.enableErrorReportsPanel.watchCreatedNotificationText.viewWatchLinkText', - { - defaultMessage: 'View watch', - } - )} - - -

- ), - }); - }; - - public render() { - if (!this.props.isOpen) { - return null; - } - - const dailyTime = this.state.daily; - const inputTime = `${dailyTime}Z`; // Add tz to make into UTC - const inputFormat = 'HH:mmZ'; // Parse as 24 hour w. tz - const dailyTimeFormatted = moment(inputTime, inputFormat).format('HH:mm'); // Format as 24h - const dailyTime12HourFormatted = moment(inputTime, inputFormat).format( - 'hh:mm A (z)' - ); // Format as 12h w. tz - - // Generate UTC hours for Daily Report select field - const intervalHours = range(24).map((i) => { - const hour = padStart(i.toString(), 2, '0'); - return { value: `${hour}:00`, text: `${hour}:00 UTC` }; - }); - - const flyoutBody = ( - -

- - {i18n.translate( - 'xpack.apm.serviceDetails.enableErrorReportsPanel.formDescription.documentationLinkText', - { - defaultMessage: 'documentation', - } - )} - - ), - }} - /> -

- - -

- {i18n.translate( - 'xpack.apm.serviceDetails.enableErrorReportsPanel.conditionTitle', - { - defaultMessage: 'Condition', - } - )} -

- - - - -

- {i18n.translate( - 'xpack.apm.serviceDetails.enableErrorReportsPanel.triggerScheduleTitle', - { - defaultMessage: 'Trigger schedule', - } - )} -

- - {i18n.translate( - 'xpack.apm.serviceDetails.enableErrorReportsPanel.triggerScheduleDescription', - { - defaultMessage: - 'Choose the time interval for the report, when the threshold is exceeded.', - } - )} - - - this.onChangeSchedule('daily')} - checked={this.state.schedule === 'daily'} - /> - - - - - - this.onChangeSchedule('interval')} - checked={this.state.schedule === 'interval'} - /> - - - - - - - - - - - - - - - -

- {i18n.translate( - 'xpack.apm.serviceDetails.enableErrorReportsPanel.actionsTitle', - { - defaultMessage: 'Actions', - } - )} -

- - {i18n.translate( - 'xpack.apm.serviceDetails.enableErrorReportsPanel.actionsDescription', - { - defaultMessage: - 'Reports can be sent by email or posted to a Slack channel. Each report will include the top 10 errors sorted by occurrence.', - } - )} - - - this.onChangeAction('email')} - /> - - {this.state.actions.email && ( - - - {i18n.translate( - 'xpack.apm.serviceDetails.enableErrorReportsPanel.recipientsHelpText.documentationLinkText', - { - defaultMessage: 'documentation', - } - )} - - ), - }} - /> - - } - > - - - )} - - this.onChangeAction('slack')} - /> - - {this.state.actions.slack && ( - - - {i18n.translate( - 'xpack.apm.serviceDetails.enableErrorReportsPanel.slackWebhookURLHelpText.documentationLinkText', - { - defaultMessage: 'documentation', - } - )} - - ), - }} - /> - - } - > - - - )} -
-
- ); - - return ( - - - -

- {i18n.translate( - 'xpack.apm.serviceDetails.enableErrorReportsPanel.enableErrorReportsTitle', - { - defaultMessage: 'Enable error reports', - } - )} -

-
-
- {flyoutBody} - - - - this.createWatch()} - fill - disabled={ - !this.state.actions.email && !this.state.actions.slack - } - > - {i18n.translate( - 'xpack.apm.serviceDetails.enableErrorReportsPanel.createWatchButtonLabel', - { - defaultMessage: 'Create watch', - } - )} - - - - -
- ); - } -} diff --git a/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/__test__/__snapshots__/createErrorGroupWatch.test.ts.snap b/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/__test__/__snapshots__/createErrorGroupWatch.test.ts.snap deleted file mode 100644 index 88f254747c686e..00000000000000 --- a/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/__test__/__snapshots__/createErrorGroupWatch.test.ts.snap +++ /dev/null @@ -1,169 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`createErrorGroupWatch should format email correctly 1`] = ` -"Your service \\"opbeans-node\\" has error groups which exceeds 10 occurrences within \\"24h\\" - - -this is a string -N/A -7761 occurrences - -foo - (server/coffee.js) -7752 occurrences - -socket hang up -createHangUpError (_http_client.js) -3887 occurrences - -this will not get captured by express - (server/coffee.js) -3886 occurrences -" -`; - -exports[`createErrorGroupWatch should format slack message correctly 1`] = ` -"Your service \\"opbeans-node\\" has error groups which exceeds 10 occurrences within \\"24h\\" - ->*this is a string* ->N/A ->7761 occurrences - ->*foo* ->\` (server/coffee.js)\` ->7752 occurrences - ->*socket hang up* ->\`createHangUpError (_http_client.js)\` ->3887 occurrences - ->*this will not get captured by express* ->\` (server/coffee.js)\` ->3886 occurrences -" -`; - -exports[`createErrorGroupWatch should format template correctly 1`] = ` -Object { - "actions": Object { - "email": Object { - "email": Object { - "body": Object { - "html": "Your service \\"opbeans-node\\" has error groups which exceeds 10 occurrences within \\"24h\\"


this is a string
N/A
7761 occurrences

foo
(server/coffee.js)
7752 occurrences

socket hang up
createHangUpError (_http_client.js)
3887 occurrences

this will not get captured by express
(server/coffee.js)
3886 occurrences
", - }, - "subject": "\\"opbeans-node\\" has error groups which exceeds the threshold", - "to": "my@email.dk,mySecond@email.dk", - }, - }, - "log_error": Object { - "logging": Object { - "text": "Your service \\"opbeans-node\\" has error groups which exceeds 10 occurrences within \\"24h\\"


this is a string
N/A
7761 occurrences

foo
(server/coffee.js)
7752 occurrences

socket hang up
createHangUpError (_http_client.js)
3887 occurrences

this will not get captured by express
(server/coffee.js)
3886 occurrences
", - }, - }, - "slack_webhook": Object { - "webhook": Object { - "body": "__json__::{\\"text\\":\\"Your service \\\\\\"opbeans-node\\\\\\" has error groups which exceeds 10 occurrences within \\\\\\"24h\\\\\\"\\\\n\\\\n>*this is a string*\\\\n>N/A\\\\n>7761 occurrences\\\\n\\\\n>*foo*\\\\n>\` (server/coffee.js)\`\\\\n>7752 occurrences\\\\n\\\\n>*socket hang up*\\\\n>\`createHangUpError (_http_client.js)\`\\\\n>3887 occurrences\\\\n\\\\n>*this will not get captured by express*\\\\n>\` (server/coffee.js)\`\\\\n>3886 occurrences\\\\n\\"}", - "headers": Object { - "Content-Type": "application/json", - }, - "host": "hooks.slack.com", - "method": "POST", - "path": "/services/slackid1/slackid2/slackid3", - "port": 443, - "scheme": "https", - }, - }, - }, - "condition": Object { - "script": Object { - "source": "return ctx.payload.aggregations.error_groups.buckets.length > 0", - }, - }, - "input": Object { - "search": Object { - "request": Object { - "body": Object { - "aggs": Object { - "error_groups": Object { - "aggs": Object { - "sample": Object { - "top_hits": Object { - "_source": Array [ - "error.log.message", - "error.exception.message", - "error.exception.handled", - "error.culprit", - "error.grouping_key", - "@timestamp", - ], - "size": 1, - "sort": Array [ - Object { - "@timestamp": "desc", - }, - ], - }, - }, - }, - "terms": Object { - "field": "error.grouping_key", - "min_doc_count": "10", - "order": Object { - "_count": "desc", - }, - "size": 10, - }, - }, - }, - "query": Object { - "bool": Object { - "filter": Array [ - Object { - "term": Object { - "service.name": "opbeans-node", - }, - }, - Object { - "term": Object { - "processor.event": "error", - }, - }, - Object { - "range": Object { - "@timestamp": Object { - "gte": "now-24h", - }, - }, - }, - ], - }, - }, - "size": 0, - }, - "indices": Array [ - "myIndexPattern", - ], - }, - }, - }, - "metadata": Object { - "emails": Array [ - "my@email.dk", - "mySecond@email.dk", - ], - "serviceName": "opbeans-node", - "slackUrlPath": "/services/slackid1/slackid2/slackid3", - "threshold": 10, - "timeRangeUnit": "h", - "timeRangeValue": 24, - "trigger": "This value must be changed in trigger section", - }, - "trigger": Object { - "schedule": Object { - "daily": Object { - "at": "08:00", - }, - }, - }, -} -`; diff --git a/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/__test__/createErrorGroupWatch.test.ts b/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/__test__/createErrorGroupWatch.test.ts deleted file mode 100644 index 054476af28de1c..00000000000000 --- a/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/__test__/createErrorGroupWatch.test.ts +++ /dev/null @@ -1,120 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { isArray, isObject, isString } from 'lodash'; -import mustache from 'mustache'; -import uuid from 'uuid'; -import * as rest from '../../../../../services/rest/watcher'; -import { createErrorGroupWatch } from '../createErrorGroupWatch'; -import { esResponse } from './esResponse'; -import { HttpSetup } from 'kibana/public'; - -// disable html escaping since this is also disabled in watcher\s mustache implementation -mustache.escape = (value) => value; - -jest.mock('../../../../../services/rest/callApi', () => ({ - callApi: () => Promise.resolve(null), -})); - -describe('createErrorGroupWatch', () => { - let createWatchResponse: string; - let tmpl: any; - const createWatchSpy = jest - .spyOn(rest, 'createWatch') - .mockResolvedValue(undefined); - - beforeEach(async () => { - jest.spyOn(uuid, 'v4').mockReturnValue(Buffer.from('mocked-uuid')); - - createWatchResponse = await createErrorGroupWatch({ - http: {} as HttpSetup, - emails: ['my@email.dk', 'mySecond@email.dk'], - schedule: { - daily: { - at: '08:00', - }, - }, - serviceName: 'opbeans-node', - slackUrl: 'https://hooks.slack.com/services/slackid1/slackid2/slackid3', - threshold: 10, - timeRange: { value: 24, unit: 'h' }, - apmIndexPatternTitle: 'myIndexPattern', - }); - - const watchBody = createWatchSpy.mock.calls[0][0].watch; - const templateCtx = { - payload: esResponse, - metadata: watchBody.metadata, - }; - - tmpl = renderMustache(createWatchSpy.mock.calls[0][0].watch, templateCtx); - }); - - afterEach(() => jest.restoreAllMocks()); - - it('should call createWatch with correct args', () => { - expect(createWatchSpy.mock.calls[0][0].id).toBe('apm-mocked-uuid'); - }); - - it('should format slack message correctly', () => { - expect(tmpl.actions.slack_webhook.webhook.path).toBe( - '/services/slackid1/slackid2/slackid3' - ); - - expect( - JSON.parse(tmpl.actions.slack_webhook.webhook.body.slice(10)).text - ).toMatchSnapshot(); - }); - - it('should format email correctly', () => { - expect(tmpl.actions.email.email.to).toEqual( - 'my@email.dk,mySecond@email.dk' - ); - expect(tmpl.actions.email.email.subject).toBe( - '"opbeans-node" has error groups which exceeds the threshold' - ); - expect( - tmpl.actions.email.email.body.html.replace(//g, '\n') - ).toMatchSnapshot(); - }); - - it('should format template correctly', () => { - expect(tmpl).toMatchSnapshot(); - }); - - it('should return watch id', async () => { - const id = createWatchSpy.mock.calls[0][0].id; - expect(createWatchResponse).toEqual(id); - }); -}); - -// Recursively iterate a nested structure and render strings as mustache templates -type InputOutput = string | string[] | Record; -function renderMustache( - input: InputOutput, - ctx: Record -): InputOutput { - if (isString(input)) { - return mustache.render(input, { - ctx, - join: () => (text: string, render: any) => render(`{{${text}}}`, { ctx }), - }); - } - - if (isArray(input)) { - return input.map((itemValue) => renderMustache(itemValue, ctx)); - } - - if (isObject(input)) { - return Object.keys(input).reduce((acc, key) => { - const value = (input as any)[key]; - - return { ...acc, [key]: renderMustache(value, ctx) }; - }, {}); - } - - return input; -} diff --git a/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/__test__/esResponse.ts b/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/__test__/esResponse.ts deleted file mode 100644 index e17cb54b52b5c9..00000000000000 --- a/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/__test__/esResponse.ts +++ /dev/null @@ -1,149 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -export const esResponse = { - took: 454, - timed_out: false, - _shards: { - total: 10, - successful: 10, - skipped: 0, - failed: 0, - }, - hits: { - total: 23287, - max_score: 0, - hits: [], - }, - aggregations: { - error_groups: { - doc_count_error_upper_bound: 0, - sum_other_doc_count: 0, - buckets: [ - { - key: '63925d00b445cdf4b532dd09d185f5c6', - doc_count: 7761, - sample: { - hits: { - total: 7761, - max_score: null, - hits: [ - { - _index: 'apm-7.0.0-alpha1-error-2018.04.25', - _id: 'qH7C_WIBcmGuKeCHJvvT', - _score: null, - _source: { - '@timestamp': '2018-04-25T17:03:02.296Z', - error: { - log: { - message: 'this is a string', - }, - grouping_key: '63925d00b445cdf4b532dd09d185f5c6', - }, - }, - sort: [1524675782296], - }, - ], - }, - }, - }, - { - key: '89bb1a1f644c7f4bbe8d1781b5cb5fd5', - doc_count: 7752, - sample: { - hits: { - total: 7752, - max_score: null, - hits: [ - { - _index: 'apm-7.0.0-alpha1-error-2018.04.25', - _id: '_3_D_WIBcmGuKeCHFwOW', - _score: null, - _source: { - '@timestamp': '2018-04-25T17:04:03.504Z', - error: { - exception: [ - { - handled: true, - message: 'foo', - }, - ], - culprit: ' (server/coffee.js)', - grouping_key: '89bb1a1f644c7f4bbe8d1781b5cb5fd5', - }, - }, - sort: [1524675843504], - }, - ], - }, - }, - }, - { - key: '7a17ea60604e3531bd8de58645b8631f', - doc_count: 3887, - sample: { - hits: { - total: 3887, - max_score: null, - hits: [ - { - _index: 'apm-7.0.0-alpha1-error-2018.04.25', - _id: 'dn_D_WIBcmGuKeCHQgXJ', - _score: null, - _source: { - '@timestamp': '2018-04-25T17:04:14.575Z', - error: { - exception: [ - { - handled: false, - message: 'socket hang up', - }, - ], - culprit: 'createHangUpError (_http_client.js)', - grouping_key: '7a17ea60604e3531bd8de58645b8631f', - }, - }, - sort: [1524675854575], - }, - ], - }, - }, - }, - { - key: 'b9e1027f29c221763f864f6fa2ad9f5e', - doc_count: 3886, - sample: { - hits: { - total: 3886, - max_score: null, - hits: [ - { - _index: 'apm-7.0.0-alpha1-error-2018.04.25', - _id: 'dX_D_WIBcmGuKeCHQgXJ', - _score: null, - _source: { - '@timestamp': '2018-04-25T17:04:14.533Z', - error: { - exception: [ - { - handled: false, - message: 'this will not get captured by express', - }, - ], - culprit: ' (server/coffee.js)', - grouping_key: 'b9e1027f29c221763f864f6fa2ad9f5e', - }, - }, - sort: [1524675854533], - }, - ], - }, - }, - }, - ], - }, - }, -}; diff --git a/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/createErrorGroupWatch.ts b/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/createErrorGroupWatch.ts deleted file mode 100644 index 151c4abb9fce33..00000000000000 --- a/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/createErrorGroupWatch.ts +++ /dev/null @@ -1,261 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { i18n } from '@kbn/i18n'; -import { isEmpty } from 'lodash'; -import url from 'url'; -import uuid from 'uuid'; -import { HttpSetup } from 'kibana/public'; -import { - ERROR_CULPRIT, - ERROR_EXC_HANDLED, - ERROR_EXC_MESSAGE, - ERROR_GROUP_ID, - ERROR_LOG_MESSAGE, - PROCESSOR_EVENT, - SERVICE_NAME, -} from '../../../../../common/elasticsearch_fieldnames'; -import { createWatch } from '../../../../services/rest/watcher'; - -function getSlackPathUrl(slackUrl?: string) { - if (slackUrl) { - const { path } = url.parse(slackUrl); - return path; - } -} - -export interface Schedule { - interval?: string; - daily?: { - at: string; - }; -} - -interface Arguments { - http: HttpSetup; - emails: string[]; - schedule: Schedule; - serviceName: string; - slackUrl?: string; - threshold: number; - timeRange: { - value: number; - unit: string; - }; - apmIndexPatternTitle: string; -} - -interface Actions { - log_error: { logging: { text: string } }; - slack_webhook?: Record; - email?: Record; -} - -export async function createErrorGroupWatch({ - http, - emails = [], - schedule, - serviceName, - slackUrl, - threshold, - timeRange, - apmIndexPatternTitle, -}: Arguments) { - const id = `apm-${uuid.v4()}`; - - const slackUrlPath = getSlackPathUrl(slackUrl); - const emailTemplate = i18n.translate( - 'xpack.apm.serviceDetails.enableErrorReportsPanel.emailTemplateText', - { - defaultMessage: - 'Your service {serviceName} has error groups which exceeds {threshold} occurrences within {timeRange}{br}' + - '{br}' + - '{errorGroupsBuckets}{br}' + - '{errorLogMessage}{br}' + - '{errorCulprit}N/A{slashErrorCulprit}{br}' + - '{docCountParam} occurrences{br}' + - '{slashErrorGroupsBucket}', - values: { - serviceName: '"{{ctx.metadata.serviceName}}"', - threshold: '{{ctx.metadata.threshold}}', - timeRange: - '"{{ctx.metadata.timeRangeValue}}{{ctx.metadata.timeRangeUnit}}"', - errorGroupsBuckets: - '{{#ctx.payload.aggregations.error_groups.buckets}}', - errorLogMessage: - '{{sample.hits.hits.0._source.error.log.message}}{{^sample.hits.hits.0._source.error.log.message}}{{sample.hits.hits.0._source.error.exception.0.message}}{{/sample.hits.hits.0._source.error.log.message}}', - errorCulprit: - '{{sample.hits.hits.0._source.error.culprit}}{{^sample.hits.hits.0._source.error.culprit}}', - slashErrorCulprit: '{{/sample.hits.hits.0._source.error.culprit}}', - docCountParam: '{{doc_count}}', - slashErrorGroupsBucket: - '{{/ctx.payload.aggregations.error_groups.buckets}}', - br: '
', - }, - } - ); - - const slackTemplate = i18n.translate( - 'xpack.apm.serviceDetails.enableErrorReportsPanel.slackTemplateText', - { - defaultMessage: `Your service {serviceName} has error groups which exceeds {threshold} occurrences within {timeRange} -{errorGroupsBuckets} -{errorLogMessage} -{errorCulprit}N/A{slashErrorCulprit} -{docCountParam} occurrences -{slashErrorGroupsBucket}`, - values: { - serviceName: '"{{ctx.metadata.serviceName}}"', - threshold: '{{ctx.metadata.threshold}}', - timeRange: - '"{{ctx.metadata.timeRangeValue}}{{ctx.metadata.timeRangeUnit}}"', - errorGroupsBuckets: - '{{#ctx.payload.aggregations.error_groups.buckets}}', - errorLogMessage: - '>*{{sample.hits.hits.0._source.error.log.message}}{{^sample.hits.hits.0._source.error.log.message}}{{sample.hits.hits.0._source.error.exception.0.message}}{{/sample.hits.hits.0._source.error.log.message}}*', - errorCulprit: - '>{{#sample.hits.hits.0._source.error.culprit}}`{{sample.hits.hits.0._source.error.culprit}}`{{/sample.hits.hits.0._source.error.culprit}}{{^sample.hits.hits.0._source.error.culprit}}', - slashErrorCulprit: '{{/sample.hits.hits.0._source.error.culprit}}', - docCountParam: '>{{doc_count}}', - slashErrorGroupsBucket: - '{{/ctx.payload.aggregations.error_groups.buckets}}', - }, - } - ); - - const actions: Actions = { - log_error: { logging: { text: emailTemplate } }, - }; - - const body = { - metadata: { - emails, - trigger: i18n.translate( - 'xpack.apm.serviceDetails.enableErrorReportsPanel.triggerText', - { - defaultMessage: 'This value must be changed in trigger section', - } - ), - serviceName, - threshold, - timeRangeValue: timeRange.value, - timeRangeUnit: timeRange.unit, - slackUrlPath, - }, - trigger: { - schedule, - }, - input: { - search: { - request: { - indices: [apmIndexPatternTitle], - body: { - size: 0, - query: { - bool: { - filter: [ - { term: { [SERVICE_NAME]: '{{ctx.metadata.serviceName}}' } }, - { term: { [PROCESSOR_EVENT]: 'error' } }, - { - range: { - '@timestamp': { - gte: - 'now-{{ctx.metadata.timeRangeValue}}{{ctx.metadata.timeRangeUnit}}', - }, - }, - }, - ], - }, - }, - aggs: { - error_groups: { - terms: { - min_doc_count: '{{ctx.metadata.threshold}}', - field: ERROR_GROUP_ID, - size: 10, - order: { - _count: 'desc', - }, - }, - aggs: { - sample: { - top_hits: { - _source: [ - ERROR_LOG_MESSAGE, - ERROR_EXC_MESSAGE, - ERROR_EXC_HANDLED, - ERROR_CULPRIT, - ERROR_GROUP_ID, - '@timestamp', - ], - sort: [ - { - '@timestamp': 'desc', - }, - ], - size: 1, - }, - }, - }, - }, - }, - }, - }, - }, - }, - condition: { - script: { - source: - 'return ctx.payload.aggregations.error_groups.buckets.length > 0', - }, - }, - actions, - }; - - if (slackUrlPath) { - body.actions.slack_webhook = { - webhook: { - scheme: 'https', - host: 'hooks.slack.com', - port: 443, - method: 'POST', - path: '{{ctx.metadata.slackUrlPath}}', - headers: { - 'Content-Type': 'application/json', - }, - body: `__json__::${JSON.stringify({ - text: slackTemplate, - })}`, - }, - }; - } - - if (!isEmpty(emails)) { - body.actions.email = { - email: { - to: '{{#join}}ctx.metadata.emails{{/join}}', - subject: i18n.translate( - 'xpack.apm.serviceDetails.enableErrorReportsPanel.emailSubjectText', - { - defaultMessage: - '{serviceName} has error groups which exceeds the threshold', - values: { serviceName: '"{{ctx.metadata.serviceName}}"' }, - } - ), - body: { - html: emailTemplate, - }, - }, - }; - } - - await createWatch({ - http, - id, - watch: body, - }); - return id; -} diff --git a/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/index.tsx b/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/index.tsx deleted file mode 100644 index 0a7dcbd0be3dfc..00000000000000 --- a/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/index.tsx +++ /dev/null @@ -1,122 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { EuiButtonEmpty, EuiContextMenu, EuiPopover } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import React from 'react'; -import { IUrlParams } from '../../../../context/UrlParamsContext/types'; -import { WatcherFlyout } from './WatcherFlyout'; -import { ApmPluginContext } from '../../../../context/ApmPluginContext'; - -interface Props { - urlParams: IUrlParams; -} -interface State { - isPopoverOpen: boolean; - activeFlyout: FlyoutName; -} -type FlyoutName = null | 'Watcher'; - -export class ServiceIntegrations extends React.Component { - static contextType = ApmPluginContext; - context!: React.ContextType; - - public state: State = { isPopoverOpen: false, activeFlyout: null }; - - public getWatcherPanelItems = () => { - const { core } = this.context; - - return [ - { - name: i18n.translate( - 'xpack.apm.serviceDetails.integrationsMenu.enableWatcherErrorReportsButtonLabel', - { - defaultMessage: 'Enable watcher error reports', - } - ), - icon: 'watchesApp', - onClick: () => { - this.closePopover(); - this.openFlyout('Watcher'); - }, - }, - { - name: i18n.translate( - 'xpack.apm.serviceDetails.integrationsMenu.viewWatchesButtonLabel', - { - defaultMessage: 'View existing watches', - } - ), - icon: 'watchesApp', - href: core.http.basePath.prepend( - '/app/management/insightsAndAlerting/watcher' - ), - target: '_blank', - onClick: () => this.closePopover(), - }, - ]; - }; - - public openPopover = () => - this.setState({ - isPopoverOpen: true, - }); - - public closePopover = () => - this.setState({ - isPopoverOpen: false, - }); - - public openFlyout = (name: FlyoutName) => - this.setState({ activeFlyout: name }); - - public closeFlyouts = () => this.setState({ activeFlyout: null }); - - public render() { - const button = ( - - {i18n.translate( - 'xpack.apm.serviceDetails.integrationsMenu.integrationsButtonLabel', - { - defaultMessage: 'Integrations', - } - )} - - ); - - return ( - <> - - - - - - ); - } -} diff --git a/x-pack/plugins/apm/public/components/app/ServiceDetails/index.tsx b/x-pack/plugins/apm/public/components/app/ServiceDetails/index.tsx index 2d52ad88d20dca..4488a962d0ba88 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceDetails/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceDetails/index.tsx @@ -14,7 +14,6 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { ApmHeader } from '../../shared/ApmHeader'; import { ServiceDetailTabs } from './ServiceDetailTabs'; -import { ServiceIntegrations } from './ServiceIntegrations'; import { useUrlParams } from '../../../hooks/useUrlParams'; import { AlertIntegrations } from './AlertIntegrations'; import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; @@ -54,9 +53,6 @@ export function ServiceDetails({ tab }: Props) {

{serviceName}

- - - {isAlertingAvailable && ( +>; +export type CreateEndpointListItemSchema = RequiredKeepUndefined< + t.TypeOf +>; + +// This type is used after a decode since some things are defaults after a decode. +export type CreateEndpointListItemSchemaDecoded = Identity< + Omit & { + _tags: _Tags; + comments: CreateCommentsArray; + tags: Tags; + item_id: ItemId; + entries: EntriesArray; + } +>; diff --git a/x-pack/plugins/lists/common/schemas/request/delete_endpoint_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/delete_endpoint_list_item_schema.ts new file mode 100644 index 00000000000000..311af3a4c04377 --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/request/delete_endpoint_list_item_schema.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; + * you may not use this file except in compliance with the Elastic License. + */ + +/* eslint-disable @typescript-eslint/camelcase */ + +import * as t from 'io-ts'; + +import { id, item_id } from '../common/schemas'; + +export const deleteEndpointListItemSchema = t.exact( + t.partial({ + id, + item_id, + }) +); + +export type DeleteEndpointListItemSchema = t.TypeOf; + +// This type is used after a decode since some things are defaults after a decode. +export type DeleteEndpointListItemSchemaDecoded = DeleteEndpointListItemSchema; diff --git a/x-pack/plugins/lists/common/schemas/request/find_endpoint_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/find_endpoint_list_item_schema.ts new file mode 100644 index 00000000000000..c9ee46994d7201 --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/request/find_endpoint_list_item_schema.ts @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +/* eslint-disable @typescript-eslint/camelcase */ + +import * as t from 'io-ts'; + +import { filter, sort_field, sort_order } from '../common/schemas'; +import { RequiredKeepUndefined } from '../../types'; +import { StringToPositiveNumber } from '../types/string_to_positive_number'; + +export const findEndpointListItemSchema = t.exact( + t.partial({ + filter, // defaults to undefined if not set during decode + page: StringToPositiveNumber, // defaults to undefined if not set during decode + per_page: StringToPositiveNumber, // defaults to undefined if not set during decode + sort_field, // defaults to undefined if not set during decode + sort_order, // defaults to undefined if not set during decode + }) +); + +export type FindEndpointListItemSchemaPartial = t.OutputOf; + +// This type is used after a decode since some things are defaults after a decode. +export type FindEndpointListItemSchemaPartialDecoded = t.TypeOf; + +// This type is used after a decode since some things are defaults after a decode. +export type FindEndpointListItemSchemaDecoded = RequiredKeepUndefined< + FindEndpointListItemSchemaPartialDecoded +>; + +export type FindEndpointListItemSchema = RequiredKeepUndefined< + t.TypeOf +>; diff --git a/x-pack/plugins/lists/common/schemas/request/find_exception_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/find_exception_list_item_schema.ts index 826da972fe7a37..aa53fa0fd912ce 100644 --- a/x-pack/plugins/lists/common/schemas/request/find_exception_list_item_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/find_exception_list_item_schema.ts @@ -26,7 +26,7 @@ export const findExceptionListItemSchema = t.intersection([ ), t.exact( t.partial({ - filter: EmptyStringArray, // defaults to undefined if not set during decode + filter: EmptyStringArray, // defaults to an empty array [] if not set during decode namespace_type: DefaultNamespaceArray, // defaults to ['single'] if not set during decode page: StringToPositiveNumber, // defaults to undefined if not set during decode per_page: StringToPositiveNumber, // defaults to undefined if not set during decode diff --git a/x-pack/plugins/lists/common/schemas/request/index.ts b/x-pack/plugins/lists/common/schemas/request/index.ts index 7ab3d943f14daf..172d73a5c7377a 100644 --- a/x-pack/plugins/lists/common/schemas/request/index.ts +++ b/x-pack/plugins/lists/common/schemas/request/index.ts @@ -4,15 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ +export * from './create_endpoint_list_item_schema'; export * from './create_exception_list_item_schema'; export * from './create_exception_list_schema'; export * from './create_list_item_schema'; export * from './create_list_schema'; +export * from './delete_endpoint_list_item_schema'; export * from './delete_exception_list_item_schema'; export * from './delete_exception_list_schema'; export * from './delete_list_item_schema'; export * from './delete_list_schema'; export * from './export_list_item_query_schema'; +export * from './find_endpoint_list_item_schema'; export * from './find_exception_list_item_schema'; export * from './find_exception_list_schema'; export * from './find_list_item_schema'; @@ -20,10 +23,12 @@ export * from './find_list_schema'; export * from './import_list_item_schema'; export * from './patch_list_item_schema'; export * from './patch_list_schema'; -export * from './read_exception_list_item_schema'; +export * from './read_endpoint_list_item_schema'; export * from './read_exception_list_schema'; +export * from './read_exception_list_item_schema'; export * from './read_list_item_schema'; export * from './read_list_schema'; +export * from './update_endpoint_list_item_schema'; export * from './update_exception_list_item_schema'; export * from './update_exception_list_schema'; export * from './import_list_item_query_schema'; diff --git a/x-pack/plugins/lists/common/schemas/request/read_endpoint_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/read_endpoint_list_item_schema.ts new file mode 100644 index 00000000000000..22750f5db6a1d3 --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/request/read_endpoint_list_item_schema.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* eslint-disable @typescript-eslint/camelcase */ + +import * as t from 'io-ts'; + +import { id, item_id } from '../common/schemas'; +import { RequiredKeepUndefined } from '../../types'; + +export const readEndpointListItemSchema = t.exact( + t.partial({ + id, + item_id, + }) +); + +export type ReadEndpointListItemSchemaPartial = t.TypeOf; + +// This type is used after a decode since some things are defaults after a decode. +export type ReadEndpointListItemSchemaPartialDecoded = ReadEndpointListItemSchemaPartial; + +// This type is used after a decode since some things are defaults after a decode. +export type ReadEndpointListItemSchemaDecoded = RequiredKeepUndefined< + ReadEndpointListItemSchemaPartialDecoded +>; + +export type ReadEndpointListItemSchema = RequiredKeepUndefined; diff --git a/x-pack/plugins/lists/common/schemas/request/update_endpoint_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/update_endpoint_list_item_schema.ts new file mode 100644 index 00000000000000..dbe38f6d468e2a --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/request/update_endpoint_list_item_schema.ts @@ -0,0 +1,66 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* eslint-disable @typescript-eslint/camelcase */ + +import * as t from 'io-ts'; + +import { + Tags, + _Tags, + _tags, + description, + exceptionListItemType, + id, + meta, + name, + tags, +} from '../common/schemas'; +import { Identity, RequiredKeepUndefined } from '../../types'; +import { + DefaultEntryArray, + DefaultUpdateCommentsArray, + EntriesArray, + UpdateCommentsArray, +} from '../types'; + +export const updateEndpointListItemSchema = t.intersection([ + t.exact( + t.type({ + description, + name, + type: exceptionListItemType, + }) + ), + t.exact( + t.partial({ + _tags, // defaults to empty array if not set during decode + comments: DefaultUpdateCommentsArray, // defaults to empty array if not set during decode + entries: DefaultEntryArray, // defaults to empty array if not set during decode + id, // defaults to undefined if not set during decode + item_id: t.union([t.string, t.undefined]), + meta, // defaults to undefined if not set during decode + tags, // defaults to empty array if not set during decode + }) + ), +]); + +export type UpdateEndpointListItemSchemaPartial = Identity< + t.TypeOf +>; +export type UpdateEndpointListItemSchema = RequiredKeepUndefined< + t.TypeOf +>; + +// This type is used after a decode since some things are defaults after a decode. +export type UpdateEndpointListItemSchemaDecoded = Identity< + Omit & { + _tags: _Tags; + comments: UpdateCommentsArray; + tags: Tags; + entries: EntriesArray; + } +>; diff --git a/x-pack/plugins/lists/server/routes/create_endpoint_list_item_route.ts b/x-pack/plugins/lists/server/routes/create_endpoint_list_item_route.ts new file mode 100644 index 00000000000000..b6eacc3b7dd04c --- /dev/null +++ b/x-pack/plugins/lists/server/routes/create_endpoint_list_item_route.ts @@ -0,0 +1,86 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IRouter } from 'kibana/server'; + +import { ENDPOINT_LIST_ITEM_URL } from '../../common/constants'; +import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; +import { validate } from '../../common/siem_common_deps'; +import { + CreateEndpointListItemSchemaDecoded, + createEndpointListItemSchema, + exceptionListItemSchema, +} from '../../common/schemas'; + +import { getExceptionListClient } from './utils/get_exception_list_client'; + +export const createEndpointListItemRoute = (router: IRouter): void => { + router.post( + { + options: { + tags: ['access:lists'], + }, + path: ENDPOINT_LIST_ITEM_URL, + validate: { + body: buildRouteValidation< + typeof createEndpointListItemSchema, + CreateEndpointListItemSchemaDecoded + >(createEndpointListItemSchema), + }, + }, + async (context, request, response) => { + const siemResponse = buildSiemResponse(response); + try { + const { + name, + _tags, + tags, + meta, + comments, + description, + entries, + item_id: itemId, + type, + } = request.body; + const exceptionLists = getExceptionListClient(context); + const exceptionListItem = await exceptionLists.getEndpointListItem({ + id: undefined, + itemId, + }); + if (exceptionListItem != null) { + return siemResponse.error({ + body: `exception list item id: "${itemId}" already exists`, + statusCode: 409, + }); + } else { + const createdList = await exceptionLists.createEndpointListItem({ + _tags, + comments, + description, + entries, + itemId, + meta, + name, + tags, + type, + }); + const [validated, errors] = validate(createdList, exceptionListItemSchema); + if (errors != null) { + return siemResponse.error({ body: errors, statusCode: 500 }); + } else { + return response.ok({ body: validated ?? {} }); + } + } + } catch (err) { + const error = transformError(err); + return siemResponse.error({ + body: error.message, + statusCode: error.statusCode, + }); + } + } + ); +}; diff --git a/x-pack/plugins/lists/server/routes/create_endpoint_list_route.ts b/x-pack/plugins/lists/server/routes/create_endpoint_list_route.ts new file mode 100644 index 00000000000000..5d0f3599729b33 --- /dev/null +++ b/x-pack/plugins/lists/server/routes/create_endpoint_list_route.ts @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IRouter } from 'kibana/server'; +import * as t from 'io-ts'; + +import { ENDPOINT_LIST_URL } from '../../common/constants'; +import { buildSiemResponse, transformError } from '../siem_server_deps'; +import { validate } from '../../common/siem_common_deps'; +import { exceptionListSchema } from '../../common/schemas'; + +import { getExceptionListClient } from './utils/get_exception_list_client'; + +/** + * This creates the endpoint list if it does not exist. If it does exist, + * this will conflict but continue. This is intended to be as fast as possible so it tries + * each and every time it is called to create the endpoint_list and just ignores any + * conflict so at worse case only one round trip happens per API call. If any error other than conflict + * happens this will return that error. If the list already exists this will return an empty + * object. + * @param router The router to use. + */ +export const createEndpointListRoute = (router: IRouter): void => { + router.post( + { + options: { + tags: ['access:lists'], + }, + path: ENDPOINT_LIST_URL, + validate: false, + }, + async (context, _, response) => { + const siemResponse = buildSiemResponse(response); + try { + // Our goal is be fast as possible and block the least amount of + const exceptionLists = getExceptionListClient(context); + const createdList = await exceptionLists.createEndpointList(); + if (createdList != null) { + const [validated, errors] = validate(createdList, t.union([exceptionListSchema, t.null])); + if (errors != null) { + return siemResponse.error({ body: errors, statusCode: 500 }); + } else { + return response.ok({ body: validated ?? {} }); + } + } else { + // We always return ok on a create endpoint list route but with an empty body as + // an additional fetch of the full list would be slower and the UI has everything hard coded + // within it to get the list if it needs details about it. + return response.ok({ body: {} }); + } + } catch (err) { + const error = transformError(err); + return siemResponse.error({ + body: error.message, + statusCode: error.statusCode, + }); + } + } + ); +}; diff --git a/x-pack/plugins/lists/server/routes/delete_endpoint_list_item_route.ts b/x-pack/plugins/lists/server/routes/delete_endpoint_list_item_route.ts new file mode 100644 index 00000000000000..b8946c542b27e1 --- /dev/null +++ b/x-pack/plugins/lists/server/routes/delete_endpoint_list_item_route.ts @@ -0,0 +1,72 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IRouter } from 'kibana/server'; + +import { ENDPOINT_LIST_ITEM_URL } from '../../common/constants'; +import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; +import { validate } from '../../common/siem_common_deps'; +import { + DeleteEndpointListItemSchemaDecoded, + deleteEndpointListItemSchema, + exceptionListItemSchema, +} from '../../common/schemas'; + +import { getErrorMessageExceptionListItem, getExceptionListClient } from './utils'; + +export const deleteEndpointListItemRoute = (router: IRouter): void => { + router.delete( + { + options: { + tags: ['access:lists'], + }, + path: ENDPOINT_LIST_ITEM_URL, + validate: { + query: buildRouteValidation< + typeof deleteEndpointListItemSchema, + DeleteEndpointListItemSchemaDecoded + >(deleteEndpointListItemSchema), + }, + }, + async (context, request, response) => { + const siemResponse = buildSiemResponse(response); + try { + const exceptionLists = getExceptionListClient(context); + const { item_id: itemId, id } = request.query; + if (itemId == null && id == null) { + return siemResponse.error({ + body: 'Either "item_id" or "id" needs to be defined in the request', + statusCode: 400, + }); + } else { + const deleted = await exceptionLists.deleteEndpointListItem({ + id, + itemId, + }); + if (deleted == null) { + return siemResponse.error({ + body: getErrorMessageExceptionListItem({ id, itemId }), + statusCode: 404, + }); + } else { + const [validated, errors] = validate(deleted, exceptionListItemSchema); + if (errors != null) { + return siemResponse.error({ body: errors, statusCode: 500 }); + } else { + return response.ok({ body: validated ?? {} }); + } + } + } + } catch (err) { + const error = transformError(err); + return siemResponse.error({ + body: error.message, + statusCode: error.statusCode, + }); + } + } + ); +}; diff --git a/x-pack/plugins/lists/server/routes/find_endpoint_list_item_route.ts b/x-pack/plugins/lists/server/routes/find_endpoint_list_item_route.ts new file mode 100644 index 00000000000000..7374ff7dc92ea7 --- /dev/null +++ b/x-pack/plugins/lists/server/routes/find_endpoint_list_item_route.ts @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IRouter } from 'kibana/server'; + +import { ENDPOINT_LIST_ID, ENDPOINT_LIST_ITEM_URL } from '../../common/constants'; +import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; +import { validate } from '../../common/siem_common_deps'; +import { + FindEndpointListItemSchemaDecoded, + findEndpointListItemSchema, + foundExceptionListItemSchema, +} from '../../common/schemas'; + +import { getExceptionListClient } from './utils'; + +export const findEndpointListItemRoute = (router: IRouter): void => { + router.get( + { + options: { + tags: ['access:lists'], + }, + path: `${ENDPOINT_LIST_ITEM_URL}/_find`, + validate: { + query: buildRouteValidation< + typeof findEndpointListItemSchema, + FindEndpointListItemSchemaDecoded + >(findEndpointListItemSchema), + }, + }, + async (context, request, response) => { + const siemResponse = buildSiemResponse(response); + try { + const exceptionLists = getExceptionListClient(context); + const { + filter, + page, + per_page: perPage, + sort_field: sortField, + sort_order: sortOrder, + } = request.query; + + const exceptionListItems = await exceptionLists.findEndpointListItem({ + filter, + page, + perPage, + sortField, + sortOrder, + }); + if (exceptionListItems == null) { + // Although I have this line of code here, this is an incredibly rare thing to have + // happen as the findEndpointListItem tries to auto-create the endpoint list if + // does not exist. + return siemResponse.error({ + body: `list id: "${ENDPOINT_LIST_ID}" does not exist`, + statusCode: 404, + }); + } + const [validated, errors] = validate(exceptionListItems, foundExceptionListItemSchema); + if (errors != null) { + return siemResponse.error({ body: errors, statusCode: 500 }); + } else { + return response.ok({ body: validated ?? {} }); + } + } catch (err) { + const error = transformError(err); + return siemResponse.error({ + body: error.message, + statusCode: error.statusCode, + }); + } + } + ); +}; diff --git a/x-pack/plugins/lists/server/routes/index.ts b/x-pack/plugins/lists/server/routes/index.ts index 72117c46213fe0..0d99d726d232da 100644 --- a/x-pack/plugins/lists/server/routes/index.ts +++ b/x-pack/plugins/lists/server/routes/index.ts @@ -4,17 +4,21 @@ * you may not use this file except in compliance with the Elastic License. */ +export * from './create_endpoint_list_item_route'; +export * from './create_endpoint_list_route'; export * from './create_exception_list_item_route'; export * from './create_exception_list_route'; export * from './create_list_index_route'; export * from './create_list_item_route'; export * from './create_list_route'; +export * from './delete_endpoint_list_item_route'; export * from './delete_exception_list_route'; export * from './delete_exception_list_item_route'; export * from './delete_list_index_route'; export * from './delete_list_item_route'; export * from './delete_list_route'; export * from './export_list_item_route'; +export * from './find_endpoint_list_item_route'; export * from './find_exception_list_item_route'; export * from './find_exception_list_route'; export * from './find_list_item_route'; @@ -23,11 +27,14 @@ export * from './import_list_item_route'; export * from './init_routes'; export * from './patch_list_item_route'; export * from './patch_list_route'; +export * from './read_endpoint_list_item_route'; export * from './read_exception_list_item_route'; export * from './read_exception_list_route'; export * from './read_list_index_route'; export * from './read_list_item_route'; export * from './read_list_route'; +export * from './read_privileges_route'; +export * from './update_endpoint_list_item_route'; export * from './update_exception_list_item_route'; export * from './update_exception_list_route'; export * from './update_list_item_route'; diff --git a/x-pack/plugins/lists/server/routes/init_routes.ts b/x-pack/plugins/lists/server/routes/init_routes.ts index fef7f19f02df2d..7e9e956ebf0945 100644 --- a/x-pack/plugins/lists/server/routes/init_routes.ts +++ b/x-pack/plugins/lists/server/routes/init_routes.ts @@ -9,20 +9,22 @@ import { IRouter } from 'kibana/server'; import { SecurityPluginSetup } from '../../../security/server'; import { ConfigType } from '../config'; -import { readPrivilegesRoute } from './read_privileges_route'; - import { + createEndpointListItemRoute, + createEndpointListRoute, createExceptionListItemRoute, createExceptionListRoute, createListIndexRoute, createListItemRoute, createListRoute, + deleteEndpointListItemRoute, deleteExceptionListItemRoute, deleteExceptionListRoute, deleteListIndexRoute, deleteListItemRoute, deleteListRoute, exportListItemRoute, + findEndpointListItemRoute, findExceptionListItemRoute, findExceptionListRoute, findListItemRoute, @@ -30,11 +32,14 @@ import { importListItemRoute, patchListItemRoute, patchListRoute, + readEndpointListItemRoute, readExceptionListItemRoute, readExceptionListRoute, readListIndexRoute, readListItemRoute, readListRoute, + readPrivilegesRoute, + updateEndpointListItemRoute, updateExceptionListItemRoute, updateExceptionListRoute, updateListItemRoute, @@ -83,4 +88,14 @@ export const initRoutes = ( updateExceptionListItemRoute(router); deleteExceptionListItemRoute(router); findExceptionListItemRoute(router); + + // endpoint list + createEndpointListRoute(router); + + // endpoint list items + createEndpointListItemRoute(router); + readEndpointListItemRoute(router); + updateEndpointListItemRoute(router); + deleteEndpointListItemRoute(router); + findEndpointListItemRoute(router); }; diff --git a/x-pack/plugins/lists/server/routes/read_endpoint_list_item_route.ts b/x-pack/plugins/lists/server/routes/read_endpoint_list_item_route.ts new file mode 100644 index 00000000000000..5e7ed901bf0cb5 --- /dev/null +++ b/x-pack/plugins/lists/server/routes/read_endpoint_list_item_route.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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IRouter } from 'kibana/server'; + +import { ENDPOINT_LIST_ITEM_URL } from '../../common/constants'; +import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; +import { validate } from '../../common/siem_common_deps'; +import { + ReadEndpointListItemSchemaDecoded, + exceptionListItemSchema, + readEndpointListItemSchema, +} from '../../common/schemas'; + +import { getErrorMessageExceptionListItem, getExceptionListClient } from './utils'; + +export const readEndpointListItemRoute = (router: IRouter): void => { + router.get( + { + options: { + tags: ['access:lists'], + }, + path: ENDPOINT_LIST_ITEM_URL, + validate: { + query: buildRouteValidation< + typeof readEndpointListItemSchema, + ReadEndpointListItemSchemaDecoded + >(readEndpointListItemSchema), + }, + }, + async (context, request, response) => { + const siemResponse = buildSiemResponse(response); + try { + const { id, item_id: itemId } = request.query; + const exceptionLists = getExceptionListClient(context); + if (id != null || itemId != null) { + const exceptionListItem = await exceptionLists.getEndpointListItem({ + id, + itemId, + }); + if (exceptionListItem == null) { + return siemResponse.error({ + body: getErrorMessageExceptionListItem({ id, itemId }), + statusCode: 404, + }); + } else { + const [validated, errors] = validate(exceptionListItem, exceptionListItemSchema); + if (errors != null) { + return siemResponse.error({ body: errors, statusCode: 500 }); + } else { + return response.ok({ body: validated ?? {} }); + } + } + } else { + return siemResponse.error({ body: 'id or item_id required', statusCode: 400 }); + } + } catch (err) { + const error = transformError(err); + return siemResponse.error({ + body: error.message, + statusCode: error.statusCode, + }); + } + } + ); +}; diff --git a/x-pack/plugins/lists/server/routes/update_endpoint_list_item_route.ts b/x-pack/plugins/lists/server/routes/update_endpoint_list_item_route.ts new file mode 100644 index 00000000000000..1ecf4e8a9765de --- /dev/null +++ b/x-pack/plugins/lists/server/routes/update_endpoint_list_item_route.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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IRouter } from 'kibana/server'; + +import { ENDPOINT_LIST_ITEM_URL } from '../../common/constants'; +import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; +import { validate } from '../../common/siem_common_deps'; +import { + UpdateEndpointListItemSchemaDecoded, + exceptionListItemSchema, + updateEndpointListItemSchema, +} from '../../common/schemas'; + +import { getExceptionListClient } from '.'; + +export const updateEndpointListItemRoute = (router: IRouter): void => { + router.put( + { + options: { + tags: ['access:lists'], + }, + path: ENDPOINT_LIST_ITEM_URL, + validate: { + body: buildRouteValidation< + typeof updateEndpointListItemSchema, + UpdateEndpointListItemSchemaDecoded + >(updateEndpointListItemSchema), + }, + }, + async (context, request, response) => { + const siemResponse = buildSiemResponse(response); + try { + const { + description, + id, + name, + meta, + type, + _tags, + comments, + entries, + item_id: itemId, + tags, + } = request.body; + const exceptionLists = getExceptionListClient(context); + const exceptionListItem = await exceptionLists.updateEndpointListItem({ + _tags, + comments, + description, + entries, + id, + itemId, + meta, + name, + tags, + type, + }); + if (exceptionListItem == null) { + if (id != null) { + return siemResponse.error({ + body: `list item id: "${id}" not found`, + statusCode: 404, + }); + } else { + return siemResponse.error({ + body: `list item item_id: "${itemId}" not found`, + statusCode: 404, + }); + } + } else { + const [validated, errors] = validate(exceptionListItem, exceptionListItemSchema); + if (errors != null) { + return siemResponse.error({ body: errors, statusCode: 500 }); + } else { + return response.ok({ body: validated ?? {} }); + } + } + } catch (err) { + const error = transformError(err); + return siemResponse.error({ + body: error.message, + statusCode: error.statusCode, + }); + } + } + ); +}; diff --git a/x-pack/plugins/lists/server/routes/update_exception_list_item_route.ts b/x-pack/plugins/lists/server/routes/update_exception_list_item_route.ts index 0ec33b7651982d..f6c7bcebedc135 100644 --- a/x-pack/plugins/lists/server/routes/update_exception_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/update_exception_list_item_route.ts @@ -62,10 +62,17 @@ export const updateExceptionListItemRoute = (router: IRouter): void => { type, }); if (exceptionListItem == null) { - return siemResponse.error({ - body: `list item id: "${id}" not found`, - statusCode: 404, - }); + if (id != null) { + return siemResponse.error({ + body: `list item id: "${id}" not found`, + statusCode: 404, + }); + } else { + return siemResponse.error({ + body: `list item item_id: "${itemId}" not found`, + statusCode: 404, + }); + } } else { const [validated, errors] = validate(exceptionListItem, exceptionListItemSchema); if (errors != null) { diff --git a/x-pack/plugins/lists/server/scripts/delete_endpoint_list_item.sh b/x-pack/plugins/lists/server/scripts/delete_endpoint_list_item.sh new file mode 100755 index 00000000000000..b668869bbd82fb --- /dev/null +++ b/x-pack/plugins/lists/server/scripts/delete_endpoint_list_item.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +set -e +./check_env_variables.sh + +# Example: ./delete_endpoint_list_item.sh ${item_id} +curl -s -k \ + -H 'kbn-xsrf: 123' \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X DELETE "${KIBANA_URL}${SPACE_URL}/api/endpoint_list/items?item_id=$1" | jq . diff --git a/x-pack/plugins/lists/server/scripts/delete_endpoint_list_item_by_id.sh b/x-pack/plugins/lists/server/scripts/delete_endpoint_list_item_by_id.sh new file mode 100755 index 00000000000000..86dcd0ff1debc7 --- /dev/null +++ b/x-pack/plugins/lists/server/scripts/delete_endpoint_list_item_by_id.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +set -e +./check_env_variables.sh + +# Example: ./delete_endpoint_list_item_by_id.sh ${list_id} +curl -s -k \ + -H 'kbn-xsrf: 123' \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X DELETE "${KIBANA_URL}${SPACE_URL}/api/endpoint_list/items?id=$1" | jq . diff --git a/x-pack/plugins/lists/server/scripts/exception_lists/new/endpoint_list_item.json b/x-pack/plugins/lists/server/scripts/exception_lists/new/endpoint_list_item.json new file mode 100644 index 00000000000000..8ccbe707f204cb --- /dev/null +++ b/x-pack/plugins/lists/server/scripts/exception_lists/new/endpoint_list_item.json @@ -0,0 +1,21 @@ +{ + "item_id": "simple_list_item", + "_tags": ["endpoint", "process", "malware", "os:linux"], + "tags": ["user added string for a tag", "malware"], + "type": "simple", + "description": "This is a sample endpoint type exception", + "name": "Sample Endpoint Exception List", + "entries": [ + { + "field": "actingProcess.file.signer", + "operator": "excluded", + "type": "exists" + }, + { + "field": "host.name", + "operator": "included", + "type": "match_any", + "value": ["some host", "another host"] + } + ] +} diff --git a/x-pack/plugins/lists/server/scripts/exception_lists/updates/simple_update_item.json b/x-pack/plugins/lists/server/scripts/exception_lists/updates/simple_update_item.json index 08bd95b7d124c8..da345fb930c048 100644 --- a/x-pack/plugins/lists/server/scripts/exception_lists/updates/simple_update_item.json +++ b/x-pack/plugins/lists/server/scripts/exception_lists/updates/simple_update_item.json @@ -1,5 +1,5 @@ { - "item_id": "endpoint_list_item", + "item_id": "simple_list_item", "_tags": ["endpoint", "process", "malware", "os:windows"], "tags": ["user added string for a tag", "malware"], "type": "simple", diff --git a/x-pack/plugins/lists/server/scripts/find_endpoint_list_items.sh b/x-pack/plugins/lists/server/scripts/find_endpoint_list_items.sh new file mode 100755 index 00000000000000..9372389a70b017 --- /dev/null +++ b/x-pack/plugins/lists/server/scripts/find_endpoint_list_items.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +set -e +./check_env_variables.sh + +# Optionally, post at least one list item +# ./post_endpoint_list_item.sh ./exception_lists/new/endpoint_list_item.json +# +# Then you can query it as in: +# Example: ./find_endpoint_list_item.sh +# +curl -s -k \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X GET "${KIBANA_URL}${SPACE_URL}/api/endpoint_list/items/_find" | jq . diff --git a/x-pack/plugins/lists/server/scripts/get_endpoint_list_item.sh b/x-pack/plugins/lists/server/scripts/get_endpoint_list_item.sh new file mode 100755 index 00000000000000..4f5842048293a9 --- /dev/null +++ b/x-pack/plugins/lists/server/scripts/get_endpoint_list_item.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +set -e +./check_env_variables.sh + +# Example: ./get_endpoint_list_item.sh ${item_id} +curl -s -k \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X GET "${KIBANA_URL}${SPACE_URL}/api/endpoint_list/items?item_id=$1" | jq . diff --git a/x-pack/plugins/lists/server/scripts/get_endpoint_list_item_by_id.sh b/x-pack/plugins/lists/server/scripts/get_endpoint_list_item_by_id.sh new file mode 100755 index 00000000000000..6e035010014a17 --- /dev/null +++ b/x-pack/plugins/lists/server/scripts/get_endpoint_list_item_by_id.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +set -e +./check_env_variables.sh + +set -e +./check_env_variables.sh + +# Example: ./get_endpoint_list_item.sh ${id} +curl -s -k \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X GET "${KIBANA_URL}${SPACE_URL}/api/endpoint_list/items?id=$1" | jq . diff --git a/x-pack/plugins/lists/server/scripts/post_endpoint_list.sh b/x-pack/plugins/lists/server/scripts/post_endpoint_list.sh new file mode 100755 index 00000000000000..e0b179f443547c --- /dev/null +++ b/x-pack/plugins/lists/server/scripts/post_endpoint_list.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +set -e +./check_env_variables.sh + +# Uses a default if no argument is specified +LISTS=(${@:-./exception_lists/new/exception_list.json}) + +# Example: ./post_endpoint_list.sh +curl -s -k \ + -H 'Content-Type: application/json' \ + -H 'kbn-xsrf: 123' \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X POST ${KIBANA_URL}${SPACE_URL}/api/endpoint_list \ + | jq .; diff --git a/x-pack/plugins/lists/server/scripts/post_endpoint_list_item.sh b/x-pack/plugins/lists/server/scripts/post_endpoint_list_item.sh new file mode 100755 index 00000000000000..8235a2ec06eb7f --- /dev/null +++ b/x-pack/plugins/lists/server/scripts/post_endpoint_list_item.sh @@ -0,0 +1,30 @@ +#!/bin/sh + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +set -e +./check_env_variables.sh + +# Uses a default if no argument is specified +LISTS=(${@:-./exception_lists/new/endpoint_list_item.json}) + +# Example: ./post_endpoint_list_item.sh +# Example: ./post_endpoint_list_item.sh ./exception_lists/new/endpoint_list_item.json +for LIST in "${LISTS[@]}" +do { + [ -e "$LIST" ] || continue + curl -s -k \ + -H 'Content-Type: application/json' \ + -H 'kbn-xsrf: 123' \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X POST ${KIBANA_URL}${SPACE_URL}/api/endpoint_list/items \ + -d @${LIST} \ + | jq .; +} & +done + +wait diff --git a/x-pack/plugins/lists/server/scripts/update_endpoint_item.sh b/x-pack/plugins/lists/server/scripts/update_endpoint_item.sh new file mode 100755 index 00000000000000..4a6ca3881a323d --- /dev/null +++ b/x-pack/plugins/lists/server/scripts/update_endpoint_item.sh @@ -0,0 +1,30 @@ +#!/bin/sh + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +set -e +./check_env_variables.sh + +# Uses a default if no argument is specified +LISTS=(${@:-./exception_lists/updates/simple_update_item.json}) + +# Example: ./update_endpoint_list_item.sh +# Example: ./update_endpoint_list_item.sh ./exception_lists/updates/simple_update_item.json +for LIST in "${LISTS[@]}" +do { + [ -e "$LIST" ] || continue + curl -s -k \ + -H 'Content-Type: application/json' \ + -H 'kbn-xsrf: 123' \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X PUT ${KIBANA_URL}${SPACE_URL}/api/endpoint_list/items \ + -d @${LIST} \ + | jq .; +} & +done + +wait diff --git a/x-pack/plugins/lists/server/services/exception_lists/create_endpoint_list.ts b/x-pack/plugins/lists/server/services/exception_lists/create_endpoint_list.ts new file mode 100644 index 00000000000000..b9a0194e20074b --- /dev/null +++ b/x-pack/plugins/lists/server/services/exception_lists/create_endpoint_list.ts @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { SavedObjectsClientContract } from 'kibana/server'; +import uuid from 'uuid'; + +import { + ENDPOINT_LIST_DESCRIPTION, + ENDPOINT_LIST_ID, + ENDPOINT_LIST_NAME, +} from '../../../common/constants'; +import { ExceptionListSchema, ExceptionListSoSchema } from '../../../common/schemas'; + +import { getSavedObjectType, transformSavedObjectToExceptionList } from './utils'; + +interface CreateEndpointListOptions { + savedObjectsClient: SavedObjectsClientContract; + user: string; + tieBreaker?: string; +} + +export const createEndpointList = async ({ + savedObjectsClient, + user, + tieBreaker, +}: CreateEndpointListOptions): Promise => { + const savedObjectType = getSavedObjectType({ namespaceType: 'agnostic' }); + const dateNow = new Date().toISOString(); + try { + const savedObject = await savedObjectsClient.create( + savedObjectType, + { + _tags: [], + comments: undefined, + created_at: dateNow, + created_by: user, + description: ENDPOINT_LIST_DESCRIPTION, + entries: undefined, + item_id: undefined, + list_id: ENDPOINT_LIST_ID, + list_type: 'list', + meta: undefined, + name: ENDPOINT_LIST_NAME, + tags: [], + tie_breaker_id: tieBreaker ?? uuid.v4(), + type: 'endpoint', + updated_by: user, + }, + { + // We intentionally hard coding the id so that there can only be one exception list within the space + id: ENDPOINT_LIST_ID, + } + ); + return transformSavedObjectToExceptionList({ savedObject }); + } catch (err) { + if (err.status === 409) { + return null; + } else { + throw err; + } + } +}; diff --git a/x-pack/plugins/lists/server/services/exception_lists/create_exception_list.ts b/x-pack/plugins/lists/server/services/exception_lists/create_exception_list.ts index f6a3bca10028d7..4da74c7df48bfd 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/create_exception_list.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/create_exception_list.ts @@ -68,5 +68,5 @@ export const createExceptionList = async ({ type, updated_by: user, }); - return transformSavedObjectToExceptionList({ namespaceType, savedObject }); + return transformSavedObjectToExceptionList({ savedObject }); }; diff --git a/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.ts b/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.ts index 62afda52bd79de..5c9607e2d956d5 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.ts @@ -6,6 +6,7 @@ import { SavedObjectsClientContract } from 'kibana/server'; +import { ENDPOINT_LIST_ID } from '../../../common/constants'; import { ExceptionListItemSchema, ExceptionListSchema, @@ -15,15 +16,20 @@ import { import { ConstructorOptions, + CreateEndpointListItemOptions, CreateExceptionListItemOptions, CreateExceptionListOptions, + DeleteEndpointListItemOptions, DeleteExceptionListItemOptions, DeleteExceptionListOptions, + FindEndpointListItemOptions, FindExceptionListItemOptions, FindExceptionListOptions, FindExceptionListsItemOptions, + GetEndpointListItemOptions, GetExceptionListItemOptions, GetExceptionListOptions, + UpdateEndpointListItemOptions, UpdateExceptionListItemOptions, UpdateExceptionListOptions, } from './exception_list_client_types'; @@ -38,6 +44,7 @@ import { deleteExceptionListItem } from './delete_exception_list_item'; import { findExceptionListItem } from './find_exception_list_item'; import { findExceptionList } from './find_exception_list'; import { findExceptionListsItem } from './find_exception_list_items'; +import { createEndpointList } from './create_endpoint_list'; export class ExceptionListClient { private readonly user: string; @@ -67,6 +74,103 @@ export class ExceptionListClient { return getExceptionListItem({ id, itemId, namespaceType, savedObjectsClient }); }; + /** + * This creates an agnostic space endpoint list if it does not exist. This tries to be + * as fast as possible by ignoring conflict errors and not returning the contents of the + * list if it already exists. + * @returns ExceptionListSchema if it created the endpoint list, otherwise null if it already exists + */ + public createEndpointList = async (): Promise => { + const { savedObjectsClient, user } = this; + return createEndpointList({ + savedObjectsClient, + user, + }); + }; + + /** + * This is the same as "createListItem" except it applies specifically to the agnostic endpoint list and will + * auto-call the "createEndpointList" for you so that you have the best chance of the agnostic endpoint + * being there and existing before the item is inserted into the agnostic endpoint list. + */ + public createEndpointListItem = async ({ + _tags, + comments, + description, + entries, + itemId, + meta, + name, + tags, + type, + }: CreateEndpointListItemOptions): Promise => { + const { savedObjectsClient, user } = this; + await this.createEndpointList(); + return createExceptionListItem({ + _tags, + comments, + description, + entries, + itemId, + listId: ENDPOINT_LIST_ID, + meta, + name, + namespaceType: 'agnostic', + savedObjectsClient, + tags, + type, + user, + }); + }; + + /** + * This is the same as "updateListItem" except it applies specifically to the endpoint list and will + * auto-call the "createEndpointList" for you so that you have the best chance of the endpoint + * being there if it did not exist before. If the list did not exist before, then creating it here will still cause a + * return of null but at least the list exists again. + */ + public updateEndpointListItem = async ({ + _tags, + comments, + description, + entries, + id, + itemId, + meta, + name, + tags, + type, + }: UpdateEndpointListItemOptions): Promise => { + const { savedObjectsClient, user } = this; + await this.createEndpointList(); + return updateExceptionListItem({ + _tags, + comments, + description, + entries, + id, + itemId, + meta, + name, + namespaceType: 'agnostic', + savedObjectsClient, + tags, + type, + user, + }); + }; + + /** + * This is the same as "getExceptionListItem" except it applies specifically to the endpoint list. + */ + public getEndpointListItem = async ({ + itemId, + id, + }: GetEndpointListItemOptions): Promise => { + const { savedObjectsClient } = this; + return getExceptionListItem({ id, itemId, namespaceType: 'agnostic', savedObjectsClient }); + }; + public createExceptionList = async ({ _tags, description, @@ -209,6 +313,22 @@ export class ExceptionListClient { }); }; + /** + * This is the same as "deleteExceptionListItem" except it applies specifically to the endpoint list. + */ + public deleteEndpointListItem = async ({ + id, + itemId, + }: DeleteEndpointListItemOptions): Promise => { + const { savedObjectsClient } = this; + return deleteExceptionListItem({ + id, + itemId, + namespaceType: 'agnostic', + savedObjectsClient, + }); + }; + public findExceptionListItem = async ({ listId, filter, @@ -272,4 +392,33 @@ export class ExceptionListClient { sortOrder, }); }; + + /** + * This is the same as "findExceptionList" except it applies specifically to the endpoint list and will + * auto-call the "createEndpointList" for you so that you have the best chance of the endpoint + * being there if it did not exist before. If the list did not exist before, then creating it here should give you + * a good guarantee that you will get an empty record set rather than null. I keep the null as the return value in + * the off chance that you still might somehow not get into a race condition where the endpoint list does + * not exist because someone deleted it in-between the initial create and then the find. + */ + public findEndpointListItem = async ({ + filter, + perPage, + page, + sortField, + sortOrder, + }: FindEndpointListItemOptions): Promise => { + const { savedObjectsClient } = this; + await this.createEndpointList(); + return findExceptionListItem({ + filter, + listId: ENDPOINT_LIST_ID, + namespaceType: 'agnostic', + page, + perPage, + savedObjectsClient, + sortField, + sortOrder, + }); + }; } diff --git a/x-pack/plugins/lists/server/services/exception_lists/exception_list_client_types.ts b/x-pack/plugins/lists/server/services/exception_lists/exception_list_client_types.ts index b3070f2d4a70d1..89f8310281648d 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/exception_list_client_types.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/exception_list_client_types.ts @@ -86,12 +86,22 @@ export interface DeleteExceptionListItemOptions { namespaceType: NamespaceType; } +export interface DeleteEndpointListItemOptions { + id: IdOrUndefined; + itemId: ItemIdOrUndefined; +} + export interface GetExceptionListItemOptions { itemId: ItemIdOrUndefined; id: IdOrUndefined; namespaceType: NamespaceType; } +export interface GetEndpointListItemOptions { + itemId: ItemIdOrUndefined; + id: IdOrUndefined; +} + export interface CreateExceptionListItemOptions { _tags: _Tags; comments: CreateCommentsArray; @@ -106,6 +116,18 @@ export interface CreateExceptionListItemOptions { type: ExceptionListItemType; } +export interface CreateEndpointListItemOptions { + _tags: _Tags; + comments: CreateCommentsArray; + entries: EntriesArray; + itemId: ItemId; + name: Name; + description: Description; + meta: MetaOrUndefined; + tags: Tags; + type: ExceptionListItemType; +} + export interface UpdateExceptionListItemOptions { _tags: _TagsOrUndefined; comments: UpdateCommentsArray; @@ -120,6 +142,19 @@ export interface UpdateExceptionListItemOptions { type: ExceptionListItemTypeOrUndefined; } +export interface UpdateEndpointListItemOptions { + _tags: _TagsOrUndefined; + comments: UpdateCommentsArray; + entries: EntriesArrayOrUndefined; + id: IdOrUndefined; + itemId: ItemIdOrUndefined; + name: NameOrUndefined; + description: DescriptionOrUndefined; + meta: MetaOrUndefined; + tags: TagsOrUndefined; + type: ExceptionListItemTypeOrUndefined; +} + export interface FindExceptionListItemOptions { listId: ListId; namespaceType: NamespaceType; @@ -130,6 +165,14 @@ export interface FindExceptionListItemOptions { sortOrder: SortOrderOrUndefined; } +export interface FindEndpointListItemOptions { + filter: FilterOrUndefined; + perPage: PerPageOrUndefined; + page: PageOrUndefined; + sortField: SortFieldOrUndefined; + sortOrder: SortOrderOrUndefined; +} + export interface FindExceptionListsItemOptions { listId: NonEmptyStringArrayDecoded; namespaceType: NamespaceTypeArray; diff --git a/x-pack/plugins/lists/server/services/exception_lists/find_exception_list.ts b/x-pack/plugins/lists/server/services/exception_lists/find_exception_list.ts index 899ed308637709..84cc7ba2f10214 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/find_exception_list.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/find_exception_list.ts @@ -48,7 +48,7 @@ export const findExceptionList = async ({ sortOrder, type: savedObjectType, }); - return transformSavedObjectsToFoundExceptionList({ namespaceType, savedObjectsFindResponse }); + return transformSavedObjectsToFoundExceptionList({ savedObjectsFindResponse }); }; export const getExceptionListFilter = ({ diff --git a/x-pack/plugins/lists/server/services/exception_lists/get_exception_list.ts b/x-pack/plugins/lists/server/services/exception_lists/get_exception_list.ts index 8f511d140b0ff4..a5c1e2e5c6bc99 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/get_exception_list.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/get_exception_list.ts @@ -35,7 +35,7 @@ export const getExceptionList = async ({ if (id != null) { try { const savedObject = await savedObjectsClient.get(savedObjectType, id); - return transformSavedObjectToExceptionList({ namespaceType, savedObject }); + return transformSavedObjectToExceptionList({ savedObject }); } catch (err) { if (SavedObjectsErrorHelpers.isNotFoundError(err)) { return null; @@ -55,7 +55,6 @@ export const getExceptionList = async ({ }); if (savedObject.saved_objects[0] != null) { return transformSavedObjectToExceptionList({ - namespaceType, savedObject: savedObject.saved_objects[0], }); } else { diff --git a/x-pack/plugins/lists/server/services/exception_lists/update_exception_list.ts b/x-pack/plugins/lists/server/services/exception_lists/update_exception_list.ts index e4d6718ddc29fe..a739366c673314 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/update_exception_list.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/update_exception_list.ts @@ -69,6 +69,6 @@ export const updateExceptionList = async ({ updated_by: user, } ); - return transformSavedObjectUpdateToExceptionList({ exceptionList, namespaceType, savedObject }); + return transformSavedObjectUpdateToExceptionList({ exceptionList, savedObject }); } }; diff --git a/x-pack/plugins/lists/server/services/exception_lists/update_exception_list_item.ts b/x-pack/plugins/lists/server/services/exception_lists/update_exception_list_item.ts index 2059c730d809fa..a5ed1e38df374a 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/update_exception_list_item.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/update_exception_list_item.ts @@ -93,7 +93,6 @@ export const updateExceptionListItem = async ({ ); return transformSavedObjectUpdateToExceptionListItem({ exceptionListItem, - namespaceType, savedObject, }); } diff --git a/x-pack/plugins/lists/server/services/exception_lists/utils.ts b/x-pack/plugins/lists/server/services/exception_lists/utils.ts index 3ef2c337e80b66..ded39933fe9d8f 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/utils.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/utils.ts @@ -67,10 +67,8 @@ export const getSavedObjectTypes = ({ export const transformSavedObjectToExceptionList = ({ savedObject, - namespaceType, }: { savedObject: SavedObject; - namespaceType: NamespaceType; }): ExceptionListSchema => { const dateNow = new Date().toISOString(); const { @@ -102,7 +100,7 @@ export const transformSavedObjectToExceptionList = ({ list_id, meta, name, - namespace_type: namespaceType, + namespace_type: getExceptionListType({ savedObjectType: savedObject.type }), tags, tie_breaker_id, type: exceptionListType.is(type) ? type : 'detection', @@ -114,11 +112,9 @@ export const transformSavedObjectToExceptionList = ({ export const transformSavedObjectUpdateToExceptionList = ({ exceptionList, savedObject, - namespaceType, }: { exceptionList: ExceptionListSchema; savedObject: SavedObjectsUpdateResponse; - namespaceType: NamespaceType; }): ExceptionListSchema => { const dateNow = new Date().toISOString(); const { @@ -138,7 +134,7 @@ export const transformSavedObjectUpdateToExceptionList = ({ list_id: exceptionList.list_id, meta: meta ?? exceptionList.meta, name: name ?? exceptionList.name, - namespace_type: namespaceType, + namespace_type: getExceptionListType({ savedObjectType: savedObject.type }), tags: tags ?? exceptionList.tags, tie_breaker_id: exceptionList.tie_breaker_id, type: exceptionListType.is(type) ? type : exceptionList.type, @@ -200,11 +196,9 @@ export const transformSavedObjectToExceptionListItem = ({ export const transformSavedObjectUpdateToExceptionListItem = ({ exceptionListItem, savedObject, - namespaceType, }: { exceptionListItem: ExceptionListItemSchema; savedObject: SavedObjectsUpdateResponse; - namespaceType: NamespaceType; }): ExceptionListItemSchema => { const dateNow = new Date().toISOString(); const { @@ -239,7 +233,7 @@ export const transformSavedObjectUpdateToExceptionListItem = ({ list_id: exceptionListItem.list_id, meta: meta ?? exceptionListItem.meta, name: name ?? exceptionListItem.name, - namespace_type: namespaceType, + namespace_type: getExceptionListType({ savedObjectType: savedObject.type }), tags: tags ?? exceptionListItem.tags, tie_breaker_id: exceptionListItem.tie_breaker_id, type: exceptionListItemType.is(type) ? type : exceptionListItem.type, @@ -265,14 +259,12 @@ export const transformSavedObjectsToFoundExceptionListItem = ({ export const transformSavedObjectsToFoundExceptionList = ({ savedObjectsFindResponse, - namespaceType, }: { savedObjectsFindResponse: SavedObjectsFindResponse; - namespaceType: NamespaceType; }): FoundExceptionListSchema => { return { data: savedObjectsFindResponse.saved_objects.map((savedObject) => - transformSavedObjectToExceptionList({ namespaceType, savedObject }) + transformSavedObjectToExceptionList({ savedObject }) ), page: savedObjectsFindResponse.page, per_page: savedObjectsFindResponse.per_page, diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_prebuilt.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_prebuilt.spec.ts index 986a7c7177a798..00ddc85a736504 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_prebuilt.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_prebuilt.spec.ts @@ -67,7 +67,8 @@ describe('Alerts rules, prebuilt rules', () => { }); }); -describe('Deleting prebuilt rules', () => { +// https://github.com/elastic/kibana/issues/71814 +describe.skip('Deleting prebuilt rules', () => { beforeEach(() => { const expectedNumberOfRules = totalNumberOfPrebuiltRules; const expectedElasticRulesBtnText = `Elastic rules (${expectedNumberOfRules})`; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts index 1226be71f63f5b..b1f6f73b096278 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts @@ -55,6 +55,10 @@ export const addPrepackedRulesRoute = ( if (!siemClient || !alertsClient) { return siemResponse.error({ statusCode: 404 }); } + + // This will create the endpoint list if it does not exist yet + await context.lists?.getExceptionListClient().createEndpointList(); + const rulesFromFileSystem = getPrepackagedRules(); const prepackagedRules = await getExistingPrepackagedRules({ alertsClient }); const rulesToInstall = getRulesToInstall(rulesFromFileSystem, prepackagedRules); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.ts index edad3dd8a4f213..482edb9925557b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.ts @@ -97,7 +97,6 @@ export const createRulesRoute = (router: IRouter, ml: SetupPlugins['ml']): void // TODO: Fix these either with an is conversion or by better typing them within io-ts const actions: RuleAlertAction[] = actionsRest as RuleAlertAction[]; const filters: PartialFilter[] | undefined = filtersRest as PartialFilter[]; - const alertsClient = context.alerting?.getAlertsClient(); const clusterClient = context.core.elasticsearch.legacy.client; const savedObjectsClient = context.core.savedObjects.client; @@ -127,6 +126,8 @@ export const createRulesRoute = (router: IRouter, ml: SetupPlugins['ml']): void }); } } + // This will create the endpoint list if it does not exist yet + await context.lists?.getExceptionListClient().createEndpointList(); const createdRule = await createRules({ alertsClient, diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index b9d2fdcbbfca7b..b54f88f83fbe00 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -4279,44 +4279,7 @@ "xpack.apm.serviceDetails.alertsMenu.errorRate": "エラー率", "xpack.apm.serviceDetails.alertsMenu.transactionDuration": "トランザクション期間", "xpack.apm.serviceDetails.alertsMenu.viewActiveAlerts": "アクティブアラートを表示", - "xpack.apm.serviceDetails.enableErrorReportsPanel.actionsDescription": "レポートはメールで送信するか Slack チャンネルに投稿できます。各レポートにはオカランス別のトップ 10 のエラーが含まれます。", - "xpack.apm.serviceDetails.enableErrorReportsPanel.actionsTitle": "アクション", - "xpack.apm.serviceDetails.enableErrorReportsPanel.conditionTitle": "コンディション", - "xpack.apm.serviceDetails.enableErrorReportsPanel.createWatchButtonLabel": "ウォッチを作成", - "xpack.apm.serviceDetails.enableErrorReportsPanel.dailyReportHelpText": "デイリーレポートは {dailyTimeFormatted} / {dailyTime12HourFormatted} に送信されます。", - "xpack.apm.serviceDetails.enableErrorReportsPanel.dailyReportRadioButtonLabel": "デイリーレポート", - "xpack.apm.serviceDetails.enableErrorReportsPanel.emailSubjectText": "{serviceName} にしきい値を超えたエラーグループがあります", - "xpack.apm.serviceDetails.enableErrorReportsPanel.emailTemplateText": "{serviceName} サービスに {timeRange}{br}{br}{errorGroupsBuckets}{br}{errorLogMessage}{br}{errorCulprit}N/A{slashErrorCulprit}{br}{docCountParam} オカレンス {br}{slashErrorGroupsBucket} 内で {threshold} 件のオカレンスを超えるエラーグループがあります", - "xpack.apm.serviceDetails.enableErrorReportsPanel.enableErrorReportsTitle": "エラーレポートを有効にする", - "xpack.apm.serviceDetails.enableErrorReportsPanel.formDescription": "このフォームは、このサービスでのエラーのオカレンスを通知するウォッチの作成をアシストします。Watcher の書斎は、{documentationLink} をご覧ください。", - "xpack.apm.serviceDetails.enableErrorReportsPanel.formDescription.documentationLinkText": "ドキュメンテーション", - "xpack.apm.serviceDetails.enableErrorReportsPanel.intervalHelpText": "レポートの間隔。", - "xpack.apm.serviceDetails.enableErrorReportsPanel.intervalRadioButtonLabel": "間隔", - "xpack.apm.serviceDetails.enableErrorReportsPanel.intervalUnit.hrsLabel": "時間", - "xpack.apm.serviceDetails.enableErrorReportsPanel.intervalUnit.minsLabel": "分", - "xpack.apm.serviceDetails.enableErrorReportsPanel.occurrencesThresholdHelpText": "エラーグループがレポートに含まれるしきい値です。", - "xpack.apm.serviceDetails.enableErrorReportsPanel.occurrencesThresholdLabel": "エラーグループごとのオカレンスのしきい値", - "xpack.apm.serviceDetails.enableErrorReportsPanel.recipientsHelpText": "メールを構成していない場合は、{documentationLink} をご覧ください。", - "xpack.apm.serviceDetails.enableErrorReportsPanel.recipientsHelpText.documentationLinkText": "ドキュメンテーション", - "xpack.apm.serviceDetails.enableErrorReportsPanel.recipientsLabel": "受信者 (コンマ区切り)", - "xpack.apm.serviceDetails.enableErrorReportsPanel.sendEmailLabel": "メールを送信", - "xpack.apm.serviceDetails.enableErrorReportsPanel.sendSlackNotificationLabel": "Slack 通知を送信", - "xpack.apm.serviceDetails.enableErrorReportsPanel.slackTemplateText": "{serviceName} サービスに {timeRange} 以内に {threshold} 件のオカレンスを超えるエラーグループがあります。\n{errorGroupsBuckets}\n{errorLogMessage}\n{errorCulprit}N/A{slashErrorCulprit}\n{docCountParam} 件のオカレンス\n{slashErrorGroupsBucket}", - "xpack.apm.serviceDetails.enableErrorReportsPanel.slackWebhookURLHelpText": "Slack webhook の取得方法は、{documentationLink} をご覧ください。", - "xpack.apm.serviceDetails.enableErrorReportsPanel.slackWebhookURLHelpText.documentationLinkText": "ドキュメンテーション", - "xpack.apm.serviceDetails.enableErrorReportsPanel.slackWebhookURLLabel": "Slack Webhook URL", - "xpack.apm.serviceDetails.enableErrorReportsPanel.triggerScheduleDescription": "しきい値を超えた際のレポートの間隔を選択してください。", - "xpack.apm.serviceDetails.enableErrorReportsPanel.triggerScheduleTitle": "トリガースケジュール", - "xpack.apm.serviceDetails.enableErrorReportsPanel.triggerText": "この値はトリガーセクションで変更する必要があります。", - "xpack.apm.serviceDetails.enableErrorReportsPanel.watchCreatedNotificationText": "ウォッチの準備が完了し、{serviceName} のエラーレポートが送信されます。", - "xpack.apm.serviceDetails.enableErrorReportsPanel.watchCreatedNotificationText.viewWatchLinkText": "ウォッチを表示", - "xpack.apm.serviceDetails.enableErrorReportsPanel.watchCreatedNotificationTitle": "新規ウォッチが作成されました!", - "xpack.apm.serviceDetails.enableErrorReportsPanel.watchCreationFailedNotificationText": "ユーザーにウォッチ作成のパーミッションがあることを確認してください。", - "xpack.apm.serviceDetails.enableErrorReportsPanel.watchCreationFailedNotificationTitle": "ウォッチの作成に失敗", "xpack.apm.serviceDetails.errorsTabLabel": "エラー", - "xpack.apm.serviceDetails.integrationsMenu.enableWatcherErrorReportsButtonLabel": "ウォッチエラーレポートを有効にする", - "xpack.apm.serviceDetails.integrationsMenu.integrationsButtonLabel": "統合", - "xpack.apm.serviceDetails.integrationsMenu.viewWatchesButtonLabel": "既存のウォッチを表示", "xpack.apm.serviceDetails.metrics.cpuUsageChartTitle": "CPU 使用状況", "xpack.apm.serviceDetails.metrics.errorOccurrencesChartTitle": "エラーのオカレンス", "xpack.apm.serviceDetails.metrics.memoryUsageChartTitle": "システムメモリー使用状況", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index b45f02f41d11f5..389e0083d5a9f8 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -4283,44 +4283,7 @@ "xpack.apm.serviceDetails.alertsMenu.errorRate": "错误率", "xpack.apm.serviceDetails.alertsMenu.transactionDuration": "事务持续时间", "xpack.apm.serviceDetails.alertsMenu.viewActiveAlerts": "查看活动的告警", - "xpack.apm.serviceDetails.enableErrorReportsPanel.actionsDescription": "可以通过电子邮件发送报告或将报告发布到 Slack 频道。每个报告将包括按发生次数排序的前 10 个错误。", - "xpack.apm.serviceDetails.enableErrorReportsPanel.actionsTitle": "操作", - "xpack.apm.serviceDetails.enableErrorReportsPanel.conditionTitle": "条件", - "xpack.apm.serviceDetails.enableErrorReportsPanel.createWatchButtonLabel": "创建监视", - "xpack.apm.serviceDetails.enableErrorReportsPanel.dailyReportHelpText": "每日报告将在 {dailyTimeFormatted} / {dailyTime12HourFormatted} 发送。", - "xpack.apm.serviceDetails.enableErrorReportsPanel.dailyReportRadioButtonLabel": "每日报告", - "xpack.apm.serviceDetails.enableErrorReportsPanel.emailSubjectText": "{serviceName} 具有超过阈值的错误组", - "xpack.apm.serviceDetails.enableErrorReportsPanel.emailTemplateText": "您的服务 {serviceName} 具有在 {timeRange}内发生次数超过 {threshold} 次的错误组{br}{br}{errorGroupsBuckets}{br}{errorLogMessage}{br}{errorCulprit}不适用{slashErrorCulprit}{br}{docCountParam} 次{br}{slashErrorGroupsBucket}", - "xpack.apm.serviceDetails.enableErrorReportsPanel.enableErrorReportsTitle": "启用错误报告", - "xpack.apm.serviceDetails.enableErrorReportsPanel.formDescription": "此表单将帮助创建从此服务向您通知错误发生次数的监视。要详细了解 Watcher,请阅读我们的恶{documentationLink}", - "xpack.apm.serviceDetails.enableErrorReportsPanel.formDescription.documentationLinkText": "文档", - "xpack.apm.serviceDetails.enableErrorReportsPanel.intervalHelpText": "报告时间间隔。", - "xpack.apm.serviceDetails.enableErrorReportsPanel.intervalRadioButtonLabel": "时间间隔", - "xpack.apm.serviceDetails.enableErrorReportsPanel.intervalUnit.hrsLabel": "小时", - "xpack.apm.serviceDetails.enableErrorReportsPanel.intervalUnit.minsLabel": "分钟", - "xpack.apm.serviceDetails.enableErrorReportsPanel.occurrencesThresholdHelpText": "要将错误组包括在报告中所要达到的阈值。", - "xpack.apm.serviceDetails.enableErrorReportsPanel.occurrencesThresholdLabel": "每错误组的发生次数阈值", - "xpack.apm.serviceDetails.enableErrorReportsPanel.recipientsHelpText": "如果未配置电子邮件,请参阅{documentationLink}", - "xpack.apm.serviceDetails.enableErrorReportsPanel.recipientsHelpText.documentationLinkText": "文档", - "xpack.apm.serviceDetails.enableErrorReportsPanel.recipientsLabel": "接收人(逗号分隔)", - "xpack.apm.serviceDetails.enableErrorReportsPanel.sendEmailLabel": "发送电子邮件", - "xpack.apm.serviceDetails.enableErrorReportsPanel.sendSlackNotificationLabel": "发送 Slack 通知", - "xpack.apm.serviceDetails.enableErrorReportsPanel.slackTemplateText": "您的服务 {serviceName} 具有在 {timeRange}内发生次数超过 {threshold} 次的错误组\n{errorGroupsBuckets}\n{errorLogMessage}\n{errorCulprit}不适用{slashErrorCulprit}\n{docCountParam} 次发生\n{slashErrorGroupsBucket}", - "xpack.apm.serviceDetails.enableErrorReportsPanel.slackWebhookURLHelpText": "要获取 Slack Webhook,请参阅{documentationLink}", - "xpack.apm.serviceDetails.enableErrorReportsPanel.slackWebhookURLHelpText.documentationLinkText": "文档", - "xpack.apm.serviceDetails.enableErrorReportsPanel.slackWebhookURLLabel": "Slack Webhook URL", - "xpack.apm.serviceDetails.enableErrorReportsPanel.triggerScheduleDescription": "选择阈值达到时报告的时间间隔。", - "xpack.apm.serviceDetails.enableErrorReportsPanel.triggerScheduleTitle": "触发排定", - "xpack.apm.serviceDetails.enableErrorReportsPanel.triggerText": "必须在触发器部分更改此值", - "xpack.apm.serviceDetails.enableErrorReportsPanel.watchCreatedNotificationText": "监视已就绪,将发送 {serviceName} 的错误报告。", - "xpack.apm.serviceDetails.enableErrorReportsPanel.watchCreatedNotificationText.viewWatchLinkText": "查看监视", - "xpack.apm.serviceDetails.enableErrorReportsPanel.watchCreatedNotificationTitle": "新监视已创建!", - "xpack.apm.serviceDetails.enableErrorReportsPanel.watchCreationFailedNotificationText": "确保您的用户有权创建监视。", - "xpack.apm.serviceDetails.enableErrorReportsPanel.watchCreationFailedNotificationTitle": "监视创建失败", "xpack.apm.serviceDetails.errorsTabLabel": "错误", - "xpack.apm.serviceDetails.integrationsMenu.enableWatcherErrorReportsButtonLabel": "启用 Watcher 错误报告", - "xpack.apm.serviceDetails.integrationsMenu.integrationsButtonLabel": "集成", - "xpack.apm.serviceDetails.integrationsMenu.viewWatchesButtonLabel": "查看现有监视", "xpack.apm.serviceDetails.metrics.cpuUsageChartTitle": "CPU 使用", "xpack.apm.serviceDetails.metrics.errorOccurrencesChartTitle": "错误发生次数", "xpack.apm.serviceDetails.metrics.memoryUsageChartTitle": "系统内存使用", diff --git a/x-pack/test/apm_api_integration/basic/tests/services/transactions/top_transaction_groups.ts b/x-pack/test/apm_api_integration/basic/tests/services/transactions/top_transaction_groups.ts index 3df1e9972d5ace..bf8d3f6a56e6aa 100644 --- a/x-pack/test/apm_api_integration/basic/tests/services/transactions/top_transaction_groups.ts +++ b/x-pack/test/apm_api_integration/basic/tests/services/transactions/top_transaction_groups.ts @@ -25,7 +25,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { ); expect(response.status).to.be(200); - expect(response.body).to.eql({ items: [], isAggregationAccurate: true, bucketSize: 100 }); + expect(response.body).to.eql({ items: [], isAggregationAccurate: true, bucketSize: 1000 }); }); }); diff --git a/x-pack/test/apm_api_integration/basic/tests/traces/top_traces.ts b/x-pack/test/apm_api_integration/basic/tests/traces/top_traces.ts index ca50ae291f110e..aef208b6fc06b5 100644 --- a/x-pack/test/apm_api_integration/basic/tests/traces/top_traces.ts +++ b/x-pack/test/apm_api_integration/basic/tests/traces/top_traces.ts @@ -24,7 +24,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { ); expect(response.status).to.be(200); - expect(response.body).to.eql({ items: [], isAggregationAccurate: true, bucketSize: 100 }); + expect(response.body).to.eql({ items: [], isAggregationAccurate: true, bucketSize: 1000 }); }); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/add_prepackaged_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/add_prepackaged_rules.ts index 242f906d0d197c..5e0ce0b8243239 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/add_prepackaged_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/add_prepackaged_rules.ts @@ -20,7 +20,8 @@ export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const es = getService('es'); - describe('add_prepackaged_rules', () => { + // https://github.com/elastic/kibana/issues/71814 + describe.skip('add_prepackaged_rules', () => { describe('validation errors', () => { it('should give an error that the index must exist first if it does not exist before adding prepackaged rules', async () => { const { body } = await supertest diff --git a/x-pack/test/ingest_manager_api_integration/apis/epm/install.ts b/x-pack/test/ingest_manager_api_integration/apis/epm/install.ts index 54a7e0dcb92428..f2ca98ca39a0bb 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/epm/install.ts +++ b/x-pack/test/ingest_manager_api_integration/apis/epm/install.ts @@ -23,7 +23,7 @@ export default function ({ getService }: FtrProviderContext) { // Temporarily skipped to promote snapshot // Re-enabled in https://github.com/elastic/kibana/pull/71727 - describe('installs packages that include settings and mappings overrides', async () => { + describe.skip('installs packages that include settings and mappings overrides', async () => { after(async () => { if (server.enabled) { // remove the package just in case it being installed will affect other tests