diff --git a/.ci/pipeline-library/src/test/prChanges.groovy b/.ci/pipeline-library/src/test/prChanges.groovy index f149340517ff0..0f354e7687246 100644 --- a/.ci/pipeline-library/src/test/prChanges.groovy +++ b/.ci/pipeline-library/src/test/prChanges.groovy @@ -97,4 +97,17 @@ class PrChangesTest extends KibanaBasePipelineTest { assertFalse(prChanges.areChangesSkippable()) } + + @Test + void 'areChangesSkippable() with plugin readme changes'() { + props([ + githubPrs: [ + getChanges: { [ + [filename: 'src/plugins/foo/README.asciidoc'], + ] }, + ], + ]) + + assertFalse(prChanges.areChangesSkippable()) + } } diff --git a/src/plugins/visualize/public/application/components/experimental_vis_info.tsx b/src/plugins/visualize/public/application/components/experimental_vis_info.tsx index 51abb3ca530a4..5f08bea60e538 100644 --- a/src/plugins/visualize/public/application/components/experimental_vis_info.tsx +++ b/src/plugins/visualize/public/application/components/experimental_vis_info.tsx @@ -26,12 +26,20 @@ export const InfoComponent = () => { <> {' '} - - GitHub - - {'.'} + defaultMessage="This visualization is experimental and is not subject to the support SLA of official GA features. + For feedback, please create an issue in {githubLink}." + values={{ + githubLink: ( + + GitHub + + ), + }} + /> ); diff --git a/vars/prChanges.groovy b/vars/prChanges.groovy index adaacf952b5b6..a7fe46e7bf014 100644 --- a/vars/prChanges.groovy +++ b/vars/prChanges.groovy @@ -22,6 +22,8 @@ def getNotSkippablePaths() { return [ // this file is auto-generated and changes to it need to be validated with CI /^docs\/developer\/architecture\/code-exploration.asciidoc$/, + // don't skip CI on prs with changes to plugin readme files (?i) is for case-insensitive matching + /(?i)\/plugins\/[^\/]+\/readme\.(md|asciidoc)$/, ] } diff --git a/x-pack/plugins/alerts/server/alerts_client.ts b/x-pack/plugins/alerts/server/alerts_client.ts index 256cae24e519f..dd66ccc7a0256 100644 --- a/x-pack/plugins/alerts/server/alerts_client.ts +++ b/x-pack/plugins/alerts/server/alerts_client.ts @@ -387,11 +387,18 @@ export class AlertsClient { updateResult.scheduledTaskId && !isEqual(alertSavedObject.attributes.schedule, updateResult.schedule) ) { - this.taskManager.runNow(updateResult.scheduledTaskId).catch((err: Error) => { - this.logger.error( - `Alert update failed to run its underlying task. TaskManager runNow failed with Error: ${err.message}` - ); - }); + this.taskManager + .runNow(updateResult.scheduledTaskId) + .then(() => { + this.logger.debug( + `Alert update has rescheduled the underlying task: ${updateResult.scheduledTaskId}` + ); + }) + .catch((err: Error) => { + this.logger.error( + `Alert update failed to run its underlying task. TaskManager runNow failed with Error: ${err.message}` + ); + }); } })(), ]); diff --git a/x-pack/plugins/enterprise_search/common/constants.ts b/x-pack/plugins/enterprise_search/common/constants.ts index fc9a47717871b..c5839df4c603b 100644 --- a/x-pack/plugins/enterprise_search/common/constants.ts +++ b/x-pack/plugins/enterprise_search/common/constants.ts @@ -4,6 +4,40 @@ * you may not use this file except in compliance with the Elastic License. */ +import { i18n } from '@kbn/i18n'; + +export const ENTERPRISE_SEARCH_PLUGIN = { + ID: 'enterpriseSearch', + NAME: i18n.translate('xpack.enterpriseSearch.productName', { + defaultMessage: 'Enterprise Search', + }), + URL: '/app/enterprise_search', +}; + +export const APP_SEARCH_PLUGIN = { + ID: 'appSearch', + NAME: i18n.translate('xpack.enterpriseSearch.appSearch.productName', { + defaultMessage: 'App Search', + }), + DESCRIPTION: i18n.translate('xpack.enterpriseSearch.appSearch.productDescription', { + defaultMessage: + 'Leverage dashboards, analytics, and APIs for advanced application search made simple.', + }), + URL: '/app/enterprise_search/app_search', +}; + +export const WORKPLACE_SEARCH_PLUGIN = { + ID: 'workplaceSearch', + NAME: i18n.translate('xpack.enterpriseSearch.workplaceSearch.productName', { + defaultMessage: 'Workplace Search', + }), + DESCRIPTION: i18n.translate('xpack.enterpriseSearch.workplaceSearch.productDescription', { + defaultMessage: + 'Search all documents, files, and sources available across your virtual workplace.', + }), + URL: '/app/enterprise_search/workplace_search', +}; + export const JSON_HEADER = { 'Content-Type': 'application/json' }; // This needs specific casing or Chrome throws a 415 error export const ENGINES_PAGE_SIZE = 10; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/setup_guide/setup_guide.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/setup_guide/setup_guide.tsx index df278bf938a69..f899423319afc 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/setup_guide/setup_guide.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/setup_guide/setup_guide.tsx @@ -9,6 +9,7 @@ import { EuiSpacer, EuiTitle, EuiText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; +import { APP_SEARCH_PLUGIN } from '../../../../../common/constants'; import { SetupGuide as SetupGuideLayout } from '../../../shared/setup_guide'; import { SetAppSearchBreadcrumbs as SetBreadcrumbs } from '../../../shared/kibana_breadcrumbs'; import { SendAppSearchTelemetry as SendTelemetry } from '../../../shared/telemetry'; @@ -16,14 +17,16 @@ import GettingStarted from '../../assets/getting_started.png'; export const SetupGuide: React.FC = () => ( - + ( breadcrumbs: TBreadcrumbs = [] ) => [ - generateBreadcrumb({ text: 'Enterprise Search' }), + generateBreadcrumb({ text: ENTERPRISE_SEARCH_PLUGIN.NAME }), ...breadcrumbs.map(({ text, path }: IGenerateBreadcrumbProps) => generateBreadcrumb({ text, path, history }) ), ]; export const appSearchBreadcrumbs = (history: History) => (breadcrumbs: TBreadcrumbs = []) => - enterpriseSearchBreadcrumbs(history)([{ text: 'App Search', path: '/' }, ...breadcrumbs]); + enterpriseSearchBreadcrumbs(history)([ + { text: APP_SEARCH_PLUGIN.NAME, path: '/' }, + ...breadcrumbs, + ]); export const workplaceSearchBreadcrumbs = (history: History) => (breadcrumbs: TBreadcrumbs = []) => - enterpriseSearchBreadcrumbs(history)([{ text: 'Workplace Search', path: '/' }, ...breadcrumbs]); + enterpriseSearchBreadcrumbs(history)([ + { text: WORKPLACE_SEARCH_PLUGIN.NAME, path: '/' }, + ...breadcrumbs, + ]); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/error_state/error_state.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/error_state/error_state.tsx index 9fa508d599425..a1bc17e05dc05 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/error_state/error_state.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/error_state/error_state.tsx @@ -6,8 +6,8 @@ import React from 'react'; import { EuiPage, EuiPageBody, EuiPageContent } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; +import { WORKPLACE_SEARCH_PLUGIN } from '../../../../../common/constants'; import { ErrorStatePrompt } from '../../../shared/error_state'; import { SetWorkplaceSearchBreadcrumbs as SetBreadcrumbs } from '../../../shared/kibana_breadcrumbs'; import { SendWorkplaceSearchTelemetry as SendTelemetry } from '../../../shared/telemetry'; @@ -20,11 +20,7 @@ export const ErrorState: React.FC = () => { - + diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/setup_guide/setup_guide.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/setup_guide/setup_guide.tsx index 5b5d067d23eb8..e96d114c67c5d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/setup_guide/setup_guide.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/setup_guide/setup_guide.tsx @@ -9,8 +9,8 @@ import { EuiSpacer, EuiTitle, EuiText, EuiButton } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; +import { WORKPLACE_SEARCH_PLUGIN } from '../../../../../common/constants'; import { SetupGuide as SetupGuideLayout } from '../../../shared/setup_guide'; - import { SetWorkplaceSearchBreadcrumbs as SetBreadcrumbs } from '../../../shared/kibana_breadcrumbs'; import { SendWorkplaceSearchTelemetry as SendTelemetry } from '../../../shared/telemetry'; import GettingStarted from '../../assets/getting_started.png'; @@ -21,14 +21,16 @@ const GETTING_STARTED_LINK_URL = export const SetupGuide: React.FC = () => { return ( - + diff --git a/x-pack/plugins/enterprise_search/public/plugin.ts b/x-pack/plugins/enterprise_search/public/plugin.ts index fc95828a3f4a4..66d2ae82fe3ce 100644 --- a/x-pack/plugins/enterprise_search/public/plugin.ts +++ b/x-pack/plugins/enterprise_search/public/plugin.ts @@ -20,6 +20,7 @@ import { import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/public'; import { LicensingPluginSetup } from '../../licensing/public'; +import { APP_SEARCH_PLUGIN, WORKPLACE_SEARCH_PLUGIN } from '../common/constants'; import { getPublicUrl } from './applications/shared/enterprise_search_url'; import AppSearchLogo from './applications/app_search/assets/logo.svg'; import WorkplaceSearchLogo from './applications/workplace_search/assets/logo.svg'; @@ -44,9 +45,9 @@ export class EnterpriseSearchPlugin implements Plugin { const config = { host: this.config.host }; core.application.register({ - id: 'appSearch', - title: 'App Search', - appRoute: '/app/enterprise_search/app_search', + id: APP_SEARCH_PLUGIN.ID, + title: APP_SEARCH_PLUGIN.NAME, + appRoute: APP_SEARCH_PLUGIN.URL, category: DEFAULT_APP_CATEGORIES.enterpriseSearch, mount: async (params: AppMountParameters) => { const [coreStart] = await core.getStartServices(); @@ -61,9 +62,9 @@ export class EnterpriseSearchPlugin implements Plugin { }); core.application.register({ - id: 'workplaceSearch', - title: 'Workplace Search', - appRoute: '/app/enterprise_search/workplace_search', + id: WORKPLACE_SEARCH_PLUGIN.ID, + title: WORKPLACE_SEARCH_PLUGIN.NAME, + appRoute: WORKPLACE_SEARCH_PLUGIN.URL, category: DEFAULT_APP_CATEGORIES.enterpriseSearch, mount: async (params: AppMountParameters) => { const [coreStart] = await core.getStartServices(); @@ -76,23 +77,21 @@ export class EnterpriseSearchPlugin implements Plugin { }); plugins.home.featureCatalogue.register({ - id: 'appSearch', - title: 'App Search', + id: APP_SEARCH_PLUGIN.ID, + title: APP_SEARCH_PLUGIN.NAME, icon: AppSearchLogo, - description: - 'Leverage dashboards, analytics, and APIs for advanced application search made simple.', - path: '/app/enterprise_search/app_search', + description: APP_SEARCH_PLUGIN.DESCRIPTION, + path: APP_SEARCH_PLUGIN.URL, category: FeatureCatalogueCategory.DATA, showOnHomePage: true, }); plugins.home.featureCatalogue.register({ - id: 'workplaceSearch', - title: 'Workplace Search', + id: WORKPLACE_SEARCH_PLUGIN.ID, + title: WORKPLACE_SEARCH_PLUGIN.NAME, icon: WorkplaceSearchLogo, - description: - 'Search all documents, files, and sources available across your virtual workplace.', - path: '/app/enterprise_search/workplace_search', + description: WORKPLACE_SEARCH_PLUGIN.DESCRIPTION, + path: WORKPLACE_SEARCH_PLUGIN.URL, category: FeatureCatalogueCategory.DATA, showOnHomePage: true, }); diff --git a/x-pack/plugins/enterprise_search/server/plugin.ts b/x-pack/plugins/enterprise_search/server/plugin.ts index a7bd68f92f78b..6de6671337797 100644 --- a/x-pack/plugins/enterprise_search/server/plugin.ts +++ b/x-pack/plugins/enterprise_search/server/plugin.ts @@ -19,6 +19,11 @@ import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { SecurityPluginSetup } from '../../security/server'; import { PluginSetupContract as FeaturesPluginSetup } from '../../features/server'; +import { + ENTERPRISE_SEARCH_PLUGIN, + APP_SEARCH_PLUGIN, + WORKPLACE_SEARCH_PLUGIN, +} from '../common/constants'; import { ConfigType } from './'; import { checkAccess } from './lib/check_access'; import { registerPublicUrlRoute } from './routes/enterprise_search/public_url'; @@ -64,13 +69,13 @@ export class EnterpriseSearchPlugin implements Plugin { * Register space/feature control */ features.registerFeature({ - id: 'enterpriseSearch', - name: 'Enterprise Search', + id: ENTERPRISE_SEARCH_PLUGIN.ID, + name: ENTERPRISE_SEARCH_PLUGIN.NAME, order: 0, icon: 'logoEnterpriseSearch', - navLinkId: 'appSearch', // TODO - remove this once functional tests no longer rely on navLinkId - app: ['kibana', 'appSearch', 'workplaceSearch'], // TODO: 'enterpriseSearch' - catalogue: ['appSearch', 'workplaceSearch'], // TODO: 'enterpriseSearch' + navLinkId: APP_SEARCH_PLUGIN.ID, // TODO - remove this once functional tests no longer rely on navLinkId + app: ['kibana', APP_SEARCH_PLUGIN.ID, WORKPLACE_SEARCH_PLUGIN.ID], + catalogue: [APP_SEARCH_PLUGIN.ID, WORKPLACE_SEARCH_PLUGIN.ID], privileges: null, }); diff --git a/x-pack/plugins/infra/public/containers/metrics_explorer/with_metrics_explorer_options_url_state.test.ts b/x-pack/plugins/infra/public/containers/metrics_explorer/with_metrics_explorer_options_url_state.test.ts new file mode 100644 index 0000000000000..8be34b4498c3f --- /dev/null +++ b/x-pack/plugins/infra/public/containers/metrics_explorer/with_metrics_explorer_options_url_state.test.ts @@ -0,0 +1,70 @@ +/* + * 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 { omit } from 'lodash'; +import { mapToUrlState } from './with_metrics_explorer_options_url_state'; + +describe('WithMetricsExplorerOptionsUrlState', () => { + describe('mapToUrlState', () => { + it('loads a valid URL state', () => { + expect(mapToUrlState(validState)).toEqual(validState); + }); + it('discards invalid properties and loads valid properties into the URL', () => { + expect(mapToUrlState(invalidState)).toEqual(omit(invalidState, 'options')); + }); + }); +}); + +const validState = { + chartOptions: { + stack: false, + type: 'line', + yAxisMode: 'fromZero', + }, + options: { + aggregation: 'avg', + filterQuery: '', + groupBy: ['host.hostname'], + metrics: [ + { + aggregation: 'avg', + color: 'color0', + field: 'system.cpu.user.pct', + }, + { + aggregation: 'avg', + color: 'color1', + field: 'system.load.1', + }, + ], + source: 'url', + }, + timerange: { + from: 'now-1h', + interval: '>=10s', + to: 'now', + }, +}; + +const invalidState = { + chartOptions: { + stack: false, + type: 'line', + yAxisMode: 'fromZero', + }, + options: { + aggregation: 'avg', + filterQuery: '', + groupBy: ['host.hostname'], + metrics: 'this is the wrong data type', + source: 'url', + }, + timerange: { + from: 'now-1h', + interval: '>=10s', + to: 'now', + }, +}; diff --git a/x-pack/plugins/infra/public/containers/metrics_explorer/with_metrics_explorer_options_url_state.tsx b/x-pack/plugins/infra/public/containers/metrics_explorer/with_metrics_explorer_options_url_state.tsx index 35fb66b2620d6..c263d0f68a45e 100644 --- a/x-pack/plugins/infra/public/containers/metrics_explorer/with_metrics_explorer_options_url_state.tsx +++ b/x-pack/plugins/infra/public/containers/metrics_explorer/with_metrics_explorer_options_url_state.tsx @@ -5,19 +5,17 @@ */ import { set } from '@elastic/safer-lodash-set'; -import { values } from 'lodash'; import React, { useContext, useMemo } from 'react'; -import * as t from 'io-ts'; import { ThrowReporter } from 'io-ts/lib/ThrowReporter'; -import { MetricsExplorerColor } from '../../../common/color_palette'; import { UrlStateContainer } from '../../utils/url_state'; import { MetricsExplorerOptions, MetricsExplorerOptionsContainer, MetricsExplorerTimeOptions, - MetricsExplorerYAxisMode, - MetricsExplorerChartType, MetricsExplorerChartOptions, + metricExplorerOptionsRT, + metricsExplorerChartOptionsRT, + metricsExplorerTimeOptionsRT, } from '../../pages/metrics/metrics_explorer/hooks/use_metrics_explorer_options'; interface MetricsExplorerUrlState { @@ -74,36 +72,7 @@ export const WithMetricsExplorerOptionsUrlState = () => { }; function isMetricExplorerOptions(subject: any): subject is MetricsExplorerOptions { - const MetricRequired = t.type({ - aggregation: t.string, - }); - - const MetricOptional = t.partial({ - field: t.string, - rate: t.boolean, - color: t.keyof( - Object.fromEntries(values(MetricsExplorerColor).map((c) => [c, null])) as Record - ), - label: t.string, - }); - - const Metric = t.intersection([MetricRequired, MetricOptional]); - - const OptionsRequired = t.type({ - aggregation: t.string, - metrics: t.array(Metric), - }); - - const OptionsOptional = t.partial({ - limit: t.number, - groupBy: t.string, - filterQuery: t.string, - source: t.string, - }); - - const Options = t.intersection([OptionsRequired, OptionsOptional]); - - const result = Options.decode(subject); + const result = metricExplorerOptionsRT.decode(subject); try { ThrowReporter.report(result); @@ -114,22 +83,7 @@ function isMetricExplorerOptions(subject: any): subject is MetricsExplorerOption } function isMetricExplorerChartOptions(subject: any): subject is MetricsExplorerChartOptions { - const ChartOptions = t.type({ - yAxisMode: t.keyof( - Object.fromEntries(values(MetricsExplorerYAxisMode).map((v) => [v, null])) as Record< - string, - null - > - ), - type: t.keyof( - Object.fromEntries(values(MetricsExplorerChartType).map((v) => [v, null])) as Record< - string, - null - > - ), - stack: t.boolean, - }); - const result = ChartOptions.decode(subject); + const result = metricsExplorerChartOptionsRT.decode(subject); try { ThrowReporter.report(result); @@ -140,12 +94,7 @@ function isMetricExplorerChartOptions(subject: any): subject is MetricsExplorerC } function isMetricExplorerTimeOption(subject: any): subject is MetricsExplorerTimeOptions { - const TimeRange = t.type({ - from: t.string, - to: t.string, - interval: t.string, - }); - const result = TimeRange.decode(subject); + const result = metricsExplorerTimeOptionsRT.decode(subject); try { ThrowReporter.report(result); return true; @@ -154,7 +103,7 @@ function isMetricExplorerTimeOption(subject: any): subject is MetricsExplorerTim } } -const mapToUrlState = (value: any): MetricsExplorerUrlState | undefined => { +export const mapToUrlState = (value: any): MetricsExplorerUrlState | undefined => { const finalState = {}; if (value) { if (value.options && isMetricExplorerOptions(value.options)) { diff --git a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_options.ts b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_options.ts index fa103ce15e3e8..299231f1821f0 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_options.ts +++ b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_options.ts @@ -4,19 +4,29 @@ * you may not use this file except in compliance with the Elastic License. */ +import * as t from 'io-ts'; +import { values } from 'lodash'; import createContainer from 'constate'; import { useState, useEffect, useMemo, Dispatch, SetStateAction } from 'react'; import { useAlertPrefillContext } from '../../../../alerting/use_alert_prefill'; import { MetricsExplorerColor } from '../../../../../common/color_palette'; -import { - MetricsExplorerAggregation, - MetricsExplorerMetric, -} from '../../../../../common/http_api/metrics_explorer'; - -export type MetricsExplorerOptionsMetric = MetricsExplorerMetric & { - color?: MetricsExplorerColor; - label?: string; -}; +import { metricsExplorerMetricRT } from '../../../../../common/http_api/metrics_explorer'; + +const metricsExplorerOptionsMetricRT = t.intersection([ + metricsExplorerMetricRT, + t.partial({ + rate: t.boolean, + color: t.keyof( + Object.fromEntries(values(MetricsExplorerColor).map((c) => [c, null])) as Record< + MetricsExplorerColor, + null + > + ), + label: t.string, + }), +]); + +export type MetricsExplorerOptionsMetric = t.TypeOf; export enum MetricsExplorerChartType { line = 'line', @@ -29,28 +39,50 @@ export enum MetricsExplorerYAxisMode { auto = 'auto', } -export interface MetricsExplorerChartOptions { - type: MetricsExplorerChartType; - yAxisMode: MetricsExplorerYAxisMode; - stack: boolean; -} - -export interface MetricsExplorerOptions { - metrics: MetricsExplorerOptionsMetric[]; - limit?: number; - groupBy?: string | string[]; - filterQuery?: string; - aggregation: MetricsExplorerAggregation; - forceInterval?: boolean; - dropLastBucket?: boolean; - source?: string; -} - -export interface MetricsExplorerTimeOptions { - from: string; - to: string; - interval: string; -} +export const metricsExplorerChartOptionsRT = t.type({ + yAxisMode: t.keyof( + Object.fromEntries(values(MetricsExplorerYAxisMode).map((v) => [v, null])) as Record< + MetricsExplorerYAxisMode, + null + > + ), + type: t.keyof( + Object.fromEntries(values(MetricsExplorerChartType).map((v) => [v, null])) as Record< + MetricsExplorerChartType, + null + > + ), + stack: t.boolean, +}); + +export type MetricsExplorerChartOptions = t.TypeOf; + +const metricExplorerOptionsRequiredRT = t.type({ + aggregation: t.string, + metrics: t.array(metricsExplorerOptionsMetricRT), +}); + +const metricExplorerOptionsOptionalRT = t.partial({ + limit: t.number, + groupBy: t.union([t.string, t.array(t.string)]), + filterQuery: t.string, + source: t.string, + forceInterval: t.boolean, + dropLastBucket: t.boolean, +}); +export const metricExplorerOptionsRT = t.intersection([ + metricExplorerOptionsRequiredRT, + metricExplorerOptionsOptionalRT, +]); + +export type MetricsExplorerOptions = t.TypeOf; + +export const metricsExplorerTimeOptionsRT = t.type({ + from: t.string, + to: t.string, + interval: t.string, +}); +export type MetricsExplorerTimeOptions = t.TypeOf; export const DEFAULT_TIMERANGE: MetricsExplorerTimeOptions = { from: 'now-1h', diff --git a/x-pack/plugins/ingest_manager/common/constants/agent_config.ts b/x-pack/plugins/ingest_manager/common/constants/agent_config.ts index 30ca92f5f32f3..aa6399b73f14e 100644 --- a/x-pack/plugins/ingest_manager/common/constants/agent_config.ts +++ b/x-pack/plugins/ingest_manager/common/constants/agent_config.ts @@ -5,7 +5,7 @@ */ import { AgentConfigStatus, DefaultPackages } from '../types'; -export const AGENT_CONFIG_SAVED_OBJECT_TYPE = 'ingest-agent-configs'; +export const AGENT_CONFIG_SAVED_OBJECT_TYPE = 'ingest-agent-policies'; export const DEFAULT_AGENT_CONFIG = { name: 'Default config', diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/enrollment_instructions/manual/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/enrollment_instructions/manual/index.tsx index 8ea236b2dd6c3..9efc95b0a04cf 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/enrollment_instructions/manual/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/enrollment_instructions/manual/index.tsx @@ -36,8 +36,8 @@ export const ManualInstructions: React.FunctionComponent = ({ systemctl enable elastic-agent systemctl start elastic-agent`; - const windowsCommand = `.\elastic-agent enroll ${enrollArgs} -./install-service-elastic-agent.ps1`; + const windowsCommand = `.\\elastic-agent enroll ${enrollArgs} +.\\install-service-elastic-agent.ps1`; return ( <> diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx index 7b5c714c236b3..90e24f6da5d0a 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx @@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n'; import { Direction, - EuiButtonEmpty, + EuiButton, EuiCallOut, EuiEmptyPrompt, EuiFlexGroup, @@ -147,25 +147,29 @@ export const DataFrameAnalyticsList: FC = ({ return ( <> {i18n.translate('xpack.ml.dataFrame.analyticsList.emptyPromptTitle', { - defaultMessage: 'No data frame analytics jobs found', + defaultMessage: 'Create your first data frame analytics job', })} } actions={ !isManagementTable ? [ - setIsSourceIndexModalVisible(true)} isDisabled={disabled} + color="primary" + iconType="plusInCircle" + fill data-test-subj="mlAnalyticsCreateFirstButton" > {i18n.translate('xpack.ml.dataFrame.analyticsList.emptyPromptButtonText', { - defaultMessage: 'Create your first data frame analytics job', + defaultMessage: 'Create job', })} - , + , ] : [] } diff --git a/x-pack/plugins/task_manager/server/task_events.ts b/x-pack/plugins/task_manager/server/task_events.ts index b17a3636c1730..e1dd85f868cdd 100644 --- a/x-pack/plugins/task_manager/server/task_events.ts +++ b/x-pack/plugins/task_manager/server/task_events.ts @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +import { Option } from 'fp-ts/lib/Option'; + import { ConcreteTaskInstance } from './task'; import { Result, Err } from './lib/result_type'; @@ -22,7 +24,7 @@ export interface TaskEvent { } export type TaskMarkRunning = TaskEvent; export type TaskRun = TaskEvent; -export type TaskClaim = TaskEvent; +export type TaskClaim = TaskEvent>; export type TaskRunRequest = TaskEvent; export function asTaskMarkRunningEvent( @@ -46,7 +48,7 @@ export function asTaskRunEvent(id: string, event: Result + event: Result> ): TaskClaim { return { id, diff --git a/x-pack/plugins/task_manager/server/task_manager.test.ts b/x-pack/plugins/task_manager/server/task_manager.test.ts index 80215ffa7abba..7035971ad6061 100644 --- a/x-pack/plugins/task_manager/server/task_manager.test.ts +++ b/x-pack/plugins/task_manager/server/task_manager.test.ts @@ -7,6 +7,7 @@ import _ from 'lodash'; import sinon from 'sinon'; import { Subject } from 'rxjs'; +import { none } from 'fp-ts/lib/Option'; import { asTaskMarkRunningEvent, @@ -297,7 +298,9 @@ describe('TaskManager', () => { events$.next(asTaskMarkRunningEvent(id, asOk(task))); events$.next(asTaskRunEvent(id, asErr(new Error('some thing gone wrong')))); - return expect(result).rejects.toEqual(new Error('some thing gone wrong')); + return expect(result).rejects.toMatchInlineSnapshot( + `[Error: Failed to run task "01ddff11-e88a-4d13-bc4e-256164e755e2": Error: some thing gone wrong]` + ); }); test('rejects when the task mark as running fails', () => { @@ -311,7 +314,9 @@ describe('TaskManager', () => { events$.next(asTaskClaimEvent(id, asOk(task))); events$.next(asTaskMarkRunningEvent(id, asErr(new Error('some thing gone wrong')))); - return expect(result).rejects.toEqual(new Error('some thing gone wrong')); + return expect(result).rejects.toMatchInlineSnapshot( + `[Error: Failed to run task "01ddff11-e88a-4d13-bc4e-256164e755e2": Error: some thing gone wrong]` + ); }); test('when a task claim fails we ensure the task exists', async () => { @@ -321,7 +326,7 @@ describe('TaskManager', () => { const result = awaitTaskRunResult(id, events$, getLifecycle); - events$.next(asTaskClaimEvent(id, asErr(new Error('failed to claim')))); + events$.next(asTaskClaimEvent(id, asErr(none))); await expect(result).rejects.toEqual( new Error(`Failed to run task "${id}" as it does not exist`) @@ -337,7 +342,7 @@ describe('TaskManager', () => { const result = awaitTaskRunResult(id, events$, getLifecycle); - events$.next(asTaskClaimEvent(id, asErr(new Error('failed to claim')))); + events$.next(asTaskClaimEvent(id, asErr(none))); await expect(result).rejects.toEqual( new Error(`Failed to run task "${id}" as it is currently running`) @@ -353,7 +358,7 @@ describe('TaskManager', () => { const result = awaitTaskRunResult(id, events$, getLifecycle); - events$.next(asTaskClaimEvent(id, asErr(new Error('failed to claim')))); + events$.next(asTaskClaimEvent(id, asErr(none))); await expect(result).rejects.toEqual( new Error(`Failed to run task "${id}" as it is currently running`) @@ -386,9 +391,11 @@ describe('TaskManager', () => { const result = awaitTaskRunResult(id, events$, getLifecycle); - events$.next(asTaskClaimEvent(id, asErr(new Error('failed to claim')))); + events$.next(asTaskClaimEvent(id, asErr(none))); - await expect(result).rejects.toEqual(new Error('failed to claim')); + await expect(result).rejects.toMatchInlineSnapshot( + `[Error: Failed to run task "01ddff11-e88a-4d13-bc4e-256164e755e2" for unknown reason (Current Task Lifecycle is "idle")]` + ); expect(getLifecycle).toHaveBeenCalledWith(id); }); @@ -400,9 +407,11 @@ describe('TaskManager', () => { const result = awaitTaskRunResult(id, events$, getLifecycle); - events$.next(asTaskClaimEvent(id, asErr(new Error('failed to claim')))); + events$.next(asTaskClaimEvent(id, asErr(none))); - await expect(result).rejects.toEqual(new Error('failed to claim')); + await expect(result).rejects.toMatchInlineSnapshot( + `[Error: Failed to run task "01ddff11-e88a-4d13-bc4e-256164e755e2" for unknown reason (Current Task Lifecycle is "failed")]` + ); expect(getLifecycle).toHaveBeenCalledWith(id); }); @@ -424,7 +433,9 @@ describe('TaskManager', () => { events$.next(asTaskRunEvent(id, asErr(new Error('some thing gone wrong')))); - return expect(result).rejects.toEqual(new Error('some thing gone wrong')); + return expect(result).rejects.toMatchInlineSnapshot( + `[Error: Failed to run task "01ddff11-e88a-4d13-bc4e-256164e755e2": Error: some thing gone wrong]` + ); }); }); }); diff --git a/x-pack/plugins/task_manager/server/task_manager.ts b/x-pack/plugins/task_manager/server/task_manager.ts index 35ca439bb9130..7165fd28678c1 100644 --- a/x-pack/plugins/task_manager/server/task_manager.ts +++ b/x-pack/plugins/task_manager/server/task_manager.ts @@ -9,13 +9,14 @@ import { filter } from 'rxjs/operators'; import { performance } from 'perf_hooks'; import { pipe } from 'fp-ts/lib/pipeable'; -import { Option, some, map as mapOptional } from 'fp-ts/lib/Option'; +import { Option, some, map as mapOptional, getOrElse } from 'fp-ts/lib/Option'; + import { SavedObjectsSerializer, ILegacyScopedClusterClient, ISavedObjectsRepository, } from '../../../../src/core/server'; -import { Result, asErr, either, map, mapErr, promiseResult } from './lib/result_type'; +import { Result, asOk, asErr, either, map, mapErr, promiseResult } from './lib/result_type'; import { TaskManagerConfig } from './config'; import { Logger } from './types'; @@ -405,7 +406,9 @@ export async function claimAvailableTasks( if (docs.length !== claimedTasks) { logger.warn( - `[Task Ownership error]: (${claimedTasks}) tasks were claimed by Kibana, but (${docs.length}) tasks were fetched` + `[Task Ownership error]: ${claimedTasks} tasks were claimed by Kibana, but ${ + docs.length + } task(s) were fetched (${docs.map((doc) => doc.id).join(', ')})` ); } return docs; @@ -437,48 +440,65 @@ export async function awaitTaskRunResult( // listen for all events related to the current task .pipe(filter(({ id }: TaskLifecycleEvent) => id === taskId)) .subscribe((taskEvent: TaskLifecycleEvent) => { - either( - taskEvent.event, - (taskInstance: ConcreteTaskInstance) => { - // resolve if the task has run sucessfully - if (isTaskRunEvent(taskEvent)) { - subscription.unsubscribe(); - resolve({ id: taskInstance.id }); - } - }, - async (error: Error) => { + if (isTaskClaimEvent(taskEvent)) { + mapErr(async (error: Option) => { // reject if any error event takes place for the requested task subscription.unsubscribe(); - if (isTaskRunRequestEvent(taskEvent)) { - return reject( - new Error( - `Failed to run task "${taskId}" as Task Manager is at capacity, please try again later` - ) - ); - } else if (isTaskClaimEvent(taskEvent)) { - reject( - map( - // if the error happened in the Claim phase - we try to provide better insight - // into why we failed to claim by getting the task's current lifecycle status - await promiseResult(getLifecycle(taskId)), - (taskLifecycleStatus: TaskLifecycle) => { - if (taskLifecycleStatus === TaskLifecycleResult.NotFound) { - return new Error(`Failed to run task "${taskId}" as it does not exist`); - } else if ( - taskLifecycleStatus === TaskStatus.Running || - taskLifecycleStatus === TaskStatus.Claiming - ) { - return new Error(`Failed to run task "${taskId}" as it is currently running`); - } - return error; - }, - () => error - ) - ); + return reject( + map( + await pipe( + error, + mapOptional(async (taskReturnedBySweep) => asOk(taskReturnedBySweep.status)), + getOrElse(() => + // if the error happened in the Claim phase - we try to provide better insight + // into why we failed to claim by getting the task's current lifecycle status + promiseResult(getLifecycle(taskId)) + ) + ), + (taskLifecycleStatus: TaskLifecycle) => { + if (taskLifecycleStatus === TaskLifecycleResult.NotFound) { + return new Error(`Failed to run task "${taskId}" as it does not exist`); + } else if ( + taskLifecycleStatus === TaskStatus.Running || + taskLifecycleStatus === TaskStatus.Claiming + ) { + return new Error(`Failed to run task "${taskId}" as it is currently running`); + } + return new Error( + `Failed to run task "${taskId}" for unknown reason (Current Task Lifecycle is "${taskLifecycleStatus}")` + ); + }, + (getLifecycleError: Error) => + new Error( + `Failed to run task "${taskId}" and failed to get current Status:${getLifecycleError}` + ) + ) + ); + }, taskEvent.event); + } else { + either>( + taskEvent.event, + (taskInstance: ConcreteTaskInstance) => { + // resolve if the task has run sucessfully + if (isTaskRunEvent(taskEvent)) { + subscription.unsubscribe(); + resolve({ id: taskInstance.id }); + } + }, + async (error: Error | Option) => { + // reject if any error event takes place for the requested task + subscription.unsubscribe(); + if (isTaskRunRequestEvent(taskEvent)) { + return reject( + new Error( + `Failed to run task "${taskId}" as Task Manager is at capacity, please try again later` + ) + ); + } + return reject(new Error(`Failed to run task "${taskId}": ${error}`)); } - return reject(error); - } - ); + ); + } }); }); } diff --git a/x-pack/plugins/task_manager/server/task_store.test.ts b/x-pack/plugins/task_manager/server/task_store.test.ts index 771b4e2d7d9cb..d65c39f4f454d 100644 --- a/x-pack/plugins/task_manager/server/task_store.test.ts +++ b/x-pack/plugins/task_manager/server/task_store.test.ts @@ -8,6 +8,7 @@ import _ from 'lodash'; import sinon from 'sinon'; import uuid from 'uuid'; import { filter } from 'rxjs/operators'; +import { Option, some, none } from 'fp-ts/lib/Option'; import { TaskDictionary, @@ -972,7 +973,7 @@ if (doc['task.runAt'].size()!=0) { const runAt = new Date(); const tasks = [ { - _id: 'aaa', + _id: 'claimed-by-id', _source: { type: 'task', task: { @@ -980,7 +981,7 @@ if (doc['task.runAt'].size()!=0) { taskType: 'foo', schedule: undefined, attempts: 0, - status: 'idle', + status: 'claiming', params: '{ "hello": "world" }', state: '{ "baby": "Henhen" }', user: 'jimbo', @@ -996,7 +997,31 @@ if (doc['task.runAt'].size()!=0) { sort: ['a', 1], }, { - _id: 'bbb', + _id: 'claimed-by-schedule', + _source: { + type: 'task', + task: { + runAt, + taskType: 'bar', + schedule: { interval: '5m' }, + attempts: 2, + status: 'claiming', + params: '{ "shazm": 1 }', + state: '{ "henry": "The 8th" }', + user: 'dabo', + scope: ['reporting', 'ceo'], + ownerId: taskManagerId, + startedAt: null, + retryAt: null, + scheduledAt: new Date(), + }, + }, + _seq_no: 3, + _primary_term: 4, + sort: ['b', 2], + }, + { + _id: 'already-running', _source: { type: 'task', task: { @@ -1045,19 +1070,24 @@ if (doc['task.runAt'].size()!=0) { }); const sub = store.events - .pipe(filter((event: TaskEvent) => event.id === 'aaa')) + .pipe( + filter( + (event: TaskEvent>) => + event.id === 'claimed-by-id' + ) + ) .subscribe({ - next: (event: TaskEvent) => { + next: (event: TaskEvent>) => { expect(event).toMatchObject( asTaskClaimEvent( - 'aaa', + 'claimed-by-id', asOk({ - id: 'aaa', + id: 'claimed-by-id', runAt, taskType: 'foo', schedule: undefined, attempts: 0, - status: 'idle' as TaskStatus, + status: 'claiming' as TaskStatus, params: { hello: 'world' }, state: { baby: 'Henhen' }, user: 'jimbo', @@ -1075,7 +1105,7 @@ if (doc['task.runAt'].size()!=0) { }); await store.claimAvailableTasks({ - claimTasksById: ['aaa'], + claimTasksById: ['claimed-by-id'], claimOwnershipUntil: new Date(), size: 10, }); @@ -1102,19 +1132,24 @@ if (doc['task.runAt'].size()!=0) { }); const sub = store.events - .pipe(filter((event: TaskEvent) => event.id === 'bbb')) + .pipe( + filter( + (event: TaskEvent>) => + event.id === 'claimed-by-schedule' + ) + ) .subscribe({ - next: (event: TaskEvent) => { + next: (event: TaskEvent>) => { expect(event).toMatchObject( asTaskClaimEvent( - 'bbb', + 'claimed-by-schedule', asOk({ - id: 'bbb', + id: 'claimed-by-schedule', runAt, taskType: 'bar', schedule: { interval: '5m' }, attempts: 2, - status: 'running' as TaskStatus, + status: 'claiming' as TaskStatus, params: { shazm: 1 }, state: { henry: 'The 8th' }, user: 'dabo', @@ -1132,14 +1167,14 @@ if (doc['task.runAt'].size()!=0) { }); await store.claimAvailableTasks({ - claimTasksById: ['aaa'], + claimTasksById: ['claimed-by-id'], claimOwnershipUntil: new Date(), size: 10, }); }); test('emits an event when the store fails to claim a required task by id', async (done) => { - const { taskManagerId, tasks } = generateTasks(); + const { taskManagerId, runAt, tasks } = generateTasks(); const callCluster = sinon.spy(async (name: string, params?: unknown) => name === 'updateByQuery' ? { @@ -1159,11 +1194,36 @@ if (doc['task.runAt'].size()!=0) { }); const sub = store.events - .pipe(filter((event: TaskEvent) => event.id === 'ccc')) + .pipe( + filter( + (event: TaskEvent>) => + event.id === 'already-running' + ) + ) .subscribe({ - next: (event: TaskEvent) => { + next: (event: TaskEvent>) => { expect(event).toMatchObject( - asTaskClaimEvent('ccc', asErr(new Error(`failed to claim task 'ccc'`))) + asTaskClaimEvent( + 'already-running', + asErr( + some({ + id: 'already-running', + runAt, + taskType: 'bar', + schedule: { interval: '5m' }, + attempts: 2, + status: 'running' as TaskStatus, + params: { shazm: 1 }, + state: { henry: 'The 8th' }, + user: 'dabo', + scope: ['reporting', 'ceo'], + ownerId: taskManagerId, + startedAt: null, + retryAt: null, + scheduledAt: new Date(), + }) + ) + ) ); sub.unsubscribe(); done(); @@ -1171,7 +1231,49 @@ if (doc['task.runAt'].size()!=0) { }); await store.claimAvailableTasks({ - claimTasksById: ['ccc'], + claimTasksById: ['already-running'], + claimOwnershipUntil: new Date(), + size: 10, + }); + }); + + test('emits an event when the store fails to find a task which was required by id', async (done) => { + const { taskManagerId, tasks } = generateTasks(); + const callCluster = sinon.spy(async (name: string, params?: unknown) => + name === 'updateByQuery' + ? { + total: tasks.length, + updated: tasks.length, + } + : { hits: { hits: tasks } } + ); + const store = new TaskStore({ + callCluster, + maxAttempts: 2, + definitions: taskDefinitions, + serializer, + savedObjectsRepository: savedObjectsClient, + taskManagerId, + index: '', + }); + + const sub = store.events + .pipe( + filter( + (event: TaskEvent>) => + event.id === 'unknown-task' + ) + ) + .subscribe({ + next: (event: TaskEvent>) => { + expect(event).toMatchObject(asTaskClaimEvent('unknown-task', asErr(none))); + sub.unsubscribe(); + done(); + }, + }); + + await store.claimAvailableTasks({ + claimTasksById: ['unknown-task'], claimOwnershipUntil: new Date(), size: 10, }); diff --git a/x-pack/plugins/task_manager/server/task_store.ts b/x-pack/plugins/task_manager/server/task_store.ts index cac37db72202d..a18fb57b35b3d 100644 --- a/x-pack/plugins/task_manager/server/task_store.ts +++ b/x-pack/plugins/task_manager/server/task_store.ts @@ -9,7 +9,9 @@ */ import apm from 'elastic-apm-node'; import { Subject, Observable } from 'rxjs'; -import { omit, difference, defaults } from 'lodash'; +import { omit, difference, partition, map, defaults } from 'lodash'; + +import { some, none } from 'fp-ts/lib/Option'; import { SearchResponse, UpdateDocumentByQueryResponse } from 'elasticsearch'; import { @@ -31,6 +33,7 @@ import { TaskLifecycle, TaskLifecycleResult, SerializedConcreteTaskInstance, + TaskStatus, } from './task'; import { TaskClaim, asTaskClaimEvent } from './task_events'; @@ -221,13 +224,35 @@ export class TaskStore { // emit success/fail events for claimed tasks by id if (claimTasksById && claimTasksById.length) { - this.emitEvents(docs.map((doc) => asTaskClaimEvent(doc.id, asOk(doc)))); + const [documentsReturnedById, documentsClaimedBySchedule] = partition(docs, (doc) => + claimTasksById.includes(doc.id) + ); + + const [documentsClaimedById, documentsRequestedButNotClaimed] = partition( + documentsReturnedById, + // we filter the schduled tasks down by status is 'claiming' in the esearch, + // but we do not apply this limitation on tasks claimed by ID so that we can + // provide more detailed error messages when we fail to claim them + (doc) => doc.status === TaskStatus.Claiming + ); + + const documentsRequestedButNotReturned = difference( + claimTasksById, + map(documentsReturnedById, 'id') + ); + + this.emitEvents( + [...documentsClaimedById, ...documentsClaimedBySchedule].map((doc) => + asTaskClaimEvent(doc.id, asOk(doc)) + ) + ); + + this.emitEvents( + documentsRequestedButNotClaimed.map((doc) => asTaskClaimEvent(doc.id, asErr(some(doc)))) + ); this.emitEvents( - difference( - claimTasksById, - docs.map((doc) => doc.id) - ).map((id) => asTaskClaimEvent(id, asErr(new Error(`failed to claim task '${id}'`)))) + documentsRequestedButNotReturned.map((id) => asTaskClaimEvent(id, asErr(none))) ); } diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 998d5f488629b..028d647cefd58 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -4529,7 +4529,6 @@ "visualize.createVisualization.noVisTypeErrorMessage": "有効なビジュアライゼーションタイプを指定してください", "visualize.editor.createBreadcrumb": "作成", "visualize.error.title": "ビジュアライゼーションエラー", - "visualize.experimentalVisInfoText": "このビジュアライゼーションは実験的なものです。フィードバックがありますか?で問題を報告してください。", "visualize.helpMenu.appName": "可視化", "visualize.linkedToSearch.unlinkSuccessNotificationText": "保存された検索「{searchTitle}」からリンクが解除されました", "visualize.listing.betaTitle": "ベータ", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 814bdf8b3d45c..c826953f5418b 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -4530,7 +4530,6 @@ "visualize.createVisualization.noVisTypeErrorMessage": "必须提供有效的可视化类型", "visualize.editor.createBreadcrumb": "创建", "visualize.error.title": "可视化错误", - "visualize.experimentalVisInfoText": "此可视化标记为“实验性”。想反馈?请在以下位置创建问题:", "visualize.helpMenu.appName": "Visualize", "visualize.linkedToSearch.unlinkSuccessNotificationText": "已取消与已保存搜索“{searchTitle}”的链接", "visualize.listing.betaTitle": "公测版", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx index 2b2897a2181b1..3d55c51e45281 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx @@ -152,6 +152,10 @@ export const AlertsList: React.FunctionComponent = () => { data: alertsResponse.data, totalItemCount: alertsResponse.total, }); + + if (!alertsResponse.data?.length && page.index > 0) { + setPage({ ...page, index: 0 }); + } } catch (e) { toastNotifications.addDanger({ title: i18n.translate( @@ -399,18 +403,9 @@ export const AlertsList: React.FunctionComponent = () => { return (
{ - if (selectedIds.length === 0 || selectedIds.length === deleted.length) { - const updatedAlerts = alertsState.data.filter( - (alert) => alert.id && !alertsToDelete.includes(alert.id) - ); - setAlertsState({ - isLoading: false, - data: updatedAlerts, - totalItemCount: alertsState.totalItemCount - deleted.length, - }); - setSelectedIds([]); - } + onDeleted={async (deleted: string[]) => { + loadAlertsData(); + setSelectedIds([]); setAlertsToDelete([]); }} onErrors={async () => { diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/task_manager_fixture/server/plugin.ts b/x-pack/test/alerting_api_integration/common/fixtures/plugins/task_manager_fixture/server/plugin.ts index 18fdd5f9c3ac3..0833dd0425894 100644 --- a/x-pack/test/alerting_api_integration/common/fixtures/plugins/task_manager_fixture/server/plugin.ts +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/task_manager_fixture/server/plugin.ts @@ -51,7 +51,8 @@ export class SampleTaskManagerFixturePlugin .toPromise(); public setup(core: CoreSetup) { - core.http.createRouter().get( + const router = core.http.createRouter(); + router.get( { path: '/api/alerting_tasks/{taskId}', validate: { @@ -77,6 +78,23 @@ export class SampleTaskManagerFixturePlugin } } ); + + router.get( + { + path: `/api/ensure_tasks_index_refreshed`, + validate: {}, + }, + async function ( + context: RequestHandlerContext, + req: KibanaRequest, + res: KibanaResponseFactory + ): Promise> { + await core.elasticsearch.legacy.client.callAsInternalUser('indices.refresh', { + index: '.kibana_task_manager', + }); + return res.ok({ body: {} }); + } + ); } public start(core: CoreStart, { taskManager }: SampleTaskManagerFixtureStartDeps) { diff --git a/x-pack/test/functional/es_archives/fleet/agents/data.json b/x-pack/test/functional/es_archives/fleet/agents/data.json index b3d49199b0d9e..c94b87f6ad1ec 100644 --- a/x-pack/test/functional/es_archives/fleet/agents/data.json +++ b/x-pack/test/functional/es_archives/fleet/agents/data.json @@ -203,11 +203,11 @@ { "type": "doc", "value": { - "id": "ingest-agent-configs:config1", + "id": "ingest-agent-policies:config1", "index": ".kibana", "source": { - "type": "ingest-agent-configs", - "ingest-agent-configs": { + "type": "ingest-agent-policies", + "ingest-agent-policies": { "name": "Test config", "namespace": "default", "description": "Config 1", diff --git a/x-pack/test/functional/es_archives/fleet/agents/mappings.json b/x-pack/test/functional/es_archives/fleet/agents/mappings.json index 1f0aa2f24d6df..acc32c3e2cbb5 100644 --- a/x-pack/test/functional/es_archives/fleet/agents/mappings.json +++ b/x-pack/test/functional/es_archives/fleet/agents/mappings.json @@ -58,7 +58,7 @@ "siem-ui-timeline": "f2d929253ecd06ffbac78b4047f45a86", "kql-telemetry": "d12a98a6f19a2d273696597547e064ee", "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", - "ingest-agent-configs": "f4bdc17427437537ca1754d5d5057ad5", + "ingest-agent-policies": "f4bdc17427437537ca1754d5d5057ad5", "url": "b675c3be8d76ecf029294d51dc7ec65d", "migrationVersion": "4a1746014a75ade3a714e1db5763276f", "index-pattern": "66eccb05066c5a89924f48a9e9736499", @@ -1797,7 +1797,7 @@ } } }, - "ingest-agent-configs": { + "ingest-agent-policies": { "properties": { "package_configs": { "type": "keyword" diff --git a/x-pack/test/functional/es_archives/lists/mappings.json b/x-pack/test/functional/es_archives/lists/mappings.json index c1b277b8183a3..2fc1f1a3111a7 100644 --- a/x-pack/test/functional/es_archives/lists/mappings.json +++ b/x-pack/test/functional/es_archives/lists/mappings.json @@ -61,7 +61,7 @@ "siem-ui-timeline": "94bc38c7a421d15fbfe8ea565370a421", "kql-telemetry": "d12a98a6f19a2d273696597547e064ee", "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", - "ingest-agent-configs": "9326f99c977fd2ef5ab24b6336a0675c", + "ingest-agent-policies": "9326f99c977fd2ef5ab24b6336a0675c", "url": "c7f66a0df8b1b52f17c28c4adb111105", "endpoint:user-artifact-manifest": "67c28185da541c1404e7852d30498cd6", "migrationVersion": "4a1746014a75ade3a714e1db5763276f", @@ -1210,7 +1210,7 @@ } } }, - "ingest-agent-configs": { + "ingest-agent-policies": { "properties": { "description": { "type": "text" @@ -2488,4 +2488,4 @@ } } } -} \ No newline at end of file +} diff --git a/x-pack/test/functional/es_archives/reporting/canvas_disallowed_url/mappings.json b/x-pack/test/functional/es_archives/reporting/canvas_disallowed_url/mappings.json index 1432a53b45461..1fd338fbb0ffb 100644 --- a/x-pack/test/functional/es_archives/reporting/canvas_disallowed_url/mappings.json +++ b/x-pack/test/functional/es_archives/reporting/canvas_disallowed_url/mappings.json @@ -2,8 +2,7 @@ "type": "index", "value": { "aliases": { - ".kibana": { - } + ".kibana": {} }, "index": ".kibana_1", "mappings": { @@ -38,7 +37,7 @@ "fleet-enrollment-api-keys": "28b91e20b105b6f928e2012600085d8f", "graph-workspace": "cd7ba1330e6682e9cc00b78850874be1", "index-pattern": "66eccb05066c5a89924f48a9e9736499", - "ingest-agent-configs": "9326f99c977fd2ef5ab24b6336a0675c", + "ingest-agent-policies": "9326f99c977fd2ef5ab24b6336a0675c", "ingest-outputs": "8aa988c376e65443fefc26f1075e93a3", "ingest-package-configs": "48e8bd97e488008e21c0b5a2367b83ad", "ingest_manager_settings": "012cf278ec84579495110bb827d1ed09", @@ -1149,7 +1148,7 @@ } } }, - "ingest-agent-configs": { + "ingest-agent-policies": { "properties": { "description": { "type": "text" @@ -2213,4 +2212,4 @@ } } } -} \ No newline at end of file +} diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts index fa714e8374ec7..56952919e416a 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts @@ -5,6 +5,7 @@ */ import uuid from 'uuid'; +import { times } from 'lodash'; import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; @@ -361,11 +362,22 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); it('should delete all selection', async () => { - const createdAlert = await createAlert(); + const namePrefix = generateUniqueKey(); + let count = 0; + const createdAlertsFirstPage = await Promise.all( + times(10, () => createAlert({ name: `${namePrefix}-0${count++}` })) + ); + + const createdAlertsSecondPage = await Promise.all( + times(2, () => createAlert({ name: `${namePrefix}-1${count++}` })) + ); + await pageObjects.common.navigateToApp('triggersActions'); - await pageObjects.triggersActionsUI.searchAlerts(createdAlert.name); + await pageObjects.triggersActionsUI.searchAlerts(namePrefix); - await testSubjects.click(`checkboxSelectRow-${createdAlert.id}`); + for (const createdAlert of createdAlertsFirstPage) { + await testSubjects.click(`checkboxSelectRow-${createdAlert.id}`); + } await testSubjects.click('bulkAction'); @@ -377,9 +389,11 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await pageObjects.common.closeToast(); await pageObjects.common.navigateToApp('triggersActions'); - await pageObjects.triggersActionsUI.searchAlerts(createdAlert.name); + await pageObjects.triggersActionsUI.searchAlerts(namePrefix); const searchResultsAfterDelete = await pageObjects.triggersActionsUI.getAlertsList(); - expect(searchResultsAfterDelete.length).to.eql(0); + expect(searchResultsAfterDelete).to.have.length(2); + expect(searchResultsAfterDelete[0].name).to.eql(createdAlertsSecondPage[0].name); + expect(searchResultsAfterDelete[1].name).to.eql(createdAlertsSecondPage[1].name); }); }); }; diff --git a/x-pack/test/ingest_manager_api_integration/apis/epm/list.ts b/x-pack/test/ingest_manager_api_integration/apis/epm/list.ts index 20414fcb90521..0b6a37d77387e 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/epm/list.ts +++ b/x-pack/test/ingest_manager_api_integration/apis/epm/list.ts @@ -29,7 +29,7 @@ export default function ({ getService }: FtrProviderContext) { return response.body; }; const listResponse = await fetchPackageList(); - expect(listResponse.response.length).to.be(14); + expect(listResponse.response.length).to.be(8); } else { warnAndSkipTest(this, log); } diff --git a/x-pack/test/ingest_manager_api_integration/apis/fixtures/test_packages/all_assets/0.1.0/dataset/test_logs/fields/fields.yml b/x-pack/test/ingest_manager_api_integration/apis/fixtures/test_packages/all_assets/0.1.0/dataset/test_logs/fields/fields.yml index 12a9a03c1337b..6e003ed0ad147 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/fixtures/test_packages/all_assets/0.1.0/dataset/test_logs/fields/fields.yml +++ b/x-pack/test/ingest_manager_api_integration/apis/fixtures/test_packages/all_assets/0.1.0/dataset/test_logs/fields/fields.yml @@ -1,15 +1,15 @@ -- name: dataset.type +- name: data_stream.type type: constant_keyword description: > - Dataset type. -- name: dataset.name + Data stream type. +- name: data_stream.dataset type: constant_keyword description: > - Dataset name. -- name: dataset.namespace + Data stream dataset. +- name: data_stream.namespace type: constant_keyword description: > - Dataset namespace. + Data stream namespace. - name: '@timestamp' type: date description: > diff --git a/x-pack/test/ingest_manager_api_integration/apis/fixtures/test_packages/all_assets/0.1.0/dataset/test_metrics/fields/fields.yml b/x-pack/test/ingest_manager_api_integration/apis/fixtures/test_packages/all_assets/0.1.0/dataset/test_metrics/fields/fields.yml index 12a9a03c1337b..6e003ed0ad147 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/fixtures/test_packages/all_assets/0.1.0/dataset/test_metrics/fields/fields.yml +++ b/x-pack/test/ingest_manager_api_integration/apis/fixtures/test_packages/all_assets/0.1.0/dataset/test_metrics/fields/fields.yml @@ -1,15 +1,15 @@ -- name: dataset.type +- name: data_stream.type type: constant_keyword description: > - Dataset type. -- name: dataset.name + Data stream type. +- name: data_stream.dataset type: constant_keyword description: > - Dataset name. -- name: dataset.namespace + Data stream dataset. +- name: data_stream.namespace type: constant_keyword description: > - Dataset namespace. + Data stream namespace. - name: '@timestamp' type: date description: > diff --git a/x-pack/test/ingest_manager_api_integration/apis/fixtures/test_packages/multiple_versions/0.1.0/dataset/test/fields/fields.yml b/x-pack/test/ingest_manager_api_integration/apis/fixtures/test_packages/multiple_versions/0.1.0/dataset/test/fields/fields.yml index 12a9a03c1337b..6e003ed0ad147 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/fixtures/test_packages/multiple_versions/0.1.0/dataset/test/fields/fields.yml +++ b/x-pack/test/ingest_manager_api_integration/apis/fixtures/test_packages/multiple_versions/0.1.0/dataset/test/fields/fields.yml @@ -1,15 +1,15 @@ -- name: dataset.type +- name: data_stream.type type: constant_keyword description: > - Dataset type. -- name: dataset.name + Data stream type. +- name: data_stream.dataset type: constant_keyword description: > - Dataset name. -- name: dataset.namespace + Data stream dataset. +- name: data_stream.namespace type: constant_keyword description: > - Dataset namespace. + Data stream namespace. - name: '@timestamp' type: date description: > diff --git a/x-pack/test/ingest_manager_api_integration/apis/fixtures/test_packages/multiple_versions/0.2.0/dataset/test/fields/fields.yml b/x-pack/test/ingest_manager_api_integration/apis/fixtures/test_packages/multiple_versions/0.2.0/dataset/test/fields/fields.yml index 12a9a03c1337b..6e003ed0ad147 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/fixtures/test_packages/multiple_versions/0.2.0/dataset/test/fields/fields.yml +++ b/x-pack/test/ingest_manager_api_integration/apis/fixtures/test_packages/multiple_versions/0.2.0/dataset/test/fields/fields.yml @@ -1,15 +1,15 @@ -- name: dataset.type +- name: data_stream.type type: constant_keyword description: > - Dataset type. -- name: dataset.name + Data stream type. +- name: data_stream.dataset type: constant_keyword description: > - Dataset name. -- name: dataset.namespace + Data stream dataset. +- name: data_stream.namespace type: constant_keyword description: > - Dataset namespace. + Data stream namespace. - name: '@timestamp' type: date description: > diff --git a/x-pack/test/ingest_manager_api_integration/apis/fixtures/test_packages/multiple_versions/0.3.0/dataset/test/fields/fields.yml b/x-pack/test/ingest_manager_api_integration/apis/fixtures/test_packages/multiple_versions/0.3.0/dataset/test/fields/fields.yml index 12a9a03c1337b..6e003ed0ad147 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/fixtures/test_packages/multiple_versions/0.3.0/dataset/test/fields/fields.yml +++ b/x-pack/test/ingest_manager_api_integration/apis/fixtures/test_packages/multiple_versions/0.3.0/dataset/test/fields/fields.yml @@ -1,15 +1,15 @@ -- name: dataset.type +- name: data_stream.type type: constant_keyword description: > - Dataset type. -- name: dataset.name + Data stream type. +- name: data_stream.dataset type: constant_keyword description: > - Dataset name. -- name: dataset.namespace + Data stream dataset. +- name: data_stream.namespace type: constant_keyword description: > - Dataset namespace. + Data stream namespace. - name: '@timestamp' type: date description: > diff --git a/x-pack/test/ingest_manager_api_integration/apis/fixtures/test_packages/overrides/0.1.0/dataset/test/fields/fields.yml b/x-pack/test/ingest_manager_api_integration/apis/fixtures/test_packages/overrides/0.1.0/dataset/test/fields/fields.yml index 12a9a03c1337b..6e003ed0ad147 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/fixtures/test_packages/overrides/0.1.0/dataset/test/fields/fields.yml +++ b/x-pack/test/ingest_manager_api_integration/apis/fixtures/test_packages/overrides/0.1.0/dataset/test/fields/fields.yml @@ -1,15 +1,15 @@ -- name: dataset.type +- name: data_stream.type type: constant_keyword description: > - Dataset type. -- name: dataset.name + Data stream type. +- name: data_stream.dataset type: constant_keyword description: > - Dataset name. -- name: dataset.namespace + Data stream dataset. +- name: data_stream.namespace type: constant_keyword description: > - Dataset namespace. + Data stream namespace. - name: '@timestamp' type: date description: > diff --git a/x-pack/test/ingest_manager_api_integration/apis/package_config/create.ts b/x-pack/test/ingest_manager_api_integration/apis/package_config/create.ts index cae4ff79bdef6..a2c2b99364d50 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/package_config/create.ts +++ b/x-pack/test/ingest_manager_api_integration/apis/package_config/create.ts @@ -100,7 +100,7 @@ export default function ({ getService }: FtrProviderContext) { package: { name: 'endpoint', title: 'Endpoint', - version: '0.8.0', + version: '0.13.0', }, }) .expect(200); @@ -118,7 +118,7 @@ export default function ({ getService }: FtrProviderContext) { package: { name: 'endpoint', title: 'Endpoint', - version: '0.8.0', + version: '0.13.0', }, }) .expect(500); diff --git a/x-pack/test/ingest_manager_api_integration/config.ts b/x-pack/test/ingest_manager_api_integration/config.ts index 85d1c20c7f155..08d5da148b51e 100644 --- a/x-pack/test/ingest_manager_api_integration/config.ts +++ b/x-pack/test/ingest_manager_api_integration/config.ts @@ -12,7 +12,7 @@ import { defineDockerServersConfig } from '@kbn/test'; // Docker image to use for Ingest Manager API integration tests. // This hash comes from the commit hash here: https://github.com/elastic/package-storage/commit export const dockerImage = - 'docker.elastic.co/package-registry/distribution:80e93ade87f65e18d487b1c407406825915daba8'; + 'docker.elastic.co/package-registry/distribution:f6b01daec8cfe355101e366de9941d35a4c3763e'; export default async function ({ readConfigFile }: FtrConfigProviderContext) { const xPackAPITestsConfig = await readConfigFile(require.resolve('../api_integration/config.ts')); diff --git a/x-pack/test/plugin_api_integration/plugins/sample_task_plugin/server/init_routes.ts b/x-pack/test/plugin_api_integration/plugins/sample_task_plugin/server/init_routes.ts index f35d6baac8f5a..266e66b5a1a45 100644 --- a/x-pack/test/plugin_api_integration/plugins/sample_task_plugin/server/init_routes.ts +++ b/x-pack/test/plugin_api_integration/plugins/sample_task_plugin/server/init_routes.ts @@ -223,6 +223,21 @@ export function initRoutes( } ); + router.get( + { + path: `/api/ensure_tasks_index_refreshed`, + validate: {}, + }, + async function ( + context: RequestHandlerContext, + req: KibanaRequest, + res: KibanaResponseFactory + ): Promise> { + await ensureIndexIsRefreshed(); + return res.ok({ body: {} }); + } + ); + router.delete( { path: `/api/sample_tasks`, diff --git a/x-pack/test/plugin_api_integration/test_suites/task_manager/task_manager_integration.js b/x-pack/test/plugin_api_integration/test_suites/task_manager/task_manager_integration.js index 165e79fb311ea..c87a5039360b8 100644 --- a/x-pack/test/plugin_api_integration/test_suites/task_manager/task_manager_integration.js +++ b/x-pack/test/plugin_api_integration/test_suites/task_manager/task_manager_integration.js @@ -28,8 +28,7 @@ export default function ({ getService }) { const testHistoryIndex = '.kibana_task_manager_test_result'; const supertest = supertestAsPromised(url.format(config.get('servers.kibana'))); - // FLAKY: https://github.com/elastic/kibana/issues/71390 - describe.skip('scheduling and running tasks', () => { + describe('scheduling and running tasks', () => { beforeEach( async () => await supertest.delete('/api/sample_tasks').set('kbn-xsrf', 'xxx').expect(200) ); @@ -69,6 +68,14 @@ export default function ({ getService }) { .then((response) => response.body); } + function ensureTasksIndexRefreshed() { + return supertest + .get(`/api/ensure_tasks_index_refreshed`) + .send({}) + .expect(200) + .then((response) => response.body); + } + function historyDocs(taskId) { return es .search({ @@ -404,7 +411,7 @@ export default function ({ getService }) { const originalTask = await scheduleTask({ taskType: 'sampleTask', schedule: { interval: `30m` }, - params: { failWith: 'error on run now', failOn: 3 }, + params: { failWith: 'this task was meant to fail!', failOn: 3 }, }); await retry.try(async () => { @@ -415,11 +422,14 @@ export default function ({ getService }) { const task = await currentTask(originalTask.id); expect(task.state.count).to.eql(1); + expect(task.status).to.eql('idle'); // ensure this task shouldnt run for another half hour expectReschedule(Date.parse(originalTask.runAt), task, 30 * 60000); }); + await ensureTasksIndexRefreshed(); + // second run should still be successful const successfulRunNowResult = await runTaskNow({ id: originalTask.id, @@ -429,14 +439,20 @@ export default function ({ getService }) { await retry.try(async () => { const task = await currentTask(originalTask.id); expect(task.state.count).to.eql(2); + expect(task.status).to.eql('idle'); }); + await ensureTasksIndexRefreshed(); + // third run should fail const failedRunNowResult = await runTaskNow({ id: originalTask.id, }); - expect(failedRunNowResult).to.eql({ id: originalTask.id, error: `Error: error on run now` }); + expect(failedRunNowResult).to.eql({ + id: originalTask.id, + error: `Error: Failed to run task \"${originalTask.id}\": Error: this task was meant to fail!`, + }); await retry.try(async () => { expect( @@ -479,8 +495,13 @@ export default function ({ getService }) { expect( docs.filter((taskDoc) => taskDoc._source.taskId === longRunningTask.id).length ).to.eql(1); + + const task = await currentTask(longRunningTask.id); + expect(task.status).to.eql('running'); }); + await ensureTasksIndexRefreshed(); + // first runNow should fail const failedRunNowResult = await runTaskNow({ id: longRunningTask.id, @@ -496,8 +517,13 @@ export default function ({ getService }) { await retry.try(async () => { const tasks = (await currentTasks()).docs; expect(getTaskById(tasks, longRunningTask.id).state.count).to.eql(1); + + const task = await currentTask(longRunningTask.id); + expect(task.status).to.eql('idle'); }); + await ensureTasksIndexRefreshed(); + // second runNow should be successful const successfulRunNowResult = runTaskNow({ id: longRunningTask.id, diff --git a/x-pack/test/security_solution_cypress/es_archives/export_rule/mappings.json b/x-pack/test/security_solution_cypress/es_archives/export_rule/mappings.json index 2cfa0bde4e977..dc92d23a618d3 100644 --- a/x-pack/test/security_solution_cypress/es_archives/export_rule/mappings.json +++ b/x-pack/test/security_solution_cypress/es_archives/export_rule/mappings.json @@ -39,7 +39,7 @@ "graph-workspace": "cd7ba1330e6682e9cc00b78850874be1", "index-pattern": "66eccb05066c5a89924f48a9e9736499", "infrastructure-ui-source": "2b2809653635caf490c93f090502d04c", - "ingest-agent-configs": "9326f99c977fd2ef5ab24b6336a0675c", + "ingest-agent-policies": "9326f99c977fd2ef5ab24b6336a0675c", "ingest-outputs": "8aa988c376e65443fefc26f1075e93a3", "ingest-package-configs": "48e8bd97e488008e21c0b5a2367b83ad", "ingest_manager_settings": "012cf278ec84579495110bb827d1ed09", @@ -1222,7 +1222,7 @@ } } }, - "ingest-agent-configs": { + "ingest-agent-policies": { "properties": { "description": { "type": "text"