diff --git a/x-pack/plugins/screenshotting/common/context.ts b/x-pack/plugins/screenshotting/common/context.ts
deleted file mode 100644
index c47f8706533b8..0000000000000
--- a/x-pack/plugins/screenshotting/common/context.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-/**
- * Screenshot context.
- * This is a serializable object that can be passed from the screenshotting backend and then deserialized on the target page.
- */
-export type Context = Record
;
-
-/**
- * @interal
- */
-export const SCREENSHOTTING_CONTEXT_KEY = '__SCREENSHOTTING_CONTEXT_KEY__';
diff --git a/x-pack/plugins/screenshotting/common/index.ts b/x-pack/plugins/screenshotting/common/index.ts
index 04296dd5426b5..1492f3f945abe 100644
--- a/x-pack/plugins/screenshotting/common/index.ts
+++ b/x-pack/plugins/screenshotting/common/index.ts
@@ -5,6 +5,5 @@
* 2.0.
*/
-export type { Context } from './context';
export type { LayoutParams } from './layout';
export { LayoutTypes } from './layout';
diff --git a/x-pack/plugins/screenshotting/kibana.json b/x-pack/plugins/screenshotting/kibana.json
index 32446551627e0..9a071bdf46f46 100644
--- a/x-pack/plugins/screenshotting/kibana.json
+++ b/x-pack/plugins/screenshotting/kibana.json
@@ -9,6 +9,5 @@
"description": "Kibana Screenshotting Plugin",
"requiredPlugins": ["screenshotMode"],
"configPath": ["xpack", "screenshotting"],
- "server": true,
- "ui": true
+ "server": true
}
diff --git a/x-pack/plugins/screenshotting/public/context_storage.ts b/x-pack/plugins/screenshotting/public/context_storage.ts
deleted file mode 100644
index 76a2cf231cf83..0000000000000
--- a/x-pack/plugins/screenshotting/public/context_storage.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { Context, SCREENSHOTTING_CONTEXT_KEY } from '../common/context';
-
-declare global {
- interface Window {
- [SCREENSHOTTING_CONTEXT_KEY]?: Context;
- }
-}
-
-export class ContextStorage {
- get(): T {
- return (window[SCREENSHOTTING_CONTEXT_KEY] ?? {}) as T;
- }
-}
diff --git a/x-pack/plugins/screenshotting/public/index.ts b/x-pack/plugins/screenshotting/public/index.ts
deleted file mode 100644
index 659dbc81917a7..0000000000000
--- a/x-pack/plugins/screenshotting/public/index.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { ScreenshottingPlugin } from './plugin';
-
-/**
- * Screenshotting plugin entry point.
- */
-export function plugin(...args: ConstructorParameters) {
- return new ScreenshottingPlugin(...args);
-}
-
-export { LayoutTypes } from '../common';
-export type { ScreenshottingSetup, ScreenshottingStart } from './plugin';
diff --git a/x-pack/plugins/screenshotting/public/plugin.tsx b/x-pack/plugins/screenshotting/public/plugin.tsx
deleted file mode 100755
index 4ba5046b8a881..0000000000000
--- a/x-pack/plugins/screenshotting/public/plugin.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import type { Plugin } from 'src/core/public';
-import { ContextStorage } from './context_storage';
-
-/**
- * Setup public contract.
- */
-export interface ScreenshottingSetup {
- /**
- * Gathers screenshot context that has been set on the backend.
- */
- getContext: ContextStorage['get'];
-}
-
-/**
- * Start public contract.
- */
-export type ScreenshottingStart = ScreenshottingSetup;
-
-export class ScreenshottingPlugin implements Plugin {
- private contextStorage = new ContextStorage();
-
- setup(): ScreenshottingSetup {
- return {
- getContext: () => this.contextStorage.get(),
- };
- }
-
- start(): ScreenshottingStart {
- return {
- getContext: () => this.contextStorage.get(),
- };
- }
-
- stop() {}
-}
diff --git a/x-pack/plugins/screenshotting/server/browsers/chromium/driver.ts b/x-pack/plugins/screenshotting/server/browsers/chromium/driver.ts
index 57f987f866ec9..4e71194486d01 100644
--- a/x-pack/plugins/screenshotting/server/browsers/chromium/driver.ts
+++ b/x-pack/plugins/screenshotting/server/browsers/chromium/driver.ts
@@ -10,12 +10,10 @@ import open from 'opn';
import puppeteer, { ElementHandle, EvaluateFn, Page, SerializableOrJSHandle } from 'puppeteer';
import { parse as parseUrl } from 'url';
import { Logger } from 'src/core/server';
-import type { Layout } from 'src/plugins/screenshot_mode/common';
import {
KBN_SCREENSHOT_MODE_HEADER,
ScreenshotModePluginSetup,
} from '../../../../../../src/plugins/screenshot_mode/server';
-import { Context, SCREENSHOTTING_CONTEXT_KEY } from '../../../common/context';
import { ConfigType } from '../../config';
import { allowRequest } from '../network_policy';
@@ -31,6 +29,8 @@ export interface ConditionalHeaders {
conditions: ConditionalHeadersConditions;
}
+export type Context = Record;
+
export interface ElementPosition {
boundingClientRect: {
// modern browsers support x/y, but older ones don't
@@ -56,7 +56,6 @@ interface OpenOptions {
context?: Context;
waitForSelector: string;
timeout: number;
- layout?: Layout;
}
interface WaitForSelectorOpts {
@@ -124,13 +123,7 @@ export class HeadlessChromiumDriver {
*/
async open(
url: string,
- {
- conditionalHeaders,
- context,
- layout,
- waitForSelector: pageLoadSelector,
- timeout,
- }: OpenOptions,
+ { conditionalHeaders, context, waitForSelector: pageLoadSelector, timeout }: OpenOptions,
logger: Logger
): Promise {
logger.info(`opening url ${url}`);
@@ -144,23 +137,8 @@ export class HeadlessChromiumDriver {
*/
await this.page.evaluateOnNewDocument(this.screenshotMode.setScreenshotModeEnabled);
- if (context) {
- await this.page.evaluateOnNewDocument(
- (key: string, value: unknown) => {
- Object.defineProperty(window, key, {
- configurable: false,
- writable: true,
- enumerable: true,
- value,
- });
- },
- SCREENSHOTTING_CONTEXT_KEY,
- context
- );
- }
-
- if (layout) {
- await this.page.evaluateOnNewDocument(this.screenshotMode.setScreenshotLayout, layout);
+ for (const [key, value] of Object.entries(context ?? {})) {
+ await this.page.evaluateOnNewDocument(this.screenshotMode.setScreenshotContext, key, value);
}
await this.page.setRequestInterception(true);
diff --git a/x-pack/plugins/screenshotting/server/browsers/chromium/index.ts b/x-pack/plugins/screenshotting/server/browsers/chromium/index.ts
index 31a48cae7475e..88572081c5491 100644
--- a/x-pack/plugins/screenshotting/server/browsers/chromium/index.ts
+++ b/x-pack/plugins/screenshotting/server/browsers/chromium/index.ts
@@ -9,7 +9,7 @@ export const getChromiumDisconnectedError = () =>
new Error('Browser was closed unexpectedly! Check the server logs for more info.');
export { HeadlessChromiumDriver } from './driver';
-export type { ConditionalHeaders } from './driver';
+export type { ConditionalHeaders, Context } from './driver';
export { DEFAULT_VIEWPORT, HeadlessChromiumDriverFactory } from './driver_factory';
export type { PerformanceMetrics } from './driver_factory';
export { ChromiumArchivePaths } from './paths';
diff --git a/x-pack/plugins/screenshotting/server/browsers/index.ts b/x-pack/plugins/screenshotting/server/browsers/index.ts
index ef5069ae51112..51687d74ff31d 100644
--- a/x-pack/plugins/screenshotting/server/browsers/index.ts
+++ b/x-pack/plugins/screenshotting/server/browsers/index.ts
@@ -7,7 +7,7 @@
export { download } from './download';
export { install } from './install';
-export type { ConditionalHeaders, PerformanceMetrics } from './chromium';
+export type { ConditionalHeaders, Context, PerformanceMetrics } from './chromium';
export {
getChromiumDisconnectedError,
ChromiumArchivePaths,
diff --git a/x-pack/plugins/screenshotting/server/screenshots/observable.ts b/x-pack/plugins/screenshotting/server/screenshots/observable.ts
index a238af5bcc25b..5cfda2c841cb8 100644
--- a/x-pack/plugins/screenshotting/server/screenshots/observable.ts
+++ b/x-pack/plugins/screenshotting/server/screenshots/observable.ts
@@ -9,8 +9,7 @@ import type { Transaction } from 'elastic-apm-node';
import { defer, forkJoin, throwError, Observable } from 'rxjs';
import { catchError, mergeMap, switchMapTo, timeoutWith } from 'rxjs/operators';
import type { Logger } from 'src/core/server';
-import type { Layout as ScreenshotModeLayout } from 'src/plugins/screenshot_mode/common';
-import type { ConditionalHeaders, HeadlessChromiumDriver } from '../browsers';
+import type { ConditionalHeaders, Context, HeadlessChromiumDriver } from '../browsers';
import { getChromiumDisconnectedError, DEFAULT_VIEWPORT } from '../browsers';
import type { Layout } from '../layouts';
import type { ElementsPositionAndAttribute } from './get_element_position_data';
@@ -22,7 +21,6 @@ import type { Screenshot } from './get_screenshots';
import { getTimeRange } from './get_time_range';
import { injectCustomCss } from './inject_css';
import { openUrl } from './open_url';
-import type { UrlOrUrlWithContext } from './open_url';
import { waitForRenderComplete } from './wait_for_render';
import { waitForVisualizations } from './wait_for_visualizations';
@@ -48,6 +46,10 @@ export interface PhaseTimeouts {
loadDelay: number;
}
+type Url = string;
+type UrlWithContext = [url: Url, context: Context];
+export type UrlOrUrlWithContext = Url | UrlWithContext;
+
export interface ScreenshotObservableOptions {
/**
* The browser timezone that will be emulated in the browser instance.
@@ -157,18 +159,27 @@ export class ScreenshotObservableHandler {
);
}
- private openUrl(index: number, url: UrlOrUrlWithContext) {
- return defer(() =>
- openUrl(
+ private openUrl(index: number, urlOrUrlWithContext: UrlOrUrlWithContext) {
+ return defer(() => {
+ let url: string;
+ let context: Context | undefined;
+
+ if (typeof urlOrUrlWithContext === 'string') {
+ url = urlOrUrlWithContext;
+ } else {
+ [url, context] = urlOrUrlWithContext;
+ }
+
+ return openUrl(
this.driver,
this.logger,
this.options.timeouts.openUrl,
index,
url,
- this.options.conditionalHeaders,
- this.layout.id as ScreenshotModeLayout
- )
- ).pipe(this.waitUntil(this.options.timeouts.openUrl, 'open URL'));
+ { ...(context ?? {}), layout: this.layout.id },
+ this.options.conditionalHeaders
+ );
+ }).pipe(this.waitUntil(this.options.timeouts.openUrl, 'open URL'));
}
private waitForElements() {
diff --git a/x-pack/plugins/screenshotting/server/screenshots/open_url.ts b/x-pack/plugins/screenshotting/server/screenshots/open_url.ts
index a27c9b5db4090..a4c4bd6217205 100644
--- a/x-pack/plugins/screenshotting/server/screenshots/open_url.ts
+++ b/x-pack/plugins/screenshotting/server/screenshots/open_url.ts
@@ -7,24 +7,18 @@
import apm from 'elastic-apm-node';
import type { Logger } from 'src/core/server';
-import type { Layout } from 'src/plugins/screenshot_mode/common';
-import { Context } from '../../common';
import type { HeadlessChromiumDriver } from '../browsers';
-import type { ConditionalHeaders } from '../browsers';
+import type { ConditionalHeaders, Context } from '../browsers';
import { DEFAULT_PAGELOAD_SELECTOR } from './constants';
-type Url = string;
-type UrlWithContext = [url: Url, context: Context];
-export type UrlOrUrlWithContext = Url | UrlWithContext;
-
export const openUrl = async (
browser: HeadlessChromiumDriver,
logger: Logger,
timeout: number,
index: number,
- urlOrUrlWithContext: UrlOrUrlWithContext,
- conditionalHeaders: ConditionalHeaders,
- layout?: Layout
+ url: string,
+ context: Context,
+ conditionalHeaders: ConditionalHeaders
): Promise => {
// If we're moving to another page in the app, we'll want to wait for the app to tell us
// it's loaded the next page.
@@ -32,21 +26,8 @@ export const openUrl = async (
const waitForSelector = page > 1 ? `[data-shared-page="${page}"]` : DEFAULT_PAGELOAD_SELECTOR;
const span = apm.startSpan('open_url', 'wait');
- let url: string;
- let context: Context | undefined;
-
- if (typeof urlOrUrlWithContext === 'string') {
- url = urlOrUrlWithContext;
- } else {
- [url, context] = urlOrUrlWithContext;
- }
-
try {
- await browser.open(
- url,
- { conditionalHeaders, context, layout, waitForSelector, timeout },
- logger
- );
+ await browser.open(url, { conditionalHeaders, context, waitForSelector, timeout }, logger);
} catch (err) {
logger.error(err);
throw new Error(`An error occurred when trying to open the Kibana URL: ${err.message}`);
diff --git a/x-pack/plugins/screenshotting/tsconfig.json b/x-pack/plugins/screenshotting/tsconfig.json
index a1e81c4fb38d9..bc9b65441f53d 100644
--- a/x-pack/plugins/screenshotting/tsconfig.json
+++ b/x-pack/plugins/screenshotting/tsconfig.json
@@ -8,7 +8,6 @@
},
"include": [
"common/**/*",
- "public/**/*",
"server/**/*",
"../../../typings/**/*"
],
diff --git a/x-pack/plugins/security_solution/public/cases/pages/use_fetch_alert_data.ts b/x-pack/plugins/security_solution/public/cases/pages/use_fetch_alert_data.ts
index d75e7324c9afe..d39549a5bbac8 100644
--- a/x-pack/plugins/security_solution/public/cases/pages/use_fetch_alert_data.ts
+++ b/x-pack/plugins/security_solution/public/cases/pages/use_fetch_alert_data.ts
@@ -12,7 +12,7 @@ import { useQueryAlerts } from '../../detections/containers/detection_engine/ale
import { Ecs } from '../../../../cases/common';
import { buildAlertsQuery, formatAlertToEcsSignal, SignalHit } from '../../common/utils/alerts';
-export const useFetchAlertData = (alertIds: string[]): [boolean, Record] => {
+export const useFetchAlertData = (alertIds: string[]): [boolean, Record] => {
const { selectedPatterns } = useSourcererDataView(SourcererScopeName.detections);
const alertsQuery = useMemo(() => buildAlertsQuery(alertIds), [alertIds]);
diff --git a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/es_deprecations/deprecations_list.test.ts b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/es_deprecations/deprecations_list.test.ts
index 49c084eb9f27d..c118eed5e2616 100644
--- a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/es_deprecations/deprecations_list.test.ts
+++ b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/es_deprecations/deprecations_list.test.ts
@@ -44,6 +44,7 @@ describe('ES deprecations table', () => {
aliases: [],
},
});
+ httpRequestsMockHelpers.setLoadRemoteClustersResponse([]);
await act(async () => {
testBed = await setupElasticsearchPage({ isReadOnlyMode: false });
@@ -105,6 +106,25 @@ describe('ES deprecations table', () => {
expect(find('warningDeprecationsCount').text()).toContain(warningDeprecations.length);
});
+ describe('remote clusters callout', () => {
+ beforeEach(async () => {
+ httpRequestsMockHelpers.setLoadRemoteClustersResponse(['test_remote_cluster']);
+
+ await act(async () => {
+ testBed = await setupElasticsearchPage({ isReadOnlyMode: false });
+ });
+
+ testBed.component.update();
+ });
+
+ it('shows a warning message if a user has remote clusters configured', () => {
+ const { exists } = testBed;
+
+ // Verify warning exists
+ expect(exists('remoteClustersWarningCallout')).toBe(true);
+ });
+ });
+
describe('search bar', () => {
it('filters results by "critical" status', async () => {
const { find, actions } = testBed;
diff --git a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/helpers/http_requests.ts b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/helpers/http_requests.ts
index b29b6d4fbff37..774dfba4c1b9f 100644
--- a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/helpers/http_requests.ts
+++ b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/helpers/http_requests.ts
@@ -203,6 +203,17 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => {
]);
};
+ const setLoadRemoteClustersResponse = (response?: object, error?: ResponseError) => {
+ const status = error ? error.statusCode || 400 : 200;
+ const body = error ? error : response;
+
+ server.respondWith('GET', `${API_BASE_PATH}/remote_clusters`, [
+ status,
+ { 'Content-Type': 'application/json' },
+ JSON.stringify(body),
+ ]);
+ };
+
return {
setLoadCloudBackupStatusResponse,
setLoadEsDeprecationsResponse,
@@ -220,6 +231,7 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => {
setReindexStatusResponse,
setLoadMlUpgradeModeResponse,
setGetUpgradeStatusResponse,
+ setLoadRemoteClustersResponse,
};
};
diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/reindex/flyout/checklist_step.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/reindex/flyout/checklist_step.tsx
index 85d8b260c1b79..1be57d33c935b 100644
--- a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/reindex/flyout/checklist_step.tsx
+++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/reindex/flyout/checklist_step.tsx
@@ -109,7 +109,6 @@ export const ChecklistFlyoutStep: React.FunctionComponent<{
)}
{(hasFetchFailed || hasReindexingFailed) && (
<>
-
{reindexState.errorMessage}
+
>
)}
diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/es_deprecations.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/es_deprecations.tsx
index d4bbab5102d99..b585ea20b1714 100644
--- a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/es_deprecations.tsx
+++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/es_deprecations.tsx
@@ -8,7 +8,7 @@
import React, { useEffect, useMemo } from 'react';
import { withRouter, RouteComponentProps } from 'react-router-dom';
-import { EuiPageHeader, EuiSpacer, EuiPageContent, EuiLink } from '@elastic/eui';
+import { EuiPageHeader, EuiSpacer, EuiPageContent, EuiLink, EuiCallOut } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { DocLinksStart } from 'kibana/public';
@@ -51,6 +51,26 @@ const i18nTexts = {
isLoading: i18n.translate('xpack.upgradeAssistant.esDeprecations.loadingText', {
defaultMessage: 'Loading deprecation issues…',
}),
+ remoteClustersDetectedTitle: i18n.translate(
+ 'xpack.upgradeAssistant.esDeprecations.remoteClustersDetectedTitle',
+ {
+ defaultMessage: 'Remote cluster compatibility',
+ }
+ ),
+ getRemoteClustersDetectedDescription: (remoteClustersCount: number) =>
+ i18n.translate('xpack.upgradeAssistant.esDeprecations.remoteClustersDetectedDescription', {
+ defaultMessage:
+ 'You have {remoteClustersCount} {remoteClustersCount, plural, one {remote cluster} other {remote clusters}} configured. If you use cross-cluster search, note that 8.x can only search remote clusters running the previous minor version or later. If you use cross-cluster replication, a cluster that contains follower indices must run the same or newer version as the remote cluster.',
+ values: {
+ remoteClustersCount,
+ },
+ }),
+ remoteClustersLinkText: i18n.translate(
+ 'xpack.upgradeAssistant.esDeprecations.remoteClustersLinkText',
+ {
+ defaultMessage: 'View remote clusters.',
+ }
+ ),
};
const getBatchReindexLink = (docLinks: DocLinksStart) => {
@@ -75,6 +95,22 @@ const getBatchReindexLink = (docLinks: DocLinksStart) => {
);
};
+const RemoteClustersAppLink: React.FunctionComponent = () => {
+ const {
+ plugins: { share },
+ } = useAppContext();
+
+ const remoteClustersUrl = share.url.locators
+ .get('REMOTE_CLUSTERS_LOCATOR')
+ ?.useUrl({ page: 'remoteClusters' });
+
+ return (
+
+ {i18nTexts.remoteClustersLinkText}
+
+ );
+};
+
export const EsDeprecations = withRouter(({ history }: RouteComponentProps) => {
const {
services: {
@@ -85,6 +121,7 @@ export const EsDeprecations = withRouter(({ history }: RouteComponentProps) => {
} = useAppContext();
const { data: esDeprecations, isLoading, error, resendRequest } = api.useLoadEsDeprecations();
+ const { data: remoteClusters } = api.useLoadRemoteClusters();
const deprecationsCountByLevel: {
warningDeprecations: number;
@@ -140,10 +177,29 @@ export const EsDeprecations = withRouter(({ history }: RouteComponentProps) => {
>
}
>
-
+ <>
+ {remoteClusters && remoteClusters.length > 0 && (
+ <>
+
+
+ {i18nTexts.getRemoteClustersDetectedDescription(remoteClusters.length)}{' '}
+
+
+
+
+ >
+ )}
+
+
+ >
diff --git a/x-pack/plugins/upgrade_assistant/public/application/lib/api.ts b/x-pack/plugins/upgrade_assistant/public/application/lib/api.ts
index f3c5680b56e20..49be16b44236d 100644
--- a/x-pack/plugins/upgrade_assistant/public/application/lib/api.ts
+++ b/x-pack/plugins/upgrade_assistant/public/application/lib/api.ts
@@ -239,6 +239,13 @@ export class ApiService {
method: 'get',
});
}
+
+ public useLoadRemoteClusters() {
+ return this.useRequest({
+ path: `${API_BASE_PATH}/remote_clusters`,
+ method: 'get',
+ });
+ }
}
export const apiService = new ApiService();
diff --git a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.ts b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.ts
index 724b282a87747..3182a74498994 100644
--- a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.ts
+++ b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.ts
@@ -293,12 +293,8 @@ export const reindexServiceFactory = (
// Do any other cleanup work necessary
reindexOp = await cleanupChanges(reindexOp);
} else {
- // Check that it reindexed all documents
- const {
- body: { count },
- } = await esClient.count({ index: reindexOp.attributes.indexName });
-
- if (taskResponse.task.status!.created < count) {
+ // Check that no failures occurred
+ if (taskResponse.response?.failures?.length) {
// Include the entire task result in the error message. This should be guaranteed
// to be JSON-serializable since it just came back from Elasticsearch.
throw error.reindexTaskFailed(`Reindexing failed: ${JSON.stringify(taskResponse)}`);
diff --git a/x-pack/plugins/upgrade_assistant/server/routes/register_routes.ts b/x-pack/plugins/upgrade_assistant/server/routes/register_routes.ts
index b6c8850376684..c0f422711901b 100644
--- a/x-pack/plugins/upgrade_assistant/server/routes/register_routes.ts
+++ b/x-pack/plugins/upgrade_assistant/server/routes/register_routes.ts
@@ -18,6 +18,7 @@ import { registerUpdateSettingsRoute } from './update_index_settings';
import { registerMlSnapshotRoutes } from './ml_snapshots';
import { ReindexWorker } from '../lib/reindexing';
import { registerUpgradeStatusRoute } from './status';
+import { registerRemoteClustersRoute } from './remote_clusters';
export function registerRoutes(dependencies: RouteDependencies, getWorker: () => ReindexWorker) {
registerAppRoutes(dependencies);
@@ -32,4 +33,5 @@ export function registerRoutes(dependencies: RouteDependencies, getWorker: () =>
registerMlSnapshotRoutes(dependencies);
// Route for cloud to retrieve the upgrade status for ES and Kibana
registerUpgradeStatusRoute(dependencies);
+ registerRemoteClustersRoute(dependencies);
}
diff --git a/x-pack/plugins/upgrade_assistant/server/routes/remote_clusters.ts b/x-pack/plugins/upgrade_assistant/server/routes/remote_clusters.ts
new file mode 100644
index 0000000000000..de2177cffa1fe
--- /dev/null
+++ b/x-pack/plugins/upgrade_assistant/server/routes/remote_clusters.ts
@@ -0,0 +1,40 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { API_BASE_PATH } from '../../common/constants';
+import { versionCheckHandlerWrapper } from '../lib/es_version_precheck';
+import { RouteDependencies } from '../types';
+
+export function registerRemoteClustersRoute({ router, lib: { handleEsError } }: RouteDependencies) {
+ router.get(
+ {
+ path: `${API_BASE_PATH}/remote_clusters`,
+ validate: false,
+ },
+ versionCheckHandlerWrapper(
+ async (
+ {
+ core: {
+ elasticsearch: { client },
+ },
+ },
+ request,
+ response
+ ) => {
+ try {
+ const { body: clustersByName } = await client.asCurrentUser.cluster.remoteInfo();
+
+ const remoteClusters = Object.keys(clustersByName);
+
+ return response.ok({ body: remoteClusters });
+ } catch (error) {
+ return handleEsError({ error, response });
+ }
+ }
+ )
+ );
+}
diff --git a/x-pack/plugins/uptime/e2e/config.ts b/x-pack/plugins/uptime/e2e/config.ts
index 4d509d4045be9..48b4e048000f2 100644
--- a/x-pack/plugins/uptime/e2e/config.ts
+++ b/x-pack/plugins/uptime/e2e/config.ts
@@ -8,6 +8,12 @@
import { FtrConfigProviderContext } from '@kbn/test';
import { CA_CERT_PATH } from '@kbn/dev-utils';
+import { readKibanaConfig } from './tasks/read_kibana_config';
+
+const MANIFEST_KEY = 'xpack.uptime.service.manifestUrl';
+const SERVICE_PASSWORD = 'xpack.uptime.service.password';
+const SERVICE_USERNAME = 'xpack.uptime.service.username';
+
async function config({ readConfigFile }: FtrConfigProviderContext) {
const kibanaCommonTestsConfig = await readConfigFile(
require.resolve('../../../../test/common/config.js')
@@ -16,6 +22,12 @@ async function config({ readConfigFile }: FtrConfigProviderContext) {
require.resolve('../../../test/functional/config.js')
);
+ const kibanaConfig = readKibanaConfig();
+
+ const manifestUrl = process.env.SYNTHETICS_SERVICE_MANIFEST ?? kibanaConfig[MANIFEST_KEY];
+ const serviceUsername = process.env.SYNTHETICS_SERVICE_USERNAME ?? kibanaConfig[SERVICE_USERNAME];
+ const servicPassword = process.env.SYNTHETICS_SERVICE_PASSWORD ?? kibanaConfig[SERVICE_PASSWORD];
+
return {
...kibanaCommonTestsConfig.getAll(),
@@ -44,9 +56,9 @@ async function config({ readConfigFile }: FtrConfigProviderContext) {
`--elasticsearch.username=kibana_system`,
`--elasticsearch.password=changeme`,
'--xpack.reporting.enabled=false',
- `--xpack.uptime.service.manifestUrl=${process.env.SYNTHETICS_SERVICE_MANIFEST}`,
- `--xpack.uptime.service.username=${process.env.SYNTHETICS_SERVICE_USERNAME}`,
- `--xpack.uptime.service.password=${process.env.SYNTHETICS_SERVICE_PASSWORD}`,
+ `--xpack.uptime.service.manifestUrl=${manifestUrl}`,
+ `--xpack.uptime.service.username=${serviceUsername}`,
+ `--xpack.uptime.service.password=${servicPassword}`,
'--xpack.uptime.ui.monitorManagement.enabled=true',
],
},
diff --git a/x-pack/plugins/uptime/e2e/helpers/make_checks.ts b/x-pack/plugins/uptime/e2e/helpers/make_checks.ts
new file mode 100644
index 0000000000000..b9e913524cb1f
--- /dev/null
+++ b/x-pack/plugins/uptime/e2e/helpers/make_checks.ts
@@ -0,0 +1,182 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import uuid from 'uuid';
+import { merge, flattenDeep } from 'lodash';
+import type { Client } from '@elastic/elasticsearch';
+import { makePing } from './make_ping';
+import { TlsProps } from './make_tls';
+
+interface CheckProps {
+ es: Client;
+ monitorId?: string;
+ numIps?: number;
+ fields?: { [key: string]: any };
+ mogrify?: (doc: any) => any;
+ refresh?: boolean;
+ tls?: boolean | TlsProps;
+ isFleetManaged?: boolean;
+}
+
+const getRandomMonitorId = () => {
+ return 'monitor-' + Math.random().toString(36).substring(7);
+};
+export const makeCheck = async ({
+ es,
+ monitorId = getRandomMonitorId(),
+ numIps = 1,
+ fields = {},
+ mogrify = (d) => d,
+ refresh = true,
+ tls = false,
+ isFleetManaged = false,
+}: CheckProps): Promise<{ monitorId: string; docs: any }> => {
+ const cgFields = {
+ monitor: {
+ check_group: uuid.v4(),
+ },
+ };
+
+ const docs = [];
+ const summary = {
+ up: 0,
+ down: 0,
+ };
+ for (let i = 0; i < numIps; i++) {
+ const pingFields = merge(fields, cgFields, {
+ monitor: {
+ ip: `127.0.0.${i}`,
+ },
+ });
+ if (i === numIps - 1) {
+ pingFields.summary = summary;
+ }
+ const doc = await makePing(
+ es,
+ monitorId,
+ pingFields,
+ mogrify,
+ false,
+ tls as any,
+ isFleetManaged
+ );
+ docs.push(doc);
+ // @ts-ignore
+ summary[doc.monitor.status]++;
+ }
+
+ if (refresh) {
+ await es.indices.refresh();
+ }
+
+ return { monitorId, docs };
+};
+
+export const makeChecks = async (
+ es: Client,
+ monitorId: string,
+ numChecks: number = 1,
+ numIps: number = 1,
+ every: number = 10000, // number of millis between checks
+ fields: { [key: string]: any } = {},
+ mogrify: (doc: any) => any = (d) => d,
+ refresh: boolean = true,
+ isFleetManaged: boolean = false
+) => {
+ const checks = [];
+ const oldestTime = new Date().getTime() - numChecks * every;
+ let newestTime = oldestTime;
+ for (let li = 0; li < numChecks; li++) {
+ const checkDate = new Date(newestTime + every);
+ newestTime = checkDate.getTime() + every;
+ fields = merge(fields, {
+ '@timestamp': checkDate.toISOString(),
+ monitor: {
+ timespan: {
+ gte: checkDate.toISOString(),
+ lt: new Date(newestTime).toISOString(),
+ },
+ },
+ });
+ const { docs } = await makeCheck({
+ es,
+ monitorId,
+ numIps,
+ fields,
+ mogrify,
+ refresh: false,
+ isFleetManaged,
+ });
+ checks.push(docs);
+ }
+
+ if (refresh) {
+ await es.indices.refresh();
+ }
+
+ return checks;
+};
+
+export const makeChecksWithStatus = async (
+ es: Client,
+ monitorId: string,
+ numChecks: number,
+ numIps: number,
+ every: number,
+ fields: { [key: string]: any } = {},
+ status: 'up' | 'down',
+ mogrify: (doc: any) => any = (d) => d,
+ refresh: boolean = true,
+ isFleetManaged: boolean = false
+) => {
+ const oppositeStatus = status === 'up' ? 'down' : 'up';
+
+ return await makeChecks(
+ es,
+ monitorId,
+ numChecks,
+ numIps,
+ every,
+ fields,
+ (d) => {
+ d.monitor.status = status;
+ if (d.summary) {
+ d.summary[status] += d.summary[oppositeStatus];
+ d.summary[oppositeStatus] = 0;
+ }
+
+ return mogrify(d);
+ },
+ refresh,
+ isFleetManaged
+ );
+};
+
+// Helper for processing a list of checks to find the time picker bounds.
+export const getChecksDateRange = (checks: any[]) => {
+ // Flatten 2d arrays
+ const flattened = flattenDeep(checks);
+
+ let startTime = 1 / 0;
+ let endTime = -1 / 0;
+ flattened.forEach((c) => {
+ const ts = Date.parse(c['@timestamp']);
+
+ if (ts < startTime) {
+ startTime = ts;
+ }
+
+ if (ts > endTime) {
+ endTime = ts;
+ }
+ });
+
+ return {
+ start: new Date(startTime).toISOString(),
+ end: new Date(endTime).toISOString(),
+ };
+};
diff --git a/x-pack/plugins/uptime/e2e/helpers/make_ping.ts b/x-pack/plugins/uptime/e2e/helpers/make_ping.ts
new file mode 100644
index 0000000000000..c93d6f437268d
--- /dev/null
+++ b/x-pack/plugins/uptime/e2e/helpers/make_ping.ts
@@ -0,0 +1,126 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import uuid from 'uuid';
+import { merge } from 'lodash';
+import type { Client } from '@elastic/elasticsearch';
+import { makeTls, TlsProps } from './make_tls';
+
+const DEFAULT_INDEX_NAME = 'heartbeat-8-full-test';
+const DATA_STREAM_INDEX_NAME = 'synthetics-http-default';
+
+export const makePing = async (
+ es: Client,
+ monitorId: string,
+ fields: { [key: string]: any },
+ mogrify: (doc: any) => any,
+ refresh: boolean = true,
+ tls: boolean | TlsProps = false,
+ isFleetManaged: boolean | undefined = false
+) => {
+ const timestamp = new Date();
+ const baseDoc: any = {
+ tcp: {
+ rtt: {
+ connect: {
+ us: 14687,
+ },
+ },
+ },
+ observer: {
+ geo: {
+ name: 'mpls',
+ location: '37.926868, -78.024902',
+ },
+ hostname: 'avc-x1e',
+ },
+ agent: {
+ hostname: 'avc-x1e',
+ id: '10730a1a-4cb7-45ce-8524-80c4820476ab',
+ type: 'heartbeat',
+ ephemeral_id: '0d9a8dc6-f604-49e3-86a0-d8f9d6f2cbad',
+ version: '8.0.0',
+ },
+ '@timestamp': timestamp.toISOString(),
+ resolve: {
+ rtt: {
+ us: 350,
+ },
+ ip: '127.0.0.1',
+ },
+ ecs: {
+ version: '1.1.0',
+ },
+ host: {
+ name: 'avc-x1e',
+ },
+ http: {
+ rtt: {
+ response_header: {
+ us: 19349,
+ },
+ total: {
+ us: 48954,
+ },
+ write_request: {
+ us: 33,
+ },
+ content: {
+ us: 51,
+ },
+ validate: {
+ us: 19400,
+ },
+ },
+ response: {
+ status_code: 200,
+ body: {
+ bytes: 3,
+ hash: '27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf',
+ },
+ },
+ },
+ monitor: {
+ duration: {
+ us: 49347,
+ },
+ ip: '127.0.0.1',
+ id: monitorId,
+ check_group: uuid.v4(),
+ type: 'http',
+ status: 'up',
+ timespan: {
+ gte: timestamp.toISOString(),
+ lt: new Date(timestamp.getTime() + 5000).toISOString,
+ },
+ },
+ event: {
+ dataset: 'uptime',
+ },
+ url: {
+ path: '/pattern',
+ scheme: 'http',
+ port: 5678,
+ domain: 'localhost',
+ query: 'r=200x5,500x1',
+ full: 'http://localhost:5678/pattern?r=200x5,500x1',
+ },
+ };
+
+ if (tls) {
+ baseDoc.tls = makeTls(tls as any);
+ }
+
+ const doc = mogrify(merge(baseDoc, fields));
+
+ await es.index({
+ index: isFleetManaged ? DATA_STREAM_INDEX_NAME : DEFAULT_INDEX_NAME,
+ refresh,
+ body: doc,
+ });
+ return doc;
+};
diff --git a/x-pack/plugins/uptime/e2e/helpers/make_tls.ts b/x-pack/plugins/uptime/e2e/helpers/make_tls.ts
new file mode 100644
index 0000000000000..e654a2754e51d
--- /dev/null
+++ b/x-pack/plugins/uptime/e2e/helpers/make_tls.ts
@@ -0,0 +1,68 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import moment from 'moment';
+import crypto from 'crypto';
+
+export interface TlsProps {
+ valid?: boolean;
+ commonName?: string;
+ expiry?: string;
+ sha256?: string;
+}
+
+type Props = TlsProps & boolean;
+
+// Note This is just a mock sha256 value, this doesn't actually generate actually sha 256 val
+export const getSha256 = () => {
+ return crypto.randomBytes(64).toString('hex').toUpperCase();
+};
+
+export const makeTls = ({ valid = true, commonName = '*.elastic.co', expiry, sha256 }: Props) => {
+ const expiryDate =
+ expiry ??
+ moment()
+ .add(valid ? 2 : -2, 'months')
+ .toISOString();
+
+ return {
+ version: '1.3',
+ cipher: 'TLS-AES-128-GCM-SHA256',
+ certificate_not_valid_before: '2020-03-01T00:00:00.000Z',
+ certificate_not_valid_after: expiryDate,
+ server: {
+ x509: {
+ not_before: '2020-03-01T00:00:00.000Z',
+ not_after: expiryDate,
+ issuer: {
+ distinguished_name:
+ 'CN=DigiCert SHA2 High Assurance Server CA,OU=www.digicert.com,O=DigiCert Inc,C=US',
+ common_name: 'DigiCert SHA2 High Assurance Server CA',
+ },
+ subject: {
+ common_name: commonName,
+ distinguished_name: 'CN=*.facebook.com,O=Facebook Inc.,L=Menlo Park,ST=California,C=US',
+ },
+ serial_number: '10043199409725537507026285099403602396',
+ signature_algorithm: 'SHA256-RSA',
+ public_key_algorithm: 'ECDSA',
+ public_key_curve: 'P-256',
+ },
+ hash: {
+ sha256: sha256 ?? '1a48f1db13c3bd1482ba1073441e74a1bb1308dc445c88749e0dc4f1889a88a4',
+ sha1: '23291c758d925b9f4bb3584de3763317e94c6ce9',
+ },
+ },
+ established: true,
+ rtt: {
+ handshake: {
+ us: 33103,
+ },
+ },
+ version_protocol: 'tls',
+ };
+};
diff --git a/x-pack/plugins/uptime/e2e/journeys/data_view_permissions.ts b/x-pack/plugins/uptime/e2e/journeys/data_view_permissions.ts
index cf50e0d6b58ae..5d8d2a1a5d848 100644
--- a/x-pack/plugins/uptime/e2e/journeys/data_view_permissions.ts
+++ b/x-pack/plugins/uptime/e2e/journeys/data_view_permissions.ts
@@ -6,8 +6,7 @@
*/
import { journey, step, expect, before } from '@elastic/synthetics';
-import { loginToKibana, waitForLoadingToFinish } from './utils';
-import { byTestId } from './uptime.journey';
+import { byTestId, loginToKibana, waitForLoadingToFinish } from './utils';
import { callKibana } from '../../../apm/scripts/create_apm_users_and_roles/helpers/call_kibana';
journey('DataViewPermissions', async ({ page, params }) => {
diff --git a/x-pack/plugins/uptime/e2e/journeys/index.ts b/x-pack/plugins/uptime/e2e/journeys/index.ts
index fe8a4960eac12..ae3450a992256 100644
--- a/x-pack/plugins/uptime/e2e/journeys/index.ts
+++ b/x-pack/plugins/uptime/e2e/journeys/index.ts
@@ -12,3 +12,4 @@ export * from './alerts';
export * from './read_only_user';
export * from './monitor_name.journey';
export * from './monitor_management.journey';
+export * from './monitor_details';
diff --git a/x-pack/plugins/uptime/e2e/journeys/monitor_details/index.ts b/x-pack/plugins/uptime/e2e/journeys/monitor_details/index.ts
new file mode 100644
index 0000000000000..51b782f118c2a
--- /dev/null
+++ b/x-pack/plugins/uptime/e2e/journeys/monitor_details/index.ts
@@ -0,0 +1,10 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export * from './monitor_details.journey';
+export * from './monitor_alerts.journey';
+export * from './ping_redirects.journey';
diff --git a/x-pack/plugins/uptime/e2e/journeys/monitor_details/monitor_alerts.journey.ts b/x-pack/plugins/uptime/e2e/journeys/monitor_details/monitor_alerts.journey.ts
new file mode 100644
index 0000000000000..c44dbc187bd53
--- /dev/null
+++ b/x-pack/plugins/uptime/e2e/journeys/monitor_details/monitor_alerts.journey.ts
@@ -0,0 +1,94 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { journey, step, expect, before, Page } from '@elastic/synthetics';
+import { noop } from 'lodash';
+import { monitorDetailsPageProvider } from '../../page_objects/monitor_details';
+import { byTestId, delay } from '../utils';
+
+const dateRangeStart = '2019-09-10T12:40:08.078Z';
+const dateRangeEnd = '2019-09-11T19:40:08.078Z';
+const monitorId = '0000-intermittent';
+
+const alertId = 'uptime-anomaly-alert';
+const alertThreshold = 'major';
+
+journey('MonitorAlerts', async ({ page, params }: { page: Page; params: any }) => {
+ const monitorDetails = monitorDetailsPageProvider({ page, kibanaUrl: params.kibanaUrl });
+
+ before(async () => {
+ await monitorDetails.waitForLoadingToFinish();
+ });
+
+ step('go to overview', async () => {
+ await monitorDetails.navigateToOverviewPage({ dateRangeEnd, dateRangeStart });
+ });
+
+ step('login to Kibana', async () => {
+ await monitorDetails.loginToKibana();
+ });
+
+ step('go to monitor details', async () => {
+ await monitorDetails.navigateToMonitorDetails(monitorId);
+ await monitorDetails.waitForLoadingToFinish();
+ });
+
+ step('clean previous data if available', async () => {
+ // Should only happen locally
+ await monitorDetails.disableAnomalyDetectionAlert().catch(noop);
+ await monitorDetails.disableAnomalyDetection().catch(noop);
+ });
+
+ step('open anomaly detection flyout', async () => {
+ await monitorDetails.waitAndRefresh(5000);
+ await monitorDetails.enableAnomalyDetection();
+ await monitorDetails.ensureAnomalyDetectionFlyoutIsOpen();
+ });
+
+ step('can create job', async () => {
+ const canCreateJob = await monitorDetails.canCreateJob();
+ const missingLicense = await page
+ .waitForSelector('uptimeMLLicenseInfo', { timeout: 10000 })
+ .then(() => true)
+ .catch(() => false);
+ expect(canCreateJob).toBeTruthy();
+ expect(missingLicense).toBeFalsy();
+ });
+
+ step('creates ML job', async () => {
+ await page.click(byTestId('uptimeMLCreateJobBtn'));
+ await page.waitForSelector(byTestId('uptimeMLJobSuccessfullyCreated'), { timeout: 30000 });
+ await page.click(byTestId('toastCloseButton'));
+ });
+
+ step('close anomaly detection flyout', async () => {
+ await page.click(byTestId('cancelSaveAlertButton'));
+ });
+
+ step('open anomaly detection alert', async () => {
+ await monitorDetails.waitAndRefresh(3000);
+ await monitorDetails.openAnomalyDetectionMenu();
+ await page.click(byTestId('uptimeEnableAnomalyAlertBtn'));
+ });
+
+ step('update anomaly detection alert', async () => {
+ await monitorDetails.updateAlert({ id: alertId, threshold: alertThreshold });
+ });
+
+ step('save anomaly detection alert', async () => {
+ await page.click(byTestId('saveAlertButton'));
+ await page.click(byTestId('confirmModalConfirmButton'));
+ await page.waitForSelector(`text=Created rule "${alertId}"`);
+ });
+
+ step('disable anomaly detection alert', async () => {
+ await monitorDetails.waitAndRefresh(5000);
+ await monitorDetails.disableAnomalyDetectionAlert();
+ await delay(1000); // Menu has delay when closing
+ await monitorDetails.disableAnomalyDetection();
+ });
+});
diff --git a/x-pack/plugins/uptime/e2e/journeys/monitor_details/monitor_details.journey.ts b/x-pack/plugins/uptime/e2e/journeys/monitor_details/monitor_details.journey.ts
new file mode 100644
index 0000000000000..2965c1acf2c2b
--- /dev/null
+++ b/x-pack/plugins/uptime/e2e/journeys/monitor_details/monitor_details.journey.ts
@@ -0,0 +1,60 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { journey, step, before, Page } from '@elastic/synthetics';
+import { monitorDetailsPageProvider } from '../../page_objects/monitor_details';
+import { byTestId } from '../utils';
+
+const dateRangeStart = '2019-09-10T12:40:08.078Z';
+const dateRangeEnd = '2019-09-11T19:40:08.078Z';
+const monitorId = '0000-intermittent';
+
+journey('MonitorDetails', async ({ page, params }: { page: Page; params: any }) => {
+ const monitorDetails = monitorDetailsPageProvider({ page, kibanaUrl: params.kibanaUrl });
+
+ before(async () => {
+ await monitorDetails.waitForLoadingToFinish();
+ });
+
+ step('go to overview', async () => {
+ await monitorDetails.navigateToOverviewPage({ dateRangeEnd, dateRangeStart });
+ });
+
+ step('login to Kibana', async () => {
+ await monitorDetails.loginToKibana();
+ });
+
+ step('go to monitor details', async () => {
+ await monitorDetails.navigateToMonitorDetails(monitorId);
+ await monitorDetails.waitForLoadingToFinish();
+ });
+
+ step('should select the ping list location filter', async () => {
+ await monitorDetails.selectFilterItem('Location', 'mpls');
+ });
+
+ step('should set the status filter', async () => {
+ await monitorDetails.setStatusFilterUp();
+ });
+
+ step('displays ping data as expected', async () => {
+ await Promise.all(
+ [
+ 'XZtoHm0B0I9WX_CznN-6',
+ '7ZtoHm0B0I9WX_CzJ96M',
+ 'pptnHm0B0I9WX_Czst5X',
+ 'I5tnHm0B0I9WX_CzPd46',
+ 'y5tmHm0B0I9WX_Czx93x',
+ 'XZtmHm0B0I9WX_CzUt3H',
+ '-JtlHm0B0I9WX_Cz3dyX',
+ 'k5tlHm0B0I9WX_CzaNxm',
+ 'NZtkHm0B0I9WX_Cz89w9',
+ 'zJtkHm0B0I9WX_CzftsN',
+ ].map((id) => page.waitForSelector(byTestId(`"xpack.uptime.pingList.ping-${id}"`)))
+ );
+ });
+});
diff --git a/x-pack/plugins/uptime/e2e/journeys/monitor_details/ping_redirects.journey.ts b/x-pack/plugins/uptime/e2e/journeys/monitor_details/ping_redirects.journey.ts
new file mode 100644
index 0000000000000..ffbc322d7e779
--- /dev/null
+++ b/x-pack/plugins/uptime/e2e/journeys/monitor_details/ping_redirects.journey.ts
@@ -0,0 +1,73 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { journey, step, expect, before, Page } from '@elastic/synthetics';
+import { makeChecksWithStatus } from '../../helpers/make_checks';
+import { monitorDetailsPageProvider } from '../../page_objects/monitor_details';
+import { byTestId, delay } from '../utils';
+
+journey('MonitorPingRedirects', async ({ page, params }: { page: Page; params: any }) => {
+ const monitorDetails = monitorDetailsPageProvider({ page, kibanaUrl: params.kibanaUrl });
+ const testMonitor = {
+ id: '0000-intermittent',
+ start: 'now-15m',
+ end: 'now',
+ redirects: ['http://localhost:3000/first', 'https://www.washingtonpost.com/'],
+ };
+
+ before(async () => {
+ await monitorDetails.waitForLoadingToFinish();
+ await makeChecksWithStatus(
+ params.getService('es'),
+ testMonitor.id,
+ 5,
+ 2,
+ 10000,
+ {
+ http: {
+ rtt: { total: { us: 157784 } },
+ response: {
+ status_code: 200,
+ redirects: testMonitor.redirects,
+ body: {
+ bytes: 642102,
+ hash: '597a8cfb33ff8e09bff16283306553c3895282aaf5386e1843d466d44979e28a',
+ },
+ },
+ },
+ },
+ 'up'
+ );
+ await delay(5000);
+ });
+
+ step('go to monitor-management', async () => {
+ await monitorDetails.navigateToOverviewPage({
+ dateRangeEnd: testMonitor.end,
+ dateRangeStart: testMonitor.start,
+ });
+ });
+
+ step('login to Kibana', async () => {
+ await monitorDetails.loginToKibana();
+ });
+
+ step('go to monitor details', async () => {
+ await monitorDetails.navigateToMonitorDetails(testMonitor.id);
+ });
+
+ step('displays redirect info in detail panel', async () => {
+ await monitorDetails.waitForLoadingToFinish();
+ expect(await monitorDetails.getMonitorRedirects()).toEqual(`${testMonitor.redirects.length}`);
+ });
+
+ step('displays redirects in ping list expand row', async () => {
+ await monitorDetails.expandPingDetails();
+ await monitorDetails.waitForLoadingToFinish();
+ await page.waitForSelector(byTestId('uptimeMonitorPingListRedirectInfo'));
+ });
+});
diff --git a/x-pack/plugins/uptime/e2e/journeys/monitor_management.journey.ts b/x-pack/plugins/uptime/e2e/journeys/monitor_management.journey.ts
index 0286f7e898add..d2decba3e9a99 100644
--- a/x-pack/plugins/uptime/e2e/journeys/monitor_management.journey.ts
+++ b/x-pack/plugins/uptime/e2e/journeys/monitor_management.journey.ts
@@ -8,6 +8,7 @@
import { journey, step, expect, before, after, Page } from '@elastic/synthetics';
import { monitorManagementPageProvider } from '../page_objects/monitor_management';
import { DataStream } from '../../common/runtime_types/monitor_management';
+import { byTestId } from './utils';
const basicMonitorDetails = {
location: 'US Central',
@@ -197,6 +198,8 @@ journey('Monitor Management breadcrumbs', async ({ page, params }: { page: Page;
step('edit http monitor and check breadcrumb', async () => {
await uptime.editMonitor();
+ // breadcrumb is available before edit page is loaded, make sure its edit view
+ await page.waitForSelector(byTestId('monitorManagementMonitorName'));
const breadcrumbs = await page.$$('[data-test-subj=breadcrumb]');
expect(await breadcrumbs[1].textContent()).toEqual('Monitor management');
const lastBreadcrumb = await (await uptime.findByTestSubj('"breadcrumb last"')).textContent();
diff --git a/x-pack/plugins/uptime/e2e/journeys/uptime.journey.ts b/x-pack/plugins/uptime/e2e/journeys/uptime.journey.ts
index 1c3f82c517b82..7a256e1157272 100644
--- a/x-pack/plugins/uptime/e2e/journeys/uptime.journey.ts
+++ b/x-pack/plugins/uptime/e2e/journeys/uptime.journey.ts
@@ -6,11 +6,7 @@
*/
import { journey, step, before } from '@elastic/synthetics';
-import { waitForLoadingToFinish } from './utils';
-
-export const byTestId = (testId: string) => {
- return `[data-test-subj=${testId}]`;
-};
+import { byTestId, waitForLoadingToFinish } from './utils';
journey('uptime', ({ page, params }) => {
before(async () => {
diff --git a/x-pack/plugins/uptime/e2e/journeys/utils.ts b/x-pack/plugins/uptime/e2e/journeys/utils.ts
index 8dbc2699a438f..a042d37a99375 100644
--- a/x-pack/plugins/uptime/e2e/journeys/utils.ts
+++ b/x-pack/plugins/uptime/e2e/journeys/utils.ts
@@ -44,3 +44,11 @@ export const assertText = async ({ page, text }: { page: Page; text: string }) =
export const assertNotText = async ({ page, text }: { page: Page; text: string }) => {
expect(await page.$(`text=${text}`)).toBeFalsy();
};
+
+export const getQuerystring = (params: object) => {
+ return Object.entries(params)
+ .map(([key, value]) => encodeURIComponent(key) + '=' + encodeURIComponent(value))
+ .join('&');
+};
+
+export const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
diff --git a/x-pack/plugins/uptime/e2e/page_objects/monitor_details.tsx b/x-pack/plugins/uptime/e2e/page_objects/monitor_details.tsx
new file mode 100644
index 0000000000000..efadc26a383c0
--- /dev/null
+++ b/x-pack/plugins/uptime/e2e/page_objects/monitor_details.tsx
@@ -0,0 +1,129 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { Page } from '@elastic/synthetics';
+import { byTestId, delay } from '../journeys/utils';
+import { monitorManagementPageProvider } from './monitor_management';
+
+interface AlertType {
+ id: string;
+ threshold: string;
+}
+
+export function monitorDetailsPageProvider({ page, kibanaUrl }: { page: Page; kibanaUrl: string }) {
+ return {
+ ...monitorManagementPageProvider({ page, kibanaUrl }),
+
+ async navigateToMonitorDetails(monitorId: string) {
+ await page.click(byTestId(`monitor-page-link-${monitorId}`));
+ },
+
+ async selectFilterItem(filterType: string, itemArg: string | string[]) {
+ const itemList = Array.isArray(itemArg) ? itemArg : [itemArg];
+ await page.click(`[aria-label="expands filter group for ${filterType} filter"]`);
+ await this.clickFilterItems(itemList);
+ return this.applyFilterItems(filterType);
+ },
+
+ async clickFilterItems(itemList: string[]) {
+ for (const title of itemList) {
+ await page.click(`li[title="${title}"]`);
+ }
+ },
+
+ async applyFilterItems(filterType: string) {
+ await page.click(`[aria-label="Apply the selected filters for ${filterType}"]`);
+ },
+
+ async setStatusFilterUp() {
+ await page.click('[data-test-subj="xpack.uptime.filterBar.filterStatusUp"]');
+ },
+
+ async setStatusFilterDown() {
+ await page.click('[data-test-subj="xpack.uptime.filterBar.filterStatusDown"]');
+ },
+
+ async refreshFromES() {
+ await this.byTestId('superDatePickerApplyTimeButton');
+ },
+
+ async enableAnomalyDetection() {
+ await page.click(byTestId('uptimeEnableAnomalyBtn'));
+ },
+
+ async getMonitorRedirects() {
+ return await page.textContent(byTestId('uptimeMonitorRedirectInfo'));
+ },
+
+ async expandPingDetails() {
+ await page.click(byTestId('uptimePingListExpandBtn'));
+ },
+
+ async ensureAnomalyDetectionFlyoutIsOpen() {
+ await page.waitForSelector(byTestId('uptimeMLFlyout'));
+ },
+
+ async isMLMenuVisible() {
+ return await page.isVisible(byTestId('uptimeManageMLContextMenu'), {
+ timeout: 3000,
+ });
+ },
+
+ async canCreateJob(): Promise {
+ await this.ensureAnomalyDetectionFlyoutIsOpen();
+ const createJobBtn = await page.$(byTestId('uptimeMLCreateJobBtn'));
+ return await createJobBtn!.isEnabled();
+ },
+
+ async openAnomalyDetectionMenu() {
+ const visible = await this.isMLMenuVisible();
+ if (visible === false) {
+ await page.click(byTestId('uptimeManageMLJobBtn'), { timeout: 5000 });
+ }
+ },
+
+ async closeAnomalyDetectionMenu() {
+ if ((await this.isMLMenuVisible()) === true) {
+ await page.click(byTestId('uptimeManageMLJobBtn'), { timeout: 5000 });
+ }
+ },
+
+ async waitAndRefresh(timeout?: number) {
+ await delay(timeout ?? 1000);
+ await this.refreshFromES();
+ await this.waitForLoadingToFinish();
+ },
+
+ async updateAlert({ id, threshold }: AlertType) {
+ await this.fillByTestSubj('alertNameInput', id);
+ await this.selectAlertThreshold(threshold);
+ },
+
+ async selectAlertThreshold(threshold: string) {
+ await this.clickByTestSubj('uptimeAnomalySeverity');
+ await this.clickByTestSubj('anomalySeveritySelect');
+ await page.click(`text=${threshold}`);
+ },
+
+ async disableAnomalyDetection() {
+ await this.openAnomalyDetectionMenu();
+ await page.click(byTestId('uptimeDeleteMLJobBtn'), { timeout: 10000 });
+ await page.click(byTestId('confirmModalConfirmButton'));
+ await page.waitForSelector('text=Job deleted');
+ await this.closeAnomalyDetectionMenu();
+ },
+
+ async disableAnomalyDetectionAlert() {
+ await this.openAnomalyDetectionMenu();
+ await page.click(byTestId('uptimeManageAnomalyAlertBtn'), { timeout: 10000 });
+ await page.click(byTestId('uptimeDisableAnomalyAlertBtn'));
+ await page.click(byTestId('confirmModalConfirmButton'));
+ await page.waitForSelector('text=Rule successfully disabled!');
+ await this.closeAnomalyDetectionMenu();
+ },
+ };
+}
diff --git a/x-pack/plugins/uptime/e2e/page_objects/monitor_management.tsx b/x-pack/plugins/uptime/e2e/page_objects/monitor_management.tsx
index fd877708f2bce..a19f14fa1a6d1 100644
--- a/x-pack/plugins/uptime/e2e/page_objects/monitor_management.tsx
+++ b/x-pack/plugins/uptime/e2e/page_objects/monitor_management.tsx
@@ -7,6 +7,7 @@
import { Page } from '@elastic/synthetics';
import { DataStream } from '../../common/runtime_types/monitor_management';
+import { getQuerystring } from '../journeys/utils';
import { loginPageProvider } from './login';
import { utilsPageProvider } from './utils';
@@ -46,8 +47,8 @@ export function monitorManagementPageProvider({
});
},
- async navigateToOverviewPage() {
- await page.goto(overview, {
+ async navigateToOverviewPage(options?: object) {
+ await page.goto(`${overview}${options ? `?${getQuerystring(options)}` : ''}`, {
waitUntil: 'networkidle',
});
},
@@ -58,11 +59,12 @@ export function monitorManagementPageProvider({
async deleteMonitor() {
await this.clickByTestSubj('monitorManagementDeleteMonitor');
+ await this.clickByTestSubj('confirmModalConfirmButton');
return await this.findByTestSubj('uptimeDeleteMonitorSuccess');
},
async editMonitor() {
- await this.clickByTestSubj('monitorManagementEditMonitor');
+ await page.click(this.byTestId('monitorManagementEditMonitor'), { delay: 800 });
},
async findMonitorConfiguration(monitorConfig: Record) {
@@ -96,9 +98,8 @@ export function monitorManagementPageProvider({
},
async selectLocations({ locations }: { locations: string[] }) {
- await this.clickByTestSubj('syntheticsServiceLocationsComboBox');
for (let i = 0; i < locations.length; i++) {
- await page.click(`text=${locations[i]}`);
+ await page.check(`text=${locations[i]}`);
}
},
diff --git a/x-pack/plugins/uptime/e2e/page_objects/utils.tsx b/x-pack/plugins/uptime/e2e/page_objects/utils.tsx
index 072d4497e856d..024609e2f69ef 100644
--- a/x-pack/plugins/uptime/e2e/page_objects/utils.tsx
+++ b/x-pack/plugins/uptime/e2e/page_objects/utils.tsx
@@ -38,6 +38,10 @@ export function utilsPageProvider({ page }: { page: Page }) {
await page.selectOption(`[data-test-subj=${dataTestSubj}]`, value);
},
+ async checkByTestSubj(dataTestSubj: string, value: string) {
+ await page.check(`[data-test-subj=${dataTestSubj}]`);
+ },
+
async clickByTestSubj(dataTestSubj: string) {
await page.click(`[data-test-subj=${dataTestSubj}]`);
},
diff --git a/x-pack/plugins/uptime/e2e/playwright_start.ts b/x-pack/plugins/uptime/e2e/playwright_start.ts
index cb616be66b7c9..9108412405602 100644
--- a/x-pack/plugins/uptime/e2e/playwright_start.ts
+++ b/x-pack/plugins/uptime/e2e/playwright_start.ts
@@ -15,26 +15,12 @@ import './journeys';
import { createApmAndObsUsersAndRoles } from '../../apm/scripts/create_apm_users_and_roles/create_apm_users_and_roles';
import { importMonitors } from './tasks/import_monitors';
-const listOfJourneys = [
- 'uptime',
- 'StepsDuration',
- 'TlsFlyoutInAlertingApp',
- 'StatusFlyoutInAlertingApp',
- 'DefaultEmailSettings',
- 'MonitorManagement-http',
- 'MonitorManagement-tcp',
- 'MonitorManagement-icmp',
- 'MonitorManagement-browser',
- 'MonitorManagement breadcrumbs',
- 'Monitor Management read only user',
-] as const;
-
export function playwrightRunTests({ headless, match }: { headless: boolean; match?: string }) {
return async ({ getService }: any) => {
- const result = await playwrightStart(getService, headless, match);
+ const results = await playwrightStart(getService, headless, match);
- listOfJourneys.forEach((journey) => {
- if (result?.[journey] && result[journey].status !== 'succeeded') {
+ Object.entries(results).forEach(([_journey, result]) => {
+ if (result.status !== 'succeeded') {
throw new Error('Tests failed');
}
});
@@ -66,7 +52,7 @@ async function playwrightStart(getService: any, headless = true, match?: string)
});
const res = await playwrightRun({
- params: { kibanaUrl },
+ params: { kibanaUrl, getService },
playwrightOptions: { headless, chromiumSandbox: false, timeout: 60 * 1000 },
match: match === 'undefined' ? '' : match,
});
diff --git a/x-pack/plugins/uptime/e2e/tasks/read_kibana_config.ts b/x-pack/plugins/uptime/e2e/tasks/read_kibana_config.ts
new file mode 100644
index 0000000000000..3867386e41104
--- /dev/null
+++ b/x-pack/plugins/uptime/e2e/tasks/read_kibana_config.ts
@@ -0,0 +1,22 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import path from 'path';
+import fs from 'fs';
+import yaml from 'js-yaml';
+
+export type KibanaConfig = ReturnType;
+
+export const readKibanaConfig = () => {
+ const kibanaConfigDir = path.join(__filename, '../../../../../../config');
+ const kibanaDevConfig = path.join(kibanaConfigDir, 'kibana.dev.yml');
+ const kibanaConfig = path.join(kibanaConfigDir, 'kibana.yml');
+
+ return (yaml.safeLoad(
+ fs.readFileSync(fs.existsSync(kibanaDevConfig) ? kibanaDevConfig : kibanaConfig, 'utf8')
+ ) || {}) as Record;
+};
diff --git a/x-pack/plugins/uptime/public/components/monitor/ml/manage_ml_job.tsx b/x-pack/plugins/uptime/public/components/monitor/ml/manage_ml_job.tsx
index df0abcb88180b..1176ea5e6a5c3 100644
--- a/x-pack/plugins/uptime/public/components/monitor/ml/manage_ml_job.tsx
+++ b/x-pack/plugins/uptime/public/components/monitor/ml/manage_ml_job.tsx
@@ -75,7 +75,7 @@ export const ManageMLJobComponent = ({ hasMLJob, onEnableJob, onJobDelete }: Pro
const button = (
setIsPopOverOpen(true) : onEnableJob}
+ onClick={hasMLJob ? () => setIsPopOverOpen(!isPopOverOpen) : onEnableJob}
disabled={hasMLJob && !canDeleteMLJob}
isLoading={showLoading}
size="s"
diff --git a/x-pack/plugins/uptime/public/components/monitor_management/monitor_config/locations.test.tsx b/x-pack/plugins/uptime/public/components/monitor_management/monitor_config/locations.test.tsx
index 11caf092c93c7..ccc3e7b619c68 100644
--- a/x-pack/plugins/uptime/public/components/monitor_management/monitor_config/locations.test.tsx
+++ b/x-pack/plugins/uptime/public/components/monitor_management/monitor_config/locations.test.tsx
@@ -8,8 +8,7 @@
import React from 'react';
import { screen } from '@testing-library/react';
import { render } from '../../../lib/helper/rtl_helpers';
-import { ServiceLocations, LOCATIONS_LABEL } from './locations';
-import userEvent from '@testing-library/user-event';
+import { ServiceLocations } from './locations';
describe('', () => {
const setLocations = jest.fn();
@@ -52,47 +51,7 @@ describe('', () => {
{ state }
);
- expect(screen.getByText(LOCATIONS_LABEL)).toBeInTheDocument();
- expect(screen.queryByText('US Central')).not.toBeInTheDocument();
- });
-
- it('shows location options when clicked', async () => {
- render(
- ,
- { state }
- );
-
- userEvent.click(screen.getByRole('button'));
-
- expect(screen.getByText('US Central')).toBeInTheDocument();
- });
-
- it('prevents bad inputs', async () => {
- render(
- ,
- { state }
- );
-
- userEvent.click(screen.getByRole('button'));
- userEvent.type(screen.getByRole('textbox'), 'fake location');
-
- expect(screen.getByText("doesn't match any options")).toBeInTheDocument();
-
- userEvent.keyboard(`{enter}`);
-
- expect(screen.getByText('"fake location" is not a valid option')).toBeInTheDocument();
- });
-
- it('calls setLocations', async () => {
- render(
- ,
- { state }
- );
-
- userEvent.click(screen.getByRole('button'));
- userEvent.click(screen.getByText('US Central'));
-
- expect(setLocations).toBeCalledWith([location]);
+ expect(screen.queryByText('US Central')).toBeInTheDocument();
});
it('shows invalid error', async () => {
diff --git a/x-pack/plugins/uptime/public/components/monitor_management/monitor_config/locations.tsx b/x-pack/plugins/uptime/public/components/monitor_management/monitor_config/locations.tsx
index 2d261e169299a..1877cc4ade126 100644
--- a/x-pack/plugins/uptime/public/components/monitor_management/monitor_config/locations.tsx
+++ b/x-pack/plugins/uptime/public/components/monitor_management/monitor_config/locations.tsx
@@ -5,10 +5,10 @@
* 2.0.
*/
-import React, { useState } from 'react';
+import React, { useState, useEffect } from 'react';
import { useSelector } from 'react-redux';
import { i18n } from '@kbn/i18n';
-import { EuiComboBox, EuiComboBoxOptionOption, EuiFormRow } from '@elastic/eui';
+import { EuiCheckboxGroup, EuiFormRow } from '@elastic/eui';
import { monitorManagementListSelector } from '../../../state/selectors';
import { ServiceLocation } from '../../../../common/runtime_types';
@@ -20,51 +20,49 @@ interface Props {
export const ServiceLocations = ({ selectedLocations, setLocations, isInvalid }: Props) => {
const [error, setError] = useState(null);
+ const [checkboxIdToSelectedMap, setCheckboxIdToSelectedMap] = useState>(
+ {}
+ );
const { locations } = useSelector(monitorManagementListSelector);
- const onLocationChange = (
- selectedLocationOptions: Array>
- ) => {
- setLocations(selectedLocationOptions as ServiceLocation[]);
- setError(null);
- };
-
- const onSearchChange = (value: string, hasMatchingOptions?: boolean) => {
- setError(value.length === 0 || hasMatchingOptions ? null : getInvalidOptionError(value));
- };
-
- const onBlur = (event: unknown) => {
- const inputElement = (event as FocusEvent)?.target as HTMLInputElement;
- if (inputElement) {
- const { value } = inputElement;
- setError(value.length === 0 ? null : getInvalidOptionError(value));
+ const onLocationChange = (optionId: string) => {
+ const isSelected = !checkboxIdToSelectedMap[optionId];
+ const location = locations.find((loc) => loc.id === optionId);
+ if (isSelected) {
+ setLocations((prevLocations) => (location ? [...prevLocations, location] : prevLocations));
+ } else {
+ setLocations((prevLocations) => [...prevLocations].filter((loc) => loc.id !== optionId));
}
+ setError(null);
};
const errorMessage = error ?? (isInvalid ? VALIDATION_ERROR : null);
+ useEffect(() => {
+ const newCheckboxIdToSelectedMap = selectedLocations.reduce>(
+ (acc, location) => {
+ acc[location.id] = true;
+ return acc;
+ },
+ {}
+ );
+ setCheckboxIdToSelectedMap(newCheckboxIdToSelectedMap);
+ }, [selectedLocations]);
+
return (
- ({
+ ...location,
+ 'data-test-subj': `syntheticsServiceLocation--${location.id}`,
+ }))}
+ idToSelectedMap={checkboxIdToSelectedMap}
+ onChange={(id) => onLocationChange(id)}
/>
);
};
-const PLACEHOLDER_LABEL = i18n.translate(
- 'xpack.uptime.monitorManagement.serviceLocationsPlaceholderLabel',
- {
- defaultMessage: 'Select one or more locations to run your monitor.',
- }
-);
-
const VALIDATION_ERROR = i18n.translate(
'xpack.uptime.monitorManagement.serviceLocationsValidationError',
{
@@ -72,14 +70,6 @@ const VALIDATION_ERROR = i18n.translate(
}
);
-const getInvalidOptionError = (value: string) =>
- i18n.translate('xpack.uptime.monitorManagement.serviceLocationsOptionError', {
- defaultMessage: '"{value}" is not a valid option',
- values: {
- value,
- },
- });
-
export const LOCATIONS_LABEL = i18n.translate(
'xpack.uptime.monitorManagement.monitorLocationsLabel',
{
diff --git a/x-pack/plugins/uptime/public/components/monitor_management/monitor_list/actions.test.tsx b/x-pack/plugins/uptime/public/components/monitor_management/monitor_list/actions.test.tsx
index fd868214d4db9..e79150a4eb79e 100644
--- a/x-pack/plugins/uptime/public/components/monitor_management/monitor_list/actions.test.tsx
+++ b/x-pack/plugins/uptime/public/components/monitor_management/monitor_list/actions.test.tsx
@@ -7,19 +7,12 @@
import React from 'react';
import { screen } from '@testing-library/react';
-import userEvent from '@testing-library/user-event';
import { render } from '../../../lib/helper/rtl_helpers';
-import * as fetchers from '../../../state/api/monitor_management';
-import {
- FETCH_STATUS,
- useFetcher as originalUseFetcher,
-} from '../../../../../observability/public';
-import { spyOnUseFetcher } from '../../../lib/helper/spy_use_fetcher';
+
import { Actions } from './actions';
describe('', () => {
const onUpdate = jest.fn();
- const useFetcher = spyOnUseFetcher({});
it('navigates to edit monitor flow on edit pencil', () => {
render();
@@ -29,38 +22,4 @@ describe('', () => {
'/app/uptime/edit-monitor/dGVzdC1pZA=='
);
});
-
- it('calls delete monitor on monitor deletion', () => {
- useFetcher.mockImplementation(originalUseFetcher);
- const deleteMonitor = jest.spyOn(fetchers, 'deleteMonitor');
- const id = 'test-id';
- render();
-
- expect(deleteMonitor).not.toBeCalled();
-
- userEvent.click(screen.getByRole('button'));
-
- expect(deleteMonitor).toBeCalledWith({ id });
- });
-
- it('calls set refresh when deletion is successful', () => {
- const id = 'test-id';
- render();
-
- userEvent.click(screen.getByLabelText('Delete monitor'));
-
- expect(onUpdate).toHaveBeenCalled();
- });
-
- it('shows loading spinner while waiting for monitor to delete', () => {
- const id = 'test-id';
- useFetcher.mockReturnValue({
- data: {},
- status: FETCH_STATUS.LOADING,
- refetch: () => {},
- });
- render();
-
- expect(screen.getByLabelText('Deleting monitor...')).toBeInTheDocument();
- });
});
diff --git a/x-pack/plugins/uptime/public/components/monitor_management/monitor_list/actions.tsx b/x-pack/plugins/uptime/public/components/monitor_management/monitor_list/actions.tsx
index 6c85ae3d8a2a0..5fa29b7cd7c56 100644
--- a/x-pack/plugins/uptime/public/components/monitor_management/monitor_list/actions.tsx
+++ b/x-pack/plugins/uptime/public/components/monitor_management/monitor_list/actions.tsx
@@ -5,13 +5,11 @@
* 2.0.
*/
-import React, { useContext, useState, useEffect } from 'react';
+import React, { useContext } from 'react';
import { i18n } from '@kbn/i18n';
-import { EuiButtonIcon, EuiFlexItem, EuiFlexGroup, EuiLoadingSpinner } from '@elastic/eui';
+import { EuiButtonIcon, EuiFlexItem, EuiFlexGroup } from '@elastic/eui';
import { UptimeSettingsContext } from '../../../contexts';
-import { useFetcher, FETCH_STATUS } from '../../../../../observability/public';
-import { deleteMonitor } from '../../../state/api';
-import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
+import { DeleteMonitor } from './delete_monitor';
interface Props {
id: string;
@@ -20,41 +18,8 @@ interface Props {
}
export const Actions = ({ id, onUpdate, isDisabled }: Props) => {
- const [isDeleting, setIsDeleting] = useState(false);
const { basePath } = useContext(UptimeSettingsContext);
- const { notifications } = useKibana();
-
- const { status } = useFetcher(() => {
- if (isDeleting) {
- return deleteMonitor({ id });
- }
- }, [id, isDeleting]);
-
- // TODO: add popup to confirm deletion
- const handleDelete = () => {
- setIsDeleting(true);
- };
-
- useEffect(() => {
- if (status === FETCH_STATUS.SUCCESS || status === FETCH_STATUS.FAILURE) {
- setIsDeleting(false);
- }
- if (status === FETCH_STATUS.FAILURE) {
- notifications.toasts.danger({
- title: {MONITOR_DELETE_FAILURE_LABEL}
,
- toastLifeTimeMs: 3000,
- });
- } else if (status === FETCH_STATUS.SUCCESS) {
- onUpdate();
- notifications.toasts.success({
- title: {MONITOR_DELETE_SUCCESS_LABEL}
,
- toastLifeTimeMs: 3000,
- });
- }
- }, [setIsDeleting, onUpdate, notifications.toasts, status]);
-
- // TODO: Add popovers to icons
return (
@@ -67,17 +32,7 @@ export const Actions = ({ id, onUpdate, isDisabled }: Props) => {
/>
- {status === FETCH_STATUS.LOADING ? (
-
- ) : (
-
- )}
+
);
@@ -86,29 +41,3 @@ export const Actions = ({ id, onUpdate, isDisabled }: Props) => {
const EDIT_MONITOR_LABEL = i18n.translate('xpack.uptime.monitorManagement.editMonitorLabel', {
defaultMessage: 'Edit monitor',
});
-
-const DELETE_MONITOR_LABEL = i18n.translate('xpack.uptime.monitorManagement.deleteMonitorLabel', {
- defaultMessage: 'Delete monitor',
-});
-
-const MONITOR_DELETE_SUCCESS_LABEL = i18n.translate(
- 'xpack.uptime.monitorManagement.monitorDeleteSuccessMessage',
- {
- defaultMessage: 'Monitor deleted successfully.',
- }
-);
-
-// TODO: Discuss error states with product
-const MONITOR_DELETE_FAILURE_LABEL = i18n.translate(
- 'xpack.uptime.monitorManagement.monitorDeleteFailureMessage',
- {
- defaultMessage: 'Monitor was unable to be deleted. Please try again later.',
- }
-);
-
-const MONITOR_DELETE_LOADING_LABEL = i18n.translate(
- 'xpack.uptime.monitorManagement.monitorDeleteLoadingMessage',
- {
- defaultMessage: 'Deleting monitor...',
- }
-);
diff --git a/x-pack/plugins/uptime/public/components/monitor_management/monitor_list/delete_monitor.test.tsx b/x-pack/plugins/uptime/public/components/monitor_management/monitor_list/delete_monitor.test.tsx
new file mode 100644
index 0000000000000..a5c712b6e0456
--- /dev/null
+++ b/x-pack/plugins/uptime/public/components/monitor_management/monitor_list/delete_monitor.test.tsx
@@ -0,0 +1,60 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+import { screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import { render } from '../../../lib/helper/rtl_helpers';
+import * as fetchers from '../../../state/api/monitor_management';
+import {
+ FETCH_STATUS,
+ useFetcher as originalUseFetcher,
+} from '../../../../../observability/public';
+import { spyOnUseFetcher } from '../../../lib/helper/spy_use_fetcher';
+import { Actions } from './actions';
+import { DeleteMonitor } from './delete_monitor';
+
+describe('', () => {
+ const onUpdate = jest.fn();
+ const useFetcher = spyOnUseFetcher({});
+
+ it('calls delete monitor on monitor deletion', () => {
+ useFetcher.mockImplementation(originalUseFetcher);
+ const deleteMonitor = jest.spyOn(fetchers, 'deleteMonitor');
+ const id = 'test-id';
+ render();
+
+ expect(deleteMonitor).not.toBeCalled();
+
+ userEvent.click(screen.getByRole('button'));
+
+ userEvent.click(screen.getByTestId('confirmModalConfirmButton'));
+
+ expect(deleteMonitor).toBeCalledWith({ id });
+ });
+
+ it('calls set refresh when deletion is successful', () => {
+ const id = 'test-id';
+ render();
+
+ userEvent.click(screen.getByLabelText('Delete monitor'));
+
+ expect(onUpdate).toHaveBeenCalled();
+ });
+
+ it('shows loading spinner while waiting for monitor to delete', () => {
+ const id = 'test-id';
+ useFetcher.mockReturnValue({
+ data: {},
+ status: FETCH_STATUS.LOADING,
+ refetch: () => {},
+ });
+ render();
+
+ expect(screen.getByLabelText('Deleting monitor...')).toBeInTheDocument();
+ });
+});
diff --git a/x-pack/plugins/uptime/public/components/monitor_management/monitor_list/delete_monitor.tsx b/x-pack/plugins/uptime/public/components/monitor_management/monitor_list/delete_monitor.tsx
new file mode 100644
index 0000000000000..e0b706241b79a
--- /dev/null
+++ b/x-pack/plugins/uptime/public/components/monitor_management/monitor_list/delete_monitor.tsx
@@ -0,0 +1,142 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { i18n } from '@kbn/i18n';
+import React, { useEffect, useState } from 'react';
+import { EuiButtonIcon, EuiConfirmModal, EuiLoadingSpinner } from '@elastic/eui';
+
+import { FETCH_STATUS, useFetcher } from '../../../../../observability/public';
+import { deleteMonitor } from '../../../state/api';
+import { kibanaService } from '../../../state/kibana_service';
+import { toMountPoint } from '../../../../../../../src/plugins/kibana_react/public';
+
+export const DeleteMonitor = ({
+ id,
+ onUpdate,
+ isDisabled,
+}: {
+ id: string;
+ isDisabled?: boolean;
+ onUpdate: () => void;
+}) => {
+ const [isDeleting, setIsDeleting] = useState(false);
+ const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false);
+
+ const onConfirmDelete = () => {
+ setIsDeleting(true);
+ setIsDeleteModalVisible(false);
+ };
+ const showDeleteModal = () => setIsDeleteModalVisible(true);
+
+ const { status } = useFetcher(() => {
+ if (isDeleting) {
+ return deleteMonitor({ id });
+ }
+ }, [id, isDeleting]);
+
+ const handleDelete = () => {
+ showDeleteModal();
+ };
+
+ useEffect(() => {
+ if (status === FETCH_STATUS.SUCCESS || status === FETCH_STATUS.FAILURE) {
+ setIsDeleting(false);
+ }
+ if (status === FETCH_STATUS.FAILURE) {
+ kibanaService.toasts.addDanger(
+ {
+ title: toMountPoint(
+ {MONITOR_DELETE_FAILURE_LABEL}
+ ),
+ },
+ { toastLifeTimeMs: 3000 }
+ );
+ } else if (status === FETCH_STATUS.SUCCESS) {
+ onUpdate();
+ kibanaService.toasts.addSuccess(
+ {
+ title: toMountPoint(
+ {MONITOR_DELETE_SUCCESS_LABEL}
+ ),
+ },
+ { toastLifeTimeMs: 3000 }
+ );
+ }
+ }, [setIsDeleting, onUpdate, status]);
+
+ const destroyModal = (
+ setIsDeleteModalVisible(false)}
+ onConfirm={onConfirmDelete}
+ cancelButtonText={NO_LABEL}
+ confirmButtonText={YES_LABEL}
+ buttonColor="danger"
+ defaultFocusedButton="confirm"
+ >
+ {DELETE_DESCRIPTION_LABEL}
+
+ );
+
+ return (
+ <>
+ {status === FETCH_STATUS.LOADING ? (
+
+ ) : (
+
+ )}
+ {isDeleteModalVisible && destroyModal}
+ >
+ );
+};
+
+const DELETE_DESCRIPTION_LABEL = i18n.translate(
+ 'xpack.uptime.monitorManagement.confirmDescriptionLabel',
+ {
+ defaultMessage: 'Are you sure you want to do delete the monitor?',
+ }
+);
+
+const YES_LABEL = i18n.translate('xpack.uptime.monitorManagement.yesLabel', {
+ defaultMessage: 'Yes',
+});
+
+const NO_LABEL = i18n.translate('xpack.uptime.monitorManagement.noLabel', {
+ defaultMessage: 'No',
+});
+
+const DELETE_MONITOR_LABEL = i18n.translate('xpack.uptime.monitorManagement.deleteMonitorLabel', {
+ defaultMessage: 'Delete monitor',
+});
+
+const MONITOR_DELETE_SUCCESS_LABEL = i18n.translate(
+ 'xpack.uptime.monitorManagement.monitorDeleteSuccessMessage',
+ {
+ defaultMessage: 'Monitor deleted successfully.',
+ }
+);
+
+// TODO: Discuss error states with product
+const MONITOR_DELETE_FAILURE_LABEL = i18n.translate(
+ 'xpack.uptime.monitorManagement.monitorDeleteFailureMessage',
+ {
+ defaultMessage: 'Monitor was unable to be deleted. Please try again later.',
+ }
+);
+
+const MONITOR_DELETE_LOADING_LABEL = i18n.translate(
+ 'xpack.uptime.monitorManagement.monitorDeleteLoadingMessage',
+ {
+ defaultMessage: 'Deleting monitor...',
+ }
+);
diff --git a/x-pack/plugins/uptime/server/lib/synthetics_service/get_service_locations.ts b/x-pack/plugins/uptime/server/lib/synthetics_service/get_service_locations.ts
index a58c4dd421cbe..ffec06259f4e7 100644
--- a/x-pack/plugins/uptime/server/lib/synthetics_service/get_service_locations.ts
+++ b/x-pack/plugins/uptime/server/lib/synthetics_service/get_service_locations.ts
@@ -16,7 +16,7 @@ import { UptimeServerSetup } from '../adapters/framework';
export async function getServiceLocations(server: UptimeServerSetup) {
const locations: ServiceLocations = [];
- if (!server.config.service!.manifestUrl!) {
+ if (!server.config.service?.manifestUrl) {
return { locations };
}
diff --git a/x-pack/test/api_integration/apis/upgrade_assistant/index.ts b/x-pack/test/api_integration/apis/upgrade_assistant/index.ts
index 4d92d2e2c76df..20d6e57a71c12 100644
--- a/x-pack/test/api_integration/apis/upgrade_assistant/index.ts
+++ b/x-pack/test/api_integration/apis/upgrade_assistant/index.ts
@@ -14,5 +14,6 @@ export default function ({ loadTestFile }: FtrProviderContext) {
loadTestFile(require.resolve('./privileges'));
loadTestFile(require.resolve('./es_deprecations'));
loadTestFile(require.resolve('./es_deprecation_logs'));
+ loadTestFile(require.resolve('./remote_clusters'));
});
}
diff --git a/x-pack/test/api_integration/apis/upgrade_assistant/remote_clusters.ts b/x-pack/test/api_integration/apis/upgrade_assistant/remote_clusters.ts
new file mode 100644
index 0000000000000..5d8dcaf339068
--- /dev/null
+++ b/x-pack/test/api_integration/apis/upgrade_assistant/remote_clusters.ts
@@ -0,0 +1,75 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import expect from '@kbn/expect';
+
+import { FtrProviderContext } from '../../ftr_provider_context';
+import { API_BASE_PATH } from '../../../../plugins/upgrade_assistant/common/constants';
+
+export default function ({ getService }: FtrProviderContext) {
+ const supertest = getService('supertest');
+ const es = getService('es');
+ const log = getService('log');
+
+ describe('Remote clusters', () => {
+ describe('GET /api/upgrade_assistant/remote_clusters', () => {
+ before(async () => {
+ try {
+ // Configure a remote cluster
+ await es.cluster.putSettings({
+ body: {
+ persistent: {
+ cluster: {
+ remote: {
+ test_cluster: {
+ seeds: ['127.0.0.1:9400'],
+ },
+ },
+ },
+ },
+ },
+ });
+ } catch (e) {
+ log.debug('Error creating remote cluster');
+ throw e;
+ }
+ });
+
+ after(async () => {
+ try {
+ // Delete remote cluster
+ await es.cluster.putSettings({
+ body: {
+ persistent: {
+ cluster: {
+ remote: {
+ test_cluster: {
+ seeds: null,
+ },
+ },
+ },
+ },
+ },
+ });
+ } catch (e) {
+ log.debug('Error deleting remote cluster');
+ throw e;
+ }
+ });
+
+ it('returns an array of remote clusters', async () => {
+ const { body: apiRequestResponse } = await supertest
+ .get(`${API_BASE_PATH}/remote_clusters`)
+ .set('kbn-xsrf', 'xxx')
+ .expect(200);
+
+ expect(Array.isArray(apiRequestResponse)).be(true);
+ expect(apiRequestResponse.length).be(1);
+ });
+ });
+ });
+}
diff --git a/x-pack/test/api_integration/config.ts b/x-pack/test/api_integration/config.ts
index aa2a61f8b0092..af355695f3ed8 100644
--- a/x-pack/test/api_integration/config.ts
+++ b/x-pack/test/api_integration/config.ts
@@ -37,7 +37,6 @@ export async function getApiIntegrationConfig({ readConfigFile }: FtrConfigProvi
'--xpack.ruleRegistry.write.cache.enabled=false',
'--xpack.uptime.ui.monitorManagement.enabled=true',
'--xpack.uptime.service.password=test',
- '--xpack.uptime.service.manifestUrl=http://test.com',
'--xpack.uptime.service.username=localKibanaIntegrationTestsUser',
`--xpack.securitySolution.enableExperimental=${JSON.stringify(['ruleRegistryEnabled'])}`,
],
diff --git a/x-pack/test/cases_api_integration/common/lib/utils.ts b/x-pack/test/cases_api_integration/common/lib/utils.ts
index de27836a9559e..9c56db80e45fc 100644
--- a/x-pack/test/cases_api_integration/common/lib/utils.ts
+++ b/x-pack/test/cases_api_integration/common/lib/utils.ts
@@ -1131,3 +1131,15 @@ export const getServiceNowSimulationServer = async (): Promise<{
return { server, url };
};
+
+/**
+ * Extracts the warning value a warning header that is formatted according to RFC 7234.
+ * For example for the string 299 Kibana-8.1.0 "Deprecation endpoint", the return value is Deprecation endpoint.
+ *
+ */
+export const extractWarningValueFromWarningHeader = (warningHeader: string) => {
+ const firstQuote = warningHeader.indexOf('"');
+ const lastQuote = warningHeader.length - 1;
+ const warningValue = warningHeader.substring(firstQuote + 1, lastQuote);
+ return warningValue;
+};
diff --git a/x-pack/test/cases_api_integration/common/lib/validation.ts b/x-pack/test/cases_api_integration/common/lib/validation.ts
index 220074f22db86..826ac7e2600e1 100644
--- a/x-pack/test/cases_api_integration/common/lib/validation.ts
+++ b/x-pack/test/cases_api_integration/common/lib/validation.ts
@@ -37,3 +37,17 @@ export function arraysToEqual(array1?: object[], array2?: object[]) {
const array1AsSet = new Set(array1);
return array2.every((item) => array1AsSet.has(item));
}
+
+/**
+ * Regular expression to test if a string matches the RFC7234 specification (without warn-date) for warning headers. This pattern assumes that the warn code
+ * is always 299. Further, this pattern assumes that the warn agent represents a version of Kibana.
+ *
+ * Example: 299 Kibana-8.2.0 "Deprecated endpoint"
+ */
+const WARNING_HEADER_REGEX =
+ /299 Kibana-\d+.\d+.\d+(?:-(?:alpha|beta|rc)\\d+)?(?:-SNAPSHOT)? \".+\"/g;
+
+export const assertWarningHeader = (warningHeader: string) => {
+ const res = warningHeader.match(WARNING_HEADER_REGEX);
+ expect(res).not.to.be(null);
+};
diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/get_case.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/get_case.ts
index 8df044baf813e..abafc514e56f3 100644
--- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/get_case.ts
+++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/get_case.ts
@@ -8,7 +8,7 @@
import expect from '@kbn/expect';
import { FtrProviderContext } from '../../../../common/ftr_provider_context';
-import { AttributesTypeUser } from '../../../../../../plugins/cases/common/api';
+import { AttributesTypeUser, getCaseDetailsUrl } from '../../../../../../plugins/cases/common/api';
import { CASES_URL } from '../../../../../../plugins/cases/common/constants';
import {
defaultUser,
@@ -24,6 +24,7 @@ import {
createComment,
removeServerGeneratedPropertiesFromCase,
removeServerGeneratedPropertiesFromSavedObject,
+ extractWarningValueFromWarningHeader,
} from '../../../../common/lib/utils';
import {
secOnly,
@@ -37,6 +38,7 @@ import {
obsSec,
} from '../../../../common/lib/authentication/users';
import { getUserInfo } from '../../../../common/lib/authentication';
+import { assertWarningHeader } from '../../../../common/lib/validation';
// eslint-disable-next-line import/no-default-export
export default ({ getService }: FtrProviderContext): void => {
@@ -191,5 +193,29 @@ export default ({ getService }: FtrProviderContext): void => {
});
});
});
+
+ describe('deprecations', () => {
+ for (const paramValue of [true, false]) {
+ it(`should return a warning header if includeComments=${paramValue}`, async () => {
+ const theCase = await createCase(supertest, postCaseReq);
+ const res = await supertest
+ .get(`${getCaseDetailsUrl(theCase.id)}?includeComments=${paramValue}`)
+ .expect(200);
+ const warningHeader = res.header.warning;
+
+ assertWarningHeader(warningHeader);
+
+ const warningValue = extractWarningValueFromWarningHeader(warningHeader);
+ expect(warningValue).to.be('Deprecated query parameter includeComments');
+ });
+ }
+
+ it('should not return a warning header if includeComments is not provided', async () => {
+ const theCase = await createCase(supertest, postCaseReq);
+ const res = await supertest.get(getCaseDetailsUrl(theCase.id)).expect(200);
+ const warningHeader = res.header.warning;
+ expect(warningHeader).to.be(undefined);
+ });
+ });
});
};
diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/status/get_status.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/status/get_status.ts
index 02ace7077a20a..c170dc0ff3ccd 100644
--- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/status/get_status.ts
+++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/status/get_status.ts
@@ -16,6 +16,7 @@ import {
getAllCasesStatuses,
deleteAllCaseItems,
superUserSpace1Auth,
+ extractWarningValueFromWarningHeader,
} from '../../../../../common/lib/utils';
import {
globalRead,
@@ -26,6 +27,8 @@ import {
secOnlyRead,
superUser,
} from '../../../../../common/lib/authentication/users';
+import { CASE_STATUS_URL } from '../../../../../../../plugins/cases/common/constants';
+import { assertWarningHeader } from '../../../../../common/lib/validation';
// eslint-disable-next-line import/no-default-export
export default ({ getService }: FtrProviderContext): void => {
@@ -181,5 +184,18 @@ export default ({ getService }: FtrProviderContext): void => {
});
}
});
+
+ describe('deprecations', () => {
+ it('should return a warning header', async () => {
+ await createCase(supertest, postCaseReq);
+ const res = await supertest.get(CASE_STATUS_URL).expect(200);
+ const warningHeader = res.header.warning;
+
+ assertWarningHeader(warningHeader);
+
+ const warningValue = extractWarningValueFromWarningHeader(warningHeader);
+ expect(warningValue).to.be('Deprecated endpoint');
+ });
+ });
});
};
diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/comments/get_all_comments.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/comments/get_all_comments.ts
index e0ee2576ef610..d6e52b6577fef 100644
--- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/comments/get_all_comments.ts
+++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/comments/get_all_comments.ts
@@ -15,6 +15,7 @@ import {
createComment,
getAllComments,
superUserSpace1Auth,
+ extractWarningValueFromWarningHeader,
} from '../../../../common/lib/utils';
import {
globalRead,
@@ -27,6 +28,8 @@ import {
secOnlyRead,
superUser,
} from '../../../../common/lib/authentication/users';
+import { getCaseCommentsUrl } from '../../../../../../plugins/cases/common/api';
+import { assertWarningHeader } from '../../../../common/lib/validation';
// eslint-disable-next-line import/no-default-export
export default ({ getService }: FtrProviderContext): void => {
@@ -148,5 +151,18 @@ export default ({ getService }: FtrProviderContext): void => {
});
});
});
+
+ describe('deprecations', () => {
+ it('should return a warning header', async () => {
+ const theCase = await createCase(supertest, postCaseReq);
+ const res = await supertest.get(getCaseCommentsUrl(theCase.id)).expect(200);
+ const warningHeader = res.header.warning;
+
+ assertWarningHeader(warningHeader);
+
+ const warningValue = extractWarningValueFromWarningHeader(warningHeader);
+ expect(warningValue).to.be('Deprecated endpoint');
+ });
+ });
});
};
diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/user_actions/get_all_user_actions.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/user_actions/get_all_user_actions.ts
index 17863b282bf05..b09059966a16b 100644
--- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/user_actions/get_all_user_actions.ts
+++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/user_actions/get_all_user_actions.ts
@@ -13,6 +13,7 @@ import {
CaseStatuses,
CommentType,
ConnectorTypes,
+ getCaseUserActionUrl,
} from '../../../../../../plugins/cases/common/api';
import { CreateCaseUserAction } from '../../../../../../plugins/cases/common/api/cases/user_actions/create_case';
import { postCaseReq, postCommentUserReq, getPostCaseRequest } from '../../../../common/lib/mock';
@@ -26,6 +27,7 @@ import {
createComment,
updateComment,
deleteComment,
+ extractWarningValueFromWarningHeader,
} from '../../../../common/lib/utils';
import {
globalRead,
@@ -36,6 +38,7 @@ import {
secOnlyRead,
superUser,
} from '../../../../common/lib/authentication/users';
+import { assertWarningHeader } from '../../../../common/lib/validation';
// eslint-disable-next-line import/no-default-export
export default ({ getService }: FtrProviderContext): void => {
@@ -354,5 +357,18 @@ export default ({ getService }: FtrProviderContext): void => {
});
}
});
+
+ describe('deprecations', () => {
+ it('should return a warning header', async () => {
+ const theCase = await createCase(supertest, postCaseReq);
+ const res = await supertest.get(getCaseUserActionUrl(theCase.id)).expect(200);
+ const warningHeader = res.header.warning;
+
+ assertWarningHeader(warningHeader);
+
+ const warningValue = extractWarningValueFromWarningHeader(warningHeader);
+ expect(warningValue).to.be('Deprecated endpoint');
+ });
+ });
});
};
diff --git a/x-pack/test/functional/apps/uptime/index.ts b/x-pack/test/functional/apps/uptime/index.ts
index d36f8124599fe..6ecf87f88b8be 100644
--- a/x-pack/test/functional/apps/uptime/index.ts
+++ b/x-pack/test/functional/apps/uptime/index.ts
@@ -61,10 +61,6 @@ export default ({ loadTestFile, getService }: FtrProviderContext) => {
loadTestFile(require.resolve('./certificates'));
});
- describe('with generated data but no data reset', () => {
- loadTestFile(require.resolve('./ping_redirects'));
- });
-
describe('with real-world data', () => {
before(async () => {
await esArchiver.unload(ARCHIVE);
@@ -75,7 +71,6 @@ export default ({ loadTestFile, getService }: FtrProviderContext) => {
after(async () => await esArchiver.unload(ARCHIVE));
loadTestFile(require.resolve('./overview'));
- loadTestFile(require.resolve('./monitor'));
loadTestFile(require.resolve('./ml_anomaly'));
loadTestFile(require.resolve('./feature_controls'));
});
diff --git a/x-pack/test/functional/apps/uptime/monitor.ts b/x-pack/test/functional/apps/uptime/monitor.ts
deleted file mode 100644
index de88bd12a00fb..0000000000000
--- a/x-pack/test/functional/apps/uptime/monitor.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { FtrProviderContext } from '../../ftr_provider_context';
-
-export default ({ getPageObjects, getService }: FtrProviderContext) => {
- const esArchiver = getService('esArchiver');
- const uptimeService = getService('uptime');
- const { uptime } = getPageObjects(['uptime']);
- const archive = 'x-pack/test/functional/es_archives/uptime/full_heartbeat';
-
- describe('monitor page', function () {
- this.tags(['skipFirefox']);
- const dateStart = 'Sep 10, 2019 @ 12:40:08.078';
- const dateEnd = 'Sep 11, 2019 @ 19:40:08.078';
- const monitorId = '0000-intermittent';
-
- before(async () => {
- await esArchiver.loadIfNeeded(archive);
- await uptimeService.navigation.goToUptime();
- });
-
- after(async () => {
- await esArchiver.unload(archive);
- });
-
- describe('navigation to monitor page', () => {
- before(async () => {
- await uptime.loadDataAndGoToMonitorPage(dateStart, dateEnd, monitorId);
- });
-
- it('should select the ping list location filter', async () => {
- await uptimeService.common.selectFilterItem('Location', 'mpls');
- });
-
- it('should set the status filter', async () => {
- await uptimeService.common.setStatusFilterUp();
- });
-
- it('displays ping data as expected', async () => {
- await uptime.checkPingListInteractions([
- 'XZtoHm0B0I9WX_CznN-6',
- '7ZtoHm0B0I9WX_CzJ96M',
- 'pptnHm0B0I9WX_Czst5X',
- 'I5tnHm0B0I9WX_CzPd46',
- 'y5tmHm0B0I9WX_Czx93x',
- 'XZtmHm0B0I9WX_CzUt3H',
- '-JtlHm0B0I9WX_Cz3dyX',
- 'k5tlHm0B0I9WX_CzaNxm',
- 'NZtkHm0B0I9WX_Cz89w9',
- 'zJtkHm0B0I9WX_CzftsN',
- ]);
- });
- });
- });
-};
diff --git a/x-pack/test/functional/apps/uptime/ping_redirects.ts b/x-pack/test/functional/apps/uptime/ping_redirects.ts
deleted file mode 100644
index 06352d37ada28..0000000000000
--- a/x-pack/test/functional/apps/uptime/ping_redirects.ts
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { makeChecksWithStatus } from '../../../api_integration/apis/uptime/rest/helper/make_checks';
-import { FtrProviderContext } from '../../ftr_provider_context';
-
-const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
-
-export default ({ getPageObjects, getService }: FtrProviderContext) => {
- const { uptime: uptimePage, header } = getPageObjects(['uptime', 'header']);
- const uptime = getService('uptime');
- const esArchiver = getService('esArchiver');
-
- const archive = 'x-pack/test/functional/es_archives/uptime/blank';
-
- const monitor = () => uptime.monitor;
-
- // FLAKY: https://github.com/elastic/kibana/issues/118476
- describe.skip('Ping redirects', () => {
- const start = '~ 15 minutes ago';
- const end = 'now';
-
- const MONITOR_ID = 'redirect-testing-id';
-
- before(async () => {
- await esArchiver.loadIfNeeded(archive);
- });
-
- after('unload', async () => {
- await esArchiver.unload(archive);
- });
-
- beforeEach(async () => {
- await makeChecksWithStatus(
- getService('es'),
- MONITOR_ID,
- 5,
- 2,
- 10000,
- {
- http: {
- rtt: { total: { us: 157784 } },
- response: {
- status_code: 200,
- redirects: ['http://localhost:3000/first', 'https://www.washingtonpost.com/'],
- body: {
- bytes: 642102,
- hash: '597a8cfb33ff8e09bff16283306553c3895282aaf5386e1843d466d44979e28a',
- },
- },
- },
- },
- 'up'
- );
- await delay(1000);
- });
-
- it('loads and goes to details page', async () => {
- await uptime.navigation.goToUptime();
- await uptimePage.loadDataAndGoToMonitorPage(start, end, MONITOR_ID);
- });
-
- it('display redirect info in detail panel', async () => {
- await header.waitUntilLoadingHasFinished();
- await monitor().hasRedirectInfo();
- });
-
- it('displays redirects in ping list expand row', async () => {
- await monitor().hasRedirectInfoInPingList();
- });
- });
-};
diff --git a/x-pack/test/functional_with_es_ssl/apps/uptime/anomaly_alert.ts b/x-pack/test/functional_with_es_ssl/apps/uptime/anomaly_alert.ts
deleted file mode 100644
index 6f65c620b527c..0000000000000
--- a/x-pack/test/functional_with_es_ssl/apps/uptime/anomaly_alert.ts
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import expect from '@kbn/expect';
-import { FtrProviderContext } from '../../ftr_provider_context';
-
-export default ({ getPageObjects, getService }: FtrProviderContext) => {
- // FLAKY: https://github.com/elastic/kibana/issues/111667
- describe.skip('uptime anomaly alert', () => {
- const pageObjects = getPageObjects(['common', 'uptime']);
- const supertest = getService('supertest');
- const retry = getService('retry');
-
- const monitorId = '0000-intermittent';
-
- const uptime = getService('uptime');
-
- const DEFAULT_DATE_START = 'Sep 10, 2019 @ 12:40:08.078';
- const DEFAULT_DATE_END = 'Sep 11, 2019 @ 19:40:08.078';
- let alerts: any;
- const alertId = 'uptime-anomaly-alert';
-
- before(async () => {
- alerts = getService('uptime').alerts;
-
- await uptime.navigation.goToUptime();
-
- await uptime.navigation.loadDataAndGoToMonitorPage(
- DEFAULT_DATE_START,
- DEFAULT_DATE_END,
- monitorId
- );
- });
-
- it('can delete existing job', async () => {
- if (await uptime.ml.alreadyHasJob()) {
- await uptime.ml.openMLManageMenu();
- await uptime.ml.deleteMLJob();
- await uptime.navigation.refreshApp();
- }
- });
-
- it('can open ml flyout', async () => {
- await uptime.ml.openMLFlyout();
- });
-
- it('has permission to create job', async () => {
- expect(uptime.ml.canCreateJob()).to.eql(true);
- expect(uptime.ml.hasNoLicenseInfo()).to.eql(false);
- });
-
- it('can create job successfully', async () => {
- await uptime.ml.createMLJob();
- await pageObjects.common.closeToast();
- await uptime.ml.cancelAlertFlyout();
- });
-
- it('can open ML Manage Menu', async () => {
- await uptime.ml.openMLManageMenu();
- });
-
- it('can open anomaly alert flyout', async () => {
- await uptime.ml.openAlertFlyout();
- });
-
- it('can set alert name', async () => {
- await alerts.setAlertName(alertId);
- });
-
- it('can set alert tags', async () => {
- await alerts.setAlertTags(['uptime', 'anomaly-alert']);
- });
-
- it('can change anomaly alert threshold', async () => {
- await uptime.ml.changeAlertThreshold('major');
- });
-
- it('can save alert', async () => {
- await alerts.clickSaveAlertButton();
- await alerts.clickSaveAlertsConfirmButton();
- await pageObjects.common.closeToast();
- });
-
- it('has created a valid alert with expected parameters', async () => {
- let alert: any;
- await retry.tryForTime(15000, async () => {
- const apiResponse = await supertest.get(`/api/alerts/_find?search=${alertId}`);
- const alertsFromThisTest = apiResponse.body.data.filter(
- ({ name }: { name: string }) => name === alertId
- );
- expect(alertsFromThisTest).to.have.length(1);
- alert = alertsFromThisTest[0];
- });
-
- // Ensure the parameters and other stateful data
- // on the alert match up with the values we provided
- // for our test helper to input into the flyout.
- const { actions, alertTypeId, consumer, id, params, tags } = alert;
- try {
- expect(actions).to.eql([]);
- expect(alertTypeId).to.eql('xpack.uptime.alerts.durationAnomaly');
- expect(consumer).to.eql('uptime');
- expect(tags).to.eql(['uptime', 'anomaly-alert']);
- expect(params.monitorId).to.eql(monitorId);
- expect(params.severity).to.eql(50);
- } finally {
- await supertest.delete(`/api/alerts/alert/${id}`).set('kbn-xsrf', 'true').expect(204);
- }
- });
-
- it('change button to disable anomaly alert', async () => {
- await uptime.ml.openMLManageMenu();
- expect(uptime.ml.manageAnomalyAlertIsVisible()).to.eql(true);
- });
-
- it('can delete job successfully', async () => {
- await uptime.ml.deleteMLJob();
- });
-
- it('verifies that alert is also deleted', async () => {
- await retry.tryForTime(15000, async () => {
- const apiResponse = await supertest.get(`/api/alerts/_find?search=${alertId}`);
- const alertsFromThisTest = apiResponse.body.data.filter(
- ({ name }: { name: string }) => name === alertId
- );
- expect(alertsFromThisTest).to.have.length(0);
- });
- });
- });
-};
diff --git a/x-pack/test/functional_with_es_ssl/apps/uptime/index.ts b/x-pack/test/functional_with_es_ssl/apps/uptime/index.ts
index 222dcd22d6f86..d2078267bde85 100644
--- a/x-pack/test/functional_with_es_ssl/apps/uptime/index.ts
+++ b/x-pack/test/functional_with_es_ssl/apps/uptime/index.ts
@@ -24,7 +24,6 @@ export default ({ getService, loadTestFile }: FtrProviderContext) => {
after(async () => await esArchiver.unload(ARCHIVE));
loadTestFile(require.resolve('./alert_flyout'));
- loadTestFile(require.resolve('./anomaly_alert'));
loadTestFile(require.resolve('./simple_down_alert'));
});
});