From 1464741e54b91d2013fa366c65b630b18fe927f9 Mon Sep 17 00:00:00 2001
From: Brian Gaddis
Date: Tue, 4 Sep 2018 12:32:03 -0400
Subject: [PATCH 01/68] TypeScript Reporting Layouts (#22454)
* wip
WIP
* Changed any ypes to actual types
Made sure all types are set on the new layout classes.
* Changes recommended from code review
Changed location of type interfaces and fixed naming errors
* Latest Code Review Changes
Fix naming on properties and methods as well as a few other fixes
* Name Changes and spacing
Name Changes and spacing
* Name Change
Name Change
* Changes for typescript import and direct reference to layout_factory
Changes for typescript import and direct reference to layout_factory
* Move types locally
* Evaluate function changes for puppeteer
* Removed String as a type and renamed index.d.ts to types.d.ts for consistency
Removed String as a type and renamed index.d.ts to types.d.ts for consistency
* Changed layout_factoy to create_layout
---
.../common/{constants.js => constants.ts} | 2 +-
.../printable_pdf/server/lib/generate_pdf.js | 7 +-
.../server/lib/layouts/create_layout.ts | 24 ++++
.../printable_pdf/server/lib/layouts/index.js | 20 ----
.../printable_pdf/server/lib/layouts/index.ts | 9 ++
.../server/lib/layouts/layout.ts | 43 ++++++++
.../server/lib/layouts/preserve_layout.js | 69 ------------
.../server/lib/layouts/preserve_layout.ts | 81 ++++++++++++++
.../printable_pdf/server/lib/layouts/print.js | 89 ---------------
.../server/lib/layouts/print_layout.ts | 103 ++++++++++++++++++
.../server/lib/layouts/types.d.ts | 17 +++
x-pack/plugins/reporting/types.d.ts | 23 ++++
12 files changed, 305 insertions(+), 182 deletions(-)
rename x-pack/plugins/reporting/export_types/printable_pdf/common/{constants.js => constants.ts} (99%)
create mode 100644 x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/create_layout.ts
delete mode 100644 x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/index.js
create mode 100644 x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/index.ts
create mode 100644 x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/layout.ts
delete mode 100644 x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/preserve_layout.js
create mode 100644 x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/preserve_layout.ts
delete mode 100644 x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/print.js
create mode 100644 x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/print_layout.ts
create mode 100644 x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/types.d.ts
create mode 100644 x-pack/plugins/reporting/types.d.ts
diff --git a/x-pack/plugins/reporting/export_types/printable_pdf/common/constants.js b/x-pack/plugins/reporting/export_types/printable_pdf/common/constants.ts
similarity index 99%
rename from x-pack/plugins/reporting/export_types/printable_pdf/common/constants.js
rename to x-pack/plugins/reporting/export_types/printable_pdf/common/constants.ts
index 8044c7c40ba2f..ddc678592760a 100644
--- a/x-pack/plugins/reporting/export_types/printable_pdf/common/constants.js
+++ b/x-pack/plugins/reporting/export_types/printable_pdf/common/constants.ts
@@ -7,4 +7,4 @@
export const LayoutTypes = {
PRESERVE_LAYOUT: 'preserve_layout',
PRINT: 'print',
-};
\ No newline at end of file
+};
diff --git a/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/generate_pdf.js b/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/generate_pdf.js
index b68a7c24e1627..198a4d7dde0f1 100644
--- a/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/generate_pdf.js
+++ b/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/generate_pdf.js
@@ -11,7 +11,7 @@ import { pdf } from './pdf';
import { groupBy } from 'lodash';
import { oncePerServer } from '../../../../server/lib/once_per_server';
import { screenshotsObservableFactory } from './screenshots';
-import { getLayoutFactory } from './layouts';
+import { createLayout } from './layouts';
const getTimeRange = (urlScreenshots) => {
const grouped = groupBy(urlScreenshots.map(u => u.timeRange));
@@ -31,7 +31,6 @@ const formatDate = (date, timezone) => {
function generatePdfObservableFn(server) {
const screenshotsObservable = screenshotsObservableFactory(server);
const captureConcurrency = 1;
- const getLayout = getLayoutFactory(server);
const urlScreenshotsObservable = (urls, headers, layout) => {
return Rx.from(urls).pipe(
@@ -68,7 +67,9 @@ function generatePdfObservableFn(server) {
return function generatePdfObservable(title, urls, browserTimezone, headers, layoutParams, logo) {
- const layout = getLayout(layoutParams);
+
+ const layout = createLayout(server, layoutParams);
+
const screenshots$ = urlScreenshotsObservable(urls, headers, layout);
return screenshots$.pipe(
diff --git a/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/create_layout.ts b/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/create_layout.ts
new file mode 100644
index 0000000000000..1863c2050fb44
--- /dev/null
+++ b/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/create_layout.ts
@@ -0,0 +1,24 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import { KbnServer, Size } from '../../../../../types';
+import { LayoutTypes } from '../../../common/constants';
+import { Layout } from './layout';
+import { PreserveLayout } from './preserve_layout';
+import { PrintLayout } from './print_layout';
+
+interface LayoutParams {
+ id: string;
+ dimensions: Size;
+}
+
+export function createLayout(server: KbnServer, layoutParams: LayoutParams): Layout {
+ if (layoutParams && layoutParams.id === LayoutTypes.PRESERVE_LAYOUT) {
+ return new PreserveLayout(layoutParams.id, layoutParams.dimensions);
+ }
+
+ // this is the default because some jobs won't have anything specified
+ return new PrintLayout(server, layoutParams.id);
+}
diff --git a/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/index.js b/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/index.js
deleted file mode 100644
index 14c593071b617..0000000000000
--- a/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/index.js
+++ /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;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { LayoutTypes } from '../../../common/constants';
-import { preserveLayoutFactory } from './preserve_layout';
-import { printLayoutFactory } from './print';
-
-export function getLayoutFactory(server) {
- return function getLayout(layoutParams) {
- if (layoutParams && layoutParams.id === LayoutTypes.PRESERVE_LAYOUT) {
- return preserveLayoutFactory(server, layoutParams);
- }
-
- // this is the default because some jobs won't have anything specified
- return printLayoutFactory(server, layoutParams);
- };
-}
diff --git a/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/index.ts b/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/index.ts
new file mode 100644
index 0000000000000..fd35485779ba0
--- /dev/null
+++ b/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/index.ts
@@ -0,0 +1,9 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export { createLayout } from './create_layout';
+export { PrintLayout } from './print_layout';
+export { PreserveLayout } from './preserve_layout';
diff --git a/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/layout.ts b/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/layout.ts
new file mode 100644
index 0000000000000..eb7caf10307f3
--- /dev/null
+++ b/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/layout.ts
@@ -0,0 +1,43 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import { Size } from '../../../../../types';
+import { ViewZoomWidthHeight } from './types';
+
+export interface PageSizeParams {
+ pageMarginTop: number;
+ pageMarginBottom: number;
+ pageMarginWidth: number;
+ tableBorderWidth: number;
+ headingHeight: number;
+ subheadingHeight: number;
+}
+
+export interface PdfImageSize {
+ width: number;
+ height?: number;
+}
+
+export abstract class Layout {
+ public id: string = '';
+
+ constructor(id: string) {
+ this.id = id;
+ }
+
+ public abstract getPdfImageSize(): PdfImageSize;
+
+ public abstract getPdfPageOrientation(): string | undefined;
+
+ public abstract getPdfPageSize(pageSizeParams: PageSizeParams): string | Size;
+
+ public abstract getViewport(itemsCount: number): ViewZoomWidthHeight;
+
+ public abstract getBrowserZoom(): number;
+
+ public abstract getBrowserViewport(): Size;
+
+ public abstract getCssOverridesPath(): string;
+}
diff --git a/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/preserve_layout.js b/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/preserve_layout.js
deleted file mode 100644
index cee717553e101..0000000000000
--- a/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/preserve_layout.js
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import path from 'path';
-
-// you'll notice that we aren't passing the zoom at this time, while it'd be possible to use
-// window.pixelDensity to figure out what the current user is seeing, if they're going to send the
-// PDF to someone else, I can see there being benefit to using a higher pixel density, so we're
-// going to leave this hard-coded for the time being
-export function preserveLayoutFactory(server, { dimensions: { height, width }, zoom = 2 }) {
- const scaledHeight = height * zoom;
- const scaledWidth = width * zoom;
-
- return {
- getCssOverridesPath() {
- return path.join(__dirname, 'preserve_layout.css');
- },
-
- getBrowserViewport() {
- return {
- height: scaledHeight,
- width: scaledWidth,
- };
- },
-
- getBrowserZoom() {
- return zoom;
- },
-
- getViewport() {
- return {
- height: scaledHeight,
- width: scaledWidth,
- zoom
- };
- },
-
- getPdfImageSize() {
- return {
- height: height,
- width: width,
- };
- },
-
- getPdfPageOrientation() {
- return undefined;
- },
-
- getPdfPageSize({ pageMarginTop, pageMarginBottom, pageMarginWidth, tableBorderWidth, headingHeight, subheadingHeight }) {
- return {
- height: height + pageMarginTop + pageMarginBottom + (tableBorderWidth * 2) + headingHeight + subheadingHeight,
- width: width + (pageMarginWidth * 2) + (tableBorderWidth * 2),
- };
- },
-
- groupCount: 1,
-
- selectors: {
- screenshot: '[data-shared-items-container]',
- renderComplete: '[data-shared-item]',
- itemsCountAttribute: 'data-shared-items-count',
- timefilterFromAttribute: 'data-shared-timefilter-from',
- timefilterToAttribute: 'data-shared-timefilter-to',
- }
- };
-}
\ No newline at end of file
diff --git a/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/preserve_layout.ts b/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/preserve_layout.ts
new file mode 100644
index 0000000000000..ea58a09c5550e
--- /dev/null
+++ b/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/preserve_layout.ts
@@ -0,0 +1,81 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import path from 'path';
+import { Size } from '../../../../../types';
+import { Layout, PageSizeParams } from './layout';
+
+const ZOOM: number = 2;
+
+export class PreserveLayout extends Layout {
+ public readonly selectors = {
+ screenshot: '[data-shared-items-container]',
+ renderComplete: '[data-shared-item]',
+ itemsCountAttribute: 'data-shared-items-count',
+ timefilterFromAttribute: 'data-shared-timefilter-from',
+ timefilterToAttribute: 'data-shared-timefilter-to',
+ };
+
+ public readonly groupCount = 1;
+ private readonly height: number;
+ private readonly width: number;
+ private readonly scaledHeight: number;
+ private readonly scaledWidth: number;
+
+ constructor(id: string, size: Size) {
+ super(id);
+ this.height = size.height;
+ this.width = size.width;
+ this.scaledHeight = size.height * ZOOM;
+ this.scaledWidth = size.width * ZOOM;
+ }
+
+ public getCssOverridesPath() {
+ return path.join(__dirname, 'preserve_layout.css');
+ }
+
+ public getBrowserViewport() {
+ return {
+ height: this.scaledHeight,
+ width: this.scaledWidth,
+ };
+ }
+
+ public getBrowserZoom() {
+ return ZOOM;
+ }
+
+ public getViewport() {
+ return {
+ height: this.scaledHeight,
+ width: this.scaledWidth,
+ zoom: ZOOM,
+ };
+ }
+
+ public getPdfImageSize() {
+ return {
+ height: this.height,
+ width: this.width,
+ };
+ }
+
+ public getPdfPageOrientation() {
+ return undefined;
+ }
+
+ public getPdfPageSize(pageSizeParams: PageSizeParams) {
+ return {
+ height:
+ this.height +
+ pageSizeParams.pageMarginTop +
+ pageSizeParams.pageMarginBottom +
+ pageSizeParams.tableBorderWidth * 2 +
+ pageSizeParams.headingHeight +
+ pageSizeParams.subheadingHeight,
+ width: this.width + pageSizeParams.pageMarginWidth * 2 + pageSizeParams.tableBorderWidth * 2,
+ };
+ }
+}
diff --git a/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/print.js b/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/print.js
deleted file mode 100644
index 1986a2e5a8889..0000000000000
--- a/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/print.js
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import path from 'path';
-
-export function printLayoutFactory(server) {
- const config = server.config();
- const captureConfig = config.get('xpack.reporting.capture');
-
- const selectors = {
- screenshot: '[data-shared-item]',
- renderComplete: '[data-shared-item]',
- itemsCountAttribute: 'data-shared-items-count',
- timefilterFromAttribute: 'data-shared-timefilter-from',
- timefilterToAttribute: 'data-shared-timefilter-to',
- };
-
- return {
-
- getCssOverridesPath() {
- return path.join(__dirname, 'print.css');
- },
-
- getBrowserViewport() {
- return captureConfig.viewport;
- },
-
- getBrowserZoom() {
- return captureConfig.zoom;
- },
-
- getViewport(itemsCount) {
- return {
- zoom: captureConfig.zoom,
- width: captureConfig.viewport.width,
- height: captureConfig.viewport.height * itemsCount,
- };
- },
-
- async positionElements(browser) {
- const elementSize = {
- width: captureConfig.viewport.width / captureConfig.zoom,
- height: captureConfig.viewport.height / captureConfig.zoom
- };
-
- await browser.evaluate({
- fn: function (selector, height, width) {
- const visualizations = document.querySelectorAll(selector);
- const visualizationsLength = visualizations.length;
-
- for (let i = 0; i < visualizationsLength; i++) {
- const visualization = visualizations[i];
- const style = visualization.style;
- style.position = 'fixed';
- style.top = `${height * i}px`;
- style.left = 0;
- style.width = `${width}px`;
- style.height = `${height}px`;
- style.zIndex = 1;
- style.backgroundColor = 'inherit';
- }
- },
- args: [selectors.screenshot, elementSize.height, elementSize.width],
- });
- },
-
- getPdfImageSize() {
- return {
- width: 500,
- };
- },
-
- getPdfPageOrientation() {
- return 'portrait';
- },
-
- getPdfPageSize() {
- return 'A4';
- },
-
- groupCount: 2,
-
- selectors
-
- };
-}
\ No newline at end of file
diff --git a/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/print_layout.ts b/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/print_layout.ts
new file mode 100644
index 0000000000000..44f65d35e6d9c
--- /dev/null
+++ b/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/print_layout.ts
@@ -0,0 +1,103 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import path from 'path';
+import { KbnServer, Size } from '../../../../../types';
+import { Layout } from './layout';
+import { CaptureConfig } from './types';
+
+type EvalArgs = any[];
+
+interface EvaluateOptions {
+ // 'fn' is a function in string form to avoid tslint from auto formatting it into a version not
+ // underfood by transform_fn safeWrap.
+ fn: ((...evalArgs: EvalArgs) => any);
+ args: EvalArgs; // Arguments to be passed into the function defined by fn.
+}
+
+interface BrowserClient {
+ evaluate: (evaluateOptions: EvaluateOptions) => void;
+}
+
+export class PrintLayout extends Layout {
+ public selectors = {
+ screenshot: '[data-shared-item]',
+ renderComplete: '[data-shared-item]',
+ itemsCountAttribute: 'data-shared-items-count',
+ timefilterFromAttribute: 'data-shared-timefilter-from',
+ timefilterToAttribute: 'data-shared-timefilter-to',
+ };
+
+ public readonly groupCount = 2;
+
+ private captureConfig: CaptureConfig;
+
+ constructor(server: KbnServer, id: string) {
+ super(id);
+ this.captureConfig = server.config().get('xpack.reporting.capture');
+ }
+
+ public getCssOverridesPath() {
+ return path.join(__dirname, 'print.css');
+ }
+
+ public getBrowserViewport() {
+ return this.captureConfig.viewport;
+ }
+
+ public getBrowserZoom() {
+ return this.captureConfig.zoom;
+ }
+
+ public getViewport(itemsCount: number) {
+ return {
+ zoom: this.captureConfig.zoom,
+ width: this.captureConfig.viewport.width,
+ height: this.captureConfig.viewport.height * itemsCount,
+ };
+ }
+
+ public async positionElements(browser: BrowserClient): Promise {
+ const elementSize: Size = {
+ width: this.captureConfig.viewport.width / this.captureConfig.zoom,
+ height: this.captureConfig.viewport.height / this.captureConfig.zoom,
+ };
+ const evalOptions: EvaluateOptions = {
+ fn: (selector: string, height: number, width: number) => {
+ const visualizations = document.querySelectorAll(selector) as NodeListOf;
+ const visualizationsLength = visualizations.length;
+
+ for (let i = 0; i < visualizationsLength; i++) {
+ const visualization = visualizations[i];
+ const style = visualization.style;
+ style.position = 'fixed';
+ style.top = `${height * i}px`;
+ style.left = '0';
+ style.width = `${width}px`;
+ style.height = `${height}px`;
+ style.zIndex = '1';
+ style.backgroundColor = 'inherit';
+ }
+ },
+ args: [this.selectors.screenshot, elementSize.height, elementSize.width],
+ };
+
+ await browser.evaluate(evalOptions);
+ }
+
+ public getPdfImageSize() {
+ return {
+ width: 500,
+ };
+ }
+
+ public getPdfPageOrientation() {
+ return 'portrait';
+ }
+
+ public getPdfPageSize() {
+ return 'A4';
+ }
+}
diff --git a/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/types.d.ts b/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/types.d.ts
new file mode 100644
index 0000000000000..0ade1093312ea
--- /dev/null
+++ b/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/types.d.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import { Size } from '../../../../../types';
+
+export interface CaptureConfig {
+ zoom: number;
+ viewport: Size;
+}
+
+export interface ViewZoomWidthHeight {
+ zoom: number;
+ width: number;
+ height: number;
+}
diff --git a/x-pack/plugins/reporting/types.d.ts b/x-pack/plugins/reporting/types.d.ts
new file mode 100644
index 0000000000000..3e5f243fc8252
--- /dev/null
+++ b/x-pack/plugins/reporting/types.d.ts
@@ -0,0 +1,23 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+export interface KbnServer {
+ config: () => ConfigObject;
+}
+
+export interface ConfigObject {
+ get: (path: string) => any;
+}
+
+export interface Size {
+ width: number;
+ height: number;
+}
+
+export interface Logger {
+ debug: (message: string) => void;
+ error: (message: string) => void;
+ warning: (message: string) => void;
+}
From 005be474a226f07e12a38f30805183aa96b9e6b1 Mon Sep 17 00:00:00 2001
From: James Gowdy
Date: Tue, 4 Sep 2018 18:08:20 +0100
Subject: [PATCH 02/68] [ML] Fixing links to results for obs with no results
(#22650)
---
.../public/jobs/jobs_list/components/job_actions/results.js | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/x-pack/plugins/ml/public/jobs/jobs_list/components/job_actions/results.js b/x-pack/plugins/ml/public/jobs/jobs_list/components/job_actions/results.js
index fe81291165bac..3565f510645da 100644
--- a/x-pack/plugins/ml/public/jobs/jobs_list/components/job_actions/results.js
+++ b/x-pack/plugins/ml/public/jobs/jobs_list/components/job_actions/results.js
@@ -30,6 +30,11 @@ function getLink(location, jobs) {
to = tos[0].string;
}
+ // if either of the dates are empty, set them to undefined
+ // moment will convert undefined to now.
+ from = (from === '') ? undefined : from;
+ to = (to === '') ? undefined : to;
+
const jobIds = jobs.map(j => j.id);
const url = mlJobService.createResultsUrl(jobIds, from, to, location);
return `${chrome.getBasePath()}/app/${url}`;
From 85bee360c7ca2fae8a88f6d0321f6f9e443be273 Mon Sep 17 00:00:00 2001
From: James Gowdy
Date: Tue, 4 Sep 2018 18:09:47 +0100
Subject: [PATCH 03/68] [ML] Adding milliseconds to watch start and end times
(#22659)
---
.../ml/public/jobs/new_job/simple/components/watcher/watch.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/x-pack/plugins/ml/public/jobs/new_job/simple/components/watcher/watch.js b/x-pack/plugins/ml/public/jobs/new_job/simple/components/watcher/watch.js
index 33de20299cba5..b193f0cc55ed1 100644
--- a/x-pack/plugins/ml/public/jobs/new_job/simple/components/watcher/watch.js
+++ b/x-pack/plugins/ml/public/jobs/new_job/simple/components/watcher/watch.js
@@ -83,7 +83,7 @@ export const watch = {
script: {
lang: 'painless',
inline: `LocalDateTime.ofEpochSecond((doc["timestamp"].date.getMillis()-((doc["bucket_span"].value * 1000)
- * params.padding)) / 1000, 0, ZoneOffset.UTC).toString()`,
+ * params.padding)) / 1000, 0, ZoneOffset.UTC).toString()+\":00.000Z\"`,
params: {
'padding': 10
}
@@ -93,7 +93,7 @@ export const watch = {
script: {
lang: 'painless',
inline: `LocalDateTime.ofEpochSecond((doc["timestamp"].date.getMillis()+((doc["bucket_span"].value * 1000)
- * params.padding)) / 1000, 0, ZoneOffset.UTC).toString()`,
+ * params.padding)) / 1000, 0, ZoneOffset.UTC).toString()+\":00.000Z\"`,
params: {
'padding': 10
}
From 6c896134ed6e1feaaefc2d5253bc3a4af327479c Mon Sep 17 00:00:00 2001
From: James Gowdy
Date: Tue, 4 Sep 2018 19:07:07 +0100
Subject: [PATCH 04/68] [ML] Removing calendars from job when cloning (#22667)
---
x-pack/plugins/ml/public/services/job_service.js | 1 +
1 file changed, 1 insertion(+)
diff --git a/x-pack/plugins/ml/public/services/job_service.js b/x-pack/plugins/ml/public/services/job_service.js
index 825141a586a01..00d3f1bc3a434 100644
--- a/x-pack/plugins/ml/public/services/job_service.js
+++ b/x-pack/plugins/ml/public/services/job_service.js
@@ -520,6 +520,7 @@ class JobService {
delete tempJob.model_snapshot_id;
delete tempJob.open_time;
delete tempJob.established_model_memory;
+ delete tempJob.calendars;
delete tempJob.analysis_config.use_per_partition_normalization;
From bcb793857b7eb1cb01e3c262dc89896c150377bf Mon Sep 17 00:00:00 2001
From: CJ Cenizal
Date: Tue, 4 Sep 2018 11:26:58 -0700
Subject: [PATCH 05/68] Extract index pattern validation rules into ui/public
(#22606)
* Rename containsInvalidCharacters to containsIllegalCharacters and return a value which accurately reflects the name.
* Move illegal index pattern characters from Management into ui/public/index_patterns.
---
.../__tests__/render.test.js | 6 ++++++
.../__tests__/step_index_pattern.test.js | 6 ++++++
.../step_index_pattern/step_index_pattern.js | 7 ++++---
.../constants/index.js | 1 -
.../contains_invalid_characters.test.js | 16 +++++++-------
...ters.js => contains_illegal_characters.js} | 4 ++--
.../create_index_pattern_wizard/lib/index.js | 2 +-
.../public/index_patterns/constants/index.js | 21 +++++++++++++++++++
src/ui/public/index_patterns/index.js | 6 ++++++
9 files changed, 54 insertions(+), 15 deletions(-)
rename src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/lib/{contains_invalid_characters.js => contains_illegal_characters.js} (86%)
create mode 100644 src/ui/public/index_patterns/constants/index.js
diff --git a/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/__tests__/render.test.js b/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/__tests__/render.test.js
index 16da9009f5d5f..9ef4a3e38ee9b 100644
--- a/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/__tests__/render.test.js
+++ b/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/__tests__/render.test.js
@@ -22,6 +22,12 @@ const unmountComponentAtNode = jest.fn();
jest.doMock('react-dom', () => ({ render, unmountComponentAtNode }));
+// If we don't mock this, Jest fails with the error `TypeError: Cannot redefine property: prototype
+// at Function.defineProperties`.
+jest.mock('ui/index_patterns', () => ({
+ INDEX_PATTERN_ILLEGAL_CHARACTERS: ['\\', '/', '?', '"', '<', '>', '|', ' '],
+}));
+
jest.mock('ui/chrome', () => ({
getUiSettingsClient: () => ({
get: () => '',
diff --git a/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/components/step_index_pattern/__tests__/step_index_pattern.test.js b/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/components/step_index_pattern/__tests__/step_index_pattern.test.js
index 3b05ec4b71ec4..37df9762b54df 100644
--- a/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/components/step_index_pattern/__tests__/step_index_pattern.test.js
+++ b/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/components/step_index_pattern/__tests__/step_index_pattern.test.js
@@ -26,6 +26,12 @@ jest.mock('../../../lib/ensure_minimum_time', () => ({
ensureMinimumTime: async (promises) => Array.isArray(promises) ? await Promise.all(promises) : await promises
}));
+// If we don't mock this, Jest fails with the error `TypeError: Cannot redefine property: prototype
+// at Function.defineProperties`.
+jest.mock('ui/index_patterns', () => ({
+ INDEX_PATTERN_ILLEGAL_CHARACTERS: ['\\', '/', '?', '"', '<', '>', '|', ' '],
+}));
+
jest.mock('ui/chrome', () => ({
getUiSettingsClient: () => ({
get: () => '',
diff --git a/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.js b/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.js
index bf6468c3bf15b..133154de52619 100644
--- a/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.js
+++ b/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.js
@@ -19,10 +19,11 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
-import { ILLEGAL_CHARACTERS, MAX_SEARCH_SIZE } from '../../constants';
+import { INDEX_PATTERN_ILLEGAL_CHARACTERS as ILLEGAL_CHARACTERS } from 'ui/index_patterns';
+import { MAX_SEARCH_SIZE } from '../../constants';
import {
getIndices,
- containsInvalidCharacters,
+ containsIllegalCharacters,
getMatchedIndices,
canAppendWildcard,
ensureMinimumTime
@@ -240,7 +241,7 @@ export class StepIndexPatternComponent extends Component {
// This is an error scenario but do not report an error
containsErrors = true;
}
- else if (!containsInvalidCharacters(query, ILLEGAL_CHARACTERS)) {
+ else if (containsIllegalCharacters(query, ILLEGAL_CHARACTERS)) {
const errorMessage = intl.formatMessage(
{
id: 'kbn.management.createIndexPattern.step.invalidCharactersErrorMessage',
diff --git a/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/constants/index.js b/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/constants/index.js
index 79bdbaed0d732..86246903b4440 100644
--- a/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/constants/index.js
+++ b/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/constants/index.js
@@ -26,4 +26,3 @@ export const MAX_NUMBER_OF_MATCHING_INDICES = 100;
export const MAX_SEARCH_SIZE = MAX_NUMBER_OF_MATCHING_INDICES + ESTIMATED_NUMBER_OF_SYSTEM_INDICES;
export const PER_PAGE_INCREMENTS = [5, 10, 20, 50];
-export const ILLEGAL_CHARACTERS = ['\\', '/', '?', '"', '<', '>', '|', ' '];
diff --git a/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/lib/__tests__/contains_invalid_characters.test.js b/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/lib/__tests__/contains_invalid_characters.test.js
index 2385f3baec6bc..05c4aba2571bd 100644
--- a/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/lib/__tests__/contains_invalid_characters.test.js
+++ b/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/lib/__tests__/contains_invalid_characters.test.js
@@ -17,16 +17,16 @@
* under the License.
*/
-import { containsInvalidCharacters } from '../contains_invalid_characters';
+import { containsIllegalCharacters } from '../contains_illegal_characters';
-describe('containsInvalidCharacters', () => {
- it('should fail with illegal characters', () => {
- const valid = containsInvalidCharacters('abc', ['a']);
- expect(valid).toBeFalsy();
+describe('containsIllegalCharacters', () => {
+ it('returns true with illegal characters', () => {
+ const isInvalid = containsIllegalCharacters('abc', ['a']);
+ expect(isInvalid).toBe(true);
});
- it('should pass with no illegal characters', () => {
- const valid = containsInvalidCharacters('abc', ['%']);
- expect(valid).toBeTruthy();
+ it('returns false with no illegal characters', () => {
+ const isInvalid = containsIllegalCharacters('abc', ['%']);
+ expect(isInvalid).toBe(false);
});
});
diff --git a/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/lib/contains_invalid_characters.js b/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/lib/contains_illegal_characters.js
similarity index 86%
rename from src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/lib/contains_invalid_characters.js
rename to src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/lib/contains_illegal_characters.js
index 5dbe3d7111061..31485bb3daaa2 100644
--- a/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/lib/contains_invalid_characters.js
+++ b/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/lib/contains_illegal_characters.js
@@ -17,6 +17,6 @@
* under the License.
*/
-export function containsInvalidCharacters(pattern, illegalCharacters) {
- return !illegalCharacters.some(char => pattern.includes(char));
+export function containsIllegalCharacters(pattern, illegalCharacters) {
+ return illegalCharacters.some(char => pattern.includes(char));
}
diff --git a/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/lib/index.js b/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/lib/index.js
index 22efa498c84ab..0930eb82514e1 100644
--- a/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/lib/index.js
+++ b/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/lib/index.js
@@ -25,6 +25,6 @@ export { getIndices } from './get_indices';
export { getMatchedIndices } from './get_matched_indices';
-export { containsInvalidCharacters } from './contains_invalid_characters';
+export { containsIllegalCharacters } from './contains_illegal_characters';
export { extractTimeFields } from './extract_time_fields';
diff --git a/src/ui/public/index_patterns/constants/index.js b/src/ui/public/index_patterns/constants/index.js
new file mode 100644
index 0000000000000..b22c1682173ca
--- /dev/null
+++ b/src/ui/public/index_patterns/constants/index.js
@@ -0,0 +1,21 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+export const INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE = ['\\', '/', '?', '"', '<', '>', '|'];
+export const INDEX_PATTERN_ILLEGAL_CHARACTERS = INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE.concat(' ');
diff --git a/src/ui/public/index_patterns/index.js b/src/ui/public/index_patterns/index.js
index 025c0248b0848..5df0b7bb8a614 100644
--- a/src/ui/public/index_patterns/index.js
+++ b/src/ui/public/index_patterns/index.js
@@ -18,6 +18,12 @@
*/
export { IndexPatternsProvider } from './index_patterns';
+
export {
IndexPatternsApiClientProvider,
} from './index_patterns_api_client_provider';
+
+export {
+ INDEX_PATTERN_ILLEGAL_CHARACTERS,
+ INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE,
+} from './constants';
From d874c21eeea5887cba672ab8b2063da4e732fb9d Mon Sep 17 00:00:00 2001
From: Rashmi Kulkarni
Date: Tue, 4 Sep 2018 14:08:25 -0700
Subject: [PATCH 06/68] added assertion for the email field.
---
x-pack/test/functional/apps/security/users.js | 1 +
x-pack/test/functional/page_objects/security_page.js | 4 +++-
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/x-pack/test/functional/apps/security/users.js b/x-pack/test/functional/apps/security/users.js
index 3d0983dfda921..8f38e8e1d7472 100644
--- a/x-pack/test/functional/apps/security/users.js
+++ b/x-pack/test/functional/apps/security/users.js
@@ -42,6 +42,7 @@ export default function ({ getService, getPageObjects }) {
log.debug('actualUsers = %j', users);
expect(users.Lee.roles).to.eql(['kibana_user']);
expect(users.Lee.fullname).to.eql('LeeFirst LeeLast');
+ expect(users.Lee.email).to.eql('lee@myEmail.com');
expect(users.Lee.reserved).to.be(false);
});
diff --git a/x-pack/test/functional/page_objects/security_page.js b/x-pack/test/functional/page_objects/security_page.js
index 96e30e152689f..f4f3781abc5a2 100644
--- a/x-pack/test/functional/page_objects/security_page.js
+++ b/x-pack/test/functional/page_objects/security_page.js
@@ -187,12 +187,14 @@ export function SecurityPageProvider({ getService, getPageObjects }) {
return mapAsync(users, async user => {
const fullnameElement = await user.findByCssSelector('[data-test-subj="userRowFullName"]');
const usernameElement = await user.findByCssSelector('[data-test-subj="userRowUserName"]');
+ const emailElement = await user.findByCssSelector('[data-header="Email Address"]');
const rolesElement = await user.findByCssSelector('[data-test-subj="userRowRoles"]');
const isReservedElementVisible = await user.findByCssSelector('td:last-child');
return {
username: await usernameElement.getVisibleText(),
fullname: await fullnameElement.getVisibleText(),
+ email: await emailElement.getVisibleText(),
roles: (await rolesElement.getVisibleText()).split(',').map(role => role.trim()),
reserved: (await isReservedElementVisible.getProperty('innerHTML')).includes('reservedUser')
};
@@ -228,7 +230,7 @@ export function SecurityPageProvider({ getService, getPageObjects }) {
await testSubjects.setValue('passwordInput', userObj.password);
await testSubjects.setValue('passwordConfirmationInput', userObj.confirmPassword);
await testSubjects.setValue('userFormFullNameInput', userObj.fullname);
- await testSubjects.setValue('userFormEmailInput', 'example@example.com');
+ await testSubjects.setValue('userFormEmailInput', userObj.email);
log.debug('Add roles: ', userObj.roles);
const rolesToAdd = userObj.roles || [];
for (let i = 0; i < rolesToAdd.length; i++) {
From e89abb3a39a3067cd223b69b36f50a68d76e2936 Mon Sep 17 00:00:00 2001
From: Jonathan Budzenski
Date: Tue, 4 Sep 2018 16:52:51 -0500
Subject: [PATCH 07/68] [config] logging.useUTC -> logging.timezone (#21561)
* [config] logging.useUTC -> logging.timezone
* docs
* [env] exit if starting as root
* fix import path
* add link and timezone example
* Revert "[env] exit if starting as root"
This reverts commit f6e9090833a5180fe360a9ff54543c37c0ca3a58.
---
docs/migration/migrate_7_0.asciidoc | 5 +++++
docs/setup/settings.asciidoc | 2 +-
src/server/config/schema.js | 2 +-
src/server/config/transform_deprecations.js | 13 ++++++++++++-
src/server/logging/configuration.js | 2 +-
src/server/logging/log_format.js | 8 ++++----
src/server/logging/log_format_json.test.js | 12 +++++-------
src/server/logging/log_format_string.test.js | 10 ++++------
8 files changed, 33 insertions(+), 21 deletions(-)
diff --git a/docs/migration/migrate_7_0.asciidoc b/docs/migration/migrate_7_0.asciidoc
index 60272acc8ad28..7a9dd5ebae21e 100644
--- a/docs/migration/migrate_7_0.asciidoc
+++ b/docs/migration/migrate_7_0.asciidoc
@@ -56,3 +56,8 @@ considered unique based on its persistent UUID, which is written to the path.dat
*Details:* The `/shorten` API has been deprecated since 6.5, when it was replaced by the `/api/shorten_url` API.
*Impact:* The '/shorten' API has been removed. Use the '/api/shorten_url' API instead.
+
+=== Deprecated kibana.yml setting logging.useUTC has been replaced with logging.timezone
+*Details:* Any timezone can now be specified by canonical id.
+
+*Impact:* The logging.useUTC flag will have to be replaced with a timezone id. If set to true the id is `UTC`.
diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc
index 1f09e5dc6e2a6..7a21705336313 100644
--- a/docs/setup/settings.asciidoc
+++ b/docs/setup/settings.asciidoc
@@ -73,7 +73,7 @@ error messages.
[[logging-verbose]]`logging.verbose:`:: *Default: false* Set the value of this setting to `true` to log all events, including system usage information and all requests. Supported on Elastic Cloud Enterprise.
-`logging.useUTC`:: *Default: true* Set the value of this setting to `false` to log events using the timezone of the server, rather than UTC.
+`logging.timezone`:: *Default: UTC* Set to the canonical timezone id (e.g. `US/Pacific`) to log events using that timezone. A list of timezones can be referenced at https://en.wikipedia.org/wiki/List_of_tz_database_time_zones.
`map.includeElasticMapsService:`:: *Default: true* Turns on or off whether layers from the Elastic Maps Service should be included in the vector and tile layer option list.
By turning this off, only the layers that are configured here will be included.
diff --git a/src/server/config/schema.js b/src/server/config/schema.js
index 6fa09a400cc09..3648e88fac2e8 100644
--- a/src/server/config/schema.js
+++ b/src/server/config/schema.js
@@ -186,7 +186,7 @@ export default () => Joi.object({
then: Joi.default(!process.stdout.isTTY),
otherwise: Joi.default(true)
}),
- useUTC: Joi.boolean().default(true),
+ timezone: Joi.string().allow(false).default('UTC')
}).default(),
ops: Joi.object({
diff --git a/src/server/config/transform_deprecations.js b/src/server/config/transform_deprecations.js
index 44571cee6c58d..d15e171f031ff 100644
--- a/src/server/config/transform_deprecations.js
+++ b/src/server/config/transform_deprecations.js
@@ -17,8 +17,9 @@
* under the License.
*/
-import _, { partial } from 'lodash';
+import _, { partial, set } from 'lodash';
import { createTransform, Deprecations } from '../../deprecation';
+import { unset } from '../../utils';
const { rename, unused } = Deprecations;
@@ -55,6 +56,15 @@ const rewriteBasePath = (settings, log) => {
}
};
+const loggingTimezone = (settings, log) => {
+ if (_.has(settings, 'logging.useUTC')) {
+ const timezone = settings.logging.useUTC ? 'UTC' : false;
+ set('logging.timezone', timezone);
+ unset(settings, 'logging.UTC');
+ log(`Config key "logging.useUTC" is deprecated. It has been replaced with "logging.timezone"`);
+ }
+};
+
const deprecations = [
//server
rename('server.ssl.cert', 'server.ssl.certificate'),
@@ -68,6 +78,7 @@ const deprecations = [
serverSslEnabled,
savedObjectsIndexCheckTimeout,
rewriteBasePath,
+ loggingTimezone,
];
export const transformDeprecations = createTransform(deprecations);
diff --git a/src/server/logging/configuration.js b/src/server/logging/configuration.js
index beec30b54bccb..59019ad873129 100644
--- a/src/server/logging/configuration.js
+++ b/src/server/logging/configuration.js
@@ -61,7 +61,7 @@ export default function loggingConfiguration(config) {
config: {
json: config.get('logging.json'),
dest: config.get('logging.dest'),
- useUTC: config.get('logging.useUTC'),
+ timezone: config.get('logging.timezone'),
// I'm adding the default here because if you add another filter
// using the commandline it will remove authorization. I want users
diff --git a/src/server/logging/log_format.js b/src/server/logging/log_format.js
index 43ffca7fd39c6..994b5af8b89f4 100644
--- a/src/server/logging/log_format.js
+++ b/src/server/logging/log_format.js
@@ -18,7 +18,7 @@
*/
import Stream from 'stream';
-import moment from 'moment';
+import moment from 'moment-timezone';
import { get, _ } from 'lodash';
import numeral from '@elastic/numeral';
import chalk from 'chalk';
@@ -66,10 +66,10 @@ export default class TransformObjStream extends Stream.Transform {
}
extractAndFormatTimestamp(data, format) {
- const { useUTC } = this.config;
+ const { timezone } = this.config;
const date = moment(data['@timestamp']);
- if (useUTC) {
- date.utc();
+ if (timezone) {
+ date.tz(timezone);
}
return date.format(format);
}
diff --git a/src/server/logging/log_format_json.test.js b/src/server/logging/log_format_json.test.js
index b9878e63f0898..1632b2b401c8a 100644
--- a/src/server/logging/log_format_json.test.js
+++ b/src/server/logging/log_format_json.test.js
@@ -196,10 +196,10 @@ describe('KbnLoggerJsonFormat', () => {
});
});
- describe('useUTC', () => {
- it('logs in UTC when useUTC is true', async () => {
+ describe('timezone', () => {
+ it('logs in UTC', async () => {
const format = new KbnLoggerJsonFormat({
- useUTC: true
+ timezone: 'UTC'
});
const result = await createPromiseFromStreams([
@@ -211,10 +211,8 @@ describe('KbnLoggerJsonFormat', () => {
expect(timestamp).toBe(moment.utc(time).format());
});
- it('logs in local timezone when useUTC is false', async () => {
- const format = new KbnLoggerJsonFormat({
- useUTC: false
- });
+ it('logs in local timezone timezone is undefined', async () => {
+ const format = new KbnLoggerJsonFormat({});
const result = await createPromiseFromStreams([
createListStream([makeEvent('log')]),
diff --git a/src/server/logging/log_format_string.test.js b/src/server/logging/log_format_string.test.js
index ca572f8c03e66..e20b5eb59b76c 100644
--- a/src/server/logging/log_format_string.test.js
+++ b/src/server/logging/log_format_string.test.js
@@ -37,9 +37,9 @@ const makeEvent = () => ({
});
describe('KbnLoggerStringFormat', () => {
- it('logs in UTC when useUTC is true', async () => {
+ it('logs in UTC', async () => {
const format = new KbnLoggerStringFormat({
- useUTC: true
+ timezone: 'UTC'
});
const result = await createPromiseFromStreams([
@@ -51,10 +51,8 @@ describe('KbnLoggerStringFormat', () => {
.toContain(moment.utc(time).format('HH:mm:ss.SSS'));
});
- it('logs in local timezone when useUTC is false', async () => {
- const format = new KbnLoggerStringFormat({
- useUTC: false
- });
+ it('logs in local timezone when timezone is undefined', async () => {
+ const format = new KbnLoggerStringFormat({});
const result = await createPromiseFromStreams([
createListStream([makeEvent()]),
From a64738d277a896d1f5419744f29c8e6042d25997 Mon Sep 17 00:00:00 2001
From: Jonathan Budzenski
Date: Tue, 4 Sep 2018 17:09:16 -0500
Subject: [PATCH 08/68] [docs] fix missing float
---
docs/migration/migrate_7_0.asciidoc | 1 +
1 file changed, 1 insertion(+)
diff --git a/docs/migration/migrate_7_0.asciidoc b/docs/migration/migrate_7_0.asciidoc
index 7a9dd5ebae21e..c7e521c812326 100644
--- a/docs/migration/migrate_7_0.asciidoc
+++ b/docs/migration/migrate_7_0.asciidoc
@@ -57,6 +57,7 @@ considered unique based on its persistent UUID, which is written to the path.dat
*Impact:* The '/shorten' API has been removed. Use the '/api/shorten_url' API instead.
+[float]
=== Deprecated kibana.yml setting logging.useUTC has been replaced with logging.timezone
*Details:* Any timezone can now be specified by canonical id.
From 5baa6d51c9a5f6628a3448b034bb49f608a860a3 Mon Sep 17 00:00:00 2001
From: Spencer
Date: Tue, 4 Sep 2018 15:48:46 -0700
Subject: [PATCH 09/68] [ftr/asyncInstance] fix error thrown for undefined
provider instances (#22689)
When a FTR service is created async the promise created by its provider is wrapped in a [`Proxy`](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Proxy) that checks before each property access if the instance has finished initializing. This breaks if the service provider returns undefined, which is the case for the `failureDebugging` service, because our truthy check will fail and we throw the error claiming the service is uninitialized, which is probably incorrect.
This PR updates the proxy to use a `Symbol` to indicate when a service instance is not available yet and throws a different error when the proxy receives any request (get, set, etc.) and the service instance is not an object, as required by the Reflect APIs.
---
.../lib/providers/async_instance.js | 39 +++++++++++--------
1 file changed, 23 insertions(+), 16 deletions(-)
diff --git a/src/functional_test_runner/lib/providers/async_instance.js b/src/functional_test_runner/lib/providers/async_instance.js
index f6118656ae0c4..aead3291efc91 100644
--- a/src/functional_test_runner/lib/providers/async_instance.js
+++ b/src/functional_test_runner/lib/providers/async_instance.js
@@ -18,95 +18,102 @@
*/
const createdInstanceProxies = new WeakSet();
+const INITIALIZING = Symbol('async instance initializing');
export const isAsyncInstance = val =>(
createdInstanceProxies.has(val)
);
export const createAsyncInstance = (type, name, promiseForValue) => {
- let finalValue;
+ let instance = INITIALIZING;
- const initPromise = promiseForValue.then(v => finalValue = v);
+ const initPromise = promiseForValue.then(v => instance = v);
const initFn = () => initPromise;
const assertReady = desc => {
- if (!finalValue) {
+ if (instance === INITIALIZING) {
throw new Error(`
${type} \`${desc}\` is loaded asynchronously but isn't available yet. Either await the
promise returned from ${name}.init(), or move this access into a test hook
like \`before()\` or \`beforeEach()\`.
`);
}
+
+ if (typeof instance !== 'object') {
+ throw new TypeError(`
+ ${type} \`${desc}\` is not supported because ${name} is ${typeof instance}
+ `);
+ }
};
const proxy = new Proxy({}, {
apply(target, context, args) {
assertReady(`${name}()`);
- return Reflect.apply(finalValue, context, args);
+ return Reflect.apply(instance, context, args);
},
construct(target, args, newTarget) {
assertReady(`new ${name}()`);
- return Reflect.construct(finalValue, args, newTarget);
+ return Reflect.construct(instance, args, newTarget);
},
defineProperty(target, prop, descriptor) {
assertReady(`${name}.${prop}`);
- return Reflect.defineProperty(finalValue, prop, descriptor);
+ return Reflect.defineProperty(instance, prop, descriptor);
},
deleteProperty(target, prop) {
assertReady(`${name}.${prop}`);
- return Reflect.deleteProperty(finalValue, prop);
+ return Reflect.deleteProperty(instance, prop);
},
get(target, prop, receiver) {
if (prop === 'init') return initFn;
assertReady(`${name}.${prop}`);
- return Reflect.get(finalValue, prop, receiver);
+ return Reflect.get(instance, prop, receiver);
},
getOwnPropertyDescriptor(target, prop) {
assertReady(`${name}.${prop}`);
- return Reflect.getOwnPropertyDescriptor(finalValue, prop);
+ return Reflect.getOwnPropertyDescriptor(instance, prop);
},
getPrototypeOf() {
assertReady(`${name}`);
- return Reflect.getPrototypeOf(finalValue);
+ return Reflect.getPrototypeOf(instance);
},
has(target, prop) {
if (prop === 'init') return true;
assertReady(`${name}.${prop}`);
- return Reflect.has(finalValue, prop);
+ return Reflect.has(instance, prop);
},
isExtensible() {
assertReady(`${name}`);
- return Reflect.isExtensible(finalValue);
+ return Reflect.isExtensible(instance);
},
ownKeys() {
assertReady(`${name}`);
- return Reflect.ownKeys(finalValue);
+ return Reflect.ownKeys(instance);
},
preventExtensions() {
assertReady(`${name}`);
- return Reflect.preventExtensions(finalValue);
+ return Reflect.preventExtensions(instance);
},
set(target, prop, value, receiver) {
assertReady(`${name}.${prop}`);
- return Reflect.set(finalValue, prop, value, receiver);
+ return Reflect.set(instance, prop, value, receiver);
},
setPrototypeOf(target, prototype) {
assertReady(`${name}`);
- return Reflect.setPrototypeOf(finalValue, prototype);
+ return Reflect.setPrototypeOf(instance, prototype);
}
});
From cd83db703783aa80b474fbc8caf56c7d9ab46fa0 Mon Sep 17 00:00:00 2001
From: Chris Davies
Date: Tue, 4 Sep 2018 21:46:59 -0400
Subject: [PATCH 10/68] Fix #22510, dashboard-only mode doesn't display saved
searches (#22685)
---
x-pack/plugins/dashboard_mode/public/dashboard_viewer.js | 1 +
1 file changed, 1 insertion(+)
diff --git a/x-pack/plugins/dashboard_mode/public/dashboard_viewer.js b/x-pack/plugins/dashboard_mode/public/dashboard_viewer.js
index 272174029ded9..ef2929f2ab2ff 100644
--- a/x-pack/plugins/dashboard_mode/public/dashboard_viewer.js
+++ b/x-pack/plugins/dashboard_mode/public/dashboard_viewer.js
@@ -33,6 +33,7 @@ import 'ui/vislib';
import 'ui/agg_response';
import 'ui/agg_types';
import 'ui/timepicker';
+import 'ui/pager';
import 'leaflet';
import { showAppRedirectNotification } from 'ui/notify';
From 5f4a1c58e88826789a2b558e2b2f8ea56af82a7b Mon Sep 17 00:00:00 2001
From: Tim Roes
Date: Wed, 5 Sep 2018 08:55:39 +0200
Subject: [PATCH 11/68] Move timezone settings into autoload file (#22623)
* Move timezone settings into autoload file
* Remove applying setting from timelion
* Remove manual set from ML
* Remove manual set from monitoring
* Remove now obsolete code from embedding test plugin
---
src/core_plugins/kibana/public/kibana.js | 3 --
.../kibana/public/kibana_root_controller.js | 34 -------------
src/core_plugins/timelion/public/app.js | 4 --
src/ui/public/autoload/all.js | 1 +
src/ui/public/autoload/settings.js | 50 +++++++++++++++++++
.../plugins/visualize_embedding/public/app.js | 28 -----------
.../dashboard_mode/public/dashboard_viewer.js | 4 +-
x-pack/plugins/ml/public/app.js | 8 ---
.../plugins/monitoring/public/monitoring.js | 4 --
9 files changed, 52 insertions(+), 84 deletions(-)
delete mode 100644 src/core_plugins/kibana/public/kibana_root_controller.js
create mode 100644 src/ui/public/autoload/settings.js
diff --git a/src/core_plugins/kibana/public/kibana.js b/src/core_plugins/kibana/public/kibana.js
index 522b6b7087025..984706556800d 100644
--- a/src/core_plugins/kibana/public/kibana.js
+++ b/src/core_plugins/kibana/public/kibana.js
@@ -59,7 +59,6 @@ import 'ui/agg_types';
import 'ui/timepicker';
import { showAppRedirectNotification } from 'ui/notify';
import 'leaflet';
-import { KibanaRootController } from './kibana_root_controller';
routes.enable();
@@ -68,6 +67,4 @@ routes
redirectTo: `/${chrome.getInjected('kbnDefaultAppId', 'discover')}`
});
-chrome.setRootController('kibana', KibanaRootController);
-
uiModules.get('kibana').run(showAppRedirectNotification);
diff --git a/src/core_plugins/kibana/public/kibana_root_controller.js b/src/core_plugins/kibana/public/kibana_root_controller.js
deleted file mode 100644
index 830cd0bd16c7b..0000000000000
--- a/src/core_plugins/kibana/public/kibana_root_controller.js
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import moment from 'moment-timezone';
-
-export function KibanaRootController($scope, courier, config) {
- config.watch('dateFormat:tz', setDefaultTimezone, $scope);
- config.watch('dateFormat:dow', setStartDayOfWeek, $scope);
-
- function setDefaultTimezone(tz) {
- moment.tz.setDefault(tz);
- }
-
- function setStartDayOfWeek(day) {
- const dow = moment.weekdays().indexOf(day);
- moment.updateLocale(moment.locale(), { week: { dow } });
- }
-}
diff --git a/src/core_plugins/timelion/public/app.js b/src/core_plugins/timelion/public/app.js
index ffadf7c9cdb1b..316a9704b5c3d 100644
--- a/src/core_plugins/timelion/public/app.js
+++ b/src/core_plugins/timelion/public/app.js
@@ -18,7 +18,6 @@
*/
import _ from 'lodash';
-import moment from 'moment-timezone';
import { DocTitleProvider } from 'ui/doc_title';
import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_registry';
@@ -101,9 +100,6 @@ app.controller('timelion', function (
$scope.page = config.get('timelion:showTutorial', true) ? 1 : 0;
$scope.setPage = (page) => $scope.page = page;
- // TODO: For some reason the Kibana core doesn't correctly do this for all apps.
- moment.tz.setDefault(config.get('dateFormat:tz'));
-
timefilter.enableAutoRefreshSelector();
timefilter.enableTimeRangeSelector();
diff --git a/src/ui/public/autoload/all.js b/src/ui/public/autoload/all.js
index fba21aacd1984..b1aa11cb72494 100644
--- a/src/ui/public/autoload/all.js
+++ b/src/ui/public/autoload/all.js
@@ -21,4 +21,5 @@ import './accessibility';
import './modules';
import './directives';
import './filters';
+import './settings';
import './styles';
diff --git a/src/ui/public/autoload/settings.js b/src/ui/public/autoload/settings.js
new file mode 100644
index 0000000000000..48505037dffe4
--- /dev/null
+++ b/src/ui/public/autoload/settings.js
@@ -0,0 +1,50 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * Autoload this file if we want some of the top level settings applied to a plugin.
+ * Currently this file makes sure the following settings are applied globally:
+ * - dateFormat:tz (meaning the Kibana time zone will be used in your plugin)
+ * - dateFormat:dow (meaning the Kibana configured start of the week will be used in your plugin)
+ */
+
+import moment from 'moment-timezone';
+import chrome from '../chrome';
+
+function setDefaultTimezone(tz) {
+ moment.tz.setDefault(tz);
+}
+
+function setStartDayOfWeek(day) {
+ const dow = moment.weekdays().indexOf(day);
+ moment.updateLocale(moment.locale(), { week: { dow } });
+}
+
+const uiSettings = chrome.getUiSettingsClient();
+
+setDefaultTimezone(uiSettings.get('dateFormat:tz'));
+setStartDayOfWeek(uiSettings.get('dateFormat:dow'));
+
+uiSettings.subscribe(({ key, newValue }) => {
+ if (key === 'dateFormat:tz') {
+ setDefaultTimezone(newValue);
+ } else if (key === 'dateFormat:dow') {
+ setStartDayOfWeek(newValue);
+ }
+});
diff --git a/test/plugin_functional/plugins/visualize_embedding/public/app.js b/test/plugin_functional/plugins/visualize_embedding/public/app.js
index 3f622c946a115..4463feac27513 100644
--- a/test/plugin_functional/plugins/visualize_embedding/public/app.js
+++ b/test/plugin_functional/plugins/visualize_embedding/public/app.js
@@ -37,34 +37,6 @@ import 'uiExports/savedObjectTypes';
import 'uiExports/fieldFormats';
import 'uiExports/search';
-// ----------- TODO Remove once https://github.com/elastic/kibana/pull/22623 is merged
-
-import moment from 'moment-timezone';
-
-function setDefaultTimezone(tz) {
- moment.tz.setDefault(tz);
-}
-
-function setStartDayOfWeek(day) {
- const dow = moment.weekdays().indexOf(day);
- moment.updateLocale(moment.locale(), { week: { dow } });
-}
-
-const uiSettings = chrome.getUiSettingsClient();
-
-setDefaultTimezone(uiSettings.get('dateFormat:tz'));
-setStartDayOfWeek(uiSettings.get('dateFormat:dow'));
-
-uiSettings.subscribe(({ key, newValue }) => {
- if (key === 'dateFormat:tz') {
- setDefaultTimezone(newValue);
- } else if (key === 'dateFormat:dow') {
- setStartDayOfWeek(newValue);
- }
-});
-
-// ----------------- END OF REMOVAL ----------
-
import { Main } from './components/main';
const app = uiModules.get('apps/firewallDemoPlugin', ['kibana']);
diff --git a/x-pack/plugins/dashboard_mode/public/dashboard_viewer.js b/x-pack/plugins/dashboard_mode/public/dashboard_viewer.js
index ef2929f2ab2ff..2b6997742280c 100644
--- a/x-pack/plugins/dashboard_mode/public/dashboard_viewer.js
+++ b/x-pack/plugins/dashboard_mode/public/dashboard_viewer.js
@@ -38,7 +38,6 @@ import 'leaflet';
import { showAppRedirectNotification } from 'ui/notify';
import { DashboardConstants, createDashboardEditUrl } from 'plugins/kibana/dashboard/dashboard_constants';
-import { KibanaRootController } from 'plugins/kibana/kibana_root_controller';
uiModules.get('kibana')
.config(dashboardConfigProvider => dashboardConfigProvider.turnHideWriteControlsOn());
@@ -47,9 +46,8 @@ routes.enable();
routes.otherwise({ redirectTo: defaultUrl() });
chrome
- .setRootController('kibana', function ($controller, $scope, courier, config) {
+ .setRootController('kibana', function () {
chrome.showOnlyById('kibana:dashboard');
- $controller(KibanaRootController, { $scope, courier, config });
});
uiModules.get('kibana').run(showAppRedirectNotification);
diff --git a/x-pack/plugins/ml/public/app.js b/x-pack/plugins/ml/public/app.js
index ba1d17bdd9b8d..ad76c56cfdf19 100644
--- a/x-pack/plugins/ml/public/app.js
+++ b/x-pack/plugins/ml/public/app.js
@@ -34,14 +34,6 @@ import 'plugins/ml/components/loading_indicator';
import 'plugins/ml/settings';
import uiRoutes from 'ui/routes';
-import moment from 'moment-timezone';
-import { uiModules } from 'ui/modules';
-
-const uiModule = uiModules.get('kibana');
-uiModule.run((config) => {
- // Set the timezone for moment formatting to that configured in Kibana.
- moment.tz.setDefault(config.get('dateFormat:tz'));
-});
if (typeof uiRoutes.enable === 'function') {
uiRoutes.enable();
diff --git a/x-pack/plugins/monitoring/public/monitoring.js b/x-pack/plugins/monitoring/public/monitoring.js
index f564848d02c90..0e4528ba679c3 100644
--- a/x-pack/plugins/monitoring/public/monitoring.js
+++ b/x-pack/plugins/monitoring/public/monitoring.js
@@ -4,7 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import moment from 'moment-timezone';
import uiRoutes from 'ui/routes';
import chrome from 'ui/chrome';
import 'ui/autoload/all';
@@ -20,9 +19,6 @@ import 'plugins/monitoring/views/all';
const uiSettings = chrome.getUiSettingsClient();
-// Allow UTC times to be entered for Absolute Time range in timepicker
-moment.tz.setDefault(uiSettings.get('dateFormat:tz'));
-
// default timepicker default to the last hour
uiSettings.overrideLocalDefault('timepicker:timeDefaults', JSON.stringify({
from: 'now-1h',
From 9c01863c0a454ec0eceafa157dc479d4ee1f179f Mon Sep 17 00:00:00 2001
From: Tim Roes
Date: Wed, 5 Sep 2018 09:35:18 +0200
Subject: [PATCH 12/68] Fix react vis type documentation (#22573)
---
.../visualize/development-create-visualization.asciidoc | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/development/visualize/development-create-visualization.asciidoc b/docs/development/visualize/development-create-visualization.asciidoc
index 437b4ff9e5f3d..f843904fcdc41 100644
--- a/docs/development/visualize/development-create-visualization.asciidoc
+++ b/docs/development/visualize/development-create-visualization.asciidoc
@@ -179,7 +179,7 @@ VisTypesRegistryProvider.register(MyNewVisType);
[[development-react-visualization-type]]
==== React Visualization Type
React visualization type assumes you are using React as your rendering technology.
-Just pass in a React component to `visConfig.template`.
+Just pass in a React component to `visConfig.component`.
The visualization will receive `vis`, `appState`, `updateStatus` and `visData` as props.
It also has a `renderComplete` property, which needs to be called once the rendering has completed.
@@ -197,7 +197,7 @@ const MyNewVisType = (Private) => {
icon: 'my_icon',
description: 'Cool new chart',
visConfig: {
- template: ReactComponent
+ component: ReactComponent
}
});
}
From 4cf727aa980c7b5596c330c8e7082c097f42caea Mon Sep 17 00:00:00 2001
From: Leanid Shutau
Date: Wed, 5 Sep 2018 14:02:15 +0300
Subject: [PATCH 13/68] Add logging to messages validation (#22296)
* Add logging and parallelization to messages validation
* Refactor dev/i18n
* Resolve comments
* Remove parallelism and fix tests
* Resolve comments
---
.../extract_code_messages.test.js.snap | 31 ---
.../extract_default_translations.test.js.snap | 179 +++++-------------
.../extract_handlebars_messages.test.js.snap | 21 --
.../extract_html_messages.test.js.snap | 31 ---
.../extract_i18n_call_messages.test.js.snap | 29 ---
.../extract_pug_messages.test.js.snap | 15 --
.../extract_react_messages.test.js.snap | 27 ---
src/dev/i18n/extract_default_translations.js | 101 ++--------
.../i18n/extract_default_translations.test.js | 35 +---
.../__snapshots__/code.test.js.snap | 31 +++
.../__snapshots__/handlebars.test.js.snap | 21 ++
.../__snapshots__/html.test.js.snap | 31 +++
.../__snapshots__/i18n_call.test.js.snap | 29 +++
.../extractors/__snapshots__/pug.test.js.snap | 15 ++
.../__snapshots__/react.test.js.snap | 27 +++
.../code.js} | 6 +-
.../code.test.js} | 18 +-
.../handlebars.js} | 30 +--
.../handlebars.test.js} | 4 +-
.../html.js} | 36 +---
.../html.test.js} | 4 +-
.../i18n_call.js} | 29 +--
.../i18n_call.test.js} | 6 +-
src/dev/i18n/extractors/index.js | 25 +++
.../pug.js} | 4 +-
.../pug.test.js} | 4 +-
.../react.js} | 38 ++--
.../react.test.js} | 6 +-
src/dev/i18n/index.js | 22 +++
.../__snapshots__/json.test.js.snap | 67 +++++++
.../__snapshots__/json5.test.js.snap | 65 +++++++
src/dev/i18n/serializers/index.js | 21 ++
src/dev/i18n/serializers/json.js | 34 ++++
src/dev/i18n/serializers/json.test.js | 37 ++++
src/dev/i18n/serializers/json5.js | 48 +++++
src/dev/i18n/serializers/json5.test.js | 42 ++++
src/dev/run/index.js | 2 +-
src/dev/run_i18n_check.js | 47 ++++-
38 files changed, 701 insertions(+), 517 deletions(-)
delete mode 100644 src/dev/i18n/__snapshots__/extract_code_messages.test.js.snap
delete mode 100644 src/dev/i18n/__snapshots__/extract_handlebars_messages.test.js.snap
delete mode 100644 src/dev/i18n/__snapshots__/extract_html_messages.test.js.snap
delete mode 100644 src/dev/i18n/__snapshots__/extract_i18n_call_messages.test.js.snap
delete mode 100644 src/dev/i18n/__snapshots__/extract_pug_messages.test.js.snap
delete mode 100644 src/dev/i18n/__snapshots__/extract_react_messages.test.js.snap
create mode 100644 src/dev/i18n/extractors/__snapshots__/code.test.js.snap
create mode 100644 src/dev/i18n/extractors/__snapshots__/handlebars.test.js.snap
create mode 100644 src/dev/i18n/extractors/__snapshots__/html.test.js.snap
create mode 100644 src/dev/i18n/extractors/__snapshots__/i18n_call.test.js.snap
create mode 100644 src/dev/i18n/extractors/__snapshots__/pug.test.js.snap
create mode 100644 src/dev/i18n/extractors/__snapshots__/react.test.js.snap
rename src/dev/i18n/{extract_code_messages.js => extractors/code.js} (94%)
rename src/dev/i18n/{extract_code_messages.test.js => extractors/code.test.js} (89%)
rename src/dev/i18n/{extract_handlebars_messages.js => extractors/handlebars.js} (68%)
rename src/dev/i18n/{extract_handlebars_messages.test.js => extractors/handlebars.test.js} (95%)
rename src/dev/i18n/{extract_html_messages.js => extractors/html.js} (78%)
rename src/dev/i18n/{extract_html_messages.test.js => extractors/html.test.js} (94%)
rename src/dev/i18n/{extract_i18n_call_messages.js => extractors/i18n_call.js} (64%)
rename src/dev/i18n/{extract_i18n_call_messages.test.js => extractors/i18n_call.test.js} (95%)
create mode 100644 src/dev/i18n/extractors/index.js
rename src/dev/i18n/{extract_pug_messages.js => extractors/pug.js} (91%)
rename src/dev/i18n/{extract_pug_messages.test.js => extractors/pug.test.js} (94%)
rename src/dev/i18n/{extract_react_messages.js => extractors/react.js} (73%)
rename src/dev/i18n/{extract_react_messages.test.js => extractors/react.test.js} (97%)
create mode 100644 src/dev/i18n/index.js
create mode 100644 src/dev/i18n/serializers/__snapshots__/json.test.js.snap
create mode 100644 src/dev/i18n/serializers/__snapshots__/json5.test.js.snap
create mode 100644 src/dev/i18n/serializers/index.js
create mode 100644 src/dev/i18n/serializers/json.js
create mode 100644 src/dev/i18n/serializers/json.test.js
create mode 100644 src/dev/i18n/serializers/json5.js
create mode 100644 src/dev/i18n/serializers/json5.test.js
diff --git a/src/dev/i18n/__snapshots__/extract_code_messages.test.js.snap b/src/dev/i18n/__snapshots__/extract_code_messages.test.js.snap
deleted file mode 100644
index 507efdfd61595..0000000000000
--- a/src/dev/i18n/__snapshots__/extract_code_messages.test.js.snap
+++ /dev/null
@@ -1,31 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`extractCodeMessages extracts React, server-side and angular service default messages 1`] = `
-Array [
- Array [
- "kbn.mgmt.id-1",
- Object {
- "context": undefined,
- "message": "Message text 1",
- },
- ],
- Array [
- "kbn.mgmt.id-2",
- Object {
- "context": "Message context",
- "message": "Message text 2",
- },
- ],
- Array [
- "kbn.mgmt.id-3",
- Object {
- "context": undefined,
- "message": "Message text 3",
- },
- ],
-]
-`;
-
-exports[`extractCodeMessages throws on empty id 1`] = `"[37m[41m I18N ERROR [49m[39m Empty \\"id\\" value in i18n() or i18n.translate() is not allowed."`;
-
-exports[`extractCodeMessages throws on missing defaultMessage 1`] = `"[37m[41m I18N ERROR [49m[39m Empty defaultMessage in intl.formatMessage() is not allowed (\\"message-id\\")."`;
diff --git a/src/dev/i18n/__snapshots__/extract_default_translations.test.js.snap b/src/dev/i18n/__snapshots__/extract_default_translations.test.js.snap
index 750280b7f8d95..1a9997dc07dd9 100644
--- a/src/dev/i18n/__snapshots__/extract_default_translations.test.js.snap
+++ b/src/dev/i18n/__snapshots__/extract_default_translations.test.js.snap
@@ -1,142 +1,63 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`dev/i18n/extract_default_translations extracts messages to en.json 1`] = `
-"{
- \\"formats\\": {
- \\"number\\": {
- \\"currency\\": {
- \\"style\\": \\"currency\\"
- },
- \\"percent\\": {
- \\"style\\": \\"percent\\"
- }
+exports[`dev/i18n/extract_default_translations extracts messages from path to map 1`] = `
+Array [
+ Array [
+ "plugin_1.id_1",
+ Object {
+ "context": undefined,
+ "message": "Message 1",
},
- \\"date\\": {
- \\"short\\": {
- \\"month\\": \\"numeric\\",
- \\"day\\": \\"numeric\\",
- \\"year\\": \\"2-digit\\"
- },
- \\"medium\\": {
- \\"month\\": \\"short\\",
- \\"day\\": \\"numeric\\",
- \\"year\\": \\"numeric\\"
- },
- \\"long\\": {
- \\"month\\": \\"long\\",
- \\"day\\": \\"numeric\\",
- \\"year\\": \\"numeric\\"
- },
- \\"full\\": {
- \\"weekday\\": \\"long\\",
- \\"month\\": \\"long\\",
- \\"day\\": \\"numeric\\",
- \\"year\\": \\"numeric\\"
- }
+ ],
+ Array [
+ "plugin_1.id_2",
+ Object {
+ "context": "Message context",
+ "message": "Message 2",
},
- \\"time\\": {
- \\"short\\": {
- \\"hour\\": \\"numeric\\",
- \\"minute\\": \\"numeric\\"
- },
- \\"medium\\": {
- \\"hour\\": \\"numeric\\",
- \\"minute\\": \\"numeric\\",
- \\"second\\": \\"numeric\\"
- },
- \\"long\\": {
- \\"hour\\": \\"numeric\\",
- \\"minute\\": \\"numeric\\",
- \\"second\\": \\"numeric\\",
- \\"timeZoneName\\": \\"short\\"
- },
- \\"full\\": {
- \\"hour\\": \\"numeric\\",
- \\"minute\\": \\"numeric\\",
- \\"second\\": \\"numeric\\",
- \\"timeZoneName\\": \\"short\\"
- }
- }
- },
- \\"plugin_1.id_1\\": \\"Message 1\\",
- \\"plugin_1.id_2\\": {
- \\"text\\": \\"Message 2\\",
- \\"comment\\": \\"Message context\\"
- },
- \\"plugin_1.id_3\\": \\"Message 3\\",
- \\"plugin_1.id_4\\": \\"Message 4\\",
- \\"plugin_1.id_5\\": \\"Message 5\\",
- \\"plugin_1.id_6\\": \\"Message 6\\",
- \\"plugin_1.id_7\\": \\"Message 7\\"
-}"
-`;
-
-exports[`dev/i18n/extract_default_translations injects default formats into en.json 1`] = `
-"{
- \\"formats\\": {
- \\"number\\": {
- \\"currency\\": {
- \\"style\\": \\"currency\\"
- },
- \\"percent\\": {
- \\"style\\": \\"percent\\"
- }
+ ],
+ Array [
+ "plugin_1.id_3",
+ Object {
+ "context": undefined,
+ "message": "Message 3",
+ },
+ ],
+ Array [
+ "plugin_1.id_4",
+ Object {
+ "context": undefined,
+ "message": "Message 4",
+ },
+ ],
+ Array [
+ "plugin_1.id_5",
+ Object {
+ "context": undefined,
+ "message": "Message 5",
+ },
+ ],
+ Array [
+ "plugin_1.id_6",
+ Object {
+ "context": "",
+ "message": "Message 6",
},
- \\"date\\": {
- \\"short\\": {
- \\"month\\": \\"numeric\\",
- \\"day\\": \\"numeric\\",
- \\"year\\": \\"2-digit\\"
- },
- \\"medium\\": {
- \\"month\\": \\"short\\",
- \\"day\\": \\"numeric\\",
- \\"year\\": \\"numeric\\"
- },
- \\"long\\": {
- \\"month\\": \\"long\\",
- \\"day\\": \\"numeric\\",
- \\"year\\": \\"numeric\\"
- },
- \\"full\\": {
- \\"weekday\\": \\"long\\",
- \\"month\\": \\"long\\",
- \\"day\\": \\"numeric\\",
- \\"year\\": \\"numeric\\"
- }
+ ],
+ Array [
+ "plugin_1.id_7",
+ Object {
+ "context": undefined,
+ "message": "Message 7",
},
- \\"time\\": {
- \\"short\\": {
- \\"hour\\": \\"numeric\\",
- \\"minute\\": \\"numeric\\"
- },
- \\"medium\\": {
- \\"hour\\": \\"numeric\\",
- \\"minute\\": \\"numeric\\",
- \\"second\\": \\"numeric\\"
- },
- \\"long\\": {
- \\"hour\\": \\"numeric\\",
- \\"minute\\": \\"numeric\\",
- \\"second\\": \\"numeric\\",
- \\"timeZoneName\\": \\"short\\"
- },
- \\"full\\": {
- \\"hour\\": \\"numeric\\",
- \\"minute\\": \\"numeric\\",
- \\"second\\": \\"numeric\\",
- \\"timeZoneName\\": \\"short\\"
- }
- }
- },
- \\"plugin_2.message-id\\": \\"Message text\\"
-}"
+ ],
+]
`;
exports[`dev/i18n/extract_default_translations throws on id collision 1`] = `
"[37m[41m I18N ERROR [49m[39m Error in src/dev/i18n/__fixtures__/extract_default_translations/test_plugin_3/test_file.jsx
-Error: [37m[41m I18N ERROR [49m[39m There is more than one default message for the same id \\"plugin_3.duplicate_id\\":
+Error: There is more than one default message for the same id \\"plugin_3.duplicate_id\\":
\\"Message 1\\" and \\"Message 2\\""
`;
-exports[`dev/i18n/extract_default_translations throws on wrong message namespace 1`] = `"[37m[41m I18N ERROR [49m[39m Expected \\"wrong_plugin_namespace.message-id\\" id to have \\"plugin_2\\" namespace. See i18nrc.json for the list of supported namespaces."`;
+exports[`dev/i18n/extract_default_translations throws on wrong message namespace 1`] = `"Expected \\"wrong_plugin_namespace.message-id\\" id to have \\"plugin_2\\" namespace. See .i18nrc.json for the list of supported namespaces."`;
diff --git a/src/dev/i18n/__snapshots__/extract_handlebars_messages.test.js.snap b/src/dev/i18n/__snapshots__/extract_handlebars_messages.test.js.snap
deleted file mode 100644
index 7c9f72a6921ba..0000000000000
--- a/src/dev/i18n/__snapshots__/extract_handlebars_messages.test.js.snap
+++ /dev/null
@@ -1,21 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`dev/i18n/extract_handlebars_messages extracts handlebars default messages 1`] = `
-Array [
- Array [
- "ui.id-1",
- Object {
- "context": "Message context",
- "message": "Message text",
- },
- ],
-]
-`;
-
-exports[`dev/i18n/extract_handlebars_messages throws on empty id 1`] = `"[37m[41m I18N ERROR [49m[39m Empty id argument in Handlebars i18n is not allowed."`;
-
-exports[`dev/i18n/extract_handlebars_messages throws on missing defaultMessage property 1`] = `"[37m[41m I18N ERROR [49m[39m Empty defaultMessage in Handlebars i18n is not allowed (\\"message-id\\")."`;
-
-exports[`dev/i18n/extract_handlebars_messages throws on wrong number of arguments 1`] = `"[37m[41m I18N ERROR [49m[39m Wrong number of arguments for handlebars i18n call."`;
-
-exports[`dev/i18n/extract_handlebars_messages throws on wrong properties argument type 1`] = `"[37m[41m I18N ERROR [49m[39m Properties string in Handlebars i18n should be a string literal (\\"ui.id-1\\")."`;
diff --git a/src/dev/i18n/__snapshots__/extract_html_messages.test.js.snap b/src/dev/i18n/__snapshots__/extract_html_messages.test.js.snap
deleted file mode 100644
index aa6048b92b84c..0000000000000
--- a/src/dev/i18n/__snapshots__/extract_html_messages.test.js.snap
+++ /dev/null
@@ -1,31 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`dev/i18n/extract_html_messages extracts default messages from HTML 1`] = `
-Array [
- Array [
- "kbn.dashboard.id-1",
- Object {
- "context": "Message context 1",
- "message": "Message text 1",
- },
- ],
- Array [
- "kbn.dashboard.id-2",
- Object {
- "context": undefined,
- "message": "Message text 2",
- },
- ],
- Array [
- "kbn.dashboard.id-3",
- Object {
- "context": "Message context 3",
- "message": "Message text 3",
- },
- ],
-]
-`;
-
-exports[`dev/i18n/extract_html_messages throws on empty i18n-id 1`] = `"[37m[41m I18N ERROR [49m[39m Empty \\"i18n-id\\" value in angular directive is not allowed."`;
-
-exports[`dev/i18n/extract_html_messages throws on missing i18n-default-message attribute 1`] = `"[37m[41m I18N ERROR [49m[39m Empty defaultMessage in angular directive is not allowed (\\"message-id\\")."`;
diff --git a/src/dev/i18n/__snapshots__/extract_i18n_call_messages.test.js.snap b/src/dev/i18n/__snapshots__/extract_i18n_call_messages.test.js.snap
deleted file mode 100644
index 13a79e578861c..0000000000000
--- a/src/dev/i18n/__snapshots__/extract_i18n_call_messages.test.js.snap
+++ /dev/null
@@ -1,29 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`extractI18nCallMessages extracts "i18n" and "i18n.translate" functions call message 1`] = `
-Array [
- "message-id-1",
- Object {
- "context": "Message context 1",
- "message": "Default message 1",
- },
-]
-`;
-
-exports[`extractI18nCallMessages extracts "i18n" and "i18n.translate" functions call message 2`] = `
-Array [
- "message-id-2",
- Object {
- "context": "Message context 2",
- "message": "Default message 2",
- },
-]
-`;
-
-exports[`extractI18nCallMessages throws if defaultMessage is not a string literal 1`] = `"[37m[41m I18N ERROR [49m[39m defaultMessage value in i18n() or i18n.translate() should be a string literal (\\"message-id\\")."`;
-
-exports[`extractI18nCallMessages throws if message id value is not a string literal 1`] = `"[37m[41m I18N ERROR [49m[39m Message id in i18n() or i18n.translate() should be a string literal."`;
-
-exports[`extractI18nCallMessages throws if properties object is not provided 1`] = `"[37m[41m I18N ERROR [49m[39m Empty defaultMessage in i18n() or i18n.translate() is not allowed (\\"message-id\\")."`;
-
-exports[`extractI18nCallMessages throws on empty defaultMessage 1`] = `"[37m[41m I18N ERROR [49m[39m Empty defaultMessage in i18n() or i18n.translate() is not allowed (\\"message-id\\")."`;
diff --git a/src/dev/i18n/__snapshots__/extract_pug_messages.test.js.snap b/src/dev/i18n/__snapshots__/extract_pug_messages.test.js.snap
deleted file mode 100644
index 16767f882063a..0000000000000
--- a/src/dev/i18n/__snapshots__/extract_pug_messages.test.js.snap
+++ /dev/null
@@ -1,15 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`extractPugMessages extracts messages from pug template 1`] = `
-Array [
- "message-id",
- Object {
- "context": "Message context",
- "message": "Default message",
- },
-]
-`;
-
-exports[`extractPugMessages throws on empty id 1`] = `"[37m[41m I18N ERROR [49m[39m Empty \\"id\\" value in i18n() or i18n.translate() is not allowed."`;
-
-exports[`extractPugMessages throws on missing default message 1`] = `"[37m[41m I18N ERROR [49m[39m Empty defaultMessage in i18n() or i18n.translate() is not allowed (\\"message-id\\")."`;
diff --git a/src/dev/i18n/__snapshots__/extract_react_messages.test.js.snap b/src/dev/i18n/__snapshots__/extract_react_messages.test.js.snap
deleted file mode 100644
index 2bf17cab30c28..0000000000000
--- a/src/dev/i18n/__snapshots__/extract_react_messages.test.js.snap
+++ /dev/null
@@ -1,27 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`dev/i18n/extract_react_messages extractFormattedMessages extracts messages from "" element 1`] = `
-Array [
- "message-id-2",
- Object {
- "context": "Message context 2",
- "message": "Default message 2",
- },
-]
-`;
-
-exports[`dev/i18n/extract_react_messages extractIntlMessages extracts messages from "intl.formatMessage" function call 1`] = `
-Array [
- "message-id-1",
- Object {
- "context": "Message context 1",
- "message": "Default message 1",
- },
-]
-`;
-
-exports[`dev/i18n/extract_react_messages extractIntlMessages throws if context value is not a string literal 1`] = `"[37m[41m I18N ERROR [49m[39m context value should be a string literal (\\"message-id\\")."`;
-
-exports[`dev/i18n/extract_react_messages extractIntlMessages throws if defaultMessage value is not a string literal 1`] = `"[37m[41m I18N ERROR [49m[39m defaultMessage value should be a string literal (\\"message-id\\")."`;
-
-exports[`dev/i18n/extract_react_messages extractIntlMessages throws if message id is not a string literal 1`] = `"[37m[41m I18N ERROR [49m[39m Message id should be a string literal."`;
diff --git a/src/dev/i18n/extract_default_translations.js b/src/dev/i18n/extract_default_translations.js
index 5bbfaa221b433..06d397961e233 100644
--- a/src/dev/i18n/extract_default_translations.js
+++ b/src/dev/i18n/extract_default_translations.js
@@ -18,28 +18,27 @@
*/
import path from 'path';
-import { i18n } from '@kbn/i18n';
-import JSON5 from 'json5';
import normalize from 'normalize-path';
import chalk from 'chalk';
-import { extractHtmlMessages } from './extract_html_messages';
-import { extractCodeMessages } from './extract_code_messages';
-import { extractPugMessages } from './extract_pug_messages';
-import { extractHandlebarsMessages } from './extract_handlebars_messages';
-import { globAsync, readFileAsync, writeFileAsync } from './utils';
+import {
+ extractHtmlMessages,
+ extractCodeMessages,
+ extractPugMessages,
+ extractHandlebarsMessages,
+} from './extractors';
+import { globAsync, readFileAsync } from './utils';
import { paths, exclude } from '../../../.i18nrc.json';
-import { createFailError } from '../run';
-
-const ESCAPE_SINGLE_QUOTE_REGEX = /\\([\s\S])|(')/g;
+import { createFailError, isFailError } from '../run';
function addMessageToMap(targetMap, key, value) {
const existingValue = targetMap.get(key);
+
if (targetMap.has(key) && existingValue.message !== value.message) {
- throw createFailError(`${chalk.white.bgRed(' I18N ERROR ')} \
-There is more than one default message for the same id "${key}":
+ throw createFailError(`There is more than one default message for the same id "${key}":
"${existingValue.message}" and "${value.message}"`);
}
+
targetMap.set(key, value);
}
@@ -47,7 +46,7 @@ function normalizePath(inputPath) {
return normalize(path.relative('.', inputPath));
}
-function filterPaths(inputPaths) {
+export function filterPaths(inputPaths) {
const availablePaths = Object.values(paths);
const pathsForExtraction = new Set();
@@ -79,9 +78,8 @@ export function validateMessageNamespace(id, filePath) {
);
if (!id.startsWith(`${expectedNamespace}.`)) {
- throw createFailError(`${chalk.white.bgRed(' I18N ERROR ')} \
-Expected "${id}" id to have "${expectedNamespace}" namespace. \
-See i18nrc.json for the list of supported namespaces.`);
+ throw createFailError(`Expected "${id}" id to have "${expectedNamespace}" namespace. \
+See .i18nrc.json for the list of supported namespaces.`);
}
}
@@ -133,72 +131,15 @@ export async function extractMessagesFromPathToMap(inputPath, targetMap) {
addMessageToMap(targetMap, id, value);
}
} catch (error) {
- throw createFailError(
- `${chalk.white.bgRed(' I18N ERROR ')} Error in ${normalizePath(name)}\n${error}`
- );
+ if (isFailError(error)) {
+ throw createFailError(
+ `${chalk.white.bgRed(' I18N ERROR ')} Error in ${normalizePath(name)}\n${error}`
+ );
+ }
+
+ throw error;
}
}
})
);
}
-
-function serializeToJson5(defaultMessages) {
- // .slice(0, -1): remove closing curly brace from json to append messages
- let jsonBuffer = Buffer.from(
- JSON5.stringify({ formats: i18n.formats }, { quote: `'`, space: 2 }).slice(0, -1)
- );
-
- for (const [mapKey, mapValue] of defaultMessages) {
- const formattedMessage = mapValue.message.replace(ESCAPE_SINGLE_QUOTE_REGEX, '\\$1$2');
- const formattedContext = mapValue.context
- ? mapValue.context.replace(ESCAPE_SINGLE_QUOTE_REGEX, '\\$1$2')
- : '';
-
- jsonBuffer = Buffer.concat([
- jsonBuffer,
- Buffer.from(` '${mapKey}': '${formattedMessage}',`),
- Buffer.from(formattedContext ? ` // ${formattedContext}\n` : '\n'),
- ]);
- }
-
- // append previously removed closing curly brace
- jsonBuffer = Buffer.concat([jsonBuffer, Buffer.from('}\n')]);
-
- return jsonBuffer;
-}
-
-function serializeToJson(defaultMessages) {
- const resultJsonObject = { formats: i18n.formats };
-
- for (const [mapKey, mapValue] of defaultMessages) {
- if (mapValue.context) {
- resultJsonObject[mapKey] = { text: mapValue.message, comment: mapValue.context };
- } else {
- resultJsonObject[mapKey] = mapValue.message;
- }
- }
-
- return JSON.stringify(resultJsonObject, undefined, 2);
-}
-
-export async function extractDefaultTranslations({ paths, output, outputFormat }) {
- const defaultMessagesMap = new Map();
-
- for (const inputPath of filterPaths(paths)) {
- await extractMessagesFromPathToMap(inputPath, defaultMessagesMap);
- }
-
- // messages shouldn't be extracted to a file if output is not supplied
- if (!output || !defaultMessagesMap.size) {
- return;
- }
-
- const defaultMessages = [...defaultMessagesMap].sort(([key1], [key2]) =>
- key1.localeCompare(key2)
- );
-
- await writeFileAsync(
- path.resolve(output, 'en.json'),
- outputFormat === 'json5' ? serializeToJson5(defaultMessages) : serializeToJson(defaultMessages)
- );
-}
diff --git a/src/dev/i18n/extract_default_translations.test.js b/src/dev/i18n/extract_default_translations.test.js
index 57e89f731fc6a..b89361e87fcf7 100644
--- a/src/dev/i18n/extract_default_translations.test.js
+++ b/src/dev/i18n/extract_default_translations.test.js
@@ -20,7 +20,7 @@
import path from 'path';
import {
- extractDefaultTranslations,
+ extractMessagesFromPathToMap,
validateMessageNamespace,
} from './extract_default_translations';
@@ -40,42 +40,21 @@ jest.mock('../../../.i18nrc.json', () => ({
exclude: [],
}));
-const utils = require('./utils');
-utils.writeFileAsync = jest.fn();
-
describe('dev/i18n/extract_default_translations', () => {
- test('extracts messages to en.json', async () => {
+ test('extracts messages from path to map', async () => {
const [pluginPath] = pluginsPaths;
+ const resultMap = new Map();
- utils.writeFileAsync.mockClear();
- await extractDefaultTranslations({
- paths: [pluginPath],
- output: pluginPath,
- });
-
- const [[, json]] = utils.writeFileAsync.mock.calls;
-
- expect(json.toString()).toMatchSnapshot();
- });
-
- test('injects default formats into en.json', async () => {
- const [, pluginPath] = pluginsPaths;
-
- utils.writeFileAsync.mockClear();
- await extractDefaultTranslations({
- paths: [pluginPath],
- output: pluginPath,
- });
+ await extractMessagesFromPathToMap(pluginPath, resultMap);
- const [[, json]] = utils.writeFileAsync.mock.calls;
-
- expect(json.toString()).toMatchSnapshot();
+ expect([...resultMap].sort()).toMatchSnapshot();
});
test('throws on id collision', async () => {
const [, , pluginPath] = pluginsPaths;
+
await expect(
- extractDefaultTranslations({ paths: [pluginPath], output: pluginPath })
+ extractMessagesFromPathToMap(pluginPath, new Map())
).rejects.toThrowErrorMatchingSnapshot();
});
diff --git a/src/dev/i18n/extractors/__snapshots__/code.test.js.snap b/src/dev/i18n/extractors/__snapshots__/code.test.js.snap
new file mode 100644
index 0000000000000..26c621e32964d
--- /dev/null
+++ b/src/dev/i18n/extractors/__snapshots__/code.test.js.snap
@@ -0,0 +1,31 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`dev/i18n/extractors/code extracts React, server-side and angular service default messages 1`] = `
+Array [
+ Array [
+ "kbn.mgmt.id-1",
+ Object {
+ "context": undefined,
+ "message": "Message text 1",
+ },
+ ],
+ Array [
+ "kbn.mgmt.id-2",
+ Object {
+ "context": "Message context",
+ "message": "Message text 2",
+ },
+ ],
+ Array [
+ "kbn.mgmt.id-3",
+ Object {
+ "context": undefined,
+ "message": "Message text 3",
+ },
+ ],
+]
+`;
+
+exports[`dev/i18n/extractors/code throws on empty id 1`] = `"Empty \\"id\\" value in i18n() or i18n.translate() is not allowed."`;
+
+exports[`dev/i18n/extractors/code throws on missing defaultMessage 1`] = `"Empty defaultMessage in intl.formatMessage() is not allowed (\\"message-id\\")."`;
diff --git a/src/dev/i18n/extractors/__snapshots__/handlebars.test.js.snap b/src/dev/i18n/extractors/__snapshots__/handlebars.test.js.snap
new file mode 100644
index 0000000000000..7ca5178c7538f
--- /dev/null
+++ b/src/dev/i18n/extractors/__snapshots__/handlebars.test.js.snap
@@ -0,0 +1,21 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`dev/i18n/extractors/handlebars extracts handlebars default messages 1`] = `
+Array [
+ Array [
+ "ui.id-1",
+ Object {
+ "context": "Message context",
+ "message": "Message text",
+ },
+ ],
+]
+`;
+
+exports[`dev/i18n/extractors/handlebars throws on empty id 1`] = `"Empty id argument in Handlebars i18n is not allowed."`;
+
+exports[`dev/i18n/extractors/handlebars throws on missing defaultMessage property 1`] = `"Empty defaultMessage in Handlebars i18n is not allowed (\\"message-id\\")."`;
+
+exports[`dev/i18n/extractors/handlebars throws on wrong number of arguments 1`] = `"Wrong number of arguments for handlebars i18n call."`;
+
+exports[`dev/i18n/extractors/handlebars throws on wrong properties argument type 1`] = `"Properties string in Handlebars i18n should be a string literal (\\"ui.id-1\\")."`;
diff --git a/src/dev/i18n/extractors/__snapshots__/html.test.js.snap b/src/dev/i18n/extractors/__snapshots__/html.test.js.snap
new file mode 100644
index 0000000000000..982341c880074
--- /dev/null
+++ b/src/dev/i18n/extractors/__snapshots__/html.test.js.snap
@@ -0,0 +1,31 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`dev/i18n/extractors/html extracts default messages from HTML 1`] = `
+Array [
+ Array [
+ "kbn.dashboard.id-1",
+ Object {
+ "context": "Message context 1",
+ "message": "Message text 1",
+ },
+ ],
+ Array [
+ "kbn.dashboard.id-2",
+ Object {
+ "context": undefined,
+ "message": "Message text 2",
+ },
+ ],
+ Array [
+ "kbn.dashboard.id-3",
+ Object {
+ "context": "Message context 3",
+ "message": "Message text 3",
+ },
+ ],
+]
+`;
+
+exports[`dev/i18n/extractors/html throws on empty i18n-id 1`] = `"Empty \\"i18n-id\\" value in angular directive is not allowed."`;
+
+exports[`dev/i18n/extractors/html throws on missing i18n-default-message attribute 1`] = `"Empty defaultMessage in angular directive is not allowed (\\"message-id\\")."`;
diff --git a/src/dev/i18n/extractors/__snapshots__/i18n_call.test.js.snap b/src/dev/i18n/extractors/__snapshots__/i18n_call.test.js.snap
new file mode 100644
index 0000000000000..c9bf2f07716d4
--- /dev/null
+++ b/src/dev/i18n/extractors/__snapshots__/i18n_call.test.js.snap
@@ -0,0 +1,29 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`dev/i18n/extractors/i18n_call extracts "i18n" and "i18n.translate" functions call message 1`] = `
+Array [
+ "message-id-1",
+ Object {
+ "context": "Message context 1",
+ "message": "Default message 1",
+ },
+]
+`;
+
+exports[`dev/i18n/extractors/i18n_call extracts "i18n" and "i18n.translate" functions call message 2`] = `
+Array [
+ "message-id-2",
+ Object {
+ "context": "Message context 2",
+ "message": "Default message 2",
+ },
+]
+`;
+
+exports[`dev/i18n/extractors/i18n_call throws if defaultMessage is not a string literal 1`] = `"defaultMessage value in i18n() or i18n.translate() should be a string literal (\\"message-id\\")."`;
+
+exports[`dev/i18n/extractors/i18n_call throws if message id value is not a string literal 1`] = `"Message id in i18n() or i18n.translate() should be a string literal."`;
+
+exports[`dev/i18n/extractors/i18n_call throws if properties object is not provided 1`] = `"Empty defaultMessage in i18n() or i18n.translate() is not allowed (\\"message-id\\")."`;
+
+exports[`dev/i18n/extractors/i18n_call throws on empty defaultMessage 1`] = `"Empty defaultMessage in i18n() or i18n.translate() is not allowed (\\"message-id\\")."`;
diff --git a/src/dev/i18n/extractors/__snapshots__/pug.test.js.snap b/src/dev/i18n/extractors/__snapshots__/pug.test.js.snap
new file mode 100644
index 0000000000000..c95fb0d149cd0
--- /dev/null
+++ b/src/dev/i18n/extractors/__snapshots__/pug.test.js.snap
@@ -0,0 +1,15 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`dev/i18n/extractors/pug extracts messages from pug template 1`] = `
+Array [
+ "message-id",
+ Object {
+ "context": "Message context",
+ "message": "Default message",
+ },
+]
+`;
+
+exports[`dev/i18n/extractors/pug throws on empty id 1`] = `"Empty \\"id\\" value in i18n() or i18n.translate() is not allowed."`;
+
+exports[`dev/i18n/extractors/pug throws on missing default message 1`] = `"Empty defaultMessage in i18n() or i18n.translate() is not allowed (\\"message-id\\")."`;
diff --git a/src/dev/i18n/extractors/__snapshots__/react.test.js.snap b/src/dev/i18n/extractors/__snapshots__/react.test.js.snap
new file mode 100644
index 0000000000000..6a51a5e216004
--- /dev/null
+++ b/src/dev/i18n/extractors/__snapshots__/react.test.js.snap
@@ -0,0 +1,27 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`dev/i18n/extractors/react extractFormattedMessages extracts messages from "" element 1`] = `
+Array [
+ "message-id-2",
+ Object {
+ "context": "Message context 2",
+ "message": "Default message 2",
+ },
+]
+`;
+
+exports[`dev/i18n/extractors/react extractIntlMessages extracts messages from "intl.formatMessage" function call 1`] = `
+Array [
+ "message-id-1",
+ Object {
+ "context": "Message context 1",
+ "message": "Default message 1",
+ },
+]
+`;
+
+exports[`dev/i18n/extractors/react extractIntlMessages throws if context value is not a string literal 1`] = `"context value should be a string literal (\\"message-id\\")."`;
+
+exports[`dev/i18n/extractors/react extractIntlMessages throws if defaultMessage value is not a string literal 1`] = `"defaultMessage value should be a string literal (\\"message-id\\")."`;
+
+exports[`dev/i18n/extractors/react extractIntlMessages throws if message id is not a string literal 1`] = `"Message id should be a string literal."`;
diff --git a/src/dev/i18n/extract_code_messages.js b/src/dev/i18n/extractors/code.js
similarity index 94%
rename from src/dev/i18n/extract_code_messages.js
rename to src/dev/i18n/extractors/code.js
index e7b72e6efa162..e7477b17e2759 100644
--- a/src/dev/i18n/extract_code_messages.js
+++ b/src/dev/i18n/extractors/code.js
@@ -26,9 +26,9 @@ import {
isMemberExpression,
} from '@babel/types';
-import { extractI18nCallMessages } from './extract_i18n_call_messages';
-import { isI18nTranslateFunction, traverseNodes } from './utils';
-import { extractIntlMessages, extractFormattedMessages } from './extract_react_messages';
+import { extractI18nCallMessages } from './i18n_call';
+import { isI18nTranslateFunction, traverseNodes } from '../utils';
+import { extractIntlMessages, extractFormattedMessages } from './react';
/**
* Detect Intl.formatMessage() function call (React).
diff --git a/src/dev/i18n/extract_code_messages.test.js b/src/dev/i18n/extractors/code.test.js
similarity index 89%
rename from src/dev/i18n/extract_code_messages.test.js
rename to src/dev/i18n/extractors/code.test.js
index 5b3e64ebb4f07..3cc7d39f78d40 100644
--- a/src/dev/i18n/extract_code_messages.test.js
+++ b/src/dev/i18n/extractors/code.test.js
@@ -24,8 +24,8 @@ import {
extractCodeMessages,
isFormattedMessageElement,
isIntlFormatMessageFunction,
-} from './extract_code_messages';
-import { traverseNodes } from './utils';
+} from './code';
+import { traverseNodes } from '../utils';
const extractCodeMessagesSource = Buffer.from(`
i18n('kbn.mgmt.id-1', { defaultMessage: 'Message text 1' });
@@ -65,7 +65,7 @@ function f() {
}
`;
-describe('extractCodeMessages', () => {
+describe('dev/i18n/extractors/code', () => {
test('extracts React, server-side and angular service default messages', () => {
const actual = Array.from(extractCodeMessages(extractCodeMessagesSource));
expect(actual.sort()).toMatchSnapshot();
@@ -84,12 +84,16 @@ describe('extractCodeMessages', () => {
describe('isIntlFormatMessageFunction', () => {
test('detects intl.formatMessage call expression', () => {
- const callExpressionNodes = [...traverseNodes(parse(intlFormatMessageSource).program.body)].filter(
- node => isCallExpression(node)
- );
+ const callExpressionNodes = [
+ ...traverseNodes(parse(intlFormatMessageSource).program.body),
+ ].filter(node => isCallExpression(node));
expect(callExpressionNodes).toHaveLength(4);
- expect(callExpressionNodes.every(callExpressionNode => isIntlFormatMessageFunction(callExpressionNode))).toBe(true);
+ expect(
+ callExpressionNodes.every(callExpressionNode =>
+ isIntlFormatMessageFunction(callExpressionNode)
+ )
+ ).toBe(true);
});
});
diff --git a/src/dev/i18n/extract_handlebars_messages.js b/src/dev/i18n/extractors/handlebars.js
similarity index 68%
rename from src/dev/i18n/extract_handlebars_messages.js
rename to src/dev/i18n/extractors/handlebars.js
index 1aabdb61e2be1..7c57c8d0da731 100644
--- a/src/dev/i18n/extract_handlebars_messages.js
+++ b/src/dev/i18n/extractors/handlebars.js
@@ -17,10 +17,8 @@
* under the License.
*/
-import chalk from 'chalk';
-
-import { formatJSString } from './utils';
-import { createFailError } from '../run';
+import { formatJSString } from '../utils';
+import { createFailError } from '../../run';
const HBS_REGEX = /(?<=\{\{)([\s\S]*?)(?=\}\})/g;
const TOKENS_REGEX = /[^'\s]+|(?:'([^'\\]|\\[\s\S])*')/g;
@@ -39,29 +37,22 @@ export function* extractHandlebarsMessages(buffer) {
}
if (tokens.length !== 3) {
- throw createFailError(
- `${chalk.white.bgRed(' I18N ERROR ')} Wrong number of arguments for handlebars i18n call.`
- );
+ throw createFailError(`Wrong number of arguments for handlebars i18n call.`);
}
if (!idString.startsWith(`'`) || !idString.endsWith(`'`)) {
- throw createFailError(
- `${chalk.white.bgRed(' I18N ERROR ')} Message id should be a string literal.`
- );
+ throw createFailError(`Message id should be a string literal.`);
}
const messageId = formatJSString(idString.slice(1, -1));
if (!messageId) {
- throw createFailError(
- `${chalk.white.bgRed(' I18N ERROR ')} Empty id argument in Handlebars i18n is not allowed.`
- );
+ throw createFailError(`Empty id argument in Handlebars i18n is not allowed.`);
}
if (!propertiesString.startsWith(`'`) || !propertiesString.endsWith(`'`)) {
throw createFailError(
- `${chalk.white.bgRed(' I18N ERROR ')} \
-Properties string in Handlebars i18n should be a string literal ("${messageId}").`
+ `Properties string in Handlebars i18n should be a string literal ("${messageId}").`
);
}
@@ -70,15 +61,13 @@ Properties string in Handlebars i18n should be a string literal ("${messageId}")
if (typeof message !== 'string') {
throw createFailError(
- `${chalk.white.bgRed(' I18N ERROR ')} \
-defaultMessage value in Handlebars i18n should be a string ("${messageId}").`
+ `defaultMessage value in Handlebars i18n should be a string ("${messageId}").`
);
}
if (!message) {
throw createFailError(
- `${chalk.white.bgRed(' I18N ERROR ')} \
-Empty defaultMessage in Handlebars i18n is not allowed ("${messageId}").`
+ `Empty defaultMessage in Handlebars i18n is not allowed ("${messageId}").`
);
}
@@ -86,8 +75,7 @@ Empty defaultMessage in Handlebars i18n is not allowed ("${messageId}").`
if (context != null && typeof context !== 'string') {
throw createFailError(
- `${chalk.white.bgRed(' I18N ERROR ')} \
-Context value in Handlebars i18n should be a string ("${messageId}").`
+ `Context value in Handlebars i18n should be a string ("${messageId}").`
);
}
diff --git a/src/dev/i18n/extract_handlebars_messages.test.js b/src/dev/i18n/extractors/handlebars.test.js
similarity index 95%
rename from src/dev/i18n/extract_handlebars_messages.test.js
rename to src/dev/i18n/extractors/handlebars.test.js
index e4f53852b6cc3..52365989bd7fd 100644
--- a/src/dev/i18n/extract_handlebars_messages.test.js
+++ b/src/dev/i18n/extractors/handlebars.test.js
@@ -17,9 +17,9 @@
* under the License.
*/
-import { extractHandlebarsMessages } from './extract_handlebars_messages';
+import { extractHandlebarsMessages } from './handlebars';
-describe('dev/i18n/extract_handlebars_messages', () => {
+describe('dev/i18n/extractors/handlebars', () => {
test('extracts handlebars default messages', () => {
const source = Buffer.from(`\
window.onload = function () {
diff --git a/src/dev/i18n/extract_html_messages.js b/src/dev/i18n/extractors/html.js
similarity index 78%
rename from src/dev/i18n/extract_html_messages.js
rename to src/dev/i18n/extractors/html.js
index 4c8cad3ce1008..b576acb31c6d2 100644
--- a/src/dev/i18n/extract_html_messages.js
+++ b/src/dev/i18n/extractors/html.js
@@ -17,14 +17,13 @@
* under the License.
*/
-import chalk from 'chalk';
import { jsdom } from 'jsdom';
import { parse } from '@babel/parser';
import { isDirectiveLiteral, isObjectExpression, isStringLiteral } from '@babel/types';
-import { isPropertyWithKey, formatHTMLString, formatJSString, traverseNodes } from './utils';
-import { DEFAULT_MESSAGE_KEY, CONTEXT_KEY } from './constants';
-import { createFailError } from '../run';
+import { isPropertyWithKey, formatHTMLString, formatJSString, traverseNodes } from '../utils';
+import { DEFAULT_MESSAGE_KEY, CONTEXT_KEY } from '../constants';
+import { createFailError } from '../../run';
/**
* Find all substrings of "{{ any text }}" pattern
@@ -53,17 +52,13 @@ function parseFilterObjectExpression(expression) {
for (const property of node.properties) {
if (isPropertyWithKey(property, DEFAULT_MESSAGE_KEY)) {
if (!isStringLiteral(property.value)) {
- throw createFailError(
- `${chalk.white.bgRed(' I18N ERROR ')} defaultMessage value should be a string literal.`
- );
+ throw createFailError(`defaultMessage value should be a string literal.`);
}
message = formatJSString(property.value.value);
} else if (isPropertyWithKey(property, CONTEXT_KEY)) {
if (!isStringLiteral(property.value)) {
- throw createFailError(
- `${chalk.white.bgRed(' I18N ERROR ')} context value should be a string literal.`
- );
+ throw createFailError(`context value should be a string literal.`);
}
context = formatJSString(property.value.value);
@@ -101,27 +96,20 @@ function* getFilterMessages(htmlContent) {
const filterObjectExpression = expression.slice(filterStart + I18N_FILTER_MARKER.length).trim();
if (!filterObjectExpression || !idExpression) {
- throw createFailError(
- `${chalk.white.bgRed(' I18N ERROR ')} \
-Cannot parse i18n filter expression: {{ ${expression} }}`
- );
+ throw createFailError(`Cannot parse i18n filter expression: {{ ${expression} }}`);
}
const messageId = parseIdExpression(idExpression);
if (!messageId) {
- throw createFailError(
- `${chalk.white.bgRed(' I18N ERROR ')} \
-Empty "id" value in angular filter expression is not allowed.`
- );
+ throw createFailError(`Empty "id" value in angular filter expression is not allowed.`);
}
const { message, context } = parseFilterObjectExpression(filterObjectExpression) || {};
if (!message) {
throw createFailError(
- `${chalk.white.bgRed(' I18N ERROR ')} \
-Empty defaultMessage in angular filter expression is not allowed ("${messageId}").`
+ `Empty defaultMessage in angular filter expression is not allowed ("${messageId}").`
);
}
@@ -137,17 +125,13 @@ function* getDirectiveMessages(htmlContent) {
for (const element of document.querySelectorAll('[i18n-id]')) {
const messageId = formatHTMLString(element.getAttribute('i18n-id'));
if (!messageId) {
- throw createFailError(
- `${chalk.white.bgRed(' I18N ERROR ')} \
-Empty "i18n-id" value in angular directive is not allowed.`
- );
+ throw createFailError(`Empty "i18n-id" value in angular directive is not allowed.`);
}
const message = formatHTMLString(element.getAttribute('i18n-default-message'));
if (!message) {
throw createFailError(
- `${chalk.white.bgRed(' I18N ERROR ')} \
-Empty defaultMessage in angular directive is not allowed ("${messageId}").`
+ `Empty defaultMessage in angular directive is not allowed ("${messageId}").`
);
}
diff --git a/src/dev/i18n/extract_html_messages.test.js b/src/dev/i18n/extractors/html.test.js
similarity index 94%
rename from src/dev/i18n/extract_html_messages.test.js
rename to src/dev/i18n/extractors/html.test.js
index d5cf7d6fd5ee2..40664edd81e4a 100644
--- a/src/dev/i18n/extract_html_messages.test.js
+++ b/src/dev/i18n/extractors/html.test.js
@@ -17,7 +17,7 @@
* under the License.
*/
-import { extractHtmlMessages } from './extract_html_messages';
+import { extractHtmlMessages } from './html';
const htmlSourceBuffer = Buffer.from(`
@@ -37,7 +37,7 @@ const htmlSourceBuffer = Buffer.from(`
`);
-describe('dev/i18n/extract_html_messages', () => {
+describe('dev/i18n/extractors/html', () => {
test('extracts default messages from HTML', () => {
const actual = Array.from(extractHtmlMessages(htmlSourceBuffer));
expect(actual.sort()).toMatchSnapshot();
diff --git a/src/dev/i18n/extract_i18n_call_messages.js b/src/dev/i18n/extractors/i18n_call.js
similarity index 64%
rename from src/dev/i18n/extract_i18n_call_messages.js
rename to src/dev/i18n/extractors/i18n_call.js
index ba146c06621fe..1adcf42598e16 100644
--- a/src/dev/i18n/extract_i18n_call_messages.js
+++ b/src/dev/i18n/extractors/i18n_call.js
@@ -17,12 +17,11 @@
* under the License.
*/
-import chalk from 'chalk';
import { isObjectExpression, isStringLiteral } from '@babel/types';
-import { isPropertyWithKey, formatJSString } from './utils';
-import { DEFAULT_MESSAGE_KEY, CONTEXT_KEY } from './constants';
-import { createFailError } from '../run';
+import { isPropertyWithKey, formatJSString } from '../utils';
+import { DEFAULT_MESSAGE_KEY, CONTEXT_KEY } from '../constants';
+import { createFailError } from '../../run';
/**
* Extract messages from `funcName('id', { defaultMessage: 'Message text' })` call expression AST
@@ -31,19 +30,13 @@ export function extractI18nCallMessages(node) {
const [idSubTree, optionsSubTree] = node.arguments;
if (!isStringLiteral(idSubTree)) {
- throw createFailError(
- `${chalk.white.bgRed(' I18N ERROR ')} \
-Message id in i18n() or i18n.translate() should be a string literal.`
- );
+ throw createFailError(`Message id in i18n() or i18n.translate() should be a string literal.`);
}
const messageId = idSubTree.value;
if (!messageId) {
- throw createFailError(
- `${chalk.white.bgRed(' I18N ERROR ')} \
-Empty "id" value in i18n() or i18n.translate() is not allowed.`
- );
+ throw createFailError(`Empty "id" value in i18n() or i18n.translate() is not allowed.`);
}
let message;
@@ -51,8 +44,7 @@ Empty "id" value in i18n() or i18n.translate() is not allowed.`
if (!isObjectExpression(optionsSubTree)) {
throw createFailError(
- `${chalk.white.bgRed(' I18N ERROR ')} \
-Empty defaultMessage in i18n() or i18n.translate() is not allowed ("${messageId}").`
+ `Empty defaultMessage in i18n() or i18n.translate() is not allowed ("${messageId}").`
);
}
@@ -60,8 +52,7 @@ Empty defaultMessage in i18n() or i18n.translate() is not allowed ("${messageId}
if (isPropertyWithKey(prop, DEFAULT_MESSAGE_KEY)) {
if (!isStringLiteral(prop.value)) {
throw createFailError(
- `${chalk.white.bgRed(' I18N ERROR ')} \
-defaultMessage value in i18n() or i18n.translate() should be a string literal ("${messageId}").`
+ `defaultMessage value in i18n() or i18n.translate() should be a string literal ("${messageId}").`
);
}
@@ -69,8 +60,7 @@ defaultMessage value in i18n() or i18n.translate() should be a string literal ("
} else if (isPropertyWithKey(prop, CONTEXT_KEY)) {
if (!isStringLiteral(prop.value)) {
throw createFailError(
- `${chalk.white.bgRed(' I18N ERROR ')} \
-context value in i18n() or i18n.translate() should be a string literal ("${messageId}").`
+ `context value in i18n() or i18n.translate() should be a string literal ("${messageId}").`
);
}
@@ -80,8 +70,7 @@ context value in i18n() or i18n.translate() should be a string literal ("${messa
if (!message) {
throw createFailError(
- `${chalk.white.bgRed(' I18N ERROR ')} \
-Empty defaultMessage in i18n() or i18n.translate() is not allowed ("${messageId}").`
+ `Empty defaultMessage in i18n() or i18n.translate() is not allowed ("${messageId}").`
);
}
diff --git a/src/dev/i18n/extract_i18n_call_messages.test.js b/src/dev/i18n/extractors/i18n_call.test.js
similarity index 95%
rename from src/dev/i18n/extract_i18n_call_messages.test.js
rename to src/dev/i18n/extractors/i18n_call.test.js
index 0985233e4b3dd..f3ab92f4f1d6e 100644
--- a/src/dev/i18n/extract_i18n_call_messages.test.js
+++ b/src/dev/i18n/extractors/i18n_call.test.js
@@ -20,8 +20,8 @@
import { parse } from '@babel/parser';
import { isCallExpression } from '@babel/types';
-import { extractI18nCallMessages } from './extract_i18n_call_messages';
-import { traverseNodes } from './utils';
+import { extractI18nCallMessages } from './i18n_call';
+import { traverseNodes } from '../utils';
const i18nCallMessageSource = `
i18n('message-id-1', { defaultMessage: 'Default message 1', context: 'Message context 1' });
@@ -31,7 +31,7 @@ const translateCallMessageSource = `
i18n.translate('message-id-2', { defaultMessage: 'Default message 2', context: 'Message context 2' });
`;
-describe('extractI18nCallMessages', () => {
+describe('dev/i18n/extractors/i18n_call', () => {
test('extracts "i18n" and "i18n.translate" functions call message', () => {
let callExpressionNode = [...traverseNodes(parse(i18nCallMessageSource).program.body)].find(
node => isCallExpression(node)
diff --git a/src/dev/i18n/extractors/index.js b/src/dev/i18n/extractors/index.js
new file mode 100644
index 0000000000000..7362eeb4e7003
--- /dev/null
+++ b/src/dev/i18n/extractors/index.js
@@ -0,0 +1,25 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+export { extractCodeMessages } from './code';
+export { extractHandlebarsMessages } from './handlebars';
+export { extractHtmlMessages } from './html';
+export { extractI18nCallMessages } from './i18n_call';
+export { extractPugMessages } from './pug';
+export { extractFormattedMessages, extractIntlMessages } from './react';
diff --git a/src/dev/i18n/extract_pug_messages.js b/src/dev/i18n/extractors/pug.js
similarity index 91%
rename from src/dev/i18n/extract_pug_messages.js
rename to src/dev/i18n/extractors/pug.js
index 8451c0b11db24..59851d19e88ab 100644
--- a/src/dev/i18n/extract_pug_messages.js
+++ b/src/dev/i18n/extractors/pug.js
@@ -19,8 +19,8 @@
import { parse } from '@babel/parser';
-import { extractI18nCallMessages } from './extract_i18n_call_messages';
-import { isI18nTranslateFunction, traverseNodes } from './utils';
+import { extractI18nCallMessages } from './i18n_call';
+import { isI18nTranslateFunction, traverseNodes } from '../utils';
/**
* Matches `i18n(...)` in `#{i18n('id', { defaultMessage: 'Message text' })}`
diff --git a/src/dev/i18n/extract_pug_messages.test.js b/src/dev/i18n/extractors/pug.test.js
similarity index 94%
rename from src/dev/i18n/extract_pug_messages.test.js
rename to src/dev/i18n/extractors/pug.test.js
index 0f72c13a6a339..7f901d1d992db 100644
--- a/src/dev/i18n/extract_pug_messages.test.js
+++ b/src/dev/i18n/extractors/pug.test.js
@@ -17,9 +17,9 @@
* under the License.
*/
-import { extractPugMessages } from './extract_pug_messages';
+import { extractPugMessages } from './pug';
-describe('extractPugMessages', () => {
+describe('dev/i18n/extractors/pug', () => {
test('extracts messages from pug template', () => {
const source = Buffer.from(`\
#{i18n('message-id', { defaultMessage: 'Default message', context: 'Message context' })}
diff --git a/src/dev/i18n/extract_react_messages.js b/src/dev/i18n/extractors/react.js
similarity index 73%
rename from src/dev/i18n/extract_react_messages.js
rename to src/dev/i18n/extractors/react.js
index 014f1214d0a18..074af4a76d5b4 100644
--- a/src/dev/i18n/extract_react_messages.js
+++ b/src/dev/i18n/extractors/react.js
@@ -18,17 +18,14 @@
*/
import { isJSXIdentifier, isObjectExpression, isStringLiteral } from '@babel/types';
-import chalk from 'chalk';
-import { isPropertyWithKey, formatJSString, formatHTMLString } from './utils';
-import { DEFAULT_MESSAGE_KEY, CONTEXT_KEY } from './constants';
-import { createFailError } from '../run';
+import { isPropertyWithKey, formatJSString, formatHTMLString } from '../utils';
+import { DEFAULT_MESSAGE_KEY, CONTEXT_KEY } from '../constants';
+import { createFailError } from '../../run';
function extractMessageId(value) {
if (!isStringLiteral(value)) {
- throw createFailError(
- `${chalk.white.bgRed(' I18N ERROR ')} Message id should be a string literal.`
- );
+ throw createFailError(`Message id should be a string literal.`);
}
return value.value;
@@ -36,10 +33,7 @@ function extractMessageId(value) {
function extractMessageValue(value, id) {
if (!isStringLiteral(value)) {
- throw createFailError(
- `${chalk.white.bgRed(' I18N ERROR ')} \
-defaultMessage value should be a string literal ("${id}").`
- );
+ throw createFailError(`defaultMessage value should be a string literal ("${id}").`);
}
return value.value;
@@ -47,9 +41,7 @@ defaultMessage value should be a string literal ("${id}").`
function extractContextValue(value, id) {
if (!isStringLiteral(value)) {
- throw createFailError(
- `${chalk.white.bgRed(' I18N ERROR ')} context value should be a string literal ("${id}").`
- );
+ throw createFailError(`context value should be a string literal ("${id}").`);
}
return value.value;
@@ -65,8 +57,7 @@ export function extractIntlMessages(node) {
if (!isObjectExpression(options)) {
throw createFailError(
- `${chalk.white.bgRed(' I18N ERROR ')} \
-Object with defaultMessage property is not passed to intl.formatMessage().`
+ `Object with defaultMessage property is not passed to intl.formatMessage().`
);
}
@@ -81,10 +72,7 @@ Object with defaultMessage property is not passed to intl.formatMessage().`
: undefined;
if (!messageId) {
- createFailError(
- `${chalk.white.bgRed(' I18N ERROR ')} \
-Empty "id" value in intl.formatMessage() is not allowed.`
- );
+ createFailError(`Empty "id" value in intl.formatMessage() is not allowed.`);
}
const message = messageProperty
@@ -93,8 +81,7 @@ Empty "id" value in intl.formatMessage() is not allowed.`
if (!message) {
throw createFailError(
- `${chalk.white.bgRed(' I18N ERROR ')} \
-Empty defaultMessage in intl.formatMessage() is not allowed ("${messageId}").`
+ `Empty defaultMessage in intl.formatMessage() is not allowed ("${messageId}").`
);
}
@@ -122,9 +109,7 @@ export function extractFormattedMessages(node) {
: undefined;
if (!messageId) {
- throw createFailError(
- `${chalk.white.bgRed(' I18N ERROR ')} Empty "id" value in is not allowed.`
- );
+ throw createFailError(`Empty "id" value in is not allowed.`);
}
const message = messageProperty
@@ -133,8 +118,7 @@ export function extractFormattedMessages(node) {
if (!message) {
throw createFailError(
- `${chalk.white.bgRed(' I18N ERROR ')} \
-Empty default message in is not allowed ("${messageId}").`
+ `Empty default message in is not allowed ("${messageId}").`
);
}
diff --git a/src/dev/i18n/extract_react_messages.test.js b/src/dev/i18n/extractors/react.test.js
similarity index 97%
rename from src/dev/i18n/extract_react_messages.test.js
rename to src/dev/i18n/extractors/react.test.js
index 00233ac1abed2..91e65a0ecc20f 100644
--- a/src/dev/i18n/extract_react_messages.test.js
+++ b/src/dev/i18n/extractors/react.test.js
@@ -20,8 +20,8 @@
import { parse } from '@babel/parser';
import { isCallExpression, isJSXOpeningElement, isJSXIdentifier } from '@babel/types';
-import { extractIntlMessages, extractFormattedMessages } from './extract_react_messages';
-import { traverseNodes } from './utils';
+import { extractIntlMessages, extractFormattedMessages } from './react';
+import { traverseNodes } from '../utils';
const intlFormatMessageCallSource = `
const MyComponentContent = ({ intl }) => (
@@ -79,7 +79,7 @@ intl.formatMessage({
`,
];
-describe('dev/i18n/extract_react_messages', () => {
+describe('dev/i18n/extractors/react', () => {
describe('extractIntlMessages', () => {
test('extracts messages from "intl.formatMessage" function call', () => {
const ast = parse(intlFormatMessageCallSource, { plugins: ['jsx'] });
diff --git a/src/dev/i18n/index.js b/src/dev/i18n/index.js
new file mode 100644
index 0000000000000..703e6ac682855
--- /dev/null
+++ b/src/dev/i18n/index.js
@@ -0,0 +1,22 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+export { filterPaths, extractMessagesFromPathToMap } from './extract_default_translations';
+export { writeFileAsync } from './utils';
+export { serializeToJson, serializeToJson5 } from './serializers';
diff --git a/src/dev/i18n/serializers/__snapshots__/json.test.js.snap b/src/dev/i18n/serializers/__snapshots__/json.test.js.snap
new file mode 100644
index 0000000000000..c35e91e25cbb6
--- /dev/null
+++ b/src/dev/i18n/serializers/__snapshots__/json.test.js.snap
@@ -0,0 +1,67 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`dev/i18n/serializers/json should serialize default messages to JSON 1`] = `
+"{
+ \\"formats\\": {
+ \\"number\\": {
+ \\"currency\\": {
+ \\"style\\": \\"currency\\"
+ },
+ \\"percent\\": {
+ \\"style\\": \\"percent\\"
+ }
+ },
+ \\"date\\": {
+ \\"short\\": {
+ \\"month\\": \\"numeric\\",
+ \\"day\\": \\"numeric\\",
+ \\"year\\": \\"2-digit\\"
+ },
+ \\"medium\\": {
+ \\"month\\": \\"short\\",
+ \\"day\\": \\"numeric\\",
+ \\"year\\": \\"numeric\\"
+ },
+ \\"long\\": {
+ \\"month\\": \\"long\\",
+ \\"day\\": \\"numeric\\",
+ \\"year\\": \\"numeric\\"
+ },
+ \\"full\\": {
+ \\"weekday\\": \\"long\\",
+ \\"month\\": \\"long\\",
+ \\"day\\": \\"numeric\\",
+ \\"year\\": \\"numeric\\"
+ }
+ },
+ \\"time\\": {
+ \\"short\\": {
+ \\"hour\\": \\"numeric\\",
+ \\"minute\\": \\"numeric\\"
+ },
+ \\"medium\\": {
+ \\"hour\\": \\"numeric\\",
+ \\"minute\\": \\"numeric\\",
+ \\"second\\": \\"numeric\\"
+ },
+ \\"long\\": {
+ \\"hour\\": \\"numeric\\",
+ \\"minute\\": \\"numeric\\",
+ \\"second\\": \\"numeric\\",
+ \\"timeZoneName\\": \\"short\\"
+ },
+ \\"full\\": {
+ \\"hour\\": \\"numeric\\",
+ \\"minute\\": \\"numeric\\",
+ \\"second\\": \\"numeric\\",
+ \\"timeZoneName\\": \\"short\\"
+ }
+ }
+ },
+ \\"plugin1.message.id-1\\": \\"Message text 1 \\",
+ \\"plugin2.message.id-2\\": {
+ \\"text\\": \\"Message text 2\\",
+ \\"comment\\": \\"Message context\\"
+ }
+}"
+`;
diff --git a/src/dev/i18n/serializers/__snapshots__/json5.test.js.snap b/src/dev/i18n/serializers/__snapshots__/json5.test.js.snap
new file mode 100644
index 0000000000000..2166b32f28fd1
--- /dev/null
+++ b/src/dev/i18n/serializers/__snapshots__/json5.test.js.snap
@@ -0,0 +1,65 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`dev/i18n/serializers/json5 should serialize default messages to JSON5 1`] = `
+"{
+ formats: {
+ number: {
+ currency: {
+ style: 'currency',
+ },
+ percent: {
+ style: 'percent',
+ },
+ },
+ date: {
+ short: {
+ month: 'numeric',
+ day: 'numeric',
+ year: '2-digit',
+ },
+ medium: {
+ month: 'short',
+ day: 'numeric',
+ year: 'numeric',
+ },
+ long: {
+ month: 'long',
+ day: 'numeric',
+ year: 'numeric',
+ },
+ full: {
+ weekday: 'long',
+ month: 'long',
+ day: 'numeric',
+ year: 'numeric',
+ },
+ },
+ time: {
+ short: {
+ hour: 'numeric',
+ minute: 'numeric',
+ },
+ medium: {
+ hour: 'numeric',
+ minute: 'numeric',
+ second: 'numeric',
+ },
+ long: {
+ hour: 'numeric',
+ minute: 'numeric',
+ second: 'numeric',
+ timeZoneName: 'short',
+ },
+ full: {
+ hour: 'numeric',
+ minute: 'numeric',
+ second: 'numeric',
+ timeZoneName: 'short',
+ },
+ },
+ },
+ 'plugin1.message.id-1': 'Message text 1',
+ 'plugin2.message.id-2': 'Message text 2', // Message context
+}
+"
+`;
diff --git a/src/dev/i18n/serializers/index.js b/src/dev/i18n/serializers/index.js
new file mode 100644
index 0000000000000..3c10d7754563d
--- /dev/null
+++ b/src/dev/i18n/serializers/index.js
@@ -0,0 +1,21 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+export { serializeToJson } from './json';
+export { serializeToJson5 } from './json5';
diff --git a/src/dev/i18n/serializers/json.js b/src/dev/i18n/serializers/json.js
new file mode 100644
index 0000000000000..8e615af1e81d3
--- /dev/null
+++ b/src/dev/i18n/serializers/json.js
@@ -0,0 +1,34 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { i18n } from '@kbn/i18n';
+
+export function serializeToJson(defaultMessages) {
+ const resultJsonObject = { formats: i18n.formats };
+
+ for (const [mapKey, mapValue] of defaultMessages) {
+ if (mapValue.context) {
+ resultJsonObject[mapKey] = { text: mapValue.message, comment: mapValue.context };
+ } else {
+ resultJsonObject[mapKey] = mapValue.message;
+ }
+ }
+
+ return JSON.stringify(resultJsonObject, undefined, 2);
+}
diff --git a/src/dev/i18n/serializers/json.test.js b/src/dev/i18n/serializers/json.test.js
new file mode 100644
index 0000000000000..9486a999fe7db
--- /dev/null
+++ b/src/dev/i18n/serializers/json.test.js
@@ -0,0 +1,37 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { serializeToJson } from './json';
+
+describe('dev/i18n/serializers/json', () => {
+ test('should serialize default messages to JSON', () => {
+ const messages = new Map([
+ ['plugin1.message.id-1', { message: 'Message text 1 ' }],
+ [
+ 'plugin2.message.id-2',
+ {
+ message: 'Message text 2',
+ context: 'Message context',
+ },
+ ],
+ ]);
+
+ expect(serializeToJson(messages)).toMatchSnapshot();
+ });
+});
diff --git a/src/dev/i18n/serializers/json5.js b/src/dev/i18n/serializers/json5.js
new file mode 100644
index 0000000000000..0156053d5f43b
--- /dev/null
+++ b/src/dev/i18n/serializers/json5.js
@@ -0,0 +1,48 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import JSON5 from 'json5';
+import { i18n } from '@kbn/i18n';
+
+const ESCAPE_SINGLE_QUOTE_REGEX = /\\([\s\S])|(')/g;
+
+export function serializeToJson5(defaultMessages) {
+ // .slice(0, -1): remove closing curly brace from json to append messages
+ let jsonBuffer = Buffer.from(
+ JSON5.stringify({ formats: i18n.formats }, { quote: `'`, space: 2 }).slice(0, -1)
+ );
+
+ for (const [mapKey, mapValue] of defaultMessages) {
+ const formattedMessage = mapValue.message.replace(ESCAPE_SINGLE_QUOTE_REGEX, '\\$1$2');
+ const formattedContext = mapValue.context
+ ? mapValue.context.replace(ESCAPE_SINGLE_QUOTE_REGEX, '\\$1$2')
+ : '';
+
+ jsonBuffer = Buffer.concat([
+ jsonBuffer,
+ Buffer.from(` '${mapKey}': '${formattedMessage}',`),
+ Buffer.from(formattedContext ? ` // ${formattedContext}\n` : '\n'),
+ ]);
+ }
+
+ // append previously removed closing curly brace
+ jsonBuffer = Buffer.concat([jsonBuffer, Buffer.from('}\n')]);
+
+ return jsonBuffer;
+}
diff --git a/src/dev/i18n/serializers/json5.test.js b/src/dev/i18n/serializers/json5.test.js
new file mode 100644
index 0000000000000..90be880bd32a3
--- /dev/null
+++ b/src/dev/i18n/serializers/json5.test.js
@@ -0,0 +1,42 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { serializeToJson5 } from './json5';
+
+describe('dev/i18n/serializers/json5', () => {
+ test('should serialize default messages to JSON5', () => {
+ const messages = new Map([
+ [
+ 'plugin1.message.id-1',
+ {
+ message: 'Message text 1',
+ },
+ ],
+ [
+ 'plugin2.message.id-2',
+ {
+ message: 'Message text 2',
+ context: 'Message context',
+ },
+ ],
+ ]);
+
+ expect(serializeToJson5(messages).toString()).toMatchSnapshot();
+ });
+});
diff --git a/src/dev/run/index.js b/src/dev/run/index.js
index 1eef88d60b0a5..b176ac365fcf4 100644
--- a/src/dev/run/index.js
+++ b/src/dev/run/index.js
@@ -18,4 +18,4 @@
*/
export { run } from './run';
-export { createFailError, combineErrors } from './fail';
+export { createFailError, combineErrors, isFailError } from './fail';
diff --git a/src/dev/run_i18n_check.js b/src/dev/run_i18n_check.js
index 9d5f0011d6f38..02d70622b54cd 100644
--- a/src/dev/run_i18n_check.js
+++ b/src/dev/run_i18n_check.js
@@ -17,13 +17,46 @@
* under the License.
*/
-import { run } from './run';
-import { extractDefaultTranslations } from './i18n/extract_default_translations';
+import chalk from 'chalk';
+import Listr from 'listr';
+import { resolve } from 'path';
+
+import { run, createFailError } from './run';
+import {
+ filterPaths,
+ extractMessagesFromPathToMap,
+ writeFileAsync,
+ serializeToJson,
+ serializeToJson5,
+} from './i18n/';
run(async ({ flags: { path, output, 'output-format': outputFormat } }) => {
- await extractDefaultTranslations({
- paths: Array.isArray(path) ? path : [path || './'],
- output,
- outputFormat,
- });
+ const paths = Array.isArray(path) ? path : [path || './'];
+ const filteredPaths = filterPaths(paths);
+
+ if (filteredPaths.length === 0) {
+ throw createFailError(
+ `${chalk.white.bgRed(' I18N ERROR ')} \
+None of input paths is available for extraction or validation. See .i18nrc.json.`
+ );
+ }
+
+ const list = new Listr(
+ filteredPaths.map(filteredPath => ({
+ task: messages => extractMessagesFromPathToMap(filteredPath, messages),
+ title: filteredPath,
+ }))
+ );
+
+ // messages shouldn't be extracted to a file if output is not supplied
+ const messages = await list.run(new Map());
+ if (!output || !messages.size) {
+ return;
+ }
+
+ const sortedMessages = [...messages].sort(([key1], [key2]) => key1.localeCompare(key2));
+ await writeFileAsync(
+ resolve(output, 'en.json'),
+ outputFormat === 'json5' ? serializeToJson5(sortedMessages) : serializeToJson(sortedMessages)
+ );
});
From d8f907b18a161d487c9af8af1189935e4e7ba092 Mon Sep 17 00:00:00 2001
From: Tim Roes
Date: Wed, 5 Sep 2018 15:27:24 +0200
Subject: [PATCH 14/68] Fix broken visualize CSS (#22707)
---
.../kibana/public/visualize/editor/styles/_editor.less | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/core_plugins/kibana/public/visualize/editor/styles/_editor.less b/src/core_plugins/kibana/public/visualize/editor/styles/_editor.less
index 6991ae34d6cb1..2cfc573345885 100644
--- a/src/core_plugins/kibana/public/visualize/editor/styles/_editor.less
+++ b/src/core_plugins/kibana/public/visualize/editor/styles/_editor.less
@@ -124,7 +124,7 @@
/* Without setting this to 0 you will run into a bug where the filter bar modal is hidden under
a tilemap in an iframe: https://github.com/elastic/kibana/issues/16457 */
- > visualize {
+ > .visualize {
height: 100%;
flex: 1 1 auto;
display: flex;
@@ -419,7 +419,7 @@ a tilemap in an iframe: https://github.com/elastic/kibana/issues/16457 */
flex-basis: 100%;
}
- visualize {
+ .visualize {
.flex-parent();
flex: 1 1 100%;
}
From ccf455e9fdf3fe092abcb988e03e42afd8a5be2d Mon Sep 17 00:00:00 2001
From: Chris Davies
Date: Wed, 5 Sep 2018 11:10:38 -0400
Subject: [PATCH 15/68] Fix #22581 by introducing an artificial delay (#22601)
Introduce a delay into reports to allow visualizations time to appear in the DOM. This is intended as a temporary (and hacky) workaround until we come up with a more robust way to determine that all of the visualizations on the page are ready for capture.
---
.../export_types/printable_pdf/server/lib/screenshots.js | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/screenshots.js b/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/screenshots.js
index 0845ecad65664..57e17f924bb11 100644
--- a/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/screenshots.js
+++ b/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/screenshots.js
@@ -133,7 +133,13 @@ export function screenshotsObservableFactory(server) {
}
}
- return Promise.all(renderedTasks);
+ // The renderComplete fires before the visualizations are in the DOM, so
+ // we wait for the event loop to flush before telling reporting to continue. This
+ // seems to correct a timing issue that was causing reporting to occasionally
+ // capture the first visualization before it was actually in the DOM.
+ const hackyWaitForVisualizations = () => new Promise(r => setTimeout(r, 100));
+
+ return Promise.all(renderedTasks).then(hackyWaitForVisualizations);
},
args: [layout.selectors.renderComplete, captureConfig.loadDelay],
awaitPromise: true,
From b7918690f47a28c123e6f0cd3f331f9be89db944 Mon Sep 17 00:00:00 2001
From: Thomas Neirynck
Date: Wed, 5 Sep 2018 12:14:05 -0400
Subject: [PATCH 16/68] align staging urls with new endpoints (#22691)
---
.../public/__tests__/region_map_visualization.js | 8 ++++----
src/core_plugins/tests_bundle/tests_entry_template.js | 2 +-
.../public/__tests__/coordinate_maps_visualization.js | 2 +-
3 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/src/core_plugins/region_map/public/__tests__/region_map_visualization.js b/src/core_plugins/region_map/public/__tests__/region_map_visualization.js
index 7f3064b99380f..32b9c22839ff7 100644
--- a/src/core_plugins/region_map/public/__tests__/region_map_visualization.js
+++ b/src/core_plugins/region_map/public/__tests__/region_map_visualization.js
@@ -36,9 +36,9 @@ import afterdatachangeandresizePng from './afterdatachangeandresize.png';
import aftercolorchangePng from './aftercolorchange.png';
import changestartupPng from './changestartup.png';
-const manifestUrl = 'https://staging-dot-catalogue-dot-elastic-layer.appspot.com/v1/manifest';
-const tmsManifestUrl = `"https://tiles-maps-stage.elastic.co/v2/manifest`;
-const vectorManifestUrl = `"https://staging-dot-elastic-layer.appspot.com/v1/manifest`;
+const manifestUrl = 'https://catalogue-staging.maps.elastic.co/v2/manifest';
+const tmsManifestUrl = `https://tiles-maps-stage.elastic.co/v2/manifest`;
+const vectorManifestUrl = `https://vector-staging.maps.elastic.co/v2/manifest`;
const manifest = {
'services': [{
'id': 'tiles_v2',
@@ -189,7 +189,7 @@ describe('RegionMapsVisualizationTests', function () {
'attribution': 'Made with NaturalEarth | Elastic Maps Service
',
'name': 'World Countries',
'format': 'geojson',
- 'url': 'https://staging-dot-elastic-layer.appspot.com/blob/5715999101812736?elastic_tile_service_tos=agree&my_app_version=7.0.0-alpha1',
+ 'url': 'https://vector-staging.maps.elastic.co/blob/5715999101812736?elastic_tile_service_tos=agree&my_app_version=7.0.0-alpha1',
'fields': [{ 'name': 'iso2', 'description': 'Two letter abbreviation' }, {
'name': 'iso3',
'description': 'Three letter abbreviation'
diff --git a/src/core_plugins/tests_bundle/tests_entry_template.js b/src/core_plugins/tests_bundle/tests_entry_template.js
index eb015454aaf27..799a25033bef2 100644
--- a/src/core_plugins/tests_bundle/tests_entry_template.js
+++ b/src/core_plugins/tests_bundle/tests_entry_template.js
@@ -60,7 +60,7 @@ const legacyMetadata = {
},
mapConfig: {
includeElasticMapsService: true,
- manifestServiceUrl: 'https://staging-dot-catalogue-dot-elastic-layer.appspot.com/v1/manifest'
+ manifestServiceUrl: 'https://catalogue-staging.maps.elastic.co/v2/manifest'
},
vegaConfig: {
enabled: true,
diff --git a/src/core_plugins/tile_map/public/__tests__/coordinate_maps_visualization.js b/src/core_plugins/tile_map/public/__tests__/coordinate_maps_visualization.js
index b1cd3bfd37c36..426c2c37a08ed 100644
--- a/src/core_plugins/tile_map/public/__tests__/coordinate_maps_visualization.js
+++ b/src/core_plugins/tile_map/public/__tests__/coordinate_maps_visualization.js
@@ -50,7 +50,7 @@ function mockRawData() {
mockRawData();
-const manifestUrl = 'https://staging-dot-catalogue-dot-elastic-layer.appspot.com/v1/manifest';
+const manifestUrl = 'https://catalogue-staging.maps.elastic.co/v2/manifest';
const tmsManifestUrl = `"https://tiles-maps-stage.elastic.co/v2/manifest`;
const manifest = {
'services': [{
From 23ed2135bf2917642651248610ced7b7c724f581 Mon Sep 17 00:00:00 2001
From: James Gowdy
Date: Wed, 5 Sep 2018 17:16:13 +0100
Subject: [PATCH 17/68] [ML] Fixing issue with incorrect timezones in jobs list
(#22714)
* [ML] Fixing issue with incorrect timezones in jobs list
* refactoring min and max calculation
* changes based on review
* changing TimeStamp to Timestamp
---
.../components/job_actions/results.js | 20 +++++++---------
.../components/jobs_list/jobs_list.js | 8 +++++--
.../jobs_list_view/jobs_list_view.js | 2 +-
.../start_datafeed_modal.js | 2 +-
.../ml/server/models/job_service/jobs.js | 24 +++++++------------
5 files changed, 25 insertions(+), 31 deletions(-)
diff --git a/x-pack/plugins/ml/public/jobs/jobs_list/components/job_actions/results.js b/x-pack/plugins/ml/public/jobs/jobs_list/components/job_actions/results.js
index 3565f510645da..05e1db7989077 100644
--- a/x-pack/plugins/ml/public/jobs/jobs_list/components/job_actions/results.js
+++ b/x-pack/plugins/ml/public/jobs/jobs_list/components/job_actions/results.js
@@ -14,6 +14,8 @@ import {
} from '@elastic/eui';
import chrome from 'ui/chrome';
+import moment from 'moment';
+const TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss';
import { mlJobService } from 'plugins/ml/services/job_service';
@@ -21,22 +23,18 @@ function getLink(location, jobs) {
let from = 0;
let to = 0;
if (jobs.length === 1) {
- from = jobs[0].earliestTimeStamp.string;
- to = jobs[0].latestTimeStamp.string;
+ from = jobs[0].earliestTimestampMs;
+ to = jobs[0].latestTimestampMs;
} else {
- const froms = jobs.map(j => j.earliestTimeStamp).sort((a, b) => a.unix > b.unix);
- const tos = jobs.map(j => j.latestTimeStamp).sort((a, b) => a.unix < b.unix);
- from = froms[0].string;
- to = tos[0].string;
+ from = Math.min(...jobs.map(j => j.earliestTimestampMs));
+ to = Math.max(...jobs.map(j => j.latestTimestampMs));
}
- // if either of the dates are empty, set them to undefined
- // moment will convert undefined to now.
- from = (from === '') ? undefined : from;
- to = (to === '') ? undefined : to;
+ const fromString = moment(from).format(TIME_FORMAT);
+ const toString = moment(to).format(TIME_FORMAT);
const jobIds = jobs.map(j => j.id);
- const url = mlJobService.createResultsUrl(jobIds, from, to, location);
+ const url = mlJobService.createResultsUrl(jobIds, fromString, toString, location);
return `${chrome.getBasePath()}/app/${url}`;
}
diff --git a/x-pack/plugins/ml/public/jobs/jobs_list/components/jobs_list/jobs_list.js b/x-pack/plugins/ml/public/jobs/jobs_list/components/jobs_list/jobs_list.js
index 9898cfb0c7518..7687ac372308c 100644
--- a/x-pack/plugins/ml/public/jobs/jobs_list/components/jobs_list/jobs_list.js
+++ b/x-pack/plugins/ml/public/jobs/jobs_list/components/jobs_list/jobs_list.js
@@ -11,6 +11,7 @@ import React, {
} from 'react';
import { sortBy } from 'lodash';
+import moment from 'moment';
import { toLocaleString } from '../../../../util/string_utils';
import { ResultLinks, actionsMenuContent } from '../job_actions';
@@ -25,6 +26,7 @@ import {
const PAGE_SIZE = 10;
const PAGE_SIZE_OPTIONS = [10, 25, 50];
+const TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss';
export class JobsList extends Component {
constructor(props) {
@@ -157,11 +159,13 @@ export class JobsList extends Component {
}, {
name: 'Latest timestamp',
truncateText: false,
- field: 'latestTimeStampUnix',
+ field: 'latestTimeStampSortValue',
sortable: true,
render: (time, item) => (
- { item.latestTimeStamp.string }
+ {
+ (item.latestTimestampMs === undefined) ? '' : moment(item.latestTimestampMs).format(TIME_FORMAT)
+ }
)
}, {
diff --git a/x-pack/plugins/ml/public/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js b/x-pack/plugins/ml/public/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js
index 8205c75da9e33..d5594933cf86a 100644
--- a/x-pack/plugins/ml/public/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js
+++ b/x-pack/plugins/ml/public/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js
@@ -240,7 +240,7 @@ export class JobsListView extends Component {
fullJobsList[job.id] = job.fullJob;
delete job.fullJob;
}
- job.latestTimeStampUnix = job.latestTimeStamp.unix;
+ job.latestTimeStampSortValue = (job.latestTimeStampMs || 0);
return job;
});
const filteredJobsSummaryList = filterJobs(jobsSummaryList, this.state.filterClauses);
diff --git a/x-pack/plugins/ml/public/jobs/jobs_list/components/start_datafeed_modal/start_datafeed_modal.js b/x-pack/plugins/ml/public/jobs/jobs_list/components/start_datafeed_modal/start_datafeed_modal.js
index c22a7aeec1926..cd5a939cbba16 100644
--- a/x-pack/plugins/ml/public/jobs/jobs_list/components/start_datafeed_modal/start_datafeed_modal.js
+++ b/x-pack/plugins/ml/public/jobs/jobs_list/components/start_datafeed_modal/start_datafeed_modal.js
@@ -196,6 +196,6 @@ StartDatafeedModal.propTypes = {
};
function getLowestLatestTime(jobs) {
- const times = jobs.map(j => j.latestTimeStamp.unix.valueOf());
+ const times = jobs.map(j => j.latestTimeStampSortValue);
return moment(Math.min(...times));
}
diff --git a/x-pack/plugins/ml/server/models/job_service/jobs.js b/x-pack/plugins/ml/server/models/job_service/jobs.js
index 6a9da9ff5b75d..2cb43bdabc99a 100644
--- a/x-pack/plugins/ml/server/models/job_service/jobs.js
+++ b/x-pack/plugins/ml/server/models/job_service/jobs.js
@@ -13,8 +13,6 @@ import { fillResultsWithTimeouts, isRequestTimeout } from './error_utils';
import moment from 'moment';
import { uniq } from 'lodash';
-const TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss';
-
export function jobsProvider(callWithRequest) {
const { forceDeleteDatafeed, getDatafeedIdsByJobId } = datafeedsProvider(callWithRequest);
@@ -99,8 +97,8 @@ export function jobsProvider(callWithRequest) {
const jobs = fullJobsList.map((job) => {
const hasDatafeed = (typeof job.datafeed_config === 'object' && Object.keys(job.datafeed_config).length);
const {
- earliest: earliestTimeStamp,
- latest: latestTimeStamp } = earliestAndLatestTimeStamps(job.data_counts);
+ earliest: earliestTimestampMs,
+ latest: latestTimestampMs } = earliestAndLatestTimeStamps(job.data_counts);
const tempJob = {
id: job.job_id,
@@ -112,8 +110,8 @@ export function jobsProvider(callWithRequest) {
hasDatafeed,
datafeedId: (hasDatafeed && job.datafeed_config.datafeed_id) ? job.datafeed_config.datafeed_id : '',
datafeedState: (hasDatafeed && job.datafeed_config.state) ? job.datafeed_config.state : '',
- latestTimeStamp,
- earliestTimeStamp,
+ latestTimestampMs,
+ earliestTimestampMs,
nodeName: (job.node) ? job.node.name : undefined,
};
if (jobIds.find(j => (j === tempJob.id))) {
@@ -243,22 +241,16 @@ export function jobsProvider(callWithRequest) {
function earliestAndLatestTimeStamps(dataCounts) {
const obj = {
- earliest: { string: '', unix: 0 },
- latest: { string: '', unix: 0 },
+ earliest: undefined,
+ latest: undefined,
};
if (dataCounts.earliest_record_timestamp) {
- const ts = moment(dataCounts.earliest_record_timestamp);
- obj.earliest.string = ts.format(TIME_FORMAT);
- obj.earliest.unix = ts.valueOf();
- obj.earliest.moment = ts;
+ obj.earliest = moment(dataCounts.earliest_record_timestamp).valueOf();
}
if (dataCounts.latest_record_timestamp) {
- const ts = moment(dataCounts.latest_record_timestamp);
- obj.latest.string = ts.format(TIME_FORMAT);
- obj.latest.unix = ts.valueOf();
- obj.latest.moment = ts;
+ obj.latest = moment(dataCounts.latest_record_timestamp).valueOf();
}
return obj;
From 865a51de0aa2f0a23fe25de9f48182f7010864f9 Mon Sep 17 00:00:00 2001
From: Nathan Reese
Date: Wed, 5 Sep 2018 13:02:28 -0600
Subject: [PATCH 18/68] Add instructions for running reporting functional tests
to x-pack README (#22683)
* add instructions
* rest of instructions
* pdf-image requirements
* typo
* add Ubutnu commands
---
x-pack/README.md | 30 ++++++++++++++++++++++++++++++
1 file changed, 30 insertions(+)
diff --git a/x-pack/README.md b/x-pack/README.md
index 63722ac16ab10..177a88f6e9f43 100644
--- a/x-pack/README.md
+++ b/x-pack/README.md
@@ -100,6 +100,36 @@ We also have SAML API integration tests which set up Elasticsearch and Kibana wi
node scripts/functional_tests --config test/saml_api_integration/config
```
+#### Running Reporting functional tests
+
+prerequisites:
+The reporting functional tests use [pdf-image](https://www.npmjs.com/package/pdf-image) to convert PDF's pages to png files for image comparisions between generated reports and baseline reports.
+pdf-image requires the system commands `convert`, `gs`, and `pdfinfo` to function. Those can be set up by running the following.
+
+```sh
+//OSX
+brew install imagemagick ghostscript poppler
+
+//Ubutnu
+sudo apt-get install imagemagick ghostscript poppler-utils
+```
+
+To run the reporting functional tests:
+
+Start the test server
+```sh
+// Run from the directory KIBANA_HOME/x-pack
+node scripts/functional_tests_server
+```
+
+Start the test runner
+```sh
+// Run from the directory KIBANA_HOME/x-pack
+node ../scripts/functional_test_runner --config test/reporting/configs/chromium_functional.js
+```
+
+**Note** Configurations from `kibana.dev.yml` are picked up when running the tests. Ensure that `kibana.dev.yml` does not contain any `xpack.reporting` configurations.
+
#### Developing functional tests
If you are **developing functional tests** then you probably don't want to rebuild Elasticsearch and wait for all that setup on every test run, so instead use this command to build and start just the Elasticsearch and Kibana servers:
From f647d6cd5c0c99a46ddc67b39a0f48b5ed23675c Mon Sep 17 00:00:00 2001
From: Pete Harverson
Date: Wed, 5 Sep 2018 20:19:00 +0100
Subject: [PATCH 19/68] [ML] Makefield type icon component keyboard accessible
(#22708)
---
.../__snapshots__/field_type_icon.test.js.snap | 8 ++++----
.../field_type_icon/field_type_icon.js | 18 +++++++++---------
.../field_type_icon/field_type_icon.test.js | 7 ++++---
3 files changed, 17 insertions(+), 16 deletions(-)
diff --git a/x-pack/plugins/ml/public/components/field_type_icon/__snapshots__/field_type_icon.test.js.snap b/x-pack/plugins/ml/public/components/field_type_icon/__snapshots__/field_type_icon.test.js.snap
index f8ff8dd752323..df9367578f0ca 100644
--- a/x-pack/plugins/ml/public/components/field_type_icon/__snapshots__/field_type_icon.test.js.snap
+++ b/x-pack/plugins/ml/public/components/field_type_icon/__snapshots__/field_type_icon.test.js.snap
@@ -2,7 +2,7 @@
exports[`FieldTypeIcon render component when type matches a field type 1`] = `
@@ -10,8 +10,8 @@ exports[`FieldTypeIcon render component when type matches a field type 1`] = `
exports[`FieldTypeIcon update component 1`] = `
`;
diff --git a/x-pack/plugins/ml/public/components/field_type_icon/field_type_icon.js b/x-pack/plugins/ml/public/components/field_type_icon/field_type_icon.js
index 33c2fda04edad..cf23fa1468f7d 100644
--- a/x-pack/plugins/ml/public/components/field_type_icon/field_type_icon.js
+++ b/x-pack/plugins/ml/public/components/field_type_icon/field_type_icon.js
@@ -20,31 +20,31 @@ export function FieldTypeIcon({ tooltipEnabled = false, type }) {
switch (type) {
case ML_JOB_FIELD_TYPES.BOOLEAN:
- ariaLabel = 'Boolean field';
+ ariaLabel = 'boolean type';
iconClass = 'fa-adjust';
break;
case ML_JOB_FIELD_TYPES.DATE:
- ariaLabel = 'Date field';
+ ariaLabel = 'date type';
iconClass = 'fa-clock-o';
break;
case ML_JOB_FIELD_TYPES.NUMBER:
- ariaLabel = 'Metric field';
+ ariaLabel = 'number type';
iconChar = '#';
break;
case ML_JOB_FIELD_TYPES.GEO_POINT:
- ariaLabel = 'Geo-point field';
+ ariaLabel = 'geo_point type';
iconClass = 'fa-globe';
break;
case ML_JOB_FIELD_TYPES.KEYWORD:
- ariaLabel = 'Aggregatable string field';
+ ariaLabel = 'keyword type';
iconChar = 't';
break;
case ML_JOB_FIELD_TYPES.TEXT:
- ariaLabel = 'String field';
+ ariaLabel = 'text type';
iconClass = 'fa-file-text-o';
break;
case ML_JOB_FIELD_TYPES.IP:
- ariaLabel = 'IP address field';
+ ariaLabel = 'IP type';
iconClass = 'fa-laptop';
break;
default:
@@ -69,7 +69,7 @@ export function FieldTypeIcon({ tooltipEnabled = false, type }) {
// to support having another component directly inside the tooltip anchor
// see https://github.com/elastic/eui/issues/839
return (
-
+
);
@@ -86,7 +86,7 @@ FieldTypeIcon.propTypes = {
// To pass on its properties we apply `rest` to the outer `span` element.
function FieldTypeIconContainer({ ariaLabel, className, iconChar, ...rest }) {
return (
-
+
{(iconChar === '') ? (
) : (
diff --git a/x-pack/plugins/ml/public/components/field_type_icon/field_type_icon.test.js b/x-pack/plugins/ml/public/components/field_type_icon/field_type_icon.test.js
index 74432ee964330..975319f942739 100644
--- a/x-pack/plugins/ml/public/components/field_type_icon/field_type_icon.test.js
+++ b/x-pack/plugins/ml/public/components/field_type_icon/field_type_icon.test.js
@@ -8,6 +8,7 @@ import { mount, shallow } from 'enzyme';
import React from 'react';
import { FieldTypeIcon } from './field_type_icon';
+import { ML_JOB_FIELD_TYPES } from '../../../common/constants/field_types';
describe('FieldTypeIcon', () => {
@@ -22,12 +23,12 @@ describe('FieldTypeIcon', () => {
});
test(`render component when type matches a field type`, () => {
- const wrapper = shallow( );
+ const wrapper = shallow( );
expect(wrapper).toMatchSnapshot();
});
test(`render with tooltip and test hovering`, () => {
- const wrapper = mount( );
+ const wrapper = mount( );
const container = wrapper.find({ className: 'field-type-icon-container' });
expect(wrapper.find('EuiToolTip').children()).toHaveLength(1);
@@ -42,7 +43,7 @@ describe('FieldTypeIcon', () => {
test(`update component`, () => {
const wrapper = shallow( );
expect(wrapper.isEmptyRender()).toBeTruthy();
- wrapper.setProps({ type: 'keyword' });
+ wrapper.setProps({ type: ML_JOB_FIELD_TYPES.IP });
expect(wrapper).toMatchSnapshot();
});
From f7fbed34eb777a1f41e2d718b38b10eb4aace021 Mon Sep 17 00:00:00 2001
From: Thomas Watson
Date: Wed, 5 Sep 2018 21:31:03 +0200
Subject: [PATCH 20/68] [APM] Update Node.js onboarding instructions (#22562)
---
.../kibana/server/tutorials/apm/apm_client_instructions.js | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/core_plugins/kibana/server/tutorials/apm/apm_client_instructions.js b/src/core_plugins/kibana/server/tutorials/apm/apm_client_instructions.js
index 49c4b999af18e..183cf26a6679e 100644
--- a/src/core_plugins/kibana/server/tutorials/apm/apm_client_instructions.js
+++ b/src/core_plugins/kibana/server/tutorials/apm/apm_client_instructions.js
@@ -30,10 +30,11 @@ export const createNodeClientInstructions = () => [
textPre:
'Agents are libraries that run inside of your application process.' +
' APM services are created programmatically based on the `serviceName`.' +
- ' This agent supports Express, Koa, hapi, and custom Node.js.',
+ ' This agent supports a vararity of frameworks but can also be used with your custom stack.',
commands: `// Add this to the VERY top of the first file loaded in your app
var apm = require('elastic-apm-node').start({curlyOpen}
- // Set required service name (allowed characters: a-z, A-Z, 0-9, -, _, and space)
+ // Override service name from package.json
+ // Allowed characters: a-z, A-Z, 0-9, -, _, and space
serviceName: '',
// Use if APM Server requires a token
From 2938d94d972f7806dbb25d063effbc3d394fa6b5 Mon Sep 17 00:00:00 2001
From: Rashmi Kulkarni
Date: Wed, 5 Sep 2018 14:19:51 -0700
Subject: [PATCH 21/68] Mgmt saved object test (#22564)
---
.../objects_table/components/flyout/flyout.js | 3 +-
.../apps/management/_import_objects.js | 6 +-
.../management/_mgmt_import_saved_objects.js | 59 ++++
.../exports/mgmt_import_objects.json | 37 +++
test/functional/apps/management/index.js | 1 +
.../fixtures/es_archiver/mgmt/data.json.gz | Bin 0 -> 11964 bytes
.../fixtures/es_archiver/mgmt/mappings.json | 283 ++++++++++++++++++
test/functional/page_objects/settings_page.js | 17 +-
8 files changed, 388 insertions(+), 18 deletions(-)
create mode 100644 test/functional/apps/management/_mgmt_import_saved_objects.js
create mode 100644 test/functional/apps/management/exports/mgmt_import_objects.json
create mode 100644 test/functional/fixtures/es_archiver/mgmt/data.json.gz
create mode 100644 test/functional/fixtures/es_archiver/mgmt/mappings.json
diff --git a/src/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/flyout.js b/src/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/flyout.js
index f99789b1932fe..16e856fb610cc 100644
--- a/src/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/flyout.js
+++ b/src/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/flyout.js
@@ -324,6 +324,7 @@ export class Flyout extends Component {
const options = this.state.indexPatterns.map(indexPattern => ({
text: indexPattern.get('title'),
value: indexPattern.id,
+ ['data-test-subj']: `indexPatternOption-${indexPattern.get('title')}`,
}));
options.unshift({
@@ -333,7 +334,7 @@ export class Flyout extends Component {
return (
this.onIndexChanged(id, e)}
options={options}
/>
diff --git a/test/functional/apps/management/_import_objects.js b/test/functional/apps/management/_import_objects.js
index 16bd12edcb73c..cc2437e2a6378 100644
--- a/test/functional/apps/management/_import_objects.js
+++ b/test/functional/apps/management/_import_objects.js
@@ -53,7 +53,7 @@ export default function ({ getService, getPageObjects }) {
await PageObjects.settings.clickKibanaSavedObjects();
await PageObjects.settings.importFile(path.join(__dirname, 'exports', '_import_objects-conflicts.json'));
await PageObjects.header.waitUntilLoadingHasFinished();
- await PageObjects.settings.setImportIndexFieldOption(2);
+ await PageObjects.settings.associateIndexPattern('d1e4c910-a2e6-11e7-bb30-233be9be6a15', 'logstash-*');
await PageObjects.settings.clickConfirmChanges();
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.settings.clickImportDone();
@@ -71,7 +71,7 @@ export default function ({ getService, getPageObjects }) {
await PageObjects.settings.importFile(path.join(__dirname, 'exports', '_import_objects_exists.json'), false);
await PageObjects.header.waitUntilLoadingHasFinished();
- await PageObjects.settings.setImportIndexFieldOption(2);
+ await PageObjects.settings.associateIndexPattern('logstash-*', 'logstash-*');
await PageObjects.settings.clickConfirmChanges();
// Override the visualization.
@@ -89,7 +89,7 @@ export default function ({ getService, getPageObjects }) {
await PageObjects.settings.importFile(path.join(__dirname, 'exports', '_import_objects_exists.json'), false);
await PageObjects.header.waitUntilLoadingHasFinished();
- await PageObjects.settings.setImportIndexFieldOption(2);
+ await PageObjects.settings.associateIndexPattern('logstash-*', 'logstash-*');
await PageObjects.settings.clickConfirmChanges();
// *Don't* override the visualization.
diff --git a/test/functional/apps/management/_mgmt_import_saved_objects.js b/test/functional/apps/management/_mgmt_import_saved_objects.js
new file mode 100644
index 0000000000000..e2bfd89f07544
--- /dev/null
+++ b/test/functional/apps/management/_mgmt_import_saved_objects.js
@@ -0,0 +1,59 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import expect from 'expect.js';
+import path from 'path';
+
+export default function ({ getService, getPageObjects }) {
+ const esArchiver = getService('esArchiver');
+ const PageObjects = getPageObjects(['common', 'settings', 'header']);
+
+ //in 6.4.0 bug the Saved Search conflict would be resolved and get imported but the visualization
+ //that referenced the saved search was not imported.( https://github.com/elastic/kibana/issues/22238)
+
+ describe('mgmt saved objects', function describeIndexTests() {
+ beforeEach(async function () {
+ await esArchiver.load('discover');
+ await PageObjects.settings.navigateTo();
+ });
+
+ afterEach(async function () {
+ await esArchiver.unload('discover');
+ });
+
+ it('should import saved objects mgmt', async function () {
+
+ await PageObjects.settings.clickKibanaSavedObjects();
+ await PageObjects.settings.importFile(path.join(__dirname, 'exports', 'mgmt_import_objects.json'));
+ await PageObjects.header.waitUntilLoadingHasFinished();
+ await PageObjects.settings.associateIndexPattern('4c3f3c30-ac94-11e8-a651-614b2788174a', 'logstash-*');
+ await PageObjects.settings.clickConfirmChanges();
+ await PageObjects.settings.clickImportDone();
+ await PageObjects.settings.waitUntilSavedObjectsTableIsNotLoading();
+
+ //instead of asserting on count- am asserting on the titles- which is more accurate than count.
+ const objects = await PageObjects.settings.getSavedObjectsInTable();
+ expect(objects.includes('mysavedsearch')).to.be(true);
+ expect(objects.includes('mysavedviz')).to.be(true);
+
+ });
+
+ });
+
+}
diff --git a/test/functional/apps/management/exports/mgmt_import_objects.json b/test/functional/apps/management/exports/mgmt_import_objects.json
new file mode 100644
index 0000000000000..88e03585bf1ee
--- /dev/null
+++ b/test/functional/apps/management/exports/mgmt_import_objects.json
@@ -0,0 +1,37 @@
+[
+ {
+ "_id": "6aea5700-ac94-11e8-a651-614b2788174a",
+ "_type": "search",
+ "_source": {
+ "title": "mysavedsearch",
+ "description": "",
+ "hits": 0,
+ "columns": [
+ "_source"
+ ],
+ "sort": [
+ "@timestamp",
+ "desc"
+ ],
+ "version": 1,
+ "kibanaSavedObjectMeta": {
+ "searchSourceJSON": "{\"index\":\"4c3f3c30-ac94-11e8-a651-614b2788174a\",\"highlightAll\":true,\"version\":true,\"query\":{\"query\":\"\",\"language\":\"lucene\"},\"filter\":[]}"
+ }
+ }
+ },
+ {
+ "_id": "8411daa0-ac94-11e8-a651-614b2788174a",
+ "_type": "visualization",
+ "_source": {
+ "title": "mysavedviz",
+ "visState": "{\"title\":\"mysavedviz\",\"type\":\"pie\",\"params\":{\"type\":\"pie\",\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"isDonut\":true,\"labels\":{\"show\":false,\"values\":true,\"last_level\":true,\"truncate\":100}},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}}]}",
+ "uiStateJSON": "{}",
+ "description": "",
+ "savedSearchId": "6aea5700-ac94-11e8-a651-614b2788174a",
+ "version": 1,
+ "kibanaSavedObjectMeta": {
+ "searchSourceJSON": "{\"query\":{\"query\":\"\",\"language\":\"lucene\"},\"filter\":[]}"
+ }
+ }
+ }
+]
diff --git a/test/functional/apps/management/index.js b/test/functional/apps/management/index.js
index 727462a3fb1b6..29ff8ddb9ad78 100644
--- a/test/functional/apps/management/index.js
+++ b/test/functional/apps/management/index.js
@@ -44,6 +44,7 @@ export default function ({ getService, loadTestFile }) {
loadTestFile(require.resolve('./_index_pattern_filter'));
loadTestFile(require.resolve('./_scripted_fields_filter'));
loadTestFile(require.resolve('./_import_objects'));
+ loadTestFile(require.resolve('./_mgmt_import_saved_objects'));
loadTestFile(require.resolve('./_test_huge_fields'));
loadTestFile(require.resolve('./_handle_alias'));
loadTestFile(require.resolve('./_handle_version_conflict'));
diff --git a/test/functional/fixtures/es_archiver/mgmt/data.json.gz b/test/functional/fixtures/es_archiver/mgmt/data.json.gz
new file mode 100644
index 0000000000000000000000000000000000000000..d923b3a74e474f95d1906db534946609d43f9685
GIT binary patch
literal 11964
zcmV;tE<@2DiwFP!000026YZT*j~h9X=imEN4E%VQPT210nQlJ~aEk@*WgixYGjP`n
zg34q{w3sAKGNqCS{oRj2W=bh3C0FlkpZ_s8gF#zTl~g|kgTV+g81c97{_ux4>EzX$
z4}W;mx%SN;>4k$dBfavs{3Jiswo@G|MsWv-~Z)*zWeFJ`*$Dy^mFs)pWpxQ
z%kNL`zkG6jZ8K`q{o6R?SKs~R=l4JV@$#ci^>Q@nf6QO%-P<2!r#5fDm-79ezkl~m
z{gfZ&zxmV8oAzzr{;8MU`#00ufBkO$@2}te`rUVb`(310-tfJbDXCyTybABDGQRrE
zSBv_`x6NOE{%IlV>yNpTc+$y~KNWX!ui}4uvk-Z&mFXh=@c;bnmp4`p>X$bket9Ew
z_sg3<{!$>z3o!+4x8=3gjW*>M-{!}$4cceS{Ir)QDtbxD(C*~M&>K@2={33C2DO#x
z`qiLh_piYX|Ki&JMFSJ(PxjjB@@N<^01$IeGPqCHOH|_gZj6pQ4yaMJ2`Y|$&dEeolib2fG&6!r(``TGpsD;>WEH)}
z#FwcapiVj;05Xx|hn>>fU4j?q#K}dfN8P0zzEBG$zD%P|>i15V`nx51hQya=Nkc)F
zWdUR$$KUt3^zRpA#=@847!0xyGak4M(@|5d*@ssCu84o1B$tp(#Oy#4v--*^XWPW1u$=U_0Q1q_g3CvDk3lmszm=AcGE`m$+R&+k&i}}R)P_E@
z@hu15J}54z$t7tTs5_reXve5JeAwM_5S#q=cBgJuvW;^_Ns9tnbHQ1t>x5bqAP2t^
zUkjGRB~s;u92qh*F=j}0x>osImyOu(d%N$y1`=J8Zu3dCDK>+QNkMzEGijMZ$e=Mf
zFNy;jan@E(YkfwOX^hqW2(N-zJ#_ts;(UM^Rf~z(I&14Cm)OX7vTY7vT%6}O1Khn*
zUPz-4iVHE#EEWRBGE~=?er(#EHeFCw#8Wo?0|U_PK?Ph)<79_Rhd#)-V@qx(loxkJ
zM_Sjs!<1ZQ`w3mRykKA_ctL91cFSS-ohQ%IxCBocYVj)UP610vP`;w%=_@83+|w-wD+ewTyBk
zVI|&*TrTNA3VB+iJGBk6%ad$6m1e;jWR4VAkU~XbYovpY-Yl{bSg^v6|Ctni{9WvT
zSAqp3Z5zX6p*(`L`S0)m9S5PU$PXj>z!NGL&Y%YAz3PPDvfv#(ylDNlhExJK3b>F`
zx-<*Wx>v}7lp;V5pyiQjCKd*y<-aR+IYDt(H-4~KQ;>bH+j0g3LwESDYD{e}l8(1oux(E1P5zS<3;kuiy>iVQ||mNiPG6Pz`^
zm>;V$(c=?|V(I1;v)clm1v~*BbZmJM$C!8lqn={jAXiN6^{Sk;fg!rsiLQq}RGkp(
zwRzEI!-{o8HSy^VQEFIiA(mu0@XR7Yl-b4PA<~^s>|x>1ym4YnR2O2t9|kxfDqk4m
zP8?TS4xvb}Q$Fob(SX;7La1JJOcCqo>I~P*wEJC~SN
zEPJ6RObG^sZ0!K^q0VgIuKMU}$OPDw_1VnUghyB~xXrB#THOgijuCHJ5tFTM89`HT
zS22NhE!^1dHZo={9NWE0wSpR;Ebll;q`gY*4jqz#Fm+Ocz)A(UMO+qh!xkpg%5w|n
zJX~>uJI2qW%Qh&;0eqkrVKjmAYVi^1E{O}Oj_qwDJ(0drGD6;<1zrHRdi{$XzefcQNcC$mYca%UZH0FE9#bZ2g;b2Td~s#brzu&O1i-t+HMYRr7^ChWOit)v;YpOltBaMU34vC
zzzH3|EJ2Y^T?(i3sATadJ%fQRnDE1VF38w|0vrx?)
zxO`CQs571)oBXd;1}XZ#hfBKMM?k#VZUk#
z5pc(H0f#tE&j}~A#trfX84q@FD<0zTT9v5FVzzsb`!Wl>E+DS73qTcu3%~;OlI|Vi
z0&YsfGi)uajOeOZDZ%xzLZT~WQlXbETH?vf=f(s*VTccVvJfHkL?J-zNsgkIS$1BoQ5z@UvPB;i7*0`|aPVf&z6
z0IC!E3gqa<&~j1@x(h&EAwQ35RLuZx7iwEDbn8%y6zVMHRz<9hzt;fwTA8ktS(vs$
zpqzx)e1o`?p<^idiM!TG);wfRS9TbQy5f>{^PwFiy~KeRE63oPwA94{_S`C8P^~+t
zY;J7pA5qs-TlN3|W4O)RB;X2%aZAASYuQrX}Y%6dj$
zZ!GefstaaD)3j3ao1!<|+@|M*&uD6Lnv%SxBDX2Xbn0=Ma)W6;xlJv~SwLt%_8^U}
zH_imGj;x^j;94pQ(+P?jLdO6fHC&wV%9#@;VJVmO=`hT8VW*^TsI?;Ud|QPcnuxI&)i(ZeNZ84XMtxQ?(R{+o0fxY%Urvc#(?hA*Yb(w2&s=}#~rl0<7?3Is;9n&
z8nDFXL+ts?96d^UgbW=ltw3{#nVw=5zl*zXL2!nC=MVJi7Kvfm9&sEilgA@LU-7C3g4KFxLAFsyvE8*i94*K8%
zTiri@0k#w1>Kh35TuT3n&Di%UutIL#
z;ITH(zs_8aRu~9-my&-^XYyUPB9wRD8+YV^8%4Vj)`?PycR*(jFJe$J$}O4aQaXYG
zF*UGE=$y$pTK`UB(&9ekcSLyVKr2VU=Y-b+qqE($3n%FO0=U2Y7`zt9F3!am6yt9Y
z$CmO@IDP~CsO2#`eB5$}Uj3!{b&iD&-*}fwc$No=zg(;ZC{RScbed=v1yUba<
z*PuIvuRJriak4}A1+Q4)&G}55nZ#b57?93dD>@y7ddZWI8?TQAV
zcE9VIbdwkVqwdlUsW0%ya9%Efd!NF6=S8t6(9DrhM&ykfKws53c~Lm{diHDw3gdGC(BVY*^@KA>|^`1HBKOG)BW^#uF-ou_sI@
z=i?D5E+p;qHNM5v$f-I$TrtcmNRQJK{f5h{y4ehjhiYUh-3SyHUgOmUI>O?lXs_{s
z&e$nA5m)=pfVk^M7_G>Vpk7zjyvV@azQROR#w$o%M?1Q9uYl(?`KlY48Ex0>)k$EF
z`5G+n*i&RB+np*`ia}SMGh`=>bNkWbV)nt=#70r?X|Xl+%sW|D5fmNdpax-tjJ}S}
zh{$RRu9->3J*Gt(i{c5)yWKaL
z(WZu4O#nk8)d*R^>)g>IIz@3sN?VxhkA5-4X=5L=Qu%M)eW5fhB
zn#`fxxR6lw(GA{ND(-e)5C{m7QW#ulHYYD5Tnnv7wA%9EbYtQHn*Yhl8Wl9{<@{Y`
zD+D|3FDJ020`p-D&xyF>Um(e0Ln_^P1qW?}$h8pp)9n^hc-WS4KI_ySDjr3#$Z$xN
zfv)h%0V3wU;(`x)>9`cyIWHll@@0gk?UY6J8D>NVQY`A(q2?5>p#ttdUSXmhBHTmb
zZjqibRi~pb_wTW1IEnQl%ZZ|Kj6P?c%c2q}wh^vHx7cZ7AT1nlO;oEM86e$9-y
zTP!mnYS4wBNqLYBAzEbvi#+69*q=8e4y7$|GJLtRvkN^f^Kl5;UN^pQGcm+-EI
z_BYfy0>kNNQE_gZsOkB)KpA8UB^oFfiGUncdmXenp;37wQ}4on#-(q@@(6nw
zl194Zu?Cr(*DaPNp5+F;Al^};Eru7sI#n90jw&W_NM|}=$jc+tMP`i^S2GukHlA!N
zg7QYUH%rgPIisY-0$yu)iYVAsiQ<5k^@qW1s+>q$VSBxiR+f!t>WyxvgFeydBqM4RqVH0nSKXq@w+Q%0Ve0aFEM-l+g&EjF&dOl6(1
zE~|VMyd?TCW1h-dFj9{Z?!xR9^7-b2j+6bz=7h>lyCJOm#bQqLL)Fr2P%2^z3L35S
zapw|_Q-4JVm5yb+PwjnT%8OEyY)i%=WDP#M5=eL~s@jylD
zruLN7}oYhL}c<^4<22QkmQj|AHle+b-4FI$~XTzEB2rU)A-tkWX(1N$m0Qva>Tuz)8|Ypl~@87H$a
zAtN68KEWO2Cl`EPBYHWS!~&TY?fGUB7mWzinVJi1brhN5@`xL*kXg9m6Y=jEVJ$c$X{iI|HgIpH$#C1a8yiTi5c-F)x}0!asZeus3c^a&_^
z!U-n_=Q(Kv%G-EKT2vKrHg(DycN*x{Cg_T_AoTLyf?_D=0&==Zu~=ELhyBc(S127I
z7pOJb(Q=dT=xJd>ly6DMhuuP~blM)AIjBy!%_r3+I?5Gjse4~z&e2Q91{QQVScjc+
z4%n&0sBtx7P%&a2GR=HRW|JzhRrQK8=E#)+Dx2waCXUMV!f3n4N2P!@#{+|J+Wb*>
z9sxk~dIOAXqhN!;ig(}W5dsd`6qOxm$`z5uxVC;AYtiH(6r;uBqTn?t
z@;2HHDQ-qGfl^&cfzbx&h71ylMWA#CiaxKor0^3fXhc`g@B_LVOxW;a$(eYFGl7c}
z*sYxKkQ3V%h^UV50&((sRR@ZRnF2DW)+*R+#RVaYyzn(%+`RBL+ALXR*wDI7sYk^H
zcg2!Iu$Eer{b&ewHK`7ca;vNgvh<9=onH@tft7Yi47wyN5gFLnRa|5ZIK`xf%N;yK
z4VVxaB;(zoBJU$kuxwH@u6Y@uBBy6ulVa~XUb8Yt(U^dNcP0+bj02OT+zTuG$?OFc
zJTdnU6f`yWjuSLF_reK%4nJW+x%`Kr>D-WwJGszE-8HYlfMv#l??AtfGPYC-GRe5B
zhk{5w?XSr`WkGtJDRB=6cJcKZur@9#Hw^Ovr%h5odk0TYwPgV5#)L@;rjpqRwvvvm
z=H9YH*K8Gx?AGuiFy5uBS2K0`;(!X3wtEYz4jQXZ+AUz7F3nCSXud(ms9*_o%B~pt
zgsG6wQw7KuyG5gSVa1)dglR|bgTsJK&Td_|$w8wrbNFaX4Ig0yx579K(sp3S
zTu(TQhc`xC(r$x0+BwV}C<6z4uxCv8vCht(3JG1D6D7PfjcfNc2nHAE1(b(ODe6=#
zgjz}Ar3&Sz-O+s>RnQ84NuM#{M?0v&9pLBBLOks5HAx
zrId#)m8us}e*sIRyd}NFm9U6ZOnUC&Jd>%Hq-+wDF)NIY
zsmVe^mEs*M{=k=+gpa9hr(U>l;^Ns@WGIGBGi+cX$3fJY1S`kdCkGZUCKu!ud0tNy
zCS;`TuGkE53r?6nZ!|KXQ)YM5kzWMPIYxyp%A*W+c7gCHH?rMtLq_OCZ^pRGq&X^5
zH0$$ZVgy|tC{DyiX2)*eMw~+?tFxp*`Z~RWy6)@13VNAv3|Bno2`gG!>SNNP00OLX
z$_;P!Q-c)R_O96}=>_1`?#IM*xnE%2?!STE8n?+hP|llVR*QoYtwv%vO2#rwOAh_Lw&nUi3n64OJ~0d6|p)0R%Svl`>ZACHKyCmtjIE-`}5w!&ut
zZ^dTpdleXw2Q}n4#Rq-JVU;*&bz}rz6+1a`J_U{(>8GEtKwlL(su`yyQwFF8*gf8U
znG=#@tk0vKlf0XM$Cc_c?2kOL15fND7wj&7N{tYCDfbQcN2c6?DK{JaK1!Xq>Gp#8
zBSYrEkeQc0=O6q#H{g%lfCD#RY#pY+WFwTwJaD--})2zJcN7Nr#QU{jQ
zO!S(K_q;~@ksWbhN6bl&;l1WN+>g9)120@`-K|*=ti0t_nxRKE!i!sT;fO6A6ZfdmegGI
znw5L}5c?xf?7$P7i*B=Vl~<8J@rBiV>}4(L^_Q-%xg|t%7VoIgyUO)IWtSLpF8z0+rrT2i1wBw<GIg9gKlwa!#|>?4`YU0+(S0ZW6TQ}8+~?mE}@-*v{6po
z4@%E!^u`je{vvKn+`qzHFudKX#96Uw-|l||&8pvow`xsY)S2lP9nNBRZn~w-ZjJ!&
zb@_liw$id;WhdN|ZMm>kQ^85PbypD;wiLFe&SSAQFS&8bH_jO)?KdGk#m}bMN~3}l
zb6-O5&8czD;jGxtC=EX4QQOWPkvUrxQqw6PMW^#iwpH-7mqyJDP?HJ=39YGZcCu1+
zAA6xY4yf>BKXaC+Fnfz@q@|+^yv?658JN@BT=I6I#~O5XnD#Z%A?cw)hU|?yiZ+?X
zOWF}c*327J0Tl2(qYbJ(umFun9dB1aONZuaM-#TDmJU13rWbPKLW=mPfpOe{jaSH;DH@C1EjYh+ZVKAb$5VNk`!5h6rKR_`JETN9~$qRS?-if3kl9teh6xG
zOv=_9Lc%Bu4@@D}5*i+O?eY}}CXy|`0-Gw}h#cw5tqRDD3e78f0I55Jj!09Rg)9lQ
z-D%ThZ&&0`LW*D?AeSrnkc^SC*wGxLYr-T$5>W$PP+Z}oT36S2=QE}M*Kj#Z$nU23
zzf5PHPUTK2)n3a>qJUaW&C&HHF%m(37PwYVpwXyM}%RPS&Owbw8y
zEo~zsR{6cBpbqDH?;5Aa##^i8amz5Zz)zb;69-VykH#R=E=GSocfmFZOW;UbN~7TeOuG-U)SCmUxBWWkT6hkdnjFI@@{0=3
z**b@Ko7&uPrx8QvTS}xd)lV=>1^ZH<7g9l2ae*h)nU^Y#8Qmsm$hjvoG$d%~a^Wk|
z60R4M0}Cxp%x$tWUU0{`Hy}P|uM*VB+#}9RM5T+AC?r2pBWadjJa>
z?>(TU5GpCl0XXhtpbn6Hw09zAqP`O?7yX@Z87VM%4s66*U^xi4Bm^|smYK+a6WmXz
zkQCZ5z)b2T0d5FgFl$#(oT7+NATdt_Zz(kc$e6lL9q>7^61CSb(Q<0?Z>Y3WW3AaA
z8|#wpr(9#WJD*>nJ>SFHrX4KuF3|Fg;<)$M(P8Ic+LkbjCUNXm;xahTTlkWTnk`CJFh=h~#G|3wCYD~tFw
zUZfg{YpkBmbZ6MS8X;Z(|0*vBWw-){*9MhcQ*?7$AdxN@twMtHRV>^=rXUHK?*KVV
z)Jwhl(#SEp>TBh1zu}#Od&+JM_dVvJ2ul-Cd%K|kg*fC;GKXk6tQnvenGSM|I@4jUP!wyHI@3)b6j;0ZN03HS16yF!
zzQfw~zN15GuzC%*Z7IPS)P&E3InFgw4`{8Pt~s?UnS-JhVXRx*GSPW^YUE)Hbf4k%
z+`wv)vCFj@NOwTd#Yl|UVyu_wz~xvWvCU&;
z1Rm@`4frlPL6R2?&1hBBvIais1$xLUX^;atXxx?@3MJ6Jj0-9JgooS}DrA(|gB*$%
zKHOk@N{j+(M|I6tvdbE6JN43haw8oK;LnyNG`bO5vcd~CjJAO)p?-}Q9VH$hTce2(
z*B(J0k{7bk-Gt)T_v$OQFA#eo_cA`81Gsm-~dkd6EU|3X2dy>pxyzX%Phz$
zdFG{epE#jHXJ_0H7YCDva4RY&;f_|ed+Uxybz9Z(kn#;P193xcC{1XHcQYy)R5kb;
zHmu|)m7Tao+ulv>F=DgP+dNg0(AJMhY{njDW~{JM2}Pg~{hko6!SD*Bakh-O8xsw=
zLApb^q#YyG%T<;>sKarV7l?kWCu#8%5hb|5J2yE)=Dg2h#p6R3tI?X+{Z7=#|`qBZe5k2~kz?yaUs!M!LS)!W=3Wwqjq}+
zoaMLNz<$Op>MY=P40$1{{UV#FS_axNqiZXCaNGNKhu9J?D$P*b51No*3Y(ktFiv(z
z9km`{hHjIMbCd^mhGRj_$ZjmC`RMX3G$ReELN4lEt{q0KKF_Zrhf1Zm00(t$`CNL(
z8Mke)+Pi-Rw$~wxm`*f>>-)Px{jl7-tR@C>uVIZg>M!UDao(R5(c}pi!Epykd!RgY
zHaMdISGTCbL4}4sUu<-WxQLrkn@-%8<3u8)R$F>2=#nZf&fgyDgRA>K$
zjTm?G9WLSS3!q7y*i0BjOUISChy&7r{yp7jlJ-WuWFjHG#wB&YYsz$wMd%J!55s7o
zSH0AF0aZlrju0#d|BerMIWD!OsdJaKT@-UYY{=O%q0WW>B3{wa=dhnp6Jsi@w)tP1
z|F7Ig&nP!$zwwa?u=9~Bq6^H;x>e$ue*`V#y*kB49W=D&R>&8x1309Yt)gaK5ub9<
z)D{7j)5t_Ae7y^MF(Oa-L2oJDTC|dmAVs}si7QvDQZ@Zx*I(8+tNjEA_2VXv8@vt^
z4>TMHJKv(!84M`N%Fy$nf9)2I-5fI6^^n)5;=4_lFv0i~;6M+Buq+f_J&(E}8j
zQH-uztgA=y6qBM_s6hesC%dLH3h7Gu9b3}vK0?yDqtppjc9jlSdf_vy=rS6t+$@8Sjk1#Ct$6c@o?prRQ4}XrB~o^Wmg}g3a>tfm0oG@3M#w81}Ql|7D7a>;@DXi
zAZNtP2aJZQZG%A4LH>ZKWqikHOp1h(%WImMa4=_dW;)X7H~|>paNYT
z2g{`WIyVpC%F05Cr?dzGZ(D1*6h70f@-m!n*Wl2^fVn&q8D1CG6+Woq)+>CtHYr<8
zEsa>&T0fJ#V(38shgAho$~1A>Iusp4P&+Uf;ld59~+J}8AvC%`2*
z5PbY8JIR^2}t2Sq_Mt(gfumG
zlt`T_CrB!T+Ps5=`Vo-|5(+W%v4EAct9Q6$Kf^d?hZucyK?l~VbaY|gXbUO41zs1N_i*0B0#JqE0i3HDR!aesN4mdqk=EMc_}$N
z!_Codb2Qsrt#iaO
z*h{x7cGNqq0{v0lN+aq|1UG}Y{!QYM=@-$K^AtYQvz)|({N#e5bWu?)8&v>
zO0;z{DHKH9s=7-|=<2%npd1)^4EcfH15629e)kM{L_HqvyJF98uJbK#?c(ep_@E+4G;0B3G=bZa^bE~q@J&SK*Aa?s|4
z$(E-q-RJe!bklPHGVcOfcs}uApG(|Ay6hg<4jWRvfJ+fC^qc^L8eCG1N~RN3K2A7P
zg`7xtE+%+DQL2VOV%F-Y>XngM2OKgn%G5mpLP
z^?Kjzlr(8aRY$$Ev35!XD@5=J6{@|!4dkSRK%LHznr)27xYcDBdW@xZI*^gOXQTHC
zth!ulkfpy~Y+T<{5p&fsU=40eEI{c+-7)}$+LO8Gq$+YM2#-A7OPe<2`Cb!{)Jv;8
zIXK9Wr~_T;&<*=cS?4w|BK(x8cBheTnKL+Np{tPD1IGzS*9bdhk`&N-Igx@J$%%3&
zgWT(&1yn?{Mha@qCpwRf$6>-ya4nA(UlT)&kkRanS5t!oQn%kV&ZxIm8j%%qWWWhM
zb?P%@rA#l#@;k&YD0qkHcPfX1U4DB^6co17%T}k_k_#(g{0S^z$?hk#c)dx!1`Cvt
ze~;Qnq%GfjKBS|gnT`%q%k`-;&4(SFMLGl4sf|onW(X?aqQN4j$yrhYoAD(kuKg~Q
zu=Zx^t|I8C5smPm12sw=WV_Q=K~L-(RHV$E2Z&26w57cP%;~dEU9A#3?4rFqsf)GX
z0F7vBZxIoV?or#$9b-csfOV@V&)yq(5=ipAWmGRZiPm+nfH-C7Z^@8{6=*O07Hz6t
z0~Fk@5GGi-DK!lzE%J+Vd!@Vz&0te#r1!zS=plI@>J
zp)#?pv$kFA74eX9NKH3d?K&
literal 0
HcmV?d00001
diff --git a/test/functional/fixtures/es_archiver/mgmt/mappings.json b/test/functional/fixtures/es_archiver/mgmt/mappings.json
new file mode 100644
index 0000000000000..107a45fab187b
--- /dev/null
+++ b/test/functional/fixtures/es_archiver/mgmt/mappings.json
@@ -0,0 +1,283 @@
+{
+ "type": "index",
+ "value": {
+ "index": ".kibana",
+ "settings": {
+ "index": {
+ "number_of_shards": "1",
+ "auto_expand_replicas": "0-1",
+ "number_of_replicas": "0"
+ }
+ },
+ "mappings": {
+ "doc": {
+ "dynamic": "strict",
+ "properties": {
+ "config": {
+ "dynamic": "true",
+ "properties": {
+ "buildNum": {
+ "type": "keyword"
+ },
+ "defaultIndex": {
+ "type": "text",
+ "fields": {
+ "keyword": {
+ "type": "keyword",
+ "ignore_above": 256
+ }
+ }
+ }
+ }
+ },
+ "dashboard": {
+ "properties": {
+ "description": {
+ "type": "text"
+ },
+ "hits": {
+ "type": "integer"
+ },
+ "kibanaSavedObjectMeta": {
+ "properties": {
+ "searchSourceJSON": {
+ "type": "text"
+ }
+ }
+ },
+ "optionsJSON": {
+ "type": "text"
+ },
+ "panelsJSON": {
+ "type": "text"
+ },
+ "refreshInterval": {
+ "properties": {
+ "display": {
+ "type": "keyword"
+ },
+ "pause": {
+ "type": "boolean"
+ },
+ "section": {
+ "type": "integer"
+ },
+ "value": {
+ "type": "integer"
+ }
+ }
+ },
+ "timeFrom": {
+ "type": "keyword"
+ },
+ "timeRestore": {
+ "type": "boolean"
+ },
+ "timeTo": {
+ "type": "keyword"
+ },
+ "title": {
+ "type": "text"
+ },
+ "uiStateJSON": {
+ "type": "text"
+ },
+ "version": {
+ "type": "integer"
+ }
+ }
+ },
+ "graph-workspace": {
+ "properties": {
+ "description": {
+ "type": "text"
+ },
+ "kibanaSavedObjectMeta": {
+ "properties": {
+ "searchSourceJSON": {
+ "type": "text"
+ }
+ }
+ },
+ "numLinks": {
+ "type": "integer"
+ },
+ "numVertices": {
+ "type": "integer"
+ },
+ "title": {
+ "type": "text"
+ },
+ "version": {
+ "type": "integer"
+ },
+ "wsState": {
+ "type": "text"
+ }
+ }
+ },
+ "index-pattern": {
+ "properties": {
+ "fieldFormatMap": {
+ "type": "text"
+ },
+ "fields": {
+ "type": "text"
+ },
+ "intervalName": {
+ "type": "keyword"
+ },
+ "notExpandable": {
+ "type": "boolean"
+ },
+ "sourceFilters": {
+ "type": "text"
+ },
+ "timeFieldName": {
+ "type": "keyword"
+ },
+ "title": {
+ "type": "text"
+ }
+ }
+ },
+ "search": {
+ "properties": {
+ "columns": {
+ "type": "keyword"
+ },
+ "description": {
+ "type": "text"
+ },
+ "hits": {
+ "type": "integer"
+ },
+ "kibanaSavedObjectMeta": {
+ "properties": {
+ "searchSourceJSON": {
+ "type": "text"
+ }
+ }
+ },
+ "sort": {
+ "type": "keyword"
+ },
+ "title": {
+ "type": "text"
+ },
+ "version": {
+ "type": "integer"
+ }
+ }
+ },
+ "server": {
+ "properties": {
+ "uuid": {
+ "type": "keyword"
+ }
+ }
+ },
+ "timelion-sheet": {
+ "properties": {
+ "description": {
+ "type": "text"
+ },
+ "hits": {
+ "type": "integer"
+ },
+ "kibanaSavedObjectMeta": {
+ "properties": {
+ "searchSourceJSON": {
+ "type": "text"
+ }
+ }
+ },
+ "timelion_chart_height": {
+ "type": "integer"
+ },
+ "timelion_columns": {
+ "type": "integer"
+ },
+ "timelion_interval": {
+ "type": "keyword"
+ },
+ "timelion_other_interval": {
+ "type": "keyword"
+ },
+ "timelion_rows": {
+ "type": "integer"
+ },
+ "timelion_sheet": {
+ "type": "text"
+ },
+ "title": {
+ "type": "text"
+ },
+ "version": {
+ "type": "integer"
+ }
+ }
+ },
+ "type": {
+ "type": "keyword"
+ },
+ "updated_at": {
+ "type": "date"
+ },
+ "url": {
+ "properties": {
+ "accessCount": {
+ "type": "long"
+ },
+ "accessDate": {
+ "type": "date"
+ },
+ "createDate": {
+ "type": "date"
+ },
+ "url": {
+ "type": "text",
+ "fields": {
+ "keyword": {
+ "type": "keyword",
+ "ignore_above": 2048
+ }
+ }
+ }
+ }
+ },
+ "visualization": {
+ "properties": {
+ "description": {
+ "type": "text"
+ },
+ "kibanaSavedObjectMeta": {
+ "properties": {
+ "searchSourceJSON": {
+ "type": "text"
+ }
+ }
+ },
+ "savedSearchId": {
+ "type": "keyword"
+ },
+ "title": {
+ "type": "text"
+ },
+ "uiStateJSON": {
+ "type": "text"
+ },
+ "version": {
+ "type": "integer"
+ },
+ "visState": {
+ "type": "text"
+ }
+ }
+ }
+ }
+ }
+ },
+ "aliases": {}
+ }
+}
\ No newline at end of file
diff --git a/test/functional/page_objects/settings_page.js b/test/functional/page_objects/settings_page.js
index 1f2fc65a1182c..55d64b3ae18a4 100644
--- a/test/functional/page_objects/settings_page.js
+++ b/test/functional/page_objects/settings_page.js
@@ -570,9 +570,10 @@ export function SettingsPageProvider({ getService, getPageObjects }) {
await testSubjects.click('importSavedObjectsConfirmBtn');
}
- async setImportIndexFieldOption(child) {
+ async associateIndexPattern(oldIndexPatternId, newIndexPatternTitle) {
await find.clickByCssSelector(
- `select[data-test-subj="managementChangeIndexSelection"] > option:nth-child(${child})`
+ `select[data-test-subj="managementChangeIndexSelection-${oldIndexPatternId}"] >
+ [data-test-subj="indexPatternOption-${newIndexPatternTitle}"]`
);
}
@@ -580,18 +581,6 @@ export function SettingsPageProvider({ getService, getPageObjects }) {
await testSubjects.click('changeIndexConfirmButton');
}
- async clickVisualizationsTab() {
- await testSubjects.click('objectsTab-visualizations');
- }
-
- async clickSearchesTab() {
- await testSubjects.click('objectsTab-searches');
- }
-
- async getVisualizationRows() {
- return await testSubjects.findAll(`objectsTableRow`);
- }
-
async waitUntilSavedObjectsTableIsNotLoading() {
return retry.try(async () => {
const exists = await find.existsByDisplayedByCssSelector(
From b26e2b46f05e5af099385eda3eef8f77a40dbcc0 Mon Sep 17 00:00:00 2001
From: Nathan Reese
Date: Wed, 5 Sep 2018 15:33:57 -0600
Subject: [PATCH 22/68] Pass scoped context to tutorial providers when building
tutorials (#22260)
* Pass scoped context to tutorial providers when building tutorials
* only generated scoped context one time
* spelling
---
.../routes/api/home/register_tutorials.js | 2 +-
src/ui/tutorials_mixin.js | 28 ++++--
src/ui/tutorials_mixin.test.js | 86 +++++++++++++++++++
3 files changed, 109 insertions(+), 7 deletions(-)
create mode 100644 src/ui/tutorials_mixin.test.js
diff --git a/src/core_plugins/kibana/server/routes/api/home/register_tutorials.js b/src/core_plugins/kibana/server/routes/api/home/register_tutorials.js
index c7e0909631eae..89f1ff6213a14 100644
--- a/src/core_plugins/kibana/server/routes/api/home/register_tutorials.js
+++ b/src/core_plugins/kibana/server/routes/api/home/register_tutorials.js
@@ -23,7 +23,7 @@ export function registerTutorials(server) {
path: '/api/kibana/home/tutorials',
method: ['GET'],
handler: async function (req, reply) {
- reply(server.getTutorials());
+ reply(server.getTutorials(req));
}
});
}
diff --git a/src/ui/tutorials_mixin.js b/src/ui/tutorials_mixin.js
index 7ab14bfc45080..af3663a83b812 100644
--- a/src/ui/tutorials_mixin.js
+++ b/src/ui/tutorials_mixin.js
@@ -17,24 +17,40 @@
* under the License.
*/
-import _ from 'lodash';
import Joi from 'joi';
import { tutorialSchema } from '../core_plugins/kibana/common/tutorials/tutorial_schema';
export function tutorialsMixin(kbnServer, server) {
- const tutorials = [];
+ const tutorialProviders = [];
+ const scopedTutorialContextFactories = [];
- server.decorate('server', 'getTutorials', () => {
- return _.cloneDeep(tutorials);
+ server.decorate('server', 'getTutorials', (request) => {
+ const initialContext = {};
+ const scopedContext = scopedTutorialContextFactories.reduce((accumulatedContext, contextFactory) => {
+ return { ...accumulatedContext, ...contextFactory(request) };
+ }, initialContext);
+
+ return tutorialProviders.map((tutorialProvider) => {
+ return tutorialProvider(server, scopedContext);
+ });
});
server.decorate('server', 'registerTutorial', (specProvider) => {
- const { error, value } = Joi.validate(specProvider(server), tutorialSchema);
+ const emptyContext = {};
+ const { error } = Joi.validate(specProvider(server, emptyContext), tutorialSchema);
if (error) {
throw new Error(`Unable to register tutorial spec because its invalid. ${error}`);
}
- tutorials.push(value);
+ tutorialProviders.push(specProvider);
+ });
+
+ server.decorate('server', 'addScopedTutorialContextFactory', (scopedTutorialContextFactory) => {
+ if (typeof scopedTutorialContextFactory !== 'function') {
+ throw new Error(`Unable to add scoped(request) context factory because you did not provide a function`);
+ }
+
+ scopedTutorialContextFactories.push(scopedTutorialContextFactory);
});
}
diff --git a/src/ui/tutorials_mixin.test.js b/src/ui/tutorials_mixin.test.js
new file mode 100644
index 0000000000000..b29ff10a4d798
--- /dev/null
+++ b/src/ui/tutorials_mixin.test.js
@@ -0,0 +1,86 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { createServer } from '../test_utils/kbn_server';
+
+const validTutorial = {
+ id: 'spec1',
+ category: 'other',
+ name: 'spec1',
+ shortDescription: 'short description',
+ longDescription: 'long description',
+ onPrem: {
+ instructionSets: [
+ {
+ instructionVariants: [
+ {
+ id: 'instructionVariant1',
+ instructions: [
+ {}
+ ]
+ }
+ ]
+ }
+ ]
+ }
+};
+
+describe('tutorial mixins', () => {
+
+ let kbnServer;
+ beforeEach(async () => {
+ kbnServer = createServer();
+ await kbnServer.ready();
+ });
+
+ afterEach(async () => {
+ await kbnServer.close();
+ });
+
+ describe('scoped context', () => {
+
+ const mockRequest = {};
+ const spacesContextFactory = (request) => {
+ if (request !== mockRequest) {
+ throw new Error('context factory not called with request object');
+ }
+ return {
+ spaceId: 'my-space'
+ };
+ };
+ const specProvider = (server, context) => {
+ const tutorial = { ...validTutorial };
+ tutorial.shortDescription = `I have been provided with scoped context, spaceId: ${context.spaceId}`;
+ return tutorial;
+ };
+ beforeEach(async () => {
+ kbnServer.server.addScopedTutorialContextFactory(spacesContextFactory);
+ kbnServer.server.registerTutorial(specProvider);
+ });
+
+ test('passes scoped context to specProviders', () => {
+ const tutorials = kbnServer.server.getTutorials(mockRequest);
+ expect(tutorials.length).toBe(1);
+ expect(tutorials[0].shortDescription).toBe('I have been provided with scoped context, spaceId: my-space');
+ });
+ });
+
+});
+
+
From 97fccac8d13de3e6f55be1aa20e52b35da59537c Mon Sep 17 00:00:00 2001
From: Caroline Horn <549577+cchaos@users.noreply.github.com>
Date: Wed, 5 Sep 2018 19:49:18 -0400
Subject: [PATCH 23/68] Fix IE scrollbar issue on TSVB gauges in dashboard
(#22740)
---
.../metrics/public/visualizations/components/gauge_vis.js | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/core_plugins/metrics/public/visualizations/components/gauge_vis.js b/src/core_plugins/metrics/public/visualizations/components/gauge_vis.js
index 7f129d3e061a1..bb0e205e87393 100644
--- a/src/core_plugins/metrics/public/visualizations/components/gauge_vis.js
+++ b/src/core_plugins/metrics/public/visualizations/components/gauge_vis.js
@@ -85,7 +85,8 @@ class GaugeVis extends Component {
position: 'relative',
display: 'flex',
rowDirection: 'column',
- flex: '1 0 auto'
+ flex: '1 0 auto',
+ overflow: 'hidden', // Fixes IE scrollbars issue
},
svg: {
position: 'absolute',
From eeee0d800ae1601254af94cbbb3106b207b91c65 Mon Sep 17 00:00:00 2001
From: Lee Drengenberg
Date: Wed, 5 Sep 2018 19:54:38 -0500
Subject: [PATCH 24/68] x-pack tests should use servers from other config files
already loaded (#22739)
* x-pack tests should use servers from other config files already loaded
* Fix es_test_config that was using TEST_KIBANA_USERNAME/PASSWORD
---
packages/kbn-test/src/es/es_test_config.js | 5 +++--
x-pack/test/functional/config.js | 24 +---------------------
2 files changed, 4 insertions(+), 25 deletions(-)
diff --git a/packages/kbn-test/src/es/es_test_config.js b/packages/kbn-test/src/es/es_test_config.js
index 2e9e91e50b4b2..e273172ef614b 100644
--- a/packages/kbn-test/src/es/es_test_config.js
+++ b/packages/kbn-test/src/es/es_test_config.js
@@ -53,8 +53,9 @@ export const esTestConfig = new class EsTestConfig {
};
}
- const username = process.env.TEST_KIBANA_USERNAME || adminTestUser.username;
- const password = process.env.TEST_KIBANA_PASSWORD || adminTestUser.password;
+ const username = process.env.TEST_ES_USERNAME || adminTestUser.username;
+ const password = process.env.TEST_ES_PASSWORD || adminTestUser.password;
+
return {
// Allow setting any individual component(s) of the URL,
// or use default values (username and password from ../kbn/users.js)
diff --git a/x-pack/test/functional/config.js b/x-pack/test/functional/config.js
index 5f4cb60fa81e8..77c0aee49b83e 100644
--- a/x-pack/test/functional/config.js
+++ b/x-pack/test/functional/config.js
@@ -7,7 +7,6 @@
/* eslint-disable kibana-custom/no-default-export */
import { resolve } from 'path';
-import { format as formatUrl } from 'url';
import {
SecurityPageProvider,
@@ -57,25 +56,6 @@ export default async function ({ readConfigFile }) {
const kibanaFunctionalConfig = await readConfigFile(require.resolve('../../../test/functional/config.js'));
const kibanaAPITestsConfig = await readConfigFile(require.resolve('../../../test/api_integration/config.js'));
- const servers = {
- elasticsearch: {
- protocol: process.env.TEST_ES_PROTOCOL || 'http',
- hostname: process.env.TEST_ES_HOSTNAME || 'localhost',
- port: parseInt(process.env.TEST_ES_PORT, 10) || 9240,
- auth: 'elastic:changeme',
- username: 'elastic',
- password: 'changeme',
- },
- kibana: {
- protocol: process.env.TEST_KIBANA_PROTOCOL || 'http',
- hostname: process.env.TEST_KIBANA_HOSTNAME || 'localhost',
- port: parseInt(process.env.TEST_KIBANA_PORT, 10) || 5640,
- auth: 'elastic:changeme',
- username: 'elastic',
- password: 'changeme',
- },
- };
-
return {
// list paths to the files that contain your plugins tests
testFiles: [
@@ -135,7 +115,7 @@ export default async function ({ readConfigFile }) {
reporting: ReportingPageProvider,
},
- servers,
+ servers: kibanaFunctionalConfig.get('servers'),
esTestCluster: {
license: 'trial',
@@ -151,8 +131,6 @@ export default async function ({ readConfigFile }) {
serverArgs: [
...kibanaCommonConfig.get('kbnTestServer.serverArgs'),
'--server.uuid=5b2de169-2785-441b-ae8c-186a1936b17d',
- `--server.port=${servers.kibana.port}`,
- `--elasticsearch.url=${formatUrl(servers.elasticsearch)}`,
'--xpack.xpack_main.telemetry.enabled=false',
'--xpack.security.encryptionKey="wuGNaIhoMpk5sO4UBxgr3NyW1sFcLgIf"', // server restarts should not invalidate active sessions
],
From 8d8513ca6cfeb7551e62ca2b84e7bcc06bb64095 Mon Sep 17 00:00:00 2001
From: Aliaksandr Yankouski
Date: Thu, 6 Sep 2018 09:55:29 +0300
Subject: [PATCH 25/68] Translations for tutorial common (#22071)
* filebeat_instructions translations
* tutorials common
* fix translations
* use unicode code
* folder path to variable
* Fix message id
* Remove disabling no-multi-str rule, remove spaces
---
.../common/tutorials/filebeat_instructions.js | 467 +++++++++++++-----
.../common/tutorials/logstash_instructions.js | 53 +-
.../tutorials/metricbeat_instructions.js | 428 +++++++++++-----
.../tutorials/onprem_cloud_instructions.js | 47 +-
4 files changed, 718 insertions(+), 277 deletions(-)
diff --git a/src/core_plugins/kibana/common/tutorials/filebeat_instructions.js b/src/core_plugins/kibana/common/tutorials/filebeat_instructions.js
index 692074d2ce897..b96f1f6b1b7d0 100644
--- a/src/core_plugins/kibana/common/tutorials/filebeat_instructions.js
+++ b/src/core_plugins/kibana/common/tutorials/filebeat_instructions.js
@@ -17,16 +17,22 @@
* under the License.
*/
+import { i18n } from '@kbn/i18n';
import { INSTRUCTION_VARIANT } from './instruction_variant';
import { createTrycloudOption1, createTrycloudOption2 } from './onprem_cloud_instructions';
export const createFilebeatInstructions = () => ({
INSTALL: {
OSX: {
- title: 'Download and install Filebeat',
- textPre:
- 'First time using Filebeat? See the [Getting Started Guide]' +
- '({config.docs.beats.filebeat}/filebeat-getting-started.html).',
+ title: i18n.translate('kbn.common.tutorials.filebeatInstructions.install.osxTitle', {
+ defaultMessage: 'Download and install Filebeat',
+ }),
+ textPre: i18n.translate('kbn.common.tutorials.filebeatInstructions.install.osxTextPre', {
+ defaultMessage: 'First time using Filebeat? See the [Getting Started Guide]({linkUrl}).',
+ values: {
+ linkUrl: '{config.docs.beats.filebeat}/filebeat-getting-started.html',
+ },
+ }),
commands: [
'curl -L -O https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-{config.kibana.version}-darwin-x86_64.tar.gz',
'tar xzvf filebeat-{config.kibana.version}-darwin-x86_64.tar.gz',
@@ -34,76 +40,123 @@ export const createFilebeatInstructions = () => ({
],
},
DEB: {
- title: 'Download and install Filebeat',
- textPre:
- 'First time using Filebeat? See the [Getting Started Guide]' +
- '({config.docs.beats.filebeat}/filebeat-getting-started.html).',
+ title: i18n.translate('kbn.common.tutorials.filebeatInstructions.install.debTitle', {
+ defaultMessage: 'Download and install Filebeat',
+ }),
+ textPre: i18n.translate('kbn.common.tutorials.filebeatInstructions.install.debTextPre', {
+ defaultMessage: 'First time using Filebeat? See the [Getting Started Guide]({linkUrl}).',
+ values: {
+ linkUrl: '{config.docs.beats.filebeat}/filebeat-getting-started.html',
+ },
+ }),
commands: [
'curl -L -O https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-{config.kibana.version}-amd64.deb',
'sudo dpkg -i filebeat-{config.kibana.version}-amd64.deb',
],
- textPost:
- 'Looking for the 32-bit packages? See the [Download page](https://www.elastic.co/downloads/beats/filebeat).',
+ textPost: i18n.translate('kbn.common.tutorials.filebeatInstructions.install.debTextPost', {
+ defaultMessage: 'Looking for the 32-bit packages? See the [Download page]({linkUrl}).',
+ values: {
+ linkUrl: 'https://www.elastic.co/downloads/beats/filebeat',
+ },
+ }),
},
RPM: {
- title: 'Download and install Filebeat',
- textPre:
- 'First time using Filebeat? See the [Getting Started Guide]' +
- '({config.docs.beats.filebeat}/filebeat-getting-started.html).',
+ title: i18n.translate('kbn.common.tutorials.filebeatInstructions.install.rpmTitle', {
+ defaultMessage: 'Download and install Filebeat',
+ }),
+ textPre: i18n.translate('kbn.common.tutorials.filebeatInstructions.install.rpmTextPre', {
+ defaultMessage: 'First time using Filebeat? See the [Getting Started Guide]({linkUrl}).',
+ values: {
+ linkUrl: '{config.docs.beats.filebeat}/filebeat-getting-started.html',
+ },
+ }),
commands: [
'curl -L -O https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-{config.kibana.version}-x86_64.rpm',
'sudo rpm -vi filebeat-{config.kibana.version}-x86_64.rpm',
],
- textPost:
- 'Looking for the 32-bit packages? See the [Download page](https://www.elastic.co/downloads/beats/filebeat).',
+ textPost: i18n.translate('kbn.common.tutorials.filebeatInstructions.install.rpmTextPost', {
+ defaultMessage: 'Looking for the 32-bit packages? See the [Download page]({linkUrl}).',
+ values: {
+ linkUrl: 'https://www.elastic.co/downloads/beats/filebeat',
+ },
+ }),
},
WINDOWS: {
- title: 'Download and install Filebeat',
- textPre:
- 'First time using Filebeat? See the [Getting Started Guide]' +
- '({config.docs.beats.filebeat}/filebeat-getting-started.html).\n' +
- '1. Download the Filebeat Windows zip file from the [Download](https://www.elastic.co/downloads/beats/filebeat) page.\n' +
- '2. Extract the contents of the zip file into `C:\\Program Files`.\n' +
- '3. Rename the `filebeat-{config.kibana.version}-windows` directory to `Filebeat`.\n' +
- '4. Open a PowerShell prompt as an Administrator (right-click the PowerShell icon and select' +
- ' **Run As Administrator**). If you are running Windows XP, you might need to download and install PowerShell.\n' +
- '5. From the PowerShell prompt, run the following commands to install Filebeat as a Windows service.',
+ title: i18n.translate('kbn.common.tutorials.filebeatInstructions.install.windowsTitle', {
+ defaultMessage: 'Download and install Filebeat',
+ }),
+ textPre: i18n.translate('kbn.common.tutorials.filebeatInstructions.install.windowsTextPre', {
+ defaultMessage: 'First time using Filebeat? See the [Getting Started Guide]({guideLinkUrl}).\n\
+ 1. Download the Filebeat Windows zip file from the [Download]({filebeatLinkUrl}) page.\n\
+ 2. Extract the contents of the zip file into {folderPath}.\n\
+ 3. Rename the `{directoryName}` directory to `Filebeat`.\n\
+ 4. Open a PowerShell prompt as an Administrator (right-click the PowerShell icon and select \
+**Run As Administrator**). If you are running Windows XP, you might need to download and install PowerShell.\n\
+ 5. From the PowerShell prompt, run the following commands to install Filebeat as a Windows service.',
+ values: {
+ folderPath: '`C:\\Program Files`',
+ guideLinkUrl: '{config.docs.beats.filebeat}/filebeat-getting-started.html',
+ filebeatLinkUrl: 'https://www.elastic.co/downloads/beats/filebeat',
+ directoryName: 'filebeat-{config.kibana.version}-windows',
+ }
+ }),
commands: [
'PS > cd C:\\Program Files\\Filebeat',
'PS C:\\Program Files\\Filebeat> .\\install-service-filebeat.ps1',
],
- textPost:
- 'Modify the settings under `output.elasticsearch` in the ' +
- '`C:\\Program Files\\Filebeat\\filebeat.yml` file to point to your Elasticsearch installation.',
- },
+ textPost: i18n.translate('kbn.common.tutorials.filebeatInstructions.install.windowsTextPost', {
+ defaultMessage: 'Modify the settings under {propertyName} in the {filebeatPath} file to point to your Elasticsearch installation.',
+ values: {
+ propertyName: '`output.elasticsearch`',
+ filebeatPath: '`C:\\Program Files\\Filebeat\\filebeat.yml`',
+ }
+ }),
+ }
},
START: {
OSX: {
- title: 'Start Filebeat',
- textPre:
- 'The `setup` command loads the Kibana dashboards.' +
- ' If the dashboards are already set up, omit this command.',
- commands: ['./filebeat setup', './filebeat -e'],
+ title: i18n.translate('kbn.common.tutorials.filebeatInstructions.start.osxTitle', {
+ defaultMessage: 'Start Filebeat',
+ }),
+ textPre: i18n.translate('kbn.common.tutorials.filebeatInstructions.start.osxTextPre', {
+ defaultMessage: 'The `setup` command loads the Kibana dashboards. If the dashboards are already set up, omit this command.',
+ }),
+ commands: [
+ './filebeat setup',
+ './filebeat -e',
+ ]
},
DEB: {
- title: 'Start Filebeat',
- textPre:
- 'The `setup` command loads the Kibana dashboards. If the dashboards are already set up, ' +
- 'omit this command.',
- commands: ['sudo filebeat setup', 'sudo service filebeat start'],
+ title: i18n.translate('kbn.common.tutorials.filebeatInstructions.start.debTitle', {
+ defaultMessage: 'Start Filebeat',
+ }),
+ textPre: i18n.translate('kbn.common.tutorials.filebeatInstructions.start.debTextPre', {
+ defaultMessage: 'The `setup` command loads the Kibana dashboards. If the dashboards are already set up, omit this command.',
+ }),
+ commands: [
+ 'sudo filebeat setup',
+ 'sudo service filebeat start',
+ ]
},
RPM: {
- title: 'Start Filebeat',
- textPre:
- 'The `setup` command loads the Kibana dashboards. If the dashboards are already set up, ' +
- 'omit this command.',
- commands: ['sudo filebeat setup', 'sudo service filebeat start'],
+ title: i18n.translate('kbn.common.tutorials.filebeatInstructions.start.rpmTitle', {
+ defaultMessage: 'Start Filebeat',
+ }),
+ textPre: i18n.translate('kbn.common.tutorials.filebeatInstructions.start.rpmTextPre', {
+ defaultMessage: 'The `setup` command loads the Kibana dashboards. If the dashboards are already set up, omit this command.',
+ }),
+ commands: [
+ 'sudo filebeat setup',
+ 'sudo service filebeat start',
+ ],
},
WINDOWS: {
- title: 'Start Filebeat',
- textPre:
- 'The `setup` command loads the Kibana dashboards. If the dashboards are already set up, ' +
- 'omit this command.',
+ title: i18n.translate('kbn.common.tutorials.filebeatInstructions.start.windowsTitle', {
+ defaultMessage: 'Start Filebeat',
+ }),
+ textPre: i18n.translate('kbn.common.tutorials.filebeatInstructions.start.windowsTextPre', {
+ defaultMessage: 'The `setup` command loads the Kibana dashboards. If the dashboards are already set up, omit this command.',
+ }),
commands: [
'PS C:\\Program Files\\Filebeat> filebeat.exe setup',
'PS C:\\Program Files\\Filebeat> Start-Service filebeat',
@@ -112,8 +165,15 @@ export const createFilebeatInstructions = () => ({
},
CONFIG: {
OSX: {
- title: 'Edit the configuration',
- textPre: 'Modify `filebeat.yml` to set the connection information:',
+ title: i18n.translate('kbn.common.tutorials.filebeatInstructions.config.osxTitle', {
+ defaultMessage: 'Edit the configuration',
+ }),
+ textPre: i18n.translate('kbn.common.tutorials.filebeatInstructions.config.osxTextPre', {
+ defaultMessage: 'Modify {path} to set the connection information:',
+ values: {
+ path: '`filebeat.yml`',
+ },
+ }),
commands: [
'output.elasticsearch:',
' hosts: [""]',
@@ -122,13 +182,26 @@ export const createFilebeatInstructions = () => ({
'setup.kibana:',
' host: ""',
],
- textPost:
- 'Where `` is the password of the `elastic` user, ' +
- '`` is the URL of Elasticsearch, and `` is the URL of Kibana.',
+ textPost: i18n.translate('kbn.common.tutorials.filebeatInstructions.config.osxTextPost', {
+ defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user, {esUrlTemplate} is the URL of Elasticsearch, \
+and {kibanaUrlTemplate} is the URL of Kibana.',
+ values: {
+ passwordTemplate: '``',
+ esUrlTemplate: '``',
+ kibanaUrlTemplate: '``',
+ },
+ }),
},
DEB: {
- title: 'Edit the configuration',
- textPre: 'Modify `/etc/filebeat/filebeat.yml` to set the connection information:',
+ title: i18n.translate('kbn.common.tutorials.filebeatInstructions.config.debTitle', {
+ defaultMessage: 'Edit the configuration',
+ }),
+ textPre: i18n.translate('kbn.common.tutorials.filebeatInstructions.config.debTextPre', {
+ defaultMessage: 'Modify {path} to set the connection information:',
+ values: {
+ path: '`/etc/filebeat/filebeat.yml`',
+ },
+ }),
commands: [
'output.elasticsearch:',
' hosts: [""]',
@@ -137,13 +210,26 @@ export const createFilebeatInstructions = () => ({
'setup.kibana:',
' host: ""',
],
- textPost:
- 'Where `` is the password of the `elastic` user, ' +
- '`` is the URL of Elasticsearch, and `` is the URL of Kibana.',
+ textPost: i18n.translate('kbn.common.tutorials.filebeatInstructions.config.debTextPost', {
+ defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user, {esUrlTemplate} is the URL of Elasticsearch, \
+and {kibanaUrlTemplate} is the URL of Kibana.',
+ values: {
+ passwordTemplate: '``',
+ esUrlTemplate: '``',
+ kibanaUrlTemplate: '``',
+ },
+ }),
},
RPM: {
- title: 'Edit the configuration',
- textPre: 'Modify `/etc/filebeat/filebeat.yml` to set the connection information:',
+ title: i18n.translate('kbn.common.tutorials.filebeatInstructions.config.rpmTitle', {
+ defaultMessage: 'Edit the configuration',
+ }),
+ textPre: i18n.translate('kbn.common.tutorials.filebeatInstructions.config.rpmTextPre', {
+ defaultMessage: 'Modify {path} to set the connection information:',
+ values: {
+ path: '`/etc/filebeat/filebeat.yml`',
+ },
+ }),
commands: [
'output.elasticsearch:',
' hosts: [""]',
@@ -152,14 +238,26 @@ export const createFilebeatInstructions = () => ({
'setup.kibana:',
' host: ""',
],
- textPost:
- 'Where `` is the password of the `elastic` user, ' +
- '`` is the URL of Elasticsearch, and `` is the URL of Kibana.',
+ textPost: i18n.translate('kbn.common.tutorials.filebeatInstructions.config.rpmTextPost', {
+ defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user, {esUrlTemplate} is the URL of Elasticsearch, \
+and {kibanaUrlTemplate} is the URL of Kibana.',
+ values: {
+ passwordTemplate: '``',
+ esUrlTemplate: '``',
+ kibanaUrlTemplate: '``',
+ },
+ }),
},
WINDOWS: {
- title: 'Edit the configuration',
- textPre:
- 'Modify `C:\\Program Files\\Filebeat\\filebeat.yml` to set the connection information:',
+ title: i18n.translate('kbn.common.tutorials.filebeatInstructions.config.windowsTitle', {
+ defaultMessage: 'Edit the configuration',
+ }),
+ textPre: i18n.translate('kbn.common.tutorials.filebeatInstructions.config.windowsTextPre', {
+ defaultMessage: 'Modify {path} to set the connection information:',
+ values: {
+ path: '`C:\\Program Files\\Filebeat\\filebeat.yml`',
+ },
+ }),
commands: [
'output.elasticsearch:',
' hosts: [""]',
@@ -168,98 +266,209 @@ export const createFilebeatInstructions = () => ({
'setup.kibana:',
' host: ""',
],
- textPost:
- 'Where `` is the password of the `elastic` user, ' +
- '`` is the URL of Elasticsearch, and `` is the URL of Kibana.',
- },
+ textPost: i18n.translate('kbn.common.tutorials.filebeatInstructions.config.windowsTextPost', {
+ defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user, {esUrlTemplate} is the URL of Elasticsearch, \
+and {kibanaUrlTemplate} is the URL of Kibana.',
+ values: {
+ passwordTemplate: '``',
+ esUrlTemplate: '``',
+ kibanaUrlTemplate: '``',
+ },
+ }),
+ }
},
PLUGINS: {
GEOIP_AND_UA: {
- title: 'Install Elasticsearch GeoIP and user agent plugins',
- textPre:
- 'This module requires two Elasticsearch plugins that are not ' +
- 'installed by default.\n\nFrom the Elasticsearch installation folder, run:',
+ title: i18n.translate('kbn.common.tutorials.filebeatInstructions.plugins.geoipUaTitle', {
+ defaultMessage: 'Install Elasticsearch GeoIP and user agent plugins',
+ }),
+ textPre: i18n.translate('kbn.common.tutorials.filebeatInstructions.plugins.geoipUaTextPre', {
+ defaultMessage: 'This module requires two Elasticsearch plugins that are not installed by default.\n\n\
+From the Elasticsearch installation folder, run:',
+ }),
commands: [
'bin/elasticsearch-plugin install ingest-geoip',
'bin/elasticsearch-plugin install ingest-user-agent',
],
},
GEOIP: {
- title: 'Install Elasticsearch GeoIP plugin',
- textPre:
- 'This module requires an Elasticsearch plugin that is not ' +
- 'installed by default.\n\nFrom the Elasticsearch installation folder, run:',
- commands: ['bin/elasticsearch-plugin install ingest-geoip'],
- },
- },
+ title: i18n.translate('kbn.common.tutorials.filebeatInstructions.plugins.geoipTitle', {
+ defaultMessage: 'Install Elasticsearch GeoIP plugin',
+ }),
+ textPre: i18n.translate('kbn.common.tutorials.filebeatInstructions.plugins.geoipTextPre', {
+ defaultMessage: 'This module requires two Elasticsearch plugins that are not installed by default.\n\n\
+From the Elasticsearch installation folder, run:',
+ }),
+ commands: [
+ 'bin/elasticsearch-plugin install ingest-geoip'
+ ]
+ }
+ }
});
export const createFilebeatCloudInstructions = () => ({
CONFIG: {
OSX: {
- title: 'Edit the configuration',
- textPre: 'Modify `filebeat.yml` to set the connection information for Elastic Cloud:',
- commands: ['cloud.id: "{config.cloud.id}"', 'cloud.auth: "elastic:"'],
- textPost: 'Where `` is the password of the `elastic` user.',
+ title: i18n.translate('kbn.common.tutorials.filebeatCloudInstructions.config.osxTitle', {
+ defaultMessage: 'Edit the configuration',
+ }),
+ textPre: i18n.translate('kbn.common.tutorials.filebeatCloudInstructions.config.osxTextPre', {
+ defaultMessage: 'Modify {path} to set the connection information for Elastic Cloud:',
+ values: {
+ path: '`filebeat.yml`',
+ },
+ }),
+ commands: [
+ 'cloud.id: "{config.cloud.id}"',
+ 'cloud.auth: "elastic:"'
+ ],
+ textPost: i18n.translate('kbn.common.tutorials.filebeatCloudInstructions.config.osxTextPost', {
+ defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user.',
+ values: { passwordTemplate: '``' },
+ }),
},
DEB: {
- title: 'Edit the configuration',
- textPre:
- 'Modify `/etc/filebeat/filebeat.yml` to set the connection information for Elastic Cloud:',
- commands: ['cloud.id: "{config.cloud.id}"', 'cloud.auth: "elastic:"'],
- textPost: 'Where `` is the password of the `elastic` user.',
+ title: i18n.translate('kbn.common.tutorials.filebeatCloudInstructions.config.debTitle', {
+ defaultMessage: 'Edit the configuration',
+ }),
+ textPre: i18n.translate('kbn.common.tutorials.filebeatCloudInstructions.config.debTextPre', {
+ defaultMessage: 'Modify {path} to set the connection information for Elastic Cloud:',
+ values: {
+ path: '`/etc/filebeat/filebeat.yml`',
+ },
+ }),
+ commands: [
+ 'cloud.id: "{config.cloud.id}"',
+ 'cloud.auth: "elastic:"'
+ ],
+ textPost: i18n.translate('kbn.common.tutorials.filebeatCloudInstructions.config.debTextPost', {
+ defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user.',
+ values: { passwordTemplate: '``' },
+ }),
},
RPM: {
- title: 'Edit the configuration',
- textPre:
- 'Modify `/etc/filebeat/filebeat.yml` to set the connection information for Elastic Cloud:',
- commands: ['cloud.id: "{config.cloud.id}"', 'cloud.auth: "elastic:"'],
- textPost: 'Where `` is the password of the `elastic` user.',
+ title: i18n.translate('kbn.common.tutorials.filebeatCloudInstructions.config.rpmTitle', {
+ defaultMessage: 'Edit the configuration',
+ }),
+ textPre: i18n.translate('kbn.common.tutorials.filebeatCloudInstructions.config.rpmTextPre', {
+ defaultMessage: 'Modify {path} to set the connection information for Elastic Cloud:',
+ values: {
+ path: '`/etc/filebeat/filebeat.yml`',
+ },
+ }),
+ commands: [
+ 'cloud.id: "{config.cloud.id}"',
+ 'cloud.auth: "elastic:"'
+ ],
+ textPost: i18n.translate('kbn.common.tutorials.filebeatCloudInstructions.config.rpmTextPost', {
+ defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user.',
+ values: { passwordTemplate: '``' },
+ }),
},
WINDOWS: {
- title: 'Edit the configuration',
- textPre:
- 'Modify `C:\\Program Files\\Filebeat\\filebeat.yml` to set the connection information for Elastic Cloud:',
- commands: ['cloud.id: "{config.cloud.id}"', 'cloud.auth: "elastic:"'],
- textPost: 'Where `` is the password of the `elastic` user.',
- },
- },
+ title: i18n.translate('kbn.common.tutorials.filebeatCloudInstructions.config.windowsTitle', {
+ defaultMessage: 'Edit the configuration',
+ }),
+ textPre: i18n.translate('kbn.common.tutorials.filebeatCloudInstructions.config.windowsTextPre', {
+ defaultMessage: 'Modify {path} to set the connection information for Elastic Cloud:',
+ values: {
+ path: '`C:\\Program Files\\Filebeat\\filebeat.yml`',
+ },
+ }),
+ commands: [
+ 'cloud.id: "{config.cloud.id}"',
+ 'cloud.auth: "elastic:"'
+ ],
+ textPost: i18n.translate('kbn.common.tutorials.filebeatCloudInstructions.config.windowsTextPost', {
+ defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user.',
+ values: { passwordTemplate: '``' },
+ }),
+ }
+ }
});
export function filebeatEnableInstructions(moduleName) {
return {
OSX: {
- title: 'Enable and configure the ' + moduleName + ' module',
- textPre: 'From the installation directory, run:',
- commands: ['./filebeat modules enable ' + moduleName],
- textPost: 'Modify the settings in the `modules.d/' + moduleName + '.yml` file.',
+ title: i18n.translate('kbn.common.tutorials.filebeatEnableInstructions.osxTitle', {
+ defaultMessage: 'Enable and configure the {moduleName} module',
+ values: { moduleName },
+ }),
+ textPre: i18n.translate('kbn.common.tutorials.filebeatEnableInstructions.osxTextPre', {
+ defaultMessage: 'From the installation directory, run:',
+ }),
+ commands: [
+ './filebeat modules enable ' + moduleName,
+ ],
+ textPost: i18n.translate('kbn.common.tutorials.filebeatEnableInstructions.osxTextPost', {
+ defaultMessage: 'Modify the settings in the `modules.d/{moduleName}.yml` file.',
+ values: { moduleName },
+ }),
},
DEB: {
- title: 'Enable and configure the ' + moduleName + ' module',
- commands: ['sudo filebeat modules enable ' + moduleName],
- textPost: 'Modify the settings in the `/etc/filebeat/modules.d/' + moduleName + '.yml` file.',
+ title: i18n.translate('kbn.common.tutorials.filebeatEnableInstructions.debTitle', {
+ defaultMessage: 'Enable and configure the {moduleName} module',
+ values: { moduleName },
+ }),
+ commands: [
+ 'sudo filebeat modules enable ' + moduleName,
+ ],
+ textPost: i18n.translate('kbn.common.tutorials.filebeatEnableInstructions.debTextPost', {
+ defaultMessage: 'Modify the settings in the `/etc/filebeat/modules.d/{moduleName}.yml` file.',
+ values: { moduleName },
+ }),
},
RPM: {
- title: 'Enable and configure the ' + moduleName + ' module',
- commands: ['sudo filebeat modules enable ' + moduleName],
- textPost: 'Modify the settings in the `/etc/filebeat/modules.d/' + moduleName + '.yml` file.',
+ title: i18n.translate('kbn.common.tutorials.filebeatEnableInstructions.rpmTitle', {
+ defaultMessage: 'Enable and configure the {moduleName} module',
+ values: { moduleName },
+ }),
+ commands: [
+ 'sudo filebeat modules enable ' + moduleName,
+ ],
+ textPost: i18n.translate('kbn.common.tutorials.filebeatEnableInstructions.rpmTextPost', {
+ defaultMessage: 'Modify the settings in the `/etc/filebeat/modules.d/{moduleName}.yml` file.',
+ values: { moduleName },
+ }),
},
WINDOWS: {
- title: 'Enable and configure the ' + moduleName + ' module',
- textPre: 'From the `C:\\Program Files\\Filebeat` folder, run:',
- commands: ['PS C:\\Program Files\\Filebeat> filebeat.exe modules enable ' + moduleName],
- textPost: 'Modify the settings in the `modules.d/' + moduleName + '.yml` file.',
- },
+ title: i18n.translate('kbn.common.tutorials.filebeatEnableInstructions.windowsTitle', {
+ defaultMessage: 'Enable and configure the {moduleName} module',
+ values: { moduleName },
+ }),
+ textPre: i18n.translate('kbn.common.tutorials.filebeatEnableInstructions.windowsTextPre', {
+ defaultMessage: 'From the {path} folder, run:',
+ values: { path: `C:\\Program Files\\Filebeat` },
+ }),
+ commands: [
+ 'PS C:\\Program Files\\Filebeat> filebeat.exe modules enable ' + moduleName,
+ ],
+ textPost: i18n.translate('kbn.common.tutorials.filebeatEnableInstructions.windowsTextPost', {
+ defaultMessage: 'Modify the settings in the `modules.d/{moduleName}.yml` file.',
+ values: { moduleName },
+ }),
+ }
};
}
export function filebeatStatusCheck(moduleName) {
return {
- title: 'Module status',
- text: 'Check that data is received from the Filebeat `' + moduleName + '` module',
- btnLabel: 'Check data',
- success: 'Data successfully received from this module',
- error: 'No data has been received from this module yet',
+ title: i18n.translate('kbn.common.tutorials.filebeatStatusCheck.title', {
+ defaultMessage: 'Module status',
+ }),
+ text: i18n.translate('kbn.common.tutorials.filebeatStatusCheck.text', {
+ defaultMessage: 'Check that data is received from the Filebeat `{moduleName}` module',
+ values: { moduleName },
+ }),
+ btnLabel: i18n.translate('kbn.common.tutorials.filebeatStatusCheck.buttonLabel', {
+ defaultMessage: 'Check data',
+ }),
+ success: i18n.translate('kbn.common.tutorials.filebeatStatusCheck.successText', {
+ defaultMessage: 'Data successfully received from this module',
+ }),
+ error: i18n.translate('kbn.common.tutorials.filebeatStatusCheck.errorText', {
+ defaultMessage: 'No data has been received from this module yet',
+ }),
esHitsCheck: {
index: 'filebeat-*',
query: {
@@ -299,7 +508,9 @@ export function onPremInstructions(moduleName, platforms, geoipRequired, uaRequi
return {
instructionSets: [
{
- title: 'Getting Started',
+ title: i18n.translate('kbn.common.tutorials.filebeat.premInstructions.gettingStarted.title', {
+ defaultMessage: 'Getting Started',
+ }),
instructionVariants: variants,
statusCheck: filebeatStatusCheck(moduleName),
},
@@ -331,7 +542,9 @@ export function onPremCloudInstructions(moduleName, platforms) {
return {
instructionSets: [
{
- title: 'Getting Started',
+ title: i18n.translate('kbn.common.tutorials.filebeat.premCloudInstructions.gettingStarted.title', {
+ defaultMessage: 'Getting Started',
+ }),
instructionVariants: variants,
statusCheck: filebeatStatusCheck(moduleName),
},
@@ -360,7 +573,9 @@ export function cloudInstructions(moduleName, platforms) {
return {
instructionSets: [
{
- title: 'Getting Started',
+ title: i18n.translate('kbn.common.tutorials.filebeat.cloudInstructions.gettingStarted.title', {
+ defaultMessage: 'Getting Started',
+ }),
instructionVariants: variants,
statusCheck: filebeatStatusCheck(moduleName),
},
diff --git a/src/core_plugins/kibana/common/tutorials/logstash_instructions.js b/src/core_plugins/kibana/common/tutorials/logstash_instructions.js
index 4c52314236d10..5203f44ef9c8d 100644
--- a/src/core_plugins/kibana/common/tutorials/logstash_instructions.js
+++ b/src/core_plugins/kibana/common/tutorials/logstash_instructions.js
@@ -17,19 +17,28 @@
* under the License.
*/
+import { i18n } from '@kbn/i18n';
+
export const createLogstashInstructions = () => ({
INSTALL: {
OSX: [
{
- title: 'Download and install the Java Runtime Environment',
- textPre:
- 'Follow the installation instructions [here](https://docs.oracle.com/javase/8/docs/technotes/guides/install/mac_jre.html).',
+ title: i18n.translate('kbn.common.tutorials.logstashInstructions.install.java.osxTitle', {
+ defaultMessage: 'Download and install the Java Runtime Environment',
+ }),
+ textPre: i18n.translate('kbn.common.tutorials.logstashInstructions.install.java.osxTextPre', {
+ defaultMessage: 'Follow the installation instructions [here]({link}).',
+ values: { link: 'https://docs.oracle.com/javase/8/docs/technotes/guides/install/mac_jre.html' },
+ }),
},
{
- title: 'Download and install Logstash',
- textPre:
- 'First time using Logstash? See the ' +
- '[Getting Started Guide]({config.docs.base_url}guide/en/logstash/current/getting-started-with-logstash.html).',
+ title: i18n.translate('kbn.common.tutorials.logstashInstructions.install.logstash.osxTitle', {
+ defaultMessage: 'Download and install Logstash',
+ }),
+ textPre: i18n.translate('kbn.common.tutorials.logstashInstructions.install.logstash.osxTextPre', {
+ defaultMessage: 'First time using Logstash? See the [Getting Started Guide]({link}).',
+ values: { link: '{config.docs.base_url}guide/en/logstash/current/getting-started-with-logstash.html' },
+ }),
commands: [
'curl -L -O https://artifacts.elastic.co/downloads/logstash/logstash-{config.kibana.version}.tar.gz',
'tar xzvf logstash-{config.kibana.version}.tar.gz',
@@ -38,18 +47,28 @@ export const createLogstashInstructions = () => ({
],
WINDOWS: [
{
- title: 'Download and install the Java Runtime Environment',
- textPre:
- 'Follow the installation instructions [here](https://docs.oracle.com/javase/8/docs/technotes/guides/install/windows_jre_install.html).',
+ title: i18n.translate('kbn.common.tutorials.logstashInstructions.install.java.windowsTitle', {
+ defaultMessage: 'Download and install the Java Runtime Environment',
+ }),
+ textPre: i18n.translate('kbn.common.tutorials.logstashInstructions.install.java.windowsTextPre', {
+ defaultMessage: 'Follow the installation instructions [here]({link}).',
+ values: { link: 'https://docs.oracle.com/javase/8/docs/technotes/guides/install/windows_jre_install.html' },
+ }),
},
{
- title: 'Download and install Logstash',
- textPre:
- 'First time using Logstash? See the ' +
- '[Getting Started Guide]({config.docs.base_url}guide/en/logstash/current/getting-started-with-logstash.html).\n' +
- ' 1. [Download](https://artifacts.elastic.co/downloads/logstash/logstash-{config.kibana.version}.zip) the Logstash Windows zip file.\n' +
- ' 2. Extract the contents of the zip file.',
- },
+ title: i18n.translate('kbn.common.tutorials.logstashInstructions.install.logstash.windowsTitle', {
+ defaultMessage: 'Download and install Logstash',
+ }),
+ textPre: i18n.translate('kbn.common.tutorials.logstashInstructions.install.logstash.windowsTextPre', {
+ defaultMessage: 'First time using Logstash? See the [Getting Started Guide]({logstashLink}).\n\
+ 1. [Download]({elasticLink}) the Logstash Windows zip file.\n\
+ 2. Extract the contents of the zip file.',
+ values: {
+ logstashLink: '{config.docs.base_url}guide/en/logstash/current/getting-started-with-logstash.html',
+ elasticLink: 'https://artifacts.elastic.co/downloads/logstash/logstash-{config.kibana.version}.zip'
+ },
+ }),
+ }
],
},
});
diff --git a/src/core_plugins/kibana/common/tutorials/metricbeat_instructions.js b/src/core_plugins/kibana/common/tutorials/metricbeat_instructions.js
index 8e1b9b95bcb47..b15d3aaab1e7e 100644
--- a/src/core_plugins/kibana/common/tutorials/metricbeat_instructions.js
+++ b/src/core_plugins/kibana/common/tutorials/metricbeat_instructions.js
@@ -17,16 +17,20 @@
* under the License.
*/
+import { i18n } from '@kbn/i18n';
import { INSTRUCTION_VARIANT } from './instruction_variant';
import { createTrycloudOption1, createTrycloudOption2 } from './onprem_cloud_instructions';
export const createMetricbeatInstructions = () => ({
INSTALL: {
OSX: {
- title: 'Download and install Metricbeat',
- textPre:
- 'First time using Metricbeat? See the [Getting Started Guide]' +
- '({config.docs.beats.metricbeat}/metricbeat-getting-started.html).',
+ title: i18n.translate('kbn.common.tutorials.metricbeatInstructions.install.osxTitle', {
+ defaultMessage: 'Download and install Metricbeat',
+ }),
+ textPre: i18n.translate('kbn.common.tutorials.metricbeatInstructions.install.osxTextPre', {
+ defaultMessage: 'First time using Metricbeat? See the [Getting Started Guide]({link}).',
+ values: { link: '{config.docs.beats.metricbeat}/metricbeat-getting-started.html' },
+ }),
commands: [
'curl -L -O https://artifacts.elastic.co/downloads/beats/metricbeat/metricbeat-{config.kibana.version}-darwin-x86_64.tar.gz',
'tar xzvf metricbeat-{config.kibana.version}-darwin-x86_64.tar.gz',
@@ -34,76 +38,112 @@ export const createMetricbeatInstructions = () => ({
],
},
DEB: {
- title: 'Download and install Metricbeat',
- textPre:
- 'First time using Metricbeat? See the [Getting Started Guide]' +
- '({config.docs.beats.metricbeat}/metricbeat-getting-started.html).',
+ title: i18n.translate('kbn.common.tutorials.metricbeatInstructions.install.debTitle', {
+ defaultMessage: 'Download and install Metricbeat',
+ }),
+ textPre: i18n.translate('kbn.common.tutorials.metricbeatInstructions.install.debTextPre', {
+ defaultMessage: 'First time using Metricbeat? See the [Getting Started Guide]({link}).',
+ values: { link: '{config.docs.beats.metricbeat}/metricbeat-getting-started.html' },
+ }),
commands: [
'curl -L -O https://artifacts.elastic.co/downloads/beats/metricbeat/metricbeat-{config.kibana.version}-amd64.deb',
'sudo dpkg -i metricbeat-{config.kibana.version}-amd64.deb',
],
- textPost:
- 'Looking for the 32-bit packages? See the [Download page](https://www.elastic.co/downloads/beats/metricbeat).',
+ textPost: i18n.translate('kbn.common.tutorials.metricbeatInstructions.install.debTextPost', {
+ defaultMessage: 'Looking for the 32-bit packages? See the [Download page]({link}).',
+ values: { link: 'https://www.elastic.co/downloads/beats/metricbeat' },
+ }),
},
RPM: {
- title: 'Download and install Metricbeat',
- textPre:
- 'First time using Metricbeat? See the [Getting Started Guide]' +
- '({config.docs.beats.metricbeat}/metricbeat-getting-started.html).',
+ title: i18n.translate('kbn.common.tutorials.metricbeatInstructions.install.rpmTitle', {
+ defaultMessage: 'Download and install Metricbeat',
+ }),
+ textPre: i18n.translate('kbn.common.tutorials.metricbeatInstructions.install.rpmTextPre', {
+ defaultMessage: 'First time using Metricbeat? See the [Getting Started Guide]({link}).',
+ values: { link: '{config.docs.beats.metricbeat}/metricbeat-getting-started.html' },
+ }),
commands: [
'curl -L -O https://artifacts.elastic.co/downloads/beats/metricbeat/metricbeat-{config.kibana.version}-x86_64.rpm',
'sudo rpm -vi metricbeat-{config.kibana.version}-x86_64.rpm',
],
- textPost:
- 'Looking for the 32-bit packages? See the [Download page](https://www.elastic.co/downloads/beats/metricbeat).',
+ textPost: i18n.translate('kbn.common.tutorials.metricbeatInstructions.install.debTextPost', {
+ defaultMessage: 'Looking for the 32-bit packages? See the [Download page]({link}).',
+ values: { link: 'https://www.elastic.co/downloads/beats/metricbeat' },
+ }),
},
WINDOWS: {
- title: 'Download and install Metricbeat',
- textPre:
- 'First time using Metricbeat? See the [Getting Started Guide]' +
- '({config.docs.beats.metricbeat}/metricbeat-getting-started.html).\n' +
- '1. Download the Metricbeat Windows zip file from the [Download](https://www.elastic.co/downloads/beats/metricbeat) page.\n' +
- '2. Extract the contents of the zip file into `C:\\Program Files`.\n' +
- '3. Rename the `metricbeat-{config.kibana.version}-windows` directory to `Metricbeat`.\n' +
- '4. Open a PowerShell prompt as an Administrator (right-click the PowerShell icon and select' +
- ' **Run As Administrator**). If you are running Windows XP, you might need to download and install PowerShell.\n' +
- '5. From the PowerShell prompt, run the following commands to install Metricbeat as a Windows service.',
+ title: i18n.translate('kbn.common.tutorials.metricbeatInstructions.install.windowsTitle', {
+ defaultMessage: 'Download and install Metricbeat',
+ }),
+ textPre: i18n.translate('kbn.common.tutorials.metricbeatInstructions.install.windowsTextPre', {
+ defaultMessage: 'First time using Metricbeat? See the [Getting Started Guide]({metricbeatLink}).\n\
+ 1. Download the Metricbeat Windows zip file from the [Download]({elasticLink}) page.\n\
+ 2. Extract the contents of the zip file into {folderPath}.\n\
+ 3. Rename the {directoryName} directory to `Metricbeat`.\n\
+ 4. Open a PowerShell prompt as an Administrator (right-click the PowerShell icon and select \
+**Run As Administrator**). If you are running Windows XP, you might need to download and install PowerShell.\n\
+ 5. From the PowerShell prompt, run the following commands to install Metricbeat as a Windows service.',
+ values: {
+ directoryName: '`metricbeat-{config.kibana.version}-windows`',
+ folderPath: '`C:\\Program Files`',
+ metricbeatLink: '{config.docs.beats.metricbeat}/metricbeat-getting-started.html',
+ elasticLink: 'https://www.elastic.co/downloads/beats/metricbeat',
+ },
+ }),
commands: [
'PS > cd C:\\Program Files\\Metricbeat',
'PS C:\\Program Files\\Metricbeat> .\\install-service-metricbeat.ps1',
],
- textPost:
- 'Modify the settings under `output.elasticsearch` in the ' +
- '`C:\\Program Files\\Metricbeat\\metricbeat.yml` file to point to your Elasticsearch installation.',
- },
+ textPost: i18n.translate('kbn.common.tutorials.metricbeatInstructions.install.windowsTextPost', {
+ defaultMessage: 'Modify the settings under `output.elasticsearch` in the {path} file to point to your Elasticsearch installation.',
+ values: { path: '`C:\\Program Files\\Metricbeat\\metricbeat.yml`' },
+ }),
+ }
},
START: {
OSX: {
- title: 'Start Metricbeat',
- textPre:
- 'The `setup` command loads the Kibana dashboards.' +
- ' If the dashboards are already set up, omit this command.',
- commands: ['./metricbeat setup', './metricbeat -e'],
+ title: i18n.translate('kbn.common.tutorials.metricbeatInstructions.start.osxTitle', {
+ defaultMessage: 'Start Metricbeat',
+ }),
+ textPre: i18n.translate('kbn.common.tutorials.metricbeatInstructions.start.osxTextPre', {
+ defaultMessage: 'The `setup` command loads the Kibana dashboards. If the dashboards are already set up, omit this command.',
+ }),
+ commands: [
+ './metricbeat setup',
+ './metricbeat -e',
+ ]
},
DEB: {
- title: 'Start Metricbeat',
- textPre:
- 'The `setup` command loads the Kibana dashboards. If the dashboards are already set up, ' +
- 'omit this command.',
- commands: ['sudo metricbeat setup', 'sudo service metricbeat start'],
+ title: i18n.translate('kbn.common.tutorials.metricbeatInstructions.start.debTitle', {
+ defaultMessage: 'Start Metricbeat',
+ }),
+ textPre: i18n.translate('kbn.common.tutorials.metricbeatInstructions.start.debTextPre', {
+ defaultMessage: 'The `setup` command loads the Kibana dashboards. If the dashboards are already set up, omit this command.',
+ }),
+ commands: [
+ 'sudo metricbeat setup',
+ 'sudo service metricbeat start',
+ ]
},
RPM: {
- title: 'Start Metricbeat',
- textPre:
- 'The `setup` command loads the Kibana dashboards. If the dashboards are already set up, ' +
- 'omit this command.',
- commands: ['sudo metricbeat setup', 'sudo service metricbeat start'],
+ title: i18n.translate('kbn.common.tutorials.metricbeatInstructions.start.rpmTitle', {
+ defaultMessage: 'Start Metricbeat',
+ }),
+ textPre: i18n.translate('kbn.common.tutorials.metricbeatInstructions.start.rpmTextPre', {
+ defaultMessage: 'The `setup` command loads the Kibana dashboards. If the dashboards are already set up, omit this command.',
+ }),
+ commands: [
+ 'sudo metricbeat setup',
+ 'sudo service metricbeat start',
+ ],
},
WINDOWS: {
- title: 'Start Metricbeat',
- textPre:
- 'The `setup` command loads the Kibana dashboards. If the dashboards are already set up, ' +
- 'omit this command.',
+ title: i18n.translate('kbn.common.tutorials.metricbeatInstructions.start.windowsTitle', {
+ defaultMessage: 'Start Metricbeat',
+ }),
+ textPre: i18n.translate('kbn.common.tutorials.metricbeatInstructions.start.windowsTextPre', {
+ defaultMessage: 'The `setup` command loads the Kibana dashboards. If the dashboards are already set up, omit this command.',
+ }),
commands: [
'PS C:\\Program Files\\Metricbeat> metricbeat.exe setup',
'PS C:\\Program Files\\Metricbeat> Start-Service metricbeat',
@@ -112,8 +152,15 @@ export const createMetricbeatInstructions = () => ({
},
CONFIG: {
OSX: {
- title: 'Edit the configuration',
- textPre: 'Modify `metricbeat.yml` to set the connection information:',
+ title: i18n.translate('kbn.common.tutorials.metricbeatInstructions.config.osxTitle', {
+ defaultMessage: 'Edit the configuration',
+ }),
+ textPre: i18n.translate('kbn.common.tutorials.metricbeatInstructions.config.osxTextPre', {
+ defaultMessage: 'Modify {path} to set the connection information:',
+ values: {
+ path: '`metricbeat.yml`',
+ },
+ }),
commands: [
'output.elasticsearch:',
' hosts: [""]',
@@ -122,13 +169,26 @@ export const createMetricbeatInstructions = () => ({
'setup.kibana:',
' host: ""',
],
- textPost:
- 'Where `` is the password of the `elastic` user, ' +
- '`` is the URL of Elasticsearch, and `` is the URL of Kibana.',
+ textPost: i18n.translate('kbn.common.tutorials.metricbeatInstructions.config.osxTextPost', {
+ defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user, {esUrlTemplate} is the URL of Elasticsearch, \
+and {kibanaUrlTemplate} is the URL of Kibana.',
+ values: {
+ passwordTemplate: '``',
+ esUrlTemplate: '``',
+ kibanaUrlTemplate: '``',
+ },
+ }),
},
DEB: {
- title: 'Edit the configuration',
- textPre: 'Modify `/etc/metricbeat/metricbeat.yml` to set the connection information:',
+ title: i18n.translate('kbn.common.tutorials.metricbeatInstructions.config.debTitle', {
+ defaultMessage: 'Edit the configuration',
+ }),
+ textPre: i18n.translate('kbn.common.tutorials.metricbeatInstructions.config.debTextPre', {
+ defaultMessage: 'Modify {path} to set the connection information:',
+ values: {
+ path: '`/etc/metricbeat/metricbeat.yml`',
+ },
+ }),
commands: [
'output.elasticsearch:',
' hosts: [""]',
@@ -137,13 +197,26 @@ export const createMetricbeatInstructions = () => ({
'setup.kibana:',
' host: ""',
],
- textPost:
- 'Where `` is the password of the `elastic` user, ' +
- '`` is the URL of Elasticsearch, and `` is the URL of Kibana.',
+ textPost: i18n.translate('kbn.common.tutorials.metricbeatInstructions.config.debTextPost', {
+ defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user, {esUrlTemplate} is the URL of Elasticsearch, \
+and {kibanaUrlTemplate} is the URL of Kibana.',
+ values: {
+ passwordTemplate: '``',
+ esUrlTemplate: '``',
+ kibanaUrlTemplate: '``',
+ },
+ }),
},
RPM: {
- title: 'Edit the configuration',
- textPre: 'Modify `/etc/metricbeat/metricbeat.yml` to set the connection information:',
+ title: i18n.translate('kbn.common.tutorials.metricbeatInstructions.config.rpmTitle', {
+ defaultMessage: 'Edit the configuration',
+ }),
+ textPre: i18n.translate('kbn.common.tutorials.metricbeatInstructions.config.rpmTextPre', {
+ defaultMessage: 'Modify {path} to set the connection information:',
+ values: {
+ path: '`/etc/metricbeat/metricbeat.yml`',
+ },
+ }),
commands: [
'output.elasticsearch:',
' hosts: [""]',
@@ -152,14 +225,26 @@ export const createMetricbeatInstructions = () => ({
'setup.kibana:',
' host: ""',
],
- textPost:
- 'Where `` is the password of the `elastic` user, ' +
- '`` is the URL of Elasticsearch, and `` is the URL of Kibana.',
+ textPost: i18n.translate('kbn.common.tutorials.metricbeatInstructions.config.rpmTextPost', {
+ defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user, {esUrlTemplate} is the URL of Elasticsearch, \
+and {kibanaUrlTemplate} is the URL of Kibana.',
+ values: {
+ passwordTemplate: '``',
+ esUrlTemplate: '``',
+ kibanaUrlTemplate: '``',
+ },
+ }),
},
WINDOWS: {
- title: 'Edit the configuration',
- textPre:
- 'Modify `C:\\Program Files\\Metricbeat\\metricbeat.yml` to set the connection information:',
+ title: i18n.translate('kbn.common.tutorials.metricbeatInstructions.config.windowsTitle', {
+ defaultMessage: 'Edit the configuration',
+ }),
+ textPre: i18n.translate('kbn.common.tutorials.metricbeatInstructions.config.windowsTextPre', {
+ defaultMessage: 'Modify {path} to set the connection information:',
+ values: {
+ path: '`C:\\Program Files\\Metricbeat\\metricbeat.yml`',
+ },
+ }),
commands: [
'output.elasticsearch:',
' hosts: [""]',
@@ -168,81 +253,182 @@ export const createMetricbeatInstructions = () => ({
'setup.kibana:',
' host: ""',
],
- textPost:
- 'Where `` is the password of the `elastic` user, ' +
- '`` is the URL of Elasticsearch, and `` is the URL of Kibana.',
- },
- },
+ textPost: i18n.translate('kbn.common.tutorials.metricbeatInstructions.config.windowsTextPost', {
+ defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user, {esUrlTemplate} is the URL of Elasticsearch, \
+and {kibanaUrlTemplate} is the URL of Kibana.',
+ values: {
+ passwordTemplate: '``',
+ esUrlTemplate: '``',
+ kibanaUrlTemplate: '``',
+ },
+ }),
+ }
+ }
});
export const createMetricbeatCloudInstructions = () => ({
CONFIG: {
OSX: {
- title: 'Edit the configuration',
- textPre: 'Modify `metricbeat.yml` to set the connection information for Elastic Cloud:',
- commands: ['cloud.id: "{config.cloud.id}"', 'cloud.auth: "elastic:"'],
- textPost: 'Where `` is the password of the `elastic` user.',
+ title: i18n.translate('kbn.common.tutorials.metricbeatCloudInstructions.config.osxTitle', {
+ defaultMessage: 'Edit the configuration',
+ }),
+ textPre: i18n.translate('kbn.common.tutorials.metricbeatCloudInstructions.config.osxTextPre', {
+ defaultMessage: 'Modify {path} to set the connection information for Elastic Cloud:',
+ values: {
+ path: '`metricbeat.yml`',
+ },
+ }),
+ commands: [
+ 'cloud.id: "{config.cloud.id}"',
+ 'cloud.auth: "elastic:"'
+ ],
+ textPost: i18n.translate('kbn.common.tutorials.metricbeatCloudInstructions.config.osxTextPost', {
+ defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user.',
+ values: { passwordTemplate: '``' },
+ }),
},
DEB: {
- title: 'Edit the configuration',
- textPre:
- 'Modify `/etc/metricbeat/metricbeat.yml` to set the connection information for Elastic Cloud:',
- commands: ['cloud.id: "{config.cloud.id}"', 'cloud.auth: "elastic:"'],
- textPost: 'Where `` is the password of the `elastic` user.',
+ title: i18n.translate('kbn.common.tutorials.metricbeatCloudInstructions.config.debTitle', {
+ defaultMessage: 'Edit the configuration',
+ }),
+ textPre: i18n.translate('kbn.common.tutorials.metricbeatCloudInstructions.config.debTextPre', {
+ defaultMessage: 'Modify {path} to set the connection information for Elastic Cloud:',
+ values: {
+ path: '`/etc/metricbeat/metricbeat.yml`',
+ },
+ }),
+ commands: [
+ 'cloud.id: "{config.cloud.id}"',
+ 'cloud.auth: "elastic:"'
+ ],
+ textPost: i18n.translate('kbn.common.tutorials.metricbeatCloudInstructions.config.debTextPost', {
+ defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user.',
+ values: { passwordTemplate: '``' },
+ }),
},
RPM: {
- title: 'Edit the configuration',
- textPre:
- 'Modify `/etc/metricbeat/metricbeat.yml` to set the connection information for Elastic Cloud:',
- commands: ['cloud.id: "{config.cloud.id}"', 'cloud.auth: "elastic:"'],
- textPost: 'Where `` is the password of the `elastic` user.',
+ title: i18n.translate('kbn.common.tutorials.metricbeatCloudInstructions.config.rpmTitle', {
+ defaultMessage: 'Edit the configuration',
+ }),
+ textPre: i18n.translate('kbn.common.tutorials.metricbeatCloudInstructions.config.rpmTextPre', {
+ defaultMessage: 'Modify {path} to set the connection information for Elastic Cloud:',
+ values: {
+ path: '`/etc/metricbeat/metricbeat.yml`',
+ },
+ }),
+ commands: [
+ 'cloud.id: "{config.cloud.id}"',
+ 'cloud.auth: "elastic:"'
+ ],
+ textPost: i18n.translate('kbn.common.tutorials.metricbeatCloudInstructions.config.rpmTextPost', {
+ defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user.',
+ values: { passwordTemplate: '``' },
+ }),
},
WINDOWS: {
- title: 'Edit the configuration',
- textPre:
- 'Modify `C:\\Program Files\\Filebeat\\metricbeat.yml` to set the connection information for Elastic Cloud:',
- commands: ['cloud.id: "{config.cloud.id}"', 'cloud.auth: "elastic:"'],
- textPost: 'Where `` is the password of the `elastic` user.',
- },
- },
+ title: i18n.translate('kbn.common.tutorials.metricbeatCloudInstructions.config.windowsTitle', {
+ defaultMessage: 'Edit the configuration',
+ }),
+ textPre: i18n.translate('kbn.common.tutorials.metricbeatCloudInstructions.config.windowsTextPre', {
+ defaultMessage: 'Modify {path} to set the connection information for Elastic Cloud:',
+ values: {
+ path: '`C:\\Program Files\\Metricbeat\\metricbeat.yml`',
+ },
+ }),
+ commands: [
+ 'cloud.id: "{config.cloud.id}"',
+ 'cloud.auth: "elastic:"'
+ ],
+ textPost: i18n.translate('kbn.common.tutorials.metricbeatCloudInstructions.config.windowsTextPost', {
+ defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user.',
+ values: { passwordTemplate: '``' },
+ }),
+ }
+ }
});
export function metricbeatEnableInstructions(moduleName) {
return {
OSX: {
- title: 'Enable and configure the ' + moduleName + ' module',
- textPre: 'From the installation directory, run:',
- commands: ['./metricbeat modules enable ' + moduleName],
- textPost: 'Modify the settings in the `modules.d/' + moduleName + '.yml` file.',
+ title: i18n.translate('kbn.common.tutorials.metricbeatEnableInstructions.osxTitle', {
+ defaultMessage: 'Enable and configure the {moduleName} module',
+ values: { moduleName },
+ }),
+ textPre: i18n.translate('kbn.common.tutorials.metricbeatEnableInstructions.osxTextPre', {
+ defaultMessage: 'From the installation directory, run:',
+ }),
+ commands: [
+ './metricbeat modules enable ' + moduleName,
+ ],
+ textPost: i18n.translate('kbn.common.tutorials.metricbeatEnableInstructions.osxTextPost', {
+ defaultMessage: 'Modify the settings in the `modules.d/{moduleName}.yml` file.',
+ values: { moduleName },
+ }),
},
DEB: {
- title: 'Enable and configure the ' + moduleName + ' module',
- commands: ['sudo metricbeat modules enable ' + moduleName],
- textPost:
- 'Modify the settings in the `/etc/metricbeat/modules.d/' + moduleName + '.yml` file.',
+ title: i18n.translate('kbn.common.tutorials.metricbeatEnableInstructions.debTitle', {
+ defaultMessage: 'Enable and configure the {moduleName} module',
+ values: { moduleName },
+ }),
+ commands: [
+ 'sudo metricbeat modules enable ' + moduleName,
+ ],
+ textPost: i18n.translate('kbn.common.tutorials.metricbeatEnableInstructions.debTextPost', {
+ defaultMessage: 'Modify the settings in the `/etc/metricbeat/modules.d/{moduleName}.yml` file.',
+ values: { moduleName },
+ }),
},
RPM: {
- title: 'Enable and configure the ' + moduleName + ' module',
- commands: ['sudo metricbeat modules enable ' + moduleName],
- textPost:
- 'Modify the settings in the `/etc/metricbeat/modules.d/' + moduleName + '.yml` file.',
+ title: i18n.translate('kbn.common.tutorials.metricbeatEnableInstructions.rpmTitle', {
+ defaultMessage: 'Enable and configure the {moduleName} module',
+ values: { moduleName },
+ }),
+ commands: [
+ 'sudo metricbeat modules enable ' + moduleName,
+ ],
+ textPost: i18n.translate('kbn.common.tutorials.metricbeatEnableInstructions.rpmTextPost', {
+ defaultMessage: 'Modify the settings in the `/etc/metricbeat/modules.d/{moduleName}.yml` file.',
+ values: { moduleName },
+ }),
},
WINDOWS: {
- title: 'Enable and configure the ' + moduleName + ' module',
- textPre: 'From the `C:\\Program Files\\Metricbeat` folder, run:',
- commands: ['PS C:\\Program Files\\Metricbeat> metricbeat.exe modules enable ' + moduleName],
- textPost: 'Modify the settings in the `modules.d/' + moduleName + '.yml` file.',
- },
+ title: i18n.translate('kbn.common.tutorials.metricbeatEnableInstructions.windowsTitle', {
+ defaultMessage: 'Enable and configure the {moduleName} module',
+ values: { moduleName },
+ }),
+ textPre: i18n.translate('kbn.common.tutorials.metricbeatEnableInstructions.windowsTextPre', {
+ defaultMessage: 'From the {path} folder, run:',
+ values: { path: `C:\\Program Files\\Metricbeat` },
+ }),
+ commands: [
+ 'PS C:\\Program Files\\Metricbeat> metricbeat.exe modules enable ' + moduleName,
+ ],
+ textPost: i18n.translate('kbn.common.tutorials.metricbeatEnableInstructions.windowsTextPost', {
+ defaultMessage: 'Modify the settings in the `modules.d/{moduleName}.yml` file.',
+ values: { moduleName },
+ }),
+ }
};
}
export function metricbeatStatusCheck(moduleName) {
return {
- title: 'Module status',
- text: 'Check that data is received from the Metricbeat `' + moduleName + '` module',
- btnLabel: 'Check data',
- success: 'Data successfully received from this module',
- error: 'No data has been received from this module yet',
+ title: i18n.translate('kbn.common.tutorials.metricbeatStatusCheck.title', {
+ defaultMessage: 'Module status',
+ }),
+ text: i18n.translate('kbn.common.tutorials.metricbeatStatusCheck.text', {
+ defaultMessage: 'Check that data is received from the Metricbeat `{moduleName}` module',
+ values: { moduleName },
+ }),
+ btnLabel: i18n.translate('kbn.common.tutorials.metricbeatStatusCheck.buttonLabel', {
+ defaultMessage: 'Check data',
+ }),
+ success: i18n.translate('kbn.common.tutorials.metricbeatStatusCheck.successText', {
+ defaultMessage: 'Data successfully received from this module',
+ }),
+ error: i18n.translate('kbn.common.tutorials.metricbeatStatusCheck.errorText', {
+ defaultMessage: 'No data has been received from this module yet',
+ }),
esHitsCheck: {
index: 'metricbeat-*',
query: {
@@ -264,7 +450,9 @@ export function onPremInstructions(moduleName) {
return {
instructionSets: [
{
- title: 'Getting Started',
+ title: i18n.translate('kbn.common.tutorials.metricbeat.premInstructions.gettingStarted.title', {
+ defaultMessage: 'Getting Started',
+ }),
instructionVariants: [
{
id: INSTRUCTION_VARIANT.OSX,
@@ -317,7 +505,9 @@ export function onPremCloudInstructions(moduleName) {
return {
instructionSets: [
{
- title: 'Getting Started',
+ title: i18n.translate('kbn.common.tutorials.metricbeat.premCloudInstructions.gettingStarted.title', {
+ defaultMessage: 'Getting Started',
+ }),
instructionVariants: [
{
id: INSTRUCTION_VARIANT.OSX,
@@ -377,7 +567,9 @@ export function cloudInstructions(moduleName) {
return {
instructionSets: [
{
- title: 'Getting Started',
+ title: i18n.translate('kbn.common.tutorials.metricbeat.cloudInstructions.gettingStarted.title', {
+ defaultMessage: 'Getting Started',
+ }),
instructionVariants: [
{
id: INSTRUCTION_VARIANT.OSX,
diff --git a/src/core_plugins/kibana/common/tutorials/onprem_cloud_instructions.js b/src/core_plugins/kibana/common/tutorials/onprem_cloud_instructions.js
index eec1848c06a93..088038ab5beb1 100644
--- a/src/core_plugins/kibana/common/tutorials/onprem_cloud_instructions.js
+++ b/src/core_plugins/kibana/common/tutorials/onprem_cloud_instructions.js
@@ -17,24 +17,39 @@
* under the License.
*/
+import { i18n } from '@kbn/i18n';
+
export const createTrycloudOption1 = () => ({
- title: 'Option 1: Try module in Elastic Cloud',
- textPre:
- 'Go to [Elastic Cloud](https://www.elastic.co/cloud/as-a-service/signup?blade=kib). Register if you ' +
- 'do not already have an account. Free 14-day trial available.\n\n' +
- 'Log into the Elastic Cloud console\n\n' +
- 'To create a cluster, in Elastic Cloud console:\n' +
- ' 1. Select **Create Deployment** and specify the **Deployment Name**\n' +
- ' 2. Modify the other deployment options as needed (or not, the defaults are great to get started)\n' +
- ' 3. Click **Create Deployment**\n' +
- ' 4. Wait until deployment creation completes\n' +
- ' 5. Go to the new Cloud Kibana instance and follow the Kibana Home instructions',
+ title: i18n.translate('kbn.common.tutorials.premCloudInstructions.option1.title', {
+ defaultMessage: 'Option 1: Try module in Elastic Cloud',
+ }),
+ textPre: i18n.translate('kbn.common.tutorials.premCloudInstructions.option1.textPre', {
+ defaultMessage: 'Go to [Elastic Cloud]({link}). Register if you \
+do not already have an account. Free 14-day trial available.\n\n\
+Log into the Elastic Cloud console\n\n\
+To create a cluster, in Elastic Cloud console:\n\
+ 1. Select **Create Deployment** and specify the **Deployment Name**\n\
+ 2. Modify the other deployment options as needed (or not, the defaults are great to get started)\n\
+ 3. Click **Create Deployment**\n\
+ 4. Wait until deployment creation completes\n\
+ 5. Go to the new Cloud Kibana instance and follow the Kibana Home instructions',
+ values: {
+ link: 'https://www.elastic.co/cloud/as-a-service/signup?blade=kib',
+ }
+ }),
});
export const createTrycloudOption2 = () => ({
- title: 'Option 2: Connect local Kibana to a Cloud instance',
- textPre:
- 'If you are running this Kibana instance against a hosted Elasticsearch instance,' +
- ' proceed with manual setup.\n\n' +
- 'Save the **Elasticsearch** endpoint as `` and the cluster **Password** as `` for your records',
+ title: i18n.translate('kbn.common.tutorials.premCloudInstructions.option2.title', {
+ defaultMessage: 'Option 2: Connect local Kibana to a Cloud instance',
+ }),
+ textPre: i18n.translate('kbn.common.tutorials.premCloudInstructions.option2.textPre', {
+ defaultMessage: 'If you are running this Kibana instance against a hosted Elasticsearch instance, \
+proceed with manual setup.\n\n\
+Save the **Elasticsearch** endpoint as {urlTemplate} and the cluster **Password** as {passwordTemplate} for your records',
+ values: {
+ urlTemplate: '``',
+ passwordTemplate: '``',
+ }
+ }),
});
From e6ebcf2506ba6adcc8f7c9349417689a4dc813f2 Mon Sep 17 00:00:00 2001
From: pavel06081991
Date: Thu, 6 Sep 2018 13:59:43 +0300
Subject: [PATCH 26/68] translate tutorials(apm) (#22217)
* translate tutorials(apm)
* remove tabs from the line beginning
* remove id duplicate
* Remove disabling no-multi-str rule
* Move command to the values
---
.../tutorials/apm/apm_client_instructions.js | 394 ++++++++++++------
.../tutorials/apm/apm_server_instructions.js | 69 ++-
.../server/tutorials/apm/elastic_cloud.js | 15 +-
.../kibana/server/tutorials/apm/index.js | 36 +-
.../kibana/server/tutorials/apm/on_prem.js | 49 ++-
5 files changed, 393 insertions(+), 170 deletions(-)
diff --git a/src/core_plugins/kibana/server/tutorials/apm/apm_client_instructions.js b/src/core_plugins/kibana/server/tutorials/apm/apm_client_instructions.js
index 183cf26a6679e..138bebdc57998 100644
--- a/src/core_plugins/kibana/server/tutorials/apm/apm_client_instructions.js
+++ b/src/core_plugins/kibana/server/tutorials/apm/apm_client_instructions.js
@@ -17,123 +17,198 @@
* under the License.
*/
-/* eslint-disable max-len */
+import { i18n } from '@kbn/i18n';
export const createNodeClientInstructions = () => [
{
- title: 'Install the APM agent',
- textPre: 'Install the APM agent for Node.js as a dependency to your application.',
+ title: i18n.translate('kbn.server.tutorials.apm.nodeClient.install.title', {
+ defaultMessage: 'Install the APM agent',
+ }),
+ textPre: i18n.translate('kbn.server.tutorials.apm.nodeClient.install.textPre', {
+ defaultMessage: 'Install the APM agent for Node.js as a dependency to your application.',
+ }),
commands: ['npm install elastic-apm-node --save'],
},
{
- title: 'Configure the agent',
- textPre:
- 'Agents are libraries that run inside of your application process.' +
- ' APM services are created programmatically based on the `serviceName`.' +
- ' This agent supports a vararity of frameworks but can also be used with your custom stack.',
- commands: `// Add this to the VERY top of the first file loaded in your app
+ title: i18n.translate('kbn.server.tutorials.apm.nodeClient.configure.title', {
+ defaultMessage: 'Configure the agent',
+ }),
+ textPre: i18n.translate('kbn.server.tutorials.apm.nodeClient.configure.textPre', {
+ defaultMessage: 'Agents are libraries that run inside of your application process. \
+APM services are created programmatically based on the `serviceName`. \
+This agent supports a vararity of frameworks but can also be used with your custom stack.',
+ }),
+ commands: `// ${i18n.translate('kbn.server.tutorials.apm.nodeClient.configure.commands.addThisToTheFileTopComment', {
+ defaultMessage: 'Add this to the VERY top of the first file loaded in your app',
+ })}
var apm = require('elastic-apm-node').start({curlyOpen}
- // Override service name from package.json
- // Allowed characters: a-z, A-Z, 0-9, -, _, and space
+ // ${i18n.translate('kbn.server.tutorials.apm.nodeClient.configure.commands.setRequiredServiceNameComment', {
+ defaultMessage: 'Override service name from package.json',
+ })}
+ // ${i18n.translate('kbn.server.tutorials.apm.nodeClient.configure.commands.allowedCharactersComment', {
+ defaultMessage: 'Allowed characters: a-z, A-Z, 0-9, -, _, and space',
+ })}
serviceName: '',
- // Use if APM Server requires a token
+ // ${i18n.translate('kbn.server.tutorials.apm.nodeClient.configure.commands.useIfApmRequiresTokenComment', {
+ defaultMessage: 'Use if APM Server requires a token',
+ })}
secretToken: '',
- // Set custom APM Server URL (default: http://localhost:8200)
+ // ${i18n.translate('kbn.server.tutorials.apm.nodeClient.configure.commands.setCustomApmServerUrlComment', {
+ defaultMessage: 'Set custom APM Server URL (default: {defaultApmServerUrl})',
+ values: { defaultApmServerUrl: 'http://localhost:8200' },
+ })}
serverUrl: ''
{curlyClose})`.split('\n'),
- textPost: `See [the documentation]({config.docs.base_url}guide/en/apm/agent/nodejs/1.x/index.html) for advanced usage, including how to use with [Babel/ES Modules]({config.docs.base_url}guide/en/apm/agent/nodejs/1.x/advanced-setup.html#es-modules).`,
+ textPost: i18n.translate('kbn.server.tutorials.apm.nodeClient.configure.textPost', {
+ defaultMessage: 'See [the documentation]({documentationLink}) for advanced usage, including how to use with \
+[Babel/ES Modules]({babelEsModulesLink}).',
+ values: {
+ documentationLink: '{config.docs.base_url}guide/en/apm/agent/nodejs/1.x/index.html',
+ babelEsModulesLink: '{config.docs.base_url}guide/en/apm/agent/nodejs/1.x/advanced-setup.html#es-modules',
+ },
+ }),
},
];
export const createDjangoClientInstructions = () => [
{
- title: 'Install the APM agent',
- textPre: 'Install the APM agent for Python as a dependency.',
+ title: i18n.translate('kbn.server.tutorials.apm.djangoClient.install.title', {
+ defaultMessage: 'Install the APM agent',
+ }),
+ textPre: i18n.translate('kbn.server.tutorials.apm.djangoClient.install.textPre', {
+ defaultMessage: 'Install the APM agent for Python as a dependency.',
+ }),
commands: ['$ pip install elastic-apm'],
},
{
- title: 'Configure the agent',
- textPre:
- 'Agents are libraries that run inside of your application process.' +
- ' APM services are created programmatically based on the `SERVICE_NAME`.',
- commands: `# Add the agent to the installed apps
+ title: i18n.translate('kbn.server.tutorials.apm.djangoClient.configure.title', {
+ defaultMessage: 'Configure the agent',
+ }),
+ textPre: i18n.translate('kbn.server.tutorials.apm.djangoClient.configure.textPre', {
+ defaultMessage: 'Agents are libraries that run inside of your application process. \
+APM services are created programmatically based on the `SERVICE_NAME`.',
+ }),
+ commands: `# ${i18n.translate('kbn.server.tutorials.apm.djangoClient.configure.commands.addAgentComment', {
+ defaultMessage: 'Add the agent to the installed apps',
+ })}
INSTALLED_APPS = (
'elasticapm.contrib.django',
# ...
)
ELASTIC_APM = {curlyOpen}
- # Set required service name. Allowed characters:
- # a-z, A-Z, 0-9, -, _, and space
+ # ${i18n.translate('kbn.server.tutorials.apm.djangoClient.configure.commands.setRequiredServiceNameComment', {
+ defaultMessage: 'Set required service name. Allowed characters:',
+ })}
+ # ${i18n.translate('kbn.server.tutorials.apm.djangoClient.configure.commands.allowedCharactersComment', {
+ defaultMessage: 'a-z, A-Z, 0-9, -, _, and space',
+ })}
'SERVICE_NAME': '',
- # Use if APM Server requires a token
+ # ${i18n.translate('kbn.server.tutorials.apm.djangoClient.configure.commands.useIfApmServerRequiresTokenComment', {
+ defaultMessage: 'Use if APM Server requires a token',
+ })}
'SECRET_TOKEN': '',
- # Set custom APM Server URL (default: http://localhost:8200)
+ # ${i18n.translate('kbn.server.tutorials.apm.djangoClient.configure.commands.setCustomApmServerUrlComment', {
+ defaultMessage: 'Set custom APM Server URL (default: {defaultApmServerUrl})',
+ values: { defaultApmServerUrl: 'http://localhost:8200' },
+ })}
'SERVER_URL': '',
{curlyClose}
-# To send performance metrics, add our tracing middleware:
+# ${i18n.translate('kbn.server.tutorials.apm.djangoClient.configure.commands.addTracingMiddlewareComment', {
+ defaultMessage: 'To send performance metrics, add our tracing middleware:',
+ })}
MIDDLEWARE = (
'elasticapm.contrib.django.middleware.TracingMiddleware',
#...
)`.split('\n'),
- textPost:
- 'See the [documentation]' +
- '({config.docs.base_url}guide/en/apm/agent/python/2.x/django-support.html) for advanced usage.',
+ textPost: i18n.translate('kbn.server.tutorials.apm.djangoClient.configure.textPost', {
+ defaultMessage: 'See the [documentation]({documentationLink}) for advanced usage.',
+ values: { documentationLink: '{config.docs.base_url}guide/en/apm/agent/python/2.x/django-support.html' },
+ }),
},
];
export const createFlaskClientInstructions = () => [
{
- title: 'Install the APM agent',
- textPre: 'Install the APM agent for Python as a dependency.',
+ title: i18n.translate('kbn.server.tutorials.apm.flaskClient.install.title', {
+ defaultMessage: 'Install the APM agent',
+ }),
+ textPre: i18n.translate('kbn.server.tutorials.apm.flaskClient.install.textPre', {
+ defaultMessage: 'Install the APM agent for Python as a dependency.',
+ }),
commands: ['$ pip install elastic-apm[flask]'],
},
{
- title: 'Configure the agent',
- textPre:
- 'Agents are libraries that run inside of your application process.' +
- ' APM services are created programmatically based on the `SERVICE_NAME`.',
- commands: `# initialize using environment variables
+ title: i18n.translate('kbn.server.tutorials.apm.flaskClient.configure.title', {
+ defaultMessage: 'Configure the agent',
+ }),
+ textPre: i18n.translate('kbn.server.tutorials.apm.flaskClient.configure.textPre', {
+ defaultMessage: 'Agents are libraries that run inside of your application process. \
+APM services are created programmatically based on the `SERVICE_NAME`.',
+ }),
+ commands: `# ${i18n.translate('kbn.server.tutorials.apm.flaskClient.configure.commands.initializeUsingEnvironmentVariablesComment', {
+ defaultMessage: 'initialize using environment variables',
+ })}
from elasticapm.contrib.flask import ElasticAPM
app = Flask(__name__)
apm = ElasticAPM(app)
-# or configure to use ELASTIC_APM in your application's settings
+# ${i18n.translate('kbn.server.tutorials.apm.flaskClient.configure.commands.configureElasticApmComment', {
+ defaultMessage: 'or configure to use ELASTIC_APM in your application\'s settings',
+ })}
from elasticapm.contrib.flask import ElasticAPM
app.config['ELASTIC_APM'] = {curlyOpen}
- # Set required service name. Allowed characters:
- # a-z, A-Z, 0-9, -, _, and space
+ # ${i18n.translate('kbn.server.tutorials.apm.flaskClient.configure.commands.setRequiredServiceNameComment', {
+ defaultMessage: 'Set required service name. Allowed characters:',
+ })}
+ # ${i18n.translate('kbn.server.tutorials.apm.flaskClient.configure.commands.allowedCharactersComment', {
+ defaultMessage: 'a-z, A-Z, 0-9, -, _, and space',
+ })}
'SERVICE_NAME': '',
- # Use if APM Server requires a token
+ # ${i18n.translate('kbn.server.tutorials.apm.flaskClient.configure.commands.useIfApmServerRequiresTokenComment', {
+ defaultMessage: 'Use if APM Server requires a token',
+ })}
'SECRET_TOKEN': '',
- # Set custom APM Server URL (default: http://localhost:8200)
+ # ${i18n.translate('kbn.server.tutorials.apm.flaskClient.configure.commands.setCustomApmServerUrlComment', {
+ defaultMessage: 'Set custom APM Server URL (default: {defaultApmServerUrl})',
+ values: { defaultApmServerUrl: 'http://localhost:8200' },
+ })}
'SERVER_URL': '',
{curlyClose}
apm = ElasticAPM(app)`.split('\n'),
- textPost:
- 'See the [documentation]' +
- '({config.docs.base_url}guide/en/apm/agent/python/2.x/flask-support.html) for advanced usage.',
+ textPost: i18n.translate('kbn.server.tutorials.apm.flaskClient.configure.textPost', {
+ defaultMessage: 'See the [documentation]({documentationLink}) for advanced usage.',
+ values: { documentationLink: '{config.docs.base_url}guide/en/apm/agent/python/2.x/flask-support.html' },
+ }),
},
];
export const createRailsClientInstructions = () => [
{
- title: 'Install the APM agent',
- textPre: 'Add the agent to your Gemfile.',
+ title: i18n.translate('kbn.server.tutorials.apm.railsClient.install.title', {
+ defaultMessage: 'Install the APM agent',
+ }),
+ textPre: i18n.translate('kbn.server.tutorials.apm.railsClient.install.textPre', {
+ defaultMessage: 'Add the agent to your Gemfile.',
+ }),
commands: [`gem 'elastic-apm'`],
},
{
- title: 'Configure the agent',
- textPre:
- 'APM is automatically started when your app boots. Configure the agent, by creating the config file `config/elastic_apm.yml`',
+ title: i18n.translate('kbn.server.tutorials.apm.railsClient.configure.title', {
+ defaultMessage: 'Configure the agent',
+ }),
+ textPre: i18n.translate('kbn.server.tutorials.apm.railsClient.configure.textPre', {
+ defaultMessage: 'APM is automatically started when your app boots. Configure the agent, by creating the config file {configFile}',
+ values: { configFile: '`config/elastic_apm.yml`' },
+ }),
commands: `# config/elastic_apm.yml:
# Set service name - allowed characters: a-z, A-Z, 0-9, -, _ and space
@@ -145,22 +220,30 @@ export const createRailsClientInstructions = () => [
# Set custom APM Server URL (default: http://localhost:8200)
# server_url: 'http://localhost:8200'`.split('\n'),
- textPost:
- 'See the [documentation]' +
- '({config.docs.base_url}guide/en/apm/agent/ruby/1.x/index.html) for configuration options and advanced usage.\n\n',
+ textPost: i18n.translate('kbn.server.tutorials.apm.railsClient.configure.textPost', {
+ defaultMessage: 'See the [documentation]({documentationLink}) for configuration options and advanced usage.\n\n',
+ values: { documentationLink: '{config.docs.base_url}guide/en/apm/agent/ruby/1.x/index.html' },
+ }),
},
];
export const createRackClientInstructions = () => [
{
- title: 'Install the APM agent',
- textPre: 'Add the agent to your Gemfile.',
+ title: i18n.translate('kbn.server.tutorials.apm.rackClient.install.title', {
+ defaultMessage: 'Install the APM agent',
+ }),
+ textPre: i18n.translate('kbn.server.tutorials.apm.rackClient.install.textPre', {
+ defaultMessage: 'Add the agent to your Gemfile.',
+ }),
commands: [`gem 'elastic-apm'`],
},
{
- title: 'Configure the agent',
- textPre:
- 'For Rack or a compatible framework (e.g. Sinatra), include the middleware in your app and start the agent.',
+ title: i18n.translate('kbn.server.tutorials.apm.rackClient.configure.title', {
+ defaultMessage: 'Configure the agent',
+ }),
+ textPre: i18n.translate('kbn.server.tutorials.apm.rackClient.configure.textPre', {
+ defaultMessage: 'For Rack or a compatible framework (e.g. Sinatra), include the middleware in your app and start the agent.',
+ }),
commands: `# config.ru
require 'sinatra/base'
@@ -171,8 +254,12 @@ export const createRackClientInstructions = () => [
end
ElasticAPM.start(
- app: MySinatraApp, # required
- config_file: '' # optional, defaults to config/elastic_apm.yml
+ app: MySinatraApp, # ${i18n.translate('kbn.server.tutorials.apm.rackClient.configure.commands.requiredComment', {
+ defaultMessage: 'required',
+ })}
+ config_file: '' # ${i18n.translate('kbn.server.tutorials.apm.rackClient.configure.commands.optionalComment', {
+ defaultMessage: 'optional, defaults to config/elastic_apm.yml',
+ })}
)
run MySinatraApp
@@ -180,90 +267,146 @@ export const createRackClientInstructions = () => [
at_exit {curlyOpen} ElasticAPM.stop {curlyClose}`.split('\n'),
},
{
- title: 'Create config file',
- textPre: 'Create a config file `config/elastic_apm.yml`:',
+ title: i18n.translate('kbn.server.tutorials.apm.rackClient.createConfig.title', {
+ defaultMessage: 'Create config file',
+ }),
+ textPre: i18n.translate('kbn.server.tutorials.apm.rackClient.createConfig.textPre', {
+ defaultMessage: 'Create a config file {configFile}:',
+ values: { configFile: '`config/elastic_apm.yml`' },
+ }),
commands: `# config/elastic_apm.yml:
-# Set service name - allowed characters: a-z, A-Z, 0-9, -, _ and space
-# Defaults to the name of your Rack app's class.
+# ${i18n.translate('kbn.server.tutorials.apm.rackClient.createConfig.commands.setServiceNameComment', {
+ defaultMessage: 'Set service name - allowed characters: a-z, A-Z, 0-9, -, _ and space',
+ })}
+# ${i18n.translate('kbn.server.tutorials.apm.rackClient.createConfig.commands.defaultsToTheNameOfRackAppClassComment', {
+ defaultMessage: 'Defaults to the name of your Rack app\'s class.',
+ })}
# service_name: 'my-service'
-# Use if APM Server requires a token
+# ${i18n.translate('kbn.server.tutorials.apm.rackClient.createConfig.commands.useIfApmServerRequiresTokenComment', {
+ defaultMessage: 'Use if APM Server requires a token',
+ })}
# secret_token: ''
-# Set custom APM Server URL (default: http://localhost:8200)
+# ${i18n.translate('kbn.server.tutorials.apm.rackClient.createConfig.commands.setCustomApmServerComment', {
+ defaultMessage: 'Set custom APM Server URL (default: {defaultServerUrl})',
+ values: { defaultServerUrl: 'http://localhost:8200' },
+ })}
# server_url: 'http://localhost:8200'`.split('\n'),
- textPost:
- 'See the [documentation]' +
- '({config.docs.base_url}guide/en/apm/agent/ruby/1.x/index.html) for configuration options and advanced usage.\n\n',
+ textPost: i18n.translate('kbn.server.tutorials.apm.rackClient.createConfig.textPost', {
+ defaultMessage: 'See the [documentation]({documentationLink}) for configuration options and advanced usage.\n\n',
+ values: { documentationLink: '{config.docs.base_url}guide/en/apm/agent/ruby/1.x/index.html' },
+ }),
},
];
export const createJsClientInstructions = () => [
{
- title: 'Enable Real User Monitoring support in the APM server',
- textPre:
- 'Please refer to [the documentation]({config.docs.base_url}guide/en/apm/server/{config.docs.version}/rum.html).',
+ title: i18n.translate('kbn.server.tutorials.apm.jsClient.enableRealUserMonitoring.title', {
+ defaultMessage: 'Enable Real User Monitoring support in the APM server',
+ }),
+ textPre: i18n.translate('kbn.server.tutorials.apm.jsClient.enableRealUserMonitoring.textPre', {
+ defaultMessage: 'Please refer to [the documentation]({documentationLink}).',
+ values: { documentationLink: '{config.docs.base_url}guide/en/apm/server/{config.docs.version}/rum.html' },
+ }),
},
{
- title: 'Install the APM agent',
- textPre: 'Install the APM agent for JavaScript as a dependency to your application:',
+ title: i18n.translate('kbn.server.tutorials.apm.jsClient.install.title', {
+ defaultMessage: 'Install the APM agent',
+ }),
+ textPre: i18n.translate('kbn.server.tutorials.apm.jsClient.install.textPre', {
+ defaultMessage: 'Install the APM agent for JavaScript as a dependency to your application:',
+ }),
commands: [`npm install elastic-apm-js-base --save`],
},
{
- title: 'Configure the agent',
- textPre: 'Agents are libraries that run inside of your application.',
+ title: i18n.translate('kbn.server.tutorials.apm.jsClient.configure.title', {
+ defaultMessage: 'Configure the agent',
+ }),
+ textPre: i18n.translate('kbn.server.tutorials.apm.jsClient.configure.textPre', {
+ defaultMessage: 'Agents are libraries that run inside of your application.',
+ }),
commands: `import {curlyOpen} init as initApm {curlyClose} from 'elastic-apm-js-base'
var apm = initApm({curlyOpen}
- // Set required service name (allowed characters: a-z, A-Z, 0-9, -, _, and space)
+ // ${i18n.translate('kbn.server.tutorials.apm.jsClient.configure.commands.setRequiredServiceNameComment', {
+ defaultMessage: 'Set required service name (allowed characters: a-z, A-Z, 0-9, -, _, and space)',
+ })}
serviceName: '',
- // Set custom APM Server URL (default: http://localhost:8200)
+ // ${i18n.translate('kbn.server.tutorials.apm.jsClient.configure.commands.setCustomApmServerUrlComment', {
+ defaultMessage: 'Set custom APM Server URL (default: {defaultApmServerUrl})',
+ values: { defaultApmServerUrl: 'http://localhost:8200' },
+ })}
serverUrl: '',
- // Set service version (required for sourcemap feature)
+ // ${i18n.translate('kbn.server.tutorials.apm.jsClient.configure.commands.setServiceVersionComment', {
+ defaultMessage: 'Set service version (required for sourcemap feature)',
+ })}
serviceVersion: ''
{curlyClose})`.split('\n'),
- textPost:
- 'See the [documentation]' +
- '({config.docs.base_url}guide/en/apm/agent/js-base/current/index.html) for advanced usage.',
+ textPost: i18n.translate('kbn.server.tutorials.apm.jsClient.configure.textPost', {
+ defaultMessage: 'See the [documentation]({documentationLink}) for advanced usage.',
+ values: { documentationLink: '{config.docs.base_url}guide/en/apm/agent/js-base/current/index.html' },
+ }),
},
];
export const createGoClientInstructions = () => [
{
- title: 'Install the APM agent',
- textPre: 'Install the APM agent packages for Go.',
+ title: i18n.translate('kbn.server.tutorials.apm.goClient.install.title', {
+ defaultMessage: 'Install the APM agent',
+ }),
+ textPre: i18n.translate('kbn.server.tutorials.apm.goClient.install.textPre', {
+ defaultMessage: 'Install the APM agent packages for Go.',
+ }),
commands: ['go get github.com/elastic/apm-agent-go'],
},
{
- title: 'Configure the agent',
- textPre:
- 'Agents are libraries that run inside of your application process.' +
- ' APM services are created programmatically based on the executable ' +
- ' file name, or the `ELASTIC_APM_SERVICE_NAME` environment variable.',
- commands: `# Initialize using environment variables:
-
-# Set the service name. Allowed characters: # a-z, A-Z, 0-9, -, _, and space.
-# If ELASTIC_APM_SERVICE_NAME is not specified, the executable name will be used.
+ title: i18n.translate('kbn.server.tutorials.apm.goClient.configure.title', {
+ defaultMessage: 'Configure the agent',
+ }),
+ textPre: i18n.translate('kbn.server.tutorials.apm.goClient.configure.textPre', {
+ defaultMessage: 'Agents are libraries that run inside of your application process. \
+APM services are created programmatically based on the executable \
+file name, or the `ELASTIC_APM_SERVICE_NAME` environment variable.',
+ }),
+ commands: `# ${i18n.translate('kbn.server.tutorials.apm.goClient.configure.commands.initializeUsingEnvironmentVariablesComment', {
+ defaultMessage: 'Initialize using environment variables:',
+ })}
+
+# ${i18n.translate('kbn.server.tutorials.apm.goClient.configure.commands.setServiceNameComment', {
+ defaultMessage: 'Set the service name. Allowed characters: # a-z, A-Z, 0-9, -, _, and space.',
+ })}
+# ${i18n.translate('kbn.server.tutorials.apm.goClient.configure.commands.usedExecutableNameComment', {
+ defaultMessage: 'If ELASTIC_APM_SERVICE_NAME is not specified, the executable name will be used.',
+ })}
export ELASTIC_APM_SERVICE_NAME=
-# Set the APM Server URL. If unspecified, the agent will effectively be disabled.
+# ${i18n.translate('kbn.server.tutorials.apm.goClient.configure.commands.setAmpServerUrlComment', {
+ defaultMessage: 'Set the APM Server URL. If unspecified, the agent will effectively be disabled.',
+ })}
export ELASTIC_APM_SERVER_URL=
-# Set if APM Server requires a token.
+# ${i18n.translate('kbn.server.tutorials.apm.goClient.configure.commands.setIfAmpServerRequiresTokenComment', {
+ defaultMessage: 'Set if APM Server requires a token.',
+ })}
export ELASTIC_APM_SECRET_TOKEN=
`.split('\n'),
- textPost:
- 'See the [documentation]' +
- '({config.docs.base_url}guide/en/apm/agent/go/current/configuration.html) for advanced configuration.',
+ textPost: i18n.translate('kbn.server.tutorials.apm.goClient.configure.textPost', {
+ defaultMessage: 'See the [documentation]({documenationLink}) for advanced configuration.',
+ values: { documenationLink: '{config.docs.base_url}guide/en/apm/agent/go/current/configuration.html' },
+ }),
},
{
- title: 'Instrument your application',
- textPre:
- 'Instrument your Go application by using one of the provided instrumentation modules or ' +
- 'by using the tracer API directly.',
+ title: i18n.translate('kbn.server.tutorials.apm.goClient.instrument.title', {
+ defaultMessage: 'Instrument your application',
+ }),
+ textPre: i18n.translate('kbn.server.tutorials.apm.goClient.instrument.textPre', {
+ defaultMessage: 'Instrument your Go application by using one of the provided instrumentation modules or \
+by using the tracer API directly.',
+ }),
commands: `
import (
"net/http"
@@ -277,37 +420,46 @@ func main() {curlyOpen}
http.ListenAndServe(":8080", apmhttp.Wrap(mux))
{curlyClose}
`.split('\n'),
- textPost:
- 'See the [documentation]' +
- '({config.docs.base_url}guide/en/apm/agent/go/current/instrumenting-source.html) for a detailed ' +
- 'guide to instrumenting Go source code.\n\n' +
- '**Warning: The Go agent is currently in Beta and not meant for production use.**',
+ textPost: i18n.translate('kbn.server.tutorials.apm.goClient.instrument.textPost', {
+ defaultMessage: 'See the [documentation]({documentationLink}) for a detailed \
+guide to instrumenting Go source code.\n\n\
+**Warning: The Go agent is currently in Beta and not meant for production use.**',
+ values: { documentationLink: '{config.docs.base_url}guide/en/apm/agent/go/current/instrumenting-source.html' },
+ }),
},
];
export const createJavaClientInstructions = () => [
{
- title: 'Download the APM agent',
- textPre:
- 'Download the agent jar from [Maven Central](http://search.maven.org/#search%7Cga%7C1%7Ca%3Aelastic-apm-agent). ' +
- 'Do **not** add the agent as a dependency to your application.',
+ title: i18n.translate('kbn.server.tutorials.apm.javaClient.download.title', {
+ defaultMessage: 'Download the APM agent',
+ }),
+ textPre: i18n.translate('kbn.server.tutorials.apm.javaClient.download.textPre', {
+ defaultMessage: 'Download the agent jar from [Maven Central]({mavenCentralLink}). \
+Do **not** add the agent as a dependency to your application.',
+ values: { mavenCentralLink: 'http://search.maven.org/#search%7Cga%7C1%7Ca%3Aelastic-apm-agent' },
+ }),
},
{
- title: 'Start your application with the javaagent flag',
- textPre:
- 'Add the `-javaagent` flag and configure the agent with system properties.\n' +
- '\n' +
- ' * Set required service name (allowed characters: a-z, A-Z, 0-9, -, _, and space)\n' +
- ' * Set custom APM Server URL (default: http://localhost:8200)\n' +
- ' * Set the base package of your application',
+ title: i18n.translate('kbn.server.tutorials.apm.javaClient.startApplication.title', {
+ defaultMessage: 'Start your application with the javaagent flag',
+ }),
+ textPre: i18n.translate('kbn.server.tutorials.apm.javaClient.startApplication.textPre', {
+ defaultMessage: 'Add the `-javaagent` flag and configure the agent with system properties.\n\n \
+* Set required service name (allowed characters: a-z, A-Z, 0-9, -, _, and space)\n \
+* Set custom APM Server URL (default: {customApmServerUrl})\n \
+* Set the base package of your application',
+ values: { customApmServerUrl: 'http://localhost:8200' },
+ }),
commands: `java -javaagent:/path/to/elastic-apm-agent-.jar \\
-Delastic.apm.service_name=my-application \\
-Delastic.apm.server_url=http://localhost:8200 \\
-Delastic.apm.application_packages=org.example \\
-jar my-application.jar`.split('\n'),
- textPost:
- 'See the [documentation]' +
- '({config.docs.base_url}guide/en/apm/agent/java/current/index.html) for configuration options and advanced usage.\n\n' +
- '**Warning: The Java agent is currently in Beta and not meant for production use.**',
+ textPost: i18n.translate('kbn.server.tutorials.apm.javaClient.startApplication.textPost', {
+ defaultMessage: 'See the [documentation]({documenationLink}) for configuration options and advanced \
+usage.\n\n**Warning: The Java agent is currently in Beta and not meant for production use.**',
+ values: { documenationLink: '{config.docs.base_url}guide/en/apm/agent/java/current/index.html' },
+ }),
},
];
diff --git a/src/core_plugins/kibana/server/tutorials/apm/apm_server_instructions.js b/src/core_plugins/kibana/server/tutorials/apm/apm_server_instructions.js
index 59d4d5fe75c1b..2826aca5194db 100644
--- a/src/core_plugins/kibana/server/tutorials/apm/apm_server_instructions.js
+++ b/src/core_plugins/kibana/server/tutorials/apm/apm_server_instructions.js
@@ -17,11 +17,16 @@
* under the License.
*/
+import { i18n } from '@kbn/i18n';
+
export const createEditConfig = () => ({
- title: 'Edit the configuration',
- textPre:
- `If you're using an X-Pack secured version of Elastic Stack, you must specify` +
- ' credentials in the `apm-server.yml` config file.',
+ title: i18n.translate('kbn.server.tutorials.apm.editConfig.title', {
+ defaultMessage: 'Edit the configuration',
+ }),
+ textPre: i18n.translate('kbn.server.tutorials.apm.editConfig.textPre', {
+ defaultMessage: 'If you\'re using an X-Pack secured version of Elastic Stack, you must specify \
+credentials in the `apm-server.yml` config file.',
+ }),
commands: [
'output.elasticsearch:',
' hosts: [""]',
@@ -31,8 +36,12 @@ export const createEditConfig = () => ({
});
const createStartServer = () => ({
- title: 'Start APM Server',
- textPre: 'The server processes and stores application performance metrics in Elasticsearch.',
+ title: i18n.translate('kbn.server.tutorials.apm.startServer.title', {
+ defaultMessage: 'Start APM Server',
+ }),
+ textPre: i18n.translate('kbn.server.tutorials.apm.startServer.textPre', {
+ defaultMessage: 'The server processes and stores application performance metrics in Elasticsearch.',
+ }),
});
export function createStartServerUnix() {
@@ -45,7 +54,9 @@ export function createStartServerUnix() {
};
}
-const createDownloadServerTitle = () => 'Download and unpack APM Server';
+const createDownloadServerTitle = () => i18n.translate('kbn.server.tutorials.apm.downloadServer.title', {
+ defaultMessage: 'Download and unpack APM Server',
+});
export const createDownloadServerOsx = () => ({
title: createDownloadServerTitle(),
@@ -62,8 +73,10 @@ export const createDownloadServerDeb = () => ({
'curl -L -O https://artifacts.elastic.co/downloads/apm-server/apm-server-{config.kibana.version}-amd64.deb',
'sudo dpkg -i apm-server-{config.kibana.version}-amd64.deb',
],
- textPost:
- 'Looking for the 32-bit packages? See the [Download page]({config.docs.base_url}downloads/apm/apm-server).',
+ textPost: i18n.translate('kbn.server.tutorials.apm.downloadServerTitle', {
+ defaultMessage: 'Looking for the 32-bit packages? See the [Download page]({downloadPageLink}).',
+ values: { downloadPageLink: '{config.docs.base_url}downloads/apm/apm-server' },
+ }),
});
export const createDownloadServerRpm = () => ({
@@ -72,8 +85,10 @@ export const createDownloadServerRpm = () => ({
'curl -L -O https://artifacts.elastic.co/downloads/apm-server/apm-server-{config.kibana.version}-x86_64.rpm',
'sudo rpm -vi apm-server-{config.kibana.version}-x86_64.rpm',
],
- textPost:
- 'Looking for the 32-bit packages? See the [Download page]({config.docs.base_url}downloads/apm/apm-server).',
+ textPost: i18n.translate('kbn.server.tutorials.apm.downloadServerRpm', {
+ defaultMessage: 'Looking for the 32-bit packages? See the [Download page]({downloadPageLink}).',
+ values: { downloadPageLink: '{config.docs.base_url}downloads/apm/apm-server' },
+ }),
});
export function createWindowsServerInstructions() {
@@ -82,20 +97,32 @@ export function createWindowsServerInstructions() {
return [
{
title: createDownloadServerTitle(),
- textPre:
- '1. Download the APM Server Windows zip file from the [Download page](https://www.elastic.co/downloads/apm/apm-server).\n' +
- '2. Extract the contents of the zip file into `C:\\Program Files`.\n' +
- '3. Rename the `apm-server-{config.kibana.version}-windows` directory to `APM-Server`.\n' +
- '4. Open a PowerShell prompt as an Administrator (right-click the PowerShell icon and select' +
- ' **Run As Administrator**). If you are running Windows XP, you might need to download and install PowerShell.\n' +
- '5. From the PowerShell prompt, run the following commands to install APM Server as a Windows service:',
+ textPre: i18n.translate('kbn.server.tutorials.apm.windowsServerInstructions.textPre', {
+ defaultMessage: '1. Download the APM Server Windows zip file from the \
+[Download page]({downloadPageLink}).\n2. Extract the contents of \
+the zip file into {zipFileExtractFolder}.\n3. Rename the {apmServerDirectory} \
+directory to `APM-Server`.\n4. Open a PowerShell prompt as an Administrator \
+(right-click the PowerShell icon and select \
+**Run As Administrator**). If you are running Windows XP, you might need to download and install \
+PowerShell.\n5. From the PowerShell prompt, run the following commands to install APM Server as a Windows service:',
+ values: {
+ downloadPageLink: 'https://www.elastic.co/downloads/apm/apm-server',
+ zipFileExtractFolder: '`C:\\Program Files`',
+ apmServerDirectory: '`apm-server-{config.kibana.version}-windows`',
+ }
+ }),
commands: [
`PS > cd 'C:\\Program Files\\APM-Server'`,
`PS C:\\Program Files\\APM-Server> .\\install-service-apm-server.ps1`,
],
- textPost:
- 'Note: If script execution is disabled on your system, you need to set the execution policy for the current session' +
- ' to allow the script to run. For example: `PowerShell.exe -ExecutionPolicy UnRestricted -File .\\install-service-apm-server.ps1`.',
+ textPost: i18n.translate('kbn.server.tutorials.apm.windowsServerInstructions.textPost', {
+ defaultMessage: 'Note: If script execution is disabled on your system, \
+you need to set the execution policy for the current session \
+to allow the script to run. For example: {command}.',
+ values: {
+ command: '`PowerShell.exe -ExecutionPolicy UnRestricted -File .\\install-service-apm-server.ps1`'
+ }
+ }),
},
createEditConfig(),
{
diff --git a/src/core_plugins/kibana/server/tutorials/apm/elastic_cloud.js b/src/core_plugins/kibana/server/tutorials/apm/elastic_cloud.js
index 4bce847b082e7..0a18d06d3fc81 100644
--- a/src/core_plugins/kibana/server/tutorials/apm/elastic_cloud.js
+++ b/src/core_plugins/kibana/server/tutorials/apm/elastic_cloud.js
@@ -17,6 +17,7 @@
* under the License.
*/
+import { i18n } from '@kbn/i18n';
import { INSTRUCTION_VARIANT } from '../../../common/tutorials/instruction_variant';
import {
@@ -31,9 +32,13 @@ import {
} from './apm_client_instructions';
const createServerUrlInstruction = () => ({
- title: 'APM Server endpoint',
- textPre: `Retrieve the APM Server URL from the Deployments section on the Elastic Cloud dashboard.
- You will also need the APM Server secret token, which was generated on deployment.`,
+ title: i18n.translate('kbn.server.tutorials.apm.serverUrlInstruction.title', {
+ defaultMessage: 'APM Server endpoint',
+ }),
+ textPre: i18n.translate('kbn.server.tutorials.apm.serverUrlInstruction.textPre', {
+ defaultMessage: 'Retrieve the APM Server URL from the Deployments section on the Elastic Cloud dashboard. \
+You will also need the APM Server secret token, which was generated on deployment.',
+ }),
});
export function createElasticCloudInstructions() {
@@ -42,7 +47,9 @@ export function createElasticCloudInstructions() {
return {
instructionSets: [
{
- title: 'APM Agents',
+ title: i18n.translate('kbn.server.tutorials.apm.elasticCloudInstructions.title', {
+ defaultMessage: 'APM Agents',
+ }),
instructionVariants: [
{
id: INSTRUCTION_VARIANT.NODE,
diff --git a/src/core_plugins/kibana/server/tutorials/apm/index.js b/src/core_plugins/kibana/server/tutorials/apm/index.js
index fb92e9acb9609..512805a4231dd 100644
--- a/src/core_plugins/kibana/server/tutorials/apm/index.js
+++ b/src/core_plugins/kibana/server/tutorials/apm/index.js
@@ -17,12 +17,15 @@
* under the License.
*/
+import { i18n } from '@kbn/i18n';
import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category';
import { onPremInstructions } from './on_prem';
import { createElasticCloudInstructions } from './elastic_cloud';
import { getSavedObjects } from './saved_objects/get_saved_objects';
-const apmIntro = 'Collect in-depth performance metrics and errors from inside your applications.';
+const apmIntro = i18n.translate('kbn.server.tutorials.apm.introduction', {
+ defaultMessage: 'Collect in-depth performance metrics and errors from inside your applications.',
+});
function isEnabled(config) {
const ENABLED_KEY = 'xpack.apm.ui.enabled';
@@ -41,7 +44,9 @@ export function apmSpecProvider(server) {
dashboards: [
{
id: '8d3ed660-7828-11e7-8c47-65b845b5cfb3',
- linkLabel: 'APM dashboard',
+ linkLabel: i18n.translate('kbn.server.tutorials.apm.specProvider.artifacts.dashboards.linkLabel', {
+ defaultMessage: 'APM dashboard',
+ }),
isOverview: true,
},
],
@@ -50,28 +55,35 @@ export function apmSpecProvider(server) {
if (isEnabled(config)) {
artifacts.application = {
path: '/app/apm',
- label: 'Launch APM',
+ label: i18n.translate('kbn.server.tutorials.apm.specProvider.artifacts.application.label', {
+ defaultMessage: 'Launch APM',
+ }),
};
}
return {
id: 'apm',
- name: 'APM',
+ name: i18n.translate('kbn.server.tutorials.apm.specProvider.name', {
+ defaultMessage: 'APM',
+ }),
category: TUTORIAL_CATEGORY.OTHER,
shortDescription: apmIntro,
- longDescription:
- 'Application Performance Monitoring (APM) collects in-depth' +
- ' performance metrics and errors from inside your application.' +
- ' It allows you to monitor the performance of thousands of applications in real time.' +
- ' [Learn more]({config.docs.base_url}guide/en/apm/get-started/{config.docs.version}/index.html).',
+ longDescription: i18n.translate('kbn.server.tutorials.apm.specProvider.longDescription', {
+ defaultMessage: 'Application Performance Monitoring (APM) collects in-depth \
+performance metrics and errors from inside your application. \
+It allows you to monitor the performance of thousands of applications in real time. \
+[Learn more]({learnMoreLink}).',
+ values: { learnMoreLink: '{config.docs.base_url}guide/en/apm/get-started/{config.docs.version}/index.html' },
+ }),
euiIconType: 'apmApp',
artifacts: artifacts,
onPrem: onPremInstructions(apmIndexPattern),
elasticCloud: createElasticCloudInstructions(),
previewImagePath: '/plugins/kibana/home/tutorial_resources/apm/apm.png',
savedObjects: getSavedObjects(apmIndexPattern),
- savedObjectsInstallMsg:
- 'Load index pattern, visualizations, and pre-defined dashboards.' +
- ' An index pattern is required for some features in the APM UI.',
+ savedObjectsInstallMsg: i18n.translate('kbn.server.tutorials.apm.specProvider.savedObjectsInstallMsg', {
+ defaultMessage: 'Load index pattern, visualizations, and pre-defined dashboards. \
+An index pattern is required for some features in the APM UI.',
+ }),
};
}
diff --git a/src/core_plugins/kibana/server/tutorials/apm/on_prem.js b/src/core_plugins/kibana/server/tutorials/apm/on_prem.js
index b1a3938592e73..a75ce27384b21 100644
--- a/src/core_plugins/kibana/server/tutorials/apm/on_prem.js
+++ b/src/core_plugins/kibana/server/tutorials/apm/on_prem.js
@@ -17,6 +17,7 @@
* under the License.
*/
+import { i18n } from '@kbn/i18n';
import { INSTRUCTION_VARIANT } from '../../../common/tutorials/instruction_variant';
import {
createWindowsServerInstructions,
@@ -44,7 +45,9 @@ export function onPremInstructions(apmIndexPattern) {
return {
instructionSets: [
{
- title: 'APM Server',
+ title: i18n.translate('kbn.server.tutorials.apm.apmServer.title', {
+ defaultMessage: 'APM Server',
+ }),
instructionVariants: [
{
id: INSTRUCTION_VARIANT.OSX,
@@ -64,11 +67,21 @@ export function onPremInstructions(apmIndexPattern) {
},
],
statusCheck: {
- title: 'APM Server status',
- text: 'Make sure APM Server is running before you start implementing the APM agents.',
- btnLabel: 'Check APM Server status',
- success: 'You have correctly setup APM-Server',
- error: 'APM-Server has still not connected to Elasticsearch',
+ title: i18n.translate('kbn.server.tutorials.apm.apmServer.statusCheck.title', {
+ defaultMessage: 'APM Server status',
+ }),
+ text: i18n.translate('kbn.server.tutorials.apm.apmServer.statusCheck.text', {
+ defaultMessage: 'Make sure APM Server is running before you start implementing the APM agents.',
+ }),
+ btnLabel: i18n.translate('kbn.server.tutorials.apm.apmServer.statusCheck.btnLabel', {
+ defaultMessage: 'Check APM Server status',
+ }),
+ success: i18n.translate('kbn.server.tutorials.apm.apmServer.statusCheck.successMessage', {
+ defaultMessage: 'You have correctly setup APM-Server',
+ }),
+ error: i18n.translate('kbn.server.tutorials.apm.apmServer.statusCheck.errorMessage', {
+ defaultMessage: 'APM-Server has still not connected to Elasticsearch',
+ }),
esHitsCheck: {
index: apmIndexPattern,
query: {
@@ -84,7 +97,9 @@ export function onPremInstructions(apmIndexPattern) {
},
},
{
- title: 'APM Agents',
+ title: i18n.translate('kbn.server.tutorials.apm.apmAgents.title', {
+ defaultMessage: 'APM Agents',
+ }),
instructionVariants: [
{
id: INSTRUCTION_VARIANT.NODE,
@@ -120,11 +135,21 @@ export function onPremInstructions(apmIndexPattern) {
},
],
statusCheck: {
- title: 'Agent status',
- text: 'Make sure your application is running and the agents are sending data.',
- btnLabel: 'Check agent status',
- success: 'Data successfully received from one or more agents',
- error: `No data has been received from agents yet`,
+ title: i18n.translate('kbn.server.tutorials.apm.apmAgents.statusCheck.title', {
+ defaultMessage: 'Agent status',
+ }),
+ text: i18n.translate('kbn.server.tutorials.apm.apmAgents.statusCheck.text', {
+ defaultMessage: 'Make sure your application is running and the agents are sending data.',
+ }),
+ btnLabel: i18n.translate('kbn.server.tutorials.apm.apmAgents.statusCheck.btnLabel', {
+ defaultMessage: 'Check agent status',
+ }),
+ success: i18n.translate('kbn.server.tutorials.apm.apmAgents.statusCheck.successMessage', {
+ defaultMessage: 'Data successfully received from one or more agents',
+ }),
+ error: i18n.translate('kbn.server.tutorials.apm.apmAgents.statusCheck.errorMessage', {
+ defaultMessage: 'No data has been received from agents yet',
+ }),
esHitsCheck: {
index: apmIndexPattern,
query: {
From a2265211184235de329dbde065a9f0728b6fa70a Mon Sep 17 00:00:00 2001
From: Larry Gregory
Date: Thu, 6 Sep 2018 08:30:35 -0400
Subject: [PATCH 27/68] [Spaces] - Experimental Public spaces api (#22501)
[skip ci]
---
docs/api/spaces-management/delete.asciidoc | 25 ++
docs/api/spaces-management/get.asciidoc | 77 +++++
docs/api/spaces-management/post.asciidoc | 50 ++++
docs/api/spaces-management/put.asciidoc | 50 ++++
docs/api/spaces.asciidoc | 17 ++
.../spaces/common/is_reserved_space.ts | 2 +-
x-pack/plugins/spaces/index.js | 6 +-
.../spaces/public/lib/spaces_manager.ts | 6 +-
.../server/lib/{errors.js => errors.ts} | 4 +-
..._license.js => route_pre_check_license.ts} | 6 +-
.../lib/{space_schema.js => space_schema.ts} | 2 +-
...ces_url_parser.js => spaces_url_parser.ts} | 13 +-
.../routes/api/__fixtures__/create_spaces.ts | 29 ++
.../api/__fixtures__/create_test_handler.ts | 142 +++++++++
.../server/routes/api/__fixtures__/index.ts | 15 +
.../server/routes/api/public/delete.test.ts | 85 ++++++
.../spaces/server/routes/api/public/delete.ts | 42 +++
.../server/routes/api/public/get.test.ts | 86 ++++++
.../spaces/server/routes/api/public/get.ts | 61 ++++
.../spaces/server/routes/api/public/index.ts | 20 ++
.../server/routes/api/public/post.test.ts | 106 +++++++
.../spaces/server/routes/api/public/post.ts | 46 +++
.../server/routes/api/public/put.test.ts | 97 ++++++
.../spaces/server/routes/api/public/put.ts | 49 +++
.../spaces/server/routes/api/v1/index.ts | 13 +
.../spaces/server/routes/api/v1/spaces.js | 202 -------------
.../server/routes/api/v1/spaces.test.js | 281 ------------------
.../server/routes/api/v1/spaces.test.ts | 93 ++++++
.../spaces/server/routes/api/v1/spaces.ts | 45 +++
.../lib/convert_saved_object_to_space.test.ts | 27 ++
.../lib/convert_saved_object_to_space.ts | 20 ++
.../server/routes/lib/get_space_by_id.ts | 19 ++
.../plugins/spaces/server/routes/lib/index.ts | 8 +
33 files changed, 1246 insertions(+), 498 deletions(-)
create mode 100644 docs/api/spaces-management/delete.asciidoc
create mode 100644 docs/api/spaces-management/get.asciidoc
create mode 100644 docs/api/spaces-management/post.asciidoc
create mode 100644 docs/api/spaces-management/put.asciidoc
create mode 100644 docs/api/spaces.asciidoc
rename x-pack/plugins/spaces/server/lib/{errors.js => errors.ts} (85%)
rename x-pack/plugins/spaces/server/lib/{route_pre_check_license.js => route_pre_check_license.ts} (80%)
rename x-pack/plugins/spaces/server/lib/{space_schema.js => space_schema.ts} (96%)
rename x-pack/plugins/spaces/server/lib/{spaces_url_parser.js => spaces_url_parser.ts} (80%)
create mode 100644 x-pack/plugins/spaces/server/routes/api/__fixtures__/create_spaces.ts
create mode 100644 x-pack/plugins/spaces/server/routes/api/__fixtures__/create_test_handler.ts
create mode 100644 x-pack/plugins/spaces/server/routes/api/__fixtures__/index.ts
create mode 100644 x-pack/plugins/spaces/server/routes/api/public/delete.test.ts
create mode 100644 x-pack/plugins/spaces/server/routes/api/public/delete.ts
create mode 100644 x-pack/plugins/spaces/server/routes/api/public/get.test.ts
create mode 100644 x-pack/plugins/spaces/server/routes/api/public/get.ts
create mode 100644 x-pack/plugins/spaces/server/routes/api/public/index.ts
create mode 100644 x-pack/plugins/spaces/server/routes/api/public/post.test.ts
create mode 100644 x-pack/plugins/spaces/server/routes/api/public/post.ts
create mode 100644 x-pack/plugins/spaces/server/routes/api/public/put.test.ts
create mode 100644 x-pack/plugins/spaces/server/routes/api/public/put.ts
create mode 100644 x-pack/plugins/spaces/server/routes/api/v1/index.ts
delete mode 100644 x-pack/plugins/spaces/server/routes/api/v1/spaces.js
delete mode 100644 x-pack/plugins/spaces/server/routes/api/v1/spaces.test.js
create mode 100644 x-pack/plugins/spaces/server/routes/api/v1/spaces.test.ts
create mode 100644 x-pack/plugins/spaces/server/routes/api/v1/spaces.ts
create mode 100644 x-pack/plugins/spaces/server/routes/lib/convert_saved_object_to_space.test.ts
create mode 100644 x-pack/plugins/spaces/server/routes/lib/convert_saved_object_to_space.ts
create mode 100644 x-pack/plugins/spaces/server/routes/lib/get_space_by_id.ts
create mode 100644 x-pack/plugins/spaces/server/routes/lib/index.ts
diff --git a/docs/api/spaces-management/delete.asciidoc b/docs/api/spaces-management/delete.asciidoc
new file mode 100644
index 0000000000000..c5ae025dd9e2e
--- /dev/null
+++ b/docs/api/spaces-management/delete.asciidoc
@@ -0,0 +1,25 @@
+[[spaces-api-delete]]
+=== Delete space
+
+experimental[This API is *experimental* and may be changed or removed completely in a future release. The underlying Spaces concepts are stable, but the APIs for managing Spaces are currently experimental.]
+
+[WARNING]
+==================================================
+Deleting a space will automatically delete all saved objects that belong to that space. This operation cannot be undone!
+==================================================
+
+==== Request
+
+To delete a space, submit a DELETE request to the `/api/spaces/space/`
+endpoint:
+
+[source,js]
+--------------------------------------------------
+DELETE /api/spaces/space/marketing
+--------------------------------------------------
+// KIBANA
+
+==== Response
+
+If the space is successfully deleted, the response code is `204`; otherwise, the response
+code is 404.
diff --git a/docs/api/spaces-management/get.asciidoc b/docs/api/spaces-management/get.asciidoc
new file mode 100644
index 0000000000000..c79a883a80e4b
--- /dev/null
+++ b/docs/api/spaces-management/get.asciidoc
@@ -0,0 +1,77 @@
+[[spaces-api-get]]
+=== Get Space
+
+experimental[This API is *experimental* and may be changed or removed completely in a future release. The underlying Spaces concepts are stable, but the APIs for managing Spaces are currently experimental.]
+
+Retrieves all {kib} spaces, or a specific space.
+
+==== Get all {kib} spaces
+
+===== Request
+
+To retrieve all spaces, issue a GET request to the
+/api/spaces/space endpoint.
+
+[source,js]
+--------------------------------------------------
+GET /api/spaces/space
+--------------------------------------------------
+// KIBANA
+
+===== Response
+
+A successful call returns a response code of `200` and a response body containing a JSON
+representation of the spaces.
+
+[source,js]
+--------------------------------------------------
+[
+ {
+ "id": "default",
+ "name": "Default",
+ "description" : "This is the Default Space",
+ "_reserved": true
+ },
+ {
+ "id": "marketing",
+ "name": "Marketing",
+ "description" : "This is the Marketing Space",
+ "color": "#aabbcc",
+ "initials": "MK"
+ },
+ {
+ "id": "sales",
+ "name": "Sales",
+ "initials": "MK"
+ },
+]
+--------------------------------------------------
+
+==== Get a specific space
+
+===== Request
+
+To retrieve a specific space, issue a GET request to
+the `/api/spaces/space/` endpoint:
+
+[source,js]
+--------------------------------------------------
+GET /api/spaces/space/marketing
+--------------------------------------------------
+// KIBANA
+
+===== Response
+
+A successful call returns a response code of `200` and a response body containing a JSON
+representation of the space.
+
+[source,js]
+--------------------------------------------------
+{
+ "id": "marketing",
+ "name": "Marketing",
+ "description" : "This is the Marketing Space",
+ "color": "#aabbcc",
+ "initials": "MK"
+}
+--------------------------------------------------
diff --git a/docs/api/spaces-management/post.asciidoc b/docs/api/spaces-management/post.asciidoc
new file mode 100644
index 0000000000000..569835c78b2f8
--- /dev/null
+++ b/docs/api/spaces-management/post.asciidoc
@@ -0,0 +1,50 @@
+[[spaces-api-post]]
+=== Create Space
+
+experimental[This API is *experimental* and may be changed or removed completely in a future release. The underlying Spaces concepts are stable, but the APIs for managing Spaces are currently experimental.]
+
+Creates a new {kib} space. To update an existing space, use the PUT command.
+
+==== Request
+
+To create a space, issue a POST request to the
+`/api/spaces/space` endpoint.
+
+[source,js]
+--------------------------------------------------
+PUT /api/spaces/space
+--------------------------------------------------
+
+==== Request Body
+
+The following parameters can be specified in the body of a POST request to create a space:
+
+`id`:: (string) Required identifier for the space. This identifier becomes part of Kibana's URL when inside the space. This cannot be changed by the update operation.
+
+`name`:: (string) Required display name for the space.
+
+`description`:: (string) Optional description for the space.
+
+`initials`:: (string) Optionally specify the initials shown in the Space Avatar for this space. By default, the initials will be automatically generated from the space name.
+If specified, initials should be either 1 or 2 characters.
+
+`color`:: (string) Optioanlly specify the hex color code used in the Space Avatar for this space. By default, the color will be automatically generated from the space name.
+
+===== Example
+
+[source,js]
+--------------------------------------------------
+POST /api/spaces/space
+{
+ "id": "marketing",
+ "name": "Marketing",
+ "description" : "This is the Marketing Space",
+ "color": "#aabbcc",
+ "initials": "MK"
+}
+--------------------------------------------------
+// KIBANA
+
+==== Response
+
+A successful call returns a response code of `200` with the created Space.
diff --git a/docs/api/spaces-management/put.asciidoc b/docs/api/spaces-management/put.asciidoc
new file mode 100644
index 0000000000000..529742bf2ce66
--- /dev/null
+++ b/docs/api/spaces-management/put.asciidoc
@@ -0,0 +1,50 @@
+[[spaces-api-put]]
+=== Update Space
+
+experimental[This API is *experimental* and may be changed or removed completely in a future release. The underlying Spaces concepts are stable, but the APIs for managing Spaces are currently experimental.]
+
+Updates an existing {kib} space. To create a new space, use the POST command.
+
+==== Request
+
+To update a space, issue a PUT request to the
+`/api/spaces/space/` endpoint.
+
+[source,js]
+--------------------------------------------------
+PUT /api/spaces/space/
+--------------------------------------------------
+
+==== Request Body
+
+The following parameters can be specified in the body of a PUT request to update a space:
+
+`id`:: (string) Required identifier for the space. This identifier becomes part of Kibana's URL when inside the space. This cannot be changed by the update operation.
+
+`name`:: (string) Required display name for the space.
+
+`description`:: (string) Optional description for the space.
+
+`initials`:: (string) Optionally specify the initials shown in the Space Avatar for this space. By default, the initials will be automatically generated from the space name.
+If specified, initials should be either 1 or 2 characters.
+
+`color`:: (string) Optioanlly specify the hex color code used in the Space Avatar for this space. By default, the color will be automatically generated from the space name.
+
+===== Example
+
+[source,js]
+--------------------------------------------------
+PUT /api/spaces/space/marketing
+{
+ "id": "marketing",
+ "name": "Marketing",
+ "description" : "This is the Marketing Space",
+ "color": "#aabbcc",
+ "initials": "MK"
+}
+--------------------------------------------------
+// KIBANA
+
+==== Response
+
+A successful call returns a response code of `200` with the updated Space.
diff --git a/docs/api/spaces.asciidoc b/docs/api/spaces.asciidoc
new file mode 100644
index 0000000000000..ea66d50d396b9
--- /dev/null
+++ b/docs/api/spaces.asciidoc
@@ -0,0 +1,17 @@
+[role="xpack"]
+[[spaces-api]]
+== Kibana Spaces API
+
+experimental[This API is *experimental* and may be changed or removed completely in a future release. The underlying Spaces concepts are stable, but the APIs for managing Spaces are currently experimental.]
+
+The spaces API allows people to manage their spaces within {kib}.
+
+* <>
+* <>
+* <>
+* <>
+
+include::spaces-management/put.asciidoc[]
+include::spaces-management/post.asciidoc[]
+include::spaces-management/get.asciidoc[]
+include::spaces-management/delete.asciidoc[]
diff --git a/x-pack/plugins/spaces/common/is_reserved_space.ts b/x-pack/plugins/spaces/common/is_reserved_space.ts
index 0889686aa77f5..40acd7630b66c 100644
--- a/x-pack/plugins/spaces/common/is_reserved_space.ts
+++ b/x-pack/plugins/spaces/common/is_reserved_space.ts
@@ -13,6 +13,6 @@ import { Space } from './model/space';
* @param space the space
* @returns boolean
*/
-export function isReservedSpace(space: Space): boolean {
+export function isReservedSpace(space: Space | null): boolean {
return get(space, '_reserved', false);
}
diff --git a/x-pack/plugins/spaces/index.js b/x-pack/plugins/spaces/index.js
index f03e8bde19afa..3eafab6461f9e 100644
--- a/x-pack/plugins/spaces/index.js
+++ b/x-pack/plugins/spaces/index.js
@@ -7,7 +7,8 @@
import { resolve } from 'path';
import { validateConfig } from './server/lib/validate_config';
import { checkLicense } from './server/lib/check_license';
-import { initSpacesApi } from './server/routes/api/v1/spaces';
+import { initPublicSpacesApi } from './server/routes/api/public';
+import { initPrivateApis } from './server/routes/api/v1';
import { initSpacesRequestInterceptors } from './server/lib/space_request_interceptors';
import { createDefaultSpace } from './server/lib/create_default_space';
import { createSpacesService } from './server/lib/create_spaces_service';
@@ -93,7 +94,8 @@ export const spaces = (kibana) => new kibana.Plugin({
spacesSavedObjectsClientWrapperFactory(spacesService)
);
- initSpacesApi(server);
+ initPrivateApis(server);
+ initPublicSpacesApi(server);
initSpacesRequestInterceptors(server);
diff --git a/x-pack/plugins/spaces/public/lib/spaces_manager.ts b/x-pack/plugins/spaces/public/lib/spaces_manager.ts
index a6b21f2fa5229..53835ab122f87 100644
--- a/x-pack/plugins/spaces/public/lib/spaces_manager.ts
+++ b/x-pack/plugins/spaces/public/lib/spaces_manager.ts
@@ -16,12 +16,12 @@ export class SpacesManager extends EventEmitter {
constructor(httpAgent: any, chrome: any) {
super();
this.httpAgent = httpAgent;
- this.baseUrl = chrome.addBasePath(`/api/spaces/v1`);
+ this.baseUrl = chrome.addBasePath(`/api/spaces`);
}
public async getSpaces(): Promise {
return await this.httpAgent
- .get(`${this.baseUrl}/spaces`)
+ .get(`${this.baseUrl}/space`)
.then((response: IHttpResponse) => response.data);
}
@@ -43,7 +43,7 @@ export class SpacesManager extends EventEmitter {
public async changeSelectedSpace(space: Space) {
return await this.httpAgent
- .post(`${this.baseUrl}/space/${space.id}/select`)
+ .post(`${this.baseUrl}/v1/space/${space.id}/select`)
.then((response: IHttpResponse) => {
if (response.data && response.data.location) {
window.location = response.data.location;
diff --git a/x-pack/plugins/spaces/server/lib/errors.js b/x-pack/plugins/spaces/server/lib/errors.ts
similarity index 85%
rename from x-pack/plugins/spaces/server/lib/errors.js
rename to x-pack/plugins/spaces/server/lib/errors.ts
index 6996faeaac8de..4f95c175b0f15 100644
--- a/x-pack/plugins/spaces/server/lib/errors.js
+++ b/x-pack/plugins/spaces/server/lib/errors.ts
@@ -3,9 +3,9 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
-
+// @ts-ignore
import { wrap as wrapBoom } from 'boom';
-export function wrapError(error) {
+export function wrapError(error: any) {
return wrapBoom(error, error.status);
}
diff --git a/x-pack/plugins/spaces/server/lib/route_pre_check_license.js b/x-pack/plugins/spaces/server/lib/route_pre_check_license.ts
similarity index 80%
rename from x-pack/plugins/spaces/server/lib/route_pre_check_license.js
rename to x-pack/plugins/spaces/server/lib/route_pre_check_license.ts
index 891e9fc4125a9..449836633993c 100644
--- a/x-pack/plugins/spaces/server/lib/route_pre_check_license.js
+++ b/x-pack/plugins/spaces/server/lib/route_pre_check_license.ts
@@ -4,12 +4,12 @@
* you may not use this file except in compliance with the Elastic License.
*/
-const Boom = require('boom');
+import Boom from 'boom';
-export function routePreCheckLicense(server) {
+export function routePreCheckLicense(server: any) {
const xpackMainPlugin = server.plugins.xpack_main;
const pluginId = 'spaces';
- return function forbidApiAccess(request, reply) {
+ return function forbidApiAccess(request: any, reply: any) {
const licenseCheckResults = xpackMainPlugin.info.feature(pluginId).getLicenseCheckResults();
if (!licenseCheckResults.showSpaces) {
reply(Boom.forbidden(licenseCheckResults.linksMessage));
diff --git a/x-pack/plugins/spaces/server/lib/space_schema.js b/x-pack/plugins/spaces/server/lib/space_schema.ts
similarity index 96%
rename from x-pack/plugins/spaces/server/lib/space_schema.js
rename to x-pack/plugins/spaces/server/lib/space_schema.ts
index dc60c10a36bc2..043856235acba 100644
--- a/x-pack/plugins/spaces/server/lib/space_schema.js
+++ b/x-pack/plugins/spaces/server/lib/space_schema.ts
@@ -13,5 +13,5 @@ export const spaceSchema = Joi.object({
description: Joi.string(),
initials: Joi.string().max(MAX_SPACE_INITIALS),
color: Joi.string().regex(/^#[a-z0-9]{6}$/, `6 digit hex color, starting with a #`),
- _reserved: Joi.boolean()
+ _reserved: Joi.boolean(),
}).default();
diff --git a/x-pack/plugins/spaces/server/lib/spaces_url_parser.js b/x-pack/plugins/spaces/server/lib/spaces_url_parser.ts
similarity index 80%
rename from x-pack/plugins/spaces/server/lib/spaces_url_parser.js
rename to x-pack/plugins/spaces/server/lib/spaces_url_parser.ts
index 397863785e86c..14113cbf9d807 100644
--- a/x-pack/plugins/spaces/server/lib/spaces_url_parser.js
+++ b/x-pack/plugins/spaces/server/lib/spaces_url_parser.ts
@@ -5,8 +5,11 @@
*/
import { DEFAULT_SPACE_ID } from '../../common/constants';
-export function getSpaceIdFromPath(requestBasePath = '/', serverBasePath = '/') {
- let pathToCheck = requestBasePath;
+export function getSpaceIdFromPath(
+ requestBasePath: string = '/',
+ serverBasePath: string = '/'
+): string {
+ let pathToCheck: string = requestBasePath;
if (serverBasePath && serverBasePath !== '/' && requestBasePath.startsWith(serverBasePath)) {
pathToCheck = requestBasePath.substr(serverBasePath.length);
@@ -28,7 +31,11 @@ export function getSpaceIdFromPath(requestBasePath = '/', serverBasePath = '/')
return spaceId;
}
-export function addSpaceIdToPath(basePath = '/', spaceId = '', requestedPath = '') {
+export function addSpaceIdToPath(
+ basePath: string = '/',
+ spaceId: string = '',
+ requestedPath: string = ''
+): string {
if (requestedPath && !requestedPath.startsWith('/')) {
throw new Error(`path must start with a /`);
}
diff --git a/x-pack/plugins/spaces/server/routes/api/__fixtures__/create_spaces.ts b/x-pack/plugins/spaces/server/routes/api/__fixtures__/create_spaces.ts
new file mode 100644
index 0000000000000..85284e3fc3a1c
--- /dev/null
+++ b/x-pack/plugins/spaces/server/routes/api/__fixtures__/create_spaces.ts
@@ -0,0 +1,29 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export function createSpaces() {
+ return [
+ {
+ id: 'a-space',
+ attributes: {
+ name: 'a space',
+ },
+ },
+ {
+ id: 'b-space',
+ attributes: {
+ name: 'b space',
+ },
+ },
+ {
+ id: 'default',
+ attributes: {
+ name: 'Default Space',
+ _reserved: true,
+ },
+ },
+ ];
+}
diff --git a/x-pack/plugins/spaces/server/routes/api/__fixtures__/create_test_handler.ts b/x-pack/plugins/spaces/server/routes/api/__fixtures__/create_test_handler.ts
new file mode 100644
index 0000000000000..a184f21076d4f
--- /dev/null
+++ b/x-pack/plugins/spaces/server/routes/api/__fixtures__/create_test_handler.ts
@@ -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;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+// @ts-ignore
+import { Server } from 'hapi';
+import { createSpaces } from './create_spaces';
+
+export interface TestConfig {
+ [configKey: string]: any;
+}
+
+export interface TestOptions {
+ setupFn?: (server: any) => void;
+ testConfig?: TestConfig;
+ payload?: any;
+ preCheckLicenseImpl?: (req: any, reply: any) => any;
+}
+
+export type TeardownFn = () => void;
+
+export interface RequestRunnerResult {
+ server: any;
+ mockSavedObjectsClient: any;
+ response: any;
+}
+
+export type RequestRunner = (
+ method: string,
+ path: string,
+ options?: TestOptions
+) => Promise;
+
+export const defaultPreCheckLicenseImpl = (request: any, reply: any) => reply();
+
+const baseConfig: TestConfig = {
+ 'server.basePath': '',
+};
+
+export function createTestHandler(initApiFn: (server: any, preCheckLicenseImpl: any) => void) {
+ const teardowns: TeardownFn[] = [];
+
+ const spaces = createSpaces();
+
+ const request: RequestRunner = async (
+ method: string,
+ path: string,
+ options: TestOptions = {}
+ ) => {
+ const {
+ setupFn = () => {
+ return;
+ },
+ testConfig = {},
+ payload,
+ preCheckLicenseImpl = defaultPreCheckLicenseImpl,
+ } = options;
+
+ let pre = jest.fn();
+ if (preCheckLicenseImpl) {
+ pre = pre.mockImplementation(preCheckLicenseImpl);
+ }
+
+ const server = new Server();
+
+ const config = {
+ ...baseConfig,
+ ...testConfig,
+ };
+
+ server.connection({ port: 0 });
+
+ await setupFn(server);
+
+ server.decorate(
+ 'server',
+ 'config',
+ jest.fn(() => {
+ return {
+ get: (key: string) => config[key],
+ };
+ })
+ );
+
+ initApiFn(server, pre);
+
+ server.decorate('request', 'getBasePath', jest.fn());
+ server.decorate('request', 'setBasePath', jest.fn());
+
+ // Mock server.getSavedObjectsClient()
+ const mockSavedObjectsClient = {
+ get: jest.fn((type, id) => {
+ return spaces.filter(s => s.id === id)[0];
+ }),
+ find: jest.fn(() => {
+ return {
+ total: spaces.length,
+ saved_objects: spaces,
+ };
+ }),
+ create: jest.fn(() => ({})),
+ update: jest.fn(() => ({})),
+ delete: jest.fn(),
+ errors: {
+ isNotFoundError: jest.fn(() => true),
+ },
+ };
+
+ server.decorate('request', 'getSavedObjectsClient', () => mockSavedObjectsClient);
+
+ teardowns.push(() => server.stop());
+
+ const testRun = async () => {
+ const response = await server.inject({
+ method,
+ url: path,
+ payload,
+ });
+
+ if (preCheckLicenseImpl) {
+ expect(pre).toHaveBeenCalled();
+ } else {
+ expect(pre).not.toHaveBeenCalled();
+ }
+
+ return response;
+ };
+
+ return {
+ server,
+ mockSavedObjectsClient,
+ response: await testRun(),
+ };
+ };
+
+ return {
+ request,
+ teardowns,
+ };
+}
diff --git a/x-pack/plugins/spaces/server/routes/api/__fixtures__/index.ts b/x-pack/plugins/spaces/server/routes/api/__fixtures__/index.ts
new file mode 100644
index 0000000000000..37fe32c80032e
--- /dev/null
+++ b/x-pack/plugins/spaces/server/routes/api/__fixtures__/index.ts
@@ -0,0 +1,15 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export { createSpaces } from './create_spaces';
+export {
+ createTestHandler,
+ TestConfig,
+ TestOptions,
+ TeardownFn,
+ RequestRunner,
+ RequestRunnerResult,
+} from './create_test_handler';
diff --git a/x-pack/plugins/spaces/server/routes/api/public/delete.test.ts b/x-pack/plugins/spaces/server/routes/api/public/delete.test.ts
new file mode 100644
index 0000000000000..523b5a2cb2e7b
--- /dev/null
+++ b/x-pack/plugins/spaces/server/routes/api/public/delete.test.ts
@@ -0,0 +1,85 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+jest.mock('../../../lib/route_pre_check_license', () => {
+ return {
+ routePreCheckLicense: () => (request: any, reply: any) => reply.continue(),
+ };
+});
+
+jest.mock('../../../../../../server/lib/get_client_shield', () => {
+ return {
+ getClient: () => {
+ return {
+ callWithInternalUser: jest.fn(() => {
+ return;
+ }),
+ };
+ },
+ };
+});
+import Boom from 'boom';
+import { createTestHandler, RequestRunner, TeardownFn } from '../__fixtures__';
+import { initDeleteSpacesApi } from './delete';
+
+describe('Spaces Public API', () => {
+ let request: RequestRunner;
+ let teardowns: TeardownFn[];
+
+ beforeEach(() => {
+ const setup = createTestHandler(initDeleteSpacesApi);
+
+ request = setup.request;
+ teardowns = setup.teardowns;
+ });
+
+ afterEach(async () => {
+ await Promise.all(teardowns.splice(0).map(fn => fn()));
+ });
+
+ test(`'DELETE spaces/{id}' deletes the space`, async () => {
+ const { response } = await request('DELETE', '/api/spaces/space/a-space');
+
+ const { statusCode } = response;
+
+ expect(statusCode).toEqual(204);
+ });
+
+ test(`returns result of routePreCheckLicense`, async () => {
+ const { response } = await request('DELETE', '/api/spaces/space/a-space', {
+ preCheckLicenseImpl: (req: any, reply: any) =>
+ reply(Boom.forbidden('test forbidden message')),
+ });
+
+ const { statusCode, payload } = response;
+
+ expect(statusCode).toEqual(403);
+ expect(JSON.parse(payload)).toMatchObject({
+ message: 'test forbidden message',
+ });
+ });
+
+ test('DELETE spaces/{id} pretends to delete a non-existent space', async () => {
+ const { response } = await request('DELETE', '/api/spaces/space/not-a-space');
+
+ const { statusCode } = response;
+
+ expect(statusCode).toEqual(204);
+ });
+
+ test(`'DELETE spaces/{id}' cannot delete reserved spaces`, async () => {
+ const { response } = await request('DELETE', '/api/spaces/space/default');
+
+ const { statusCode, payload } = response;
+
+ expect(statusCode).toEqual(400);
+ expect(JSON.parse(payload)).toEqual({
+ statusCode: 400,
+ error: 'Bad Request',
+ message: 'This Space cannot be deleted because it is reserved.',
+ });
+ });
+});
diff --git a/x-pack/plugins/spaces/server/routes/api/public/delete.ts b/x-pack/plugins/spaces/server/routes/api/public/delete.ts
new file mode 100644
index 0000000000000..9937b786ccfa7
--- /dev/null
+++ b/x-pack/plugins/spaces/server/routes/api/public/delete.ts
@@ -0,0 +1,42 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import Boom from 'boom';
+import { isReservedSpace } from '../../../../common/is_reserved_space';
+import { wrapError } from '../../../lib/errors';
+import { getSpaceById } from '../../lib';
+
+export function initDeleteSpacesApi(server: any, routePreCheckLicenseFn: any) {
+ server.route({
+ method: 'DELETE',
+ path: '/api/spaces/space/{id}',
+ async handler(request: any, reply: any) {
+ const client = request.getSavedObjectsClient();
+
+ const id = request.params.id;
+
+ let result;
+
+ try {
+ const existingSpace = await getSpaceById(client, id);
+ if (isReservedSpace(existingSpace)) {
+ return reply(
+ wrapError(Boom.badRequest('This Space cannot be deleted because it is reserved.'))
+ );
+ }
+
+ result = await client.delete('space', id);
+ } catch (error) {
+ return reply(wrapError(error));
+ }
+
+ return reply(result).code(204);
+ },
+ config: {
+ pre: [routePreCheckLicenseFn],
+ },
+ });
+}
diff --git a/x-pack/plugins/spaces/server/routes/api/public/get.test.ts b/x-pack/plugins/spaces/server/routes/api/public/get.test.ts
new file mode 100644
index 0000000000000..4d04759b283a8
--- /dev/null
+++ b/x-pack/plugins/spaces/server/routes/api/public/get.test.ts
@@ -0,0 +1,86 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+jest.mock('../../../lib/route_pre_check_license', () => {
+ return {
+ routePreCheckLicense: () => (request: any, reply: any) => reply.continue(),
+ };
+});
+
+jest.mock('../../../../../../server/lib/get_client_shield', () => {
+ return {
+ getClient: () => {
+ return {
+ callWithInternalUser: jest.fn(() => {
+ return;
+ }),
+ };
+ },
+ };
+});
+import Boom from 'boom';
+import { Space } from '../../../../common/model/space';
+import { createSpaces, createTestHandler, RequestRunner, TeardownFn } from '../__fixtures__';
+import { initGetSpacesApi } from './get';
+
+describe('GET spaces', () => {
+ let request: RequestRunner;
+ let teardowns: TeardownFn[];
+ const spaces = createSpaces();
+
+ beforeEach(() => {
+ const setup = createTestHandler(initGetSpacesApi);
+
+ request = setup.request;
+ teardowns = setup.teardowns;
+ });
+
+ afterEach(async () => {
+ await Promise.all(teardowns.splice(0).map(fn => fn()));
+ });
+
+ test(`'GET spaces' returns all available spaces`, async () => {
+ const { response } = await request('GET', '/api/spaces/space');
+
+ const { statusCode, payload } = response;
+
+ expect(statusCode).toEqual(200);
+ const resultSpaces: Space[] = JSON.parse(payload);
+ expect(resultSpaces.map(s => s.id)).toEqual(spaces.map(s => s.id));
+ });
+
+ test(`returns result of routePreCheckLicense`, async () => {
+ const { response } = await request('GET', '/api/spaces/space', {
+ preCheckLicenseImpl: (req: any, reply: any) =>
+ reply(Boom.forbidden('test forbidden message')),
+ });
+
+ const { statusCode, payload } = response;
+
+ expect(statusCode).toEqual(403);
+ expect(JSON.parse(payload)).toMatchObject({
+ message: 'test forbidden message',
+ });
+ });
+
+ test(`'GET spaces/{id}' returns the space with that id`, async () => {
+ const { response } = await request('GET', '/api/spaces/space/default');
+
+ const { statusCode, payload } = response;
+
+ expect(statusCode).toEqual(200);
+ const resultSpace = JSON.parse(payload);
+ expect(resultSpace.id).toEqual('default');
+ });
+
+ test(`'GET spaces/{id}' returns 404 when retrieving a non-existent space`, async () => {
+ const { response } = await request('GET', '/api/spaces/space/not-a-space');
+
+ const { statusCode } = response;
+
+ expect(statusCode).toEqual(404);
+ });
+});
diff --git a/x-pack/plugins/spaces/server/routes/api/public/get.ts b/x-pack/plugins/spaces/server/routes/api/public/get.ts
new file mode 100644
index 0000000000000..95f1d273a8f6b
--- /dev/null
+++ b/x-pack/plugins/spaces/server/routes/api/public/get.ts
@@ -0,0 +1,61 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import Boom from 'boom';
+import { wrapError } from '../../../lib/errors';
+import { convertSavedObjectToSpace } from '../../lib';
+
+export function initGetSpacesApi(server: any, routePreCheckLicenseFn: any) {
+ server.route({
+ method: 'GET',
+ path: '/api/spaces/space',
+ async handler(request: any, reply: any) {
+ const client = request.getSavedObjectsClient();
+
+ let spaces;
+
+ try {
+ const result = await client.find({
+ type: 'space',
+ sortField: 'name.keyword',
+ });
+
+ spaces = result.saved_objects.map(convertSavedObjectToSpace);
+ } catch (error) {
+ return reply(wrapError(error));
+ }
+
+ return reply(spaces);
+ },
+ config: {
+ pre: [routePreCheckLicenseFn],
+ },
+ });
+
+ server.route({
+ method: 'GET',
+ path: '/api/spaces/space/{id}',
+ async handler(request: any, reply: any) {
+ const spaceId = request.params.id;
+
+ const client = request.getSavedObjectsClient();
+
+ try {
+ const response = await client.get('space', spaceId);
+
+ return reply(convertSavedObjectToSpace(response));
+ } catch (error) {
+ if (client.errors.isNotFoundError(error)) {
+ return reply(Boom.notFound());
+ }
+ return reply(wrapError(error));
+ }
+ },
+ config: {
+ pre: [routePreCheckLicenseFn],
+ },
+ });
+}
diff --git a/x-pack/plugins/spaces/server/routes/api/public/index.ts b/x-pack/plugins/spaces/server/routes/api/public/index.ts
new file mode 100644
index 0000000000000..602b62ab26d06
--- /dev/null
+++ b/x-pack/plugins/spaces/server/routes/api/public/index.ts
@@ -0,0 +1,20 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { routePreCheckLicense } from '../../../lib/route_pre_check_license';
+import { initDeleteSpacesApi } from './delete';
+import { initGetSpacesApi } from './get';
+import { initPostSpacesApi } from './post';
+import { initPutSpacesApi } from './put';
+
+export function initPublicSpacesApi(server: any) {
+ const routePreCheckLicenseFn = routePreCheckLicense(server);
+
+ initDeleteSpacesApi(server, routePreCheckLicenseFn);
+ initGetSpacesApi(server, routePreCheckLicenseFn);
+ initPostSpacesApi(server, routePreCheckLicenseFn);
+ initPutSpacesApi(server, routePreCheckLicenseFn);
+}
diff --git a/x-pack/plugins/spaces/server/routes/api/public/post.test.ts b/x-pack/plugins/spaces/server/routes/api/public/post.test.ts
new file mode 100644
index 0000000000000..f97931d36ed66
--- /dev/null
+++ b/x-pack/plugins/spaces/server/routes/api/public/post.test.ts
@@ -0,0 +1,106 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+jest.mock('../../../lib/route_pre_check_license', () => {
+ return {
+ routePreCheckLicense: () => (request: any, reply: any) => reply.continue(),
+ };
+});
+
+jest.mock('../../../../../../server/lib/get_client_shield', () => {
+ return {
+ getClient: () => {
+ return {
+ callWithInternalUser: jest.fn(() => {
+ return;
+ }),
+ };
+ },
+ };
+});
+
+import Boom from 'boom';
+import { createTestHandler, RequestRunner, TeardownFn } from '../__fixtures__';
+import { initPostSpacesApi } from './post';
+
+describe('Spaces Public API', () => {
+ let request: RequestRunner;
+ let teardowns: TeardownFn[];
+
+ beforeEach(() => {
+ const setup = createTestHandler(initPostSpacesApi);
+
+ request = setup.request;
+ teardowns = setup.teardowns;
+ });
+
+ afterEach(async () => {
+ await Promise.all(teardowns.splice(0).map(fn => fn()));
+ });
+
+ test('POST /space should create a new space with the provided ID', async () => {
+ const payload = {
+ id: 'my-space-id',
+ name: 'my new space',
+ description: 'with a description',
+ };
+
+ const { mockSavedObjectsClient, response } = await request('POST', '/api/spaces/space', {
+ payload,
+ });
+
+ const { statusCode } = response;
+
+ expect(statusCode).toEqual(200);
+ expect(mockSavedObjectsClient.create).toHaveBeenCalledTimes(1);
+ expect(mockSavedObjectsClient.create).toHaveBeenCalledWith(
+ 'space',
+ { name: 'my new space', description: 'with a description' },
+ { id: 'my-space-id', overwrite: false }
+ );
+ });
+
+ test(`returns result of routePreCheckLicense`, async () => {
+ const payload = {
+ id: 'my-space-id',
+ name: 'my new space',
+ description: 'with a description',
+ };
+
+ const { response } = await request('POST', '/api/spaces/space', {
+ preCheckLicenseImpl: (req: any, reply: any) =>
+ reply(Boom.forbidden('test forbidden message')),
+ payload,
+ });
+
+ const { statusCode, payload: responsePayload } = response;
+
+ expect(statusCode).toEqual(403);
+ expect(JSON.parse(responsePayload)).toMatchObject({
+ message: 'test forbidden message',
+ });
+ });
+
+ test('POST /space should not allow a space to be updated', async () => {
+ const payload = {
+ id: 'a-space',
+ name: 'my updated space',
+ description: 'with a description',
+ };
+
+ const { response } = await request('POST', '/api/spaces/space', { payload });
+
+ const { statusCode, payload: responsePayload } = response;
+
+ expect(statusCode).toEqual(409);
+ expect(JSON.parse(responsePayload)).toEqual({
+ error: 'Conflict',
+ message:
+ 'A space with the identifier a-space already exists. Please choose a different identifier',
+ statusCode: 409,
+ });
+ });
+});
diff --git a/x-pack/plugins/spaces/server/routes/api/public/post.ts b/x-pack/plugins/spaces/server/routes/api/public/post.ts
new file mode 100644
index 0000000000000..fd51390af5023
--- /dev/null
+++ b/x-pack/plugins/spaces/server/routes/api/public/post.ts
@@ -0,0 +1,46 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import Boom from 'boom';
+import { omit } from 'lodash';
+import { wrapError } from '../../../lib/errors';
+import { spaceSchema } from '../../../lib/space_schema';
+import { getSpaceById } from '../../lib';
+
+export function initPostSpacesApi(server: any, routePreCheckLicenseFn: any) {
+ server.route({
+ method: 'POST',
+ path: '/api/spaces/space',
+ async handler(request: any, reply: any) {
+ const client = request.getSavedObjectsClient();
+
+ const space = omit(request.payload, ['id', '_reserved']);
+
+ const id = request.payload.id;
+
+ const existingSpace = await getSpaceById(client, id);
+ if (existingSpace) {
+ return reply(
+ Boom.conflict(
+ `A space with the identifier ${id} already exists. Please choose a different identifier`
+ )
+ );
+ }
+
+ try {
+ return reply(await client.create('space', { ...space }, { id, overwrite: false }));
+ } catch (error) {
+ return reply(wrapError(error));
+ }
+ },
+ config: {
+ validate: {
+ payload: spaceSchema,
+ },
+ pre: [routePreCheckLicenseFn],
+ },
+ });
+}
diff --git a/x-pack/plugins/spaces/server/routes/api/public/put.test.ts b/x-pack/plugins/spaces/server/routes/api/public/put.test.ts
new file mode 100644
index 0000000000000..2af4fc9bbeaf3
--- /dev/null
+++ b/x-pack/plugins/spaces/server/routes/api/public/put.test.ts
@@ -0,0 +1,97 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+jest.mock('../../../lib/route_pre_check_license', () => {
+ return {
+ routePreCheckLicense: () => (request: any, reply: any) => reply.continue(),
+ };
+});
+
+jest.mock('../../../../../../server/lib/get_client_shield', () => {
+ return {
+ getClient: () => {
+ return {
+ callWithInternalUser: jest.fn(() => {
+ return;
+ }),
+ };
+ },
+ };
+});
+import Boom from 'boom';
+import { createTestHandler, RequestRunner, TeardownFn } from '../__fixtures__';
+import { initPutSpacesApi } from './put';
+
+describe('Spaces Public API', () => {
+ let request: RequestRunner;
+ let teardowns: TeardownFn[];
+
+ beforeEach(() => {
+ const setup = createTestHandler(initPutSpacesApi);
+
+ request = setup.request;
+ teardowns = setup.teardowns;
+ });
+
+ afterEach(async () => {
+ await Promise.all(teardowns.splice(0).map(fn => fn()));
+ });
+
+ test('PUT /space should update an existing space with the provided ID', async () => {
+ const payload = {
+ id: 'a-space',
+ name: 'my updated space',
+ description: 'with a description',
+ };
+
+ const { mockSavedObjectsClient, response } = await request('PUT', '/api/spaces/space/a-space', {
+ payload,
+ });
+
+ const { statusCode } = response;
+
+ expect(statusCode).toEqual(200);
+ expect(mockSavedObjectsClient.update).toHaveBeenCalledTimes(1);
+ expect(mockSavedObjectsClient.update).toHaveBeenCalledWith('space', 'a-space', {
+ name: 'my updated space',
+ description: 'with a description',
+ });
+ });
+
+ test(`returns result of routePreCheckLicense`, async () => {
+ const payload = {
+ id: 'a-space',
+ name: 'my updated space',
+ description: 'with a description',
+ };
+
+ const { response } = await request('PUT', '/api/spaces/space/a-space', {
+ preCheckLicenseImpl: (req: any, reply: any) =>
+ reply(Boom.forbidden('test forbidden message')),
+ payload,
+ });
+
+ const { statusCode, payload: responsePayload } = response;
+
+ expect(statusCode).toEqual(403);
+ expect(JSON.parse(responsePayload)).toMatchObject({
+ message: 'test forbidden message',
+ });
+ });
+
+ test('PUT /space should not allow a new space to be created', async () => {
+ const payload = {
+ id: 'a-new-space',
+ name: 'my new space',
+ description: 'with a description',
+ };
+
+ const { response } = await request('PUT', '/api/spaces/space/a-new-space', { payload });
+
+ const { statusCode } = response;
+
+ expect(statusCode).toEqual(404);
+ });
+});
diff --git a/x-pack/plugins/spaces/server/routes/api/public/put.ts b/x-pack/plugins/spaces/server/routes/api/public/put.ts
new file mode 100644
index 0000000000000..093d7c777e786
--- /dev/null
+++ b/x-pack/plugins/spaces/server/routes/api/public/put.ts
@@ -0,0 +1,49 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import Boom from 'boom';
+import { omit } from 'lodash';
+import { Space } from '../../../../common/model/space';
+import { wrapError } from '../../../lib/errors';
+import { spaceSchema } from '../../../lib/space_schema';
+import { convertSavedObjectToSpace, getSpaceById } from '../../lib';
+
+export function initPutSpacesApi(server: any, routePreCheckLicenseFn: any) {
+ server.route({
+ method: 'PUT',
+ path: '/api/spaces/space/{id}',
+ async handler(request: any, reply: any) {
+ const client = request.getSavedObjectsClient();
+
+ const space: Space = omit(request.payload, ['id']);
+ const id = request.params.id;
+
+ const existingSpace = await getSpaceById(client, id);
+
+ if (existingSpace) {
+ space._reserved = existingSpace._reserved;
+ } else {
+ return reply(Boom.notFound(`Unable to find space with ID ${id}`));
+ }
+
+ let result;
+ try {
+ result = await client.update('space', id, { ...space });
+ } catch (error) {
+ return reply(wrapError(error));
+ }
+
+ const updatedSpace = convertSavedObjectToSpace(result);
+ return reply(updatedSpace);
+ },
+ config: {
+ validate: {
+ payload: spaceSchema,
+ },
+ pre: [routePreCheckLicenseFn],
+ },
+ });
+}
diff --git a/x-pack/plugins/spaces/server/routes/api/v1/index.ts b/x-pack/plugins/spaces/server/routes/api/v1/index.ts
new file mode 100644
index 0000000000000..75659c14c03ae
--- /dev/null
+++ b/x-pack/plugins/spaces/server/routes/api/v1/index.ts
@@ -0,0 +1,13 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { routePreCheckLicense } from '../../../lib/route_pre_check_license';
+import { initPrivateSpacesApi } from './spaces';
+
+export function initPrivateApis(server: any) {
+ const routePreCheckLicenseFn = routePreCheckLicense(server);
+ initPrivateSpacesApi(server, routePreCheckLicenseFn);
+}
diff --git a/x-pack/plugins/spaces/server/routes/api/v1/spaces.js b/x-pack/plugins/spaces/server/routes/api/v1/spaces.js
deleted file mode 100644
index 5d250c1c92c07..0000000000000
--- a/x-pack/plugins/spaces/server/routes/api/v1/spaces.js
+++ /dev/null
@@ -1,202 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import Boom from 'boom';
-import { omit } from 'lodash';
-import { routePreCheckLicense } from '../../../lib/route_pre_check_license';
-import { spaceSchema } from '../../../lib/space_schema';
-import { wrapError } from '../../../lib/errors';
-import { isReservedSpace } from '../../../../common/is_reserved_space';
-import { addSpaceIdToPath } from '../../../lib/spaces_url_parser';
-
-export function initSpacesApi(server) {
- const routePreCheckLicenseFn = routePreCheckLicense(server);
-
- function convertSavedObjectToSpace(savedObject) {
- return {
- id: savedObject.id,
- ...savedObject.attributes
- };
- }
-
- server.route({
- method: 'GET',
- path: '/api/spaces/v1/spaces',
- async handler(request, reply) {
- const client = request.getSavedObjectsClient();
-
- let spaces;
-
- try {
- const result = await client.find({
- type: 'space',
- sortField: 'name.keyword',
- });
-
- spaces = result.saved_objects.map(convertSavedObjectToSpace);
- } catch (error) {
- return reply(wrapError(error));
- }
-
- return reply(spaces);
- },
- config: {
- pre: [routePreCheckLicenseFn]
- }
- });
-
- server.route({
- method: 'GET',
- path: '/api/spaces/v1/space/{id}',
- async handler(request, reply) {
- const spaceId = request.params.id;
-
- const client = request.getSavedObjectsClient();
-
- try {
- const response = await client.get('space', spaceId);
-
- return reply(convertSavedObjectToSpace(response));
- } catch (error) {
- return reply(wrapError(error));
- }
- },
- config: {
- pre: [routePreCheckLicenseFn]
- }
- });
-
- server.route({
- method: 'POST',
- path: '/api/spaces/v1/space',
- async handler(request, reply) {
- const client = request.getSavedObjectsClient();
-
- const space = omit(request.payload, ['id', '_reserved']);
-
- const id = request.payload.id;
-
- const existingSpace = await getSpaceById(client, id);
- if (existingSpace) {
- return reply(Boom.conflict(`A space with the identifier ${id} already exists. Please choose a different identifier`));
- }
-
- try {
- return reply(await client.create('space', { ...space }, { id, overwrite: false }));
- } catch (error) {
- return reply(wrapError(error));
- }
-
- },
- config: {
- validate: {
- payload: spaceSchema
- },
- pre: [routePreCheckLicenseFn]
- }
- });
-
- server.route({
- method: 'PUT',
- path: '/api/spaces/v1/space/{id}',
- async handler(request, reply) {
- const client = request.getSavedObjectsClient();
-
- const space = omit(request.payload, ['id']);
- const id = request.params.id;
-
- const existingSpace = await getSpaceById(client, id);
-
- if (existingSpace) {
- space._reserved = existingSpace._reserved;
- } else {
- return reply(Boom.notFound(`Unable to find space with ID ${id}`));
- }
-
- let result;
- try {
- result = await client.update('space', id, { ...space });
- } catch (error) {
- return reply(wrapError(error));
- }
-
- const updatedSpace = convertSavedObjectToSpace(result);
- return reply(updatedSpace);
- },
- config: {
- validate: {
- payload: spaceSchema
- },
- pre: [routePreCheckLicenseFn]
- }
- });
-
- server.route({
- method: 'DELETE',
- path: '/api/spaces/v1/space/{id}',
- async handler(request, reply) {
- const client = request.getSavedObjectsClient();
-
- const id = request.params.id;
-
- let result;
-
- try {
- const existingSpace = await getSpaceById(client, id);
- if (isReservedSpace(existingSpace)) {
- return reply(wrapError(Boom.badRequest('This Space cannot be deleted because it is reserved.')));
- }
-
- result = await client.delete('space', id);
- } catch (error) {
- return reply(wrapError(error));
- }
-
- return reply(result).code(204);
- },
- config: {
- pre: [routePreCheckLicenseFn]
- }
- });
-
- server.route({
- method: 'POST',
- path: '/api/spaces/v1/space/{id}/select',
- async handler(request, reply) {
- const client = request.getSavedObjectsClient();
-
- const id = request.params.id;
-
- try {
- const existingSpace = await getSpaceById(client, id);
-
- const config = server.config();
-
- return reply({
- location: addSpaceIdToPath(config.get('server.basePath'), existingSpace.id, config.get('server.defaultRoute'))
- });
-
- } catch (error) {
- return reply(wrapError(error));
- }
- }
- });
-
- async function getSpaceById(client, spaceId) {
- try {
- const existingSpace = await client.get('space', spaceId);
- return {
- id: existingSpace.id,
- ...existingSpace.attributes
- };
- } catch (error) {
- if (client.errors.isNotFoundError(error)) {
- return null;
- }
- throw error;
- }
- }
-}
diff --git a/x-pack/plugins/spaces/server/routes/api/v1/spaces.test.js b/x-pack/plugins/spaces/server/routes/api/v1/spaces.test.js
deleted file mode 100644
index fb957e4586343..0000000000000
--- a/x-pack/plugins/spaces/server/routes/api/v1/spaces.test.js
+++ /dev/null
@@ -1,281 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { Server } from 'hapi';
-import { initSpacesApi } from './spaces';
-
-jest.mock('../../../lib/route_pre_check_license', () => {
- return {
- routePreCheckLicense: () => (request, reply) => reply.continue()
- };
-});
-
-jest.mock('../../../../../../server/lib/get_client_shield', () => {
- return {
- getClient: () => {
- return {
- callWithInternalUser: jest.fn(() => { })
- };
- }
- };
-});
-
-const spaces = [{
- id: 'a-space',
- attributes: {
- name: 'a space',
- }
-}, {
- id: 'b-space',
- attributes: {
- name: 'b space',
- }
-}, {
- id: 'default',
- attributes: {
- name: 'Default Space',
- _reserved: true
- }
-}];
-
-describe('Spaces API', () => {
- const teardowns = [];
- let request;
-
- const baseConfig = {
- 'server.basePath': ''
- };
-
- beforeEach(() => {
- request = async (method, path, options = {}) => {
-
- const {
- setupFn = () => { },
- testConfig = {},
- payload,
- } = options;
-
- const server = new Server();
-
- const config = {
- ...baseConfig,
- ...testConfig
- };
-
- server.connection({ port: 0 });
-
- await setupFn(server);
-
- server.decorate('server', 'config', jest.fn(() => {
- return {
- get: (key) => config[key]
- };
- }));
-
- initSpacesApi(server);
-
- server.decorate('request', 'getBasePath', jest.fn());
- server.decorate('request', 'setBasePath', jest.fn());
-
- // Mock server.getSavedObjectsClient()
- const mockSavedObjectsClient = {
- get: jest.fn((type, id) => {
- return spaces.filter(s => s.id === id)[0];
- }),
- find: jest.fn(() => {
- return {
- total: spaces.length,
- saved_objects: spaces
- };
- }),
- create: jest.fn(() => ({})),
- update: jest.fn(() => ({})),
- delete: jest.fn(),
- errors: {
- isNotFoundError: jest.fn(() => true)
- }
- };
-
- server.decorate('request', 'getSavedObjectsClient', () => mockSavedObjectsClient);
-
- teardowns.push(() => server.stop());
-
- return {
- server,
- mockSavedObjectsClient,
- response: await server.inject({
- method,
- url: path,
- payload,
- })
- };
- };
- });
-
- afterEach(async () => {
- await Promise.all(teardowns.splice(0).map(fn => fn()));
- });
-
- test(`'GET spaces' returns all available spaces`, async () => {
- const { response } = await request('GET', '/api/spaces/v1/spaces');
-
- const {
- statusCode,
- payload
- } = response;
-
- expect(statusCode).toEqual(200);
- const resultSpaces = JSON.parse(payload);
- expect(resultSpaces.map(s => s.id)).toEqual(spaces.map(s => s.id));
- });
-
- test(`'GET spaces/{id}' returns the space with that id`, async () => {
- const { response } = await request('GET', '/api/spaces/v1/space/default');
-
- const {
- statusCode,
- payload
- } = response;
-
- expect(statusCode).toEqual(200);
- const resultSpace = JSON.parse(payload);
- expect(resultSpace.id).toEqual('default');
- });
-
- test(`'DELETE spaces/{id}' deletes the space`, async () => {
- const { response } = await request('DELETE', '/api/spaces/v1/space/a-space');
-
- const {
- statusCode
- } = response;
-
- expect(statusCode).toEqual(204);
- });
-
- test(`'DELETE spaces/{id}' cannot delete reserved spaces`, async () => {
- const { response } = await request('DELETE', '/api/spaces/v1/space/default');
-
- const {
- statusCode,
- payload
- } = response;
-
- expect(statusCode).toEqual(400);
- expect(JSON.parse(payload)).toEqual({
- statusCode: 400,
- error: "Bad Request",
- message: "This Space cannot be deleted because it is reserved."
- });
- });
-
- test('POST /space should create a new space with the provided ID', async () => {
- const payload = {
- id: 'my-space-id',
- name: 'my new space',
- description: 'with a description',
- };
-
- const { mockSavedObjectsClient, response } = await request('POST', '/api/spaces/v1/space', { payload });
-
- const {
- statusCode,
- } = response;
-
- expect(statusCode).toEqual(200);
- expect(mockSavedObjectsClient.create).toHaveBeenCalledTimes(1);
- expect(mockSavedObjectsClient.create)
- .toHaveBeenCalledWith('space', { name: 'my new space', description: 'with a description' }, { id: 'my-space-id', overwrite: false });
- });
-
- test('POST /space should not allow a space to be updated', async () => {
- const payload = {
- id: 'a-space',
- name: 'my updated space',
- description: 'with a description',
- };
-
- const { response } = await request('POST', '/api/spaces/v1/space', { payload });
-
- const {
- statusCode,
- payload: responsePayload,
- } = response;
-
- expect(statusCode).toEqual(409);
- expect(JSON.parse(responsePayload)).toEqual({
- error: 'Conflict',
- message: "A space with the identifier a-space already exists. Please choose a different identifier",
- statusCode: 409
- });
- });
-
- test('PUT /space should update an existing space with the provided ID', async () => {
- const payload = {
- id: 'a-space',
- name: 'my updated space',
- description: 'with a description',
- };
-
- const { mockSavedObjectsClient, response } = await request('PUT', '/api/spaces/v1/space/a-space', { payload });
-
- const {
- statusCode,
- } = response;
-
- expect(statusCode).toEqual(200);
- expect(mockSavedObjectsClient.update).toHaveBeenCalledTimes(1);
- expect(mockSavedObjectsClient.update)
- .toHaveBeenCalledWith('space', 'a-space', { name: 'my updated space', description: 'with a description' });
- });
-
- test('PUT /space should not allow a new space to be created', async () => {
- const payload = {
- id: 'a-new-space',
- name: 'my new space',
- description: 'with a description',
- };
-
- const { response } = await request('PUT', '/api/spaces/v1/space/a-new-space', { payload });
-
- const {
- statusCode,
- } = response;
-
- expect(statusCode).toEqual(404);
- });
-
- test('POST space/{id}/select should respond with the new space location', async () => {
- const { response } = await request('POST', '/api/spaces/v1/space/a-space/select');
-
- const {
- statusCode,
- payload
- } = response;
-
- expect(statusCode).toEqual(200);
-
- const result = JSON.parse(payload);
- expect(result.location).toEqual('/s/a-space');
- });
-
- test('POST space/{id}/select should respond with the new space location when a server.basePath is in use', async () => {
- const testConfig = {
- 'server.basePath': '/my/base/path'
- };
-
- const { response } = await request('POST', '/api/spaces/v1/space/a-space/select', { testConfig });
-
- const {
- statusCode,
- payload
- } = response;
-
- expect(statusCode).toEqual(200);
-
- const result = JSON.parse(payload);
- expect(result.location).toEqual('/my/base/path/s/a-space');
- });
-});
diff --git a/x-pack/plugins/spaces/server/routes/api/v1/spaces.test.ts b/x-pack/plugins/spaces/server/routes/api/v1/spaces.test.ts
new file mode 100644
index 0000000000000..bbdab1be34910
--- /dev/null
+++ b/x-pack/plugins/spaces/server/routes/api/v1/spaces.test.ts
@@ -0,0 +1,93 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+jest.mock('../../../lib/route_pre_check_license', () => {
+ return {
+ routePreCheckLicense: () => (request: any, reply: any) => reply.continue(),
+ };
+});
+
+jest.mock('../../../../../../server/lib/get_client_shield', () => {
+ return {
+ getClient: () => {
+ return {
+ callWithInternalUser: jest.fn(() => {
+ return;
+ }),
+ };
+ },
+ };
+});
+
+import Boom from 'boom';
+import { createTestHandler, RequestRunner, TeardownFn } from '../__fixtures__';
+import { initPrivateSpacesApi } from './spaces';
+
+describe('Spaces API', () => {
+ let request: RequestRunner;
+ let teardowns: TeardownFn[];
+
+ beforeEach(() => {
+ const setup = createTestHandler(initPrivateSpacesApi);
+
+ request = setup.request;
+ teardowns = setup.teardowns;
+ });
+
+ afterEach(async () => {
+ await Promise.all(teardowns.splice(0).map(fn => fn()));
+ });
+
+ test('POST space/{id}/select should respond with the new space location', async () => {
+ const { response } = await request('POST', '/api/spaces/v1/space/a-space/select');
+
+ const { statusCode, payload } = response;
+
+ expect(statusCode).toEqual(200);
+
+ const result = JSON.parse(payload);
+ expect(result.location).toEqual('/s/a-space');
+ });
+
+ test(`returns result of routePreCheckLicense`, async () => {
+ const { response } = await request('POST', '/api/spaces/v1/space/a-space/select', {
+ preCheckLicenseImpl: (req: any, reply: any) =>
+ reply(Boom.forbidden('test forbidden message')),
+ });
+
+ const { statusCode, payload } = response;
+
+ expect(statusCode).toEqual(403);
+ expect(JSON.parse(payload)).toMatchObject({
+ message: 'test forbidden message',
+ });
+ });
+
+ test('POST space/{id}/select should respond with 404 when the space is not found', async () => {
+ const { response } = await request('POST', '/api/spaces/v1/space/not-a-space/select');
+
+ const { statusCode } = response;
+
+ expect(statusCode).toEqual(404);
+ });
+
+ test('POST space/{id}/select should respond with the new space location when a server.basePath is in use', async () => {
+ const testConfig = {
+ 'server.basePath': '/my/base/path',
+ };
+
+ const { response } = await request('POST', '/api/spaces/v1/space/a-space/select', {
+ testConfig,
+ });
+
+ const { statusCode, payload } = response;
+
+ expect(statusCode).toEqual(200);
+
+ const result = JSON.parse(payload);
+ expect(result.location).toEqual('/my/base/path/s/a-space');
+ });
+});
diff --git a/x-pack/plugins/spaces/server/routes/api/v1/spaces.ts b/x-pack/plugins/spaces/server/routes/api/v1/spaces.ts
new file mode 100644
index 0000000000000..0233cb76b96d8
--- /dev/null
+++ b/x-pack/plugins/spaces/server/routes/api/v1/spaces.ts
@@ -0,0 +1,45 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import Boom from 'boom';
+import { Space } from '../../../../common/model/space';
+import { wrapError } from '../../../lib/errors';
+import { addSpaceIdToPath } from '../../../lib/spaces_url_parser';
+import { getSpaceById } from '../../lib';
+
+export function initPrivateSpacesApi(server: any, routePreCheckLicenseFn: any) {
+ server.route({
+ method: 'POST',
+ path: '/api/spaces/v1/space/{id}/select',
+ async handler(request: any, reply: any) {
+ const client = request.getSavedObjectsClient();
+
+ const id = request.params.id;
+
+ try {
+ const existingSpace: Space | null = await getSpaceById(client, id);
+ if (!existingSpace) {
+ return reply(Boom.notFound());
+ }
+
+ const config = server.config();
+
+ return reply({
+ location: addSpaceIdToPath(
+ config.get('server.basePath'),
+ existingSpace.id,
+ config.get('server.defaultRoute')
+ ),
+ });
+ } catch (error) {
+ return reply(wrapError(error));
+ }
+ },
+ config: {
+ pre: [routePreCheckLicenseFn],
+ },
+ });
+}
diff --git a/x-pack/plugins/spaces/server/routes/lib/convert_saved_object_to_space.test.ts b/x-pack/plugins/spaces/server/routes/lib/convert_saved_object_to_space.test.ts
new file mode 100644
index 0000000000000..31738ff562865
--- /dev/null
+++ b/x-pack/plugins/spaces/server/routes/lib/convert_saved_object_to_space.test.ts
@@ -0,0 +1,27 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { convertSavedObjectToSpace } from './convert_saved_object_to_space';
+
+describe('convertSavedObjectToSpace', () => {
+ it('converts a saved object representation to a Space object', () => {
+ const savedObject = {
+ id: 'foo',
+ attributes: {
+ name: 'Foo Space',
+ description: 'no fighting',
+ _reserved: false,
+ },
+ };
+
+ expect(convertSavedObjectToSpace(savedObject)).toEqual({
+ id: 'foo',
+ name: 'Foo Space',
+ description: 'no fighting',
+ _reserved: false,
+ });
+ });
+});
diff --git a/x-pack/plugins/spaces/server/routes/lib/convert_saved_object_to_space.ts b/x-pack/plugins/spaces/server/routes/lib/convert_saved_object_to_space.ts
new file mode 100644
index 0000000000000..d3ee173a2e80f
--- /dev/null
+++ b/x-pack/plugins/spaces/server/routes/lib/convert_saved_object_to_space.ts
@@ -0,0 +1,20 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { Space } from '../../../common/model/space';
+
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export function convertSavedObjectToSpace(savedObject: any): Space {
+ return {
+ id: savedObject.id,
+ ...savedObject.attributes,
+ };
+}
diff --git a/x-pack/plugins/spaces/server/routes/lib/get_space_by_id.ts b/x-pack/plugins/spaces/server/routes/lib/get_space_by_id.ts
new file mode 100644
index 0000000000000..4143c09a79a93
--- /dev/null
+++ b/x-pack/plugins/spaces/server/routes/lib/get_space_by_id.ts
@@ -0,0 +1,19 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import { Space } from '../../../common/model/space';
+import { convertSavedObjectToSpace } from './convert_saved_object_to_space';
+
+export async function getSpaceById(client: any, spaceId: string): Promise {
+ try {
+ const existingSpace = await client.get('space', spaceId);
+ return convertSavedObjectToSpace(existingSpace);
+ } catch (error) {
+ if (client.errors.isNotFoundError(error)) {
+ return null;
+ }
+ throw error;
+ }
+}
diff --git a/x-pack/plugins/spaces/server/routes/lib/index.ts b/x-pack/plugins/spaces/server/routes/lib/index.ts
new file mode 100644
index 0000000000000..af67388792565
--- /dev/null
+++ b/x-pack/plugins/spaces/server/routes/lib/index.ts
@@ -0,0 +1,8 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export { convertSavedObjectToSpace } from './convert_saved_object_to_space';
+export { getSpaceById } from './get_space_by_id';
From 5f02f3e4ea42044debbf87932d278e81814e0a06 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?=
Date: Thu, 6 Sep 2018 15:50:45 +0200
Subject: [PATCH 28/68] [APM] Fix broken links (#22592)
* [APM] Fix broken links
* Add missing basepaths
* Remove basepath from getMlJobUrl
---
.../Watcher/WatcherButton.js | 5 +-
.../Watcher/__test__/WatcherButton.test.js | 41 ++++++++++++++
.../__snapshots__/WatcherButton.test.js.snap | 54 +++++++++++++++++++
.../DynamicBaseline/Button.js | 3 +-
.../DynamicBaseline/Flyout.js | 8 +--
.../DynamicBaseline/__jest__/Button.test.js | 39 ++++++++++++++
.../__snapshots__/Button.test.js.snap | 54 +++++++++++++++++++
.../app/TransactionOverview/view.js | 8 +--
.../__test__/__snapshots__/url.test.js.snap | 2 +-
.../apm/public/utils/__test__/url.test.js | 29 +++++++++-
10 files changed, 231 insertions(+), 12 deletions(-)
create mode 100644 x-pack/plugins/apm/public/components/app/ErrorGroupOverview/Watcher/__test__/WatcherButton.test.js
create mode 100644 x-pack/plugins/apm/public/components/app/ErrorGroupOverview/Watcher/__test__/__snapshots__/WatcherButton.test.js.snap
create mode 100644 x-pack/plugins/apm/public/components/app/TransactionOverview/DynamicBaseline/__jest__/Button.test.js
create mode 100644 x-pack/plugins/apm/public/components/app/TransactionOverview/DynamicBaseline/__jest__/__snapshots__/Button.test.js.snap
diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/Watcher/WatcherButton.js b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/Watcher/WatcherButton.js
index 4f682ed3a531b..a3c034ca3bc08 100644
--- a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/Watcher/WatcherButton.js
+++ b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/Watcher/WatcherButton.js
@@ -5,6 +5,7 @@
*/
import React, { Component } from 'react';
+import chrome from 'ui/chrome';
import { EuiButton, EuiContextMenu, EuiIcon, EuiPopover } from '@elastic/eui';
export default class WatcherButton extends Component {
@@ -31,7 +32,9 @@ export default class WatcherButton extends Component {
{
name: 'View existing watches',
icon: 'tableOfContents',
- href: '/app/kibana#/management/elasticsearch/watcher/',
+ href: chrome.addBasePath(
+ '/app/kibana#/management/elasticsearch/watcher/'
+ ),
target: '_blank',
onClick: () => this.closePopover()
}
diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/Watcher/__test__/WatcherButton.test.js b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/Watcher/__test__/WatcherButton.test.js
new file mode 100644
index 0000000000000..23590be5f0d38
--- /dev/null
+++ b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/Watcher/__test__/WatcherButton.test.js
@@ -0,0 +1,41 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import { shallow } from 'enzyme';
+import DetailView from '../WatcherButton';
+
+jest.mock('ui/chrome', () => ({
+ addBasePath: path => `myBasePath${path}`
+}));
+
+describe('WatcherButton', () => {
+ let wrapper;
+ beforeEach(() => {
+ wrapper = shallow( );
+ });
+
+ it('should render initial state', () => {
+ expect(wrapper).toMatchSnapshot();
+ });
+
+ it('should have correct url', () => {
+ const panels = wrapper.find('EuiContextMenu').prop('panels');
+ expect(panels[0].items[1].href).toBe(
+ 'myBasePath/app/kibana#/management/elasticsearch/watcher/'
+ );
+ });
+
+ it('popover should be closed', () => {
+ expect(wrapper.find('EuiPopover').prop('isOpen')).toBe(false);
+ });
+
+ it('should open popover', async () => {
+ await wrapper.instance().onButtonClick();
+ wrapper.update();
+ expect(wrapper.find('EuiPopover').prop('isOpen')).toBe(true);
+ });
+});
diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/Watcher/__test__/__snapshots__/WatcherButton.test.js.snap b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/Watcher/__test__/__snapshots__/WatcherButton.test.js.snap
new file mode 100644
index 0000000000000..8918566a7cead
--- /dev/null
+++ b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/Watcher/__test__/__snapshots__/WatcherButton.test.js.snap
@@ -0,0 +1,54 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`WatcherButton should render initial state 1`] = `
+
+ Integrations
+
+ }
+ closePopover={[Function]}
+ hasArrow={true}
+ isOpen={false}
+ ownFocus={false}
+ panelPaddingSize="none"
+>
+ ,
+ "name": "Enable error reports",
+ "onClick": [Function],
+ },
+ Object {
+ "href": "myBasePath/app/kibana#/management/elasticsearch/watcher/",
+ "icon": "tableOfContents",
+ "name": "View existing watches",
+ "onClick": [Function],
+ "target": "_blank",
+ },
+ ],
+ "title": "Watcher",
+ },
+ ]
+ }
+ />
+
+`;
diff --git a/x-pack/plugins/apm/public/components/app/TransactionOverview/DynamicBaseline/Button.js b/x-pack/plugins/apm/public/components/app/TransactionOverview/DynamicBaseline/Button.js
index bbba48082d3be..b7f8589dbd588 100644
--- a/x-pack/plugins/apm/public/components/app/TransactionOverview/DynamicBaseline/Button.js
+++ b/x-pack/plugins/apm/public/components/app/TransactionOverview/DynamicBaseline/Button.js
@@ -5,6 +5,7 @@
*/
import React, { Component } from 'react';
+import chrome from 'ui/chrome';
import { EuiButton, EuiPopover, EuiIcon, EuiContextMenu } from '@elastic/eui';
export default class DynamicBaselineButton extends Component {
@@ -31,7 +32,7 @@ export default class DynamicBaselineButton extends Component {
{
name: 'View existing jobs',
icon: 'tableOfContents',
- href: '/app/ml',
+ href: chrome.addBasePath('/app/ml'),
target: '_blank',
onClick: () => this.closePopover()
}
diff --git a/x-pack/plugins/apm/public/components/app/TransactionOverview/DynamicBaseline/Flyout.js b/x-pack/plugins/apm/public/components/app/TransactionOverview/DynamicBaseline/Flyout.js
index a48db866b41d1..1231e60e5bd80 100644
--- a/x-pack/plugins/apm/public/components/app/TransactionOverview/DynamicBaseline/Flyout.js
+++ b/x-pack/plugins/apm/public/components/app/TransactionOverview/DynamicBaseline/Flyout.js
@@ -172,9 +172,11 @@ export default class DynamicBaselineFlyout extends Component {
Jobs can be created per transaction type and based on the average
response time. Once a job is created, you can manage it and see
more details in the{' '}
- Machine Learning jobs management page . It
- might take some time for the job to calculate the results. Please
- refresh the graph a few minutes after creating the job.
+
+ Machine Learning jobs management page
+
+ . It might take some time for the job to calculate the results.
+ Please refresh the graph a few minutes after creating the job.
{/* Learn more about the Machine Learning integration. */}
diff --git a/x-pack/plugins/apm/public/components/app/TransactionOverview/DynamicBaseline/__jest__/Button.test.js b/x-pack/plugins/apm/public/components/app/TransactionOverview/DynamicBaseline/__jest__/Button.test.js
new file mode 100644
index 0000000000000..c7396cf8c4847
--- /dev/null
+++ b/x-pack/plugins/apm/public/components/app/TransactionOverview/DynamicBaseline/__jest__/Button.test.js
@@ -0,0 +1,39 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import { shallow } from 'enzyme';
+import DetailView from '../Button';
+
+jest.mock('ui/chrome', () => ({
+ addBasePath: path => `myBasePath${path}`
+}));
+
+describe('MLButton', () => {
+ let wrapper;
+ beforeEach(() => {
+ wrapper = shallow( );
+ });
+
+ it('should render initial state', () => {
+ expect(wrapper).toMatchSnapshot();
+ });
+
+ it('should have correct url', () => {
+ const panels = wrapper.find('EuiContextMenu').prop('panels');
+ expect(panels[0].items[1].href).toBe('myBasePath/app/ml');
+ });
+
+ it('popover should be closed', () => {
+ expect(wrapper.find('EuiPopover').prop('isOpen')).toBe(false);
+ });
+
+ it('should open popover', async () => {
+ await wrapper.instance().onButtonClick();
+ wrapper.update();
+ expect(wrapper.find('EuiPopover').prop('isOpen')).toBe(true);
+ });
+});
diff --git a/x-pack/plugins/apm/public/components/app/TransactionOverview/DynamicBaseline/__jest__/__snapshots__/Button.test.js.snap b/x-pack/plugins/apm/public/components/app/TransactionOverview/DynamicBaseline/__jest__/__snapshots__/Button.test.js.snap
new file mode 100644
index 0000000000000..6e57f557d1bd4
--- /dev/null
+++ b/x-pack/plugins/apm/public/components/app/TransactionOverview/DynamicBaseline/__jest__/__snapshots__/Button.test.js.snap
@@ -0,0 +1,54 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`MLButton should render initial state 1`] = `
+
+ Integrations
+
+ }
+ closePopover={[Function]}
+ hasArrow={true}
+ isOpen={false}
+ ownFocus={false}
+ panelPaddingSize="none"
+>
+ ,
+ "name": "Anomaly detection (BETA)",
+ "onClick": [Function],
+ },
+ Object {
+ "href": "myBasePath/app/ml",
+ "icon": "tableOfContents",
+ "name": "View existing jobs",
+ "onClick": [Function],
+ "target": "_blank",
+ },
+ ],
+ "title": "Machine Learning",
+ },
+ ]
+ }
+ />
+
+`;
diff --git a/x-pack/plugins/apm/public/components/app/TransactionOverview/view.js b/x-pack/plugins/apm/public/components/app/TransactionOverview/view.js
index ec14242232691..b250afe837965 100644
--- a/x-pack/plugins/apm/public/components/app/TransactionOverview/view.js
+++ b/x-pack/plugins/apm/public/components/app/TransactionOverview/view.js
@@ -13,7 +13,7 @@ import { get } from 'lodash';
import { HeaderContainer, HeaderMedium } from '../../shared/UIComponents';
import TabNavigation from '../../shared/TabNavigation';
import Charts from '../../shared/charts/TransactionCharts';
-import { getMlJobUrl } from '../../../utils/url';
+import { getMlJobUrl, KibanaLink } from '../../../utils/url';
import List from './List';
import { units, px, fontSizes } from '../../../style/variables';
import { OverviewChartsRequest } from '../../../store/reactReduxRequest/overviewCharts';
@@ -75,15 +75,15 @@ class TransactionOverview extends Component {
Machine Learning:{' '}
-
View Job
-
+
) : null;
diff --git a/x-pack/plugins/apm/public/utils/__test__/__snapshots__/url.test.js.snap b/x-pack/plugins/apm/public/utils/__test__/__snapshots__/url.test.js.snap
index 4e5e7a8c64042..2252f7f40ce6b 100644
--- a/x-pack/plugins/apm/public/utils/__test__/__snapshots__/url.test.js.snap
+++ b/x-pack/plugins/apm/public/utils/__test__/__snapshots__/url.test.js.snap
@@ -3,7 +3,7 @@
exports[`KibanaLinkComponent should render correct markup 1`] = `
Go to Discover
diff --git a/x-pack/plugins/apm/public/utils/__test__/url.test.js b/x-pack/plugins/apm/public/utils/__test__/url.test.js
index 87ed6764a1fbc..28a8a6bbd469f 100644
--- a/x-pack/plugins/apm/public/utils/__test__/url.test.js
+++ b/x-pack/plugins/apm/public/utils/__test__/url.test.js
@@ -15,10 +15,15 @@ import {
KibanaLinkComponent,
RelativeLinkComponent,
encodeKibanaSearchParams,
- decodeKibanaSearchParams
+ decodeKibanaSearchParams,
+ getMlJobUrl
} from '../url';
import { toJson } from '../testHelpers';
+jest.mock('ui/chrome', () => ({
+ addBasePath: path => `myBasePath${path}`
+}));
+
describe('encodeKibanaSearchParams and decodeKibanaSearchParams should return the original string', () => {
it('should convert string to object', () => {
const search = `?_g=(ml:(jobIds:!(opbeans-node-request-high_mean_response_time)),refreshInterval:(display:Off,pause:!f,value:0),time:(from:'2018-06-06T08:20:45.437Z',mode:absolute,to:'2018-06-14T21:56:58.505Z'))&_a=(filters:!(),mlSelectInterval:(interval:(display:Auto,val:auto)),mlSelectSeverity:(threshold:(display:warning,val:0)),mlTimeSeriesExplorer:(),query:(query_string:(analyze_wildcard:!t,query:'*')))`;
@@ -207,7 +212,7 @@ describe('KibanaLinkComponent', () => {
it('should have correct url', () => {
expect(wrapper.find('a').prop('href')).toBe(
- "/app/kibana#/discover?_g=&_a=(interval:auto,query:(language:lucene,query:'context.service.name:myServiceName AND error.grouping_key:myGroupId'),sort:('@timestamp':desc))"
+ "myBasePath/app/kibana#/discover?_g=&_a=(interval:auto,query:(language:lucene,query:'context.service.name:myServiceName AND error.grouping_key:myGroupId'),sort:('@timestamp':desc))"
);
});
@@ -215,3 +220,23 @@ describe('KibanaLinkComponent', () => {
expect(toJson(wrapper)).toMatchSnapshot();
});
});
+
+describe('getMlJobUrl', () => {
+ it('should have correct url', () => {
+ const serviceName = 'myServiceName';
+ const transactionType = 'myTransactionType';
+ const location = { search: '' };
+ expect(getMlJobUrl(serviceName, transactionType, location)).toBe(
+ '/app/ml#/timeseriesexplorer/?_g=(ml:(jobIds:!(myServiceName-myTransactionType-high_mean_response_time)))&_a=!n'
+ );
+ });
+
+ it('should not contain basePath', () => {
+ const serviceName = 'myServiceName';
+ const transactionType = 'myTransactionType';
+ const location = { search: '' };
+ expect(getMlJobUrl(serviceName, transactionType, location)).toBe(
+ '/app/ml#/timeseriesexplorer/?_g=(ml:(jobIds:!(myServiceName-myTransactionType-high_mean_response_time)))&_a=!n'
+ );
+ });
+});
From 125cc36baec2a52cae5f06375b7ecfbf3d8dc613 Mon Sep 17 00:00:00 2001
From: Larry Gregory
Date: Thu, 6 Sep 2018 10:21:33 -0400
Subject: [PATCH 29/68] supply space id to tutorial context (#22760)
---
x-pack/plugins/spaces/index.js | 5 ++
...es_service.js => create_spaces_service.ts} | 11 ++--
.../spaces_tutorial_context_factory.test.ts | 55 +++++++++++++++++++
.../lib/spaces_tutorial_context_factory.ts | 15 +++++
4 files changed, 82 insertions(+), 4 deletions(-)
rename x-pack/plugins/spaces/server/lib/{create_spaces_service.js => create_spaces_service.ts} (75%)
create mode 100644 x-pack/plugins/spaces/server/lib/spaces_tutorial_context_factory.test.ts
create mode 100644 x-pack/plugins/spaces/server/lib/spaces_tutorial_context_factory.ts
diff --git a/x-pack/plugins/spaces/index.js b/x-pack/plugins/spaces/index.js
index 3eafab6461f9e..2679a894a6661 100644
--- a/x-pack/plugins/spaces/index.js
+++ b/x-pack/plugins/spaces/index.js
@@ -14,6 +14,7 @@ import { createDefaultSpace } from './server/lib/create_default_space';
import { createSpacesService } from './server/lib/create_spaces_service';
import { getActiveSpace } from './server/lib/get_active_space';
import { getSpacesUsageCollector } from './server/lib/get_spaces_usage_collector';
+import { createSpacesTutorialContextFactory } from './server/lib/spaces_tutorial_context_factory';
import { wrapError } from './server/lib/errors';
import mappings from './mappings.json';
import { spacesSavedObjectsClientWrapperFactory } from './server/lib/saved_objects_client/saved_objects_client_wrapper_factory';
@@ -94,6 +95,10 @@ export const spaces = (kibana) => new kibana.Plugin({
spacesSavedObjectsClientWrapperFactory(spacesService)
);
+ server.addScopedTutorialContextFactory(
+ createSpacesTutorialContextFactory(spacesService)
+ );
+
initPrivateApis(server);
initPublicSpacesApi(server);
diff --git a/x-pack/plugins/spaces/server/lib/create_spaces_service.js b/x-pack/plugins/spaces/server/lib/create_spaces_service.ts
similarity index 75%
rename from x-pack/plugins/spaces/server/lib/create_spaces_service.js
rename to x-pack/plugins/spaces/server/lib/create_spaces_service.ts
index 3ee55354da8dc..3269142a9cf17 100644
--- a/x-pack/plugins/spaces/server/lib/create_spaces_service.js
+++ b/x-pack/plugins/spaces/server/lib/create_spaces_service.ts
@@ -6,13 +6,16 @@
import { getSpaceIdFromPath } from './spaces_url_parser';
-export function createSpacesService(server) {
+export interface SpacesService {
+ getSpaceId: (req: any) => string;
+}
+export function createSpacesService(server: any): SpacesService {
const serverBasePath = server.config().get('server.basePath');
const contextCache = new WeakMap();
- function getSpaceId(request) {
+ function getSpaceId(request: any) {
if (!contextCache.has(request)) {
populateCache(request);
}
@@ -21,11 +24,11 @@ export function createSpacesService(server) {
return spaceId;
}
- function populateCache(request) {
+ function populateCache(request: any) {
const spaceId = getSpaceIdFromPath(request.getBasePath(), serverBasePath);
contextCache.set(request, {
- spaceId
+ spaceId,
});
}
diff --git a/x-pack/plugins/spaces/server/lib/spaces_tutorial_context_factory.test.ts b/x-pack/plugins/spaces/server/lib/spaces_tutorial_context_factory.test.ts
new file mode 100644
index 0000000000000..4ed548d64b574
--- /dev/null
+++ b/x-pack/plugins/spaces/server/lib/spaces_tutorial_context_factory.test.ts
@@ -0,0 +1,55 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { DEFAULT_SPACE_ID } from '../../common/constants';
+import { createSpacesService } from './create_spaces_service';
+import { createSpacesTutorialContextFactory } from './spaces_tutorial_context_factory';
+
+const server = {
+ config: () => {
+ return {
+ get: (key: string) => {
+ if (key === 'server.basePath') {
+ return '/foo';
+ }
+ throw new Error('unexpected key ' + key);
+ },
+ };
+ },
+};
+
+describe('createSpacesTutorialContextFactory', () => {
+ it('should create a valid context factory', () => {
+ const spacesService = createSpacesService(server);
+ expect(typeof createSpacesTutorialContextFactory(spacesService)).toEqual('function');
+ });
+
+ it('should create context with the current space id for space my-space-id', () => {
+ const spacesService = createSpacesService(server);
+ const contextFactory = createSpacesTutorialContextFactory(spacesService);
+
+ const request = {
+ getBasePath: () => '/foo/s/my-space-id',
+ };
+
+ expect(contextFactory(request)).toEqual({
+ spaceId: 'my-space-id',
+ });
+ });
+
+ it('should create context with the current space id for the default space', () => {
+ const spacesService = createSpacesService(server);
+ const contextFactory = createSpacesTutorialContextFactory(spacesService);
+
+ const request = {
+ getBasePath: () => '/foo',
+ };
+
+ expect(contextFactory(request)).toEqual({
+ spaceId: DEFAULT_SPACE_ID,
+ });
+ });
+});
diff --git a/x-pack/plugins/spaces/server/lib/spaces_tutorial_context_factory.ts b/x-pack/plugins/spaces/server/lib/spaces_tutorial_context_factory.ts
new file mode 100644
index 0000000000000..b3254fd3b3c07
--- /dev/null
+++ b/x-pack/plugins/spaces/server/lib/spaces_tutorial_context_factory.ts
@@ -0,0 +1,15 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { SpacesService } from './create_spaces_service';
+
+export function createSpacesTutorialContextFactory(spacesService: SpacesService) {
+ return function spacesTutorialContextFactory(request: any) {
+ return {
+ spaceId: spacesService.getSpaceId(request),
+ };
+ };
+}
From 5f96c903f379c89f384aaa5397b03f0c5894c4a7 Mon Sep 17 00:00:00 2001
From: Larry Gregory
Date: Thu, 6 Sep 2018 10:26:05 -0400
Subject: [PATCH 30/68] Deprecate xpack:defaultAdminEmail for monitoring alerts
(#22195)
---
docs/monitoring/cluster-alerts.asciidoc | 4 +-
.../monitoring/__tests__/deprecations.js | 62 +++++++
x-pack/plugins/monitoring/common/constants.js | 9 +-
x-pack/plugins/monitoring/config.js | 5 +-
x-pack/plugins/monitoring/deprecations.js | 12 +-
.../__tests__/check_for_email_value.js | 12 +-
.../__tests__/get_default_admin_email.js | 151 ++++++++++++++----
.../collectors/get_settings_collector.js | 35 +++-
8 files changed, 245 insertions(+), 45 deletions(-)
diff --git a/docs/monitoring/cluster-alerts.asciidoc b/docs/monitoring/cluster-alerts.asciidoc
index 15df791ae2746..fce76965dd9ae 100644
--- a/docs/monitoring/cluster-alerts.asciidoc
+++ b/docs/monitoring/cluster-alerts.asciidoc
@@ -46,8 +46,6 @@ To receive email notifications for the Cluster Alerts:
1. Configure an email account as described in
{xpack-ref}/actions-email.html#configuring-email[Configuring Email Accounts].
-2. Navigate to the *Management* page in {kib}.
-3. Go to the *Advanced Settings* page, find the `xpack:defaultAdminEmail`
-setting, and enter your email address.
+2. Configure the `xpack.monitoring.cluster_alerts.email_notifications.email_address` setting in `kibana.yml` with your email address.
Email notifications are sent only when Cluster Alerts are triggered and resolved.
diff --git a/x-pack/plugins/monitoring/__tests__/deprecations.js b/x-pack/plugins/monitoring/__tests__/deprecations.js
index 8eb6473cfd318..6b8668b156a8f 100644
--- a/x-pack/plugins/monitoring/__tests__/deprecations.js
+++ b/x-pack/plugins/monitoring/__tests__/deprecations.js
@@ -112,4 +112,66 @@ describe('monitoring plugin deprecations', function () {
expect(log.called).to.be(false);
});
+ describe('cluster_alerts.email_notifications.email_address', function () {
+ it(`shouldn't log when email notifications are disabled`, function () {
+ const settings = {
+ cluster_alerts: {
+ email_notifications: {
+ enabled: false
+ }
+ }
+ };
+
+ const log = sinon.spy();
+ transformDeprecations(settings, log);
+ expect(log.called).to.be(false);
+ });
+
+ it(`shouldn't log when cluster alerts are disabled`, function () {
+ const settings = {
+ cluster_alerts: {
+ enabled: false,
+ email_notifications: {
+ enabled: true
+ }
+ }
+ };
+
+ const log = sinon.spy();
+ transformDeprecations(settings, log);
+ expect(log.called).to.be(false);
+ });
+
+ it(`shouldn't log when email_address is specified`, function () {
+ const settings = {
+ cluster_alerts: {
+ enabled: true,
+ email_notifications: {
+ enabled: true,
+ email_address: 'foo@bar.com'
+ }
+ }
+ };
+
+ const log = sinon.spy();
+ transformDeprecations(settings, log);
+ expect(log.called).to.be(false);
+ });
+
+ it(`should log when email_address is missing, but alerts/notifications are both enabled`, function () {
+ const settings = {
+ cluster_alerts: {
+ enabled: true,
+ email_notifications: {
+ enabled: true
+ }
+ }
+ };
+
+ const log = sinon.spy();
+ transformDeprecations(settings, log);
+ expect(log.called).to.be(true);
+ });
+ });
+
});
diff --git a/x-pack/plugins/monitoring/common/constants.js b/x-pack/plugins/monitoring/common/constants.js
index 718606d8b6be2..7bc8164130adf 100644
--- a/x-pack/plugins/monitoring/common/constants.js
+++ b/x-pack/plugins/monitoring/common/constants.js
@@ -94,7 +94,7 @@ export const CALCULATE_DURATION_UNTIL = 'until';
/**
* In order to show ML Jobs tab in the Elasticsearch section / tab navigation, license must be supported
*/
-export const ML_SUPPORTED_LICENSES = [ 'trial', 'platinum' ];
+export const ML_SUPPORTED_LICENSES = ['trial', 'platinum'];
/**
* Metadata service URLs for the different cloud services that have constant URLs (e.g., unlike GCP, which is a constant prefix).
@@ -135,7 +135,12 @@ export const DEFAULT_NO_DATA_MESSAGE_WITH_FILTER = (
);
export const TABLE_ACTION_UPDATE_FILTER = 'UPDATE_FILTER';
-export const TABLE_ACTION_RESET_PAGING = 'RESET_PAGING';
+export const TABLE_ACTION_RESET_PAGING = 'RESET_PAGING';
export const DEBOUNCE_SLOW_MS = 17; // roughly how long it takes to render a frame at 60fps
export const DEBOUNCE_FAST_MS = 10; // roughly how long it takes to render a frame at 100fps
+
+/**
+ * Configuration key for setting the email address used for cluster alert notifications.
+ */
+export const CLUSTER_ALERTS_ADDRESS_CONFIG_KEY = 'cluster_alerts.email_notifications.email_address';
diff --git a/x-pack/plugins/monitoring/config.js b/x-pack/plugins/monitoring/config.js
index 02ac28728206a..431aed95182fb 100644
--- a/x-pack/plugins/monitoring/config.js
+++ b/x-pack/plugins/monitoring/config.js
@@ -13,7 +13,7 @@ import { XPACK_INFO_API_DEFAULT_POLL_FREQUENCY_IN_MILLIS } from '../../server/li
*/
export const config = (Joi) => {
const { array, boolean, number, object, string } = Joi;
- const DEFAULT_REQUEST_HEADERS = [ 'authorization' ];
+ const DEFAULT_REQUEST_HEADERS = ['authorization'];
return object({
ccs: object({
@@ -49,7 +49,8 @@ export const config = (Joi) => {
enabled: boolean().default(true),
index: string().default('.monitoring-alerts-6'),
email_notifications: object({
- enabled: boolean().default(true)
+ enabled: boolean().default(true),
+ email_address: string().email(),
}).default()
}).default(),
xpack_api_polling_frequency_millis: number().default(XPACK_INFO_API_DEFAULT_POLL_FREQUENCY_IN_MILLIS),
diff --git a/x-pack/plugins/monitoring/deprecations.js b/x-pack/plugins/monitoring/deprecations.js
index 8e882975a29cc..a6b91d4a6d604 100644
--- a/x-pack/plugins/monitoring/deprecations.js
+++ b/x-pack/plugins/monitoring/deprecations.js
@@ -5,6 +5,7 @@
*/
import { get, has, set } from 'lodash';
+import { CLUSTER_ALERTS_ADDRESS_CONFIG_KEY } from './common/constants';
/**
* Re-writes deprecated user-defined config settings and logs warnings as a
@@ -29,12 +30,19 @@ export const deprecations = ({ rename }) => {
delete settings.elasticsearch.ssl.verify;
log('Config key "xpack.monitoring.elasticsearch.ssl.verify" is deprecated. ' +
- 'It has been replaced with "xpack.monitoring.elasticsearch.ssl.verificationMode"');
+ 'It has been replaced with "xpack.monitoring.elasticsearch.ssl.verificationMode"');
},
(settings, log) => {
if (has(settings, 'report_stats')) {
log('Config key "xpack.monitoring.report_stats" is deprecated and will be removed in 7.0. ' +
- 'Use "xpack.xpack_main.telemetry.enabled" instead.');
+ 'Use "xpack.xpack_main.telemetry.enabled" instead.');
+ }
+ },
+ (settings, log) => {
+ const clusterAlertsEnabled = get(settings, 'cluster_alerts.enabled');
+ const emailNotificationsEnabled = clusterAlertsEnabled && get(settings, 'cluster_alerts.email_notifications.enabled');
+ if (emailNotificationsEnabled && !get(settings, CLUSTER_ALERTS_ADDRESS_CONFIG_KEY)) {
+ log(`Config key "${CLUSTER_ALERTS_ADDRESS_CONFIG_KEY}" will be required for email notifications to work in 7.0."`);
}
},
];
diff --git a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/__tests__/check_for_email_value.js b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/__tests__/check_for_email_value.js
index a2bc6f4b9419a..4189903684a9b 100644
--- a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/__tests__/check_for_email_value.js
+++ b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/__tests__/check_for_email_value.js
@@ -8,27 +8,31 @@ import expect from 'expect.js';
import { checkForEmailValue } from '../get_settings_collector';
describe('getSettingsCollector / checkForEmailValue', () => {
+ const mockLogger = {
+ warn: () => { }
+ };
+
it('ignores shouldUseNull=true value and returns email if email value if one is set', async () => {
const shouldUseNull = true;
const getDefaultAdminEmailMock = () => 'test@elastic.co';
- expect(await checkForEmailValue(undefined, undefined, shouldUseNull, getDefaultAdminEmailMock)).to.be('test@elastic.co');
+ expect(await checkForEmailValue(undefined, undefined, mockLogger, shouldUseNull, getDefaultAdminEmailMock)).to.be('test@elastic.co');
});
it('ignores shouldUseNull=false value and returns email if email value if one is set', async () => {
const shouldUseNull = false;
const getDefaultAdminEmailMock = () => 'test@elastic.co';
- expect(await checkForEmailValue(undefined, undefined, shouldUseNull, getDefaultAdminEmailMock)).to.be('test@elastic.co');
+ expect(await checkForEmailValue(undefined, undefined, mockLogger, shouldUseNull, getDefaultAdminEmailMock)).to.be('test@elastic.co');
});
it('returns a null if no email value is set and null is allowed', async () => {
const shouldUseNull = true;
const getDefaultAdminEmailMock = () => null;
- expect(await checkForEmailValue(undefined, undefined, shouldUseNull, getDefaultAdminEmailMock)).to.be(null);
+ expect(await checkForEmailValue(undefined, undefined, mockLogger, shouldUseNull, getDefaultAdminEmailMock)).to.be(null);
});
it('returns undefined if no email value is set and null is not allowed', async () => {
const shouldUseNull = false;
const getDefaultAdminEmailMock = () => null;
- expect(await checkForEmailValue(undefined, undefined, shouldUseNull, getDefaultAdminEmailMock)).to.be(undefined);
+ expect(await checkForEmailValue(undefined, undefined, mockLogger, shouldUseNull, getDefaultAdminEmailMock)).to.be(undefined);
});
});
diff --git a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/__tests__/get_default_admin_email.js b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/__tests__/get_default_admin_email.js
index 7796cb9621d7c..434f6bc41f585 100644
--- a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/__tests__/get_default_admin_email.js
+++ b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/__tests__/get_default_admin_email.js
@@ -9,16 +9,23 @@ import sinon from 'sinon';
import { set } from 'lodash';
import { XPACK_DEFAULT_ADMIN_EMAIL_UI_SETTING } from '../../../../../../server/lib/constants';
-import { getDefaultAdminEmail } from '../get_settings_collector';
+import { getDefaultAdminEmail, resetDeprecationWarning } from '../get_settings_collector';
+import { CLUSTER_ALERTS_ADDRESS_CONFIG_KEY } from '../../../../common/constants';
describe('getSettingsCollector / getDefaultAdminEmail', () => {
- function setup({ enabled = true, docExists = true, adminEmail = 'admin@email.com' }) {
+ function setup({ enabled = true, docExists = true, defaultAdminEmail = 'default-admin@email.com', adminEmail = null }) {
const config = { get: sinon.stub() };
config.get
.withArgs('xpack.monitoring.cluster_alerts.email_notifications.enabled')
.returns(enabled);
+ if (adminEmail) {
+ config.get
+ .withArgs(`xpack.monitoring.${CLUSTER_ALERTS_ADDRESS_CONFIG_KEY}`)
+ .returns(adminEmail);
+ }
+
config.get
.withArgs('kibana.index')
.returns('.kibana');
@@ -29,8 +36,8 @@ describe('getSettingsCollector / getDefaultAdminEmail', () => {
const doc = {};
if (docExists) {
- if (adminEmail) {
- set(doc, ['_source', 'config', XPACK_DEFAULT_ADMIN_EMAIL_UI_SETTING], adminEmail);
+ if (defaultAdminEmail) {
+ set(doc, ['_source', 'config', XPACK_DEFAULT_ADMIN_EMAIL_UI_SETTING], defaultAdminEmail);
} else {
set(doc, '_source.config', {});
}
@@ -46,41 +53,131 @@ describe('getSettingsCollector / getDefaultAdminEmail', () => {
}))
.returns(doc);
+ const log = {
+ warn: sinon.stub()
+ };
+
return {
config,
- callCluster
+ callCluster,
+ log,
};
}
- describe('xpack.monitoring.cluster_alerts.email_notifications.enabled = false', () => {
- it('returns null', async () => {
- const { config, callCluster } = setup({ enabled: false });
- expect(await getDefaultAdminEmail(config, callCluster)).to.be(null);
- sinon.assert.notCalled(callCluster);
+ describe('using xpack:defaultAdminEmail', () => {
+ beforeEach(() => {
+ resetDeprecationWarning();
});
- });
- describe('doc does not exist', () => {
- it('returns null', async () => {
- const { config, callCluster } = setup({ docExists: false });
- expect(await getDefaultAdminEmail(config, callCluster)).to.be(null);
- sinon.assert.calledOnce(callCluster);
+ describe('xpack.monitoring.cluster_alerts.email_notifications.enabled = false', () => {
+
+ it('returns null', async () => {
+ const { config, callCluster, log } = setup({ enabled: false });
+ expect(await getDefaultAdminEmail(config, callCluster, log)).to.be(null);
+ sinon.assert.notCalled(callCluster);
+ });
+
+ it('does not log a deprecation warning', async () => {
+ const { config, callCluster, log } = setup({ enabled: false });
+ await getDefaultAdminEmail(config, callCluster, log);
+ sinon.assert.notCalled(log.warn);
+ });
});
- });
- describe('value is not defined', () => {
- it('returns null', async () => {
- const { config, callCluster } = setup({ adminEmail: false });
- expect(await getDefaultAdminEmail(config, callCluster)).to.be(null);
- sinon.assert.calledOnce(callCluster);
+ describe('doc does not exist', () => {
+ it('returns null', async () => {
+ const { config, callCluster, log } = setup({ docExists: false });
+ expect(await getDefaultAdminEmail(config, callCluster, log)).to.be(null);
+ sinon.assert.calledOnce(callCluster);
+ });
+
+ it('logs a deprecation warning', async () => {
+ const { config, callCluster, log } = setup({ docExists: false });
+ await getDefaultAdminEmail(config, callCluster, log);
+ sinon.assert.calledOnce(log.warn);
+ });
+ });
+
+ describe('value is not defined', () => {
+ it('returns null', async () => {
+ const { config, callCluster, log } = setup({ defaultAdminEmail: false });
+ expect(await getDefaultAdminEmail(config, callCluster, log)).to.be(null);
+ sinon.assert.calledOnce(callCluster);
+ });
+
+ it('logs a deprecation warning', async () => {
+ const { config, callCluster, log } = setup({ defaultAdminEmail: false });
+ await getDefaultAdminEmail(config, callCluster, log);
+ sinon.assert.calledOnce(log.warn);
+ });
+ });
+
+ describe('value is defined', () => {
+ it('returns value', async () => {
+ const { config, callCluster, log } = setup({ defaultAdminEmail: 'hello@world' });
+ expect(await getDefaultAdminEmail(config, callCluster, log)).to.be('hello@world');
+ sinon.assert.calledOnce(callCluster);
+ });
+
+ it('logs a deprecation warning', async () => {
+ const { config, callCluster, log } = setup({ defaultAdminEmail: 'hello@world' });
+ await getDefaultAdminEmail(config, callCluster, log);
+ sinon.assert.calledOnce(log.warn);
+ });
});
});
- describe('value is defined', () => {
- it('returns value', async () => {
- const { config, callCluster } = setup({ adminEmail: 'hello@world' });
- expect(await getDefaultAdminEmail(config, callCluster)).to.be('hello@world');
- sinon.assert.calledOnce(callCluster);
+ describe('using xpack.monitoring.cluster_alerts.email_notifications.email_address', () => {
+ beforeEach(() => {
+ resetDeprecationWarning();
+ });
+
+ describe('xpack.monitoring.cluster_alerts.email_notifications.enabled = false', () => {
+ it('returns null', async () => {
+ const { config, callCluster, log } = setup({ enabled: false });
+ expect(await getDefaultAdminEmail(config, callCluster, log)).to.be(null);
+ sinon.assert.notCalled(callCluster);
+ });
+
+ it('does not log a deprecation warning', async () => {
+ const { config, callCluster, log } = setup({ enabled: false });
+ await getDefaultAdminEmail(config, callCluster, log);
+ sinon.assert.notCalled(log.warn);
+ });
+ });
+
+ describe('value is not defined', () => {
+ it('returns value from xpack:defaultAdminEmail', async () => {
+ const { config, callCluster, log } = setup({
+ defaultAdminEmail: 'default-admin@email.com',
+ adminEmail: false
+ });
+ expect(await getDefaultAdminEmail(config, callCluster, log)).to.be('default-admin@email.com');
+ sinon.assert.calledOnce(callCluster);
+ });
+
+ it('logs a deprecation warning', async () => {
+ const { config, callCluster, log } = setup({
+ defaultAdminEmail: 'default-admin@email.com',
+ adminEmail: false
+ });
+ await getDefaultAdminEmail(config, callCluster, log);
+ sinon.assert.calledOnce(log.warn);
+ });
+ });
+
+ describe('value is defined', () => {
+ it('returns value', async () => {
+ const { config, callCluster, log } = setup({ adminEmail: 'hello@world' });
+ expect(await getDefaultAdminEmail(config, callCluster, log)).to.be('hello@world');
+ sinon.assert.notCalled(callCluster);
+ });
+
+ it('does not log a deprecation warning', async () => {
+ const { config, callCluster, log } = setup({ adminEmail: 'hello@world' });
+ await getDefaultAdminEmail(config, callCluster, log);
+ sinon.assert.notCalled(log.warn);
+ });
});
});
});
diff --git a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/get_settings_collector.js b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/get_settings_collector.js
index c0793a4bb6b9d..0deafc2c49826 100644
--- a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/get_settings_collector.js
+++ b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/get_settings_collector.js
@@ -6,24 +6,48 @@
import { get } from 'lodash';
import { XPACK_DEFAULT_ADMIN_EMAIL_UI_SETTING } from '../../../../../server/lib/constants';
-import { KIBANA_SETTINGS_TYPE } from '../../../common/constants';
+import { CLUSTER_ALERTS_ADDRESS_CONFIG_KEY, KIBANA_SETTINGS_TYPE } from '../../../common/constants';
+
+let loggedDeprecationWarning = false;
+
+export function resetDeprecationWarning() {
+ loggedDeprecationWarning = false;
+}
/*
* Check if Cluster Alert email notifications is enabled in config
* If so, use uiSettings API to fetch the X-Pack default admin email
*/
-export async function getDefaultAdminEmail(config, callCluster) {
+export async function getDefaultAdminEmail(config, callCluster, log) {
if (!config.get('xpack.monitoring.cluster_alerts.email_notifications.enabled')) {
return null;
}
+ const emailAddressConfigKey = `xpack.monitoring.${CLUSTER_ALERTS_ADDRESS_CONFIG_KEY}`;
+ const configuredEmailAddress = config.get(emailAddressConfigKey);
+
+ if (configuredEmailAddress) {
+ return configuredEmailAddress;
+ }
+
+ // DEPRECATED (Remove below in 7.0): If an email address is not configured in kibana.yml, then fallback to xpack:defaultAdminEmail
+ if (!loggedDeprecationWarning) {
+ const message = (
+ `Monitoring is using ${XPACK_DEFAULT_ADMIN_EMAIL_UI_SETTING} for cluster alert notifications, ` +
+ `which will not be supported in Kibana 7.0. Please configure ${emailAddressConfigKey} in your kibana.yml settings`
+ );
+
+ log.warn(message);
+ loggedDeprecationWarning = true;
+ }
+
const index = config.get('kibana.index');
const version = config.get('pkg.version');
const uiSettingsDoc = await callCluster('get', {
index,
type: 'doc',
id: `config:${version}`,
- ignore: [ 400, 404 ] // 400 if the index is closed, 404 if it does not exist
+ ignore: [400, 404] // 400 if the index is closed, 404 if it does not exist
});
return get(uiSettingsDoc, ['_source', 'config', XPACK_DEFAULT_ADMIN_EMAIL_UI_SETTING], null);
@@ -35,10 +59,11 @@ let shouldUseNull = true;
export async function checkForEmailValue(
config,
callCluster,
+ log,
_shouldUseNull = shouldUseNull,
_getDefaultAdminEmail = getDefaultAdminEmail
) {
- const defaultAdminEmail = await _getDefaultAdminEmail(config, callCluster);
+ const defaultAdminEmail = await _getDefaultAdminEmail(config, callCluster, log);
// Allow null so clearing the advanced setting will be reflected in the data
const isAcceptableNull = defaultAdminEmail === null && _shouldUseNull;
@@ -61,7 +86,7 @@ export function getSettingsCollector(server) {
type: KIBANA_SETTINGS_TYPE,
async fetch(callCluster) {
let kibanaSettingsData;
- const defaultAdminEmail = await checkForEmailValue(config, callCluster);
+ const defaultAdminEmail = await checkForEmailValue(config, callCluster, this.log);
// skip everything if defaultAdminEmail === undefined
if (defaultAdminEmail || (defaultAdminEmail === null && shouldUseNull)) {
From 3d6de7cbc46aa84d327ae155bfe7d14f98e49326 Mon Sep 17 00:00:00 2001
From: Aleh Zasypkin
Date: Thu, 6 Sep 2018 18:39:04 +0300
Subject: [PATCH 31/68] Implement `LegacyService`. Use `core` to start legacy
Kibana. (#22190)
* Implement `LegacyService`. Use `core` to start legacy Kibana.
* Fix Worker tests
* Do not rely on kbnServer when testing mixins.
---
package.json | 6 +-
.../cluster.js} | 28 +-
src/cli/cluster/cluster_manager.js | 59 +--
src/cli/cluster/cluster_manager.test.js | 146 +++++++-
src/cli/cluster/configure_base_path_proxy.js | 64 ----
.../cluster/configure_base_path_proxy.test.js | 163 ---------
src/cli/cluster/worker.test.js | 152 ++++----
src/cli/color.js | 7 +-
.../__snapshots__/invalid_config.test.js.snap | 5 +-
.../integration_tests/invalid_config.test.js | 3 +-
src/cli/serve/serve.js | 119 ++----
src/core/README.md | 29 +-
src/core/index.ts | 20 --
.../server/__snapshots__/index.test.ts.snap | 21 ++
src/core/server/bootstrap.ts | 113 ++++++
.../server/config/__tests__/__mocks__/env.ts | 11 +-
.../__tests__/__snapshots__/env.test.ts.snap | 122 +++++--
.../config/__tests__/apply_argv.test.ts | 2 +-
src/core/server/config/__tests__/env.test.ts | 75 ++--
src/core/server/config/env.ts | 29 +-
src/core/server/config/index.ts | 2 +-
.../config/schema/byte_size_value/index.ts | 3 +-
.../__snapshots__/http_config.test.ts.snap | 1 +
.../__snapshots__/http_server.test.ts.snap | 2 +-
.../server/http/__tests__/http_server.test.ts | 107 +++---
.../http/__tests__/http_service.test.ts | 51 ++-
.../server/http/base_path_proxy_server.ts | 64 ++--
src/core/server/http/http_config.ts | 3 +
src/core/server/http/http_server.ts | 31 +-
src/core/server/http/http_service.ts | 11 +-
src/core/server/http/index.ts | 12 +-
src/core/server/index.test.ts | 121 +++++++
src/core/server/index.ts | 34 +-
.../legacy_platform_proxifier.test.ts.snap | 21 --
.../__snapshots__/legacy_service.test.ts.snap | 137 +++++++
....test.ts => legacy_platform_proxy.test.ts} | 74 +---
.../__tests__/legacy_service.test.ts | 339 ++++++++++++++++++
...gacy_object_to_config_adapter.test.ts.snap | 1 +
.../config/legacy_object_to_config_adapter.ts | 3 +-
src/core/server/legacy_compat/index.ts | 55 +--
.../legacy_platform_proxifier.ts | 172 ---------
.../legacy_compat/legacy_platform_proxy.ts | 107 ++++++
.../server/legacy_compat/legacy_service.ts | 204 +++++++++++
src/core/server/logging/logging_service.ts | 2 +-
src/core/server/root/base_path_proxy_root.ts | 80 -----
src/core/server/root/index.ts | 59 ++-
src/core/types/core_service.ts | 4 +-
.../server/lib/__tests__/manage_uuid.js | 2 +-
.../__tests__/lib/index.js | 20 --
.../__tests__/lib/kibana.js | 36 --
.../config/__tests__/deprecation_warnings.js | 3 +-
.../fixtures/run_kbn_server_startup.js | 8 +-
.../max_payload_size.test.js.snap | 3 -
.../http/__snapshots__/xsrf.test.js.snap | 7 -
src/server/http/index.js | 30 +-
.../max_payload_size.test.js | 52 +++
.../version_check.test.js | 63 ++--
.../http/{ => integration_tests}/xsrf.test.js | 132 ++-----
src/server/http/max_payload_size.test.js | 70 ----
src/server/http/setup_connection.js | 0
src/server/kbn_server.js | 32 +-
src/test_utils/base_auth.js | 23 --
src/test_utils/kbn_server.js | 155 --------
src/test_utils/kbn_server.ts | 184 ++++++++++
.../ui_exports_replace_injected_vars.js | 84 +++--
.../__tests__/field_formats_mixin.js | 24 +-
src/ui/tutorials_mixin.test.js | 27 +-
x-pack/package.json | 4 +-
x-pack/yarn.lock | 12 +-
yarn.lock | 18 +-
70 files changed, 2152 insertions(+), 1711 deletions(-)
rename src/cli/cluster/{_mock_cluster_fork.js => __mocks__/cluster.js} (75%)
delete mode 100644 src/cli/cluster/configure_base_path_proxy.js
delete mode 100644 src/cli/cluster/configure_base_path_proxy.test.js
delete mode 100644 src/core/index.ts
create mode 100644 src/core/server/__snapshots__/index.test.ts.snap
create mode 100644 src/core/server/bootstrap.ts
create mode 100644 src/core/server/index.test.ts
delete mode 100644 src/core/server/legacy_compat/__tests__/__snapshots__/legacy_platform_proxifier.test.ts.snap
create mode 100644 src/core/server/legacy_compat/__tests__/__snapshots__/legacy_service.test.ts.snap
rename src/core/server/legacy_compat/__tests__/{legacy_platform_proxifier.test.ts => legacy_platform_proxy.test.ts} (51%)
create mode 100644 src/core/server/legacy_compat/__tests__/legacy_service.test.ts
delete mode 100644 src/core/server/legacy_compat/legacy_platform_proxifier.ts
create mode 100644 src/core/server/legacy_compat/legacy_platform_proxy.ts
create mode 100644 src/core/server/legacy_compat/legacy_service.ts
delete mode 100644 src/core/server/root/base_path_proxy_root.ts
delete mode 100644 src/functional_test_runner/__tests__/lib/index.js
delete mode 100644 src/functional_test_runner/__tests__/lib/kibana.js
delete mode 100644 src/server/http/__snapshots__/max_payload_size.test.js.snap
delete mode 100644 src/server/http/__snapshots__/xsrf.test.js.snap
create mode 100644 src/server/http/integration_tests/max_payload_size.test.js
rename src/server/http/{ => integration_tests}/version_check.test.js (53%)
rename src/server/http/{ => integration_tests}/xsrf.test.js (55%)
delete mode 100644 src/server/http/max_payload_size.test.js
delete mode 100644 src/server/http/setup_connection.js
delete mode 100644 src/test_utils/base_auth.js
delete mode 100644 src/test_utils/kbn_server.js
create mode 100644 src/test_utils/kbn_server.ts
diff --git a/package.json b/package.json
index 5ea6728b6458e..0e372f5a65513 100644
--- a/package.json
+++ b/package.json
@@ -239,7 +239,7 @@
"@types/redux-actions": "^2.2.1",
"@types/sinon": "^5.0.0",
"@types/strip-ansi": "^3.0.0",
- "@types/supertest": "^2.0.4",
+ "@types/supertest": "^2.0.5",
"@types/type-detect": "^4.0.1",
"angular-mocks": "1.4.7",
"babel-eslint": "8.1.2",
@@ -318,8 +318,8 @@
"simple-git": "1.37.0",
"sinon": "^5.0.7",
"strip-ansi": "^3.0.1",
- "supertest": "3.0.0",
- "supertest-as-promised": "4.0.2",
+ "supertest": "^3.1.0",
+ "supertest-as-promised": "^4.0.2",
"tree-kill": "^1.1.0",
"ts-jest": "^22.4.6",
"ts-loader": "^3.5.0",
diff --git a/src/cli/cluster/_mock_cluster_fork.js b/src/cli/cluster/__mocks__/cluster.js
similarity index 75%
rename from src/cli/cluster/_mock_cluster_fork.js
rename to src/cli/cluster/__mocks__/cluster.js
index 4312f6a85c53a..14efc4b6f0150 100644
--- a/src/cli/cluster/_mock_cluster_fork.js
+++ b/src/cli/cluster/__mocks__/cluster.js
@@ -16,15 +16,14 @@
* specific language governing permissions and limitations
* under the License.
*/
+/* eslint-env jest */
import EventEmitter from 'events';
import { assign, random } from 'lodash';
-import sinon from 'sinon';
-import cluster from 'cluster';
import { delay } from 'bluebird';
-export default class MockClusterFork extends EventEmitter {
- constructor() {
+class MockClusterFork extends EventEmitter {
+ constructor(cluster) {
super();
let dead = true;
@@ -35,7 +34,7 @@ export default class MockClusterFork extends EventEmitter {
assign(this, {
process: {
- kill: sinon.spy(() => {
+ kill: jest.fn(() => {
(async () => {
await wait();
this.emit('disconnect');
@@ -46,13 +45,13 @@ export default class MockClusterFork extends EventEmitter {
})();
}),
},
- isDead: sinon.spy(() => dead),
- send: sinon.stub()
+ isDead: jest.fn(() => dead),
+ send: jest.fn()
});
- sinon.spy(this, 'on');
- sinon.spy(this, 'removeListener');
- sinon.spy(this, 'emit');
+ jest.spyOn(this, 'on');
+ jest.spyOn(this, 'removeListener');
+ jest.spyOn(this, 'emit');
(async () => {
await wait();
@@ -61,3 +60,12 @@ export default class MockClusterFork extends EventEmitter {
})();
}
}
+
+class MockCluster extends EventEmitter {
+ fork = jest.fn(() => new MockClusterFork(this));
+ setupMaster = jest.fn();
+}
+
+export function mockCluster() {
+ return new MockCluster();
+}
diff --git a/src/cli/cluster/cluster_manager.js b/src/cli/cluster/cluster_manager.js
index 0a514138b09f2..1ea8a91eb21ef 100644
--- a/src/cli/cluster/cluster_manager.js
+++ b/src/cli/cluster/cluster_manager.js
@@ -19,31 +19,30 @@
import { resolve } from 'path';
import { debounce, invoke, bindAll, once, uniq } from 'lodash';
+import { fromEvent, race } from 'rxjs';
+import { first } from 'rxjs/operators';
import Log from '../log';
import Worker from './worker';
import { Config } from '../../server/config/config';
import { transformDeprecations } from '../../server/config/transform_deprecations';
-import { configureBasePathProxy } from './configure_base_path_proxy';
process.env.kbnWorkerType = 'managr';
export default class ClusterManager {
- static async create(opts = {}, settings = {}) {
- const transformedSettings = transformDeprecations(settings);
- const config = Config.withDefaultSchema(transformedSettings);
-
- const basePathProxy = opts.basePath
- ? await configureBasePathProxy(config)
- : undefined;
-
- return new ClusterManager(opts, config, basePathProxy);
+ static create(opts, settings = {}, basePathProxy) {
+ return new ClusterManager(
+ opts,
+ Config.withDefaultSchema(transformDeprecations(settings)),
+ basePathProxy
+ );
}
constructor(opts, config, basePathProxy) {
this.log = new Log(opts.quiet, opts.silent);
this.addedCount = 0;
this.inReplMode = !!opts.repl;
+ this.basePathProxy = basePathProxy;
const serverArgv = [];
const optimizerArgv = [
@@ -51,17 +50,15 @@ export default class ClusterManager {
'--server.autoListen=false',
];
- if (basePathProxy) {
- this.basePathProxy = basePathProxy;
-
+ if (this.basePathProxy) {
optimizerArgv.push(
- `--server.basePath=${this.basePathProxy.getBasePath()}`,
+ `--server.basePath=${this.basePathProxy.basePath}`,
'--server.rewriteBasePath=true',
);
serverArgv.push(
- `--server.port=${this.basePathProxy.getTargetPort()}`,
- `--server.basePath=${this.basePathProxy.getBasePath()}`,
+ `--server.port=${this.basePathProxy.targetPort}`,
+ `--server.basePath=${this.basePathProxy.basePath}`,
'--server.rewriteBasePath=true',
);
}
@@ -82,12 +79,6 @@ export default class ClusterManager {
})
];
- if (basePathProxy) {
- // Pass server worker to the basepath proxy so that it can hold off the
- // proxying until server worker is ready.
- this.basePathProxy.serverWorker = this.server;
- }
-
// broker messages between workers
this.workers.forEach((worker) => {
worker.on('broadcast', (msg) => {
@@ -130,7 +121,10 @@ export default class ClusterManager {
this.setupManualRestart();
invoke(this.workers, 'start');
if (this.basePathProxy) {
- this.basePathProxy.start();
+ this.basePathProxy.start({
+ blockUntil: this.blockUntil.bind(this),
+ shouldRedirectFromOldBasePath: this.shouldRedirectFromOldBasePath.bind(this),
+ });
}
}
@@ -222,4 +216,23 @@ export default class ClusterManager {
this.log.bad('failed to watch files!\n', err.stack);
process.exit(1); // eslint-disable-line no-process-exit
}
+
+ shouldRedirectFromOldBasePath(path) {
+ const isApp = path.startsWith('app/');
+ const isKnownShortPath = ['login', 'logout', 'status'].includes(path);
+
+ return isApp || isKnownShortPath;
+ }
+
+ blockUntil() {
+ // Wait until `server` worker either crashes or starts to listen.
+ if (this.server.listening || this.server.crashed) {
+ return Promise.resolve();
+ }
+
+ return race(
+ fromEvent(this.server, 'listening'),
+ fromEvent(this.server, 'crashed')
+ ).pipe(first()).toPromise();
+ }
}
diff --git a/src/cli/cluster/cluster_manager.test.js b/src/cli/cluster/cluster_manager.test.js
index b80ee62da29c3..ab42c4a369bb8 100644
--- a/src/cli/cluster/cluster_manager.test.js
+++ b/src/cli/cluster/cluster_manager.test.js
@@ -17,36 +17,43 @@
* under the License.
*/
-import sinon from 'sinon';
+import { mockCluster } from './__mocks__/cluster';
+jest.mock('cluster', () => mockCluster());
+jest.mock('readline', () => ({
+ createInterface: jest.fn(() => ({
+ on: jest.fn(),
+ prompt: jest.fn(),
+ setPrompt: jest.fn(),
+ })),
+}));
+
import cluster from 'cluster';
import { sample } from 'lodash';
import ClusterManager from './cluster_manager';
import Worker from './worker';
-describe('CLI cluster manager', function () {
- const sandbox = sinon.createSandbox();
-
- beforeEach(function () {
- sandbox.stub(cluster, 'fork').callsFake(() => {
+describe('CLI cluster manager', () => {
+ beforeEach(() => {
+ cluster.fork.mockImplementation(() => {
return {
process: {
- kill: sinon.stub(),
+ kill: jest.fn(),
},
- isDead: sinon.stub().returns(false),
- removeListener: sinon.stub(),
- on: sinon.stub(),
- send: sinon.stub()
+ isDead: jest.fn().mockReturnValue(false),
+ removeListener: jest.fn(),
+ addListener: jest.fn(),
+ send: jest.fn()
};
});
});
- afterEach(function () {
- sandbox.restore();
+ afterEach(() => {
+ cluster.fork.mockReset();
});
- it('has two workers', async function () {
- const manager = await ClusterManager.create({});
+ test('has two workers', () => {
+ const manager = ClusterManager.create({});
expect(manager.workers).toHaveLength(2);
for (const worker of manager.workers) expect(worker).toBeInstanceOf(Worker);
@@ -55,8 +62,8 @@ describe('CLI cluster manager', function () {
expect(manager.server).toBeInstanceOf(Worker);
});
- it('delivers broadcast messages to other workers', async function () {
- const manager = await ClusterManager.create({});
+ test('delivers broadcast messages to other workers', () => {
+ const manager = ClusterManager.create({});
for (const worker of manager.workers) {
Worker.prototype.start.call(worker);// bypass the debounced start method
@@ -69,10 +76,111 @@ describe('CLI cluster manager', function () {
messenger.emit('broadcast', football);
for (const worker of manager.workers) {
if (worker === messenger) {
- expect(worker.fork.send.callCount).toBe(0);
+ expect(worker.fork.send).not.toHaveBeenCalled();
} else {
- expect(worker.fork.send.firstCall.args[0]).toBe(football);
+ expect(worker.fork.send).toHaveBeenCalledTimes(1);
+ expect(worker.fork.send).toHaveBeenCalledWith(football);
}
}
});
+
+ describe('interaction with BasePathProxy', () => {
+ test('correctly configures `BasePathProxy`.', async () => {
+ const basePathProxyMock = { start: jest.fn() };
+
+ ClusterManager.create({}, {}, basePathProxyMock);
+
+ expect(basePathProxyMock.start).toHaveBeenCalledWith({
+ shouldRedirectFromOldBasePath: expect.any(Function),
+ blockUntil: expect.any(Function),
+ });
+ });
+
+ describe('proxy is configured with the correct `shouldRedirectFromOldBasePath` and `blockUntil` functions.', () => {
+ let clusterManager;
+ let shouldRedirectFromOldBasePath;
+ let blockUntil;
+ beforeEach(async () => {
+ const basePathProxyMock = { start: jest.fn() };
+
+ clusterManager = ClusterManager.create({}, {}, basePathProxyMock);
+
+ jest.spyOn(clusterManager.server, 'addListener');
+ jest.spyOn(clusterManager.server, 'removeListener');
+
+ [[{ blockUntil, shouldRedirectFromOldBasePath }]] = basePathProxyMock.start.mock.calls;
+ });
+
+ test('`shouldRedirectFromOldBasePath()` returns `false` for unknown paths.', () => {
+ expect(shouldRedirectFromOldBasePath('')).toBe(false);
+ expect(shouldRedirectFromOldBasePath('some-path/')).toBe(false);
+ expect(shouldRedirectFromOldBasePath('some-other-path')).toBe(false);
+ });
+
+ test('`shouldRedirectFromOldBasePath()` returns `true` for `app` and other known paths.', () => {
+ expect(shouldRedirectFromOldBasePath('app/')).toBe(true);
+ expect(shouldRedirectFromOldBasePath('login')).toBe(true);
+ expect(shouldRedirectFromOldBasePath('logout')).toBe(true);
+ expect(shouldRedirectFromOldBasePath('status')).toBe(true);
+ });
+
+ test('`blockUntil()` resolves immediately if worker has already crashed.', async () => {
+ clusterManager.server.crashed = true;
+
+ await expect(blockUntil()).resolves.not.toBeDefined();
+ expect(clusterManager.server.addListener).not.toHaveBeenCalled();
+ expect(clusterManager.server.removeListener).not.toHaveBeenCalled();
+ });
+
+ test('`blockUntil()` resolves immediately if worker is already listening.', async () => {
+ clusterManager.server.listening = true;
+
+ await expect(blockUntil()).resolves.not.toBeDefined();
+ expect(clusterManager.server.addListener).not.toHaveBeenCalled();
+ expect(clusterManager.server.removeListener).not.toHaveBeenCalled();
+ });
+
+ test('`blockUntil()` resolves when worker crashes.', async () => {
+ const blockUntilPromise = blockUntil();
+
+ expect(clusterManager.server.addListener).toHaveBeenCalledTimes(2);
+ expect(clusterManager.server.addListener).toHaveBeenCalledWith(
+ 'crashed',
+ expect.any(Function)
+ );
+
+ const [, [eventName, onCrashed]] = clusterManager.server.addListener.mock.calls;
+ // Check event name to make sure we call the right callback,
+ // in Jest 23 we could use `toHaveBeenNthCalledWith` instead.
+ expect(eventName).toBe('crashed');
+ expect(clusterManager.server.removeListener).not.toHaveBeenCalled();
+
+ onCrashed();
+ await expect(blockUntilPromise).resolves.not.toBeDefined();
+
+ expect(clusterManager.server.removeListener).toHaveBeenCalledTimes(2);
+ });
+
+ test('`blockUntil()` resolves when worker starts listening.', async () => {
+ const blockUntilPromise = blockUntil();
+
+ expect(clusterManager.server.addListener).toHaveBeenCalledTimes(2);
+ expect(clusterManager.server.addListener).toHaveBeenCalledWith(
+ 'listening',
+ expect.any(Function)
+ );
+
+ const [[eventName, onListening]] = clusterManager.server.addListener.mock.calls;
+ // Check event name to make sure we call the right callback,
+ // in Jest 23 we could use `toHaveBeenNthCalledWith` instead.
+ expect(eventName).toBe('listening');
+ expect(clusterManager.server.removeListener).not.toHaveBeenCalled();
+
+ onListening();
+ await expect(blockUntilPromise).resolves.not.toBeDefined();
+
+ expect(clusterManager.server.removeListener).toHaveBeenCalledTimes(2);
+ });
+ });
+ });
});
diff --git a/src/cli/cluster/configure_base_path_proxy.js b/src/cli/cluster/configure_base_path_proxy.js
deleted file mode 100644
index 477b10053d1e6..0000000000000
--- a/src/cli/cluster/configure_base_path_proxy.js
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { Server } from 'hapi';
-import { createBasePathProxy } from '../../core';
-import { setupLogging } from '../../server/logging';
-
-export async function configureBasePathProxy(config) {
- // New platform forwards all logs to the legacy platform so we need HapiJS server
- // here just for logging purposes and nothing else.
- const server = new Server();
- setupLogging(server, config);
-
- const basePathProxy = createBasePathProxy({ server, config });
-
- await basePathProxy.configure({
- shouldRedirectFromOldBasePath: path => {
- const isApp = path.startsWith('app/');
- const isKnownShortPath = ['login', 'logout', 'status'].includes(path);
-
- return isApp || isKnownShortPath;
- },
-
- blockUntil: () => {
- // Wait until `serverWorker either crashes or starts to listen.
- // The `serverWorker` property should be set by the ClusterManager
- // once it creates the worker.
- const serverWorker = basePathProxy.serverWorker;
- if (serverWorker.listening || serverWorker.crashed) {
- return Promise.resolve();
- }
-
- return new Promise(resolve => {
- const done = () => {
- serverWorker.removeListener('listening', done);
- serverWorker.removeListener('crashed', done);
-
- resolve();
- };
-
- serverWorker.on('listening', done);
- serverWorker.on('crashed', done);
- });
- },
- });
-
- return basePathProxy;
-}
diff --git a/src/cli/cluster/configure_base_path_proxy.test.js b/src/cli/cluster/configure_base_path_proxy.test.js
deleted file mode 100644
index 01cbaf0bcc900..0000000000000
--- a/src/cli/cluster/configure_base_path_proxy.test.js
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-jest.mock('../../core', () => ({
- createBasePathProxy: jest.fn(),
-}));
-
-jest.mock('../../server/logging', () => ({
- setupLogging: jest.fn(),
-}));
-
-import { Server } from 'hapi';
-import { createBasePathProxy as createBasePathProxyMock } from '../../core';
-import { setupLogging as setupLoggingMock } from '../../server/logging';
-import { configureBasePathProxy } from './configure_base_path_proxy';
-
-describe('configureBasePathProxy()', () => {
- it('returns `BasePathProxy` instance.', async () => {
- const basePathProxyMock = { configure: jest.fn() };
- createBasePathProxyMock.mockReturnValue(basePathProxyMock);
-
- const basePathProxy = await configureBasePathProxy({});
-
- expect(basePathProxy).toBe(basePathProxyMock);
- });
-
- it('correctly configures `BasePathProxy`.', async () => {
- const configMock = {};
- const basePathProxyMock = { configure: jest.fn() };
- createBasePathProxyMock.mockReturnValue(basePathProxyMock);
-
- await configureBasePathProxy(configMock);
-
- // Check that logging is configured with the right parameters.
- expect(setupLoggingMock).toHaveBeenCalledWith(
- expect.any(Server),
- configMock
- );
-
- const [[server]] = setupLoggingMock.mock.calls;
- expect(createBasePathProxyMock).toHaveBeenCalledWith({
- config: configMock,
- server,
- });
-
- expect(basePathProxyMock.configure).toHaveBeenCalledWith({
- shouldRedirectFromOldBasePath: expect.any(Function),
- blockUntil: expect.any(Function),
- });
- });
-
- describe('configured with the correct `shouldRedirectFromOldBasePath` and `blockUntil` functions.', async () => {
- let serverWorkerMock;
- let shouldRedirectFromOldBasePath;
- let blockUntil;
- beforeEach(async () => {
- serverWorkerMock = {
- listening: false,
- crashed: false,
- on: jest.fn(),
- removeListener: jest.fn(),
- };
-
- const basePathProxyMock = {
- configure: jest.fn(),
- serverWorker: serverWorkerMock,
- };
-
- createBasePathProxyMock.mockReturnValue(basePathProxyMock);
-
- await configureBasePathProxy({});
-
- [[{ blockUntil, shouldRedirectFromOldBasePath }]] = basePathProxyMock.configure.mock.calls;
- });
-
- it('`shouldRedirectFromOldBasePath()` returns `false` for unknown paths.', async () => {
- expect(shouldRedirectFromOldBasePath('')).toBe(false);
- expect(shouldRedirectFromOldBasePath('some-path/')).toBe(false);
- expect(shouldRedirectFromOldBasePath('some-other-path')).toBe(false);
- });
-
- it('`shouldRedirectFromOldBasePath()` returns `true` for `app` and other known paths.', async () => {
- expect(shouldRedirectFromOldBasePath('app/')).toBe(true);
- expect(shouldRedirectFromOldBasePath('login')).toBe(true);
- expect(shouldRedirectFromOldBasePath('logout')).toBe(true);
- expect(shouldRedirectFromOldBasePath('status')).toBe(true);
- });
-
- it('`blockUntil()` resolves immediately if worker has already crashed.', async () => {
- serverWorkerMock.crashed = true;
-
- await expect(blockUntil()).resolves.not.toBeDefined();
- expect(serverWorkerMock.on).not.toHaveBeenCalled();
- expect(serverWorkerMock.removeListener).not.toHaveBeenCalled();
- });
-
- it('`blockUntil()` resolves immediately if worker is already listening.', async () => {
- serverWorkerMock.listening = true;
-
- await expect(blockUntil()).resolves.not.toBeDefined();
- expect(serverWorkerMock.on).not.toHaveBeenCalled();
- expect(serverWorkerMock.removeListener).not.toHaveBeenCalled();
- });
-
- it('`blockUntil()` resolves when worker crashes.', async () => {
- const blockUntilPromise = blockUntil();
-
- expect(serverWorkerMock.on).toHaveBeenCalledTimes(2);
- expect(serverWorkerMock.on).toHaveBeenCalledWith(
- 'crashed',
- expect.any(Function)
- );
-
- const [, [eventName, onCrashed]] = serverWorkerMock.on.mock.calls;
- // Check event name to make sure we call the right callback,
- // in Jest 23 we could use `toHaveBeenNthCalledWith` instead.
- expect(eventName).toBe('crashed');
- expect(serverWorkerMock.removeListener).not.toHaveBeenCalled();
-
- onCrashed();
- await expect(blockUntilPromise).resolves.not.toBeDefined();
-
- expect(serverWorkerMock.removeListener).toHaveBeenCalledTimes(2);
- });
-
- it('`blockUntil()` resolves when worker starts listening.', async () => {
- const blockUntilPromise = blockUntil();
-
- expect(serverWorkerMock.on).toHaveBeenCalledTimes(2);
- expect(serverWorkerMock.on).toHaveBeenCalledWith(
- 'listening',
- expect.any(Function)
- );
-
- const [[eventName, onListening]] = serverWorkerMock.on.mock.calls;
- // Check event name to make sure we call the right callback,
- // in Jest 23 we could use `toHaveBeenNthCalledWith` instead.
- expect(eventName).toBe('listening');
- expect(serverWorkerMock.removeListener).not.toHaveBeenCalled();
-
- onListening();
- await expect(blockUntilPromise).resolves.not.toBeDefined();
-
- expect(serverWorkerMock.removeListener).toHaveBeenCalledTimes(2);
- });
- });
-});
diff --git a/src/cli/cluster/worker.test.js b/src/cli/cluster/worker.test.js
index c166956bcbf34..24687d640438a 100644
--- a/src/cli/cluster/worker.test.js
+++ b/src/cli/cluster/worker.test.js
@@ -17,26 +17,25 @@
* under the License.
*/
-import sinon from 'sinon';
+import { mockCluster } from './__mocks__/cluster';
+jest.mock('cluster', () => mockCluster());
+
import cluster from 'cluster';
-import { findIndex } from 'lodash';
-import MockClusterFork from './_mock_cluster_fork';
import Worker from './worker';
import Log from '../log';
const workersToShutdown = [];
function assertListenerAdded(emitter, event) {
- sinon.assert.calledWith(emitter.on, event);
+ expect(emitter.on).toHaveBeenCalledWith(event, expect.any(Function));
}
function assertListenerRemoved(emitter, event) {
- sinon.assert.calledWith(
- emitter.removeListener,
- event,
- emitter.on.args[findIndex(emitter.on.args, { 0: event })][1]
- );
+ const [, onEventListener] = emitter.on.mock.calls.find(([eventName]) => {
+ return eventName === event;
+ });
+ expect(emitter.removeListener).toHaveBeenCalledWith(event, onEventListener);
}
function setup(opts = {}) {
@@ -50,81 +49,82 @@ function setup(opts = {}) {
return worker;
}
-describe('CLI cluster manager', function () {
- const sandbox = sinon.createSandbox();
-
- beforeEach(function () {
- sandbox.stub(cluster, 'fork').callsFake(() => new MockClusterFork());
- });
-
- afterEach(async function () {
- sandbox.restore();
+describe('CLI cluster manager', () => {
+ afterEach(async () => {
+ while(workersToShutdown.length > 0) {
+ const worker = workersToShutdown.pop();
+ // If `fork` exists we should set `exitCode` to the non-zero value to
+ // prevent worker from auto restart.
+ if (worker.fork) {
+ worker.fork.exitCode = 1;
+ }
- for (const worker of workersToShutdown) {
await worker.shutdown();
}
+
+ cluster.fork.mockClear();
});
- describe('#onChange', function () {
- describe('opts.watch = true', function () {
- it('restarts the fork', function () {
+ describe('#onChange', () => {
+ describe('opts.watch = true', () => {
+ test('restarts the fork', () => {
const worker = setup({ watch: true });
- sinon.stub(worker, 'start');
+ jest.spyOn(worker, 'start').mockImplementation(() => {});
worker.onChange('/some/path');
expect(worker.changes).toEqual(['/some/path']);
- sinon.assert.calledOnce(worker.start);
+ expect(worker.start).toHaveBeenCalledTimes(1);
});
});
- describe('opts.watch = false', function () {
- it('does not restart the fork', function () {
+ describe('opts.watch = false', () => {
+ test('does not restart the fork', () => {
const worker = setup({ watch: false });
- sinon.stub(worker, 'start');
+ jest.spyOn(worker, 'start').mockImplementation(() => {});
worker.onChange('/some/path');
expect(worker.changes).toEqual([]);
- sinon.assert.notCalled(worker.start);
+ expect(worker.start).not.toHaveBeenCalled();
});
});
});
- describe('#shutdown', function () {
- describe('after starting()', function () {
- it('kills the worker and unbinds from message, online, and disconnect events', async function () {
+ describe('#shutdown', () => {
+ describe('after starting()', () => {
+ test('kills the worker and unbinds from message, online, and disconnect events', async () => {
const worker = setup();
await worker.start();
expect(worker).toHaveProperty('online', true);
const fork = worker.fork;
- sinon.assert.notCalled(fork.process.kill);
+ expect(fork.process.kill).not.toHaveBeenCalled();
assertListenerAdded(fork, 'message');
assertListenerAdded(fork, 'online');
assertListenerAdded(fork, 'disconnect');
worker.shutdown();
- sinon.assert.calledOnce(fork.process.kill);
+ expect(fork.process.kill).toHaveBeenCalledTimes(1);
assertListenerRemoved(fork, 'message');
assertListenerRemoved(fork, 'online');
assertListenerRemoved(fork, 'disconnect');
});
});
- describe('before being started', function () {
- it('does nothing', function () {
+ describe('before being started', () => {
+ test('does nothing', () => {
const worker = setup();
worker.shutdown();
});
});
});
- describe('#parseIncomingMessage()', function () {
- describe('on a started worker', function () {
- it(`is bound to fork's message event`, async function () {
+ describe('#parseIncomingMessage()', () => {
+ describe('on a started worker', () => {
+ test(`is bound to fork's message event`, async () => {
const worker = setup();
await worker.start();
- sinon.assert.calledWith(worker.fork.on, 'message');
+ expect(worker.fork.on).toHaveBeenCalledWith('message', expect.any(Function));
});
});
- describe('do after', function () {
- it('ignores non-array messages', function () {
+ describe('do after', () => {
+ test('ignores non-array messages', () => {
const worker = setup();
worker.parseIncomingMessage('some string thing');
worker.parseIncomingMessage(0);
@@ -134,39 +134,39 @@ describe('CLI cluster manager', function () {
worker.parseIncomingMessage(/weird/);
});
- it('calls #onMessage with message parts', function () {
+ test('calls #onMessage with message parts', () => {
const worker = setup();
- const stub = sinon.stub(worker, 'onMessage');
+ jest.spyOn(worker, 'onMessage').mockImplementation(() => {});
worker.parseIncomingMessage([10, 100, 1000, 10000]);
- sinon.assert.calledWith(stub, 10, 100, 1000, 10000);
+ expect(worker.onMessage).toHaveBeenCalledWith(10, 100, 1000, 10000);
});
});
});
- describe('#onMessage', function () {
- describe('when sent WORKER_BROADCAST message', function () {
- it('emits the data to be broadcasted', function () {
+ describe('#onMessage', () => {
+ describe('when sent WORKER_BROADCAST message', () => {
+ test('emits the data to be broadcasted', () => {
const worker = setup();
const data = {};
- const stub = sinon.stub(worker, 'emit');
+ jest.spyOn(worker, 'emit').mockImplementation(() => {});
worker.onMessage('WORKER_BROADCAST', data);
- sinon.assert.calledWithExactly(stub, 'broadcast', data);
+ expect(worker.emit).toHaveBeenCalledWith('broadcast', data);
});
});
- describe('when sent WORKER_LISTENING message', function () {
- it('sets the listening flag and emits the listening event', function () {
+ describe('when sent WORKER_LISTENING message', () => {
+ test('sets the listening flag and emits the listening event', () => {
const worker = setup();
- const stub = sinon.stub(worker, 'emit');
+ jest.spyOn(worker, 'emit').mockImplementation(() => {});
expect(worker).toHaveProperty('listening', false);
worker.onMessage('WORKER_LISTENING');
expect(worker).toHaveProperty('listening', true);
- sinon.assert.calledWithExactly(stub, 'listening');
+ expect(worker.emit).toHaveBeenCalledWith('listening');
});
});
- describe('when passed an unknown message', function () {
- it('does nothing', function () {
+ describe('when passed an unknown message', () => {
+ test('does nothing', () => {
const worker = setup();
worker.onMessage('asdlfkajsdfahsdfiohuasdofihsdoif');
worker.onMessage({});
@@ -175,46 +175,46 @@ describe('CLI cluster manager', function () {
});
});
- describe('#start', function () {
- describe('when not started', function () {
- // TODO This test is flaky, see https://github.com/elastic/kibana/issues/15888
- it.skip('creates a fork and waits for it to come online', async function () {
+ describe('#start', () => {
+ describe('when not started', () => {
+ test('creates a fork and waits for it to come online', async () => {
const worker = setup();
- sinon.spy(worker, 'on');
+ jest.spyOn(worker, 'on');
await worker.start();
- sinon.assert.calledOnce(cluster.fork);
- sinon.assert.calledWith(worker.on, 'fork:online');
+ expect(cluster.fork).toHaveBeenCalledTimes(1);
+ expect(worker.on).toHaveBeenCalledWith('fork:online', expect.any(Function));
});
- // TODO This test is flaky, see https://github.com/elastic/kibana/issues/15888
- it.skip('listens for cluster and process "exit" events', async function () {
+ test('listens for cluster and process "exit" events', async () => {
const worker = setup();
- sinon.spy(process, 'on');
- sinon.spy(cluster, 'on');
+ jest.spyOn(process, 'on');
+ jest.spyOn(cluster, 'on');
await worker.start();
- sinon.assert.calledOnce(cluster.on);
- sinon.assert.calledWith(cluster.on, 'exit');
- sinon.assert.calledOnce(process.on);
- sinon.assert.calledWith(process.on, 'exit');
+ expect(cluster.on).toHaveBeenCalledTimes(1);
+ expect(cluster.on).toHaveBeenCalledWith('exit', expect.any(Function));
+ expect(process.on).toHaveBeenCalledTimes(1);
+ expect(process.on).toHaveBeenCalledWith('exit', expect.any(Function));
});
});
- describe('when already started', function () {
- it('calls shutdown and waits for the graceful shutdown to cause a restart', async function () {
+ describe('when already started', () => {
+ test('calls shutdown and waits for the graceful shutdown to cause a restart', async () => {
const worker = setup();
await worker.start();
- sinon.spy(worker, 'shutdown');
- sinon.spy(worker, 'on');
+
+ jest.spyOn(worker, 'shutdown');
+ jest.spyOn(worker, 'on');
worker.start();
- sinon.assert.calledOnce(worker.shutdown);
- sinon.assert.calledWith(worker.on, 'online');
+
+ expect(worker.shutdown).toHaveBeenCalledTimes(1);
+ expect(worker.on).toHaveBeenCalledWith('online', expect.any(Function));
});
});
});
diff --git a/src/cli/color.js b/src/cli/color.js
index b678376ef7c24..a02fb551c4181 100644
--- a/src/cli/color.js
+++ b/src/cli/color.js
@@ -17,9 +17,8 @@
* under the License.
*/
-import _ from 'lodash';
import chalk from 'chalk';
-export const green = _.flow(chalk.black, chalk.bgGreen);
-export const red = _.flow(chalk.white, chalk.bgRed);
-export const yellow = _.flow(chalk.black, chalk.bgYellow);
+export const green = chalk.black.bgGreen;
+export const red = chalk.white.bgRed;
+export const yellow = chalk.black.bgYellow;
diff --git a/src/cli/serve/integration_tests/__snapshots__/invalid_config.test.js.snap b/src/cli/serve/integration_tests/__snapshots__/invalid_config.test.js.snap
index 0e702ed6123bd..47b98f740af58 100644
--- a/src/cli/serve/integration_tests/__snapshots__/invalid_config.test.js.snap
+++ b/src/cli/serve/integration_tests/__snapshots__/invalid_config.test.js.snap
@@ -4,12 +4,15 @@ exports[`cli invalid config support exits with statusCode 64 and logs a single l
Array [
Object {
"@timestamp": "## @timestamp ##",
+ "error": "## Error with stack trace ##",
+ "level": "fatal",
"message": "\\"unknown.key\\", \\"other.unknown.key\\", \\"other.third\\", \\"some.flat.key\\", and \\"some.array\\" settings were not applied. Check for spelling errors and ensure that expected plugins are installed.",
"pid": "## PID ##",
"tags": Array [
"fatal",
+ "root",
],
- "type": "log",
+ "type": "error",
},
]
`;
diff --git a/src/cli/serve/integration_tests/invalid_config.test.js b/src/cli/serve/integration_tests/invalid_config.test.js
index 335fb1dbcaf9f..495bfbeaa939e 100644
--- a/src/cli/serve/integration_tests/invalid_config.test.js
+++ b/src/cli/serve/integration_tests/invalid_config.test.js
@@ -39,7 +39,8 @@ describe('cli invalid config support', function () {
.map(obj => ({
...obj,
pid: '## PID ##',
- '@timestamp': '## @timestamp ##'
+ '@timestamp': '## @timestamp ##',
+ error: '## Error with stack trace ##',
}));
expect(error).toBe(undefined);
diff --git a/src/cli/serve/serve.js b/src/cli/serve/serve.js
index 08495566d845e..2820ac6a64ea4 100644
--- a/src/cli/serve/serve.js
+++ b/src/cli/serve/serve.js
@@ -19,20 +19,15 @@
import _ from 'lodash';
import { statSync, lstatSync, realpathSync } from 'fs';
-import { isWorker } from 'cluster';
import { resolve } from 'path';
import { fromRoot } from '../../utils';
import { getConfig } from '../../server/path';
-import { Config } from '../../server/config/config';
-import { getConfigFromFiles } from '../../core/server/config';
+import { bootstrap } from '../../core/server';
import { readKeystore } from './read_keystore';
-import { transformDeprecations } from '../../server/config/transform_deprecations';
import { DEV_SSL_CERT_PATH, DEV_SSL_KEY_PATH } from '../dev_ssl';
-const { startRepl } = canRequire('../repl') ? require('../repl') : { };
-
function canRequire(path) {
try {
require.resolve(path);
@@ -60,6 +55,9 @@ function isSymlinkTo(link, dest) {
const CLUSTER_MANAGER_PATH = resolve(__dirname, '../cluster/cluster_manager');
const CAN_CLUSTER = canRequire(CLUSTER_MANAGER_PATH);
+const REPL_PATH = resolve(__dirname, '../repl');
+const CAN_REPL = canRequire(REPL_PATH);
+
// xpack is installed in both dev and the distributable, it's optional if
// install is a link to the source, not an actual install
const XPACK_INSTALLED_DIR = resolve(__dirname, '../../../node_modules/x-pack');
@@ -79,12 +77,11 @@ const configPathCollector = pathCollector();
const pluginDirCollector = pathCollector();
const pluginPathCollector = pathCollector();
-function readServerSettings(opts, extraCliOptions) {
- const settings = getConfigFromFiles([].concat(opts.config || []));
- const set = _.partial(_.set, settings);
- const get = _.partial(_.get, settings);
- const has = _.partial(_.has, settings);
- const merge = _.partial(_.merge, settings);
+function applyConfigOverrides(rawConfig, opts, extraCliOptions) {
+ const set = _.partial(_.set, rawConfig);
+ const get = _.partial(_.get, rawConfig);
+ const has = _.partial(_.has, rawConfig);
+ const merge = _.partial(_.merge, rawConfig);
if (opts.dev) {
set('env', 'development');
@@ -133,7 +130,7 @@ function readServerSettings(opts, extraCliOptions) {
merge(extraCliOptions);
merge(readKeystore(get('path.data')));
- return settings;
+ return rawConfig;
}
export default function (program) {
@@ -175,7 +172,7 @@ export default function (program) {
)
.option('--plugins ', 'an alias for --plugin-dir', pluginDirCollector);
- if (!!startRepl) {
+ if (CAN_REPL) {
command.option('--repl', 'Run the server with a REPL prompt and access to the server object');
}
@@ -205,81 +202,25 @@ export default function (program) {
}
}
- const getCurrentSettings = () => readServerSettings(opts, this.getUnknownOptions());
- const settings = getCurrentSettings();
-
- if (CAN_CLUSTER && opts.dev && !isWorker) {
- // stop processing the action and handoff to cluster manager
- const ClusterManager = require(CLUSTER_MANAGER_PATH);
- await ClusterManager.create(opts, settings);
- return;
- }
-
- let kbnServer = {};
- const KbnServer = require('../../server/kbn_server');
- try {
- kbnServer = new KbnServer(settings);
- if (shouldStartRepl(opts)) {
- startRepl(kbnServer);
- }
- await kbnServer.ready();
- } catch (error) {
- const { server } = kbnServer;
-
- switch (error.code) {
- case 'EADDRINUSE':
- logFatal(`Port ${error.port} is already in use. Another instance of Kibana may be running!`, server);
- break;
-
- case 'InvalidConfig':
- logFatal(error.message, server);
- break;
-
- default:
- logFatal(error, server);
- break;
- }
-
- kbnServer.close();
- const exitCode = error.processExitCode == null ? 1 : error.processExitCode;
- // eslint-disable-next-line no-process-exit
- process.exit(exitCode);
- }
-
- process.on('SIGHUP', async function reloadConfig() {
- const settings = transformDeprecations(getCurrentSettings());
- const config = new Config(kbnServer.config.getSchema(), settings);
-
- kbnServer.server.log(['info', 'config'], 'Reloading logging configuration due to SIGHUP.');
- await kbnServer.applyLoggingConfiguration(config);
- kbnServer.server.log(['info', 'config'], 'Reloaded logging configuration due to SIGHUP.');
-
- // If new platform config subscription is active, let's notify it with the updated config.
- if (kbnServer.newPlatform) {
- kbnServer.newPlatform.updateConfig(config.get());
- }
+ const unknownOptions = this.getUnknownOptions();
+ await bootstrap({
+ configs: [].concat(opts.config || []),
+ cliArgs: {
+ dev: !!opts.dev,
+ envName: unknownOptions.env ? unknownOptions.env.name : undefined,
+ quiet: !!opts.quiet,
+ silent: !!opts.silent,
+ watch: !!opts.watch,
+ repl: !!opts.repl,
+ basePath: !!opts.basePath,
+ },
+ features: {
+ isClusterModeSupported: CAN_CLUSTER,
+ isOssModeSupported: XPACK_OPTIONAL,
+ isXPackInstalled: XPACK_INSTALLED,
+ isReplModeSupported: CAN_REPL,
+ },
+ applyConfigOverrides: rawConfig => applyConfigOverrides(rawConfig, opts, unknownOptions),
});
-
- return kbnServer;
});
}
-
-function shouldStartRepl(opts) {
- if (opts.repl && !startRepl) {
- throw new Error('Kibana REPL mode can only be run in development mode.');
- }
-
- // The kbnWorkerType check is necessary to prevent the repl
- // from being started multiple times in different processes.
- // We only want one REPL.
- return opts.repl && process.env.kbnWorkerType === 'server';
-}
-
-function logFatal(message, server) {
- if (server) {
- server.log(['fatal'], message);
- }
-
- // It's possible for the Hapi logger to not be setup
- console.error('FATAL', message);
-}
diff --git a/src/core/README.md b/src/core/README.md
index c3b056f981726..196946ed9e4a3 100644
--- a/src/core/README.md
+++ b/src/core/README.md
@@ -5,26 +5,17 @@ Core is a set of systems (frontend, backend etc.) that Kibana and its plugins ar
## Integration with the "legacy" Kibana
Most of the existing core functionality is still spread over "legacy" Kibana and it will take some time to upgrade it.
-Kibana is still started using existing "legacy" CLI and bootstraps `core` only when needed. At the moment `core` manages
-HTTP connections, handles TLS configuration and base path proxy. All requests to Kibana server will hit HTTP server
-exposed by the `core` first and it will decide whether request can be solely handled by the new platform or request should
-be proxied to the "legacy" Kibana. This setup allows `core` to gradually introduce any "pre-route" processing
-logic, expose new routes or replace old ones handled by the "legacy" Kibana currently.
+Kibana is started using existing "legacy" CLI that bootstraps `core` which in turn creates the "legacy" Kibana server.
+At the moment `core` manages HTTP connections, handles TLS configuration and base path proxy. All requests to Kibana server
+will hit HTTP server exposed by the `core` first and it will decide whether request can be solely handled by the new
+platform or request should be proxied to the "legacy" Kibana. This setup allows `core` to gradually introduce any "pre-route"
+processing logic, expose new routes or replace old ones handled by the "legacy" Kibana currently.
-Once config has been loaded and validated by the "legacy" Kibana it's passed to the `core` where some of its parts will
-be additionally validated so that we can make config validation stricter with the new config validation system. Even though
-the new validation system provided by the `core` is also based on Joi internally it is complemented with custom rules
-tailored to our needs (e.g. `byteSize`, `duration` etc.). That means that config values that are accepted by the "legacy"
-Kibana may be rejected by the `core`.
-
-One can also define new configuration keys under `__newPlatform` if these keys are supposed to be used by the `core` only
-and should not be validated by the "legacy" Kibana, e.g.
-
-```yaml
-__newPlatform:
- plugins:
- scanDirs: ['./example_plugins']
-```
+Once config has been loaded and some of its parts were validated by the `core` it's passed to the "legacy" Kibana where
+it will be additionally validated so that we can make config validation stricter with the new config validation system.
+Even though the new validation system provided by the `core` is also based on Joi internally it is complemented with custom
+rules tailored to our needs (e.g. `byteSize`, `duration` etc.). That means that config values that were previously accepted
+by the "legacy" Kibana may be rejected by the `core` now.
Even though `core` has its own logging system it doesn't output log records directly (e.g. to file or terminal), but instead
forward them to the "legacy" Kibana so that they look the same as the rest of the log records throughout Kibana.
diff --git a/src/core/index.ts b/src/core/index.ts
deleted file mode 100644
index 326d08e0ec43f..0000000000000
--- a/src/core/index.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-export { injectIntoKbnServer, createBasePathProxy } from './server/legacy_compat';
diff --git a/src/core/server/__snapshots__/index.test.ts.snap b/src/core/server/__snapshots__/index.test.ts.snap
new file mode 100644
index 0000000000000..8c3022a07d074
--- /dev/null
+++ b/src/core/server/__snapshots__/index.test.ts.snap
@@ -0,0 +1,21 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`does not fail on "start" if there are unused paths detected: unused paths logs 1`] = `
+Object {
+ "debug": Array [
+ Array [
+ "starting server",
+ ],
+ ],
+ "error": Array [],
+ "fatal": Array [],
+ "info": Array [],
+ "log": Array [],
+ "trace": Array [
+ Array [
+ "some config paths are not handled by the core: [\\"some.path\\",\\"another.path\\"]",
+ ],
+ ],
+ "warn": Array [],
+}
+`;
diff --git a/src/core/server/bootstrap.ts b/src/core/server/bootstrap.ts
new file mode 100644
index 0000000000000..69b1d751010c9
--- /dev/null
+++ b/src/core/server/bootstrap.ts
@@ -0,0 +1,113 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import chalk from 'chalk';
+import { isMaster } from 'cluster';
+import { CliArgs, Env, RawConfigService } from './config';
+import { LegacyObjectToConfigAdapter } from './legacy_compat';
+import { Root } from './root';
+
+interface KibanaFeatures {
+ // Indicates whether we can run Kibana in a so called cluster mode in which
+ // Kibana is run as a "worker" process together with optimizer "worker" process
+ // that are orchestrated by the "master" process (dev mode only feature).
+ isClusterModeSupported: boolean;
+
+ // Indicates whether we can run Kibana without X-Pack plugin pack even if it's
+ // installed (dev mode only feature).
+ isOssModeSupported: boolean;
+
+ // Indicates whether we can run Kibana in REPL mode (dev mode only feature).
+ isReplModeSupported: boolean;
+
+ // Indicates whether X-Pack plugin pack is installed and available.
+ isXPackInstalled: boolean;
+}
+
+interface BootstrapArgs {
+ configs: string[];
+ cliArgs: CliArgs;
+ applyConfigOverrides: (config: Record) => Record;
+ features: KibanaFeatures;
+}
+
+export async function bootstrap({
+ configs,
+ cliArgs,
+ applyConfigOverrides,
+ features,
+}: BootstrapArgs) {
+ if (cliArgs.repl && !features.isReplModeSupported) {
+ onRootShutdown('Kibana REPL mode can only be run in development mode.');
+ }
+
+ const env = Env.createDefault({
+ configs,
+ cliArgs,
+ isDevClusterMaster: isMaster && cliArgs.dev && features.isClusterModeSupported,
+ });
+
+ const rawConfigService = new RawConfigService(
+ env.configs,
+ rawConfig => new LegacyObjectToConfigAdapter(applyConfigOverrides(rawConfig))
+ );
+
+ rawConfigService.loadConfig();
+
+ const root = new Root(rawConfigService.getConfig$(), env, onRootShutdown);
+
+ function shutdown(reason?: Error) {
+ rawConfigService.stop();
+ return root.shutdown(reason);
+ }
+
+ try {
+ await root.start();
+ } catch (err) {
+ await shutdown(err);
+ }
+
+ process.on('SIGHUP', () => {
+ const cliLogger = root.logger.get('cli');
+ cliLogger.info('Reloading logging configuration due to SIGHUP.', { tags: ['config'] });
+
+ try {
+ rawConfigService.reloadConfig();
+ } catch (err) {
+ return shutdown(err);
+ }
+
+ cliLogger.info('Reloaded logging configuration due to SIGHUP.', { tags: ['config'] });
+ });
+
+ process.on('SIGINT', () => shutdown());
+ process.on('SIGTERM', () => shutdown());
+}
+
+function onRootShutdown(reason?: any) {
+ if (reason !== undefined) {
+ // There is a chance that logger wasn't configured properly and error that
+ // that forced root to shut down could go unnoticed. To prevent this we always
+ // mirror such fatal errors in standard output with `console.error`.
+ // tslint:disable no-console
+ console.error(`\n${chalk.white.bgRed(' FATAL ')} ${reason}\n`);
+ }
+
+ process.exit(reason === undefined ? 0 : (reason as any).processExitCode || 1);
+}
diff --git a/src/core/server/config/__tests__/__mocks__/env.ts b/src/core/server/config/__tests__/__mocks__/env.ts
index fe33fd32f4648..e90c33f19ee49 100644
--- a/src/core/server/config/__tests__/__mocks__/env.ts
+++ b/src/core/server/config/__tests__/__mocks__/env.ts
@@ -21,11 +21,20 @@
import { EnvOptions } from '../../env';
-export function getEnvOptions(options: Partial = {}): EnvOptions {
+type DeepPartial = {
+ [P in keyof T]?: T[P] extends Array ? Array> : DeepPartial
+};
+
+export function getEnvOptions(options: DeepPartial = {}): EnvOptions {
return {
configs: options.configs || [],
cliArgs: {
dev: true,
+ quiet: false,
+ silent: false,
+ watch: false,
+ repl: false,
+ basePath: false,
...(options.cliArgs || {}),
},
isDevClusterMaster:
diff --git a/src/core/server/config/__tests__/__snapshots__/env.test.ts.snap b/src/core/server/config/__tests__/__snapshots__/env.test.ts.snap
index db2917da5406f..5931b0697d79c 100644
--- a/src/core/server/config/__tests__/__snapshots__/env.test.ts.snap
+++ b/src/core/server/config/__tests__/__snapshots__/env.test.ts.snap
@@ -1,12 +1,85 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
+exports[`correctly creates default environment if \`--env.name\` is supplied.: dev env properties 1`] = `
+Env {
+ "binDir": "/test/cwd/bin",
+ "cliArgs": Object {
+ "basePath": false,
+ "dev": true,
+ "envName": "development",
+ "quiet": false,
+ "repl": false,
+ "silent": false,
+ "watch": false,
+ },
+ "configDir": "/test/cwd/config",
+ "configs": Array [
+ "/some/other/path/some-kibana.yml",
+ ],
+ "corePluginsDir": "/test/cwd/core_plugins",
+ "homeDir": "/test/cwd",
+ "isDevClusterMaster": false,
+ "logDir": "/test/cwd/log",
+ "mode": Object {
+ "dev": true,
+ "name": "development",
+ "prod": false,
+ },
+ "packageInfo": Object {
+ "branch": "feature-v1",
+ "buildNum": 9007199254740991,
+ "buildSha": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
+ "version": "v1",
+ },
+ "staticFilesDir": "/test/cwd/ui",
+}
+`;
+
+exports[`correctly creates default environment if \`--env.name\` is supplied.: prod env properties 1`] = `
+Env {
+ "binDir": "/test/cwd/bin",
+ "cliArgs": Object {
+ "basePath": false,
+ "dev": false,
+ "envName": "production",
+ "quiet": false,
+ "repl": false,
+ "silent": false,
+ "watch": false,
+ },
+ "configDir": "/test/cwd/config",
+ "configs": Array [
+ "/some/other/path/some-kibana.yml",
+ ],
+ "corePluginsDir": "/test/cwd/core_plugins",
+ "homeDir": "/test/cwd",
+ "isDevClusterMaster": false,
+ "logDir": "/test/cwd/log",
+ "mode": Object {
+ "dev": false,
+ "name": "production",
+ "prod": true,
+ },
+ "packageInfo": Object {
+ "branch": "feature-v1",
+ "buildNum": 9007199254740991,
+ "buildSha": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
+ "version": "v1",
+ },
+ "staticFilesDir": "/test/cwd/ui",
+}
+`;
+
exports[`correctly creates default environment in dev mode.: env properties 1`] = `
Env {
"binDir": "/test/cwd/bin",
"cliArgs": Object {
+ "basePath": false,
"dev": true,
- "someArg": 1,
- "someOtherArg": "2",
+ "quiet": false,
+ "repl": false,
+ "silent": false,
+ "watch": false,
},
"configDir": "/test/cwd/config",
"configs": Array [
@@ -15,12 +88,6 @@ Env {
"corePluginsDir": "/test/cwd/core_plugins",
"homeDir": "/test/cwd",
"isDevClusterMaster": true,
- "legacy": EventEmitter {
- "_events": Object {},
- "_eventsCount": 0,
- "_maxListeners": undefined,
- "domain": null,
- },
"logDir": "/test/cwd/log",
"mode": Object {
"dev": true,
@@ -41,9 +108,12 @@ exports[`correctly creates default environment in prod distributable mode.: env
Env {
"binDir": "/test/cwd/bin",
"cliArgs": Object {
+ "basePath": false,
"dev": false,
- "someArg": 1,
- "someOtherArg": "2",
+ "quiet": false,
+ "repl": false,
+ "silent": false,
+ "watch": false,
},
"configDir": "/test/cwd/config",
"configs": Array [
@@ -52,12 +122,6 @@ Env {
"corePluginsDir": "/test/cwd/core_plugins",
"homeDir": "/test/cwd",
"isDevClusterMaster": false,
- "legacy": EventEmitter {
- "_events": Object {},
- "_eventsCount": 0,
- "_maxListeners": undefined,
- "domain": null,
- },
"logDir": "/test/cwd/log",
"mode": Object {
"dev": false,
@@ -78,9 +142,12 @@ exports[`correctly creates default environment in prod non-distributable mode.:
Env {
"binDir": "/test/cwd/bin",
"cliArgs": Object {
+ "basePath": false,
"dev": false,
- "someArg": 1,
- "someOtherArg": "2",
+ "quiet": false,
+ "repl": false,
+ "silent": false,
+ "watch": false,
},
"configDir": "/test/cwd/config",
"configs": Array [
@@ -89,12 +156,6 @@ Env {
"corePluginsDir": "/test/cwd/core_plugins",
"homeDir": "/test/cwd",
"isDevClusterMaster": false,
- "legacy": EventEmitter {
- "_events": Object {},
- "_eventsCount": 0,
- "_maxListeners": undefined,
- "domain": null,
- },
"logDir": "/test/cwd/log",
"mode": Object {
"dev": false,
@@ -115,9 +176,12 @@ exports[`correctly creates environment with constructor.: env properties 1`] = `
Env {
"binDir": "/some/home/dir/bin",
"cliArgs": Object {
+ "basePath": false,
"dev": false,
- "someArg": 1,
- "someOtherArg": "2",
+ "quiet": false,
+ "repl": false,
+ "silent": false,
+ "watch": false,
},
"configDir": "/some/home/dir/config",
"configs": Array [
@@ -126,12 +190,6 @@ Env {
"corePluginsDir": "/some/home/dir/core_plugins",
"homeDir": "/some/home/dir",
"isDevClusterMaster": false,
- "legacy": EventEmitter {
- "_events": Object {},
- "_eventsCount": 0,
- "_maxListeners": undefined,
- "domain": null,
- },
"logDir": "/some/home/dir/log",
"mode": Object {
"dev": false,
diff --git a/src/core/server/config/__tests__/apply_argv.test.ts b/src/core/server/config/__tests__/apply_argv.test.ts
index 7908dd2468021..b3d2f3749271a 100644
--- a/src/core/server/config/__tests__/apply_argv.test.ts
+++ b/src/core/server/config/__tests__/apply_argv.test.ts
@@ -22,7 +22,7 @@ import { Config, ObjectToConfigAdapter } from '..';
/**
* Overrides some config values with ones from argv.
*
- * @param config `RawConfig` instance to update config values for.
+ * @param config `Config` instance to update config values for.
* @param argv Argv object with key/value pairs.
*/
export function overrideConfigWithArgv(config: Config, argv: { [key: string]: any }) {
diff --git a/src/core/server/config/__tests__/env.test.ts b/src/core/server/config/__tests__/env.test.ts
index 26163c82c8464..381273a1f8ffb 100644
--- a/src/core/server/config/__tests__/env.test.ts
+++ b/src/core/server/config/__tests__/env.test.ts
@@ -33,6 +33,7 @@ const mockPackage = new Proxy({ raw: {} as any }, { get: (obj, prop) => obj.raw[
jest.mock('../../../../utils/package_json', () => ({ pkg: mockPackage }));
import { Env } from '../env';
+import { getEnvOptions } from './__mocks__/env';
test('correctly creates default environment in dev mode.', () => {
mockPackage.raw = {
@@ -40,11 +41,12 @@ test('correctly creates default environment in dev mode.', () => {
version: 'some-version',
};
- const defaultEnv = Env.createDefault({
- cliArgs: { dev: true, someArg: 1, someOtherArg: '2' },
- configs: ['/test/cwd/config/kibana.yml'],
- isDevClusterMaster: true,
- });
+ const defaultEnv = Env.createDefault(
+ getEnvOptions({
+ configs: ['/test/cwd/config/kibana.yml'],
+ isDevClusterMaster: true,
+ })
+ );
expect(defaultEnv).toMatchSnapshot('env properties');
});
@@ -60,11 +62,12 @@ test('correctly creates default environment in prod distributable mode.', () =>
},
};
- const defaultEnv = Env.createDefault({
- cliArgs: { dev: false, someArg: 1, someOtherArg: '2' },
- configs: ['/some/other/path/some-kibana.yml'],
- isDevClusterMaster: false,
- });
+ const defaultEnv = Env.createDefault(
+ getEnvOptions({
+ cliArgs: { dev: false },
+ configs: ['/some/other/path/some-kibana.yml'],
+ })
+ );
expect(defaultEnv).toMatchSnapshot('env properties');
});
@@ -80,15 +83,45 @@ test('correctly creates default environment in prod non-distributable mode.', ()
},
};
- const defaultEnv = Env.createDefault({
- cliArgs: { dev: false, someArg: 1, someOtherArg: '2' },
- configs: ['/some/other/path/some-kibana.yml'],
- isDevClusterMaster: false,
- });
+ const defaultEnv = Env.createDefault(
+ getEnvOptions({
+ cliArgs: { dev: false },
+ configs: ['/some/other/path/some-kibana.yml'],
+ })
+ );
expect(defaultEnv).toMatchSnapshot('env properties');
});
+test('correctly creates default environment if `--env.name` is supplied.', () => {
+ mockPackage.raw = {
+ branch: 'feature-v1',
+ version: 'v1',
+ build: {
+ distributable: false,
+ number: 100,
+ sha: 'feature-v1-build-sha',
+ },
+ };
+
+ const defaultDevEnv = Env.createDefault(
+ getEnvOptions({
+ cliArgs: { envName: 'development' },
+ configs: ['/some/other/path/some-kibana.yml'],
+ })
+ );
+
+ const defaultProdEnv = Env.createDefault(
+ getEnvOptions({
+ cliArgs: { dev: false, envName: 'production' },
+ configs: ['/some/other/path/some-kibana.yml'],
+ })
+ );
+
+ expect(defaultDevEnv).toMatchSnapshot('dev env properties');
+ expect(defaultProdEnv).toMatchSnapshot('prod env properties');
+});
+
test('correctly creates environment with constructor.', () => {
mockPackage.raw = {
branch: 'feature-v1',
@@ -100,11 +133,13 @@ test('correctly creates environment with constructor.', () => {
},
};
- const env = new Env('/some/home/dir', {
- cliArgs: { dev: false, someArg: 1, someOtherArg: '2' },
- configs: ['/some/other/path/some-kibana.yml'],
- isDevClusterMaster: false,
- });
+ const env = new Env(
+ '/some/home/dir',
+ getEnvOptions({
+ cliArgs: { dev: false },
+ configs: ['/some/other/path/some-kibana.yml'],
+ })
+ );
expect(env).toMatchSnapshot('env properties');
});
diff --git a/src/core/server/config/env.ts b/src/core/server/config/env.ts
index 56d6c1ae94a0c..f7b497403a28a 100644
--- a/src/core/server/config/env.ts
+++ b/src/core/server/config/env.ts
@@ -17,7 +17,6 @@
* under the License.
*/
-import { EventEmitter } from 'events';
import { resolve } from 'path';
import process from 'process';
@@ -38,10 +37,20 @@ interface EnvironmentMode {
export interface EnvOptions {
configs: string[];
- cliArgs: Record;
+ cliArgs: CliArgs;
isDevClusterMaster: boolean;
}
+export interface CliArgs {
+ dev: boolean;
+ envName?: string;
+ quiet: boolean;
+ silent: boolean;
+ watch: boolean;
+ repl: boolean;
+ basePath: boolean;
+}
+
export class Env {
/**
* @internal
@@ -66,15 +75,10 @@ export class Env {
*/
public readonly mode: Readonly;
- /**
- * @internal
- */
- public readonly legacy: EventEmitter;
-
/**
* Arguments provided through command line.
*/
- public readonly cliArgs: Readonly>;
+ public readonly cliArgs: Readonly;
/**
* Paths to the configuration files.
@@ -100,10 +104,11 @@ export class Env {
this.configs = Object.freeze(options.configs);
this.isDevClusterMaster = options.isDevClusterMaster;
+ const isDevMode = this.cliArgs.dev || this.cliArgs.envName === 'development';
this.mode = Object.freeze({
- dev: this.cliArgs.dev,
- name: this.cliArgs.dev ? 'development' : 'production',
- prod: !this.cliArgs.dev,
+ dev: isDevMode,
+ name: isDevMode ? 'development' : 'production',
+ prod: !isDevMode,
});
const isKibanaDistributable = pkg.build && pkg.build.distributable === true;
@@ -113,7 +118,5 @@ export class Env {
buildSha: isKibanaDistributable ? pkg.build.sha : 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
version: pkg.version,
});
-
- this.legacy = new EventEmitter();
}
}
diff --git a/src/core/server/config/index.ts b/src/core/server/config/index.ts
index a5f535d12db1e..c63a8d6aa7c04 100644
--- a/src/core/server/config/index.ts
+++ b/src/core/server/config/index.ts
@@ -22,6 +22,6 @@ export { RawConfigService } from './raw_config_service';
export { Config, ConfigPath } from './config';
/** @internal */
export { ObjectToConfigAdapter } from './object_to_config_adapter';
-export { Env } from './env';
+export { Env, CliArgs } from './env';
export { ConfigWithSchema } from './config_with_schema';
export { getConfigFromFiles } from './read_config';
diff --git a/src/core/server/config/schema/byte_size_value/index.ts b/src/core/server/config/schema/byte_size_value/index.ts
index 61ba879a5c926..fb0105503a149 100644
--- a/src/core/server/config/schema/byte_size_value/index.ts
+++ b/src/core/server/config/schema/byte_size_value/index.ts
@@ -36,8 +36,7 @@ export class ByteSizeValue {
const match = /([1-9][0-9]*)(b|kb|mb|gb)/.exec(text);
if (!match) {
throw new Error(
- `could not parse byte size value [${text}]. value must start with a ` +
- `number and end with bytes size unit, e.g. 10kb, 23mb, 3gb, 239493b`
+ `could not parse byte size value [${text}]. Value must be a safe positive integer.`
);
}
diff --git a/src/core/server/http/__tests__/__snapshots__/http_config.test.ts.snap b/src/core/server/http/__tests__/__snapshots__/http_config.test.ts.snap
index 6c38ae7ecf5d6..d7fe10b1c417b 100644
--- a/src/core/server/http/__tests__/__snapshots__/http_config.test.ts.snap
+++ b/src/core/server/http/__tests__/__snapshots__/http_config.test.ts.snap
@@ -11,6 +11,7 @@ Object {
exports[`has defaults for config 1`] = `
Object {
+ "autoListen": true,
"cors": false,
"host": "localhost",
"maxPayload": ByteSizeValue {
diff --git a/src/core/server/http/__tests__/__snapshots__/http_server.test.ts.snap b/src/core/server/http/__tests__/__snapshots__/http_server.test.ts.snap
index 3060d7b468960..8e868e803602f 100644
--- a/src/core/server/http/__tests__/__snapshots__/http_server.test.ts.snap
+++ b/src/core/server/http/__tests__/__snapshots__/http_server.test.ts.snap
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`broadcasts server and connection options to the legacy "channel" 1`] = `
+exports[`returns server and connection options on start 1`] = `
Object {
"host": "127.0.0.1",
"port": 12345,
diff --git a/src/core/server/http/__tests__/http_server.test.ts b/src/core/server/http/__tests__/http_server.test.ts
index 7f49d153163a9..42f93a13e1c80 100644
--- a/src/core/server/http/__tests__/http_server.test.ts
+++ b/src/core/server/http/__tests__/http_server.test.ts
@@ -17,7 +17,7 @@
* under the License.
*/
-import { getEnvOptions } from '../../config/__tests__/__mocks__/env';
+import { Server } from 'http';
jest.mock('fs', () => ({
readFileSync: jest.fn(),
@@ -26,7 +26,6 @@ jest.mock('fs', () => ({
import Chance from 'chance';
import supertest from 'supertest';
-import { Env } from '../../config';
import { ByteSizeValue } from '../../config/schema';
import { logger } from '../../logging/__mocks__';
import { HttpConfig } from '../http_config';
@@ -35,14 +34,9 @@ import { Router } from '../router';
const chance = new Chance();
-let env: Env;
let server: HttpServer;
let config: HttpConfig;
-function getServerListener(httpServer: HttpServer) {
- return (httpServer as any).server.listener;
-}
-
beforeEach(() => {
config = {
host: '127.0.0.1',
@@ -51,8 +45,7 @@ beforeEach(() => {
ssl: {},
} as HttpConfig;
- env = new Env('/kibana', getEnvOptions());
- server = new HttpServer(logger.get(), env);
+ server = new HttpServer(logger.get());
});
afterEach(async () => {
@@ -77,9 +70,9 @@ test('200 OK with body', async () => {
server.registerRouter(router);
- await server.start(config);
+ const { server: innerServer } = await server.start(config);
- await supertest(getServerListener(server))
+ await supertest(innerServer.listener)
.get('/foo/')
.expect(200)
.then(res => {
@@ -96,9 +89,9 @@ test('202 Accepted with body', async () => {
server.registerRouter(router);
- await server.start(config);
+ const { server: innerServer } = await server.start(config);
- await supertest(getServerListener(server))
+ await supertest(innerServer.listener)
.get('/foo/')
.expect(202)
.then(res => {
@@ -115,9 +108,9 @@ test('204 No content', async () => {
server.registerRouter(router);
- await server.start(config);
+ const { server: innerServer } = await server.start(config);
- await supertest(getServerListener(server))
+ await supertest(innerServer.listener)
.get('/foo/')
.expect(204)
.then(res => {
@@ -136,9 +129,9 @@ test('400 Bad request with error', async () => {
server.registerRouter(router);
- await server.start(config);
+ const { server: innerServer } = await server.start(config);
- await supertest(getServerListener(server))
+ await supertest(innerServer.listener)
.get('/foo/')
.expect(400)
.then(res => {
@@ -165,9 +158,9 @@ test('valid params', async () => {
server.registerRouter(router);
- await server.start(config);
+ const { server: innerServer } = await server.start(config);
- await supertest(getServerListener(server))
+ await supertest(innerServer.listener)
.get('/foo/some-string')
.expect(200)
.then(res => {
@@ -194,9 +187,9 @@ test('invalid params', async () => {
server.registerRouter(router);
- await server.start(config);
+ const { server: innerServer } = await server.start(config);
- await supertest(getServerListener(server))
+ await supertest(innerServer.listener)
.get('/foo/some-string')
.expect(400)
.then(res => {
@@ -226,9 +219,9 @@ test('valid query', async () => {
server.registerRouter(router);
- await server.start(config);
+ const { server: innerServer } = await server.start(config);
- await supertest(getServerListener(server))
+ await supertest(innerServer.listener)
.get('/foo/?bar=test&quux=123')
.expect(200)
.then(res => {
@@ -255,9 +248,9 @@ test('invalid query', async () => {
server.registerRouter(router);
- await server.start(config);
+ const { server: innerServer } = await server.start(config);
- await supertest(getServerListener(server))
+ await supertest(innerServer.listener)
.get('/foo/?bar=test')
.expect(400)
.then(res => {
@@ -287,9 +280,9 @@ test('valid body', async () => {
server.registerRouter(router);
- await server.start(config);
+ const { server: innerServer } = await server.start(config);
- await supertest(getServerListener(server))
+ await supertest(innerServer.listener)
.post('/foo/')
.send({
bar: 'test',
@@ -320,9 +313,9 @@ test('invalid body', async () => {
server.registerRouter(router);
- await server.start(config);
+ const { server: innerServer } = await server.start(config);
- await supertest(getServerListener(server))
+ await supertest(innerServer.listener)
.post('/foo/')
.send({ bar: 'test' })
.expect(400)
@@ -352,9 +345,9 @@ test('handles putting', async () => {
server.registerRouter(router);
- await server.start(config);
+ const { server: innerServer } = await server.start(config);
- await supertest(getServerListener(server))
+ await supertest(innerServer.listener)
.put('/foo/')
.send({ key: 'new value' })
.expect(200)
@@ -382,9 +375,9 @@ test('handles deleting', async () => {
server.registerRouter(router);
- await server.start(config);
+ const { server: innerServer } = await server.start(config);
- await supertest(getServerListener(server))
+ await supertest(innerServer.listener)
.delete('/foo/3')
.expect(200)
.then(res => {
@@ -407,9 +400,9 @@ test('filtered headers', async () => {
server.registerRouter(router);
- await server.start(config);
+ const { server: innerServer } = await server.start(config);
- await supertest(getServerListener(server))
+ await supertest(innerServer.listener)
.get('/foo/?bar=quux')
.set('x-kibana-foo', 'bar')
.set('x-kibana-bar', 'quux');
@@ -422,6 +415,7 @@ test('filtered headers', async () => {
describe('with `basepath: /bar` and `rewriteBasePath: false`', () => {
let configWithBasePath: HttpConfig;
+ let innerServerListener: Server;
beforeEach(async () => {
configWithBasePath = {
@@ -438,29 +432,30 @@ describe('with `basepath: /bar` and `rewriteBasePath: false`', () => {
server.registerRouter(router);
- await server.start(configWithBasePath);
+ const { server: innerServer } = await server.start(configWithBasePath);
+ innerServerListener = innerServer.listener;
});
test('/bar => 404', async () => {
- await supertest(getServerListener(server))
+ await supertest(innerServerListener)
.get('/bar')
.expect(404);
});
test('/bar/ => 404', async () => {
- await supertest(getServerListener(server))
+ await supertest(innerServerListener)
.get('/bar/')
.expect(404);
});
test('/bar/foo => 404', async () => {
- await supertest(getServerListener(server))
+ await supertest(innerServerListener)
.get('/bar/foo')
.expect(404);
});
test('/ => /', async () => {
- await supertest(getServerListener(server))
+ await supertest(innerServerListener)
.get('/')
.expect(200)
.then(res => {
@@ -469,7 +464,7 @@ describe('with `basepath: /bar` and `rewriteBasePath: false`', () => {
});
test('/foo => /foo', async () => {
- await supertest(getServerListener(server))
+ await supertest(innerServerListener)
.get('/foo')
.expect(200)
.then(res => {
@@ -480,6 +475,7 @@ describe('with `basepath: /bar` and `rewriteBasePath: false`', () => {
describe('with `basepath: /bar` and `rewriteBasePath: true`', () => {
let configWithBasePath: HttpConfig;
+ let innerServerListener: Server;
beforeEach(async () => {
configWithBasePath = {
@@ -496,11 +492,12 @@ describe('with `basepath: /bar` and `rewriteBasePath: true`', () => {
server.registerRouter(router);
- await server.start(configWithBasePath);
+ const { server: innerServer } = await server.start(configWithBasePath);
+ innerServerListener = innerServer.listener;
});
test('/bar => /', async () => {
- await supertest(getServerListener(server))
+ await supertest(innerServerListener)
.get('/bar')
.expect(200)
.then(res => {
@@ -509,7 +506,7 @@ describe('with `basepath: /bar` and `rewriteBasePath: true`', () => {
});
test('/bar/ => /', async () => {
- await supertest(getServerListener(server))
+ await supertest(innerServerListener)
.get('/bar/')
.expect(200)
.then(res => {
@@ -518,7 +515,7 @@ describe('with `basepath: /bar` and `rewriteBasePath: true`', () => {
});
test('/bar/foo => /foo', async () => {
- await supertest(getServerListener(server))
+ await supertest(innerServerListener)
.get('/bar/foo')
.expect(200)
.then(res => {
@@ -527,13 +524,13 @@ describe('with `basepath: /bar` and `rewriteBasePath: true`', () => {
});
test('/ => 404', async () => {
- await supertest(getServerListener(server))
+ await supertest(innerServerListener)
.get('/')
.expect(404);
});
test('/foo => 404', async () => {
- await supertest(getServerListener(server))
+ await supertest(innerServerListener)
.get('/foo')
.expect(404);
});
@@ -564,21 +561,13 @@ describe('with defined `redirectHttpFromPort`', () => {
});
});
-test('broadcasts server and connection options to the legacy "channel"', async () => {
- const onConnectionListener = jest.fn();
- env.legacy.on('connection', onConnectionListener);
-
- expect(onConnectionListener).not.toHaveBeenCalled();
-
- await server.start({
+test('returns server and connection options on start', async () => {
+ const { server: innerServer, options } = await server.start({
...config,
port: 12345,
});
- expect(onConnectionListener).toHaveBeenCalledTimes(1);
-
- const [[{ options, server: rawServer }]] = onConnectionListener.mock.calls;
- expect(rawServer).toBeDefined();
- expect(rawServer).toBe((server as any).server);
+ expect(innerServer).toBeDefined();
+ expect(innerServer).toBe((server as any).server);
expect(options).toMatchSnapshot();
});
diff --git a/src/core/server/http/__tests__/http_service.test.ts b/src/core/server/http/__tests__/http_service.test.ts
index 0cacad8817468..1c6d259848117 100644
--- a/src/core/server/http/__tests__/http_service.test.ts
+++ b/src/core/server/http/__tests__/http_service.test.ts
@@ -17,8 +17,6 @@
* under the License.
*/
-import { getEnvOptions } from '../../config/__tests__/__mocks__/env';
-
const mockHttpServer = jest.fn();
jest.mock('../http_server', () => ({
@@ -27,8 +25,6 @@ jest.mock('../http_server', () => ({
import { noop } from 'lodash';
import { BehaviorSubject } from 'rxjs';
-
-import { Env } from '../../config';
import { logger } from '../../logging/__mocks__';
import { HttpConfig } from '../http_config';
import { HttpService } from '../http_service';
@@ -55,11 +51,7 @@ test('creates and starts http server', async () => {
};
mockHttpServer.mockImplementation(() => httpServer);
- const service = new HttpService(
- config$.asObservable(),
- logger,
- new Env('/kibana', getEnvOptions())
- );
+ const service = new HttpService(config$.asObservable(), logger);
expect(mockHttpServer.mock.instances.length).toBe(1);
expect(httpServer.start).not.toHaveBeenCalled();
@@ -81,11 +73,7 @@ test('logs error if already started', async () => {
};
mockHttpServer.mockImplementation(() => httpServer);
- const service = new HttpService(
- config$.asObservable(),
- logger,
- new Env('/kibana', getEnvOptions())
- );
+ const service = new HttpService(config$.asObservable(), logger);
await service.start();
@@ -104,11 +92,7 @@ test('stops http server', async () => {
};
mockHttpServer.mockImplementation(() => httpServer);
- const service = new HttpService(
- config$.asObservable(),
- logger,
- new Env('/kibana', getEnvOptions())
- );
+ const service = new HttpService(config$.asObservable(), logger);
await service.start();
@@ -132,11 +116,7 @@ test('register route handler', () => {
};
mockHttpServer.mockImplementation(() => httpServer);
- const service = new HttpService(
- config$.asObservable(),
- logger,
- new Env('/kibana', getEnvOptions())
- );
+ const service = new HttpService(config$.asObservable(), logger);
const router = new Router('/foo');
service.registerRouter(router);
@@ -159,11 +139,7 @@ test('throws if registering route handler after http server is started', () => {
};
mockHttpServer.mockImplementation(() => httpServer);
- const service = new HttpService(
- config$.asObservable(),
- logger,
- new Env('/kibana', getEnvOptions())
- );
+ const service = new HttpService(config$.asObservable(), logger);
const router = new Router('/foo');
service.registerRouter(router);
@@ -171,3 +147,20 @@ test('throws if registering route handler after http server is started', () => {
expect(httpServer.registerRouter).toHaveBeenCalledTimes(0);
expect(logger.mockCollect()).toMatchSnapshot();
});
+
+test('returns http server contract on start', async () => {
+ const httpServerContract = {
+ server: {},
+ options: { someOption: true },
+ };
+
+ mockHttpServer.mockImplementation(() => ({
+ isListening: () => false,
+ start: jest.fn().mockReturnValue(httpServerContract),
+ stop: noop,
+ }));
+
+ const service = new HttpService(new BehaviorSubject({ ssl: {} } as HttpConfig), logger);
+
+ expect(await service.start()).toBe(httpServerContract);
+});
diff --git a/src/core/server/http/base_path_proxy_server.ts b/src/core/server/http/base_path_proxy_server.ts
index f4a9b59b77b10..b0c2144d7189a 100644
--- a/src/core/server/http/base_path_proxy_server.ts
+++ b/src/core/server/http/base_path_proxy_server.ts
@@ -29,8 +29,6 @@ import { createServer, getServerOptions } from './http_tools';
const alphabet = 'abcdefghijklmnopqrztuvwxyz'.split('');
export interface BasePathProxyServerOptions {
- httpConfig: HttpConfig;
- devConfig: DevConfig;
shouldRedirectFromOldBasePath: (path: string) => boolean;
blockUntil: () => Promise;
}
@@ -40,34 +38,38 @@ export class BasePathProxyServer {
private httpsAgent?: HttpsAgent;
get basePath() {
- return this.options.httpConfig.basePath;
+ return this.httpConfig.basePath;
}
get targetPort() {
- return this.options.devConfig.basePathProxyTargetPort;
+ return this.devConfig.basePathProxyTargetPort;
}
- constructor(private readonly log: Logger, private readonly options: BasePathProxyServerOptions) {
+ constructor(
+ private readonly log: Logger,
+ private readonly httpConfig: HttpConfig,
+ private readonly devConfig: DevConfig
+ ) {
const ONE_GIGABYTE = 1024 * 1024 * 1024;
- options.httpConfig.maxPayload = new ByteSizeValue(ONE_GIGABYTE);
+ httpConfig.maxPayload = new ByteSizeValue(ONE_GIGABYTE);
- if (!options.httpConfig.basePath) {
- options.httpConfig.basePath = `/${sample(alphabet, 3).join('')}`;
+ if (!httpConfig.basePath) {
+ httpConfig.basePath = `/${sample(alphabet, 3).join('')}`;
}
}
- public async start() {
- const { httpConfig } = this.options;
+ public async start(options: Readonly) {
+ this.log.debug('starting basepath proxy server');
- const options = getServerOptions(httpConfig);
- this.server = createServer(options);
+ const serverOptions = getServerOptions(this.httpConfig);
+ this.server = createServer(serverOptions);
// Register hapi plugin that adds proxying functionality. It can be configured
// through the route configuration object (see { handler: { proxy: ... } }).
await this.server.register({ plugin: require('h2o2-latest') });
- if (httpConfig.ssl.enabled) {
- const tlsOptions = options.tls as TlsOptions;
+ if (this.httpConfig.ssl.enabled) {
+ const tlsOptions = serverOptions.tls as TlsOptions;
this.httpsAgent = new HttpsAgent({
ca: tlsOptions.ca,
cert: tlsOptions.cert,
@@ -77,40 +79,42 @@ export class BasePathProxyServer {
});
}
- this.setupRoutes();
+ this.setupRoutes(options);
+
+ await this.server.start();
this.log.info(
- `starting basepath proxy server at ${this.server.info.uri}${httpConfig.basePath}`
+ `basepath proxy server running at ${this.server.info.uri}${this.httpConfig.basePath}`
);
-
- await this.server.start();
}
public async stop() {
- this.log.info('stopping basepath proxy server');
-
- if (this.server !== undefined) {
- await this.server.stop();
- this.server = undefined;
+ if (this.server === undefined) {
+ return;
}
+ this.log.debug('stopping basepath proxy server');
+ await this.server.stop();
+ this.server = undefined;
+
if (this.httpsAgent !== undefined) {
this.httpsAgent.destroy();
this.httpsAgent = undefined;
}
}
- private setupRoutes() {
+ private setupRoutes({
+ blockUntil,
+ shouldRedirectFromOldBasePath,
+ }: Readonly) {
if (this.server === undefined) {
throw new Error(`Routes cannot be set up since server is not initialized.`);
}
- const { httpConfig, devConfig, blockUntil, shouldRedirectFromOldBasePath } = this.options;
-
// Always redirect from root URL to the URL with basepath.
this.server.route({
handler: (request, responseToolkit) => {
- return responseToolkit.redirect(httpConfig.basePath);
+ return responseToolkit.redirect(this.httpConfig.basePath);
},
method: 'GET',
path: '/',
@@ -122,7 +126,7 @@ export class BasePathProxyServer {
agent: this.httpsAgent,
host: this.server.info.host,
passThrough: true,
- port: devConfig.basePathProxyTargetPort,
+ port: this.devConfig.basePathProxyTargetPort,
protocol: this.server.info.protocol,
xforward: true,
},
@@ -138,7 +142,7 @@ export class BasePathProxyServer {
},
],
},
- path: `${httpConfig.basePath}/{kbnPath*}`,
+ path: `${this.httpConfig.basePath}/{kbnPath*}`,
});
// It may happen that basepath has changed, but user still uses the old one,
@@ -152,7 +156,7 @@ export class BasePathProxyServer {
const isBasepathLike = oldBasePath.length === 3;
return isGet && isBasepathLike && shouldRedirectFromOldBasePath(kbnPath)
- ? responseToolkit.redirect(`${httpConfig.basePath}/${kbnPath}`)
+ ? responseToolkit.redirect(`${this.httpConfig.basePath}/${kbnPath}`)
: responseToolkit.response('Not Found').code(404);
},
method: '*',
diff --git a/src/core/server/http/http_config.ts b/src/core/server/http/http_config.ts
index 5d1504008027b..67578ecc1559c 100644
--- a/src/core/server/http/http_config.ts
+++ b/src/core/server/http/http_config.ts
@@ -28,6 +28,7 @@ const match = (regex: RegExp, errorMsg: string) => (str: string) =>
const createHttpSchema = schema.object(
{
+ autoListen: schema.boolean({ defaultValue: true }),
basePath: schema.maybe(
schema.string({
validate: match(validBasePathRegex, "must start with a slash, don't end with one"),
@@ -90,6 +91,7 @@ export class HttpConfig {
*/
public static schema = createHttpSchema;
+ public autoListen: boolean;
public host: string;
public port: number;
public cors: boolean | { origin: string[] };
@@ -103,6 +105,7 @@ export class HttpConfig {
* @internal
*/
constructor(config: HttpConfigType, env: Env) {
+ this.autoListen = config.autoListen;
this.host = config.host;
this.port = config.port;
this.cors = config.cors;
diff --git a/src/core/server/http/http_server.ts b/src/core/server/http/http_server.ts
index 21cde147b8ea2..c828ff4df5408 100644
--- a/src/core/server/http/http_server.ts
+++ b/src/core/server/http/http_server.ts
@@ -17,20 +17,24 @@
* under the License.
*/
-import { Server } from 'hapi-latest';
+import { Server, ServerOptions } from 'hapi-latest';
import { modifyUrl } from '../../utils';
-import { Env } from '../config';
import { Logger } from '../logging';
import { HttpConfig } from './http_config';
import { createServer, getServerOptions } from './http_tools';
import { Router } from './router';
+export interface HttpServerInfo {
+ server: Server;
+ options: ServerOptions;
+}
+
export class HttpServer {
private server?: Server;
private registeredRouters: Set = new Set();
- constructor(private readonly log: Logger, private readonly env: Env) {}
+ constructor(private readonly log: Logger) {}
public isListening() {
return this.server !== undefined && this.server.listener.listening;
@@ -62,21 +66,18 @@ export class HttpServer {
}
}
- // Notify legacy compatibility layer about HTTP(S) connection providing server
- // instance with connection options so that we can properly bridge core and
- // the "legacy" Kibana internally.
- this.env.legacy.emit('connection', {
- options: serverOptions,
- server: this.server,
- });
-
await this.server.start();
- this.log.info(
- `Server running at ${this.server.info.uri}${config.rewriteBasePath ? config.basePath : ''}`,
- // The "legacy" Kibana will output log records with `listening` tag even if `quiet` logging mode is enabled.
- { tags: ['listening'] }
+ this.log.debug(
+ `http server running at ${this.server.info.uri}${
+ config.rewriteBasePath ? config.basePath : ''
+ }`
);
+
+ // Return server instance with the connection options so that we can properly
+ // bridge core and the "legacy" Kibana internally. Once this bridge isn't
+ // needed anymore we shouldn't return anything from this method.
+ return { server: this.server, options: serverOptions };
}
public async stop() {
diff --git a/src/core/server/http/http_service.ts b/src/core/server/http/http_service.ts
index 3caae18e857b3..6972dfffbb1dd 100644
--- a/src/core/server/http/http_service.ts
+++ b/src/core/server/http/http_service.ts
@@ -21,24 +21,23 @@ import { Observable, Subscription } from 'rxjs';
import { first } from 'rxjs/operators';
import { CoreService } from '../../types/core_service';
-import { Env } from '../config';
import { Logger, LoggerFactory } from '../logging';
import { HttpConfig } from './http_config';
-import { HttpServer } from './http_server';
+import { HttpServer, HttpServerInfo } from './http_server';
import { HttpsRedirectServer } from './https_redirect_server';
import { Router } from './router';
-export class HttpService implements CoreService {
+export class HttpService implements CoreService {
private readonly httpServer: HttpServer;
private readonly httpsRedirectServer: HttpsRedirectServer;
private configSubscription?: Subscription;
private readonly log: Logger;
- constructor(private readonly config$: Observable, logger: LoggerFactory, env: Env) {
+ constructor(private readonly config$: Observable, logger: LoggerFactory) {
this.log = logger.get('http');
- this.httpServer = new HttpServer(logger.get('http', 'server'), env);
+ this.httpServer = new HttpServer(logger.get('http', 'server'));
this.httpsRedirectServer = new HttpsRedirectServer(logger.get('http', 'redirect', 'server'));
}
@@ -61,7 +60,7 @@ export class HttpService implements CoreService {
await this.httpsRedirectServer.start(config);
}
- await this.httpServer.start(config);
+ return await this.httpServer.start(config);
}
public async stop() {
diff --git a/src/core/server/http/index.ts b/src/core/server/http/index.ts
index e636fcd801eb5..3fd3715083416 100644
--- a/src/core/server/http/index.ts
+++ b/src/core/server/http/index.ts
@@ -19,20 +19,26 @@
import { Observable } from 'rxjs';
-import { Env } from '../config';
import { LoggerFactory } from '../logging';
import { HttpConfig } from './http_config';
import { HttpService } from './http_service';
+import { Router } from './router';
export { Router, KibanaRequest } from './router';
export { HttpService };
+export { HttpServerInfo } from './http_server';
+export { BasePathProxyServer } from './base_path_proxy_server';
export { HttpConfig };
export class HttpModule {
public readonly service: HttpService;
- constructor(readonly config$: Observable, logger: LoggerFactory, env: Env) {
- this.service = new HttpService(this.config$, logger, env);
+ constructor(readonly config$: Observable, logger: LoggerFactory) {
+ this.service = new HttpService(this.config$, logger);
+
+ const router = new Router('/core');
+ router.get({ path: '/', validate: false }, async (req, res) => res.ok({ version: '0.0.1' }));
+ this.service.registerRouter(router);
}
}
diff --git a/src/core/server/index.test.ts b/src/core/server/index.test.ts
new file mode 100644
index 0000000000000..8a83d8d500b81
--- /dev/null
+++ b/src/core/server/index.test.ts
@@ -0,0 +1,121 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+const mockHttpService = { start: jest.fn(), stop: jest.fn(), registerRouter: jest.fn() };
+jest.mock('./http/http_service', () => ({
+ HttpService: jest.fn(() => mockHttpService),
+}));
+
+const mockLegacyService = { start: jest.fn(), stop: jest.fn() };
+jest.mock('./legacy_compat/legacy_service', () => ({
+ LegacyService: jest.fn(() => mockLegacyService),
+}));
+
+import { BehaviorSubject } from 'rxjs';
+import { Server } from '.';
+import { Env } from './config';
+import { getEnvOptions } from './config/__tests__/__mocks__/env';
+import { logger } from './logging/__mocks__';
+
+const mockConfigService = { atPath: jest.fn(), getUnusedPaths: jest.fn().mockReturnValue([]) };
+const env = new Env('.', getEnvOptions());
+
+beforeEach(() => {
+ mockConfigService.atPath.mockReturnValue(new BehaviorSubject({ autoListen: true }));
+});
+
+afterEach(() => {
+ logger.mockClear();
+ mockConfigService.atPath.mockReset();
+ mockHttpService.start.mockReset();
+ mockHttpService.stop.mockReset();
+ mockLegacyService.start.mockReset();
+ mockLegacyService.stop.mockReset();
+});
+
+test('starts services on "start"', async () => {
+ const mockHttpServiceStartContract = { something: true };
+ mockHttpService.start.mockReturnValue(Promise.resolve(mockHttpServiceStartContract));
+
+ const server = new Server(mockConfigService as any, logger, env);
+
+ expect(mockHttpService.start).not.toHaveBeenCalled();
+ expect(mockLegacyService.start).not.toHaveBeenCalled();
+
+ await server.start();
+
+ expect(mockHttpService.start).toHaveBeenCalledTimes(1);
+ expect(mockLegacyService.start).toHaveBeenCalledTimes(1);
+ expect(mockLegacyService.start).toHaveBeenCalledWith(mockHttpServiceStartContract);
+});
+
+test('does not fail on "start" if there are unused paths detected', async () => {
+ mockConfigService.getUnusedPaths.mockReturnValue(['some.path', 'another.path']);
+
+ const server = new Server(mockConfigService as any, logger, env);
+ await expect(server.start()).resolves.toBeUndefined();
+ expect(logger.mockCollect()).toMatchSnapshot('unused paths logs');
+});
+
+test('does not start http service is `autoListen:false`', async () => {
+ mockConfigService.atPath.mockReturnValue(new BehaviorSubject({ autoListen: false }));
+
+ const server = new Server(mockConfigService as any, logger, env);
+
+ expect(mockLegacyService.start).not.toHaveBeenCalled();
+
+ await server.start();
+
+ expect(mockHttpService.start).not.toHaveBeenCalled();
+ expect(mockLegacyService.start).toHaveBeenCalledTimes(1);
+ expect(mockLegacyService.start).toHaveBeenCalledWith(undefined);
+});
+
+test('does not start http service if process is dev cluster master', async () => {
+ const server = new Server(
+ mockConfigService as any,
+ logger,
+ new Env('.', getEnvOptions({ isDevClusterMaster: true }))
+ );
+
+ expect(mockLegacyService.start).not.toHaveBeenCalled();
+
+ await server.start();
+
+ expect(mockHttpService.start).not.toHaveBeenCalled();
+ expect(mockLegacyService.start).toHaveBeenCalledTimes(1);
+ expect(mockLegacyService.start).toHaveBeenCalledWith(undefined);
+});
+
+test('stops services on "stop"', async () => {
+ const mockHttpServiceStartContract = { something: true };
+ mockHttpService.start.mockReturnValue(Promise.resolve(mockHttpServiceStartContract));
+
+ const server = new Server(mockConfigService as any, logger, env);
+
+ await server.start();
+
+ expect(mockHttpService.stop).not.toHaveBeenCalled();
+ expect(mockLegacyService.stop).not.toHaveBeenCalled();
+
+ await server.stop();
+
+ expect(mockHttpService.stop).toHaveBeenCalledTimes(1);
+ expect(mockLegacyService.stop).toHaveBeenCalledTimes(1);
+});
diff --git a/src/core/server/index.ts b/src/core/server/index.ts
index 7d55670239f5e..ac645b2280041 100644
--- a/src/core/server/index.ts
+++ b/src/core/server/index.ts
@@ -17,29 +17,44 @@
* under the License.
*/
+export { bootstrap } from './bootstrap';
+
+import { first } from 'rxjs/operators';
import { ConfigService, Env } from './config';
-import { HttpConfig, HttpModule, Router } from './http';
+import { HttpConfig, HttpModule, HttpServerInfo } from './http';
+import { LegacyCompatModule } from './legacy_compat';
import { Logger, LoggerFactory } from './logging';
export class Server {
private readonly http: HttpModule;
+ private readonly legacy: LegacyCompatModule;
private readonly log: Logger;
- constructor(private readonly configService: ConfigService, logger: LoggerFactory, env: Env) {
+ constructor(
+ private readonly configService: ConfigService,
+ logger: LoggerFactory,
+ private readonly env: Env
+ ) {
this.log = logger.get('server');
- const httpConfig$ = configService.atPath('server', HttpConfig);
- this.http = new HttpModule(httpConfig$, logger, env);
+ this.http = new HttpModule(configService.atPath('server', HttpConfig), logger);
+ this.legacy = new LegacyCompatModule(configService, logger, env);
}
public async start() {
- this.log.debug('starting server :tada:');
+ this.log.debug('starting server');
- const router = new Router('/core');
- router.get({ path: '/', validate: false }, async (req, res) => res.ok({ version: '0.0.1' }));
- this.http.service.registerRouter(router);
+ // We shouldn't start http service in two cases:
+ // 1. If `server.autoListen` is explicitly set to `false`.
+ // 2. When the process is run as dev cluster master in which case cluster manager
+ // will fork a dedicated process where http service will be started instead.
+ let httpServerInfo: HttpServerInfo | undefined;
+ const httpConfig = await this.http.config$.pipe(first()).toPromise();
+ if (!this.env.isDevClusterMaster && httpConfig.autoListen) {
+ httpServerInfo = await this.http.service.start();
+ }
- await this.http.service.start();
+ await this.legacy.service.start(httpServerInfo);
const unhandledConfigPaths = await this.configService.getUnusedPaths();
if (unhandledConfigPaths.length > 0) {
@@ -54,6 +69,7 @@ export class Server {
public async stop() {
this.log.debug('stopping server');
+ await this.legacy.service.stop();
await this.http.service.stop();
}
}
diff --git a/src/core/server/legacy_compat/__tests__/__snapshots__/legacy_platform_proxifier.test.ts.snap b/src/core/server/legacy_compat/__tests__/__snapshots__/legacy_platform_proxifier.test.ts.snap
deleted file mode 100644
index eb58ca8cbc5fd..0000000000000
--- a/src/core/server/legacy_compat/__tests__/__snapshots__/legacy_platform_proxifier.test.ts.snap
+++ /dev/null
@@ -1,21 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`correctly binds to the server.: proxy route options 1`] = `
-Array [
- Array [
- Object {
- "handler": [Function],
- "method": "*",
- "options": Object {
- "payload": Object {
- "maxBytes": 9007199254740991,
- "output": "stream",
- "parse": false,
- "timeout": false,
- },
- },
- "path": "/{p*}",
- },
- ],
-]
-`;
diff --git a/src/core/server/legacy_compat/__tests__/__snapshots__/legacy_service.test.ts.snap b/src/core/server/legacy_compat/__tests__/__snapshots__/legacy_service.test.ts.snap
new file mode 100644
index 0000000000000..4e10f1449d1cf
--- /dev/null
+++ b/src/core/server/legacy_compat/__tests__/__snapshots__/legacy_service.test.ts.snap
@@ -0,0 +1,137 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`once LegacyService is started in \`devClusterMaster\` mode creates ClusterManager with base path proxy.: cluster manager with base path proxy 1`] = `
+Array [
+ Array [
+ Object {
+ "basePath": true,
+ "dev": true,
+ "quiet": true,
+ "repl": false,
+ "silent": false,
+ "watch": false,
+ },
+ Object {
+ "server": Object {
+ "autoListen": true,
+ },
+ },
+ BasePathProxyServer {
+ "devConfig": Object {
+ "basePathProxyTargetPort": 100500,
+ },
+ "httpConfig": Object {
+ "basePath": "/abc",
+ "maxPayload": ByteSizeValue {
+ "valueInBytes": 1073741824,
+ },
+ },
+ "log": Object {
+ "debug": [MockFunction] {
+ "calls": Array [
+ Array [
+ "starting legacy service",
+ ],
+ ],
+ },
+ "error": [MockFunction],
+ "fatal": [MockFunction],
+ "info": [MockFunction],
+ "log": [MockFunction],
+ "trace": [MockFunction],
+ "warn": [MockFunction],
+ },
+ },
+ ],
+]
+`;
+
+exports[`once LegacyService is started in \`devClusterMaster\` mode creates ClusterManager without base path proxy.: cluster manager without base path proxy 1`] = `
+Array [
+ Array [
+ Object {
+ "basePath": false,
+ "dev": true,
+ "quiet": false,
+ "repl": false,
+ "silent": true,
+ "watch": false,
+ },
+ Object {
+ "server": Object {
+ "autoListen": true,
+ },
+ },
+ undefined,
+ ],
+]
+`;
+
+exports[`once LegacyService is started with connection info creates legacy kbnServer and closes it if \`listen\` fails. 1`] = `"something failed"`;
+
+exports[`once LegacyService is started with connection info proxy route responds with \`503\` if \`kbnServer\` is not ready yet.: 503 response 1`] = `
+Object {
+ "body": Array [
+ Array [
+ "Kibana server is not ready yet",
+ ],
+ ],
+ "code": Array [
+ Array [
+ 503,
+ ],
+ ],
+ "header": Array [
+ Array [
+ "Retry-After",
+ "30",
+ ],
+ ],
+}
+`;
+
+exports[`once LegacyService is started with connection info reconfigures logging configuration if new config is received.: applyLoggingConfiguration params 1`] = `
+Array [
+ Array [
+ Object {
+ "logging": Object {
+ "verbose": true,
+ },
+ },
+ ],
+]
+`;
+
+exports[`once LegacyService is started with connection info register proxy route.: proxy route options 1`] = `
+Array [
+ Array [
+ Object {
+ "handler": [Function],
+ "method": "*",
+ "options": Object {
+ "payload": Object {
+ "maxBytes": 9007199254740991,
+ "output": "stream",
+ "parse": false,
+ "timeout": false,
+ },
+ },
+ "path": "/{p*}",
+ },
+ ],
+]
+`;
+
+exports[`once LegacyService is started with connection info throws if fails to retrieve initial config. 1`] = `"something failed"`;
+
+exports[`once LegacyService is started without connection info reconfigures logging configuration if new config is received.: applyLoggingConfiguration params 1`] = `
+Array [
+ Array [
+ Object {
+ "logging": Object {
+ "verbose": true,
+ },
+ },
+ ],
+]
+`;
diff --git a/src/core/server/legacy_compat/__tests__/legacy_platform_proxifier.test.ts b/src/core/server/legacy_compat/__tests__/legacy_platform_proxy.test.ts
similarity index 51%
rename from src/core/server/legacy_compat/__tests__/legacy_platform_proxifier.test.ts
rename to src/core/server/legacy_compat/__tests__/legacy_platform_proxy.test.ts
index 27db835a0ecf3..8330bbb8d74db 100644
--- a/src/core/server/legacy_compat/__tests__/legacy_platform_proxifier.test.ts
+++ b/src/core/server/legacy_compat/__tests__/legacy_platform_proxy.test.ts
@@ -17,17 +17,12 @@
* under the License.
*/
-import { Server as HapiServer } from 'hapi-latest';
import { Server } from 'net';
-import { LegacyPlatformProxifier } from '..';
-import { Env } from '../../config';
-import { getEnvOptions } from '../../config/__tests__/__mocks__/env';
-import { logger } from '../../logging/__mocks__';
+
+import { LegacyPlatformProxy } from '../legacy_platform_proxy';
let server: jest.Mocked;
-let mockHapiServer: jest.Mocked;
-let root: any;
-let proxifier: LegacyPlatformProxifier;
+let proxy: LegacyPlatformProxy;
beforeEach(() => {
server = {
addListener: jest.fn(),
@@ -36,29 +31,7 @@ beforeEach(() => {
.mockReturnValue({ port: 1234, family: 'test-family', address: 'test-address' }),
getConnections: jest.fn(),
} as any;
-
- mockHapiServer = { listener: server, route: jest.fn() } as any;
-
- root = {
- logger,
- shutdown: jest.fn(),
- start: jest.fn(),
- } as any;
-
- const env = new Env('/kibana', getEnvOptions());
- proxifier = new LegacyPlatformProxifier(root, env);
- env.legacy.emit('connection', {
- server: mockHapiServer,
- options: { someOption: 'foo', someAnotherOption: 'bar' },
- });
-});
-
-test('correctly binds to the server.', () => {
- expect(mockHapiServer.route.mock.calls).toMatchSnapshot('proxy route options');
- expect(server.addListener).toHaveBeenCalledTimes(6);
- for (const eventName of ['clientError', 'close', 'connection', 'error', 'listening', 'upgrade']) {
- expect(server.addListener).toHaveBeenCalledWith(eventName, expect.any(Function));
- }
+ proxy = new LegacyPlatformProxy({ debug: jest.fn() } as any, server);
});
test('correctly redirects server events.', () => {
@@ -66,7 +39,7 @@ test('correctly redirects server events.', () => {
expect(server.addListener).toHaveBeenCalledWith(eventName, expect.any(Function));
const listener = jest.fn();
- proxifier.addListener(eventName, listener);
+ proxy.addListener(eventName, listener);
// Emit several events, to make sure that server is not being listened with `once`.
const [, serverListener] = server.addListener.mock.calls.find(
@@ -78,68 +51,47 @@ test('correctly redirects server events.', () => {
expect(listener).toHaveBeenCalledTimes(2);
expect(listener).toHaveBeenCalledWith(1, 2, 3, 4);
- expect(listener).toHaveBeenCalledWith(5, 6, 7, 8);
- proxifier.removeListener(eventName, listener);
+ proxy.removeListener(eventName, listener);
}
});
test('returns `address` from the underlying server.', () => {
- expect(proxifier.address()).toEqual({
+ expect(proxy.address()).toEqual({
address: 'test-address',
family: 'test-family',
port: 1234,
});
});
-test('`listen` starts the `root`.', async () => {
+test('`listen` calls callback immediately.', async () => {
const onListenComplete = jest.fn();
- await proxifier.listen(1234, 'host-1', onListenComplete);
+ await proxy.listen(1234, 'host-1', onListenComplete);
- expect(root.start).toHaveBeenCalledTimes(1);
expect(onListenComplete).toHaveBeenCalledTimes(1);
});
-test('`close` shuts down the `root`.', async () => {
+test('`close` calls callback immediately.', async () => {
const onCloseComplete = jest.fn();
- await proxifier.close(onCloseComplete);
+ await proxy.close(onCloseComplete);
- expect(root.shutdown).toHaveBeenCalledTimes(1);
expect(onCloseComplete).toHaveBeenCalledTimes(1);
});
test('returns connection count from the underlying server.', () => {
server.getConnections.mockImplementation(callback => callback(null, 0));
const onGetConnectionsComplete = jest.fn();
- proxifier.getConnections(onGetConnectionsComplete);
+ proxy.getConnections(onGetConnectionsComplete);
expect(onGetConnectionsComplete).toHaveBeenCalledTimes(1);
expect(onGetConnectionsComplete).toHaveBeenCalledWith(null, 0);
onGetConnectionsComplete.mockReset();
server.getConnections.mockImplementation(callback => callback(null, 100500));
- proxifier.getConnections(onGetConnectionsComplete);
+ proxy.getConnections(onGetConnectionsComplete);
expect(onGetConnectionsComplete).toHaveBeenCalledTimes(1);
expect(onGetConnectionsComplete).toHaveBeenCalledWith(null, 100500);
});
-
-test('proxy route abandons request processing and forwards it to the legacy Kibana', async () => {
- const mockResponseToolkit = { response: jest.fn(), abandon: Symbol('abandon') };
- const mockRequest = { raw: { req: { a: 1 }, res: { b: 2 } } };
-
- const onRequest = jest.fn();
- proxifier.addListener('request', onRequest);
-
- const [[{ handler }]] = mockHapiServer.route.mock.calls;
- const response = await handler(mockRequest, mockResponseToolkit);
-
- expect(response).toBe(mockResponseToolkit.abandon);
- expect(mockResponseToolkit.response).not.toHaveBeenCalled();
-
- // Make sure request hasn't been passed to the legacy platform.
- expect(onRequest).toHaveBeenCalledTimes(1);
- expect(onRequest).toHaveBeenCalledWith(mockRequest.raw.req, mockRequest.raw.res);
-});
diff --git a/src/core/server/legacy_compat/__tests__/legacy_service.test.ts b/src/core/server/legacy_compat/__tests__/legacy_service.test.ts
new file mode 100644
index 0000000000000..dc16709861084
--- /dev/null
+++ b/src/core/server/legacy_compat/__tests__/legacy_service.test.ts
@@ -0,0 +1,339 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { BehaviorSubject, Subject, throwError } from 'rxjs';
+
+jest.mock('../legacy_platform_proxy');
+jest.mock('../../../../server/kbn_server');
+jest.mock('../../../../cli/cluster/cluster_manager');
+
+import { first } from 'rxjs/operators';
+// @ts-ignore: implicit any for JS file
+import MockClusterManager from '../../../../cli/cluster/cluster_manager';
+// @ts-ignore: implicit any for JS file
+import MockKbnServer from '../../../../server/kbn_server';
+import { Config, ConfigService, Env, ObjectToConfigAdapter } from '../../config';
+import { getEnvOptions } from '../../config/__tests__/__mocks__/env';
+import { logger } from '../../logging/__mocks__';
+import { LegacyPlatformProxy } from '../legacy_platform_proxy';
+import { LegacyService } from '../legacy_service';
+
+const MockLegacyPlatformProxy: jest.Mock = LegacyPlatformProxy as any;
+
+let legacyService: LegacyService;
+let configService: jest.Mocked;
+let env: Env;
+let mockHttpServerInfo: any;
+let config$: BehaviorSubject;
+beforeEach(() => {
+ env = Env.createDefault(getEnvOptions());
+
+ MockKbnServer.prototype.ready = jest.fn().mockReturnValue(Promise.resolve());
+
+ mockHttpServerInfo = {
+ server: { listener: { addListener: jest.fn() }, route: jest.fn() },
+ options: { someOption: 'foo', someAnotherOption: 'bar' },
+ };
+
+ config$ = new BehaviorSubject(
+ new ObjectToConfigAdapter({
+ server: { autoListen: true },
+ })
+ );
+
+ configService = {
+ getConfig$: jest.fn().mockReturnValue(config$),
+ atPath: jest.fn().mockReturnValue(new BehaviorSubject({})),
+ } as any;
+ legacyService = new LegacyService(env, logger, configService);
+});
+
+afterEach(() => {
+ MockLegacyPlatformProxy.mockClear();
+ MockKbnServer.mockClear();
+ MockClusterManager.create.mockClear();
+ logger.mockClear();
+});
+
+describe('once LegacyService is started with connection info', () => {
+ test('register proxy route.', async () => {
+ await legacyService.start(mockHttpServerInfo);
+
+ expect(mockHttpServerInfo.server.route.mock.calls).toMatchSnapshot('proxy route options');
+ });
+
+ test('proxy route responds with `503` if `kbnServer` is not ready yet.', async () => {
+ configService.atPath.mockReturnValue(new BehaviorSubject({ autoListen: true }));
+
+ const kbnServerListen$ = new Subject();
+ MockKbnServer.prototype.listen = jest.fn(() => {
+ kbnServerListen$.next();
+ return kbnServerListen$.toPromise();
+ });
+
+ // Wait until listen is called and proxy route is registered, but don't allow
+ // listen to complete and make kbnServer available.
+ const legacyStartPromise = legacyService.start(mockHttpServerInfo);
+ await kbnServerListen$.pipe(first()).toPromise();
+
+ const mockResponse: any = {
+ code: jest.fn().mockImplementation(() => mockResponse),
+ header: jest.fn().mockImplementation(() => mockResponse),
+ };
+ const mockResponseToolkit = {
+ response: jest.fn().mockReturnValue(mockResponse),
+ abandon: Symbol('abandon'),
+ };
+ const mockRequest = { raw: { req: { a: 1 }, res: { b: 2 } } };
+
+ const [[{ handler }]] = mockHttpServerInfo.server.route.mock.calls;
+ const response503 = await handler(mockRequest, mockResponseToolkit);
+
+ expect(response503).toBe(mockResponse);
+ expect({
+ body: mockResponseToolkit.response.mock.calls,
+ code: mockResponse.code.mock.calls,
+ header: mockResponse.header.mock.calls,
+ }).toMatchSnapshot('503 response');
+
+ // Make sure request hasn't been passed to the legacy platform.
+ const [mockedLegacyPlatformProxy] = MockLegacyPlatformProxy.mock.instances;
+ expect(mockedLegacyPlatformProxy.emit).not.toHaveBeenCalled();
+
+ // Now wait until kibana is ready and try to request once again.
+ kbnServerListen$.complete();
+ await legacyStartPromise;
+ mockResponseToolkit.response.mockClear();
+
+ const responseProxy = await handler(mockRequest, mockResponseToolkit);
+ expect(responseProxy).toBe(mockResponseToolkit.abandon);
+ expect(mockResponseToolkit.response).not.toHaveBeenCalled();
+
+ // Make sure request has been passed to the legacy platform.
+ expect(mockedLegacyPlatformProxy.emit).toHaveBeenCalledTimes(1);
+ expect(mockedLegacyPlatformProxy.emit).toHaveBeenCalledWith(
+ 'request',
+ mockRequest.raw.req,
+ mockRequest.raw.res
+ );
+ });
+
+ test('creates legacy kbnServer and calls `listen`.', async () => {
+ configService.atPath.mockReturnValue(new BehaviorSubject({ autoListen: true }));
+
+ await legacyService.start(mockHttpServerInfo);
+
+ expect(MockKbnServer).toHaveBeenCalledTimes(1);
+ expect(MockKbnServer).toHaveBeenCalledWith(
+ { server: { autoListen: true } },
+ {
+ serverOptions: {
+ listener: expect.any(LegacyPlatformProxy),
+ someAnotherOption: 'bar',
+ someOption: 'foo',
+ },
+ }
+ );
+
+ const [mockKbnServer] = MockKbnServer.mock.instances;
+ expect(mockKbnServer.listen).toHaveBeenCalledTimes(1);
+ expect(mockKbnServer.close).not.toHaveBeenCalled();
+ });
+
+ test('creates legacy kbnServer but does not call `listen` if `autoListen: false`.', async () => {
+ configService.atPath.mockReturnValue(new BehaviorSubject({ autoListen: false }));
+
+ await legacyService.start(mockHttpServerInfo);
+
+ expect(MockKbnServer).toHaveBeenCalledTimes(1);
+ expect(MockKbnServer).toHaveBeenCalledWith(
+ { server: { autoListen: true } },
+ {
+ serverOptions: {
+ listener: expect.any(LegacyPlatformProxy),
+ someAnotherOption: 'bar',
+ someOption: 'foo',
+ },
+ }
+ );
+
+ const [mockKbnServer] = MockKbnServer.mock.instances;
+ expect(mockKbnServer.ready).toHaveBeenCalledTimes(1);
+ expect(mockKbnServer.listen).not.toHaveBeenCalled();
+ expect(mockKbnServer.close).not.toHaveBeenCalled();
+ });
+
+ test('creates legacy kbnServer and closes it if `listen` fails.', async () => {
+ configService.atPath.mockReturnValue(new BehaviorSubject({ autoListen: true }));
+ MockKbnServer.prototype.listen.mockRejectedValue(new Error('something failed'));
+
+ await expect(legacyService.start(mockHttpServerInfo)).rejects.toThrowErrorMatchingSnapshot();
+
+ const [mockKbnServer] = MockKbnServer.mock.instances;
+ expect(mockKbnServer.listen).toHaveBeenCalled();
+ expect(mockKbnServer.close).toHaveBeenCalled();
+ });
+
+ test('throws if fails to retrieve initial config.', async () => {
+ configService.getConfig$.mockReturnValue(throwError(new Error('something failed')));
+
+ await expect(legacyService.start(mockHttpServerInfo)).rejects.toThrowErrorMatchingSnapshot();
+
+ expect(MockKbnServer).not.toHaveBeenCalled();
+ expect(MockClusterManager).not.toHaveBeenCalled();
+ });
+
+ test('reconfigures logging configuration if new config is received.', async () => {
+ await legacyService.start(mockHttpServerInfo);
+
+ const [mockKbnServer] = MockKbnServer.mock.instances;
+ expect(mockKbnServer.applyLoggingConfiguration).not.toHaveBeenCalled();
+
+ config$.next(new ObjectToConfigAdapter({ logging: { verbose: true } }));
+
+ expect(mockKbnServer.applyLoggingConfiguration.mock.calls).toMatchSnapshot(
+ `applyLoggingConfiguration params`
+ );
+ });
+
+ test('logs error if re-configuring fails.', async () => {
+ await legacyService.start(mockHttpServerInfo);
+
+ const [mockKbnServer] = MockKbnServer.mock.instances;
+ expect(mockKbnServer.applyLoggingConfiguration).not.toHaveBeenCalled();
+ expect(logger.mockCollect().error).toEqual([]);
+
+ const configError = new Error('something went wrong');
+ mockKbnServer.applyLoggingConfiguration.mockImplementation(() => {
+ throw configError;
+ });
+
+ config$.next(new ObjectToConfigAdapter({ logging: { verbose: true } }));
+
+ expect(logger.mockCollect().error).toEqual([[configError]]);
+ });
+
+ test('logs error if config service fails.', async () => {
+ await legacyService.start(mockHttpServerInfo);
+
+ const [mockKbnServer] = MockKbnServer.mock.instances;
+ expect(mockKbnServer.applyLoggingConfiguration).not.toHaveBeenCalled();
+ expect(logger.mockCollect().error).toEqual([]);
+
+ const configError = new Error('something went wrong');
+ config$.error(configError);
+
+ expect(mockKbnServer.applyLoggingConfiguration).not.toHaveBeenCalled();
+ expect(logger.mockCollect().error).toEqual([[configError]]);
+ });
+
+ test('proxy route abandons request processing and forwards it to the legacy Kibana', async () => {
+ const mockResponseToolkit = { response: jest.fn(), abandon: Symbol('abandon') };
+ const mockRequest = { raw: { req: { a: 1 }, res: { b: 2 } } };
+
+ await legacyService.start(mockHttpServerInfo);
+
+ const [[{ handler }]] = mockHttpServerInfo.server.route.mock.calls;
+ const response = await handler(mockRequest, mockResponseToolkit);
+
+ expect(response).toBe(mockResponseToolkit.abandon);
+ expect(mockResponseToolkit.response).not.toHaveBeenCalled();
+
+ // Make sure request has been passed to the legacy platform.
+ const [mockedLegacyPlatformProxy] = MockLegacyPlatformProxy.mock.instances;
+ expect(mockedLegacyPlatformProxy.emit).toHaveBeenCalledTimes(1);
+ expect(mockedLegacyPlatformProxy.emit).toHaveBeenCalledWith(
+ 'request',
+ mockRequest.raw.req,
+ mockRequest.raw.res
+ );
+ });
+});
+
+describe('once LegacyService is started without connection info', () => {
+ beforeEach(async () => await legacyService.start());
+
+ test('creates legacy kbnServer with `autoListen: false`.', () => {
+ expect(mockHttpServerInfo.server.route).not.toHaveBeenCalled();
+ expect(MockKbnServer).toHaveBeenCalledTimes(1);
+ expect(MockKbnServer).toHaveBeenCalledWith(
+ { server: { autoListen: true } },
+ { serverOptions: { autoListen: false } }
+ );
+ });
+
+ test('reconfigures logging configuration if new config is received.', async () => {
+ const [mockKbnServer] = MockKbnServer.mock.instances;
+ expect(mockKbnServer.applyLoggingConfiguration).not.toHaveBeenCalled();
+
+ config$.next(new ObjectToConfigAdapter({ logging: { verbose: true } }));
+
+ expect(mockKbnServer.applyLoggingConfiguration.mock.calls).toMatchSnapshot(
+ `applyLoggingConfiguration params`
+ );
+ });
+});
+
+describe('once LegacyService is started in `devClusterMaster` mode', () => {
+ beforeEach(() => {
+ configService.atPath.mockImplementation(path => {
+ return new BehaviorSubject(
+ path === 'dev' ? { basePathProxyTargetPort: 100500 } : { basePath: '/abc' }
+ );
+ });
+ });
+
+ test('creates ClusterManager without base path proxy.', async () => {
+ const devClusterLegacyService = new LegacyService(
+ Env.createDefault(
+ getEnvOptions({
+ cliArgs: { silent: true, basePath: false },
+ isDevClusterMaster: true,
+ })
+ ),
+ logger,
+ configService
+ );
+
+ await devClusterLegacyService.start();
+
+ expect(MockClusterManager.create.mock.calls).toMatchSnapshot(
+ 'cluster manager without base path proxy'
+ );
+ });
+
+ test('creates ClusterManager with base path proxy.', async () => {
+ const devClusterLegacyService = new LegacyService(
+ Env.createDefault(
+ getEnvOptions({
+ cliArgs: { quiet: true, basePath: true },
+ isDevClusterMaster: true,
+ })
+ ),
+ logger,
+ configService
+ );
+
+ await devClusterLegacyService.start();
+
+ expect(MockClusterManager.create.mock.calls).toMatchSnapshot(
+ 'cluster manager with base path proxy'
+ );
+ });
+});
diff --git a/src/core/server/legacy_compat/config/__tests__/__snapshots__/legacy_object_to_config_adapter.test.ts.snap b/src/core/server/legacy_compat/config/__tests__/__snapshots__/legacy_object_to_config_adapter.test.ts.snap
index d03398e173e40..af2bfff0abfe3 100644
--- a/src/core/server/legacy_compat/config/__tests__/__snapshots__/legacy_object_to_config_adapter.test.ts.snap
+++ b/src/core/server/legacy_compat/config/__tests__/__snapshots__/legacy_object_to_config_adapter.test.ts.snap
@@ -2,6 +2,7 @@
exports[`#get correctly handles server config. 1`] = `
Object {
+ "autoListen": true,
"basePath": "/abc",
"cors": false,
"host": "host",
diff --git a/src/core/server/legacy_compat/config/legacy_object_to_config_adapter.ts b/src/core/server/legacy_compat/config/legacy_object_to_config_adapter.ts
index ef07e86e5fefc..483e156f4697d 100644
--- a/src/core/server/legacy_compat/config/legacy_object_to_config_adapter.ts
+++ b/src/core/server/legacy_compat/config/legacy_object_to_config_adapter.ts
@@ -32,7 +32,7 @@ interface LegacyLoggingConfig {
}
/**
- * Represents adapter between config provided by legacy platform and `RawConfig`
+ * Represents adapter between config provided by legacy platform and `Config`
* supported by the current platform.
*/
export class LegacyObjectToConfigAdapter extends ObjectToConfigAdapter {
@@ -59,6 +59,7 @@ export class LegacyObjectToConfigAdapter extends ObjectToConfigAdapter {
// TODO: New platform uses just a subset of `server` config from the legacy platform,
// new values will be exposed once we need them (eg. customResponseHeaders or xsrf).
return {
+ autoListen: configValue.autoListen,
basePath: configValue.basePath,
cors: configValue.cors,
host: configValue.host,
diff --git a/src/core/server/legacy_compat/index.ts b/src/core/server/legacy_compat/index.ts
index dc5db0ab1fb7a..3e10928aa3456 100644
--- a/src/core/server/legacy_compat/index.ts
+++ b/src/core/server/legacy_compat/index.ts
@@ -17,54 +17,17 @@
* under the License.
*/
-import { BehaviorSubject } from 'rxjs';
-import { map } from 'rxjs/operators';
+import { ConfigService, Env } from '../config';
+import { LoggerFactory } from '../logging';
+import { LegacyService } from './legacy_service';
-/** @internal */
-export { LegacyPlatformProxifier } from './legacy_platform_proxifier';
-/** @internal */
export { LegacyObjectToConfigAdapter } from './config/legacy_object_to_config_adapter';
+export { LegacyService } from './legacy_service';
-import { LegacyObjectToConfigAdapter, LegacyPlatformProxifier } from '.';
-import { Env } from '../config';
-import { Root } from '../root';
-import { BasePathProxyRoot } from '../root/base_path_proxy_root';
+export class LegacyCompatModule {
+ public readonly service: LegacyService;
-function initEnvironment(rawKbnServer: any, isDevClusterMaster = false) {
- const env = Env.createDefault({
- // The core doesn't work with configs yet, everything is provided by the
- // "legacy" Kibana, so we can have empty array here.
- configs: [],
- // `dev` is the only CLI argument we currently use.
- cliArgs: { dev: rawKbnServer.config.get('env.dev') },
- isDevClusterMaster,
- });
-
- const legacyConfig$ = new BehaviorSubject>(rawKbnServer.config.get());
- return {
- config$: legacyConfig$.pipe(map(legacyConfig => new LegacyObjectToConfigAdapter(legacyConfig))),
- env,
- // Propagates legacy config updates to the new platform.
- updateConfig(legacyConfig: Record) {
- legacyConfig$.next(legacyConfig);
- },
- };
+ constructor(private readonly configService: ConfigService, logger: LoggerFactory, env: Env) {
+ this.service = new LegacyService(env, logger, this.configService);
+ }
}
-
-/**
- * @internal
- */
-export const injectIntoKbnServer = (rawKbnServer: any) => {
- const { env, config$, updateConfig } = initEnvironment(rawKbnServer);
-
- rawKbnServer.newPlatform = {
- // Custom HTTP Listener that will be used within legacy platform by HapiJS server.
- proxyListener: new LegacyPlatformProxifier(new Root(config$, env), env),
- updateConfig,
- };
-};
-
-export const createBasePathProxy = (rawKbnServer: any) => {
- const { env, config$ } = initEnvironment(rawKbnServer, true /*isDevClusterMaster*/);
- return new BasePathProxyRoot(config$, env);
-};
diff --git a/src/core/server/legacy_compat/legacy_platform_proxifier.ts b/src/core/server/legacy_compat/legacy_platform_proxifier.ts
deleted file mode 100644
index 8baa156266ef0..0000000000000
--- a/src/core/server/legacy_compat/legacy_platform_proxifier.ts
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { EventEmitter } from 'events';
-import { Server } from 'net';
-
-import { Server as HapiServer, ServerOptions as HapiServerOptions } from 'hapi-latest';
-import { Env } from '../config';
-import { Logger } from '../logging';
-import { Root } from '../root';
-
-interface ConnectionInfo {
- server: HapiServer;
- options: HapiServerOptions;
-}
-
-/**
- * List of the server events to be forwarded to the legacy platform.
- */
-const ServerEventsToForward = [
- 'clientError',
- 'close',
- 'connection',
- 'error',
- 'listening',
- 'upgrade',
-];
-
-/**
- * Represents "proxy" between legacy and current platform.
- * @internal
- */
-export class LegacyPlatformProxifier extends EventEmitter {
- private readonly eventHandlers: Map void>;
- private readonly log: Logger;
- private server?: Server;
-
- constructor(private readonly root: Root, private readonly env: Env) {
- super();
-
- this.log = root.logger.get('legacy-platform-proxifier');
-
- // HapiJS expects that the following events will be generated by `listener`, see:
- // https://github.com/hapijs/hapi/blob/v14.2.0/lib/connection.js.
- this.eventHandlers = new Map(
- ServerEventsToForward.map(eventName => {
- return [
- eventName,
- (...args: any[]) => {
- this.log.debug(`Event is being forwarded: ${eventName}`);
- this.emit(eventName, ...args);
- },
- ] as [string, (...args: any[]) => void];
- })
- );
-
- // Once core HTTP service is ready it broadcasts the internal server it relies on
- // and server options that were used to create that server so that we can properly
- // bridge with the "legacy" Kibana. If server isn't run (e.g. if process is managed
- // by ClusterManager or optimizer) then this event will never fire.
- this.env.legacy.once('connection', (connectionInfo: ConnectionInfo) =>
- this.onConnection(connectionInfo)
- );
- }
-
- /**
- * Neither new nor legacy platform should use this method directly.
- */
- public address() {
- return this.server && this.server.address();
- }
-
- /**
- * Neither new nor legacy platform should use this method directly.
- */
- public async listen(port: number, host: string, callback?: (error?: Error) => void) {
- this.log.debug(`"listen" has been called (${host}:${port}).`);
-
- let error: Error | undefined;
- try {
- await this.root.start();
- } catch (err) {
- error = err;
- this.emit('error', err);
- }
-
- if (callback !== undefined) {
- callback(error);
- }
- }
-
- /**
- * Neither new nor legacy platform should use this method directly.
- */
- public async close(callback?: (error?: Error) => void) {
- this.log.debug('"close" has been called.');
-
- let error: Error | undefined;
- try {
- await this.root.shutdown();
- } catch (err) {
- error = err;
- this.emit('error', err);
- }
-
- if (callback !== undefined) {
- callback(error);
- }
- }
-
- /**
- * Neither new nor legacy platform should use this method directly.
- */
- public getConnections(callback: (error: Error | null, count?: number) => void) {
- // This method is used by `even-better` (before we start platform).
- // It seems that the latest version of parent `good` doesn't use this anymore.
- if (this.server) {
- this.server.getConnections(callback);
- } else {
- callback(null, 0);
- }
- }
-
- private onConnection({ server }: ConnectionInfo) {
- this.server = server.listener;
-
- for (const [eventName, eventHandler] of this.eventHandlers) {
- this.server.addListener(eventName, eventHandler);
- }
-
- // We register Kibana proxy middleware right before we start server to allow
- // all new platform plugins register their routes, so that `legacyProxy`
- // handles only requests that aren't handled by the new platform.
- server.route({
- path: '/{p*}',
- method: '*',
- options: {
- payload: {
- output: 'stream',
- parse: false,
- timeout: false,
- // Having such a large value here will allow legacy routes to override
- // maximum allowed payload size set in the core http server if needed.
- maxBytes: Number.MAX_SAFE_INTEGER,
- },
- },
- handler: async ({ raw: { req, res } }, responseToolkit) => {
- this.log.trace(`Request will be handled by proxy ${req.method}:${req.url}.`);
- // Forward request and response objects to the legacy platform. This method
- // is used whenever new platform doesn't know how to handle the request.
- this.emit('request', req, res);
- return responseToolkit.abandon;
- },
- });
- }
-}
diff --git a/src/core/server/legacy_compat/legacy_platform_proxy.ts b/src/core/server/legacy_compat/legacy_platform_proxy.ts
new file mode 100644
index 0000000000000..e91d661e30238
--- /dev/null
+++ b/src/core/server/legacy_compat/legacy_platform_proxy.ts
@@ -0,0 +1,107 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { EventEmitter } from 'events';
+import { Server } from 'net';
+
+import { Logger } from '../logging';
+
+/**
+ * List of the server events to be forwarded to the legacy platform.
+ */
+const ServerEventsToForward = [
+ 'clientError',
+ 'close',
+ 'connection',
+ 'error',
+ 'listening',
+ 'upgrade',
+];
+
+/**
+ * Represents "proxy" between legacy and current platform.
+ * @internal
+ */
+export class LegacyPlatformProxy extends EventEmitter {
+ private readonly eventHandlers: Map void>;
+
+ constructor(private readonly log: Logger, private readonly server: Server) {
+ super();
+
+ // HapiJS expects that the following events will be generated by `listener`, see:
+ // https://github.com/hapijs/hapi/blob/v14.2.0/lib/connection.js.
+ this.eventHandlers = new Map(
+ ServerEventsToForward.map(eventName => {
+ return [
+ eventName,
+ (...args: any[]) => {
+ this.log.debug(`Event is being forwarded: ${eventName}`);
+ this.emit(eventName, ...args);
+ },
+ ] as [string, (...args: any[]) => void];
+ })
+ );
+
+ for (const [eventName, eventHandler] of this.eventHandlers) {
+ this.server.addListener(eventName, eventHandler);
+ }
+ }
+
+ /**
+ * Neither new nor legacy platform should use this method directly.
+ */
+ public address() {
+ this.log.debug('"address" has been called.');
+
+ return this.server.address();
+ }
+
+ /**
+ * Neither new nor legacy platform should use this method directly.
+ */
+ public listen(port: number, host: string, callback?: (error?: Error) => void) {
+ this.log.debug(`"listen" has been called (${host}:${port}).`);
+
+ if (callback !== undefined) {
+ callback();
+ }
+ }
+
+ /**
+ * Neither new nor legacy platform should use this method directly.
+ */
+ public close(callback?: (error?: Error) => void) {
+ this.log.debug('"close" has been called.');
+
+ if (callback !== undefined) {
+ callback();
+ }
+ }
+
+ /**
+ * Neither new nor legacy platform should use this method directly.
+ */
+ public getConnections(callback: (error: Error | null, count?: number) => void) {
+ this.log.debug('"getConnections" has been called.');
+
+ // This method is used by `even-better` (before we start platform).
+ // It seems that the latest version of parent `good` doesn't use this anymore.
+ this.server.getConnections(callback);
+ }
+}
diff --git a/src/core/server/legacy_compat/legacy_service.ts b/src/core/server/legacy_compat/legacy_service.ts
new file mode 100644
index 0000000000000..092057874fa73
--- /dev/null
+++ b/src/core/server/legacy_compat/legacy_service.ts
@@ -0,0 +1,204 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { Server as HapiServer } from 'hapi-latest';
+import { combineLatest, ConnectableObservable, EMPTY, Subscription } from 'rxjs';
+import { first, map, mergeMap, publishReplay, tap } from 'rxjs/operators';
+import { CoreService } from '../../types/core_service';
+import { Config, ConfigService, Env } from '../config';
+import { DevConfig } from '../dev';
+import { BasePathProxyServer, HttpConfig, HttpServerInfo } from '../http';
+import { Logger, LoggerFactory } from '../logging';
+import { LegacyPlatformProxy } from './legacy_platform_proxy';
+
+interface LegacyKbnServer {
+ applyLoggingConfiguration: (settings: Readonly>) => void;
+ listen: () => Promise;
+ ready: () => Promise;
+ close: () => Promise;
+}
+
+export class LegacyService implements CoreService {
+ private readonly log: Logger;
+ private kbnServer?: LegacyKbnServer;
+ private configSubscription?: Subscription;
+
+ constructor(
+ private readonly env: Env,
+ private readonly logger: LoggerFactory,
+ private readonly configService: ConfigService
+ ) {
+ this.log = logger.get('legacy', 'service');
+ }
+
+ public async start(httpServerInfo?: HttpServerInfo) {
+ this.log.debug('starting legacy service');
+
+ const update$ = this.configService.getConfig$().pipe(
+ tap(config => {
+ if (this.kbnServer !== undefined) {
+ this.kbnServer.applyLoggingConfiguration(config.toRaw());
+ }
+ }),
+ tap({ error: err => this.log.error(err) }),
+ publishReplay(1)
+ ) as ConnectableObservable;
+
+ this.configSubscription = update$.connect();
+
+ // Receive initial config and create kbnServer/ClusterManager.
+ this.kbnServer = await update$
+ .pipe(
+ first(),
+ mergeMap(async config => {
+ if (this.env.isDevClusterMaster) {
+ await this.createClusterManager(config);
+ return;
+ }
+
+ return await this.createKbnServer(config, httpServerInfo);
+ })
+ )
+ .toPromise();
+ }
+
+ public async stop() {
+ this.log.debug('stopping legacy service');
+
+ if (this.configSubscription !== undefined) {
+ this.configSubscription.unsubscribe();
+ this.configSubscription = undefined;
+ }
+
+ if (this.kbnServer !== undefined) {
+ await this.kbnServer.close();
+ this.kbnServer = undefined;
+ }
+ }
+
+ private async createClusterManager(config: Config) {
+ const basePathProxy$ = this.env.cliArgs.basePath
+ ? combineLatest(
+ this.configService.atPath('dev', DevConfig),
+ this.configService.atPath('server', HttpConfig)
+ ).pipe(
+ first(),
+ map(([devConfig, httpConfig]) => {
+ return new BasePathProxyServer(this.logger.get('server'), httpConfig, devConfig);
+ })
+ )
+ : EMPTY;
+
+ require('../../../cli/cluster/cluster_manager').create(
+ this.env.cliArgs,
+ config.toRaw(),
+ await basePathProxy$.toPromise()
+ );
+ }
+
+ private async createKbnServer(config: Config, httpServerInfo?: HttpServerInfo) {
+ const KbnServer = require('../../../server/kbn_server');
+ const kbnServer: LegacyKbnServer = new KbnServer(config.toRaw(), {
+ // If core HTTP service is run we'll receive internal server reference and
+ // options that were used to create that server so that we can properly
+ // bridge with the "legacy" Kibana. If server isn't run (e.g. if process is
+ // managed by ClusterManager or optimizer) then we won't have that info,
+ // so we can't start "legacy" server either.
+ serverOptions:
+ httpServerInfo !== undefined
+ ? {
+ ...httpServerInfo.options,
+ listener: this.setupProxyListener(httpServerInfo.server),
+ }
+ : { autoListen: false },
+ });
+
+ // The kbnWorkerType check is necessary to prevent the repl
+ // from being started multiple times in different processes.
+ // We only want one REPL.
+ if (this.env.cliArgs.repl && process.env.kbnWorkerType === 'server') {
+ require('../../../cli/repl').startRepl(kbnServer);
+ }
+
+ const httpConfig = await this.configService
+ .atPath('server', HttpConfig)
+ .pipe(first())
+ .toPromise();
+
+ if (httpConfig.autoListen) {
+ try {
+ await kbnServer.listen();
+ } catch (err) {
+ await kbnServer.close();
+ throw err;
+ }
+ } else {
+ await kbnServer.ready();
+ }
+
+ return kbnServer;
+ }
+
+ private setupProxyListener(server: HapiServer) {
+ const legacyProxy = new LegacyPlatformProxy(
+ this.logger.get('legacy', 'proxy'),
+ server.listener
+ );
+
+ // We register Kibana proxy middleware right before we start server to allow
+ // all new platform plugins register their routes, so that `legacyProxy`
+ // handles only requests that aren't handled by the new platform.
+ server.route({
+ path: '/{p*}',
+ method: '*',
+ options: {
+ payload: {
+ output: 'stream',
+ parse: false,
+ timeout: false,
+ // Having such a large value here will allow legacy routes to override
+ // maximum allowed payload size set in the core http server if needed.
+ maxBytes: Number.MAX_SAFE_INTEGER,
+ },
+ },
+ handler: async ({ raw: { req, res } }, responseToolkit) => {
+ if (this.kbnServer === undefined) {
+ this.log.debug(`Kibana server is not ready yet ${req.method}:${req.url}.`);
+
+ // If legacy server is not ready yet (e.g. it's still in optimization phase),
+ // we should let client know that and ask to retry after 30 seconds.
+ return responseToolkit
+ .response('Kibana server is not ready yet')
+ .code(503)
+ .header('Retry-After', '30');
+ }
+
+ this.log.trace(`Request will be handled by proxy ${req.method}:${req.url}.`);
+
+ // Forward request and response objects to the legacy platform. This method
+ // is used whenever new platform doesn't know how to handle the request.
+ legacyProxy.emit('request', req, res);
+
+ return responseToolkit.abandon;
+ },
+ });
+
+ return legacyProxy;
+ }
+}
diff --git a/src/core/server/logging/logging_service.ts b/src/core/server/logging/logging_service.ts
index 90ee9524381de..966bd74a0df41 100644
--- a/src/core/server/logging/logging_service.ts
+++ b/src/core/server/logging/logging_service.ts
@@ -71,7 +71,7 @@ export class LoggingService implements LoggerFactory {
this.appenders.set(appenderKey, Appenders.create(appenderConfig));
}
- for (const [loggerKey, loggerAdapter] of this.loggers.entries()) {
+ for (const [loggerKey, loggerAdapter] of this.loggers) {
loggerAdapter.updateLogger(this.createLogger(loggerKey, config));
}
diff --git a/src/core/server/root/base_path_proxy_root.ts b/src/core/server/root/base_path_proxy_root.ts
deleted file mode 100644
index 80ab7d1c60677..0000000000000
--- a/src/core/server/root/base_path_proxy_root.ts
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { first } from 'rxjs/operators';
-
-import { Root } from '.';
-import { DevConfig } from '../dev';
-import { HttpConfig } from '../http';
-import { BasePathProxyServer, BasePathProxyServerOptions } from '../http/base_path_proxy_server';
-
-/**
- * Top-level entry point to start BasePathProxy server.
- */
-export class BasePathProxyRoot extends Root {
- private basePathProxy?: BasePathProxyServer;
-
- public async configure({
- blockUntil,
- shouldRedirectFromOldBasePath,
- }: Pick) {
- const [devConfig, httpConfig] = await Promise.all([
- this.configService
- .atPath('dev', DevConfig)
- .pipe(first())
- .toPromise(),
- this.configService
- .atPath('server', HttpConfig)
- .pipe(first())
- .toPromise(),
- ]);
-
- this.basePathProxy = new BasePathProxyServer(this.logger.get('server'), {
- blockUntil,
- devConfig,
- httpConfig,
- shouldRedirectFromOldBasePath,
- });
- }
-
- public getBasePath() {
- return this.getBasePathProxy().basePath;
- }
-
- public getTargetPort() {
- return this.getBasePathProxy().targetPort;
- }
-
- protected async startServer() {
- return this.getBasePathProxy().start();
- }
-
- protected async stopServer() {
- await this.getBasePathProxy().stop();
- this.basePathProxy = undefined;
- }
-
- private getBasePathProxy() {
- if (this.basePathProxy === undefined) {
- throw new Error('BasePathProxyRoot is not configured!');
- }
-
- return this.basePathProxy;
- }
-}
diff --git a/src/core/server/root/index.ts b/src/core/server/root/index.ts
index 935d11a83e963..02a5c9e559544 100644
--- a/src/core/server/root/index.ts
+++ b/src/core/server/root/index.ts
@@ -18,38 +18,34 @@
*/
import { ConnectableObservable, Observable, Subscription } from 'rxjs';
-import { catchError, first, map, publishReplay } from 'rxjs/operators';
+import { first, map, publishReplay, tap } from 'rxjs/operators';
import { Server } from '..';
import { Config, ConfigService, Env } from '../config';
-
import { Logger, LoggerFactory, LoggingConfig, LoggingService } from '../logging';
-export type OnShutdown = (reason?: Error) => void;
-
/**
* Top-level entry point to kick off the app and start the Kibana server.
*/
export class Root {
public readonly logger: LoggerFactory;
- protected readonly configService: ConfigService;
+ private readonly configService: ConfigService;
private readonly log: Logger;
- private server?: Server;
+ private readonly server: Server;
private readonly loggingService: LoggingService;
private loggingConfigSubscription?: Subscription;
constructor(
config$: Observable,
private readonly env: Env,
- private readonly onShutdown: OnShutdown = () => {
- // noop
- }
+ private readonly onShutdown?: (reason?: Error | string) => void
) {
this.loggingService = new LoggingService();
this.logger = this.loggingService.asLoggerFactory();
-
this.log = this.logger.get('root');
+
this.configService = new ConfigService(config$, env, this.logger);
+ this.server = new Server(this.configService, this.logger, this.env);
}
public async start() {
@@ -57,53 +53,46 @@ export class Root {
try {
await this.setupLogging();
- await this.startServer();
+ await this.server.start();
} catch (e) {
await this.shutdown(e);
throw e;
}
}
- public async shutdown(reason?: Error) {
+ public async shutdown(reason?: any) {
this.log.debug('shutting root down');
- await this.stopServer();
+ if (reason) {
+ if (reason.code === 'EADDRINUSE' && Number.isInteger(reason.port)) {
+ reason = new Error(
+ `Port ${reason.port} is already in use. Another instance of Kibana may be running!`
+ );
+ }
+
+ this.log.fatal(reason);
+ }
+
+ await this.server.stop();
if (this.loggingConfigSubscription !== undefined) {
this.loggingConfigSubscription.unsubscribe();
this.loggingConfigSubscription = undefined;
}
-
await this.loggingService.stop();
- this.onShutdown(reason);
- }
-
- protected async startServer() {
- this.server = new Server(this.configService, this.logger, this.env);
- return this.server.start();
- }
-
- protected async stopServer() {
- if (this.server === undefined) {
- return;
+ if (this.onShutdown !== undefined) {
+ this.onShutdown(reason);
}
-
- await this.server.stop();
- this.server = undefined;
}
private async setupLogging() {
// Stream that maps config updates to logger updates, including update failures.
const update$ = this.configService.atPath('logging', LoggingConfig).pipe(
map(config => this.loggingService.upgrade(config)),
- catchError(err => {
- // This specifically console.logs because we were not able to configure the logger.
- // tslint:disable-next-line no-console
- console.error('Configuring logger failed:', err);
-
- throw err;
- }),
+ // This specifically console.logs because we were not able to configure the logger.
+ // tslint:disable-next-line no-console
+ tap({ error: err => console.error('Configuring logger failed:', err) }),
publishReplay(1)
) as ConnectableObservable;
diff --git a/src/core/types/core_service.ts b/src/core/types/core_service.ts
index b6031e0deb7ba..8a8ac92b93ccc 100644
--- a/src/core/types/core_service.ts
+++ b/src/core/types/core_service.ts
@@ -17,7 +17,7 @@
* under the License.
*/
-export interface CoreService {
- start(): Promise;
+export interface CoreService {
+ start(): Promise;
stop(): Promise;
}
diff --git a/src/core_plugins/kibana/server/lib/__tests__/manage_uuid.js b/src/core_plugins/kibana/server/lib/__tests__/manage_uuid.js
index 54720cf98a827..2aa3953a78bd9 100644
--- a/src/core_plugins/kibana/server/lib/__tests__/manage_uuid.js
+++ b/src/core_plugins/kibana/server/lib/__tests__/manage_uuid.js
@@ -19,7 +19,7 @@
import expect from 'expect.js';
import sinon from 'sinon';
-import { startTestServers } from '../../../../../test_utils/kbn_server.js';
+import { startTestServers } from '../../../../../test_utils/kbn_server';
import manageUuid from '../manage_uuid';
describe('core_plugins/kibana/server/lib', function () {
diff --git a/src/functional_test_runner/__tests__/lib/index.js b/src/functional_test_runner/__tests__/lib/index.js
deleted file mode 100644
index a92d22e2738bb..0000000000000
--- a/src/functional_test_runner/__tests__/lib/index.js
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-export { startupKibana } from './kibana';
diff --git a/src/functional_test_runner/__tests__/lib/kibana.js b/src/functional_test_runner/__tests__/lib/kibana.js
deleted file mode 100644
index df046e34b2658..0000000000000
--- a/src/functional_test_runner/__tests__/lib/kibana.js
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { createServerWithCorePlugins } from '../../../test_utils/kbn_server';
-
-export async function startupKibana({ port, esUrl }) {
- const server = createServerWithCorePlugins({
- server: {
- port,
- autoListen: true,
- },
-
- elasticsearch: {
- url: esUrl
- }
- });
-
- await server.ready();
- return server;
-}
diff --git a/src/server/config/__tests__/deprecation_warnings.js b/src/server/config/__tests__/deprecation_warnings.js
index 4e90708456b25..9935a2e4bddbf 100644
--- a/src/server/config/__tests__/deprecation_warnings.js
+++ b/src/server/config/__tests__/deprecation_warnings.js
@@ -40,7 +40,8 @@ describe('config/deprecation warnings mixin', function () {
env: {
CREATE_SERVER_OPTS: JSON.stringify({
logging: {
- quiet: false
+ quiet: false,
+ silent: false
},
uiSettings: {
enabled: true
diff --git a/src/server/config/__tests__/fixtures/run_kbn_server_startup.js b/src/server/config/__tests__/fixtures/run_kbn_server_startup.js
index 46eb6b4661f49..d6622cf69ddb0 100644
--- a/src/server/config/__tests__/fixtures/run_kbn_server_startup.js
+++ b/src/server/config/__tests__/fixtures/run_kbn_server_startup.js
@@ -17,18 +17,18 @@
* under the License.
*/
-import { createServer } from '../../../../test_utils/kbn_server';
+import { createRoot } from '../../../../test_utils/kbn_server';
(async function run() {
- const server = createServer(JSON.parse(process.env.CREATE_SERVER_OPTS));
+ const root = createRoot(JSON.parse(process.env.CREATE_SERVER_OPTS));
// We just need the server to run through startup so that it will
// log the deprecation messages. Once it has started up we close it
// to allow the process to exit naturally
try {
- await server.ready();
+ await root.start();
} finally {
- await server.close();
+ await root.shutdown();
}
}());
diff --git a/src/server/http/__snapshots__/max_payload_size.test.js.snap b/src/server/http/__snapshots__/max_payload_size.test.js.snap
deleted file mode 100644
index 12e9ab278e1fb..0000000000000
--- a/src/server/http/__snapshots__/max_payload_size.test.js.snap
+++ /dev/null
@@ -1,3 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`fails with 400 if payload size is larger than default and route config allows 1`] = `"{\\"statusCode\\":400,\\"error\\":\\"Bad Request\\",\\"message\\":\\"Payload content length greater than maximum allowed: 200\\"}"`;
diff --git a/src/server/http/__snapshots__/xsrf.test.js.snap b/src/server/http/__snapshots__/xsrf.test.js.snap
deleted file mode 100644
index 2113d27927dce..0000000000000
--- a/src/server/http/__snapshots__/xsrf.test.js.snap
+++ /dev/null
@@ -1,7 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`xsrf request filter destructiveMethod: DELETE rejects requests without either an xsrf or version header: DELETE reject response 1`] = `"{\\"statusCode\\":400,\\"error\\":\\"Bad Request\\",\\"message\\":\\"Request must contain a kbn-xsrf header.\\"}"`;
-
-exports[`xsrf request filter destructiveMethod: POST rejects requests without either an xsrf or version header: POST reject response 1`] = `"{\\"statusCode\\":400,\\"error\\":\\"Bad Request\\",\\"message\\":\\"Request must contain a kbn-xsrf header.\\"}"`;
-
-exports[`xsrf request filter destructiveMethod: PUT rejects requests without either an xsrf or version header: PUT reject response 1`] = `"{\\"statusCode\\":400,\\"error\\":\\"Bad Request\\",\\"message\\":\\"Request must contain a kbn-xsrf header.\\"}"`;
diff --git a/src/server/http/index.js b/src/server/http/index.js
index 3b16cec484c30..7012b095a8658 100644
--- a/src/server/http/index.js
+++ b/src/server/http/index.js
@@ -30,35 +30,7 @@ export default async function (kbnServer, server, config) {
kbnServer.server = new Hapi.Server();
server = kbnServer.server;
- // Note that all connection options configured here should be exactly the same
- // as in `getServerOptions()` in the new platform (see `src/core/server/http/http_tools`).
- //
- // The only exception is `tls` property: TLS is entirely handled by the new
- // platform and we don't have to duplicate all TLS related settings here, we just need
- // to indicate to Hapi connection that TLS is used so that it can use correct protocol
- // name in `server.info` and `request.connection.info` that are used throughout Kibana.
- //
- // Any change SHOULD BE applied in both places.
- server.connection({
- host: config.get('server.host'),
- port: config.get('server.port'),
- tls: config.get('server.ssl.enabled'),
- listener: kbnServer.newPlatform.proxyListener,
- state: {
- strictHeader: false,
- },
- routes: {
- cors: config.get('server.cors'),
- payload: {
- maxBytes: config.get('server.maxPayloadBytes'),
- },
- validate: {
- options: {
- abortEarly: false,
- },
- },
- },
- });
+ server.connection(kbnServer.core.serverOptions);
registerHapiPlugins(server);
diff --git a/src/server/http/integration_tests/max_payload_size.test.js b/src/server/http/integration_tests/max_payload_size.test.js
new file mode 100644
index 0000000000000..3fa7ca721e1ef
--- /dev/null
+++ b/src/server/http/integration_tests/max_payload_size.test.js
@@ -0,0 +1,52 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import * as kbnTestServer from '../../../test_utils/kbn_server';
+
+let root;
+beforeAll(async () => {
+ root = kbnTestServer.createRoot({ server: { maxPayloadBytes: 100 } });
+
+ await root.start();
+
+ kbnTestServer.getKbnServer(root).server.route({
+ path: '/payload_size_check/test/route',
+ method: 'POST',
+ config: { payload: { maxBytes: 200 } },
+ handler: (req, reply) => reply(null, req.payload.data.slice(0, 5)),
+ });
+}, 30000);
+
+afterAll(async () => await root.shutdown());
+
+test('accepts payload with a size larger than default but smaller than route config allows', async () => {
+ await kbnTestServer.request.post(root, '/payload_size_check/test/route')
+ .send({ data: Array(150).fill('+').join('') })
+ .expect(200, '+++++');
+});
+
+test('fails with 400 if payload size is larger than default and route config allows', async () => {
+ await kbnTestServer.request.post(root, '/payload_size_check/test/route')
+ .send({ data: Array(250).fill('+').join('') })
+ .expect(400, {
+ statusCode: 400,
+ error: 'Bad Request',
+ message: 'Payload content length greater than maximum allowed: 200'
+ });
+});
diff --git a/src/server/http/version_check.test.js b/src/server/http/integration_tests/version_check.test.js
similarity index 53%
rename from src/server/http/version_check.test.js
rename to src/server/http/integration_tests/version_check.test.js
index e5257f814e8ae..676391ce3233b 100644
--- a/src/server/http/version_check.test.js
+++ b/src/server/http/integration_tests/version_check.test.js
@@ -18,71 +18,48 @@
*/
import { resolve } from 'path';
-import * as kbnTestServer from '../../test_utils/kbn_server';
+import * as kbnTestServer from '../../../test_utils/kbn_server';
-const src = resolve.bind(null, __dirname, '../../../src');
+const src = resolve.bind(null, __dirname, '../../../../src');
const versionHeader = 'kbn-version';
const version = require(src('../package.json')).version;
describe('version_check request filter', function () {
- async function makeRequest(kbnServer, opts) {
- return await kbnTestServer.makeRequest(kbnServer, opts);
- }
+ let root;
+ beforeAll(async () => {
+ root = kbnTestServer.createRoot();
- async function makeServer() {
- const kbnServer = kbnTestServer.createServer();
+ await root.start();
- await kbnServer.ready();
-
- kbnServer.server.route({
+ kbnTestServer.getKbnServer(root).server.route({
path: '/version_check/test/route',
method: 'GET',
handler: function (req, reply) {
reply(null, 'ok');
}
});
+ }, 30000);
- return kbnServer;
- }
-
- let kbnServer;
- beforeEach(async () => kbnServer = await makeServer());
- afterEach(async () => await kbnServer.close());
+ afterAll(async () => await root.shutdown());
it('accepts requests with the correct version passed in the version header', async function () {
- const resp = await makeRequest(kbnServer, {
- url: '/version_check/test/route',
- method: 'GET',
- headers: {
- [versionHeader]: version,
- },
- });
-
- expect(resp.statusCode).toBe(200);
- expect(resp.payload).toBe('ok');
+ await kbnTestServer.request
+ .get(root, '/version_check/test/route')
+ .set(versionHeader, version)
+ .expect(200, 'ok');
});
it('rejects requests with an incorrect version passed in the version header', async function () {
- const resp = await makeRequest(kbnServer, {
- url: '/version_check/test/route',
- method: 'GET',
- headers: {
- [versionHeader]: `invalid:${version}`,
- },
- });
-
- expect(resp.statusCode).toBe(400);
- expect(resp.payload).toMatch(/"Browser client is out of date/);
+ await kbnTestServer.request
+ .get(root, '/version_check/test/route')
+ .set(versionHeader, `invalid:${version}`)
+ .expect(400, /"Browser client is out of date/);
});
it('accepts requests that do not include a version header', async function () {
- const resp = await makeRequest(kbnServer, {
- url: '/version_check/test/route',
- method: 'GET'
- });
-
- expect(resp.statusCode).toBe(200);
- expect(resp.payload).toBe('ok');
+ await kbnTestServer.request
+ .get(root, '/version_check/test/route')
+ .expect(200, 'ok');
});
});
diff --git a/src/server/http/xsrf.test.js b/src/server/http/integration_tests/xsrf.test.js
similarity index 55%
rename from src/server/http/xsrf.test.js
rename to src/server/http/integration_tests/xsrf.test.js
index 2fc6dba4703ef..a8c87653e9b40 100644
--- a/src/server/http/xsrf.test.js
+++ b/src/server/http/integration_tests/xsrf.test.js
@@ -18,10 +18,10 @@
*/
import { resolve } from 'path';
-import * as kbnTestServer from '../../test_utils/kbn_server';
+import * as kbnTestServer from '../../../test_utils/kbn_server';
const destructiveMethods = ['POST', 'PUT', 'DELETE'];
-const src = resolve.bind(null, __dirname, '../../../src');
+const src = resolve.bind(null, __dirname, '../../../../src');
const xsrfHeader = 'kbn-xsrf';
const versionHeader = 'kbn-version';
@@ -29,23 +29,18 @@ const testPath = '/xsrf/test/route';
const whitelistedTestPath = '/xsrf/test/route/whitelisted';
const actualVersion = require(src('../package.json')).version;
-describe('xsrf request filter', function () {
- async function inject(kbnServer, opts) {
- return await kbnTestServer.makeRequest(kbnServer, opts);
- }
-
- const makeServer = async function () {
- const kbnServer = kbnTestServer.createServer({
+describe('xsrf request filter', () => {
+ let root;
+ beforeAll(async () => {
+ root = kbnTestServer.createRoot({
server: {
- xsrf: {
- disableProtection: false,
- whitelist: [whitelistedTestPath]
- }
+ xsrf: { disableProtection: false, whitelist: [whitelistedTestPath] }
}
});
- await kbnServer.ready();
+ await root.start();
+ const kbnServer = kbnTestServer.getKbnServer(root);
kbnServer.server.route({
path: testPath,
method: 'GET',
@@ -81,117 +76,68 @@ describe('xsrf request filter', function () {
reply(null, 'ok');
}
});
+ }, 30000);
- return kbnServer;
- };
-
- let kbnServer;
- beforeEach(async () => {
- kbnServer = await makeServer();
- });
-
- afterEach(async () => {
- await kbnServer.close();
- });
+ afterAll(async () => await root.shutdown());
describe(`nonDestructiveMethod: GET`, function () {
it('accepts requests without a token', async function () {
- const resp = await inject(kbnServer, {
- url: testPath,
- method: 'GET'
- });
-
- expect(resp.statusCode).toBe(200);
- expect(resp.payload).toBe('ok');
+ await kbnTestServer.request
+ .get(root, testPath)
+ .expect(200, 'ok');
});
it('accepts requests with the xsrf header', async function () {
- const resp = await inject(kbnServer, {
- url: testPath,
- method: 'GET',
- headers: {
- [xsrfHeader]: 'anything',
- },
- });
-
- expect(resp.statusCode).toBe(200);
- expect(resp.payload).toBe('ok');
+ await kbnTestServer.request
+ .get(root, testPath)
+ .set(xsrfHeader, 'anything')
+ .expect(200, 'ok');
});
});
describe(`nonDestructiveMethod: HEAD`, function () {
it('accepts requests without a token', async function () {
- const resp = await inject(kbnServer, {
- url: testPath,
- method: 'HEAD'
- });
-
- expect(resp.statusCode).toBe(200);
- expect(resp.payload).toHaveLength(0);
+ await kbnTestServer.request
+ .head(root, testPath)
+ .expect(200, undefined);
});
it('accepts requests with the xsrf header', async function () {
- const resp = await inject(kbnServer, {
- url: testPath,
- method: 'HEAD',
- headers: {
- [xsrfHeader]: 'anything',
- },
- });
-
- expect(resp.statusCode).toBe(200);
- expect(resp.payload).toHaveLength(0);
+ await kbnTestServer.request
+ .head(root, testPath)
+ .set(xsrfHeader, 'anything')
+ .expect(200, undefined);
});
});
for (const method of destructiveMethods) {
describe(`destructiveMethod: ${method}`, function () { // eslint-disable-line no-loop-func
it('accepts requests with the xsrf header', async function () {
- const resp = await inject(kbnServer, {
- url: testPath,
- method: method,
- headers: {
- [xsrfHeader]: 'anything',
- },
- });
-
- expect(resp.statusCode).toBe(200);
- expect(resp.payload).toBe('ok');
+ await kbnTestServer.request[method.toLowerCase()](root, testPath)
+ .set(xsrfHeader, 'anything')
+ .expect(200, 'ok');
});
// this is still valid for existing csrf protection support
// it does not actually do any validation on the version value itself
it('accepts requests with the version header', async function () {
- const resp = await inject(kbnServer, {
- url: testPath,
- method: method,
- headers: {
- [versionHeader]: actualVersion,
- },
- });
-
- expect(resp.statusCode).toBe(200);
- expect(resp.payload).toBe('ok');
+ await kbnTestServer.request[method.toLowerCase()](root, testPath)
+ .set(versionHeader, actualVersion)
+ .expect(200, 'ok');
});
it('rejects requests without either an xsrf or version header', async function () {
- const resp = await inject(kbnServer, {
- url: testPath,
- method: method
- });
-
- expect(resp.statusCode).toBe(400);
- expect(resp.result).toMatchSnapshot(`${method} reject response`);
+ await kbnTestServer.request[method.toLowerCase()](root, testPath)
+ .expect(400, {
+ statusCode: 400,
+ error: 'Bad Request',
+ message: 'Request must contain a kbn-xsrf header.'
+ });
});
it('accepts whitelisted requests without either an xsrf or version header', async function () {
- const resp = await inject(kbnServer, {
- url: whitelistedTestPath,
- method: method
- });
-
- expect(resp.statusCode).toBe(200);
- expect(resp.payload).toBe('ok');
+ await kbnTestServer.request[method.toLowerCase()](root, whitelistedTestPath)
+ .expect(200, 'ok');
});
});
}
diff --git a/src/server/http/max_payload_size.test.js b/src/server/http/max_payload_size.test.js
deleted file mode 100644
index 499ce43b8d09a..0000000000000
--- a/src/server/http/max_payload_size.test.js
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import * as kbnTestServer from '../../test_utils/kbn_server';
-
-let kbnServer;
-async function makeServer({ maxPayloadBytesDefault, maxPayloadBytesRoute }) {
- kbnServer = kbnTestServer.createServer({
- server: { maxPayloadBytes: maxPayloadBytesDefault }
- });
-
- await kbnServer.ready();
-
- kbnServer.server.route({
- path: '/payload_size_check/test/route',
- method: 'POST',
- config: { payload: { maxBytes: maxPayloadBytesRoute } },
- handler: function (req, reply) {
- reply(null, req.payload.data.slice(0, 5));
- }
- });
-}
-
-async function makeRequest(opts) {
- return await kbnTestServer.makeRequest(kbnServer, opts);
-}
-
-afterEach(async () => await kbnServer.close());
-
-test('accepts payload with a size larger than default but smaller than route config allows', async () => {
- await makeServer({ maxPayloadBytesDefault: 100, maxPayloadBytesRoute: 200 });
-
- const resp = await makeRequest({
- url: '/payload_size_check/test/route',
- method: 'POST',
- payload: { data: Array(150).fill('+').join('') },
- });
-
- expect(resp.statusCode).toBe(200);
- expect(resp.payload).toBe('+++++');
-});
-
-test('fails with 400 if payload size is larger than default and route config allows', async () => {
- await makeServer({ maxPayloadBytesDefault: 100, maxPayloadBytesRoute: 200 });
-
- const resp = await makeRequest({
- url: '/payload_size_check/test/route',
- method: 'POST',
- payload: { data: Array(250).fill('+').join('') },
- });
-
- expect(resp.statusCode).toBe(400);
- expect(resp.payload).toMatchSnapshot();
-});
diff --git a/src/server/http/setup_connection.js b/src/server/http/setup_connection.js
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/src/server/kbn_server.js b/src/server/kbn_server.js
index 7279a8f407b11..4f4334d764ddb 100644
--- a/src/server/kbn_server.js
+++ b/src/server/kbn_server.js
@@ -21,6 +21,7 @@ import { constant, once, compact, flatten } from 'lodash';
import { fromNode } from 'bluebird';
import { isWorker } from 'cluster';
import { fromRoot, pkg } from '../utils';
+import { Config } from './config';
import loggingConfiguration from './logging/configuration';
import configSetupMixin from './config/setup';
import httpMixin from './http';
@@ -30,6 +31,7 @@ import { usageMixin } from './usage';
import { statusMixin } from './status';
import pidMixin from './pid';
import { configDeprecationWarningsMixin } from './config/deprecation_warnings';
+import { transformDeprecations } from './config/transform_deprecations';
import configCompleteMixin from './config/complete';
import optimizeMixin from '../optimize';
import * as Plugins from './plugins';
@@ -41,27 +43,26 @@ import { urlShorteningMixin } from './url_shortening';
import { serverExtensionsMixin } from './server_extensions';
import { uiMixin } from '../ui';
import { sassMixin } from './sass';
-import { injectIntoKbnServer as newPlatformMixin } from '../core';
import { i18nMixin } from './i18n';
const rootDir = fromRoot('.');
export default class KbnServer {
- constructor(settings) {
+ constructor(settings, core) {
this.name = pkg.name;
this.version = pkg.version;
this.build = pkg.build || false;
this.rootDir = rootDir;
this.settings = settings || {};
+ this.core = core;
+
this.ready = constant(this.mixin(
Plugins.waitForInitSetupMixin,
// sets this.config, reads this.settings
configSetupMixin,
- newPlatformMixin,
-
// sets this.server
httpMixin,
@@ -111,13 +112,6 @@ export default class KbnServer {
// notify any deferred setup logic that plugins have initialized
Plugins.waitForInitResolveMixin,
-
- () => {
- if (this.config.get('server.autoListen')) {
- this.ready = constant(Promise.resolve());
- return this.listen();
- }
- }
));
this.listen = once(this.listen);
@@ -148,14 +142,17 @@ export default class KbnServer {
async listen() {
await this.ready();
- const { server } = this;
- await fromNode(cb => server.start(cb));
-
if (isWorker) {
// help parent process know when we are ready
process.send(['WORKER_LISTENING']);
}
+ const { server, config } = this;
+ server.log(['listening', 'info'], `Server running at ${server.info.uri}${
+ config.get('server.rewriteBasePath')
+ ? config.get('server.basePath')
+ : ''
+ }`);
return server;
}
@@ -171,7 +168,12 @@ export default class KbnServer {
return await this.server.inject(opts);
}
- async applyLoggingConfiguration(config) {
+ applyLoggingConfiguration(settings) {
+ const config = new Config(
+ this.config.getSchema(),
+ transformDeprecations(settings)
+ );
+
const loggingOptions = loggingConfiguration(config);
const subset = {
ops: config.get('ops'),
diff --git a/src/test_utils/base_auth.js b/src/test_utils/base_auth.js
deleted file mode 100644
index 270ed7563e7c1..0000000000000
--- a/src/test_utils/base_auth.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-export function header(user, pass) {
- const encoded = new Buffer(`${user}:${pass}`).toString('base64');
- return `Basic ${encoded}`;
-}
diff --git a/src/test_utils/kbn_server.js b/src/test_utils/kbn_server.js
deleted file mode 100644
index a0c802ce052e5..0000000000000
--- a/src/test_utils/kbn_server.js
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { resolve } from 'path';
-import { defaultsDeep, set } from 'lodash';
-import { header as basicAuthHeader } from './base_auth';
-import { createEsTestCluster, esTestConfig, kibanaTestUser, kibanaServerTestUser } from '@kbn/test';
-import KbnServer from '../../src/server/kbn_server';
-import { ToolingLog } from '@kbn/dev-utils';
-
-const DEFAULTS_SETTINGS = {
- server: {
- autoListen: true,
- // Use the ephemeral port to make sure that tests use the first available
- // port and aren't affected by the timing issues in test environment.
- port: 0,
- xsrf: {
- disableProtection: true
- }
- },
- logging: {
- quiet: true
- },
- plugins: {},
- optimize: {
- enabled: false
- },
-};
-
-const DEFAULT_SETTINGS_WITH_CORE_PLUGINS = {
- plugins: {
- scanDirs: [
- resolve(__dirname, '../core_plugins'),
- ],
- },
- elasticsearch: {
- url: esTestConfig.getUrl(),
- username: kibanaServerTestUser.username,
- password: kibanaServerTestUser.password
- },
-};
-
-/**
- * Creates an instance of KbnServer with default configuration
- * tailored for unit tests
- *
- * @param {Object} [settings={}] Any config overrides for this instance
- * @return {KbnServer}
- */
-export function createServer(settings = {}) {
- return new KbnServer(defaultsDeep({}, settings, DEFAULTS_SETTINGS));
-}
-
-/**
- * Creates an instance of KbnServer, including all of the core plugins,
- * with default configuration tailored for unit tests, and starts es.
- *
- * @param {Object} options
- * @prop {Object} settings Any config overrides for this instance
- * @prop {function} adjustTimeout A function(t) => this.timeout(t) that adjust the timeout of a test,
- * ensuring the test properly waits for the server to boot without timing out.
- * @return {KbnServer}
- */
-export async function startTestServers({ adjustTimeout, settings = {} }) {
- if (!adjustTimeout) {
- throw new Error('adjustTimeout is required in order to avoid flaky tests');
- }
-
- const log = new ToolingLog({
- level: 'debug',
- writeTo: process.stdout
- });
-
- log.indent(6);
- log.info('starting elasticsearch');
- log.indent(4);
-
- const es = createEsTestCluster({ log });
-
- log.indent(-4);
-
- adjustTimeout(es.getStartTimeout());
-
- await es.start();
-
- const kbnServer = createServerWithCorePlugins(settings);
-
- await kbnServer.ready();
- await kbnServer.server.plugins.elasticsearch.waitUntilReady();
-
- return {
- kbnServer,
- es,
-
- async stop() {
- await this.kbnServer.close();
- await es.cleanup();
- },
- };
-}
-
-/**
- * Creates an instance of KbnServer, including all of the core plugins,
- * with default configuration tailored for unit tests
- *
- * @param {Object} [settings={}] Any config overrides for this instance
- * @return {KbnServer}
- */
-export function createServerWithCorePlugins(settings = {}) {
- return new KbnServer(defaultsDeep({}, settings, DEFAULT_SETTINGS_WITH_CORE_PLUGINS, DEFAULTS_SETTINGS));
-}
-
-/**
- * Creates request configuration with a basic auth header
- */
-export function authOptions() {
- const { username, password } = kibanaTestUser;
- const authHeader = basicAuthHeader(username, password);
- return set({}, 'headers.Authorization', authHeader);
-}
-
-/**
- * Makes a request with test headers via hapi server inject()
- *
- * The given options are decorated with default testing options, so it's
- * recommended to use this function instead of using inject() directly whenever
- * possible throughout the tests.
- *
- * @param {KbnServer} kbnServer
- * @param {object} options Any additional options or overrides for inject()
- */
-export async function makeRequest(kbnServer, options) {
- // Since all requests to Kibana hit core http server first and only after that
- // are proxied to the "legacy" Kibana we should inject requests through the top
- // level Hapi server used by the core.
- return await kbnServer.newPlatform.proxyListener.root.server.http.service.httpServer.server.inject(
- defaultsDeep({}, authOptions(), options)
- );
-}
diff --git a/src/test_utils/kbn_server.ts b/src/test_utils/kbn_server.ts
new file mode 100644
index 0000000000000..3b841c2b6ac02
--- /dev/null
+++ b/src/test_utils/kbn_server.ts
@@ -0,0 +1,184 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { ToolingLog } from '@kbn/dev-utils';
+// @ts-ignore: implicit any for JS file
+import { createEsTestCluster, esTestConfig, kibanaServerTestUser, kibanaTestUser } from '@kbn/test';
+import { defaultsDeep } from 'lodash';
+import { resolve } from 'path';
+import { BehaviorSubject } from 'rxjs';
+import supertest from 'supertest';
+import { Env } from '../core/server/config';
+import { LegacyObjectToConfigAdapter } from '../core/server/legacy_compat';
+import { Root } from '../core/server/root';
+
+type HttpMethod = 'delete' | 'get' | 'head' | 'post' | 'put';
+
+const DEFAULTS_SETTINGS = {
+ server: {
+ autoListen: true,
+ // Use the ephemeral port to make sure that tests use the first available
+ // port and aren't affected by the timing issues in test environment.
+ port: 0,
+ xsrf: { disableProtection: true },
+ },
+ logging: { silent: true },
+ plugins: {},
+ optimize: { enabled: false },
+};
+
+const DEFAULT_SETTINGS_WITH_CORE_PLUGINS = {
+ plugins: { scanDirs: [resolve(__dirname, '../core_plugins')] },
+ elasticsearch: {
+ url: esTestConfig.getUrl(),
+ username: kibanaServerTestUser.username,
+ password: kibanaServerTestUser.password,
+ },
+};
+
+export function createRootWithSettings(...settings: Array>) {
+ const env = Env.createDefault({
+ configs: [],
+ cliArgs: {
+ dev: false,
+ quiet: false,
+ silent: false,
+ watch: false,
+ repl: false,
+ basePath: false,
+ },
+ isDevClusterMaster: false,
+ });
+
+ return new Root(
+ new BehaviorSubject(
+ new LegacyObjectToConfigAdapter(defaultsDeep({}, ...settings, DEFAULTS_SETTINGS))
+ ),
+ env
+ );
+}
+
+/**
+ * Returns supertest request attached to the core's internal native Node server.
+ * @param root
+ * @param method
+ * @param path
+ */
+function getSupertest(root: Root, method: HttpMethod, path: string) {
+ const testUserCredentials = new Buffer(`${kibanaTestUser.username}:${kibanaTestUser.password}`);
+ return supertest((root as any).server.http.service.httpServer.server.listener)
+ [method](path)
+ .set('Authorization', `Basic ${testUserCredentials.toString('base64')}`);
+}
+
+/**
+ * Creates an instance of Root with default configuration
+ * tailored for unit tests.
+ *
+ * @param {Object} [settings={}] Any config overrides for this instance.
+ * @returns {Root}
+ */
+export function createRoot(settings = {}) {
+ return createRootWithSettings(settings);
+}
+
+/**
+ * Creates an instance of Root, including all of the core plugins,
+ * with default configuration tailored for unit tests.
+ *
+ * @param {Object} [settings={}] Any config overrides for this instance.
+ * @returns {Root}
+ */
+export function createRootWithCorePlugins(settings = {}) {
+ return createRootWithSettings(settings, DEFAULT_SETTINGS_WITH_CORE_PLUGINS);
+}
+
+/**
+ * Returns `kbnServer` instance used in the "legacy" Kibana.
+ * @param root
+ */
+export function getKbnServer(root: Root) {
+ return (root as any).server.legacy.service.kbnServer;
+}
+
+export const request: Record<
+ HttpMethod,
+ (root: Root, path: string) => ReturnType
+> = {
+ delete: (root, path) => getSupertest(root, 'delete', path),
+ get: (root, path) => getSupertest(root, 'get', path),
+ head: (root, path) => getSupertest(root, 'head', path),
+ post: (root, path) => getSupertest(root, 'post', path),
+ put: (root, path) => getSupertest(root, 'put', path),
+};
+
+/**
+ * Creates an instance of the Root, including all of the core "legacy" plugins,
+ * with default configuration tailored for unit tests, and starts es.
+ *
+ * @param options
+ * @prop settings Any config overrides for this instance.
+ * @prop adjustTimeout A function(t) => this.timeout(t) that adjust the timeout of a
+ * test, ensuring the test properly waits for the server to boot without timing out.
+ */
+export async function startTestServers({
+ adjustTimeout,
+ settings = {},
+}: {
+ adjustTimeout: (timeout: number) => void;
+ settings: Record;
+}) {
+ if (!adjustTimeout) {
+ throw new Error('adjustTimeout is required in order to avoid flaky tests');
+ }
+
+ const log = new ToolingLog({
+ level: 'debug',
+ writeTo: process.stdout,
+ });
+
+ log.indent(6);
+ log.info('starting elasticsearch');
+ log.indent(4);
+
+ const es = createEsTestCluster({ log });
+
+ log.indent(-4);
+
+ adjustTimeout(es.getStartTimeout());
+
+ await es.start();
+
+ const root = createRootWithCorePlugins(settings);
+ await root.start();
+
+ const kbnServer = getKbnServer(root);
+ await kbnServer.server.plugins.elasticsearch.waitUntilReady();
+
+ return {
+ kbnServer,
+ root,
+ es,
+
+ async stop() {
+ await root.shutdown();
+ await es.cleanup();
+ },
+ };
+}
diff --git a/src/ui/__tests__/ui_exports_replace_injected_vars.js b/src/ui/__tests__/ui_exports_replace_injected_vars.js
index b7762ef104b90..5cb05ac1dbeeb 100644
--- a/src/ui/__tests__/ui_exports_replace_injected_vars.js
+++ b/src/ui/__tests__/ui_exports_replace_injected_vars.js
@@ -25,10 +25,10 @@ import sinon from 'sinon';
import cheerio from 'cheerio';
import { noop } from 'lodash';
-import KbnServer from '../../server/kbn_server';
+import { createRoot, getKbnServer, request } from '../../test_utils/kbn_server';
const getInjectedVarsFromResponse = (resp) => {
- const $ = cheerio.load(resp.payload);
+ const $ = cheerio.load(resp.text);
const data = $('kbn-injected-metadata').attr('data');
return JSON.parse(data).legacyMetadata.vars;
};
@@ -45,45 +45,46 @@ const injectReplacer = (kbnServer, replacer) => {
};
describe('UiExports', function () {
- describe('#replaceInjectedVars', function () {
+ let root;
+ let kbnServer;
+ before(async () => {
this.slow(2000);
- this.timeout(10000);
-
- let kbnServer;
- beforeEach(async () => {
- kbnServer = new KbnServer({
- server: { port: 0 }, // pick a random open port
- logging: { silent: true }, // no logs
- optimize: { enabled: false },
- plugins: {
- paths: [resolve(__dirname, './fixtures/test_app')] // inject an app so we can hit /app/{id}
- },
- });
+ this.timeout(30000);
- await kbnServer.ready();
-
- // TODO: hopefully we can add better support for something
- // like this in the new platform
- kbnServer.server._requestor._decorations.getUiSettingsService = {
- apply: undefined,
- method() {
- return {
- getDefaults: noop,
- getUserProvided: noop
- };
- }
- };
+ root = root = createRoot({
+ // inject an app so we can hit /app/{id}
+ plugins: { paths: [resolve(__dirname, './fixtures/test_app')] },
});
- afterEach(async () => {
- await kbnServer.close();
- kbnServer = null;
- });
+ await root.start();
+
+ kbnServer = getKbnServer(root);
+
+ // TODO: hopefully we can add better support for something
+ // like this in the new platform
+ kbnServer.server._requestor._decorations.getUiSettingsService = {
+ apply: undefined,
+ method: () => ({ getDefaults: noop, getUserProvided: noop })
+ };
+ });
+
+ after(async () => await root.shutdown());
+ let originalInjectedVarsReplacers;
+ beforeEach(() => {
+ originalInjectedVarsReplacers = kbnServer.uiExports.injectedVarsReplacers;
+ });
+
+ afterEach(() => {
+ kbnServer.uiExports.injectedVarsReplacers = originalInjectedVarsReplacers;
+ });
+
+ describe('#replaceInjectedVars', function () {
it('allows sync replacing of injected vars', async () => {
injectReplacer(kbnServer, () => ({ a: 1 }));
- const resp = await kbnServer.inject('/app/test_app');
+ const resp = await request.get(root, '/app/test_app')
+ .expect(200);
const injectedVars = getInjectedVarsFromResponse(resp);
expect(injectedVars).to.eql({ a: 1 });
@@ -98,7 +99,8 @@ describe('UiExports', function () {
};
});
- const resp = await kbnServer.inject('/app/test_app');
+ const resp = await request.get(root, '/app/test_app')
+ .expect(200);
const injectedVars = getInjectedVarsFromResponse(resp);
expect(injectedVars).to.eql({
@@ -111,7 +113,8 @@ describe('UiExports', function () {
injectReplacer(kbnServer, () => ({ foo: 'bar' }));
injectReplacer(kbnServer, stub);
- await kbnServer.inject('/app/test_app');
+ await await request.get(root, '/app/test_app')
+ .expect(200);
sinon.assert.calledOnce(stub);
expect(stub.firstCall.args[0]).to.eql({ foo: 'bar' }); // originalInjectedVars
@@ -126,7 +129,8 @@ describe('UiExports', function () {
injectReplacer(kbnServer, orig => ({ name: orig.name + 'a' }));
injectReplacer(kbnServer, orig => ({ name: orig.name + 'm' }));
- const resp = await kbnServer.inject('/app/test_app');
+ const resp = await request.get(root, '/app/test_app')
+ .expect(200);
const injectedVars = getInjectedVarsFromResponse(resp);
expect(injectedVars).to.eql({ name: 'sam' });
@@ -138,15 +142,17 @@ describe('UiExports', function () {
throw new Error('replacer failed');
});
- const resp = await kbnServer.inject('/app/test_app');
- expect(resp).to.have.property('statusCode', 500);
+ await request.get(root, '/app/test_app')
+ .expect(500);
});
it('starts off with the injected vars for the app merged with the default injected vars', async () => {
const stub = sinon.stub();
injectReplacer(kbnServer, stub);
- await kbnServer.inject('/app/test_app');
+ await request.get(root, '/app/test_app')
+ .expect(200);
+
sinon.assert.calledOnce(stub);
expect(stub.firstCall.args[0]).to.eql({ from_defaults: true, from_test_app: true });
});
diff --git a/src/ui/field_formats/__tests__/field_formats_mixin.js b/src/ui/field_formats/__tests__/field_formats_mixin.js
index 58c61962c2414..3159705f3d8ed 100644
--- a/src/ui/field_formats/__tests__/field_formats_mixin.js
+++ b/src/ui/field_formats/__tests__/field_formats_mixin.js
@@ -22,31 +22,31 @@ import sinon from 'sinon';
import { FieldFormat } from '../field_format';
import * as FieldFormatsServiceNS from '../field_formats_service';
-import { createServer } from '../../../test_utils/kbn_server';
+import { fieldFormatsMixin } from '../field_formats_mixin';
describe('server.registerFieldFormat(createFormat)', () => {
const sandbox = sinon.createSandbox();
- let kbnServer;
+ let registerFieldFormat;
+ let fieldFormatServiceFactory;
+ const serverMock = { decorate() {} };
beforeEach(async () => {
- kbnServer = createServer();
- await kbnServer.ready();
+ sandbox.stub(serverMock);
+ await fieldFormatsMixin({}, serverMock);
+ [[,, fieldFormatServiceFactory], [,, registerFieldFormat]] = serverMock.decorate.args;
});
- afterEach(async () => {
- sandbox.restore();
- await kbnServer.close();
- });
+ afterEach(() => sandbox.restore());
it('throws if createFormat is not a function', () => {
- expect(() => kbnServer.server.registerFieldFormat()).to.throwError(error => {
+ expect(() => registerFieldFormat()).to.throwError(error => {
expect(error.message).to.match(/createFormat is not a function/i);
});
});
it('calls the createFormat() function with the FieldFormat class', () => {
const createFormat = sinon.stub();
- kbnServer.server.registerFieldFormat(createFormat);
+ registerFieldFormat(createFormat);
sinon.assert.calledOnce(createFormat);
sinon.assert.calledWithExactly(createFormat, sinon.match.same(FieldFormat));
});
@@ -61,9 +61,9 @@ describe('server.registerFieldFormat(createFormat)', () => {
class FooFormat {
static id = 'foo'
}
- kbnServer.server.registerFieldFormat(() => FooFormat);
+ registerFieldFormat(() => FooFormat);
- const fieldFormats = await kbnServer.server.fieldFormatServiceFactory({
+ const fieldFormats = await fieldFormatServiceFactory({
getAll: () => ({}),
getDefaults: () => ({})
});
diff --git a/src/ui/tutorials_mixin.test.js b/src/ui/tutorials_mixin.test.js
index b29ff10a4d798..6d0bc46aeb714 100644
--- a/src/ui/tutorials_mixin.test.js
+++ b/src/ui/tutorials_mixin.test.js
@@ -17,7 +17,7 @@
* under the License.
*/
-import { createServer } from '../test_utils/kbn_server';
+import { tutorialsMixin } from './tutorials_mixin';
const validTutorial = {
id: 'spec1',
@@ -42,15 +42,22 @@ const validTutorial = {
};
describe('tutorial mixins', () => {
-
- let kbnServer;
+ let getTutorials;
+ let registerTutorial;
+ let addScopedTutorialContextFactory;
+ const serverMock = { decorate: jest.fn() };
beforeEach(async () => {
- kbnServer = createServer();
- await kbnServer.ready();
+ await tutorialsMixin({}, serverMock);
+
+ [
+ [,, getTutorials],
+ [,, registerTutorial],
+ [,, addScopedTutorialContextFactory]
+ ] = serverMock.decorate.mock.calls;
});
- afterEach(async () => {
- await kbnServer.close();
+ afterEach(() => {
+ jest.clearAllMocks();
});
describe('scoped context', () => {
@@ -70,12 +77,12 @@ describe('tutorial mixins', () => {
return tutorial;
};
beforeEach(async () => {
- kbnServer.server.addScopedTutorialContextFactory(spacesContextFactory);
- kbnServer.server.registerTutorial(specProvider);
+ addScopedTutorialContextFactory(spacesContextFactory);
+ registerTutorial(specProvider);
});
test('passes scoped context to specProviders', () => {
- const tutorials = kbnServer.server.getTutorials(mockRequest);
+ const tutorials = getTutorials(mockRequest);
expect(tutorials.length).toBe(1);
expect(tutorials[0].shortDescription).toBe('I have been provided with scoped context, spaceId: my-space');
});
diff --git a/x-pack/package.json b/x-pack/package.json
index 17cef7c7d79ba..c2bd527701fed 100644
--- a/x-pack/package.json
+++ b/x-pack/package.json
@@ -69,8 +69,8 @@
"run-sequence": "^2.2.1",
"simple-git": "1.37.0",
"sinon": "^5.0.7",
- "supertest": "3.0.0",
- "supertest-as-promised": "4.0.2",
+ "supertest": "^3.1.0",
+ "supertest-as-promised": "^4.0.2",
"tmp": "0.0.31",
"tree-kill": "^1.1.0",
"typescript": "^2.9.2",
diff --git a/x-pack/yarn.lock b/x-pack/yarn.lock
index 41adacfd61a70..7c73f4699a7ec 100644
--- a/x-pack/yarn.lock
+++ b/x-pack/yarn.lock
@@ -7479,7 +7479,7 @@ subtext@4.x.x:
pez "2.x.x"
wreck "12.x.x"
-superagent@^3.0.0:
+superagent@3.8.2:
version "3.8.2"
resolved "https://registry.yarnpkg.com/superagent/-/superagent-3.8.2.tgz#e4a11b9d047f7d3efeb3bbe536d9ec0021d16403"
dependencies:
@@ -7494,19 +7494,19 @@ superagent@^3.0.0:
qs "^6.5.1"
readable-stream "^2.0.5"
-supertest-as-promised@4.0.2:
+supertest-as-promised@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/supertest-as-promised/-/supertest-as-promised-4.0.2.tgz#0464f2bd256568d4a59bce84269c0548f6879f1a"
dependencies:
bluebird "^3.3.1"
methods "^1.1.1"
-supertest@3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/supertest/-/supertest-3.0.0.tgz#8d4bb68fd1830ee07033b1c5a5a9a4021c965296"
+supertest@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/supertest/-/supertest-3.1.0.tgz#f9ebaf488e60f2176021ec580bdd23ad269e7bc6"
dependencies:
methods "~1.1.2"
- superagent "^3.0.0"
+ superagent "3.8.2"
supports-color@1.2.0:
version "1.2.0"
diff --git a/yarn.lock b/yarn.lock
index 25c1cab071a68..8c22fee2635e7 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -537,9 +537,9 @@
"@types/cookiejar" "*"
"@types/node" "*"
-"@types/supertest@^2.0.4":
- version "2.0.4"
- resolved "https://registry.yarnpkg.com/@types/supertest/-/supertest-2.0.4.tgz#28770e13293365e240a842d7d5c5a1b3d2dee593"
+"@types/supertest@^2.0.5":
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/@types/supertest/-/supertest-2.0.5.tgz#18d082a667eaed22759be98f4923e0061ae70c62"
dependencies:
"@types/superagent" "*"
@@ -12703,7 +12703,7 @@ suffix@^0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/suffix/-/suffix-0.1.1.tgz#cc58231646a0ef1102f79478ef3a9248fd9c842f"
-superagent@^3.0.0:
+superagent@3.8.2:
version "3.8.2"
resolved "https://registry.yarnpkg.com/superagent/-/superagent-3.8.2.tgz#e4a11b9d047f7d3efeb3bbe536d9ec0021d16403"
dependencies:
@@ -12718,19 +12718,19 @@ superagent@^3.0.0:
qs "^6.5.1"
readable-stream "^2.0.5"
-supertest-as-promised@4.0.2:
+supertest-as-promised@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/supertest-as-promised/-/supertest-as-promised-4.0.2.tgz#0464f2bd256568d4a59bce84269c0548f6879f1a"
dependencies:
bluebird "^3.3.1"
methods "^1.1.1"
-supertest@3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/supertest/-/supertest-3.0.0.tgz#8d4bb68fd1830ee07033b1c5a5a9a4021c965296"
+supertest@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/supertest/-/supertest-3.1.0.tgz#f9ebaf488e60f2176021ec580bdd23ad269e7bc6"
dependencies:
methods "~1.1.2"
- superagent "^3.0.0"
+ superagent "3.8.2"
supports-color@3.1.2:
version "3.1.2"
From 81096fda70d0f32a6dec1ce64b434c2e7a04872f Mon Sep 17 00:00:00 2001
From: Larry Gregory
Date: Thu, 6 Sep 2018 11:52:03 -0400
Subject: [PATCH 32/68] attempt to isolate test failure
---
scripts/functional_tests.js | 4 ++--
x-pack/scripts/functional_tests.js | 16 ++++++++--------
x-pack/test/functional/config.js | 13 +++++++------
3 files changed, 17 insertions(+), 16 deletions(-)
diff --git a/scripts/functional_tests.js b/scripts/functional_tests.js
index 9d73923704ce6..88b89f84bcd17 100644
--- a/scripts/functional_tests.js
+++ b/scripts/functional_tests.js
@@ -19,7 +19,7 @@
require('../src/setup_node_env');
require('@kbn/test').runTestsCli([
- require.resolve('../test/functional/config.js'),
+ // require.resolve('../test/functional/config.js'),
require.resolve('../test/api_integration/config.js'),
- require.resolve('../test/plugin_functional/config.js'),
+ // require.resolve('../test/plugin_functional/config.js'),
]);
diff --git a/x-pack/scripts/functional_tests.js b/x-pack/scripts/functional_tests.js
index 1b05203e4dd09..742582e9b48b6 100644
--- a/x-pack/scripts/functional_tests.js
+++ b/x-pack/scripts/functional_tests.js
@@ -6,13 +6,13 @@
require('@kbn/plugin-helpers').babelRegister();
require('@kbn/test').runTestsCli([
- require.resolve('../test/reporting/configs/chromium_api.js'),
- require.resolve('../test/reporting/configs/chromium_functional.js'),
- require.resolve('../test/reporting/configs/phantom_api.js'),
- require.resolve('../test/reporting/configs/phantom_functional.js'),
+ // require.resolve('../test/reporting/configs/chromium_api.js'),
+ // require.resolve('../test/reporting/configs/chromium_functional.js'),
+ // require.resolve('../test/reporting/configs/phantom_api.js'),
+ // require.resolve('../test/reporting/configs/phantom_functional.js'),
require.resolve('../test/functional/config.js'),
- require.resolve('../test/api_integration/config.js'),
- require.resolve('../test/saml_api_integration/config.js'),
- require.resolve('../test/rbac_api_integration/config.js'),
- require.resolve('../test/spaces_api_integration/config.js'),
+ // require.resolve('../test/api_integration/config.js'),
+ // require.resolve('../test/saml_api_integration/config.js'),
+ // require.resolve('../test/rbac_api_integration/config.js'),
+ // require.resolve('../test/spaces_api_integration/config.js'),
]);
diff --git a/x-pack/test/functional/config.js b/x-pack/test/functional/config.js
index 77c0aee49b83e..1d2010c7f38f0 100644
--- a/x-pack/test/functional/config.js
+++ b/x-pack/test/functional/config.js
@@ -59,13 +59,13 @@ export default async function ({ readConfigFile }) {
return {
// list paths to the files that contain your plugins tests
testFiles: [
- resolve(__dirname, './apps/graph'),
- resolve(__dirname, './apps/monitoring'),
- resolve(__dirname, './apps/watcher'),
+ // resolve(__dirname, './apps/graph'),
+ // resolve(__dirname, './apps/monitoring'),
+ // resolve(__dirname, './apps/watcher'),
resolve(__dirname, './apps/dashboard_mode'),
- resolve(__dirname, './apps/security'),
- resolve(__dirname, './apps/logstash'),
- resolve(__dirname, './apps/grok_debugger'),
+ // resolve(__dirname, './apps/security'),
+ // resolve(__dirname, './apps/logstash'),
+ // resolve(__dirname, './apps/grok_debugger'),
],
// define the name and providers for services that should be
@@ -133,6 +133,7 @@ export default async function ({ readConfigFile }) {
'--server.uuid=5b2de169-2785-441b-ae8c-186a1936b17d',
'--xpack.xpack_main.telemetry.enabled=false',
'--xpack.security.encryptionKey="wuGNaIhoMpk5sO4UBxgr3NyW1sFcLgIf"', // server restarts should not invalidate active sessions
+ '--logging.verbose=true',
],
},
From ec718838de85e6fbed3cc5c9de12c6e6a5427a70 Mon Sep 17 00:00:00 2001
From: Larry Gregory
Date: Thu, 6 Sep 2018 12:50:26 -0400
Subject: [PATCH 33/68] [Spaces] - copy edits (#22457)
---
.../edit_role/components/edit_role_page.tsx | 23 +++++++++----
.../index_privilege_form.test.tsx.snap | 2 +-
.../privileges/es/index_privilege_form.tsx | 2 +-
.../impacted_spaces_flyout.test.tsx.snap | 2 +-
.../privilege_space_form.test.tsx.snap | 2 +-
.../space_aware_privilege_form.test.tsx.snap | 26 +++++++++-----
.../space_selector.test.tsx.snap | 1 -
.../kibana/impacted_spaces_flyout.tsx | 4 +--
.../kibana/privilege_callout_warning.tsx | 6 +++-
.../kibana/privilege_space_form.tsx | 2 +-
.../kibana/privilege_space_table.tsx | 2 +-
.../kibana/space_aware_privilege_form.tsx | 34 +++++++------------
.../privileges/kibana/space_selector.tsx | 1 -
.../components/manage_spaces_button.tsx | 2 +-
x-pack/plugins/spaces/public/lib/constants.ts | 2 ++
.../plugins/spaces/public/register_feature.js | 3 +-
.../space_identifier.test.js.snap | 16 +++++++--
.../edit_space/manage_space_page.js | 4 +--
.../management/edit_space/space_identifier.js | 14 +++++---
.../views/management/lib/validate_space.js | 10 +++---
.../management/lib/validate_space.test.js | 14 ++++----
.../spaces_grid/spaces_grid_page.js | 8 +++--
.../spaces_description.test.tsx.snap | 2 +-
.../components/spaces_description.tsx | 6 ++--
.../__snapshots__/space_selector.test.js.snap | 2 +-
.../views/space_selector/space_selector.js | 2 +-
.../spaces/server/lib/create_default_space.js | 4 +--
.../server/lib/create_default_space.test.js | 2 +-
28 files changed, 116 insertions(+), 82 deletions(-)
diff --git a/x-pack/plugins/security/public/views/management/edit_role/components/edit_role_page.tsx b/x-pack/plugins/security/public/views/management/edit_role/components/edit_role_page.tsx
index d4497582a7511..ee1de5d99e191 100644
--- a/x-pack/plugins/security/public/views/management/edit_role/components/edit_role_page.tsx
+++ b/x-pack/plugins/security/public/views/management/edit_role/components/edit_role_page.tsx
@@ -20,7 +20,7 @@ import {
EuiTitle,
} from '@elastic/eui';
import { get } from 'lodash';
-import React, { ChangeEvent, Component, HTMLProps } from 'react';
+import React, { ChangeEvent, Component, Fragment, HTMLProps } from 'react';
import { toastNotifications } from 'ui/notify';
import { Space } from '../../../../../../spaces/common/model/space';
import { IndexPrivilege } from '../../../../../common/model/index_privilege';
@@ -66,18 +66,29 @@ export class EditRolePage extends Component {
}
public render() {
+ const description = this.props.spacesEnabled
+ ? `Set privileges on your Elasticsearch data and control access to your Kibana spaces.`
+ : `Set privileges on your Elasticsearch data and control access to Kibana.`;
+
return (
{this.getFormTitle()}
+
+
+ {description}
+
{isReservedRole(this.props.role) && (
-
-
- Reserved roles are built-in and cannot be removed or modified.
-
-
+
+
+
+
+ Reserved roles are built-in and cannot be removed or modified.
+
+
+
)}
diff --git a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/es/__snapshots__/index_privilege_form.test.tsx.snap b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/es/__snapshots__/index_privilege_form.test.tsx.snap
index d7426919a1f3b..c8a8f284b9bad 100644
--- a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/es/__snapshots__/index_privilege_form.test.tsx.snap
+++ b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/es/__snapshots__/index_privilege_form.test.tsx.snap
@@ -162,7 +162,7 @@ exports[`it renders without crashing 1`] = `
diff --git a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/es/index_privilege_form.tsx b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/es/index_privilege_form.tsx
index 4072d3b2f9008..ccf5bd42722e0 100644
--- a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/es/index_privilege_form.tsx
+++ b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/es/index_privilege_form.tsx
@@ -171,7 +171,7 @@ export class IndexPrivilegeForm extends Component {
renders without crashing 1`] = `
onClick={[Function]}
type="button"
>
- See summary of all spaces privileges
+ View summary of spaces privileges
diff --git a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/__snapshots__/privilege_space_form.test.tsx.snap b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/__snapshots__/privilege_space_form.test.tsx.snap
index afce653a9c7f4..ad9c209ec5255 100644
--- a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/__snapshots__/privilege_space_form.test.tsx.snap
+++ b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/__snapshots__/privilege_space_form.test.tsx.snap
@@ -19,7 +19,7 @@ exports[` renders without crashing 1`] = `
fullWidth={false}
hasEmptyLabelSpace={false}
isInvalid={false}
- label="Space(s)"
+ label="Spaces"
>
renders without crashing 1`] = `
- Specifies the lowest permission level for all spaces, unless a custom privilege is specified.
+ Specify the minimum actions users can perform in your spaces.
}
fullWidth={false}
gutterSize="l"
title={
- Minimum privilege
+ Minimum privileges for all spaces
}
titleSize="xs"
@@ -63,7 +63,7 @@ exports[` renders without crashing 1`] = `
describedByIds={Array []}
fullWidth={false}
hasEmptyLabelSpace={true}
- helpText="No access"
+ helpText="No access to spaces"
>
renders without crashing 1`] = `
size="xs"
>
- Space privileges
+ Higher privileges for individual spaces
renders without crashing 1`] = `
size="s"
>
- Customize permission levels per space. If a space is not customized, its permissions will default to the minimum privilege specified above.
-
-
- You can bulk-create space privileges though they will be saved individually upon saving the role.
+ Grant more privileges on a per space basis. For example, if the privileges are
+
+
+ read
+
+ for all spaces, you can set the privileges to
+
+ all
+
+
+ for an individual space.
+
{
- See summary of all spaces privileges
+ View summary of spaces privileges
{flyout}
@@ -105,7 +105,7 @@ export class ImpactedSpacesFlyout extends Component {
>
- Summary of all space privileges
+ Summary of space privileges
diff --git a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/privilege_callout_warning.tsx b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/privilege_callout_warning.tsx
index 6641f43074cbe..0b5666f391573 100644
--- a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/privilege_callout_warning.tsx
+++ b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/privilege_callout_warning.tsx
@@ -77,7 +77,11 @@ export class PrivilegeCalloutWarning extends Component {
+ The minimal possible privilege is read .
+
+ }
/>
);
}
diff --git a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/privilege_space_form.tsx b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/privilege_space_form.tsx
index 5cd139c0f42e3..cf7bcf8287b9d 100644
--- a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/privilege_space_form.tsx
+++ b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/privilege_space_form.tsx
@@ -41,7 +41,7 @@ export class PrivilegeSpaceForm extends Component {
{
search={{
box: {
incremental: true,
- placeholder: 'Filter...',
+ placeholder: 'Filter',
},
onChange: (search: any) => {
this.setState({
diff --git a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_form.tsx b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_form.tsx
index af76691b060f5..060f89726bb72 100644
--- a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_form.tsx
+++ b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_form.tsx
@@ -79,25 +79,23 @@ export class SpaceAwarePrivilegeForm extends Component {
const basePrivilege =
assignedPrivileges.global.length > 0 ? assignedPrivileges.global[0] : NO_PRIVILEGE_VALUE;
- const description = (
-
- Specifies the lowest permission level for all spaces, unless a custom privilege is
- specified.
-
- );
+ const description = Specify the minimum actions users can perform in your spaces.
;
let helptext;
if (basePrivilege === NO_PRIVILEGE_VALUE) {
- helptext = 'No access';
+ helptext = 'No access to spaces';
} else if (basePrivilege === 'all') {
- helptext = 'View, edit, and share all objects and apps within all spaces';
+ helptext = 'View, edit, and share objects and apps within all spaces';
} else if (basePrivilege === 'read') {
- helptext = 'View only mode';
+ helptext = 'View objects and apps within all spaces';
}
return (
- Minimum privilege} description={description}>
+ Minimum privileges for all spaces}
+ description={description}
+ >
{
return (
- Space privileges
+ Higher privileges for individual spaces
{
color={'subdued'}
>
- Customize permission levels per space. If a space is not customized, its permissions
- will default to the minimum privilege specified above.
+ Grant more privileges on a per space basis. For example, if the privileges are{' '}
+ read for all spaces, you can set the privileges to all {' '}
+ for an individual space.
- {basePrivilege !== 'all' &&
- this.props.editable && (
-
- You can bulk-create space privileges though they will be saved individually upon
- saving the role.
-
- )}
-
+
{(basePrivilege !== NO_PRIVILEGE_VALUE || isReservedRole(this.props.role)) && (
{
return (
{
onClick={this.navigateToManageSpaces}
style={this.props.style}
>
- Manage Spaces
+ Manage spaces
);
}
diff --git a/x-pack/plugins/spaces/public/lib/constants.ts b/x-pack/plugins/spaces/public/lib/constants.ts
index 59ac4b3aa64c3..93f21f0e46629 100644
--- a/x-pack/plugins/spaces/public/lib/constants.ts
+++ b/x-pack/plugins/spaces/public/lib/constants.ts
@@ -6,4 +6,6 @@
import chrome from 'ui/chrome';
+export const SPACES_FEATURE_DESCRIPTION = `Organize your dashboards and other saved objects into meaningful categories.`;
+
export const MANAGE_SPACES_URL = chrome.addBasePath(`/app/kibana#/management/spaces/list`);
diff --git a/x-pack/plugins/spaces/public/register_feature.js b/x-pack/plugins/spaces/public/register_feature.js
index 09b896c9caa35..1a02313aa331d 100644
--- a/x-pack/plugins/spaces/public/register_feature.js
+++ b/x-pack/plugins/spaces/public/register_feature.js
@@ -7,12 +7,13 @@
import { FeatureCatalogueRegistryProvider, FeatureCatalogueCategory } from 'ui/registry/feature_catalogue';
+import { SPACES_FEATURE_DESCRIPTION } from './lib/constants';
FeatureCatalogueRegistryProvider.register(() => {
return {
id: 'spaces',
title: 'Spaces',
- description: 'Organize your dashboards, visualizations, and other saved objects',
+ description: SPACES_FEATURE_DESCRIPTION,
icon: 'spacesApp',
path: '/app/kibana#/management/spaces/list',
showOnHomePage: true,
diff --git a/x-pack/plugins/spaces/public/views/management/edit_space/__snapshots__/space_identifier.test.js.snap b/x-pack/plugins/spaces/public/views/management/edit_space/__snapshots__/space_identifier.test.js.snap
index db250113e044d..a88906a10237e 100644
--- a/x-pack/plugins/spaces/public/views/management/edit_space/__snapshots__/space_identifier.test.js.snap
+++ b/x-pack/plugins/spaces/public/views/management/edit_space/__snapshots__/space_identifier.test.js.snap
@@ -8,13 +8,23 @@ exports[`renders without crashing 1`] = `
hasEmptyLabelSpace={false}
helpText={
- Links within Kibana will include this space identifier
+ If the identifier is
+
+ engineering
+
+ , the Kibana URL is
+
+ https://my-kibana.example
+
+ /s/engineering/
+
+ app/kibana.
}
isInvalid={false}
label={
- Space Identifier
+ URL identifier
diff --git a/x-pack/plugins/spaces/public/views/management/edit_space/manage_space_page.js b/x-pack/plugins/spaces/public/views/management/edit_space/manage_space_page.js
index 606c7d9ad1c7a..a6afad557cc3e 100644
--- a/x-pack/plugins/spaces/public/views/management/edit_space/manage_space_page.js
+++ b/x-pack/plugins/spaces/public/views/management/edit_space/manage_space_page.js
@@ -311,9 +311,9 @@ export class ManageSpacePage extends Component {
}
action
- .then(result => {
+ .then(() => {
this.props.spacesNavState.refreshSpacesList();
- toastNotifications.addSuccess(`Saved '${result.data.name}'`);
+ toastNotifications.addSuccess(`'${name}' was saved`);
window.location.hash = `#/management/spaces/list`;
})
.catch(error => {
diff --git a/x-pack/plugins/spaces/public/views/management/edit_space/space_identifier.js b/x-pack/plugins/spaces/public/views/management/edit_space/space_identifier.js
index 9fcd7460114b0..4af9003678db1 100644
--- a/x-pack/plugins/spaces/public/views/management/edit_space/space_identifier.js
+++ b/x-pack/plugins/spaces/public/views/management/edit_space/space_identifier.js
@@ -29,11 +29,15 @@ export class SpaceIdentifier extends Component {
this.textFieldRef = ref}
@@ -45,15 +49,15 @@ export class SpaceIdentifier extends Component {
getLabel = () => {
if (!this.props.editable) {
- return (Space Identifier
);
+ return (URL identifier
);
}
const editLinkText = this.state.editing ? `[stop editing]` : `[edit]`;
- return (Space Identifier {editLinkText}
);
+ return (URL identifier {editLinkText}
);
};
getHelpText = () => {
- return (Links within Kibana will include this space identifier
);
+ return (If the identifier is engineering , the Kibana URL is https://my-kibana.example/s/engineering/ app/kibana.
);
};
onEditClick = () => {
diff --git a/x-pack/plugins/spaces/public/views/management/lib/validate_space.js b/x-pack/plugins/spaces/public/views/management/lib/validate_space.js
index 56d22ca39aad5..1aa98aac8e579 100644
--- a/x-pack/plugins/spaces/public/views/management/lib/validate_space.js
+++ b/x-pack/plugins/spaces/public/views/management/lib/validate_space.js
@@ -23,7 +23,7 @@ export class SpaceValidator {
if (!this._shouldValidate) return valid();
if (!space.name) {
- return invalid(`Please provide a space name`);
+ return invalid(`Name is required`);
}
if (space.name.length > 1024) {
@@ -43,17 +43,17 @@ export class SpaceValidator {
return valid();
}
- validateSpaceIdentifier(space) {
+ validateURLIdentifier(space) {
if (!this._shouldValidate) return valid();
if (isReservedSpace(space)) return valid();
if (!space.id) {
- return invalid(`Space Identifier is required`);
+ return invalid(`URL identifier is required`);
}
if (!isValidSpaceIdentifier(space.id)) {
- return invalid('Space Identifier only allows a-z, 0-9, "_", and the "-" character');
+ return invalid('URL identifier can only contain a-z, 0-9, and the characters "_" and "-"');
}
return valid();
@@ -62,7 +62,7 @@ export class SpaceValidator {
validateForSave(space) {
const { isInvalid: isNameInvalid } = this.validateSpaceName(space);
const { isInvalid: isDescriptionInvalid } = this.validateSpaceDescription(space);
- const { isInvalid: isIdentifierInvalid } = this.validateSpaceIdentifier(space);
+ const { isInvalid: isIdentifierInvalid } = this.validateURLIdentifier(space);
if (isNameInvalid || isDescriptionInvalid || isIdentifierInvalid) {
return invalid();
diff --git a/x-pack/plugins/spaces/public/views/management/lib/validate_space.test.js b/x-pack/plugins/spaces/public/views/management/lib/validate_space.test.js
index 5adbb2ec5090e..dac3e417a371c 100644
--- a/x-pack/plugins/spaces/public/views/management/lib/validate_space.test.js
+++ b/x-pack/plugins/spaces/public/views/management/lib/validate_space.test.js
@@ -26,7 +26,7 @@ describe('validateSpaceName', () => {
name: ''
};
- expect(validator.validateSpaceName(space)).toEqual({ isInvalid: true, error: `Please provide a space name` });
+ expect(validator.validateSpaceName(space)).toEqual({ isInvalid: true, error: `Name is required` });
});
test('it cannot exceed 1024 characters', () => {
@@ -55,13 +55,13 @@ describe('validateSpaceDescription', () => {
});
});
-describe('validateSpaceIdentifier', () => {
+describe('validateURLIdentifier', () => {
test('it does not validate reserved spaces', () => {
const space = {
_reserved: true
};
- expect(validator.validateSpaceIdentifier(space)).toEqual({ isInvalid: false });
+ expect(validator.validateURLIdentifier(space)).toEqual({ isInvalid: false });
});
test('it requires a non-empty value', () => {
@@ -69,7 +69,7 @@ describe('validateSpaceIdentifier', () => {
id: ''
};
- expect(validator.validateSpaceIdentifier(space)).toEqual({ isInvalid: true, error: `Space Identifier is required` });
+ expect(validator.validateURLIdentifier(space)).toEqual({ isInvalid: true, error: `URL identifier is required` });
});
test('it requires a valid Space Identifier', () => {
@@ -77,8 +77,8 @@ describe('validateSpaceIdentifier', () => {
id: 'invalid identifier'
};
- expect(validator.validateSpaceIdentifier(space))
- .toEqual({ isInvalid: true, error: 'Space Identifier only allows a-z, 0-9, "_", and the "-" character' });
+ expect(validator.validateURLIdentifier(space))
+ .toEqual({ isInvalid: true, error: 'URL identifier can only contain a-z, 0-9, and the characters "_" and "-"' });
});
test('it allows a valid Space Identifier', () => {
@@ -86,6 +86,6 @@ describe('validateSpaceIdentifier', () => {
id: '01-valid-context-01'
};
- expect(validator.validateSpaceIdentifier(space)).toEqual({ isInvalid: false });
+ expect(validator.validateURLIdentifier(space)).toEqual({ isInvalid: false });
});
});
diff --git a/x-pack/plugins/spaces/public/views/management/spaces_grid/spaces_grid_page.js b/x-pack/plugins/spaces/public/views/management/spaces_grid/spaces_grid_page.js
index 8a5c3ecd9ae81..93e2dbe722bf7 100644
--- a/x-pack/plugins/spaces/public/views/management/spaces_grid/spaces_grid_page.js
+++ b/x-pack/plugins/spaces/public/views/management/spaces_grid/spaces_grid_page.js
@@ -54,7 +54,11 @@ export class SpacesGridPage extends Component {
onSelectionChange: this.onSelectionChange
}}
pagination={true}
- search={true}
+ search={{
+ box: {
+ placeholder: 'Search'
+ }
+ }}
loading={this.state.loading}
message={this.state.loading ? "loading..." : undefined}
/>
@@ -76,7 +80,7 @@ export class SpacesGridPage extends Component {
}
return (
- { window.location.hash = `#/management/spaces/create`; }}>Create new space
+ { window.location.hash = `#/management/spaces/create`; }}>Create space
);
}
diff --git a/x-pack/plugins/spaces/public/views/nav_control/components/__snapshots__/spaces_description.test.tsx.snap b/x-pack/plugins/spaces/public/views/nav_control/components/__snapshots__/spaces_description.test.tsx.snap
index 5f439351549c1..b46ae0481840a 100644
--- a/x-pack/plugins/spaces/public/views/nav_control/components/__snapshots__/spaces_description.test.tsx.snap
+++ b/x-pack/plugins/spaces/public/views/nav_control/components/__snapshots__/spaces_description.test.tsx.snap
@@ -12,7 +12,7 @@ exports[`SpacesDescription renders without crashing 1`] = `
grow={true}
>
- Use Spaces within Kibana to organize your Dashboards, Visualizations, and other saved objects.
+ Organize your dashboards and other saved objects into meaningful categories.
{
@@ -18,10 +19,7 @@ export const SpacesDescription: SFC = () => {
return (
-
- Use Spaces within Kibana to organize your Dashboards, Visualizations, and other saved
- objects.
-
+ {SPACES_FEATURE_DESCRIPTION}
diff --git a/x-pack/plugins/spaces/public/views/space_selector/__snapshots__/space_selector.test.js.snap b/x-pack/plugins/spaces/public/views/space_selector/__snapshots__/space_selector.test.js.snap
index 3f58530d7139b..21662970fe240 100644
--- a/x-pack/plugins/spaces/public/views/space_selector/__snapshots__/space_selector.test.js.snap
+++ b/x-pack/plugins/spaces/public/views/space_selector/__snapshots__/space_selector.test.js.snap
@@ -73,7 +73,7 @@ exports[`it renders without crashing 1`] = `
className="euiText euiText--extraSmall"
>
- You can change your space at anytime from within Kibana.
+ You can change your space at anytime.
diff --git a/x-pack/plugins/spaces/public/views/space_selector/space_selector.js b/x-pack/plugins/spaces/public/views/space_selector/space_selector.js
index f90d050fcf860..3508bdefe3b0b 100644
--- a/x-pack/plugins/spaces/public/views/space_selector/space_selector.js
+++ b/x-pack/plugins/spaces/public/views/space_selector/space_selector.js
@@ -90,7 +90,7 @@ export class SpaceSelector extends Component {
{this.getSearchField()}
- You can change your space at anytime from within Kibana.
+ You can change your space at anytime.
diff --git a/x-pack/plugins/spaces/server/lib/create_default_space.js b/x-pack/plugins/spaces/server/lib/create_default_space.js
index fc19ee325d14f..949e1fd8bbf2a 100644
--- a/x-pack/plugins/spaces/server/lib/create_default_space.js
+++ b/x-pack/plugins/spaces/server/lib/create_default_space.js
@@ -25,8 +25,8 @@ export async function createDefaultSpace(server) {
};
await savedObjectsRepository.create('space', {
- name: 'Default Space',
- description: 'This is your Default Space!',
+ name: 'Default',
+ description: 'This is your default space!',
_reserved: true
}, options);
}
diff --git a/x-pack/plugins/spaces/server/lib/create_default_space.test.js b/x-pack/plugins/spaces/server/lib/create_default_space.test.js
index 1d5a243315e92..874c5023b197f 100644
--- a/x-pack/plugins/spaces/server/lib/create_default_space.test.js
+++ b/x-pack/plugins/spaces/server/lib/create_default_space.test.js
@@ -78,7 +78,7 @@ test(`it creates the default space when one does not exist`, async () => {
expect(repository.create).toHaveBeenCalledTimes(1);
expect(repository.create).toHaveBeenCalledWith(
'space',
- { "_reserved": true, "description": "This is your Default Space!", "name": "Default Space" },
+ { "_reserved": true, "description": "This is your default space!", "name": "Default" },
{ "id": "default" }
);
});
From 4d83cfde56009b7e683a5dd7b5d7256ac32d575b Mon Sep 17 00:00:00 2001
From: Leanid Shutau
Date: Thu, 6 Sep 2018 20:34:26 +0300
Subject: [PATCH 34/68] [Tools] Fix line breaks in default JSON serializer
(#22653)
* [Tools] Fix line breaks in default JSON serializer
* Add test for not escaped line breaks
---
src/dev/i18n/__snapshots__/utils.test.js.snap | 7 ++++++-
src/dev/i18n/utils.js | 3 +--
src/dev/i18n/utils.test.js | 13 ++++---------
3 files changed, 11 insertions(+), 12 deletions(-)
diff --git a/src/dev/i18n/__snapshots__/utils.test.js.snap b/src/dev/i18n/__snapshots__/utils.test.js.snap
index 85e61058072b1..2a2f196d3f13f 100644
--- a/src/dev/i18n/__snapshots__/utils.test.js.snap
+++ b/src/dev/i18n/__snapshots__/utils.test.js.snap
@@ -1,3 +1,8 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`i18n utils should escape linebreaks 1`] = `"Text with\\\\n\\\\n\\\\nline-breaks and \\\\n\\\\n\\\\n \\\\n\\\\n\\\\n "`;
+exports[`i18n utils should not escape linebreaks 1`] = `
+"Text
+ with
+ line-breaks
+"
+`;
diff --git a/src/dev/i18n/utils.js b/src/dev/i18n/utils.js
index af01c4721f14a..658c1cbe67177 100644
--- a/src/dev/i18n/utils.js
+++ b/src/dev/i18n/utils.js
@@ -30,7 +30,6 @@ import { promisify } from 'util';
const ESCAPE_LINE_BREAK_REGEX = /(? {
test('should remove escaped linebreak', () => {
expect(formatJSString('Test\\\n str\\\ning')).toEqual('Test string');
});
-
- test('should escape linebreaks', () => {
+ test('should not escape linebreaks', () => {
expect(
- formatJSString(`Text with
-
-
-line-breaks and \n\n
- \n\n
- `)
+ formatJSString(`Text \n with
+ line-breaks
+`)
).toMatchSnapshot();
});
-
test('should detect i18n translate function call', () => {
let source = i18nTranslateSources[0];
let expressionStatementNode = [...traverseNodes(parse(source).program.body)].find(node =>
From 557bbcd26d83c6e68e46f546d15a745a8cb2fc73 Mon Sep 17 00:00:00 2001
From: Larry Gregory
Date: Thu, 6 Sep 2018 14:04:33 -0400
Subject: [PATCH 35/68] don't render space selector on login screen
---
.../spaces/public/views/nav_control/nav_control.js | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/x-pack/plugins/spaces/public/views/nav_control/nav_control.js b/x-pack/plugins/spaces/public/views/nav_control/nav_control.js
index 9dfcaf8ede84a..e0d3cfa90f483 100644
--- a/x-pack/plugins/spaces/public/views/nav_control/nav_control.js
+++ b/x-pack/plugins/spaces/public/views/nav_control/nav_control.js
@@ -30,11 +30,19 @@ module.controller('spacesNavController', ($scope, $http, chrome, activeSpace) =>
spacesManager = new SpacesManager($http, chrome);
- render( , domNode);
+ let mounted = false;
+
+ $scope.$parent.$watch('isVisible', function (isVisible) {
+ if (isVisible && !mounted) {
+ render( , domNode);
+ mounted = true;
+ }
+ });
// unmount react on controller destroy
$scope.$on('$destroy', () => {
unmountComponentAtNode(domNode);
+ mounted = false;
});
});
From b6c5f115e69422a851a45631654ff7d8002e38bb Mon Sep 17 00:00:00 2001
From: Larry Gregory
Date: Thu, 6 Sep 2018 17:56:45 -0400
Subject: [PATCH 36/68] run all tests again
---
x-pack/scripts/functional_tests.js | 16 ++++++++--------
x-pack/test/functional/config.js | 12 ++++++------
2 files changed, 14 insertions(+), 14 deletions(-)
diff --git a/x-pack/scripts/functional_tests.js b/x-pack/scripts/functional_tests.js
index 742582e9b48b6..1b05203e4dd09 100644
--- a/x-pack/scripts/functional_tests.js
+++ b/x-pack/scripts/functional_tests.js
@@ -6,13 +6,13 @@
require('@kbn/plugin-helpers').babelRegister();
require('@kbn/test').runTestsCli([
- // require.resolve('../test/reporting/configs/chromium_api.js'),
- // require.resolve('../test/reporting/configs/chromium_functional.js'),
- // require.resolve('../test/reporting/configs/phantom_api.js'),
- // require.resolve('../test/reporting/configs/phantom_functional.js'),
+ require.resolve('../test/reporting/configs/chromium_api.js'),
+ require.resolve('../test/reporting/configs/chromium_functional.js'),
+ require.resolve('../test/reporting/configs/phantom_api.js'),
+ require.resolve('../test/reporting/configs/phantom_functional.js'),
require.resolve('../test/functional/config.js'),
- // require.resolve('../test/api_integration/config.js'),
- // require.resolve('../test/saml_api_integration/config.js'),
- // require.resolve('../test/rbac_api_integration/config.js'),
- // require.resolve('../test/spaces_api_integration/config.js'),
+ require.resolve('../test/api_integration/config.js'),
+ require.resolve('../test/saml_api_integration/config.js'),
+ require.resolve('../test/rbac_api_integration/config.js'),
+ require.resolve('../test/spaces_api_integration/config.js'),
]);
diff --git a/x-pack/test/functional/config.js b/x-pack/test/functional/config.js
index 1d2010c7f38f0..23fda2b632263 100644
--- a/x-pack/test/functional/config.js
+++ b/x-pack/test/functional/config.js
@@ -59,13 +59,13 @@ export default async function ({ readConfigFile }) {
return {
// list paths to the files that contain your plugins tests
testFiles: [
- // resolve(__dirname, './apps/graph'),
- // resolve(__dirname, './apps/monitoring'),
- // resolve(__dirname, './apps/watcher'),
+ resolve(__dirname, './apps/graph'),
+ resolve(__dirname, './apps/monitoring'),
+ resolve(__dirname, './apps/watcher'),
resolve(__dirname, './apps/dashboard_mode'),
- // resolve(__dirname, './apps/security'),
- // resolve(__dirname, './apps/logstash'),
- // resolve(__dirname, './apps/grok_debugger'),
+ resolve(__dirname, './apps/security'),
+ resolve(__dirname, './apps/logstash'),
+ resolve(__dirname, './apps/grok_debugger'),
],
// define the name and providers for services that should be
From ce6bd301788fdd6900570486fb4139f4d1859153 Mon Sep 17 00:00:00 2001
From: Larry Gregory
Date: Thu, 6 Sep 2018 18:05:32 -0400
Subject: [PATCH 37/68] run all oss tests again
---
scripts/functional_tests.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/scripts/functional_tests.js b/scripts/functional_tests.js
index 88b89f84bcd17..9d73923704ce6 100644
--- a/scripts/functional_tests.js
+++ b/scripts/functional_tests.js
@@ -19,7 +19,7 @@
require('../src/setup_node_env');
require('@kbn/test').runTestsCli([
- // require.resolve('../test/functional/config.js'),
+ require.resolve('../test/functional/config.js'),
require.resolve('../test/api_integration/config.js'),
- // require.resolve('../test/plugin_functional/config.js'),
+ require.resolve('../test/plugin_functional/config.js'),
]);
From 03553dedbc12090d1b99505036da1a8ab106803f Mon Sep 17 00:00:00 2001
From: Jonathan Budzenski
Date: Thu, 6 Sep 2018 18:31:47 -0500
Subject: [PATCH 38/68] [ci] Run ES snapshot when possible (#22663)
For instances where we would otherwise be running from source in a tracked elastic/elasticsearch branch, let's use a snapshot instead. This will eliminate some gradle issues we are experiencing in master.
---
src/dev/ci_setup/git_setup.sh | 1 +
tasks/config/run.js | 11 ++++++-----
test/scripts/jenkins_cloud.sh | 2 +-
test/scripts/jenkins_selenium.sh | 1 +
test/scripts/jenkins_unit.sh | 2 +-
test/scripts/jenkins_xpack.sh | 4 ++--
6 files changed, 12 insertions(+), 9 deletions(-)
diff --git a/src/dev/ci_setup/git_setup.sh b/src/dev/ci_setup/git_setup.sh
index b5e6902e2f258..d0e2f3ffd87e4 100755
--- a/src/dev/ci_setup/git_setup.sh
+++ b/src/dev/ci_setup/git_setup.sh
@@ -70,6 +70,7 @@ function checkout_sibling {
cloneBranch="$PR_TARGET_BRANCH"
if clone_target_is_valid ; then
+ export TEST_ES_FROM=snapshot
return 0
fi
diff --git a/tasks/config/run.js b/tasks/config/run.js
index 5c17bd408dbd5..6c037e702c47e 100644
--- a/tasks/config/run.js
+++ b/tasks/config/run.js
@@ -60,6 +60,7 @@ module.exports = function (grunt) {
'--server.port=5610',
];
+ const esFrom = process.env.TEST_ES_FROM || 'source';
return {
// used by the test and jenkins:unit tasks
// runs the eslint script to check for linting errors
@@ -165,7 +166,7 @@ module.exports = function (grunt) {
args: [
'scripts/functional_tests',
'--config', 'test/api_integration/config.js',
- '--esFrom', 'source',
+ '--esFrom', esFrom,
'--bail',
'--debug',
],
@@ -177,7 +178,7 @@ module.exports = function (grunt) {
'scripts/functional_tests',
'--config', 'test/server_integration/http/ssl/config.js',
'--config', 'test/server_integration/http/ssl_redirect/config.js',
- '--esFrom', 'source',
+ '--esFrom', esFrom,
'--bail',
'--debug',
'--kibana-install-dir', `./build/oss/kibana-${PKG_VERSION}-${process.platform}-x86_64`,
@@ -189,7 +190,7 @@ module.exports = function (grunt) {
args: [
'scripts/functional_tests',
'--config', 'test/plugin_functional/config.js',
- '--esFrom', 'source',
+ '--esFrom', esFrom,
'--bail',
'--debug',
'--kibana-install-dir', `./build/oss/kibana-${PKG_VERSION}-${process.platform}-x86_64`,
@@ -203,7 +204,7 @@ module.exports = function (grunt) {
args: [
'scripts/functional_tests',
'--config', 'test/functional/config.js',
- '--esFrom', 'source',
+ '--esFrom', esFrom,
'--bail',
'--debug',
'--',
@@ -216,7 +217,7 @@ module.exports = function (grunt) {
args: [
'scripts/functional_tests',
'--config', 'test/functional/config.js',
- '--esFrom', 'source',
+ '--esFrom', esFrom,
'--bail',
'--debug',
'--kibana-install-dir', `./build/oss/kibana-${PKG_VERSION}-${process.platform}-x86_64`,
diff --git a/test/scripts/jenkins_cloud.sh b/test/scripts/jenkins_cloud.sh
index 1020fbe924290..25f683661232b 100755
--- a/test/scripts/jenkins_cloud.sh
+++ b/test/scripts/jenkins_cloud.sh
@@ -4,7 +4,7 @@
#
# The cloud instance setup is done in the elastic/elastic-stack-testing framework,
# where the following environment variables are set pointing to the cloud instance.
-#
+#
# export TEST_KIBANA_HOSTNAME
# export TEST_KIBANA_PROTOCOL=
# export TEST_KIBANA_PORT=
diff --git a/test/scripts/jenkins_selenium.sh b/test/scripts/jenkins_selenium.sh
index 7d6b26a59c485..e95be0073fe63 100755
--- a/test/scripts/jenkins_selenium.sh
+++ b/test/scripts/jenkins_selenium.sh
@@ -7,4 +7,5 @@ source "$(dirname $0)/../../src/dev/ci_setup/java_setup.sh"
node scripts/build --release --debug --oss;
+export TEST_ES_FROM=${TEST_ES_FROM:-source}
xvfb-run "$(FORCE_COLOR=0 yarn bin)/grunt" jenkins:selenium --from=source;
diff --git a/test/scripts/jenkins_unit.sh b/test/scripts/jenkins_unit.sh
index 0ce41c45110fd..4364facc45224 100755
--- a/test/scripts/jenkins_unit.sh
+++ b/test/scripts/jenkins_unit.sh
@@ -5,5 +5,5 @@ source "$(dirname $0)/../../src/dev/ci_setup/setup.sh"
source "$(dirname $0)/../../src/dev/ci_setup/git_setup.sh"
source "$(dirname $0)/../../src/dev/ci_setup/java_setup.sh"
-export TEST_ES_FROM=source
+export TEST_ES_FROM=${TEST_ES_FROM:-source}
xvfb-run "$(FORCE_COLOR=0 yarn bin)/grunt" jenkins:unit --from=source;
diff --git a/test/scripts/jenkins_xpack.sh b/test/scripts/jenkins_xpack.sh
index b1837d03c8522..6172db83a5de5 100755
--- a/test/scripts/jenkins_xpack.sh
+++ b/test/scripts/jenkins_xpack.sh
@@ -34,9 +34,9 @@ installDir="$PARENT_DIR/install/kibana"
mkdir -p "$installDir"
tar -xzf "$linuxBuild" -C "$installDir" --strip=1
-
+export TEST_ES_FROM=${TEST_ES_FROM:-source}
echo " -> Running functional and api tests"
cd "$XPACK_DIR"
-xvfb-run node scripts/functional_tests --debug --bail --kibana-install-dir "$installDir" --esFrom=source
+xvfb-run node scripts/functional_tests --debug --bail --kibana-install-dir "$installDir"
echo ""
echo ""
From df74a824a5fec02f158d54dbb8dda989968fe3c1 Mon Sep 17 00:00:00 2001
From: Larry Gregory
Date: Thu, 6 Sep 2018 19:40:29 -0400
Subject: [PATCH 39/68] update role api tests
---
.../api_integration/apis/security/roles.js | 50 ++++++-------------
x-pack/test/functional/config.js | 1 -
2 files changed, 16 insertions(+), 35 deletions(-)
diff --git a/x-pack/test/api_integration/apis/security/roles.js b/x-pack/test/api_integration/apis/security/roles.js
index f77ea88bde2c8..7e05e11944a0c 100644
--- a/x-pack/test/api_integration/apis/security/roles.js
+++ b/x-pack/test/api_integration/apis/security/roles.js
@@ -32,7 +32,7 @@ export default function ({ getService }) {
{
field_security: {
grant: ['*'],
- except: [ 'geo.*' ]
+ except: ['geo.*']
},
names: ['logstash-*'],
privileges: ['read', 'view_index_metadata'],
@@ -41,14 +41,10 @@ export default function ({ getService }) {
],
run_as: ['watcher_user'],
},
- kibana: [
- {
- privileges: ['all'],
- },
- {
- privileges: ['read'],
- },
- ],
+ kibana: {
+ global: ['all', 'read'],
+ space: {}
+ }
})
.expect(204);
@@ -62,7 +58,7 @@ export default function ({ getService }) {
privileges: ['read', 'view_index_metadata'],
field_security: {
grant: ['*'],
- except: [ 'geo.*' ]
+ except: ['geo.*']
},
query: `{ "match": { "geo.src": "CN" } }`,
},
@@ -70,12 +66,7 @@ export default function ({ getService }) {
applications: [
{
application: 'kibana-.kibana',
- privileges: ['all'],
- resources: ['*'],
- },
- {
- application: 'kibana-.kibana',
- privileges: ['read'],
+ privileges: ['all', 'read'],
resources: ['*'],
}
],
@@ -102,8 +93,8 @@ export default function ({ getService }) {
names: ['beats-*'],
privileges: ['write'],
field_security: {
- grant: [ 'request.*' ],
- except: [ 'response.*' ]
+ grant: ['request.*'],
+ except: ['response.*']
},
query: `{ "match": { "host.name": "localhost" } }`,
},
@@ -139,7 +130,7 @@ export default function ({ getService }) {
{
field_security: {
grant: ['*'],
- except: [ 'geo.*' ]
+ except: ['geo.*']
},
names: ['logstash-*'],
privileges: ['read', 'view_index_metadata'],
@@ -148,14 +139,10 @@ export default function ({ getService }) {
],
run_as: ['watcher_user'],
},
- kibana: [
- {
- privileges: ['all'],
- },
- {
- privileges: ['read'],
- },
- ],
+ kibana: {
+ global: ['all', 'read'],
+ space: {}
+ }
})
.expect(204);
@@ -169,7 +156,7 @@ export default function ({ getService }) {
privileges: ['read', 'view_index_metadata'],
field_security: {
grant: ['*'],
- except: [ 'geo.*' ]
+ except: ['geo.*']
},
query: `{ "match": { "geo.src": "CN" } }`,
},
@@ -177,12 +164,7 @@ export default function ({ getService }) {
applications: [
{
application: 'kibana-.kibana',
- privileges: ['all'],
- resources: ['*'],
- },
- {
- application: 'kibana-.kibana',
- privileges: ['read'],
+ privileges: ['all', 'read'],
resources: ['*'],
},
{
diff --git a/x-pack/test/functional/config.js b/x-pack/test/functional/config.js
index 23fda2b632263..77c0aee49b83e 100644
--- a/x-pack/test/functional/config.js
+++ b/x-pack/test/functional/config.js
@@ -133,7 +133,6 @@ export default async function ({ readConfigFile }) {
'--server.uuid=5b2de169-2785-441b-ae8c-186a1936b17d',
'--xpack.xpack_main.telemetry.enabled=false',
'--xpack.security.encryptionKey="wuGNaIhoMpk5sO4UBxgr3NyW1sFcLgIf"', // server restarts should not invalidate active sessions
- '--logging.verbose=true',
],
},
From bdd1d530cb3022e2319aa7f90153267e94d343b4 Mon Sep 17 00:00:00 2001
From: Aleh Zasypkin
Date: Fri, 7 Sep 2018 08:40:03 +0300
Subject: [PATCH 40/68] Get rid of `__tests__` folders in the core. (#22662)
---
.../{__tests__ => }/__fixtures__/config.yml | 0
.../__fixtures__/config_flat.yml | 0
.../__fixtures__/en_var_ref_config.yml | 0
.../{__tests__ => }/__fixtures__/one.yml | 0
.../{__tests__ => }/__fixtures__/two.yml | 0
.../config/{__tests__ => }/__mocks__/env.ts | 2 +-
.../__snapshots__/config_service.test.ts.snap | 0
.../__snapshots__/env.test.ts.snap | 0
.../__snapshots__/read_config.test.ts.snap | 0
.../config/{__tests__ => }/apply_argv.test.ts | 2 +-
.../{__tests__ => }/config_service.test.ts | 9 ++++-----
.../ensure_deep_object.test.ts | 2 +-
.../server/config/{__tests__ => }/env.test.ts | 4 ++--
src/core/server/config/index.ts | 1 -
.../raw_config_service.test.ts | 4 ++--
.../{__tests__ => }/read_config.test.ts | 2 +-
.../__snapshots__/index.test.ts.snap | 0
.../{__tests__ => }/index.test.ts | 2 +-
.../{__tests__ => }/schema_error.test.ts | 2 +-
.../__snapshots__/any_type.test.ts.snap | 0
.../__snapshots__/array_type.test.ts.snap | 0
.../__snapshots__/boolean_type.test.ts.snap | 0
.../__snapshots__/byte_size_type.test.ts.snap | 0
.../conditional_type.test.ts.snap | 0
.../__snapshots__/duration_type.test.ts.snap | 0
.../__snapshots__/literal_type.test.ts.snap | 0
.../__snapshots__/map_of_type.test.ts.snap | 0
.../__snapshots__/maybe_type.test.ts.snap | 0
.../__snapshots__/number_type.test.ts.snap | 0
.../__snapshots__/object_type.test.ts.snap | 0
.../__snapshots__/one_of_type.test.ts.snap | 0
.../__snapshots__/string_type.test.ts.snap | 0
.../types/{__tests__ => }/any_type.test.ts | 2 +-
.../types/{__tests__ => }/array_type.test.ts | 2 +-
.../{__tests__ => }/boolean_type.test.ts | 2 +-
.../{__tests__ => }/byte_size_type.test.ts | 4 ++--
.../{__tests__ => }/conditional_type.test.ts | 2 +-
.../{__tests__ => }/duration_type.test.ts | 2 +-
.../{__tests__ => }/literal_type.test.ts | 2 +-
.../types/{__tests__ => }/map_of_type.test.ts | 2 +-
.../types/{__tests__ => }/maybe_type.test.ts | 2 +-
.../types/{__tests__ => }/number_type.test.ts | 2 +-
.../types/{__tests__ => }/object_type.test.ts | 2 +-
.../types/{__tests__ => }/one_of_type.test.ts | 2 +-
.../types/{__tests__ => }/string_type.test.ts | 2 +-
.../__snapshots__/http_config.test.ts.snap | 0
.../__snapshots__/http_server.test.ts.snap | 0
.../__snapshots__/http_service.test.ts.snap | 0
.../https_redirect_server.test.ts.snap | 0
.../http/{__tests__ => }/http_config.test.ts | 2 +-
.../http/{__tests__ => }/http_server.test.ts | 9 ++++-----
.../http/{__tests__ => }/http_service.test.ts | 8 +++-----
.../https_redirect_server.test.ts | 8 ++++----
src/core/server/index.test.ts | 2 +-
.../__snapshots__/legacy_service.test.ts.snap | 0
...gacy_object_to_config_adapter.test.ts.snap | 0
.../legacy_object_to_config_adapter.test.ts | 2 +-
.../legacy_platform_proxy.test.ts | 2 +-
.../{__tests__ => }/legacy_service.test.ts | 20 +++++++++----------
.../legacy_appender.test.ts.snap | 0
.../{__tests__ => }/legacy_appender.test.ts | 10 +++++-----
.../__snapshots__/logging_config.test.ts.snap | 0
.../logging_service.test.ts.snap | 0
.../{__tests__ => }/appenders.test.ts | 12 +++++------
.../buffer_appender.test.ts | 2 +-
.../console_appender.test.ts | 2 +-
.../{__tests__ => file}/file_appender.test.ts | 2 +-
.../__snapshots__/json_layout.test.ts.snap | 0
.../__snapshots__/pattern_layout.test.ts.snap | 0
.../{__tests__ => }/json_layout.test.ts | 6 +++---
.../layouts/{__tests__ => }/layouts.test.ts | 6 +++---
.../{__tests__ => }/pattern_layout.test.ts | 8 ++++----
.../logging/{__tests__ => }/log_level.test.ts | 2 +-
.../logging/{__tests__ => }/logger.test.ts | 8 ++++----
.../{__tests__ => }/logger_adapter.test.ts | 4 ++--
.../{__tests__ => }/logging_config.test.ts | 2 +-
.../{__tests__ => }/logging_service.test.ts | 3 +--
.../__snapshots__/index.test.ts.snap | 0
.../server/root/{__tests__ => }/index.test.ts | 14 ++++++-------
.../__snapshots__/get.test.ts.snap | 0
src/core/utils/{__tests__ => }/get.test.ts | 2 +-
src/core/utils/{__tests__ => }/url.test.ts | 2 +-
82 files changed, 95 insertions(+), 101 deletions(-)
rename src/core/server/config/{__tests__ => }/__fixtures__/config.yml (100%)
rename src/core/server/config/{__tests__ => }/__fixtures__/config_flat.yml (100%)
rename src/core/server/config/{__tests__ => }/__fixtures__/en_var_ref_config.yml (100%)
rename src/core/server/config/{__tests__ => }/__fixtures__/one.yml (100%)
rename src/core/server/config/{__tests__ => }/__fixtures__/two.yml (100%)
rename src/core/server/config/{__tests__ => }/__mocks__/env.ts (97%)
rename src/core/server/config/{__tests__ => }/__snapshots__/config_service.test.ts.snap (100%)
rename src/core/server/config/{__tests__ => }/__snapshots__/env.test.ts.snap (100%)
rename src/core/server/config/{__tests__ => }/__snapshots__/read_config.test.ts.snap (100%)
rename src/core/server/config/{__tests__ => }/apply_argv.test.ts (97%)
rename src/core/server/config/{__tests__ => }/config_service.test.ts (97%)
rename src/core/server/config/{__tests__ => }/ensure_deep_object.test.ts (98%)
rename src/core/server/config/{__tests__ => }/env.test.ts (97%)
rename src/core/server/config/{__tests__ => }/raw_config_service.test.ts (98%)
rename src/core/server/config/{__tests__ => }/read_config.test.ts (98%)
rename src/core/server/config/schema/byte_size_value/{__tests__ => }/__snapshots__/index.test.ts.snap (100%)
rename src/core/server/config/schema/byte_size_value/{__tests__ => }/index.test.ts (99%)
rename src/core/server/config/schema/errors/{__tests__ => }/schema_error.test.ts (98%)
rename src/core/server/config/schema/types/{__tests__ => }/__snapshots__/any_type.test.ts.snap (100%)
rename src/core/server/config/schema/types/{__tests__ => }/__snapshots__/array_type.test.ts.snap (100%)
rename src/core/server/config/schema/types/{__tests__ => }/__snapshots__/boolean_type.test.ts.snap (100%)
rename src/core/server/config/schema/types/{__tests__ => }/__snapshots__/byte_size_type.test.ts.snap (100%)
rename src/core/server/config/schema/types/{__tests__ => }/__snapshots__/conditional_type.test.ts.snap (100%)
rename src/core/server/config/schema/types/{__tests__ => }/__snapshots__/duration_type.test.ts.snap (100%)
rename src/core/server/config/schema/types/{__tests__ => }/__snapshots__/literal_type.test.ts.snap (100%)
rename src/core/server/config/schema/types/{__tests__ => }/__snapshots__/map_of_type.test.ts.snap (100%)
rename src/core/server/config/schema/types/{__tests__ => }/__snapshots__/maybe_type.test.ts.snap (100%)
rename src/core/server/config/schema/types/{__tests__ => }/__snapshots__/number_type.test.ts.snap (100%)
rename src/core/server/config/schema/types/{__tests__ => }/__snapshots__/object_type.test.ts.snap (100%)
rename src/core/server/config/schema/types/{__tests__ => }/__snapshots__/one_of_type.test.ts.snap (100%)
rename src/core/server/config/schema/types/{__tests__ => }/__snapshots__/string_type.test.ts.snap (100%)
rename src/core/server/config/schema/types/{__tests__ => }/any_type.test.ts (98%)
rename src/core/server/config/schema/types/{__tests__ => }/array_type.test.ts (99%)
rename src/core/server/config/schema/types/{__tests__ => }/boolean_type.test.ts (98%)
rename src/core/server/config/schema/types/{__tests__ => }/byte_size_type.test.ts (97%)
rename src/core/server/config/schema/types/{__tests__ => }/conditional_type.test.ts (99%)
rename src/core/server/config/schema/types/{__tests__ => }/duration_type.test.ts (98%)
rename src/core/server/config/schema/types/{__tests__ => }/literal_type.test.ts (98%)
rename src/core/server/config/schema/types/{__tests__ => }/map_of_type.test.ts (98%)
rename src/core/server/config/schema/types/{__tests__ => }/maybe_type.test.ts (98%)
rename src/core/server/config/schema/types/{__tests__ => }/number_type.test.ts (98%)
rename src/core/server/config/schema/types/{__tests__ => }/object_type.test.ts (99%)
rename src/core/server/config/schema/types/{__tests__ => }/one_of_type.test.ts (99%)
rename src/core/server/config/schema/types/{__tests__ => }/string_type.test.ts (99%)
rename src/core/server/http/{__tests__ => }/__snapshots__/http_config.test.ts.snap (100%)
rename src/core/server/http/{__tests__ => }/__snapshots__/http_server.test.ts.snap (100%)
rename src/core/server/http/{__tests__ => }/__snapshots__/http_service.test.ts.snap (100%)
rename src/core/server/http/{__tests__ => }/__snapshots__/https_redirect_server.test.ts.snap (100%)
rename src/core/server/http/{__tests__ => }/http_config.test.ts (99%)
rename src/core/server/http/{__tests__ => }/http_server.test.ts (98%)
rename src/core/server/http/{__tests__ => }/http_service.test.ts (95%)
rename src/core/server/http/{__tests__ => }/https_redirect_server.test.ts (92%)
rename src/core/server/legacy_compat/{__tests__ => }/__snapshots__/legacy_service.test.ts.snap (100%)
rename src/core/server/legacy_compat/config/{__tests__ => }/__snapshots__/legacy_object_to_config_adapter.test.ts.snap (100%)
rename src/core/server/legacy_compat/config/{__tests__ => }/legacy_object_to_config_adapter.test.ts (98%)
rename src/core/server/legacy_compat/{__tests__ => }/legacy_platform_proxy.test.ts (98%)
rename src/core/server/legacy_compat/{__tests__ => }/legacy_service.test.ts (95%)
rename src/core/server/legacy_compat/logging/appenders/{__tests__ => }/__snapshots__/legacy_appender.test.ts.snap (100%)
rename src/core/server/legacy_compat/logging/appenders/{__tests__ => }/legacy_appender.test.ts (93%)
rename src/core/server/logging/{__tests__ => }/__snapshots__/logging_config.test.ts.snap (100%)
rename src/core/server/logging/{__tests__ => }/__snapshots__/logging_service.test.ts.snap (100%)
rename src/core/server/logging/appenders/{__tests__ => }/appenders.test.ts (88%)
rename src/core/server/logging/appenders/{__tests__ => buffer}/buffer_appender.test.ts (97%)
rename src/core/server/logging/appenders/{__tests__ => console}/console_appender.test.ts (97%)
rename src/core/server/logging/appenders/{__tests__ => file}/file_appender.test.ts (98%)
rename src/core/server/logging/layouts/{__tests__ => }/__snapshots__/json_layout.test.ts.snap (100%)
rename src/core/server/logging/layouts/{__tests__ => }/__snapshots__/pattern_layout.test.ts.snap (100%)
rename src/core/server/logging/layouts/{__tests__ => }/json_layout.test.ts (94%)
rename src/core/server/logging/layouts/{__tests__ => }/layouts.test.ts (93%)
rename src/core/server/logging/layouts/{__tests__ => }/pattern_layout.test.ts (93%)
rename src/core/server/logging/{__tests__ => }/log_level.test.ts (98%)
rename src/core/server/logging/{__tests__ => }/logger.test.ts (98%)
rename src/core/server/logging/{__tests__ => }/logger_adapter.test.ts (97%)
rename src/core/server/logging/{__tests__ => }/logging_config.test.ts (98%)
rename src/core/server/logging/{__tests__ => }/logging_service.test.ts (98%)
rename src/core/server/root/{__tests__ => }/__snapshots__/index.test.ts.snap (100%)
rename src/core/server/root/{__tests__ => }/index.test.ts (95%)
rename src/core/utils/{__tests__ => }/__snapshots__/get.test.ts.snap (100%)
rename src/core/utils/{__tests__ => }/get.test.ts (97%)
rename src/core/utils/{__tests__ => }/url.test.ts (98%)
diff --git a/src/core/server/config/__tests__/__fixtures__/config.yml b/src/core/server/config/__fixtures__/config.yml
similarity index 100%
rename from src/core/server/config/__tests__/__fixtures__/config.yml
rename to src/core/server/config/__fixtures__/config.yml
diff --git a/src/core/server/config/__tests__/__fixtures__/config_flat.yml b/src/core/server/config/__fixtures__/config_flat.yml
similarity index 100%
rename from src/core/server/config/__tests__/__fixtures__/config_flat.yml
rename to src/core/server/config/__fixtures__/config_flat.yml
diff --git a/src/core/server/config/__tests__/__fixtures__/en_var_ref_config.yml b/src/core/server/config/__fixtures__/en_var_ref_config.yml
similarity index 100%
rename from src/core/server/config/__tests__/__fixtures__/en_var_ref_config.yml
rename to src/core/server/config/__fixtures__/en_var_ref_config.yml
diff --git a/src/core/server/config/__tests__/__fixtures__/one.yml b/src/core/server/config/__fixtures__/one.yml
similarity index 100%
rename from src/core/server/config/__tests__/__fixtures__/one.yml
rename to src/core/server/config/__fixtures__/one.yml
diff --git a/src/core/server/config/__tests__/__fixtures__/two.yml b/src/core/server/config/__fixtures__/two.yml
similarity index 100%
rename from src/core/server/config/__tests__/__fixtures__/two.yml
rename to src/core/server/config/__fixtures__/two.yml
diff --git a/src/core/server/config/__tests__/__mocks__/env.ts b/src/core/server/config/__mocks__/env.ts
similarity index 97%
rename from src/core/server/config/__tests__/__mocks__/env.ts
rename to src/core/server/config/__mocks__/env.ts
index e90c33f19ee49..dec62978a292f 100644
--- a/src/core/server/config/__tests__/__mocks__/env.ts
+++ b/src/core/server/config/__mocks__/env.ts
@@ -19,7 +19,7 @@
// Test helpers to simplify mocking environment options.
-import { EnvOptions } from '../../env';
+import { EnvOptions } from '../env';
type DeepPartial = {
[P in keyof T]?: T[P] extends Array ? Array> : DeepPartial
diff --git a/src/core/server/config/__tests__/__snapshots__/config_service.test.ts.snap b/src/core/server/config/__snapshots__/config_service.test.ts.snap
similarity index 100%
rename from src/core/server/config/__tests__/__snapshots__/config_service.test.ts.snap
rename to src/core/server/config/__snapshots__/config_service.test.ts.snap
diff --git a/src/core/server/config/__tests__/__snapshots__/env.test.ts.snap b/src/core/server/config/__snapshots__/env.test.ts.snap
similarity index 100%
rename from src/core/server/config/__tests__/__snapshots__/env.test.ts.snap
rename to src/core/server/config/__snapshots__/env.test.ts.snap
diff --git a/src/core/server/config/__tests__/__snapshots__/read_config.test.ts.snap b/src/core/server/config/__snapshots__/read_config.test.ts.snap
similarity index 100%
rename from src/core/server/config/__tests__/__snapshots__/read_config.test.ts.snap
rename to src/core/server/config/__snapshots__/read_config.test.ts.snap
diff --git a/src/core/server/config/__tests__/apply_argv.test.ts b/src/core/server/config/apply_argv.test.ts
similarity index 97%
rename from src/core/server/config/__tests__/apply_argv.test.ts
rename to src/core/server/config/apply_argv.test.ts
index b3d2f3749271a..80aa3d9f74a40 100644
--- a/src/core/server/config/__tests__/apply_argv.test.ts
+++ b/src/core/server/config/apply_argv.test.ts
@@ -17,7 +17,7 @@
* under the License.
*/
-import { Config, ObjectToConfigAdapter } from '..';
+import { Config, ObjectToConfigAdapter } from '.';
/**
* Overrides some config values with ones from argv.
diff --git a/src/core/server/config/__tests__/config_service.test.ts b/src/core/server/config/config_service.test.ts
similarity index 97%
rename from src/core/server/config/__tests__/config_service.test.ts
rename to src/core/server/config/config_service.test.ts
index c41ae3ccd8b75..22598b2a971d0 100644
--- a/src/core/server/config/__tests__/config_service.test.ts
+++ b/src/core/server/config/config_service.test.ts
@@ -23,13 +23,12 @@ import { BehaviorSubject } from 'rxjs';
import { first } from 'rxjs/operators';
const mockPackage = new Proxy({ raw: {} as any }, { get: (obj, prop) => obj.raw[prop] });
-jest.mock('../../../../utils/package_json', () => ({ pkg: mockPackage }));
+jest.mock('../../../utils/package_json', () => ({ pkg: mockPackage }));
-import { schema, Type, TypeOf } from '../schema';
+import { schema, Type, TypeOf } from './schema';
-import { ConfigService, ObjectToConfigAdapter } from '..';
-import { logger } from '../../logging/__mocks__';
-import { Env } from '../env';
+import { ConfigService, Env, ObjectToConfigAdapter } from '.';
+import { logger } from '../logging/__mocks__';
import { getEnvOptions } from './__mocks__/env';
const emptyArgv = getEnvOptions();
diff --git a/src/core/server/config/__tests__/ensure_deep_object.test.ts b/src/core/server/config/ensure_deep_object.test.ts
similarity index 98%
rename from src/core/server/config/__tests__/ensure_deep_object.test.ts
rename to src/core/server/config/ensure_deep_object.test.ts
index 40c0732266073..5a520fbeef316 100644
--- a/src/core/server/config/__tests__/ensure_deep_object.test.ts
+++ b/src/core/server/config/ensure_deep_object.test.ts
@@ -17,7 +17,7 @@
* under the License.
*/
-import { ensureDeepObject } from '../ensure_deep_object';
+import { ensureDeepObject } from './ensure_deep_object';
test('flat object', () => {
const obj = {
diff --git a/src/core/server/config/__tests__/env.test.ts b/src/core/server/config/env.test.ts
similarity index 97%
rename from src/core/server/config/__tests__/env.test.ts
rename to src/core/server/config/env.test.ts
index 381273a1f8ffb..56ff576fd8f31 100644
--- a/src/core/server/config/__tests__/env.test.ts
+++ b/src/core/server/config/env.test.ts
@@ -30,9 +30,9 @@ jest.mock('path', () => ({
}));
const mockPackage = new Proxy({ raw: {} as any }, { get: (obj, prop) => obj.raw[prop] });
-jest.mock('../../../../utils/package_json', () => ({ pkg: mockPackage }));
+jest.mock('../../../utils/package_json', () => ({ pkg: mockPackage }));
-import { Env } from '../env';
+import { Env } from '.';
import { getEnvOptions } from './__mocks__/env';
test('correctly creates default environment in dev mode.', () => {
diff --git a/src/core/server/config/index.ts b/src/core/server/config/index.ts
index c63a8d6aa7c04..bbddec03a0f41 100644
--- a/src/core/server/config/index.ts
+++ b/src/core/server/config/index.ts
@@ -24,4 +24,3 @@ export { Config, ConfigPath } from './config';
export { ObjectToConfigAdapter } from './object_to_config_adapter';
export { Env, CliArgs } from './env';
export { ConfigWithSchema } from './config_with_schema';
-export { getConfigFromFiles } from './read_config';
diff --git a/src/core/server/config/__tests__/raw_config_service.test.ts b/src/core/server/config/raw_config_service.test.ts
similarity index 98%
rename from src/core/server/config/__tests__/raw_config_service.test.ts
rename to src/core/server/config/raw_config_service.test.ts
index 66cc31bc77774..eb5c212a31eb9 100644
--- a/src/core/server/config/__tests__/raw_config_service.test.ts
+++ b/src/core/server/config/raw_config_service.test.ts
@@ -19,12 +19,12 @@
const mockGetConfigFromFiles = jest.fn();
-jest.mock('../read_config', () => ({
+jest.mock('./read_config', () => ({
getConfigFromFiles: mockGetConfigFromFiles,
}));
import { first } from 'rxjs/operators';
-import { RawConfigService } from '../raw_config_service';
+import { RawConfigService } from '.';
const configFile = '/config/kibana.yml';
const anotherConfigFile = '/config/kibana.dev.yml';
diff --git a/src/core/server/config/__tests__/read_config.test.ts b/src/core/server/config/read_config.test.ts
similarity index 98%
rename from src/core/server/config/__tests__/read_config.test.ts
rename to src/core/server/config/read_config.test.ts
index b9aa3871b1794..46b75f28eb987 100644
--- a/src/core/server/config/__tests__/read_config.test.ts
+++ b/src/core/server/config/read_config.test.ts
@@ -18,7 +18,7 @@
*/
import { relative, resolve } from 'path';
-import { getConfigFromFiles } from '../read_config';
+import { getConfigFromFiles } from './read_config';
const fixtureFile = (name: string) => `${__dirname}/__fixtures__/${name}`;
diff --git a/src/core/server/config/schema/byte_size_value/__tests__/__snapshots__/index.test.ts.snap b/src/core/server/config/schema/byte_size_value/__snapshots__/index.test.ts.snap
similarity index 100%
rename from src/core/server/config/schema/byte_size_value/__tests__/__snapshots__/index.test.ts.snap
rename to src/core/server/config/schema/byte_size_value/__snapshots__/index.test.ts.snap
diff --git a/src/core/server/config/schema/byte_size_value/__tests__/index.test.ts b/src/core/server/config/schema/byte_size_value/index.test.ts
similarity index 99%
rename from src/core/server/config/schema/byte_size_value/__tests__/index.test.ts
rename to src/core/server/config/schema/byte_size_value/index.test.ts
index ece8769248152..46ed96c83dd1f 100644
--- a/src/core/server/config/schema/byte_size_value/__tests__/index.test.ts
+++ b/src/core/server/config/schema/byte_size_value/index.test.ts
@@ -17,7 +17,7 @@
* under the License.
*/
-import { ByteSizeValue } from '../';
+import { ByteSizeValue } from '.';
describe('parsing units', () => {
test('bytes', () => {
diff --git a/src/core/server/config/schema/errors/__tests__/schema_error.test.ts b/src/core/server/config/schema/errors/schema_error.test.ts
similarity index 98%
rename from src/core/server/config/schema/errors/__tests__/schema_error.test.ts
rename to src/core/server/config/schema/errors/schema_error.test.ts
index 15ce626621b58..0f632b781e9a6 100644
--- a/src/core/server/config/schema/errors/__tests__/schema_error.test.ts
+++ b/src/core/server/config/schema/errors/schema_error.test.ts
@@ -18,7 +18,7 @@
*/
import { relative } from 'path';
-import { SchemaError } from '..';
+import { SchemaError } from '.';
/**
* Make all paths in stacktrace relative.
diff --git a/src/core/server/config/schema/types/__tests__/__snapshots__/any_type.test.ts.snap b/src/core/server/config/schema/types/__snapshots__/any_type.test.ts.snap
similarity index 100%
rename from src/core/server/config/schema/types/__tests__/__snapshots__/any_type.test.ts.snap
rename to src/core/server/config/schema/types/__snapshots__/any_type.test.ts.snap
diff --git a/src/core/server/config/schema/types/__tests__/__snapshots__/array_type.test.ts.snap b/src/core/server/config/schema/types/__snapshots__/array_type.test.ts.snap
similarity index 100%
rename from src/core/server/config/schema/types/__tests__/__snapshots__/array_type.test.ts.snap
rename to src/core/server/config/schema/types/__snapshots__/array_type.test.ts.snap
diff --git a/src/core/server/config/schema/types/__tests__/__snapshots__/boolean_type.test.ts.snap b/src/core/server/config/schema/types/__snapshots__/boolean_type.test.ts.snap
similarity index 100%
rename from src/core/server/config/schema/types/__tests__/__snapshots__/boolean_type.test.ts.snap
rename to src/core/server/config/schema/types/__snapshots__/boolean_type.test.ts.snap
diff --git a/src/core/server/config/schema/types/__tests__/__snapshots__/byte_size_type.test.ts.snap b/src/core/server/config/schema/types/__snapshots__/byte_size_type.test.ts.snap
similarity index 100%
rename from src/core/server/config/schema/types/__tests__/__snapshots__/byte_size_type.test.ts.snap
rename to src/core/server/config/schema/types/__snapshots__/byte_size_type.test.ts.snap
diff --git a/src/core/server/config/schema/types/__tests__/__snapshots__/conditional_type.test.ts.snap b/src/core/server/config/schema/types/__snapshots__/conditional_type.test.ts.snap
similarity index 100%
rename from src/core/server/config/schema/types/__tests__/__snapshots__/conditional_type.test.ts.snap
rename to src/core/server/config/schema/types/__snapshots__/conditional_type.test.ts.snap
diff --git a/src/core/server/config/schema/types/__tests__/__snapshots__/duration_type.test.ts.snap b/src/core/server/config/schema/types/__snapshots__/duration_type.test.ts.snap
similarity index 100%
rename from src/core/server/config/schema/types/__tests__/__snapshots__/duration_type.test.ts.snap
rename to src/core/server/config/schema/types/__snapshots__/duration_type.test.ts.snap
diff --git a/src/core/server/config/schema/types/__tests__/__snapshots__/literal_type.test.ts.snap b/src/core/server/config/schema/types/__snapshots__/literal_type.test.ts.snap
similarity index 100%
rename from src/core/server/config/schema/types/__tests__/__snapshots__/literal_type.test.ts.snap
rename to src/core/server/config/schema/types/__snapshots__/literal_type.test.ts.snap
diff --git a/src/core/server/config/schema/types/__tests__/__snapshots__/map_of_type.test.ts.snap b/src/core/server/config/schema/types/__snapshots__/map_of_type.test.ts.snap
similarity index 100%
rename from src/core/server/config/schema/types/__tests__/__snapshots__/map_of_type.test.ts.snap
rename to src/core/server/config/schema/types/__snapshots__/map_of_type.test.ts.snap
diff --git a/src/core/server/config/schema/types/__tests__/__snapshots__/maybe_type.test.ts.snap b/src/core/server/config/schema/types/__snapshots__/maybe_type.test.ts.snap
similarity index 100%
rename from src/core/server/config/schema/types/__tests__/__snapshots__/maybe_type.test.ts.snap
rename to src/core/server/config/schema/types/__snapshots__/maybe_type.test.ts.snap
diff --git a/src/core/server/config/schema/types/__tests__/__snapshots__/number_type.test.ts.snap b/src/core/server/config/schema/types/__snapshots__/number_type.test.ts.snap
similarity index 100%
rename from src/core/server/config/schema/types/__tests__/__snapshots__/number_type.test.ts.snap
rename to src/core/server/config/schema/types/__snapshots__/number_type.test.ts.snap
diff --git a/src/core/server/config/schema/types/__tests__/__snapshots__/object_type.test.ts.snap b/src/core/server/config/schema/types/__snapshots__/object_type.test.ts.snap
similarity index 100%
rename from src/core/server/config/schema/types/__tests__/__snapshots__/object_type.test.ts.snap
rename to src/core/server/config/schema/types/__snapshots__/object_type.test.ts.snap
diff --git a/src/core/server/config/schema/types/__tests__/__snapshots__/one_of_type.test.ts.snap b/src/core/server/config/schema/types/__snapshots__/one_of_type.test.ts.snap
similarity index 100%
rename from src/core/server/config/schema/types/__tests__/__snapshots__/one_of_type.test.ts.snap
rename to src/core/server/config/schema/types/__snapshots__/one_of_type.test.ts.snap
diff --git a/src/core/server/config/schema/types/__tests__/__snapshots__/string_type.test.ts.snap b/src/core/server/config/schema/types/__snapshots__/string_type.test.ts.snap
similarity index 100%
rename from src/core/server/config/schema/types/__tests__/__snapshots__/string_type.test.ts.snap
rename to src/core/server/config/schema/types/__snapshots__/string_type.test.ts.snap
diff --git a/src/core/server/config/schema/types/__tests__/any_type.test.ts b/src/core/server/config/schema/types/any_type.test.ts
similarity index 98%
rename from src/core/server/config/schema/types/__tests__/any_type.test.ts
rename to src/core/server/config/schema/types/any_type.test.ts
index 6f39f3deab5fd..4d68c860ba13d 100644
--- a/src/core/server/config/schema/types/__tests__/any_type.test.ts
+++ b/src/core/server/config/schema/types/any_type.test.ts
@@ -17,7 +17,7 @@
* under the License.
*/
-import { schema } from '../..';
+import { schema } from '..';
test('works for any value', () => {
expect(schema.any().validate(true)).toBe(true);
diff --git a/src/core/server/config/schema/types/__tests__/array_type.test.ts b/src/core/server/config/schema/types/array_type.test.ts
similarity index 99%
rename from src/core/server/config/schema/types/__tests__/array_type.test.ts
rename to src/core/server/config/schema/types/array_type.test.ts
index f1fb124a95ede..c6943e0d1b5f3 100644
--- a/src/core/server/config/schema/types/__tests__/array_type.test.ts
+++ b/src/core/server/config/schema/types/array_type.test.ts
@@ -17,7 +17,7 @@
* under the License.
*/
-import { schema } from '../..';
+import { schema } from '..';
test('returns value if it matches the type', () => {
const type = schema.arrayOf(schema.string());
diff --git a/src/core/server/config/schema/types/__tests__/boolean_type.test.ts b/src/core/server/config/schema/types/boolean_type.test.ts
similarity index 98%
rename from src/core/server/config/schema/types/__tests__/boolean_type.test.ts
rename to src/core/server/config/schema/types/boolean_type.test.ts
index bfd4259af387e..d6e274f05e3ff 100644
--- a/src/core/server/config/schema/types/__tests__/boolean_type.test.ts
+++ b/src/core/server/config/schema/types/boolean_type.test.ts
@@ -17,7 +17,7 @@
* under the License.
*/
-import { schema } from '../..';
+import { schema } from '..';
test('returns value by default', () => {
expect(schema.boolean().validate(true)).toBe(true);
diff --git a/src/core/server/config/schema/types/__tests__/byte_size_type.test.ts b/src/core/server/config/schema/types/byte_size_type.test.ts
similarity index 97%
rename from src/core/server/config/schema/types/__tests__/byte_size_type.test.ts
rename to src/core/server/config/schema/types/byte_size_type.test.ts
index 786b996ae5687..67eae1e7c382a 100644
--- a/src/core/server/config/schema/types/__tests__/byte_size_type.test.ts
+++ b/src/core/server/config/schema/types/byte_size_type.test.ts
@@ -17,8 +17,8 @@
* under the License.
*/
-import { schema } from '../..';
-import { ByteSizeValue } from '../../byte_size_value';
+import { schema } from '..';
+import { ByteSizeValue } from '../byte_size_value';
const { byteSize } = schema;
diff --git a/src/core/server/config/schema/types/__tests__/conditional_type.test.ts b/src/core/server/config/schema/types/conditional_type.test.ts
similarity index 99%
rename from src/core/server/config/schema/types/__tests__/conditional_type.test.ts
rename to src/core/server/config/schema/types/conditional_type.test.ts
index 112ee874afa7b..a72c3463e00cb 100644
--- a/src/core/server/config/schema/types/__tests__/conditional_type.test.ts
+++ b/src/core/server/config/schema/types/conditional_type.test.ts
@@ -17,7 +17,7 @@
* under the License.
*/
-import { schema } from '../..';
+import { schema } from '..';
test('required by default', () => {
const type = schema.conditional(
diff --git a/src/core/server/config/schema/types/__tests__/duration_type.test.ts b/src/core/server/config/schema/types/duration_type.test.ts
similarity index 98%
rename from src/core/server/config/schema/types/__tests__/duration_type.test.ts
rename to src/core/server/config/schema/types/duration_type.test.ts
index 0c1d7e4dd8e50..9a21afc6cf40f 100644
--- a/src/core/server/config/schema/types/__tests__/duration_type.test.ts
+++ b/src/core/server/config/schema/types/duration_type.test.ts
@@ -18,7 +18,7 @@
*/
import { duration as momentDuration } from 'moment';
-import { schema } from '../..';
+import { schema } from '..';
const { duration } = schema;
diff --git a/src/core/server/config/schema/types/__tests__/literal_type.test.ts b/src/core/server/config/schema/types/literal_type.test.ts
similarity index 98%
rename from src/core/server/config/schema/types/__tests__/literal_type.test.ts
rename to src/core/server/config/schema/types/literal_type.test.ts
index 4d590200c1ccf..5ee0ac4edff68 100644
--- a/src/core/server/config/schema/types/__tests__/literal_type.test.ts
+++ b/src/core/server/config/schema/types/literal_type.test.ts
@@ -17,7 +17,7 @@
* under the License.
*/
-import { schema } from '../..';
+import { schema } from '..';
const { literal } = schema;
diff --git a/src/core/server/config/schema/types/__tests__/map_of_type.test.ts b/src/core/server/config/schema/types/map_of_type.test.ts
similarity index 98%
rename from src/core/server/config/schema/types/__tests__/map_of_type.test.ts
rename to src/core/server/config/schema/types/map_of_type.test.ts
index ed4e12f162c59..1b72d39fcec26 100644
--- a/src/core/server/config/schema/types/__tests__/map_of_type.test.ts
+++ b/src/core/server/config/schema/types/map_of_type.test.ts
@@ -17,7 +17,7 @@
* under the License.
*/
-import { schema } from '../..';
+import { schema } from '..';
test('handles object as input', () => {
const type = schema.mapOf(schema.string(), schema.string());
diff --git a/src/core/server/config/schema/types/__tests__/maybe_type.test.ts b/src/core/server/config/schema/types/maybe_type.test.ts
similarity index 98%
rename from src/core/server/config/schema/types/__tests__/maybe_type.test.ts
rename to src/core/server/config/schema/types/maybe_type.test.ts
index 950987763baf1..b29f504c03b32 100644
--- a/src/core/server/config/schema/types/__tests__/maybe_type.test.ts
+++ b/src/core/server/config/schema/types/maybe_type.test.ts
@@ -17,7 +17,7 @@
* under the License.
*/
-import { schema } from '../..';
+import { schema } from '..';
test('returns value if specified', () => {
const type = schema.maybe(schema.string());
diff --git a/src/core/server/config/schema/types/__tests__/number_type.test.ts b/src/core/server/config/schema/types/number_type.test.ts
similarity index 98%
rename from src/core/server/config/schema/types/__tests__/number_type.test.ts
rename to src/core/server/config/schema/types/number_type.test.ts
index dd6be2631d28c..b85d5113563eb 100644
--- a/src/core/server/config/schema/types/__tests__/number_type.test.ts
+++ b/src/core/server/config/schema/types/number_type.test.ts
@@ -17,7 +17,7 @@
* under the License.
*/
-import { schema } from '../..';
+import { schema } from '..';
test('returns value by default', () => {
expect(schema.number().validate(4)).toBe(4);
diff --git a/src/core/server/config/schema/types/__tests__/object_type.test.ts b/src/core/server/config/schema/types/object_type.test.ts
similarity index 99%
rename from src/core/server/config/schema/types/__tests__/object_type.test.ts
rename to src/core/server/config/schema/types/object_type.test.ts
index ec54528c292a0..e0eaabadb8ef5 100644
--- a/src/core/server/config/schema/types/__tests__/object_type.test.ts
+++ b/src/core/server/config/schema/types/object_type.test.ts
@@ -17,7 +17,7 @@
* under the License.
*/
-import { schema } from '../..';
+import { schema } from '..';
test('returns value by default', () => {
const type = schema.object({
diff --git a/src/core/server/config/schema/types/__tests__/one_of_type.test.ts b/src/core/server/config/schema/types/one_of_type.test.ts
similarity index 99%
rename from src/core/server/config/schema/types/__tests__/one_of_type.test.ts
rename to src/core/server/config/schema/types/one_of_type.test.ts
index e2f0f9688544a..72119e761590b 100644
--- a/src/core/server/config/schema/types/__tests__/one_of_type.test.ts
+++ b/src/core/server/config/schema/types/one_of_type.test.ts
@@ -17,7 +17,7 @@
* under the License.
*/
-import { schema } from '../..';
+import { schema } from '..';
test('handles string', () => {
expect(schema.oneOf([schema.string()]).validate('test')).toBe('test');
diff --git a/src/core/server/config/schema/types/__tests__/string_type.test.ts b/src/core/server/config/schema/types/string_type.test.ts
similarity index 99%
rename from src/core/server/config/schema/types/__tests__/string_type.test.ts
rename to src/core/server/config/schema/types/string_type.test.ts
index f9415a0ac2506..193d85d290731 100644
--- a/src/core/server/config/schema/types/__tests__/string_type.test.ts
+++ b/src/core/server/config/schema/types/string_type.test.ts
@@ -17,7 +17,7 @@
* under the License.
*/
-import { schema } from '../..';
+import { schema } from '..';
test('returns value is string and defined', () => {
expect(schema.string().validate('test')).toBe('test');
diff --git a/src/core/server/http/__tests__/__snapshots__/http_config.test.ts.snap b/src/core/server/http/__snapshots__/http_config.test.ts.snap
similarity index 100%
rename from src/core/server/http/__tests__/__snapshots__/http_config.test.ts.snap
rename to src/core/server/http/__snapshots__/http_config.test.ts.snap
diff --git a/src/core/server/http/__tests__/__snapshots__/http_server.test.ts.snap b/src/core/server/http/__snapshots__/http_server.test.ts.snap
similarity index 100%
rename from src/core/server/http/__tests__/__snapshots__/http_server.test.ts.snap
rename to src/core/server/http/__snapshots__/http_server.test.ts.snap
diff --git a/src/core/server/http/__tests__/__snapshots__/http_service.test.ts.snap b/src/core/server/http/__snapshots__/http_service.test.ts.snap
similarity index 100%
rename from src/core/server/http/__tests__/__snapshots__/http_service.test.ts.snap
rename to src/core/server/http/__snapshots__/http_service.test.ts.snap
diff --git a/src/core/server/http/__tests__/__snapshots__/https_redirect_server.test.ts.snap b/src/core/server/http/__snapshots__/https_redirect_server.test.ts.snap
similarity index 100%
rename from src/core/server/http/__tests__/__snapshots__/https_redirect_server.test.ts.snap
rename to src/core/server/http/__snapshots__/https_redirect_server.test.ts.snap
diff --git a/src/core/server/http/__tests__/http_config.test.ts b/src/core/server/http/http_config.test.ts
similarity index 99%
rename from src/core/server/http/__tests__/http_config.test.ts
rename to src/core/server/http/http_config.test.ts
index 45bd8962fc0df..54d28ef921fcf 100644
--- a/src/core/server/http/__tests__/http_config.test.ts
+++ b/src/core/server/http/http_config.test.ts
@@ -17,7 +17,7 @@
* under the License.
*/
-import { HttpConfig } from '../http_config';
+import { HttpConfig } from '.';
test('has defaults for config', () => {
const httpSchema = HttpConfig.schema;
diff --git a/src/core/server/http/__tests__/http_server.test.ts b/src/core/server/http/http_server.test.ts
similarity index 98%
rename from src/core/server/http/__tests__/http_server.test.ts
rename to src/core/server/http/http_server.test.ts
index 42f93a13e1c80..704a6ddf97aba 100644
--- a/src/core/server/http/__tests__/http_server.test.ts
+++ b/src/core/server/http/http_server.test.ts
@@ -26,11 +26,10 @@ jest.mock('fs', () => ({
import Chance from 'chance';
import supertest from 'supertest';
-import { ByteSizeValue } from '../../config/schema';
-import { logger } from '../../logging/__mocks__';
-import { HttpConfig } from '../http_config';
-import { HttpServer } from '../http_server';
-import { Router } from '../router';
+import { HttpConfig, Router } from '.';
+import { ByteSizeValue } from '../config/schema';
+import { logger } from '../logging/__mocks__';
+import { HttpServer } from './http_server';
const chance = new Chance();
diff --git a/src/core/server/http/__tests__/http_service.test.ts b/src/core/server/http/http_service.test.ts
similarity index 95%
rename from src/core/server/http/__tests__/http_service.test.ts
rename to src/core/server/http/http_service.test.ts
index 1c6d259848117..a42ac26745c60 100644
--- a/src/core/server/http/__tests__/http_service.test.ts
+++ b/src/core/server/http/http_service.test.ts
@@ -19,16 +19,14 @@
const mockHttpServer = jest.fn();
-jest.mock('../http_server', () => ({
+jest.mock('./http_server', () => ({
HttpServer: mockHttpServer,
}));
import { noop } from 'lodash';
import { BehaviorSubject } from 'rxjs';
-import { logger } from '../../logging/__mocks__';
-import { HttpConfig } from '../http_config';
-import { HttpService } from '../http_service';
-import { Router } from '../router';
+import { HttpConfig, HttpService, Router } from '.';
+import { logger } from '../logging/__mocks__';
beforeEach(() => {
logger.mockClear();
diff --git a/src/core/server/http/__tests__/https_redirect_server.test.ts b/src/core/server/http/https_redirect_server.test.ts
similarity index 92%
rename from src/core/server/http/__tests__/https_redirect_server.test.ts
rename to src/core/server/http/https_redirect_server.test.ts
index c92691a679ef0..6d9443335a62b 100644
--- a/src/core/server/http/__tests__/https_redirect_server.test.ts
+++ b/src/core/server/http/https_redirect_server.test.ts
@@ -25,10 +25,10 @@ import Chance from 'chance';
import { Server } from 'http';
import supertest from 'supertest';
-import { ByteSizeValue } from '../../config/schema';
-import { logger } from '../../logging/__mocks__';
-import { HttpConfig } from '../http_config';
-import { HttpsRedirectServer } from '../https_redirect_server';
+import { HttpConfig } from '.';
+import { ByteSizeValue } from '../config/schema';
+import { logger } from '../logging/__mocks__';
+import { HttpsRedirectServer } from './https_redirect_server';
const chance = new Chance();
diff --git a/src/core/server/index.test.ts b/src/core/server/index.test.ts
index 8a83d8d500b81..d6bd19d36ce3c 100644
--- a/src/core/server/index.test.ts
+++ b/src/core/server/index.test.ts
@@ -30,7 +30,7 @@ jest.mock('./legacy_compat/legacy_service', () => ({
import { BehaviorSubject } from 'rxjs';
import { Server } from '.';
import { Env } from './config';
-import { getEnvOptions } from './config/__tests__/__mocks__/env';
+import { getEnvOptions } from './config/__mocks__/env';
import { logger } from './logging/__mocks__';
const mockConfigService = { atPath: jest.fn(), getUnusedPaths: jest.fn().mockReturnValue([]) };
diff --git a/src/core/server/legacy_compat/__tests__/__snapshots__/legacy_service.test.ts.snap b/src/core/server/legacy_compat/__snapshots__/legacy_service.test.ts.snap
similarity index 100%
rename from src/core/server/legacy_compat/__tests__/__snapshots__/legacy_service.test.ts.snap
rename to src/core/server/legacy_compat/__snapshots__/legacy_service.test.ts.snap
diff --git a/src/core/server/legacy_compat/config/__tests__/__snapshots__/legacy_object_to_config_adapter.test.ts.snap b/src/core/server/legacy_compat/config/__snapshots__/legacy_object_to_config_adapter.test.ts.snap
similarity index 100%
rename from src/core/server/legacy_compat/config/__tests__/__snapshots__/legacy_object_to_config_adapter.test.ts.snap
rename to src/core/server/legacy_compat/config/__snapshots__/legacy_object_to_config_adapter.test.ts.snap
diff --git a/src/core/server/legacy_compat/config/__tests__/legacy_object_to_config_adapter.test.ts b/src/core/server/legacy_compat/config/legacy_object_to_config_adapter.test.ts
similarity index 98%
rename from src/core/server/legacy_compat/config/__tests__/legacy_object_to_config_adapter.test.ts
rename to src/core/server/legacy_compat/config/legacy_object_to_config_adapter.test.ts
index b465a55be4243..afa3cf03fe9a9 100644
--- a/src/core/server/legacy_compat/config/__tests__/legacy_object_to_config_adapter.test.ts
+++ b/src/core/server/legacy_compat/config/legacy_object_to_config_adapter.test.ts
@@ -17,7 +17,7 @@
* under the License.
*/
-import { LegacyObjectToConfigAdapter } from '../legacy_object_to_config_adapter';
+import { LegacyObjectToConfigAdapter } from './legacy_object_to_config_adapter';
describe('#get', () => {
test('correctly handles paths that do not exist.', () => {
diff --git a/src/core/server/legacy_compat/__tests__/legacy_platform_proxy.test.ts b/src/core/server/legacy_compat/legacy_platform_proxy.test.ts
similarity index 98%
rename from src/core/server/legacy_compat/__tests__/legacy_platform_proxy.test.ts
rename to src/core/server/legacy_compat/legacy_platform_proxy.test.ts
index 8330bbb8d74db..cc7436ce32170 100644
--- a/src/core/server/legacy_compat/__tests__/legacy_platform_proxy.test.ts
+++ b/src/core/server/legacy_compat/legacy_platform_proxy.test.ts
@@ -19,7 +19,7 @@
import { Server } from 'net';
-import { LegacyPlatformProxy } from '../legacy_platform_proxy';
+import { LegacyPlatformProxy } from './legacy_platform_proxy';
let server: jest.Mocked;
let proxy: LegacyPlatformProxy;
diff --git a/src/core/server/legacy_compat/__tests__/legacy_service.test.ts b/src/core/server/legacy_compat/legacy_service.test.ts
similarity index 95%
rename from src/core/server/legacy_compat/__tests__/legacy_service.test.ts
rename to src/core/server/legacy_compat/legacy_service.test.ts
index dc16709861084..70c71e3b4b0b8 100644
--- a/src/core/server/legacy_compat/__tests__/legacy_service.test.ts
+++ b/src/core/server/legacy_compat/legacy_service.test.ts
@@ -19,20 +19,20 @@
import { BehaviorSubject, Subject, throwError } from 'rxjs';
-jest.mock('../legacy_platform_proxy');
-jest.mock('../../../../server/kbn_server');
-jest.mock('../../../../cli/cluster/cluster_manager');
+jest.mock('./legacy_platform_proxy');
+jest.mock('../../../server/kbn_server');
+jest.mock('../../../cli/cluster/cluster_manager');
import { first } from 'rxjs/operators';
+import { LegacyService } from '.';
// @ts-ignore: implicit any for JS file
-import MockClusterManager from '../../../../cli/cluster/cluster_manager';
+import MockClusterManager from '../../../cli/cluster/cluster_manager';
// @ts-ignore: implicit any for JS file
-import MockKbnServer from '../../../../server/kbn_server';
-import { Config, ConfigService, Env, ObjectToConfigAdapter } from '../../config';
-import { getEnvOptions } from '../../config/__tests__/__mocks__/env';
-import { logger } from '../../logging/__mocks__';
-import { LegacyPlatformProxy } from '../legacy_platform_proxy';
-import { LegacyService } from '../legacy_service';
+import MockKbnServer from '../../../server/kbn_server';
+import { Config, ConfigService, Env, ObjectToConfigAdapter } from '../config';
+import { getEnvOptions } from '../config/__mocks__/env';
+import { logger } from '../logging/__mocks__';
+import { LegacyPlatformProxy } from './legacy_platform_proxy';
const MockLegacyPlatformProxy: jest.Mock = LegacyPlatformProxy as any;
diff --git a/src/core/server/legacy_compat/logging/appenders/__tests__/__snapshots__/legacy_appender.test.ts.snap b/src/core/server/legacy_compat/logging/appenders/__snapshots__/legacy_appender.test.ts.snap
similarity index 100%
rename from src/core/server/legacy_compat/logging/appenders/__tests__/__snapshots__/legacy_appender.test.ts.snap
rename to src/core/server/legacy_compat/logging/appenders/__snapshots__/legacy_appender.test.ts.snap
diff --git a/src/core/server/legacy_compat/logging/appenders/__tests__/legacy_appender.test.ts b/src/core/server/legacy_compat/logging/appenders/legacy_appender.test.ts
similarity index 93%
rename from src/core/server/legacy_compat/logging/appenders/__tests__/legacy_appender.test.ts
rename to src/core/server/legacy_compat/logging/appenders/legacy_appender.test.ts
index 5f62a853666d9..adc5dcae3ec9d 100644
--- a/src/core/server/legacy_compat/logging/appenders/__tests__/legacy_appender.test.ts
+++ b/src/core/server/legacy_compat/logging/appenders/legacy_appender.test.ts
@@ -17,12 +17,12 @@
* under the License.
*/
-jest.mock('../../legacy_logging_server');
+jest.mock('../legacy_logging_server');
-import { LogLevel } from '../../../../logging/log_level';
-import { LogRecord } from '../../../../logging/log_record';
-import { LegacyLoggingServer } from '../../legacy_logging_server';
-import { LegacyAppender } from '../legacy_appender';
+import { LogLevel } from '../../../logging/log_level';
+import { LogRecord } from '../../../logging/log_record';
+import { LegacyLoggingServer } from '../legacy_logging_server';
+import { LegacyAppender } from './legacy_appender';
afterEach(() => (LegacyLoggingServer as any).mockClear());
diff --git a/src/core/server/logging/__tests__/__snapshots__/logging_config.test.ts.snap b/src/core/server/logging/__snapshots__/logging_config.test.ts.snap
similarity index 100%
rename from src/core/server/logging/__tests__/__snapshots__/logging_config.test.ts.snap
rename to src/core/server/logging/__snapshots__/logging_config.test.ts.snap
diff --git a/src/core/server/logging/__tests__/__snapshots__/logging_service.test.ts.snap b/src/core/server/logging/__snapshots__/logging_service.test.ts.snap
similarity index 100%
rename from src/core/server/logging/__tests__/__snapshots__/logging_service.test.ts.snap
rename to src/core/server/logging/__snapshots__/logging_service.test.ts.snap
diff --git a/src/core/server/logging/appenders/__tests__/appenders.test.ts b/src/core/server/logging/appenders/appenders.test.ts
similarity index 88%
rename from src/core/server/logging/appenders/__tests__/appenders.test.ts
rename to src/core/server/logging/appenders/appenders.test.ts
index f141de991f453..2103f9d8187b2 100644
--- a/src/core/server/logging/appenders/__tests__/appenders.test.ts
+++ b/src/core/server/logging/appenders/appenders.test.ts
@@ -18,8 +18,8 @@
*/
const mockCreateLayout = jest.fn();
-jest.mock('../../layouts/layouts', () => {
- const { schema } = require('../../../config/schema');
+jest.mock('../layouts/layouts', () => {
+ const { schema } = require('../../config/schema');
return {
Layouts: {
configSchema: schema.object({ kind: schema.literal('mock') }),
@@ -28,10 +28,10 @@ jest.mock('../../layouts/layouts', () => {
};
});
-import { LegacyAppender } from '../../../legacy_compat/logging/appenders/legacy_appender';
-import { Appenders } from '../appenders';
-import { ConsoleAppender } from '../console/console_appender';
-import { FileAppender } from '../file/file_appender';
+import { LegacyAppender } from '../../legacy_compat/logging/appenders/legacy_appender';
+import { Appenders } from './appenders';
+import { ConsoleAppender } from './console/console_appender';
+import { FileAppender } from './file/file_appender';
beforeEach(() => {
mockCreateLayout.mockReset();
diff --git a/src/core/server/logging/appenders/__tests__/buffer_appender.test.ts b/src/core/server/logging/appenders/buffer/buffer_appender.test.ts
similarity index 97%
rename from src/core/server/logging/appenders/__tests__/buffer_appender.test.ts
rename to src/core/server/logging/appenders/buffer/buffer_appender.test.ts
index cdf2714f44e29..453a29271c582 100644
--- a/src/core/server/logging/appenders/__tests__/buffer_appender.test.ts
+++ b/src/core/server/logging/appenders/buffer/buffer_appender.test.ts
@@ -19,7 +19,7 @@
import { LogLevel } from '../../log_level';
import { LogRecord } from '../../log_record';
-import { BufferAppender } from '../buffer/buffer_appender';
+import { BufferAppender } from './buffer_appender';
test('`flush()` does not return any record buffered at the beginning.', () => {
const appender = new BufferAppender();
diff --git a/src/core/server/logging/appenders/__tests__/console_appender.test.ts b/src/core/server/logging/appenders/console/console_appender.test.ts
similarity index 97%
rename from src/core/server/logging/appenders/__tests__/console_appender.test.ts
rename to src/core/server/logging/appenders/console/console_appender.test.ts
index 35128bd6ba1fd..fc885e5f58a11 100644
--- a/src/core/server/logging/appenders/__tests__/console_appender.test.ts
+++ b/src/core/server/logging/appenders/console/console_appender.test.ts
@@ -30,7 +30,7 @@ jest.mock('../../layouts/layouts', () => {
import { LogLevel } from '../../log_level';
import { LogRecord } from '../../log_record';
-import { ConsoleAppender } from '../console/console_appender';
+import { ConsoleAppender } from './console_appender';
test('`configSchema` creates correct schema.', () => {
const appenderSchema = ConsoleAppender.configSchema;
diff --git a/src/core/server/logging/appenders/__tests__/file_appender.test.ts b/src/core/server/logging/appenders/file/file_appender.test.ts
similarity index 98%
rename from src/core/server/logging/appenders/__tests__/file_appender.test.ts
rename to src/core/server/logging/appenders/file/file_appender.test.ts
index 69b4980dff1f0..cc8f0196bff7c 100644
--- a/src/core/server/logging/appenders/__tests__/file_appender.test.ts
+++ b/src/core/server/logging/appenders/file/file_appender.test.ts
@@ -33,7 +33,7 @@ jest.mock('fs', () => ({ createWriteStream: mockCreateWriteStream }));
import { LogLevel } from '../../log_level';
import { LogRecord } from '../../log_record';
-import { FileAppender } from '../file/file_appender';
+import { FileAppender } from './file_appender';
const tickMs = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
diff --git a/src/core/server/logging/layouts/__tests__/__snapshots__/json_layout.test.ts.snap b/src/core/server/logging/layouts/__snapshots__/json_layout.test.ts.snap
similarity index 100%
rename from src/core/server/logging/layouts/__tests__/__snapshots__/json_layout.test.ts.snap
rename to src/core/server/logging/layouts/__snapshots__/json_layout.test.ts.snap
diff --git a/src/core/server/logging/layouts/__tests__/__snapshots__/pattern_layout.test.ts.snap b/src/core/server/logging/layouts/__snapshots__/pattern_layout.test.ts.snap
similarity index 100%
rename from src/core/server/logging/layouts/__tests__/__snapshots__/pattern_layout.test.ts.snap
rename to src/core/server/logging/layouts/__snapshots__/pattern_layout.test.ts.snap
diff --git a/src/core/server/logging/layouts/__tests__/json_layout.test.ts b/src/core/server/logging/layouts/json_layout.test.ts
similarity index 94%
rename from src/core/server/logging/layouts/__tests__/json_layout.test.ts
rename to src/core/server/logging/layouts/json_layout.test.ts
index ec94d023b2d64..49b8ddef07a63 100644
--- a/src/core/server/logging/layouts/__tests__/json_layout.test.ts
+++ b/src/core/server/logging/layouts/json_layout.test.ts
@@ -17,9 +17,9 @@
* under the License.
*/
-import { LogLevel } from '../../log_level';
-import { LogRecord } from '../../log_record';
-import { JsonLayout } from '../json_layout';
+import { LogLevel } from '../log_level';
+import { LogRecord } from '../log_record';
+import { JsonLayout } from './json_layout';
const records: LogRecord[] = [
{
diff --git a/src/core/server/logging/layouts/__tests__/layouts.test.ts b/src/core/server/logging/layouts/layouts.test.ts
similarity index 93%
rename from src/core/server/logging/layouts/__tests__/layouts.test.ts
rename to src/core/server/logging/layouts/layouts.test.ts
index ca70710233fee..aa1c54c846bc6 100644
--- a/src/core/server/logging/layouts/__tests__/layouts.test.ts
+++ b/src/core/server/logging/layouts/layouts.test.ts
@@ -17,9 +17,9 @@
* under the License.
*/
-import { JsonLayout } from '../json_layout';
-import { Layouts } from '../layouts';
-import { PatternLayout } from '../pattern_layout';
+import { JsonLayout } from './json_layout';
+import { Layouts } from './layouts';
+import { PatternLayout } from './pattern_layout';
test('`configSchema` creates correct schema for `pattern` layout.', () => {
const layoutsSchema = Layouts.configSchema;
diff --git a/src/core/server/logging/layouts/__tests__/pattern_layout.test.ts b/src/core/server/logging/layouts/pattern_layout.test.ts
similarity index 93%
rename from src/core/server/logging/layouts/__tests__/pattern_layout.test.ts
rename to src/core/server/logging/layouts/pattern_layout.test.ts
index 4e6ddf2c097ed..ae8b39b9cc99a 100644
--- a/src/core/server/logging/layouts/__tests__/pattern_layout.test.ts
+++ b/src/core/server/logging/layouts/pattern_layout.test.ts
@@ -17,10 +17,10 @@
* under the License.
*/
-import { stripAnsiSnapshotSerializer } from '../../../../test_helpers/strip_ansi_snapshot_serializer';
-import { LogLevel } from '../../log_level';
-import { LogRecord } from '../../log_record';
-import { PatternLayout } from '../pattern_layout';
+import { stripAnsiSnapshotSerializer } from '../../../test_helpers/strip_ansi_snapshot_serializer';
+import { LogLevel } from '../log_level';
+import { LogRecord } from '../log_record';
+import { PatternLayout } from './pattern_layout';
const records: LogRecord[] = [
{
diff --git a/src/core/server/logging/__tests__/log_level.test.ts b/src/core/server/logging/log_level.test.ts
similarity index 98%
rename from src/core/server/logging/__tests__/log_level.test.ts
rename to src/core/server/logging/log_level.test.ts
index 43de344b34cff..1f86cf21037a6 100644
--- a/src/core/server/logging/__tests__/log_level.test.ts
+++ b/src/core/server/logging/log_level.test.ts
@@ -17,7 +17,7 @@
* under the License.
*/
-import { LogLevel } from '../log_level';
+import { LogLevel } from './log_level';
const allLogLevels = [
LogLevel.Off,
diff --git a/src/core/server/logging/__tests__/logger.test.ts b/src/core/server/logging/logger.test.ts
similarity index 98%
rename from src/core/server/logging/__tests__/logger.test.ts
rename to src/core/server/logging/logger.test.ts
index 2dc16178fb47b..61eaa4912185b 100644
--- a/src/core/server/logging/__tests__/logger.test.ts
+++ b/src/core/server/logging/logger.test.ts
@@ -17,10 +17,10 @@
* under the License.
*/
-import { Appender } from '../appenders/appenders';
-import { LogLevel } from '../log_level';
-import { BaseLogger } from '../logger';
-import { LoggingConfig } from '../logging_config';
+import { LoggingConfig } from '.';
+import { Appender } from './appenders/appenders';
+import { LogLevel } from './log_level';
+import { BaseLogger } from './logger';
const context = LoggingConfig.getLoggerContext(['context', 'parent', 'child']);
let appenderMocks: Appender[];
diff --git a/src/core/server/logging/__tests__/logger_adapter.test.ts b/src/core/server/logging/logger_adapter.test.ts
similarity index 97%
rename from src/core/server/logging/__tests__/logger_adapter.test.ts
rename to src/core/server/logging/logger_adapter.test.ts
index 25a9c01b108d6..075e8f4d47ffe 100644
--- a/src/core/server/logging/__tests__/logger_adapter.test.ts
+++ b/src/core/server/logging/logger_adapter.test.ts
@@ -17,8 +17,8 @@
* under the License.
*/
-import { Logger } from '../logger';
-import { LoggerAdapter } from '../logger_adapter';
+import { Logger } from '.';
+import { LoggerAdapter } from './logger_adapter';
test('proxies all method calls to the internal logger.', () => {
const internalLogger: Logger = {
diff --git a/src/core/server/logging/__tests__/logging_config.test.ts b/src/core/server/logging/logging_config.test.ts
similarity index 98%
rename from src/core/server/logging/__tests__/logging_config.test.ts
rename to src/core/server/logging/logging_config.test.ts
index 2f1f1d9f2f7c0..f21b5aaf3c1a7 100644
--- a/src/core/server/logging/__tests__/logging_config.test.ts
+++ b/src/core/server/logging/logging_config.test.ts
@@ -17,7 +17,7 @@
* under the License.
*/
-import { LoggingConfig } from '../logging_config';
+import { LoggingConfig } from '.';
test('`schema` creates correct schema with defaults.', () => {
const loggingConfigSchema = LoggingConfig.schema;
diff --git a/src/core/server/logging/__tests__/logging_service.test.ts b/src/core/server/logging/logging_service.test.ts
similarity index 98%
rename from src/core/server/logging/__tests__/logging_service.test.ts
rename to src/core/server/logging/logging_service.test.ts
index eb452376d6ccc..b6aeb88b50052 100644
--- a/src/core/server/logging/__tests__/logging_service.test.ts
+++ b/src/core/server/logging/logging_service.test.ts
@@ -32,8 +32,7 @@ jest.spyOn(global, 'Date').mockImplementation(() => timestamp);
import { createWriteStream } from 'fs';
const mockCreateWriteStream = createWriteStream as jest.Mock;
-import { LoggingConfig } from '../logging_config';
-import { LoggingService } from '../logging_service';
+import { LoggingConfig, LoggingService } from '.';
let service: LoggingService;
beforeEach(() => (service = new LoggingService()));
diff --git a/src/core/server/root/__tests__/__snapshots__/index.test.ts.snap b/src/core/server/root/__snapshots__/index.test.ts.snap
similarity index 100%
rename from src/core/server/root/__tests__/__snapshots__/index.test.ts.snap
rename to src/core/server/root/__snapshots__/index.test.ts.snap
diff --git a/src/core/server/root/__tests__/index.test.ts b/src/core/server/root/index.test.ts
similarity index 95%
rename from src/core/server/root/__tests__/index.test.ts
rename to src/core/server/root/index.test.ts
index 851e7c9dca85e..97308ef484f4f 100644
--- a/src/core/server/root/__tests__/index.test.ts
+++ b/src/core/server/root/index.test.ts
@@ -18,24 +18,24 @@
*/
const mockLoggingService = { asLoggerFactory: jest.fn(), upgrade: jest.fn(), stop: jest.fn() };
-jest.mock('../../logging', () => ({
+jest.mock('../logging', () => ({
LoggingService: jest.fn(() => mockLoggingService),
}));
const mockConfigService = { atPath: jest.fn() };
-jest.mock('../../config/config_service', () => ({
+jest.mock('../config/config_service', () => ({
ConfigService: jest.fn(() => mockConfigService),
}));
const mockServer = { start: jest.fn(), stop: jest.fn() };
-jest.mock('../../', () => ({ Server: jest.fn(() => mockServer) }));
+jest.mock('../', () => ({ Server: jest.fn(() => mockServer) }));
import { BehaviorSubject } from 'rxjs';
import { filter, first } from 'rxjs/operators';
-import { Root } from '../';
-import { Config, Env } from '../../config';
-import { getEnvOptions } from '../../config/__tests__/__mocks__/env';
-import { logger } from '../../logging/__mocks__';
+import { Root } from '.';
+import { Config, Env } from '../config';
+import { getEnvOptions } from '../config/__mocks__/env';
+import { logger } from '../logging/__mocks__';
const env = new Env('.', getEnvOptions());
const config$ = new BehaviorSubject({} as Config);
diff --git a/src/core/utils/__tests__/__snapshots__/get.test.ts.snap b/src/core/utils/__snapshots__/get.test.ts.snap
similarity index 100%
rename from src/core/utils/__tests__/__snapshots__/get.test.ts.snap
rename to src/core/utils/__snapshots__/get.test.ts.snap
diff --git a/src/core/utils/__tests__/get.test.ts b/src/core/utils/get.test.ts
similarity index 97%
rename from src/core/utils/__tests__/get.test.ts
rename to src/core/utils/get.test.ts
index a93ad6f6d708e..f409638b5d491 100644
--- a/src/core/utils/__tests__/get.test.ts
+++ b/src/core/utils/get.test.ts
@@ -17,7 +17,7 @@
* under the License.
*/
-import { get } from '../get';
+import { get } from './get';
const obj = {
bar: {
diff --git a/src/core/utils/__tests__/url.test.ts b/src/core/utils/url.test.ts
similarity index 98%
rename from src/core/utils/__tests__/url.test.ts
rename to src/core/utils/url.test.ts
index 6ff3a75d6e725..f890b2ec13db3 100644
--- a/src/core/utils/__tests__/url.test.ts
+++ b/src/core/utils/url.test.ts
@@ -17,7 +17,7 @@
* under the License.
*/
-import { modifyUrl } from '../url';
+import { modifyUrl } from './url';
describe('modifyUrl()', () => {
test('throws an error with invalid input', () => {
From 76408be650953b3889176f9ec0d86b9d84c6d074 Mon Sep 17 00:00:00 2001
From: Walter Rafelsberger
Date: Fri, 7 Sep 2018 10:03:28 +0200
Subject: [PATCH 41/68] [ML] Anomaly Explorer Charts jest tests. (#22759)
After the migration to React in #22622, this PR adds more test coverage based on jest to the Anomaly Explorer charts. This is done as preparation for some further refactoring (#22626) and the integration of the support for charts for the rare detector (#21163). The tests use mock data based on a standard multi-metric job using the farequote dataset.
Besided the tests this PR includes the following changes:
- In explore_series.js the path is no longer a concatenanted string but now using a template literal
- The exploreSeries() function is no longer calling window.open by itself, it just returns the link
because of the above, renamed exploreSeries() to getExploreSeriesLink() and moved it to chart_utils.js
- explorer_charts_container_service.js is no longer requiring jQuery itself, it now receiving the required element as a factory argument. Further work on this is planned to get rid of jQuery.
---
.../messagebar/messagebar_service.js | 2 +-
.../__mocks__/mock_anomaly_chart_records.json | 68 ++
.../__mocks__/mock_anomaly_record.json | 33 +
.../__mocks__/mock_chart_data.js | 26 +
.../__mocks__/mock_detectors_by_job.json | 11 +
.../__mocks__/mock_job_config.json | 88 +++
.../mock_series_config_filebeat.json | 57 ++
.../mock_series_promises_response.json | 231 +++++++
...explorer_chart_config_builder.test.js.snap | 52 ++
.../explorer_chart_tooltip.test.js.snap | 24 +
.../explorer_charts_container.test.js.snap | 54 ++
...orer_charts_container_service.test.js.snap | 616 ++++++++++++++++++
.../explorer_charts/explore_series.js | 74 ---
.../explorer_charts/explorer_chart.test.js | 69 +-
.../explorer_chart_config_builder.test.js | 27 +
.../explorer_chart_tooltip.test.js | 28 +
.../explorer_charts_container.js | 5 +-
.../explorer_charts_container.test.js | 76 +++
.../explorer_charts_container_directive.js | 5 +-
.../explorer_charts_container_service.js | 6 +-
.../explorer_charts_container_service.test.js | 127 ++++
.../watcher/create_watch_service.js | 3 +-
.../public/services/ml_api_service/filters.js | 2 +-
.../public/services/ml_api_service/index.js | 2 +-
.../ml/public/services/ml_api_service/jobs.js | 2 +-
.../public/services/ml_api_service/results.js | 2 +-
.../ml/public/services/results_service.js | 8 +-
.../ml/public/util/chart_config_builder.js | 2 +-
x-pack/plugins/ml/public/util/chart_utils.js | 61 ++
.../ml/public/util/chart_utils.test.js | 63 ++
30 files changed, 1664 insertions(+), 160 deletions(-)
create mode 100644 x-pack/plugins/ml/public/explorer/explorer_charts/__mocks__/mock_anomaly_chart_records.json
create mode 100644 x-pack/plugins/ml/public/explorer/explorer_charts/__mocks__/mock_anomaly_record.json
create mode 100644 x-pack/plugins/ml/public/explorer/explorer_charts/__mocks__/mock_chart_data.js
create mode 100644 x-pack/plugins/ml/public/explorer/explorer_charts/__mocks__/mock_detectors_by_job.json
create mode 100644 x-pack/plugins/ml/public/explorer/explorer_charts/__mocks__/mock_job_config.json
create mode 100644 x-pack/plugins/ml/public/explorer/explorer_charts/__mocks__/mock_series_config_filebeat.json
create mode 100644 x-pack/plugins/ml/public/explorer/explorer_charts/__mocks__/mock_series_promises_response.json
create mode 100644 x-pack/plugins/ml/public/explorer/explorer_charts/__snapshots__/explorer_chart_config_builder.test.js.snap
create mode 100644 x-pack/plugins/ml/public/explorer/explorer_charts/__snapshots__/explorer_chart_tooltip.test.js.snap
create mode 100644 x-pack/plugins/ml/public/explorer/explorer_charts/__snapshots__/explorer_charts_container.test.js.snap
create mode 100644 x-pack/plugins/ml/public/explorer/explorer_charts/__snapshots__/explorer_charts_container_service.test.js.snap
delete mode 100644 x-pack/plugins/ml/public/explorer/explorer_charts/explore_series.js
create mode 100644 x-pack/plugins/ml/public/explorer/explorer_charts/explorer_chart_config_builder.test.js
create mode 100644 x-pack/plugins/ml/public/explorer/explorer_charts/explorer_chart_tooltip.test.js
create mode 100644 x-pack/plugins/ml/public/explorer/explorer_charts/explorer_charts_container.test.js
create mode 100644 x-pack/plugins/ml/public/explorer/explorer_charts/explorer_charts_container_service.test.js
create mode 100644 x-pack/plugins/ml/public/util/chart_utils.test.js
diff --git a/x-pack/plugins/ml/public/components/messagebar/messagebar_service.js b/x-pack/plugins/ml/public/components/messagebar/messagebar_service.js
index 3bbf7e79c683e..4395be2a9fb4b 100644
--- a/x-pack/plugins/ml/public/components/messagebar/messagebar_service.js
+++ b/x-pack/plugins/ml/public/components/messagebar/messagebar_service.js
@@ -6,7 +6,7 @@
import { notify } from 'ui/notify';
-import { MLRequestFailure } from 'plugins/ml/util/ml_error';
+import { MLRequestFailure } from '../../util/ml_error';
const messages = [];
diff --git a/x-pack/plugins/ml/public/explorer/explorer_charts/__mocks__/mock_anomaly_chart_records.json b/x-pack/plugins/ml/public/explorer/explorer_charts/__mocks__/mock_anomaly_chart_records.json
new file mode 100644
index 0000000000000..3f106d399f852
--- /dev/null
+++ b/x-pack/plugins/ml/public/explorer/explorer_charts/__mocks__/mock_anomaly_chart_records.json
@@ -0,0 +1,68 @@
+[
+ {
+ "job_id": "mock-job-id",
+ "result_type": "record",
+ "probability": 1.6552181439816634e-32,
+ "record_score": 98.56065708456248,
+ "initial_record_score": 98.56065708456248,
+ "bucket_span": 900,
+ "detector_index": 0,
+ "is_interim": false,
+ "timestamp": 1486656000000,
+ "partition_field_name": "airline",
+ "partition_field_value": "AAL",
+ "function": "mean",
+ "function_description": "mean",
+ "typical": [
+ 99.81123207526203
+ ],
+ "actual": [
+ 242.3568918440077
+ ],
+ "field_name": "responsetime",
+ "influencers": [
+ {
+ "influencer_field_name": "airline",
+ "influencer_field_values": [
+ "AAL"
+ ]
+ }
+ ],
+ "airline": [
+ "AAL"
+ ]
+ },
+ {
+ "job_id": "mock-job-id",
+ "result_type": "record",
+ "probability": 2.6276047868032343e-28,
+ "record_score": 96.93718,
+ "initial_record_score": 92.70812367638732,
+ "bucket_span": 900,
+ "detector_index": 0,
+ "is_interim": false,
+ "timestamp": 1486656900000,
+ "partition_field_name": "airline",
+ "partition_field_value": "AAL",
+ "function": "mean",
+ "function_description": "mean",
+ "typical": [
+ 100.02884159032787
+ ],
+ "actual": [
+ 282.02533259111306
+ ],
+ "field_name": "responsetime",
+ "influencers": [
+ {
+ "influencer_field_name": "airline",
+ "influencer_field_values": [
+ "AAL"
+ ]
+ }
+ ],
+ "airline": [
+ "AAL"
+ ]
+ }
+]
diff --git a/x-pack/plugins/ml/public/explorer/explorer_charts/__mocks__/mock_anomaly_record.json b/x-pack/plugins/ml/public/explorer/explorer_charts/__mocks__/mock_anomaly_record.json
new file mode 100644
index 0000000000000..ccc13b6a815a4
--- /dev/null
+++ b/x-pack/plugins/ml/public/explorer/explorer_charts/__mocks__/mock_anomaly_record.json
@@ -0,0 +1,33 @@
+{
+ "job_id": "mock-job-id",
+ "result_type": "record",
+ "probability": 0.000374234162864467,
+ "record_score": 1.3677172011743646,
+ "initial_record_score": 1.3677172011743646,
+ "bucket_span": 900,
+ "detector_index": 0,
+ "is_interim": false,
+ "timestamp": 1486743300000,
+ "partition_field_name": "airline",
+ "partition_field_value": "JAL",
+ "function": "mean",
+ "function_description": "mean",
+ "typical": [
+ 499.9850000350266
+ ],
+ "actual": [
+ 511.4997161865235
+ ],
+ "field_name": "responsetime",
+ "influencers": [
+ {
+ "influencer_field_name": "airline",
+ "influencer_field_values": [
+ "JAL"
+ ]
+ }
+ ],
+ "airline": [
+ "JAL"
+ ]
+}
diff --git a/x-pack/plugins/ml/public/explorer/explorer_charts/__mocks__/mock_chart_data.js b/x-pack/plugins/ml/public/explorer/explorer_charts/__mocks__/mock_chart_data.js
new file mode 100644
index 0000000000000..e150335114a20
--- /dev/null
+++ b/x-pack/plugins/ml/public/explorer/explorer_charts/__mocks__/mock_chart_data.js
@@ -0,0 +1,26 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export const chartData = [
+ {
+ date: new Date('2017-02-23T08:00:00.000Z'),
+ value: 228243469, anomalyScore: 63.32916, numberOfCauses: 1,
+ actual: [228243469], typical: [133107.7703441773]
+ },
+ { date: new Date('2017-02-23T09:00:00.000Z'), value: null },
+ { date: new Date('2017-02-23T10:00:00.000Z'), value: null },
+ { date: new Date('2017-02-23T11:00:00.000Z'), value: null },
+ {
+ date: new Date('2017-02-23T12:00:00.000Z'),
+ value: 625736376, anomalyScore: 97.32085, numberOfCauses: 1,
+ actual: [625736376], typical: [132830.424736973]
+ },
+ {
+ date: new Date('2017-02-23T13:00:00.000Z'),
+ value: 201039318, anomalyScore: 59.83488, numberOfCauses: 1,
+ actual: [201039318], typical: [132739.5267403542]
+ }
+];
diff --git a/x-pack/plugins/ml/public/explorer/explorer_charts/__mocks__/mock_detectors_by_job.json b/x-pack/plugins/ml/public/explorer/explorer_charts/__mocks__/mock_detectors_by_job.json
new file mode 100644
index 0000000000000..f45a2d5b8f2b9
--- /dev/null
+++ b/x-pack/plugins/ml/public/explorer/explorer_charts/__mocks__/mock_detectors_by_job.json
@@ -0,0 +1,11 @@
+{
+ "mock-job-id": [
+ {
+ "detector_description": "mean(responsetime)",
+ "function": "mean",
+ "field_name": "responsetime",
+ "partition_field_name": "airline",
+ "detector_index": 0
+ }
+ ]
+}
diff --git a/x-pack/plugins/ml/public/explorer/explorer_charts/__mocks__/mock_job_config.json b/x-pack/plugins/ml/public/explorer/explorer_charts/__mocks__/mock_job_config.json
new file mode 100644
index 0000000000000..2750ad84a8308
--- /dev/null
+++ b/x-pack/plugins/ml/public/explorer/explorer_charts/__mocks__/mock_job_config.json
@@ -0,0 +1,88 @@
+{
+ "job_id": "mock-job-id",
+ "job_type": "anomaly_detector",
+ "job_version": "7.0.0-alpha1",
+ "description": "",
+ "create_time": 1532692299663,
+ "finished_time": 1532692304364,
+ "established_model_memory": 560894,
+ "analysis_config": {
+ "bucket_span": "15m",
+ "detectors": [
+ {
+ "detector_description": "mean(responsetime)",
+ "function": "mean",
+ "field_name": "responsetime",
+ "partition_field_name": "airline",
+ "detector_index": 0
+ }
+ ],
+ "influencers": [
+ "airline"
+ ]
+ },
+ "analysis_limits": {
+ "model_memory_limit": "13mb",
+ "categorization_examples_limit": 4
+ },
+ "data_description": {
+ "time_field": "@timestamp",
+ "time_format": "epoch_ms"
+ },
+ "model_snapshot_retention_days": 1,
+ "custom_settings": {
+ "created_by": "multi-metric-wizard"
+ },
+ "model_snapshot_id": "1532692303",
+ "model_snapshot_min_version": "6.4.0",
+ "results_index_name": "shared",
+ "data_counts": {
+ "job_id": "mock-job-id",
+ "processed_record_count": 86274,
+ "processed_field_count": 172548,
+ "input_bytes": 6744642,
+ "input_field_count": 172548,
+ "invalid_date_count": 0,
+ "missing_field_count": 0,
+ "out_of_order_timestamp_count": 0,
+ "empty_bucket_count": 0,
+ "sparse_bucket_count": 0,
+ "bucket_count": 479,
+ "earliest_record_timestamp": 1486425600000,
+ "latest_record_timestamp": 1486857594000,
+ "last_data_time": 1532692303844,
+ "input_record_count": 86274
+ },
+ "model_size_stats": {
+ "job_id": "mock-job-id",
+ "result_type": "model_size_stats",
+ "model_bytes": 560894,
+ "total_by_field_count": 21,
+ "total_over_field_count": 0,
+ "total_partition_field_count": 20,
+ "bucket_allocation_failures_count": 0,
+ "memory_status": "ok",
+ "log_time": 1532692303000,
+ "timestamp": 1486855800000
+ },
+ "datafeed_config": {
+ "datafeed_id": "datafeed-mock-job-id",
+ "job_id": "mock-job-id",
+ "query_delay": "86658ms",
+ "indices": [
+ "farequote-2017"
+ ],
+ "types": [],
+ "query": {
+ "match_all": {
+ "boost": 1
+ }
+ },
+ "scroll_size": 1000,
+ "chunking_config": {
+ "mode": "auto"
+ },
+ "state": "stopped"
+ },
+ "state": "closed"
+}
diff --git a/x-pack/plugins/ml/public/explorer/explorer_charts/__mocks__/mock_series_config_filebeat.json b/x-pack/plugins/ml/public/explorer/explorer_charts/__mocks__/mock_series_config_filebeat.json
new file mode 100644
index 0000000000000..b2c974e737e48
--- /dev/null
+++ b/x-pack/plugins/ml/public/explorer/explorer_charts/__mocks__/mock_series_config_filebeat.json
@@ -0,0 +1,57 @@
+{
+ "jobId": "population-03",
+ "detectorIndex": 0,
+ "metricFunction": "sum",
+ "timeField": "@timestamp",
+ "interval": "1h",
+ "datafeedConfig": {
+ "datafeed_id": "datafeed-population-03",
+ "job_id": "population-03",
+ "query_delay": "60s",
+ "frequency": "600s",
+ "indices": [
+ "filebeat-7.0.0*"
+ ],
+ "types": [
+ "doc"
+ ],
+ "query": {
+ "match_all": {
+ "boost": 1
+ }
+ },
+ "scroll_size": 1000,
+ "chunking_config": {
+ "mode": "auto"
+ },
+ "state": "stopped"
+ },
+ "metricFieldName": "nginx.access.body_sent.bytes",
+ "functionDescription": "sum",
+ "bucketSpanSeconds": 3600,
+ "detectorLabel": "high_sum(nginx.access.body_sent.bytes) over nginx.access.remote_ip (population-03)",
+ "fieldName": "nginx.access.body_sent.bytes",
+ "entityFields": [
+ {
+ "fieldName": "nginx.access.remote_ip",
+ "fieldValue": "72.57.0.53",
+ "$$hashKey": "object:813"
+ }
+ ],
+ "infoTooltip": {
+ "jobId": "population-03",
+ "aggregationInterval": "1h",
+ "chartFunction": "sum nginx.access.body_sent.bytes",
+ "entityFields": [
+ {
+ "fieldName": "nginx.access.remote_ip",
+ "fieldValue": "72.57.0.53"
+ }
+ ]
+ },
+ "loading": false,
+ "plotEarliest": 1487534400000,
+ "plotLatest": 1488168000000,
+ "selectedEarliest": 1487808000000,
+ "selectedLatest": 1487894399999
+}
diff --git a/x-pack/plugins/ml/public/explorer/explorer_charts/__mocks__/mock_series_promises_response.json b/x-pack/plugins/ml/public/explorer/explorer_charts/__mocks__/mock_series_promises_response.json
new file mode 100644
index 0000000000000..b2d51e7de713b
--- /dev/null
+++ b/x-pack/plugins/ml/public/explorer/explorer_charts/__mocks__/mock_series_promises_response.json
@@ -0,0 +1,231 @@
+[
+ [
+ {
+ "success": true,
+ "results": {
+ "1486611900000": 95.61584963117328,
+ "1486612800000": 99.34646708170573,
+ "1486613700000": 92.54502330106847,
+ "1486614600000": 98.87258768081665,
+ "1486615500000": 102.82824816022601,
+ "1486616400000": 96.7391939163208,
+ "1486617300000": 99.72634760538737,
+ "1486618200000": 101.08556365966797,
+ "1486619100000": 84.60266517190372,
+ "1486620000000": 105.24246263504028,
+ "1486620900000": 91.86086603800456,
+ "1486621800000": 94.5369130452474,
+ "1486622700000": 97.63843189586292,
+ "1486623600000": 93.79290502211627,
+ "1486624500000": 108.91006604362937,
+ "1486625400000": 107.46900049845378,
+ "1486626300000": 100.03502061631944,
+ "1486627200000": 92.0638559129503,
+ "1486628100000": 96.06356851678146,
+ "1486629000000": 109.89569989372703,
+ "1486629900000": 96.09498441786994,
+ "1486630800000": 105.05972120496962,
+ "1486631700000": 94.53041982650757,
+ "1486632600000": 103.37048240329908,
+ "1486633500000": 105.2058048248291,
+ "1486634400000": 102.06169471740722,
+ "1486635300000": 101.4836499955919,
+ "1486636200000": 96.34219177246094,
+ "1486637100000": 102.81613063812256,
+ "1486638000000": 96.09064518321644,
+ "1486638900000": 104.8488635012978,
+ "1486639800000": 93.45240384056454,
+ "1486640700000": 102.28834065524015,
+ "1486641600000": 104.54204668317523,
+ "1486642500000": 99.85492063823499,
+ "1486643400000": 97.12778260972765,
+ "1486644300000": 103.99638447008635,
+ "1486645200000": 95.34676822863128,
+ "1486646100000": 97.04620517383923,
+ "1486647000000": 104.2849609375,
+ "1486647900000": 97.88982413796818,
+ "1486648800000": 99.03312370300293,
+ "1486649700000": 105.5509593963623,
+ "1486650600000": 100.49496881585372,
+ "1486651500000": 99.06059494018555,
+ "1486652400000": 90.58293914794922,
+ "1486653300000": 92.8633090655009,
+ "1486654200000": 96.12510445004418,
+ "1486655100000": 100.4840145111084,
+ "1486656000000": 242.3568918440077,
+ "1486656900000": 282.02533259111294,
+ "1486657800000": 100.15823459625244,
+ "1486658700000": 97.5446532754337,
+ "1486659600000": 99.53840043809679,
+ "1486660500000": 101.24810005636776,
+ "1486661400000": 101.11400771141052,
+ "1486662300000": 100.70463662398488,
+ "1486663200000": 110.70174247340152,
+ "1486664100000": 96.51030629475912,
+ "1486665000000": 103.92840491400824,
+ "1486665900000": 98.29448418868215,
+ "1486666800000": 98.0272060394287,
+ "1486667700000": 99.63833363850911,
+ "1486668600000": 105.18764642568735,
+ "1486669500000": 97.8544118669298,
+ "1486670400000": 97.99196343672902,
+ "1486671300000": 106.30481338500977,
+ "1486672200000": 99.88215498490767,
+ "1486673100000": 93.50493303934734,
+ "1486674000000": 101.2538422175816,
+ "1486674900000": 102.07398986816406,
+ "1486675800000": 102.66583075890175,
+ "1486676700000": 108.5278158748851,
+ "1486677600000": 103.91436131795247,
+ "1486678500000": 98.55452414119945,
+ "1486679400000": 88.25028387705485,
+ "1486680300000": 93.57433591570172,
+ "1486681200000": 96.70550713172325,
+ "1486682100000": 98.14921424502418,
+ "1486683000000": 96.99264602661133,
+ "1486683900000": 88.23578810691833,
+ "1486684800000": 106.89157305265728,
+ "1486685700000": 101.07822271493765,
+ "1486686600000": 101.77820564718807,
+ "1486687500000": 102.84660829816546,
+ "1486688400000": 103.91598869772518,
+ "1486689300000": 104.73469270978656,
+ "1486690200000": 97.01155325082632,
+ "1486691100000": 104.97890539730297,
+ "1486692000000": 99.66440022786459,
+ "1486692900000": 99.64117607703575,
+ "1486693800000": 87.37038326263428,
+ "1486694700000": 105.95191955566406,
+ "1486695600000": 104.33271111382379,
+ "1486696500000": 101.93921706255745,
+ "1486697400000": 101.11774004422702,
+ "1486698300000": 101.70929403866039,
+ "1486699200000": 102.61243908221905,
+ "1486700100000": 99.16273922390408,
+ "1486701000000": 105.98729952643899,
+ "1486701900000": 114.16951904296874,
+ "1486702800000": 98.25128769874573,
+ "1486703700000": 94.25434192858245,
+ "1486704600000": 99.7759528526893,
+ "1486705500000": 113.10429502788342,
+ "1486706400000": 97.95185834711248,
+ "1486707300000": 114.46214866638184,
+ "1486708200000": 105.51880025863647,
+ "1486709100000": 99.89148930140904,
+ "1486710000000": 90.5253866369074,
+ "1486710900000": 103.66612243652344,
+ "1486711800000": 103.97851837158203,
+ "1486712700000": 92.76053659539474,
+ "1486713600000": 99.99461364746094
+ }
+ },
+ {
+ "success": true,
+ "records": [
+ {
+ "job_id": "mock-job-id",
+ "result_type": "record",
+ "probability": 1.6552181439816634e-32,
+ "record_score": 98.56065708456248,
+ "initial_record_score": 98.56065708456248,
+ "bucket_span": 900,
+ "detector_index": 0,
+ "is_interim": false,
+ "timestamp": 1486656000000,
+ "partition_field_name": "airline",
+ "partition_field_value": "AAL",
+ "function": "mean",
+ "function_description": "mean",
+ "typical": [
+ 99.81123207526203
+ ],
+ "actual": [
+ 242.3568918440077
+ ],
+ "field_name": "responsetime",
+ "influencers": [
+ {
+ "influencer_field_name": "airline",
+ "influencer_field_values": [
+ "AAL"
+ ]
+ }
+ ],
+ "airline": [
+ "AAL"
+ ]
+ },
+ {
+ "job_id": "mock-job-id",
+ "result_type": "record",
+ "probability": 2.6276047868032343e-28,
+ "record_score": 96.93718,
+ "initial_record_score": 92.70812367638732,
+ "bucket_span": 900,
+ "detector_index": 0,
+ "is_interim": false,
+ "timestamp": 1486656900000,
+ "partition_field_name": "airline",
+ "partition_field_value": "AAL",
+ "function": "mean",
+ "function_description": "mean",
+ "typical": [
+ 100.02884159032787
+ ],
+ "actual": [
+ 282.02533259111306
+ ],
+ "field_name": "responsetime",
+ "influencers": [
+ {
+ "influencer_field_name": "airline",
+ "influencer_field_values": [
+ "AAL"
+ ]
+ }
+ ],
+ "airline": [
+ "AAL"
+ ]
+ },
+ {
+ "job_id": "mock-job-id",
+ "result_type": "record",
+ "probability": 0.013283203854072794,
+ "record_score": 0.02716009,
+ "initial_record_score": 0.6110770406098681,
+ "bucket_span": 900,
+ "detector_index": 0,
+ "is_interim": false,
+ "timestamp": 1486619100000,
+ "partition_field_name": "airline",
+ "partition_field_value": "AAL",
+ "function": "mean",
+ "function_description": "mean",
+ "typical": [
+ 99.79426367092864
+ ],
+ "actual": [
+ 84.60266517190372
+ ],
+ "field_name": "responsetime",
+ "influencers": [
+ {
+ "influencer_field_name": "airline",
+ "influencer_field_values": [
+ "AAL"
+ ]
+ }
+ ],
+ "airline": [
+ "AAL"
+ ]
+ }
+ ]
+ },
+ {
+ "success": true,
+ "events": {}
+ }
+ ]
+]
diff --git a/x-pack/plugins/ml/public/explorer/explorer_charts/__snapshots__/explorer_chart_config_builder.test.js.snap b/x-pack/plugins/ml/public/explorer/explorer_charts/__snapshots__/explorer_chart_config_builder.test.js.snap
new file mode 100644
index 0000000000000..f899ee14003b7
--- /dev/null
+++ b/x-pack/plugins/ml/public/explorer/explorer_charts/__snapshots__/explorer_chart_config_builder.test.js.snap
@@ -0,0 +1,52 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`buildConfig get dataConfig for anomaly record 1`] = `
+Object {
+ "bucketSpanSeconds": 900,
+ "datafeedConfig": Object {
+ "chunking_config": Object {
+ "mode": "auto",
+ },
+ "datafeed_id": "datafeed-mock-job-id",
+ "indices": Array [
+ "farequote-2017",
+ ],
+ "job_id": "mock-job-id",
+ "query": Object {
+ "match_all": Object {
+ "boost": 1,
+ },
+ },
+ "query_delay": "86658ms",
+ "scroll_size": 1000,
+ "state": "stopped",
+ "types": Array [],
+ },
+ "detectorIndex": 0,
+ "detectorLabel": "mean(responsetime)",
+ "entityFields": Array [
+ Object {
+ "fieldName": "airline",
+ "fieldValue": "JAL",
+ },
+ ],
+ "fieldName": "responsetime",
+ "functionDescription": "mean",
+ "infoTooltip": Object {
+ "aggregationInterval": "15m",
+ "chartFunction": "avg responsetime",
+ "entityFields": Array [
+ Object {
+ "fieldName": "airline",
+ "fieldValue": "JAL",
+ },
+ ],
+ "jobId": "mock-job-id",
+ },
+ "interval": "15m",
+ "jobId": "mock-job-id",
+ "metricFieldName": "responsetime",
+ "metricFunction": "avg",
+ "timeField": "@timestamp",
+}
+`;
diff --git a/x-pack/plugins/ml/public/explorer/explorer_charts/__snapshots__/explorer_chart_tooltip.test.js.snap b/x-pack/plugins/ml/public/explorer/explorer_charts/__snapshots__/explorer_chart_tooltip.test.js.snap
new file mode 100644
index 0000000000000..c602bc0373c51
--- /dev/null
+++ b/x-pack/plugins/ml/public/explorer/explorer_charts/__snapshots__/explorer_chart_tooltip.test.js.snap
@@ -0,0 +1,24 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`ExplorerChartTooltip Render tooltip based on infoTooltip data. 1`] = `
+
+ job ID:
+ mock-job-id
+
+ aggregation interval:
+ 15m
+
+ chart function:
+ avg responsetime
+
+
+ airline
+ :
+ JAL
+
+
+`;
diff --git a/x-pack/plugins/ml/public/explorer/explorer_charts/__snapshots__/explorer_charts_container.test.js.snap b/x-pack/plugins/ml/public/explorer/explorer_charts/__snapshots__/explorer_charts_container.test.js.snap
new file mode 100644
index 0000000000000..087558cfa4ed4
--- /dev/null
+++ b/x-pack/plugins/ml/public/explorer/explorer_charts/__snapshots__/explorer_charts_container.test.js.snap
@@ -0,0 +1,54 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`ExplorerChartsContainer Initialization with chart data 1`] = `
+
+
+
+ high_sum(nginx.access.body_sent.bytes) over nginx.access.remote_ip (population-03)
+ -
+
+
+ nginx.access.remote_ip
+
+ 72.57.0.53
+
+
+
+ }
+ position="left"
+ size="s"
+ type="questionInCircle"
+ />
+
+ View
+
+
+
+`;
diff --git a/x-pack/plugins/ml/public/explorer/explorer_charts/__snapshots__/explorer_charts_container_service.test.js.snap b/x-pack/plugins/ml/public/explorer/explorer_charts/__snapshots__/explorer_charts_container_service.test.js.snap
new file mode 100644
index 0000000000000..e7b6cfb8ed9b3
--- /dev/null
+++ b/x-pack/plugins/ml/public/explorer/explorer_charts/__snapshots__/explorer_charts_container_service.test.js.snap
@@ -0,0 +1,616 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`explorerChartsContainerService call anomalyChangeListener with actual series config 1`] = `
+Object {
+ "layoutCellsPerChart": 12,
+ "seriesToPlot": Array [],
+ "timeFieldName": "timestamp",
+ "tooManyBuckets": false,
+}
+`;
+
+exports[`explorerChartsContainerService call anomalyChangeListener with actual series config 2`] = `
+Object {
+ "layoutCellsPerChart": 6,
+ "seriesToPlot": Array [
+ Object {
+ "bucketSpanSeconds": 900,
+ "chartData": null,
+ "datafeedConfig": Object {
+ "chunking_config": Object {
+ "mode": "auto",
+ },
+ "datafeed_id": "datafeed-mock-job-id",
+ "indices": Array [
+ "farequote-2017",
+ ],
+ "job_id": "mock-job-id",
+ "query": Object {
+ "match_all": Object {
+ "boost": 1,
+ },
+ },
+ "query_delay": "86658ms",
+ "scroll_size": 1000,
+ "state": "stopped",
+ "types": Array [],
+ },
+ "detectorIndex": 0,
+ "detectorLabel": "mean(responsetime)",
+ "entityFields": Array [
+ Object {
+ "fieldName": "airline",
+ "fieldValue": "AAL",
+ },
+ ],
+ "fieldName": "responsetime",
+ "functionDescription": "mean",
+ "infoTooltip": Object {
+ "aggregationInterval": "15m",
+ "chartFunction": "avg responsetime",
+ "entityFields": Array [
+ Object {
+ "fieldName": "airline",
+ "fieldValue": "AAL",
+ },
+ ],
+ "jobId": "mock-job-id",
+ },
+ "interval": "15m",
+ "jobId": "mock-job-id",
+ "loading": true,
+ "metricFieldName": "responsetime",
+ "metricFunction": "avg",
+ "timeField": "@timestamp",
+ },
+ ],
+ "timeFieldName": "timestamp",
+ "tooManyBuckets": false,
+}
+`;
+
+exports[`explorerChartsContainerService call anomalyChangeListener with actual series config 3`] = `
+Object {
+ "layoutCellsPerChart": 6,
+ "seriesToPlot": Array [
+ Object {
+ "bucketSpanSeconds": 900,
+ "chartData": Array [
+ Object {
+ "date": 1486611900000,
+ "value": 95.61584963117328,
+ },
+ Object {
+ "date": 1486612800000,
+ "value": 99.34646708170573,
+ },
+ Object {
+ "date": 1486613700000,
+ "value": 92.54502330106847,
+ },
+ Object {
+ "date": 1486614600000,
+ "value": 98.87258768081665,
+ },
+ Object {
+ "date": 1486615500000,
+ "value": 102.82824816022601,
+ },
+ Object {
+ "date": 1486616400000,
+ "value": 96.7391939163208,
+ },
+ Object {
+ "date": 1486617300000,
+ "value": 99.72634760538737,
+ },
+ Object {
+ "date": 1486618200000,
+ "value": 101.08556365966797,
+ },
+ Object {
+ "actual": Array [
+ 84.60266517190372,
+ ],
+ "anomalyScore": 0.02716009,
+ "date": 1486619100000,
+ "typical": Array [
+ 99.79426367092864,
+ ],
+ "value": 84.60266517190372,
+ },
+ Object {
+ "date": 1486620000000,
+ "value": 105.24246263504028,
+ },
+ Object {
+ "date": 1486620900000,
+ "value": 91.86086603800456,
+ },
+ Object {
+ "date": 1486621800000,
+ "value": 94.5369130452474,
+ },
+ Object {
+ "date": 1486622700000,
+ "value": 97.63843189586292,
+ },
+ Object {
+ "date": 1486623600000,
+ "value": 93.79290502211627,
+ },
+ Object {
+ "date": 1486624500000,
+ "value": 108.91006604362937,
+ },
+ Object {
+ "date": 1486625400000,
+ "value": 107.46900049845378,
+ },
+ Object {
+ "date": 1486626300000,
+ "value": 100.03502061631944,
+ },
+ Object {
+ "date": 1486627200000,
+ "value": 92.0638559129503,
+ },
+ Object {
+ "date": 1486628100000,
+ "value": 96.06356851678146,
+ },
+ Object {
+ "date": 1486629000000,
+ "value": 109.89569989372703,
+ },
+ Object {
+ "date": 1486629900000,
+ "value": 96.09498441786994,
+ },
+ Object {
+ "date": 1486630800000,
+ "value": 105.05972120496962,
+ },
+ Object {
+ "date": 1486631700000,
+ "value": 94.53041982650757,
+ },
+ Object {
+ "date": 1486632600000,
+ "value": 103.37048240329908,
+ },
+ Object {
+ "date": 1486633500000,
+ "value": 105.2058048248291,
+ },
+ Object {
+ "date": 1486634400000,
+ "value": 102.06169471740722,
+ },
+ Object {
+ "date": 1486635300000,
+ "value": 101.4836499955919,
+ },
+ Object {
+ "date": 1486636200000,
+ "value": 96.34219177246094,
+ },
+ Object {
+ "date": 1486637100000,
+ "value": 102.81613063812256,
+ },
+ Object {
+ "date": 1486638000000,
+ "value": 96.09064518321644,
+ },
+ Object {
+ "date": 1486638900000,
+ "value": 104.8488635012978,
+ },
+ Object {
+ "date": 1486639800000,
+ "value": 93.45240384056454,
+ },
+ Object {
+ "date": 1486640700000,
+ "value": 102.28834065524015,
+ },
+ Object {
+ "date": 1486641600000,
+ "value": 104.54204668317523,
+ },
+ Object {
+ "date": 1486642500000,
+ "value": 99.85492063823499,
+ },
+ Object {
+ "date": 1486643400000,
+ "value": 97.12778260972765,
+ },
+ Object {
+ "date": 1486644300000,
+ "value": 103.99638447008635,
+ },
+ Object {
+ "date": 1486645200000,
+ "value": 95.34676822863128,
+ },
+ Object {
+ "date": 1486646100000,
+ "value": 97.04620517383923,
+ },
+ Object {
+ "date": 1486647000000,
+ "value": 104.2849609375,
+ },
+ Object {
+ "date": 1486647900000,
+ "value": 97.88982413796818,
+ },
+ Object {
+ "date": 1486648800000,
+ "value": 99.03312370300293,
+ },
+ Object {
+ "date": 1486649700000,
+ "value": 105.5509593963623,
+ },
+ Object {
+ "date": 1486650600000,
+ "value": 100.49496881585372,
+ },
+ Object {
+ "date": 1486651500000,
+ "value": 99.06059494018555,
+ },
+ Object {
+ "date": 1486652400000,
+ "value": 90.58293914794922,
+ },
+ Object {
+ "date": 1486653300000,
+ "value": 92.8633090655009,
+ },
+ Object {
+ "date": 1486654200000,
+ "value": 96.12510445004418,
+ },
+ Object {
+ "date": 1486655100000,
+ "value": 100.4840145111084,
+ },
+ Object {
+ "actual": Array [
+ 242.3568918440077,
+ ],
+ "anomalyScore": 98.56065708456248,
+ "date": 1486656000000,
+ "typical": Array [
+ 99.81123207526203,
+ ],
+ "value": 242.3568918440077,
+ },
+ Object {
+ "actual": Array [
+ 282.02533259111306,
+ ],
+ "anomalyScore": 96.93718,
+ "date": 1486656900000,
+ "typical": Array [
+ 100.02884159032787,
+ ],
+ "value": 282.02533259111294,
+ },
+ Object {
+ "date": 1486657800000,
+ "value": 100.15823459625244,
+ },
+ Object {
+ "date": 1486658700000,
+ "value": 97.5446532754337,
+ },
+ Object {
+ "date": 1486659600000,
+ "value": 99.53840043809679,
+ },
+ Object {
+ "date": 1486660500000,
+ "value": 101.24810005636776,
+ },
+ Object {
+ "date": 1486661400000,
+ "value": 101.11400771141052,
+ },
+ Object {
+ "date": 1486662300000,
+ "value": 100.70463662398488,
+ },
+ Object {
+ "date": 1486663200000,
+ "value": 110.70174247340152,
+ },
+ Object {
+ "date": 1486664100000,
+ "value": 96.51030629475912,
+ },
+ Object {
+ "date": 1486665000000,
+ "value": 103.92840491400824,
+ },
+ Object {
+ "date": 1486665900000,
+ "value": 98.29448418868215,
+ },
+ Object {
+ "date": 1486666800000,
+ "value": 98.0272060394287,
+ },
+ Object {
+ "date": 1486667700000,
+ "value": 99.63833363850911,
+ },
+ Object {
+ "date": 1486668600000,
+ "value": 105.18764642568735,
+ },
+ Object {
+ "date": 1486669500000,
+ "value": 97.8544118669298,
+ },
+ Object {
+ "date": 1486670400000,
+ "value": 97.99196343672902,
+ },
+ Object {
+ "date": 1486671300000,
+ "value": 106.30481338500977,
+ },
+ Object {
+ "date": 1486672200000,
+ "value": 99.88215498490767,
+ },
+ Object {
+ "date": 1486673100000,
+ "value": 93.50493303934734,
+ },
+ Object {
+ "date": 1486674000000,
+ "value": 101.2538422175816,
+ },
+ Object {
+ "date": 1486674900000,
+ "value": 102.07398986816406,
+ },
+ Object {
+ "date": 1486675800000,
+ "value": 102.66583075890175,
+ },
+ Object {
+ "date": 1486676700000,
+ "value": 108.5278158748851,
+ },
+ Object {
+ "date": 1486677600000,
+ "value": 103.91436131795247,
+ },
+ Object {
+ "date": 1486678500000,
+ "value": 98.55452414119945,
+ },
+ Object {
+ "date": 1486679400000,
+ "value": 88.25028387705485,
+ },
+ Object {
+ "date": 1486680300000,
+ "value": 93.57433591570172,
+ },
+ Object {
+ "date": 1486681200000,
+ "value": 96.70550713172325,
+ },
+ Object {
+ "date": 1486682100000,
+ "value": 98.14921424502418,
+ },
+ Object {
+ "date": 1486683000000,
+ "value": 96.99264602661133,
+ },
+ Object {
+ "date": 1486683900000,
+ "value": 88.23578810691833,
+ },
+ Object {
+ "date": 1486684800000,
+ "value": 106.89157305265728,
+ },
+ Object {
+ "date": 1486685700000,
+ "value": 101.07822271493765,
+ },
+ Object {
+ "date": 1486686600000,
+ "value": 101.77820564718807,
+ },
+ Object {
+ "date": 1486687500000,
+ "value": 102.84660829816546,
+ },
+ Object {
+ "date": 1486688400000,
+ "value": 103.91598869772518,
+ },
+ Object {
+ "date": 1486689300000,
+ "value": 104.73469270978656,
+ },
+ Object {
+ "date": 1486690200000,
+ "value": 97.01155325082632,
+ },
+ Object {
+ "date": 1486691100000,
+ "value": 104.97890539730297,
+ },
+ Object {
+ "date": 1486692000000,
+ "value": 99.66440022786459,
+ },
+ Object {
+ "date": 1486692900000,
+ "value": 99.64117607703575,
+ },
+ Object {
+ "date": 1486693800000,
+ "value": 87.37038326263428,
+ },
+ Object {
+ "date": 1486694700000,
+ "value": 105.95191955566406,
+ },
+ Object {
+ "date": 1486695600000,
+ "value": 104.33271111382379,
+ },
+ Object {
+ "date": 1486696500000,
+ "value": 101.93921706255745,
+ },
+ Object {
+ "date": 1486697400000,
+ "value": 101.11774004422702,
+ },
+ Object {
+ "date": 1486698300000,
+ "value": 101.70929403866039,
+ },
+ Object {
+ "date": 1486699200000,
+ "value": 102.61243908221905,
+ },
+ Object {
+ "date": 1486700100000,
+ "value": 99.16273922390408,
+ },
+ Object {
+ "date": 1486701000000,
+ "value": 105.98729952643899,
+ },
+ Object {
+ "date": 1486701900000,
+ "value": 114.16951904296874,
+ },
+ Object {
+ "date": 1486702800000,
+ "value": 98.25128769874573,
+ },
+ Object {
+ "date": 1486703700000,
+ "value": 94.25434192858245,
+ },
+ Object {
+ "date": 1486704600000,
+ "value": 99.7759528526893,
+ },
+ Object {
+ "date": 1486705500000,
+ "value": 113.10429502788342,
+ },
+ Object {
+ "date": 1486706400000,
+ "value": 97.95185834711248,
+ },
+ Object {
+ "date": 1486707300000,
+ "value": 114.46214866638184,
+ },
+ Object {
+ "date": 1486708200000,
+ "value": 105.51880025863647,
+ },
+ Object {
+ "date": 1486709100000,
+ "value": 99.89148930140904,
+ },
+ Object {
+ "date": 1486710000000,
+ "value": 90.5253866369074,
+ },
+ Object {
+ "date": 1486710900000,
+ "value": 103.66612243652344,
+ },
+ Object {
+ "date": 1486711800000,
+ "value": 103.97851837158203,
+ },
+ Object {
+ "date": 1486712700000,
+ "value": 92.76053659539474,
+ },
+ Object {
+ "date": 1486713600000,
+ "value": 99.99461364746094,
+ },
+ ],
+ "chartLimits": Object {
+ "max": 282.02533259111294,
+ "min": 84.60266517190372,
+ },
+ "datafeedConfig": Object {
+ "chunking_config": Object {
+ "mode": "auto",
+ },
+ "datafeed_id": "datafeed-mock-job-id",
+ "indices": Array [
+ "farequote-2017",
+ ],
+ "job_id": "mock-job-id",
+ "query": Object {
+ "match_all": Object {
+ "boost": 1,
+ },
+ },
+ "query_delay": "86658ms",
+ "scroll_size": 1000,
+ "state": "stopped",
+ "types": Array [],
+ },
+ "detectorIndex": 0,
+ "detectorLabel": "mean(responsetime)",
+ "entityFields": Array [
+ Object {
+ "fieldName": "airline",
+ "fieldValue": "AAL",
+ },
+ ],
+ "fieldName": "responsetime",
+ "functionDescription": "mean",
+ "infoTooltip": Object {
+ "aggregationInterval": "15m",
+ "chartFunction": "avg responsetime",
+ "entityFields": Array [
+ Object {
+ "fieldName": "airline",
+ "fieldValue": "AAL",
+ },
+ ],
+ "jobId": "mock-job-id",
+ },
+ "interval": "15m",
+ "jobId": "mock-job-id",
+ "loading": false,
+ "metricFieldName": "responsetime",
+ "metricFunction": "avg",
+ "plotEarliest": 1486611900000,
+ "plotLatest": 1486714500000,
+ "selectedEarliest": 1486656000000,
+ "selectedLatest": 1486670399999,
+ "timeField": "@timestamp",
+ },
+ ],
+ "timeFieldName": "timestamp",
+ "tooManyBuckets": false,
+}
+`;
diff --git a/x-pack/plugins/ml/public/explorer/explorer_charts/explore_series.js b/x-pack/plugins/ml/public/explorer/explorer_charts/explore_series.js
deleted file mode 100644
index 48b11391f5eb8..0000000000000
--- a/x-pack/plugins/ml/public/explorer/explorer_charts/explore_series.js
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import _ from 'lodash';
-import moment from 'moment';
-import rison from 'rison-node';
-
-import chrome from 'ui/chrome';
-import { timefilter } from 'ui/timefilter';
-
-export function exploreSeries(series) {
- // Open the Single Metric dashboard over the same overall bounds and
- // zoomed in to the same time as the current chart.
- const bounds = timefilter.getActiveBounds();
- const from = bounds.min.toISOString(); // e.g. 2016-02-08T16:00:00.000Z
- const to = bounds.max.toISOString();
-
- const zoomFrom = moment(series.plotEarliest).toISOString();
- const zoomTo = moment(series.plotLatest).toISOString();
-
- // Pass the detector index and entity fields (i.e. by, over, partition fields)
- // to identify the particular series to view.
- // Initially pass them in the mlTimeSeriesExplorer part of the AppState.
- // TODO - do we want to pass the entities via the filter?
- const entityCondition = {};
- _.each(series.entityFields, (entity) => {
- entityCondition[entity.fieldName] = entity.fieldValue;
- });
-
- // Use rison to build the URL .
- const _g = rison.encode({
- ml: {
- jobIds: [series.jobId]
- },
- refreshInterval: {
- display: 'Off',
- pause: false,
- value: 0
- },
- time: {
- from: from,
- to: to,
- mode: 'absolute'
- }
- });
-
- const _a = rison.encode({
- mlTimeSeriesExplorer: {
- zoom: {
- from: zoomFrom,
- to: zoomTo
- },
- detectorIndex: series.detectorIndex,
- entities: entityCondition,
- },
- filters: [],
- query: {
- query_string: {
- analyze_wildcard: true,
- query: '*'
- }
- }
- });
-
- let path = chrome.getBasePath();
- path += '/app/ml#/timeseriesexplorer';
- path += '?_g=' + _g;
- path += '&_a=' + encodeURIComponent(_a);
- window.open(path, '_blank');
-
-}
diff --git a/x-pack/plugins/ml/public/explorer/explorer_charts/explorer_chart.test.js b/x-pack/plugins/ml/public/explorer/explorer_charts/explorer_chart.test.js
index ce40308003f5f..cf28917c47a9a 100644
--- a/x-pack/plugins/ml/public/explorer/explorer_charts/explorer_chart.test.js
+++ b/x-pack/plugins/ml/public/explorer/explorer_charts/explorer_chart.test.js
@@ -4,6 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import { chartData as mockChartData } from './__mocks__/mock_chart_data';
+import seriesConfig from './__mocks__/mock_series_config_filebeat.json';
+
// Mock TimeBuckets and mlFieldFormatService, they don't play well
// with the jest based test setup yet.
jest.mock('ui/time_buckets', () => ({
@@ -26,44 +29,6 @@ import { ExplorerChart } from './explorer_chart';
import { chartLimits } from '../../util/chart_utils';
describe('ExplorerChart', () => {
- const seriesConfig = {
- jobId: 'population-03',
- detectorIndex: 0,
- metricFunction: 'sum',
- timeField: '@timestamp',
- interval: '1h',
- datafeedConfig: {
- datafeed_id: 'datafeed-population-03',
- job_id: 'population-03',
- query_delay: '60s',
- frequency: '600s',
- indices: ['filebeat-7.0.0*'],
- types: ['doc'],
- query: { match_all: { boost: 1 } },
- scroll_size: 1000,
- chunking_config: { mode: 'auto' },
- state: 'stopped'
- },
- metricFieldName: 'nginx.access.body_sent.bytes',
- functionDescription: 'sum',
- bucketSpanSeconds: 3600,
- detectorLabel: 'high_sum(nginx.access.body_sent.bytes) over nginx.access.remote_ip (population-03)',
- fieldName: 'nginx.access.body_sent.bytes',
- entityFields: [{
- fieldName: 'nginx.access.remote_ip',
- fieldValue: '72.57.0.53',
- $$hashKey: 'object:813'
- }],
- infoTooltip: `job ID: population-03
- aggregation interval: 1h chart function: sum nginx.access.body_sent.bytes
- nginx.access.remote_ip: 72.57.0.53
`,
- loading: false,
- plotEarliest: 1487534400000,
- plotLatest: 1488168000000,
- selectedEarliest: 1487808000000,
- selectedLatest: 1487894399999
- };
-
const mlSelectSeverityServiceMock = {
state: {
get: () => ({
@@ -74,9 +39,7 @@ describe('ExplorerChart', () => {
const mockedGetBBox = { x: 0, y: -11.5, width: 12.1875, height: 14.5 };
const originalGetBBox = SVGElement.prototype.getBBox;
- beforeEach(() => SVGElement.prototype.getBBox = () => {
- return mockedGetBBox;
- });
+ beforeEach(() => SVGElement.prototype.getBBox = () => mockedGetBBox);
afterEach(() => (SVGElement.prototype.getBBox = originalGetBBox));
test('Initialize', () => {
@@ -122,29 +85,7 @@ describe('ExplorerChart', () => {
}
it('Anomaly Explorer Chart with multiple data points', () => {
- // prepare data for the test case
- const chartData = [
- {
- date: new Date('2017-02-23T08:00:00.000Z'),
- value: 228243469, anomalyScore: 63.32916, numberOfCauses: 1,
- actual: [228243469], typical: [133107.7703441773]
- },
- { date: new Date('2017-02-23T09:00:00.000Z'), value: null },
- { date: new Date('2017-02-23T10:00:00.000Z'), value: null },
- { date: new Date('2017-02-23T11:00:00.000Z'), value: null },
- {
- date: new Date('2017-02-23T12:00:00.000Z'),
- value: 625736376, anomalyScore: 97.32085, numberOfCauses: 1,
- actual: [625736376], typical: [132830.424736973]
- },
- {
- date: new Date('2017-02-23T13:00:00.000Z'),
- value: 201039318, anomalyScore: 59.83488, numberOfCauses: 1,
- actual: [201039318], typical: [132739.5267403542]
- }
- ];
-
- const wrapper = init(chartData);
+ const wrapper = init(mockChartData);
// the loading indicator should not be shown
expect(wrapper.find('.ml-loading-indicator .loading-spinner')).toHaveLength(0);
diff --git a/x-pack/plugins/ml/public/explorer/explorer_charts/explorer_chart_config_builder.test.js b/x-pack/plugins/ml/public/explorer/explorer_charts/explorer_chart_config_builder.test.js
new file mode 100644
index 0000000000000..bdfee3c933310
--- /dev/null
+++ b/x-pack/plugins/ml/public/explorer/explorer_charts/explorer_chart_config_builder.test.js
@@ -0,0 +1,27 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import mockAnomalyRecord from './__mocks__/mock_anomaly_record.json';
+import mockDetectorsByJob from './__mocks__/mock_detectors_by_job.json';
+import mockJobConfig from './__mocks__/mock_job_config.json';
+
+jest.mock('../../util/ml_error', () => (class MLRequestFailure {}));
+
+jest.mock('../../services/job_service', () => ({
+ mlJobService: {
+ getJob() { return mockJobConfig; },
+ detectorsByJob: mockDetectorsByJob
+ }
+}));
+
+import { buildConfig } from './explorer_chart_config_builder';
+
+describe('buildConfig', () => {
+ test('get dataConfig for anomaly record', () => {
+ const dataConfig = buildConfig(mockAnomalyRecord);
+ expect(dataConfig).toMatchSnapshot();
+ });
+});
diff --git a/x-pack/plugins/ml/public/explorer/explorer_charts/explorer_chart_tooltip.test.js b/x-pack/plugins/ml/public/explorer/explorer_charts/explorer_chart_tooltip.test.js
new file mode 100644
index 0000000000000..c83ed6af6333b
--- /dev/null
+++ b/x-pack/plugins/ml/public/explorer/explorer_charts/explorer_chart_tooltip.test.js
@@ -0,0 +1,28 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+
+import { shallow } from 'enzyme';
+import React from 'react';
+
+import { ExplorerChartTooltip } from './explorer_chart_tooltip';
+
+describe('ExplorerChartTooltip', () => {
+ test('Render tooltip based on infoTooltip data.', () => {
+ const infoTooltip = {
+ aggregationInterval: '15m',
+ chartFunction: 'avg responsetime',
+ entityFields: [{
+ fieldName: 'airline',
+ fieldValue: 'JAL',
+ }],
+ jobId: 'mock-job-id'
+ };
+
+ const wrapper = shallow( );
+ expect(wrapper).toMatchSnapshot();
+ });
+});
diff --git a/x-pack/plugins/ml/public/explorer/explorer_charts/explorer_charts_container.js b/x-pack/plugins/ml/public/explorer/explorer_charts/explorer_charts_container.js
index 2fb75120fdb44..b4ec174a69907 100644
--- a/x-pack/plugins/ml/public/explorer/explorer_charts/explorer_charts_container.js
+++ b/x-pack/plugins/ml/public/explorer/explorer_charts/explorer_charts_container.js
@@ -9,11 +9,11 @@ import React from 'react';
import { EuiIconTip } from '@elastic/eui';
+import { getExploreSeriesLink } from '../../util/chart_utils';
import { ExplorerChart } from './explorer_chart';
import { ExplorerChartTooltip } from './explorer_chart_tooltip';
export function ExplorerChartsContainer({
- exploreSeries,
seriesToPlot,
layoutCellsPerChart,
tooManyBuckets,
@@ -60,7 +60,7 @@ export function ExplorerChartsContainer({
color="warning"
/>
)}
- exploreSeries(series)}>
+ window.open(getExploreSeriesLink(series), '_blank')}>
View
@@ -76,7 +76,6 @@ export function ExplorerChartsContainer({
);
}
ExplorerChartsContainer.propTypes = {
- exploreSeries: PropTypes.func.isRequired,
seriesToPlot: PropTypes.array.isRequired,
layoutCellsPerChart: PropTypes.number.isRequired,
tooManyBuckets: PropTypes.bool.isRequired,
diff --git a/x-pack/plugins/ml/public/explorer/explorer_charts/explorer_charts_container.test.js b/x-pack/plugins/ml/public/explorer/explorer_charts/explorer_charts_container.test.js
new file mode 100644
index 0000000000000..073cee8632ebd
--- /dev/null
+++ b/x-pack/plugins/ml/public/explorer/explorer_charts/explorer_charts_container.test.js
@@ -0,0 +1,76 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { chartData } from './__mocks__/mock_chart_data';
+import seriesConfig from './__mocks__/mock_series_config_filebeat.json';
+
+// Mock TimeBuckets and mlFieldFormatService, they don't play well
+// with the jest based test setup yet.
+jest.mock('ui/time_buckets', () => ({
+ TimeBuckets: function () {
+ this.setBounds = jest.fn();
+ this.setInterval = jest.fn();
+ this.getScaledDateFormat = jest.fn();
+ }
+}));
+jest.mock('../../services/field_format_service', () => ({
+ mlFieldFormatService: {
+ getFieldFormat: jest.fn()
+ }
+}));
+
+import { shallow } from 'enzyme';
+import React from 'react';
+
+import { chartLimits } from '../../util/chart_utils';
+import { mlChartTooltipService } from '../../components/chart_tooltip/chart_tooltip_service';
+
+import { ExplorerChartsContainer } from './explorer_charts_container';
+
+describe('ExplorerChartsContainer', () => {
+ const mlSelectSeverityServiceMock = {
+ state: {
+ get: () => ({
+ val: ''
+ })
+ }
+ };
+
+ const mockedGetBBox = { x: 0, y: -11.5, width: 12.1875, height: 14.5 };
+ const originalGetBBox = SVGElement.prototype.getBBox;
+ beforeEach(() => SVGElement.prototype.getBBox = () => mockedGetBBox);
+ afterEach(() => (SVGElement.prototype.getBBox = originalGetBBox));
+
+ test('Minimal Initialization', () => {
+ const wrapper = shallow( );
+
+ expect(wrapper.html()).toBe('
');
+ });
+
+ test('Initialization with chart data', () => {
+ const wrapper = shallow( );
+
+ // Only do a snapshot of the label section, the included
+ // ExplorerChart component does that in its own tests anyway.
+ expect(wrapper.find('.explorer-chart-label')).toMatchSnapshot();
+ });
+});
diff --git a/x-pack/plugins/ml/public/explorer/explorer_charts/explorer_charts_container_directive.js b/x-pack/plugins/ml/public/explorer/explorer_charts/explorer_charts_container_directive.js
index 412831c4d97c8..58b9994b6a650 100644
--- a/x-pack/plugins/ml/public/explorer/explorer_charts/explorer_charts_container_directive.js
+++ b/x-pack/plugins/ml/public/explorer/explorer_charts/explorer_charts_container_directive.js
@@ -18,7 +18,6 @@ import ReactDOM from 'react-dom';
import $ from 'jquery';
import { ExplorerChartsContainer } from './explorer_charts_container';
-import { exploreSeries } from './explore_series';
import { explorerChartsContainerServiceFactory } from './explorer_charts_container_service';
import { mlChartTooltipService } from '../../components/chart_tooltip/chart_tooltip_service';
@@ -33,7 +32,8 @@ module.directive('mlExplorerChartsContainer', function (
function link(scope, element) {
const anomalyDataChangeListener = explorerChartsContainerServiceFactory(
mlSelectSeverityService,
- updateComponent
+ updateComponent,
+ $('.explorer-charts')
);
mlExplorerDashboardService.anomalyDataChange.watch(anomalyDataChangeListener);
@@ -52,7 +52,6 @@ module.directive('mlExplorerChartsContainer', function (
function updateComponent(data) {
const props = {
- exploreSeries,
seriesToPlot: data.seriesToPlot,
layoutCellsPerChart: data.layoutCellsPerChart,
// convert truthy/falsy value to Boolean
diff --git a/x-pack/plugins/ml/public/explorer/explorer_charts/explorer_charts_container_service.js b/x-pack/plugins/ml/public/explorer/explorer_charts/explorer_charts_container_service.js
index fa9d764496b75..b114cd93fffb3 100644
--- a/x-pack/plugins/ml/public/explorer/explorer_charts/explorer_charts_container_service.js
+++ b/x-pack/plugins/ml/public/explorer/explorer_charts/explorer_charts_container_service.js
@@ -14,7 +14,6 @@
*/
import _ from 'lodash';
-import $ from 'jquery';
import { buildConfig } from './explorer_chart_config_builder';
import { chartLimits } from '../../util/chart_utils';
@@ -25,10 +24,9 @@ import { mlJobService } from '../../services/job_service';
export function explorerChartsContainerServiceFactory(
mlSelectSeverityService,
- callback
+ callback,
+ $chartContainer
) {
- const $chartContainer = $('.explorer-charts');
-
const FUNCTION_DESCRIPTIONS_TO_PLOT = ['mean', 'min', 'max', 'sum', 'count', 'distinct_count', 'median', 'rare'];
const CHART_MAX_POINTS = 500;
const ANOMALIES_MAX_RESULTS = 500;
diff --git a/x-pack/plugins/ml/public/explorer/explorer_charts/explorer_charts_container_service.test.js b/x-pack/plugins/ml/public/explorer/explorer_charts/explorer_charts_container_service.test.js
new file mode 100644
index 0000000000000..6978494e151d2
--- /dev/null
+++ b/x-pack/plugins/ml/public/explorer/explorer_charts/explorer_charts_container_service.test.js
@@ -0,0 +1,127 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import mockAnomalyChartRecords from './__mocks__/mock_anomaly_chart_records.json';
+import mockDetectorsByJob from './__mocks__/mock_detectors_by_job.json';
+import mockJobConfig from './__mocks__/mock_job_config.json';
+import mockSeriesPromisesResponse from './__mocks__/mock_series_promises_response.json';
+
+jest.mock('../../services/job_service', () => ({
+ mlJobService: {
+ getJob() { return mockJobConfig; },
+ detectorsByJob: mockDetectorsByJob
+ }
+}));
+
+jest.mock('../../services/results_service', () => ({
+ mlResultsService: {
+ getMetricData() {
+ return Promise.resolve(mockSeriesPromisesResponse[0][0]);
+ },
+ getRecordsForCriteria() {
+ return Promise.resolve(mockSeriesPromisesResponse[0][1]);
+ },
+ getScheduledEventsByBucket() {
+ return Promise.resolve(mockSeriesPromisesResponse[0][2]);
+ }
+ }
+}));
+
+jest.mock('../../util/string_utils', () => ({
+ mlEscape(d) { return d; }
+}));
+
+const mockMlSelectSeverityService = {
+ state: {
+ get() { return { display: 'warning', val: 0 }; }
+ }
+};
+
+const mockChartContainer = {
+ width() { return 1140; }
+};
+
+function mockGetDefaultData() {
+ return {
+ seriesToPlot: [],
+ layoutCellsPerChart: 12,
+ tooManyBuckets: false,
+ timeFieldName: 'timestamp'
+ };
+}
+
+import { explorerChartsContainerServiceFactory } from './explorer_charts_container_service';
+
+describe('explorerChartsContainerService', () => {
+ test('Initialize factory', (done) => {
+ explorerChartsContainerServiceFactory(
+ mockMlSelectSeverityService,
+ callback
+ );
+
+ function callback(data) {
+ expect(data).toEqual(mockGetDefaultData());
+ done();
+ }
+ });
+
+ test('call anomalyChangeListener with empty series config', (done) => {
+ // callback will be called multiple times.
+ // the callbackData array contains the expected data values for each consecutive call.
+ const callbackData = [];
+ callbackData.push(mockGetDefaultData());
+ callbackData.push({
+ ...mockGetDefaultData(),
+ layoutCellsPerChart: 6
+ });
+
+ const anomalyDataChangeListener = explorerChartsContainerServiceFactory(
+ mockMlSelectSeverityService,
+ callback,
+ mockChartContainer
+ );
+
+ anomalyDataChangeListener(
+ [],
+ 1486656000000,
+ 1486670399999
+ );
+
+ function callback(data) {
+ if (callbackData.length > 0) {
+ expect(data).toEqual(callbackData.shift());
+ }
+ if (callbackData.length === 0) {
+ done();
+ }
+ }
+ });
+
+ test('call anomalyChangeListener with actual series config', (done) => {
+ let testCount = 0;
+ const expectedTestCount = 3;
+
+ const anomalyDataChangeListener = explorerChartsContainerServiceFactory(
+ mockMlSelectSeverityService,
+ callback,
+ mockChartContainer
+ );
+
+ anomalyDataChangeListener(
+ mockAnomalyChartRecords,
+ 1486656000000,
+ 1486670399999
+ );
+
+ function callback(data) {
+ testCount++;
+ expect(data).toMatchSnapshot();
+ if (testCount === expectedTestCount) {
+ done();
+ }
+ }
+ });
+});
diff --git a/x-pack/plugins/ml/public/jobs/new_job/simple/components/watcher/create_watch_service.js b/x-pack/plugins/ml/public/jobs/new_job/simple/components/watcher/create_watch_service.js
index 33149d05d33dc..8386faa1f67fd 100644
--- a/x-pack/plugins/ml/public/jobs/new_job/simple/components/watcher/create_watch_service.js
+++ b/x-pack/plugins/ml/public/jobs/new_job/simple/components/watcher/create_watch_service.js
@@ -8,7 +8,7 @@
import chrome from 'ui/chrome';
import _ from 'lodash';
-import { http } from 'plugins/ml/services/http_service';
+import { http } from '../../../../../services/http_service';
import emailBody from './email.html';
import emailInfluencersBody from './email-influencers.html';
@@ -153,4 +153,3 @@ class CreateWatchService {
}
export const mlCreateWatchService = new CreateWatchService();
-
diff --git a/x-pack/plugins/ml/public/services/ml_api_service/filters.js b/x-pack/plugins/ml/public/services/ml_api_service/filters.js
index f603b34916306..b4b66e0145335 100644
--- a/x-pack/plugins/ml/public/services/ml_api_service/filters.js
+++ b/x-pack/plugins/ml/public/services/ml_api_service/filters.js
@@ -9,7 +9,7 @@
import chrome from 'ui/chrome';
-import { http } from 'plugins/ml/services/http_service';
+import { http } from '../../services/http_service';
const basePath = chrome.addBasePath('/api/ml');
diff --git a/x-pack/plugins/ml/public/services/ml_api_service/index.js b/x-pack/plugins/ml/public/services/ml_api_service/index.js
index 34bfb99564d06..aceb9d5b7f366 100644
--- a/x-pack/plugins/ml/public/services/ml_api_service/index.js
+++ b/x-pack/plugins/ml/public/services/ml_api_service/index.js
@@ -9,7 +9,7 @@
import { pick } from 'lodash';
import chrome from 'ui/chrome';
-import { http } from 'plugins/ml/services/http_service';
+import { http } from '../../services/http_service';
import { filters } from './filters';
import { results } from './results';
diff --git a/x-pack/plugins/ml/public/services/ml_api_service/jobs.js b/x-pack/plugins/ml/public/services/ml_api_service/jobs.js
index 003de9ed327d7..f9b482ea0dc8b 100644
--- a/x-pack/plugins/ml/public/services/ml_api_service/jobs.js
+++ b/x-pack/plugins/ml/public/services/ml_api_service/jobs.js
@@ -6,7 +6,7 @@
import chrome from 'ui/chrome';
-import { http } from 'plugins/ml/services/http_service';
+import { http } from '../../services/http_service';
const basePath = chrome.addBasePath('/api/ml');
diff --git a/x-pack/plugins/ml/public/services/ml_api_service/results.js b/x-pack/plugins/ml/public/services/ml_api_service/results.js
index 5f7b76ce26865..b4254fa7b2519 100644
--- a/x-pack/plugins/ml/public/services/ml_api_service/results.js
+++ b/x-pack/plugins/ml/public/services/ml_api_service/results.js
@@ -8,7 +8,7 @@
import chrome from 'ui/chrome';
-import { http } from 'plugins/ml/services/http_service';
+import { http } from '../../services/http_service';
const basePath = chrome.addBasePath('/api/ml');
diff --git a/x-pack/plugins/ml/public/services/results_service.js b/x-pack/plugins/ml/public/services/results_service.js
index fb13ed381ab30..6cd9e8ef6e3d4 100644
--- a/x-pack/plugins/ml/public/services/results_service.js
+++ b/x-pack/plugins/ml/public/services/results_service.js
@@ -10,11 +10,11 @@
// Ml Results dashboards.
import _ from 'lodash';
-import { ML_MEDIAN_PERCENTS } from 'plugins/ml/../common/util/job_utils';
-import { escapeForElasticsearchQuery } from 'plugins/ml/util/string_utils';
-import { ML_RESULTS_INDEX_PATTERN } from 'plugins/ml/../common/constants/index_patterns';
+import { ML_MEDIAN_PERCENTS } from '../../common/util/job_utils';
+import { escapeForElasticsearchQuery } from '../util/string_utils';
+import { ML_RESULTS_INDEX_PATTERN } from '../../common/constants/index_patterns';
-import { ml } from 'plugins/ml/services/ml_api_service';
+import { ml } from '../services/ml_api_service';
// Obtains the maximum bucket anomaly scores by job ID and time.
diff --git a/x-pack/plugins/ml/public/util/chart_config_builder.js b/x-pack/plugins/ml/public/util/chart_config_builder.js
index e4cd9037afbbe..1529ea868d4e6 100644
--- a/x-pack/plugins/ml/public/util/chart_config_builder.js
+++ b/x-pack/plugins/ml/public/util/chart_config_builder.js
@@ -13,7 +13,7 @@
import _ from 'lodash';
-import { mlFunctionToESAggregation } from 'plugins/ml/../common/util/job_utils';
+import { mlFunctionToESAggregation } from '../../common/util/job_utils';
// Builds the basic configuration to plot a chart of the source data
// analyzed by the the detector at the given index from the specified ML job.
diff --git a/x-pack/plugins/ml/public/util/chart_utils.js b/x-pack/plugins/ml/public/util/chart_utils.js
index f6227dad1e3ce..260635ef14163 100644
--- a/x-pack/plugins/ml/public/util/chart_utils.js
+++ b/x-pack/plugins/ml/public/util/chart_utils.js
@@ -9,6 +9,10 @@
import d3 from 'd3';
import { calculateTextWidth } from '../util/string_utils';
import moment from 'moment';
+import rison from 'rison-node';
+
+import chrome from 'ui/chrome';
+import { timefilter } from 'ui/timefilter';
const MAX_LABEL_WIDTH = 100;
@@ -117,6 +121,63 @@ export function filterAxisLabels(selection, chartWidth) {
});
}
+export function getExploreSeriesLink(series) {
+ // Open the Single Metric dashboard over the same overall bounds and
+ // zoomed in to the same time as the current chart.
+ const bounds = timefilter.getActiveBounds();
+ const from = bounds.min.toISOString(); // e.g. 2016-02-08T16:00:00.000Z
+ const to = bounds.max.toISOString();
+
+ const zoomFrom = moment(series.plotEarliest).toISOString();
+ const zoomTo = moment(series.plotLatest).toISOString();
+
+ // Pass the detector index and entity fields (i.e. by, over, partition fields)
+ // to identify the particular series to view.
+ // Initially pass them in the mlTimeSeriesExplorer part of the AppState.
+ // TODO - do we want to pass the entities via the filter?
+ const entityCondition = {};
+ series.entityFields.forEach((entity) => {
+ entityCondition[entity.fieldName] = entity.fieldValue;
+ });
+
+ // Use rison to build the URL .
+ const _g = rison.encode({
+ ml: {
+ jobIds: [series.jobId]
+ },
+ refreshInterval: {
+ display: 'Off',
+ pause: false,
+ value: 0
+ },
+ time: {
+ from: from,
+ to: to,
+ mode: 'absolute'
+ }
+ });
+
+ const _a = rison.encode({
+ mlTimeSeriesExplorer: {
+ zoom: {
+ from: zoomFrom,
+ to: zoomTo
+ },
+ detectorIndex: series.detectorIndex,
+ entities: entityCondition,
+ },
+ filters: [],
+ query: {
+ query_string: {
+ analyze_wildcard: true,
+ query: '*'
+ }
+ }
+ });
+
+ return `${chrome.getBasePath()}/app/ml#/timeseriesexplorer?_g=${_g}&_a=${encodeURIComponent(_a)}`;
+}
+
export function numTicks(axisWidth) {
return axisWidth / MAX_LABEL_WIDTH;
}
diff --git a/x-pack/plugins/ml/public/util/chart_utils.test.js b/x-pack/plugins/ml/public/util/chart_utils.test.js
new file mode 100644
index 0000000000000..5bec58b9be0c9
--- /dev/null
+++ b/x-pack/plugins/ml/public/util/chart_utils.test.js
@@ -0,0 +1,63 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import seriesConfig from '../explorer/explorer_charts/__mocks__/mock_series_config_filebeat';
+
+jest.mock('ui/chrome',
+ () => ({
+ getBasePath: () => {
+ return '';
+ },
+ getUiSettingsClient: () => {
+ return {
+ get: (key) => {
+ switch (key) {
+ case 'timepicker:timeDefaults':
+ return { from: 'now-15m', to: 'now', mode: 'quick' };
+ case 'timepicker:refreshIntervalDefaults':
+ return { pause: false, value: 0 };
+ default:
+ throw new Error(`Unexpected config key: ${key}`);
+ }
+ }
+ };
+ },
+ }), { virtual: true });
+
+jest.mock('ui/timefilter/lib/parse_querystring',
+ () => ({
+ parseQueryString: () => {
+ return {
+ // Can not access local variable from within a mock
+ forceNow: global.nowTime
+ };
+ },
+ }), { virtual: true });
+
+import moment from 'moment';
+import { timefilter } from 'ui/timefilter';
+
+import { getExploreSeriesLink } from './chart_utils';
+
+timefilter.enableTimeRangeSelector();
+timefilter.enableAutoRefreshSelector();
+timefilter.setTime({
+ from: moment(seriesConfig.selectedEarliest).toISOString(),
+ to: moment(seriesConfig.selectedLatest).toISOString()
+});
+
+describe('getExploreSeriesLink', () => {
+ test('get timeseriesexplorer link', () => {
+ const link = getExploreSeriesLink(seriesConfig);
+ const expectedLink = `/app/ml#/timeseriesexplorer?_g=(ml:(jobIds:!(population-03)),` +
+ `refreshInterval:(display:Off,pause:!f,value:0),time:(from:'2017-02-23T00:00:00.000Z',mode:absolute,` +
+ `to:'2017-02-23T23:59:59.999Z'))&_a=(filters%3A!()%2CmlTimeSeriesExplorer%3A(detectorIndex%3A0%2Centities%3A` +
+ `(nginx.access.remote_ip%3A'72.57.0.53')%2Czoom%3A(from%3A'2017-02-19T20%3A00%3A00.000Z'%2Cto%3A'2017-02-27T04%3A00%3A00.000Z'))` +
+ `%2Cquery%3A(query_string%3A(analyze_wildcard%3A!t%2Cquery%3A'*')))`;
+
+ expect(link).toBe(expectedLink);
+ });
+});
From 35226b59487563f18151c4e6cf17f47306cedf91 Mon Sep 17 00:00:00 2001
From: Leanid Shutau
Date: Fri, 7 Sep 2018 15:35:02 +0300
Subject: [PATCH 42/68] [I18n] Update i18n config (#22799)
---
.i18nrc.json | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/.i18nrc.json b/.i18nrc.json
index d45b944ff0348..60484e5efc307 100644
--- a/.i18nrc.json
+++ b/.i18nrc.json
@@ -1,9 +1,9 @@
{
"paths": {
- "kbn": "src/core_plugins/kibana",
- "common.server": "src/server",
"common.ui": "src/ui",
- "xpack.idxMgmt": "xpack/plugins/index_management"
+ "kbn": "src/core_plugins/kibana",
+ "statusPage": "src/core_plugins/status_page",
+ "xpack.idxMgmt": "x-pack/plugins/index_management"
},
"exclude": [
"src/ui/ui_render/bootstrap/app_bootstrap.js",
From d021f71f6f72c49dd71214c2ceeeb75ca11be822 Mon Sep 17 00:00:00 2001
From: Larry Gregory
Date: Fri, 7 Sep 2018 10:06:35 -0400
Subject: [PATCH 43/68] Only log deprecation warning if xpack:defaultAdminEmail
is actually set (#22774)
This PR fixes the logic for logging the deprecation warning introduced with https://github.com/elastic/kibana/pull/22765. Previously Kibana would log the warning if the new `xpack.monitoring.cluster_alerts.email_notifications.email_address` setting was not defined, regardless of `xpack:defaultAdminEmail`'s setting.
Now, we will only log the deprecation warning if all of the following are true:
1) `xpack.monitoring.cluster_alerts.email_notifications.email_address` is not set.
2) `xpack:defaultAdminEmail` is set. (**<-- this is the new part**)
3) We haven't already logged the deprecation warning
---
.../__tests__/get_default_admin_email.js | 8 +++----
.../collectors/get_settings_collector.js | 23 +++++++++++--------
2 files changed, 17 insertions(+), 14 deletions(-)
diff --git a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/__tests__/get_default_admin_email.js b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/__tests__/get_default_admin_email.js
index 434f6bc41f585..0d32c1d479853 100644
--- a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/__tests__/get_default_admin_email.js
+++ b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/__tests__/get_default_admin_email.js
@@ -91,10 +91,10 @@ describe('getSettingsCollector / getDefaultAdminEmail', () => {
sinon.assert.calledOnce(callCluster);
});
- it('logs a deprecation warning', async () => {
+ it('does not log a deprecation warning', async () => {
const { config, callCluster, log } = setup({ docExists: false });
await getDefaultAdminEmail(config, callCluster, log);
- sinon.assert.calledOnce(log.warn);
+ sinon.assert.notCalled(log.warn);
});
});
@@ -105,10 +105,10 @@ describe('getSettingsCollector / getDefaultAdminEmail', () => {
sinon.assert.calledOnce(callCluster);
});
- it('logs a deprecation warning', async () => {
+ it('does not log a deprecation warning', async () => {
const { config, callCluster, log } = setup({ defaultAdminEmail: false });
await getDefaultAdminEmail(config, callCluster, log);
- sinon.assert.calledOnce(log.warn);
+ sinon.assert.notCalled(log.warn);
});
});
diff --git a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/get_settings_collector.js b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/get_settings_collector.js
index 0deafc2c49826..e75e36a1c494e 100644
--- a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/get_settings_collector.js
+++ b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/get_settings_collector.js
@@ -31,15 +31,6 @@ export async function getDefaultAdminEmail(config, callCluster, log) {
}
// DEPRECATED (Remove below in 7.0): If an email address is not configured in kibana.yml, then fallback to xpack:defaultAdminEmail
- if (!loggedDeprecationWarning) {
- const message = (
- `Monitoring is using ${XPACK_DEFAULT_ADMIN_EMAIL_UI_SETTING} for cluster alert notifications, ` +
- `which will not be supported in Kibana 7.0. Please configure ${emailAddressConfigKey} in your kibana.yml settings`
- );
-
- log.warn(message);
- loggedDeprecationWarning = true;
- }
const index = config.get('kibana.index');
const version = config.get('pkg.version');
@@ -50,7 +41,19 @@ export async function getDefaultAdminEmail(config, callCluster, log) {
ignore: [400, 404] // 400 if the index is closed, 404 if it does not exist
});
- return get(uiSettingsDoc, ['_source', 'config', XPACK_DEFAULT_ADMIN_EMAIL_UI_SETTING], null);
+ const emailAddress = get(uiSettingsDoc, ['_source', 'config', XPACK_DEFAULT_ADMIN_EMAIL_UI_SETTING], null);
+
+ if (emailAddress && !loggedDeprecationWarning) {
+ const message = (
+ `Monitoring is using ${XPACK_DEFAULT_ADMIN_EMAIL_UI_SETTING} for cluster alert notifications, ` +
+ `which will not be supported in Kibana 7.0. Please configure ${emailAddressConfigKey} in your kibana.yml settings`
+ );
+
+ log.warn(message);
+ loggedDeprecationWarning = true;
+ }
+
+ return emailAddress;
}
// we use shouldUseNull to determine if we need to send nulls; we only send nulls if the last email wasn't null
From 06ee993e226831ddfb363a76611ca0831aa17484 Mon Sep 17 00:00:00 2001
From: Larry Gregory
Date: Fri, 7 Sep 2018 11:02:05 -0400
Subject: [PATCH 44/68] [Spaces] - Handle space renaming and deleting (#22586)
[skip ci]
This PR improves the Space management experience when updating or deleting the user's active space.
1) When a user updates the space they are currently in, the Space Avatar in the Kibana nav will automatically update to match after saving. Fixes #22537 (cc @alexfrancoeur)
2) When a user **deletes** the space they are currently in, a secondary confirmation modal is displayed, warning them that they are deleting their active space, and they will be redirected to select another space:
![test](https://user-images.githubusercontent.com/3493255/44916585-22caf180-ad04-11e8-87c1-24538d2d6c2a.gif)
---
x-pack/plugins/spaces/index.js | 5 +-
.../spaces/public/lib/{index.js => index.ts} | 2 +-
.../spaces/public/lib/spaces_manager.ts | 8 +-
.../components/confirm_delete_modal.js | 50 ----
.../components/confirm_delete_modal.tsx | 121 ++++++++
.../components/delete_spaces_button.js | 104 -------
.../components/{index.js => index.ts} | 2 +-
.../edit_space/delete_spaces_button.tsx | 113 ++++++++
.../edit_space/manage_space_page.js | 4 +-
.../views/management/{index.js => index.ts} | 13 +-
.../views/management/manage_spaces.less | 4 +-
.../public/views/management/page_routes.js | 12 +-
.../spaces_grid/{index.js => index.ts} | 2 +-
.../spaces_grid/spaces_grid_page.js | 148 ----------
.../spaces_grid/spaces_grid_page.tsx | 259 ++++++++++++++++++
.../public/views/nav_control/nav_control.js | 9 +-
.../views/nav_control/nav_control_popover.tsx | 60 ++--
.../public/views/space_selector/index.js | 4 +-
18 files changed, 559 insertions(+), 361 deletions(-)
rename x-pack/plugins/spaces/public/lib/{index.js => index.ts} (82%)
delete mode 100644 x-pack/plugins/spaces/public/views/management/components/confirm_delete_modal.js
create mode 100644 x-pack/plugins/spaces/public/views/management/components/confirm_delete_modal.tsx
delete mode 100644 x-pack/plugins/spaces/public/views/management/components/delete_spaces_button.js
rename x-pack/plugins/spaces/public/views/management/components/{index.js => index.ts} (79%)
create mode 100644 x-pack/plugins/spaces/public/views/management/edit_space/delete_spaces_button.tsx
rename x-pack/plugins/spaces/public/views/management/{index.js => index.ts} (93%)
rename x-pack/plugins/spaces/public/views/management/spaces_grid/{index.js => index.ts} (82%)
delete mode 100644 x-pack/plugins/spaces/public/views/management/spaces_grid/spaces_grid_page.js
create mode 100644 x-pack/plugins/spaces/public/views/management/spaces_grid/spaces_grid_page.tsx
diff --git a/x-pack/plugins/spaces/index.js b/x-pack/plugins/spaces/index.js
index 2679a894a6661..9992dfd148301 100644
--- a/x-pack/plugins/spaces/index.js
+++ b/x-pack/plugins/spaces/index.js
@@ -50,10 +50,11 @@ export const spaces = (kibana) => new kibana.Plugin({
},
},
home: ['plugins/spaces/register_feature'],
- injectDefaultVars: function () {
+ injectDefaultVars: function (server) {
return {
spaces: [],
- activeSpace: null
+ activeSpace: null,
+ spaceSelectorURL: server.config().get('server.basePath') || '/',
};
},
replaceInjectedVars: async function (vars, request, server) {
diff --git a/x-pack/plugins/spaces/public/lib/index.js b/x-pack/plugins/spaces/public/lib/index.ts
similarity index 82%
rename from x-pack/plugins/spaces/public/lib/index.js
rename to x-pack/plugins/spaces/public/lib/index.ts
index 0a22964efbca3..538dd77e053f5 100644
--- a/x-pack/plugins/spaces/public/lib/index.js
+++ b/x-pack/plugins/spaces/public/lib/index.ts
@@ -4,4 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export { SpacesManager } from './spaces_manager';
\ No newline at end of file
+export { SpacesManager } from './spaces_manager';
diff --git a/x-pack/plugins/spaces/public/lib/spaces_manager.ts b/x-pack/plugins/spaces/public/lib/spaces_manager.ts
index 53835ab122f87..8a11e0137897f 100644
--- a/x-pack/plugins/spaces/public/lib/spaces_manager.ts
+++ b/x-pack/plugins/spaces/public/lib/spaces_manager.ts
@@ -12,11 +12,13 @@ import { Space } from '../../common/model/space';
export class SpacesManager extends EventEmitter {
private httpAgent: any;
private baseUrl: any;
+ private spaceSelectorURL: string;
- constructor(httpAgent: any, chrome: any) {
+ constructor(httpAgent: any, chrome: any, spaceSelectorURL: string) {
super();
this.httpAgent = httpAgent;
this.baseUrl = chrome.addBasePath(`/api/spaces`);
+ this.spaceSelectorURL = spaceSelectorURL;
}
public async getSpaces(): Promise {
@@ -54,6 +56,10 @@ export class SpacesManager extends EventEmitter {
.catch(() => this._displayError());
}
+ public redirectToSpaceSelector() {
+ window.location.href = this.spaceSelectorURL;
+ }
+
public async requestRefresh() {
this.emit('request_refresh');
}
diff --git a/x-pack/plugins/spaces/public/views/management/components/confirm_delete_modal.js b/x-pack/plugins/spaces/public/views/management/components/confirm_delete_modal.js
deleted file mode 100644
index 587f32911a383..0000000000000
--- a/x-pack/plugins/spaces/public/views/management/components/confirm_delete_modal.js
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import React from 'react';
-import PropTypes from 'prop-types';
-
-import {
- EuiOverlayMask,
- EuiConfirmModal,
-} from '@elastic/eui';
-
-export const ConfirmDeleteModal = (props) => {
- const {
- spaces
- } = props;
-
- const buttonText = spaces.length > 1
- ? `Delete ${spaces.length} spaces`
- : `Delete space`;
-
- const bodyQuestion = spaces.length > 1
- ? `Are you sure you want to delete these ${spaces.length} spaces?`
- : `Are you sure you want to delete this space?`;
-
- return (
-
-
- {bodyQuestion}
- This operation cannot be undone!
-
-
- );
-};
-
-ConfirmDeleteModal.propTypes = {
- spaces: PropTypes.array.isRequired,
- onCancel: PropTypes.func.isRequired,
- onConfirm: PropTypes.func.isRequired
-};
diff --git a/x-pack/plugins/spaces/public/views/management/components/confirm_delete_modal.tsx b/x-pack/plugins/spaces/public/views/management/components/confirm_delete_modal.tsx
new file mode 100644
index 0000000000000..0810243a1d035
--- /dev/null
+++ b/x-pack/plugins/spaces/public/views/management/components/confirm_delete_modal.tsx
@@ -0,0 +1,121 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import {
+ EuiCallOut,
+ // @ts-ignore
+ EuiConfirmModal,
+ EuiFieldText,
+ EuiFormRow,
+ EuiOverlayMask,
+ EuiText,
+} from '@elastic/eui';
+import React, { ChangeEvent, Component } from 'react';
+import { Space } from '../../../../common/model/space';
+import { SpacesManager } from '../../../lib';
+
+interface Props {
+ space: Space;
+ spacesManager: SpacesManager;
+ spacesNavState: any;
+ onCancel: () => void;
+ onConfirm: () => void;
+}
+
+interface State {
+ confirmSpaceName: string;
+ error: boolean | null;
+}
+
+export class ConfirmDeleteModal extends Component {
+ public state = {
+ confirmSpaceName: '',
+ error: null,
+ };
+
+ public render() {
+ const { space, spacesNavState, onCancel } = this.props;
+
+ let warning = null;
+ if (isDeletingCurrentSpace(space, spacesNavState)) {
+ const name = (
+
+ ({space.name} )
+
+ );
+ warning = (
+
+
+ You are about to delete your current space {name}. You will be redirected to choose a
+ different space if you continue.
+
+
+ );
+ }
+
+ return (
+
+
+
+ Deleting a space permanently removes the space and all of its contents. You can't undo
+ this action.
+
+
+
+
+
+
+ {warning}
+
+
+ );
+ }
+
+ private onSpaceNameChange = (e: ChangeEvent) => {
+ if (typeof this.state.error === 'boolean') {
+ this.setState({
+ confirmSpaceName: e.target.value,
+ error: e.target.value !== this.props.space.name,
+ });
+ } else {
+ this.setState({
+ confirmSpaceName: e.target.value,
+ });
+ }
+ };
+
+ private onConfirm = async () => {
+ if (this.state.confirmSpaceName === this.props.space.name) {
+ const needsRedirect = isDeletingCurrentSpace(this.props.space, this.props.spacesNavState);
+ const spacesManager = this.props.spacesManager;
+
+ await this.props.onConfirm();
+ if (needsRedirect) {
+ spacesManager.redirectToSpaceSelector();
+ }
+ } else {
+ this.setState({
+ error: true,
+ });
+ }
+ };
+}
+
+function isDeletingCurrentSpace(space: Space, spacesNavState: any) {
+ return space.id === spacesNavState.getActiveSpace().id;
+}
diff --git a/x-pack/plugins/spaces/public/views/management/components/delete_spaces_button.js b/x-pack/plugins/spaces/public/views/management/components/delete_spaces_button.js
deleted file mode 100644
index 6cb6d596a1996..0000000000000
--- a/x-pack/plugins/spaces/public/views/management/components/delete_spaces_button.js
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import React, { Component, Fragment } from 'react';
-import PropTypes from 'prop-types';
-import { ConfirmDeleteModal } from './confirm_delete_modal';
-import {
- EuiButton
-} from '@elastic/eui';
-import { toastNotifications } from 'ui/notify';
-
-export class DeleteSpacesButton extends Component {
- state = {
- showConfirmModal: false
- };
-
- render() {
- const numSpaces = this.props.spaces.length;
-
- const buttonText = numSpaces > 1
- ? `Delete ${numSpaces} spaces`
- : `Delete space`;
-
- return (
-
-
- {buttonText}
-
- {this.getConfirmDeleteModal()}
-
- );
- }
-
- onDeleteClick = () => {
- this.setState({
- showConfirmModal: true
- });
- };
-
- getConfirmDeleteModal = () => {
- if (!this.state.showConfirmModal) {
- return null;
- }
-
- return (
- {
- this.setState({
- showConfirmModal: false
- });
- }}
- onConfirm={this.deleteSpaces}
- />
- );
- };
-
- deleteSpaces = () => {
- const {
- spacesManager,
- spaces,
- spacesNavState,
- } = this.props;
-
- const deleteOperations = spaces.map(space => spacesManager.deleteSpace(space));
-
- Promise.all(deleteOperations)
- .then(() => {
- this.setState({
- showConfirmModal: false
- });
-
- const message = spaces.length > 1
- ? `Deleted ${spaces.length} spaces.`
- : `Deleted "${spaces[0].name}" space.`;
-
- toastNotifications.addSuccess(message);
-
- if (this.props.onDelete) {
- this.props.onDelete();
- }
-
- spacesNavState.refreshSpacesList();
-
- })
- .catch(error => {
- const {
- message = ''
- } = error.data || {};
-
- toastNotifications.addDanger(`Error deleting space: ${message}`);
- });
- };
-}
-
-DeleteSpacesButton.propTypes = {
- spaces: PropTypes.array.isRequired,
- spacesManager: PropTypes.object.isRequired,
- spacesNavState: PropTypes.object.isRequired,
- onDelete: PropTypes.func
-};
diff --git a/x-pack/plugins/spaces/public/views/management/components/index.js b/x-pack/plugins/spaces/public/views/management/components/index.ts
similarity index 79%
rename from x-pack/plugins/spaces/public/views/management/components/index.js
rename to x-pack/plugins/spaces/public/views/management/components/index.ts
index e134461ee43a6..651455d00e9f2 100644
--- a/x-pack/plugins/spaces/public/views/management/components/index.js
+++ b/x-pack/plugins/spaces/public/views/management/components/index.ts
@@ -4,4 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export { DeleteSpacesButton } from './delete_spaces_button';
+export { ConfirmDeleteModal } from './confirm_delete_modal';
diff --git a/x-pack/plugins/spaces/public/views/management/edit_space/delete_spaces_button.tsx b/x-pack/plugins/spaces/public/views/management/edit_space/delete_spaces_button.tsx
new file mode 100644
index 0000000000000..e255375baf85b
--- /dev/null
+++ b/x-pack/plugins/spaces/public/views/management/edit_space/delete_spaces_button.tsx
@@ -0,0 +1,113 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { EuiButton, EuiButtonIcon, EuiButtonIconProps } from '@elastic/eui';
+import React, { Component, Fragment } from 'react';
+import { toastNotifications } from 'ui/notify';
+import { Space } from '../../../../common/model/space';
+import { SpacesManager } from '../../../lib/spaces_manager';
+import { ConfirmDeleteModal } from '../components/confirm_delete_modal';
+
+interface Props {
+ style?: 'button' | 'icon';
+ space: Space;
+ spacesManager: SpacesManager;
+ spacesNavState: any;
+ onDelete: () => void;
+}
+
+interface State {
+ showConfirmDeleteModal: boolean;
+ showConfirmRedirectModal: boolean;
+}
+
+export class DeleteSpacesButton extends Component {
+ public state = {
+ showConfirmDeleteModal: false,
+ showConfirmRedirectModal: false,
+ };
+
+ public render() {
+ const buttonText = `Delete space`;
+
+ let ButtonComponent: any = EuiButton;
+
+ const extraProps: EuiButtonIconProps = {};
+
+ if (this.props.style === 'icon') {
+ ButtonComponent = EuiButtonIcon;
+ extraProps.iconType = 'trash';
+ }
+
+ return (
+
+
+ {buttonText}
+
+ {this.getConfirmDeleteModal()}
+
+ );
+ }
+
+ public onDeleteClick = () => {
+ this.setState({
+ showConfirmDeleteModal: true,
+ });
+ };
+
+ public getConfirmDeleteModal = () => {
+ if (!this.state.showConfirmDeleteModal) {
+ return null;
+ }
+
+ const { spacesNavState, spacesManager } = this.props;
+
+ return (
+ {
+ this.setState({
+ showConfirmDeleteModal: false,
+ });
+ }}
+ onConfirm={this.deleteSpaces}
+ />
+ );
+ };
+
+ public deleteSpaces = async () => {
+ const { spacesManager, space, spacesNavState } = this.props;
+
+ try {
+ await spacesManager.deleteSpace(space);
+ } catch (error) {
+ const { message: errorMessage = '' } = error.data || {};
+
+ toastNotifications.addDanger(`Error deleting space: ${errorMessage}`);
+ }
+
+ this.setState({
+ showConfirmDeleteModal: false,
+ });
+
+ const message = `Deleted "${space.name}" space.`;
+
+ toastNotifications.addSuccess(message);
+
+ if (this.props.onDelete) {
+ this.props.onDelete();
+ }
+
+ spacesNavState.refreshSpacesList();
+ };
+}
diff --git a/x-pack/plugins/spaces/public/views/management/edit_space/manage_space_page.js b/x-pack/plugins/spaces/public/views/management/edit_space/manage_space_page.js
index a6afad557cc3e..7524fa11dfd8d 100644
--- a/x-pack/plugins/spaces/public/views/management/edit_space/manage_space_page.js
+++ b/x-pack/plugins/spaces/public/views/management/edit_space/manage_space_page.js
@@ -24,7 +24,7 @@ import {
EuiLoadingSpinner,
} from '@elastic/eui';
-import { DeleteSpacesButton } from '../components';
+import { DeleteSpacesButton } from './delete_spaces_button';
import { SpaceAvatar } from '../../../components';
import { Notifier, toastNotifications } from 'ui/notify';
@@ -215,7 +215,7 @@ export class ManageSpacePage extends Component {
return (
{
const domNode = document.getElementById(reactRootNodeId);
- const spacesManager = new SpacesManager($http, chrome);
+ const spacesManager = new SpacesManager($http, chrome, spaceSelectorURL);
render( {
const domNode = document.getElementById(reactRootNodeId);
- const spacesManager = new SpacesManager($http, chrome);
+ const spacesManager = new SpacesManager($http, chrome, spaceSelectorURL);
render( {
const domNode = document.getElementById(reactRootNodeId);
const { space } = $route.current.params;
- const spacesManager = new SpacesManager($http, chrome);
+ const spacesManager = new SpacesManager($http, chrome, spaceSelectorURL);
render(
-
-
-
- Spaces
-
- {this.getPrimaryActionButton()}
-
-
-
- !isReservedSpace(space),
- onSelectionChange: this.onSelectionChange
- }}
- pagination={true}
- search={{
- box: {
- placeholder: 'Search'
- }
- }}
- loading={this.state.loading}
- message={this.state.loading ? "loading..." : undefined}
- />
-
-
- );
- }
-
- getPrimaryActionButton() {
- if (this.state.selectedSpaces.length > 0) {
- return (
-
- );
- }
-
- return (
- { window.location.hash = `#/management/spaces/create`; }}>Create space
- );
- }
-
- loadGrid = () => {
- const {
- spacesManager
- } = this.props;
-
- this.setState({
- loading: true,
- selectedSpaces: [],
- spaces: []
- });
-
- const setSpaces = (spaces) => {
- this.setState({
- loading: false,
- spaces,
- });
- };
-
- spacesManager.getSpaces()
- .then(spaces => {
- setSpaces(spaces);
- })
- .catch(error => {
- this.setState({
- loading: false,
- error
- });
- });
- };
-
- getColumnConfig() {
- return [{
- field: 'name',
- name: 'Space',
- sortable: true,
- render: (value, record) => {
- return (
- { window.location.hash = `#/management/spaces/edit/${encodeURIComponent(record.id)}`; }}>
- {value}
-
- );
- }
- }, {
- field: 'id',
- name: 'Identifier',
- sortable: true
- }, {
- field: 'description',
- name: 'Description',
- sortable: true
- }];
- }
-
- onSelectionChange = (selectedSpaces) => {
- this.setState({ selectedSpaces });
- };
-}
-
-SpacesGridPage.propTypes = {
- spacesManager: PropTypes.object.isRequired,
- spacesNavState: PropTypes.object.isRequired,
-};
diff --git a/x-pack/plugins/spaces/public/views/management/spaces_grid/spaces_grid_page.tsx b/x-pack/plugins/spaces/public/views/management/spaces_grid/spaces_grid_page.tsx
new file mode 100644
index 0000000000000..83a7d4149a25d
--- /dev/null
+++ b/x-pack/plugins/spaces/public/views/management/spaces_grid/spaces_grid_page.tsx
@@ -0,0 +1,259 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { Component } from 'react';
+
+import {
+ EuiButton,
+ EuiFlexGroup,
+ EuiFlexItem,
+ // @ts-ignore
+ EuiInMemoryTable,
+ EuiLink,
+ EuiPage,
+ EuiPageBody,
+ EuiPageContent,
+ EuiSpacer,
+ EuiText,
+} from '@elastic/eui';
+
+import { toastNotifications } from 'ui/notify';
+
+import { isReservedSpace } from '../../../../common';
+import { Space } from '../../../../common/model/space';
+import { SpaceAvatar } from '../../../components';
+import { SpacesManager } from '../../../lib/spaces_manager';
+import { ConfirmDeleteModal } from '../components/confirm_delete_modal';
+
+interface Props {
+ spacesManager: SpacesManager;
+ spacesNavState: any;
+}
+
+interface State {
+ spaces: Space[];
+ loading: boolean;
+ showConfirmDeleteModal: boolean;
+ selectedSpace: Space | null;
+ error: Error | null;
+}
+
+export class SpacesGridPage extends Component {
+ constructor(props: Props) {
+ super(props);
+ this.state = {
+ spaces: [],
+ loading: true,
+ showConfirmDeleteModal: false,
+ selectedSpace: null,
+ error: null,
+ };
+ }
+
+ public componentDidMount() {
+ this.loadGrid();
+ }
+
+ public render() {
+ return (
+
+
+
+
+
+
+ Spaces
+
+
+ {this.getPrimaryActionButton()}
+
+
+
+
+
+
+ {this.getConfirmDeleteModal()}
+
+ );
+ }
+
+ public getPrimaryActionButton() {
+ return (
+ {
+ window.location.hash = `#/management/spaces/create`;
+ }}
+ >
+ Create space
+
+ );
+ }
+
+ public getConfirmDeleteModal = () => {
+ if (!this.state.showConfirmDeleteModal || !this.state.selectedSpace) {
+ return null;
+ }
+
+ const { spacesNavState, spacesManager } = this.props;
+
+ return (
+ {
+ this.setState({
+ showConfirmDeleteModal: false,
+ });
+ }}
+ onConfirm={this.deleteSpace}
+ />
+ );
+ };
+
+ public deleteSpace = async () => {
+ const { spacesManager, spacesNavState } = this.props;
+
+ const space = this.state.selectedSpace;
+
+ if (!space) {
+ return;
+ }
+
+ try {
+ await spacesManager.deleteSpace(space);
+ } catch (error) {
+ const { message: errorMessage = '' } = error.data || {};
+
+ toastNotifications.addDanger(`Error deleting space: ${errorMessage}`);
+ }
+
+ this.setState({
+ showConfirmDeleteModal: false,
+ });
+
+ this.loadGrid();
+
+ const message = `Deleted "${space.name}" space.`;
+
+ toastNotifications.addSuccess(message);
+
+ spacesNavState.refreshSpacesList();
+ };
+
+ public loadGrid = () => {
+ const { spacesManager } = this.props;
+
+ this.setState({
+ loading: true,
+ spaces: [],
+ });
+
+ const setSpaces = (spaces: Space[]) => {
+ this.setState({
+ loading: false,
+ spaces,
+ });
+ };
+
+ spacesManager
+ .getSpaces()
+ .then(spaces => {
+ setSpaces(spaces);
+ })
+ .catch(error => {
+ this.setState({
+ loading: false,
+ error,
+ });
+ });
+ };
+
+ public getColumnConfig() {
+ return [
+ {
+ field: 'name',
+ name: 'Space',
+ sortable: true,
+ render: (value: string, record: Space) => {
+ return (
+ {
+ this.onEditSpaceClick(record);
+ }}
+ >
+
+
+
+
+
+ {record.name}
+
+
+
+ );
+ },
+ },
+ {
+ field: 'id',
+ name: 'Identifier',
+ sortable: true,
+ },
+ {
+ field: 'description',
+ name: 'Description',
+ sortable: true,
+ },
+ {
+ name: 'Actions',
+ actions: [
+ {
+ name: 'Edit',
+ description: 'Edit this space.',
+ onClick: this.onEditSpaceClick,
+ type: 'icon',
+ icon: 'pencil',
+ color: 'primary',
+ },
+ {
+ available: (record: Space) => !isReservedSpace(record),
+ name: 'Delete',
+ description: 'Delete this space.',
+ onClick: this.onDeleteSpaceClick,
+ type: 'icon',
+ icon: 'trash',
+ color: 'danger',
+ },
+ ],
+ },
+ ];
+ }
+
+ private onEditSpaceClick = (space: Space) => {
+ window.location.hash = `#/management/spaces/edit/${encodeURIComponent(space.id)}`;
+ };
+
+ private onDeleteSpaceClick = (space: Space) => {
+ this.setState({
+ selectedSpace: space,
+ showConfirmDeleteModal: true,
+ });
+ };
+}
diff --git a/x-pack/plugins/spaces/public/views/nav_control/nav_control.js b/x-pack/plugins/spaces/public/views/nav_control/nav_control.js
index e0d3cfa90f483..c1599ed1a6bc5 100644
--- a/x-pack/plugins/spaces/public/views/nav_control/nav_control.js
+++ b/x-pack/plugins/spaces/public/views/nav_control/nav_control.js
@@ -25,10 +25,10 @@ const module = uiModules.get('spaces_nav', ['kibana']);
let spacesManager;
-module.controller('spacesNavController', ($scope, $http, chrome, activeSpace) => {
+module.controller('spacesNavController', ($scope, $http, chrome, activeSpace, spaceSelectorURL) => {
const domNode = document.getElementById(`spacesNavReactRoot`);
- spacesManager = new SpacesManager($http, chrome);
+ spacesManager = new SpacesManager($http, chrome, spaceSelectorURL);
let mounted = false;
@@ -47,8 +47,11 @@ module.controller('spacesNavController', ($scope, $http, chrome, activeSpace) =>
});
-module.service('spacesNavState', () => {
+module.service('spacesNavState', (activeSpace) => {
return {
+ getActiveSpace: () => {
+ return activeSpace.space;
+ },
refreshSpacesList: () => {
if (spacesManager) {
spacesManager.requestRefresh();
diff --git a/x-pack/plugins/spaces/public/views/nav_control/nav_control_popover.tsx b/x-pack/plugins/spaces/public/views/nav_control/nav_control_popover.tsx
index ea7a2d0ca4465..6bd643de0f3f1 100644
--- a/x-pack/plugins/spaces/public/views/nav_control/nav_control_popover.tsx
+++ b/x-pack/plugins/spaces/public/views/nav_control/nav_control_popover.tsx
@@ -22,19 +22,22 @@ interface Props {
}
interface State {
- isOpen: boolean;
+ showSpaceSelector: boolean;
loading: boolean;
- activeSpaceExists: boolean;
+ activeSpace: Space | null;
spaces: Space[];
}
export class NavControlPopover extends Component {
- public state = {
- isOpen: false,
- loading: false,
- activeSpaceExists: true,
- spaces: [],
- };
+ constructor(props: Props) {
+ super(props);
+ this.state = {
+ showSpaceSelector: false,
+ loading: false,
+ activeSpace: props.activeSpace.space,
+ spaces: [],
+ };
+ }
public componentDidMount() {
this.loadSpaces();
@@ -63,8 +66,8 @@ export class NavControlPopover extends Component {
-
+
}
iconColor="subdued"
iconType="dashboardApp"
diff --git a/src/core_plugins/kibana/public/home/components/tutorial/__snapshots__/instruction_set.test.js.snap b/src/core_plugins/kibana/public/home/components/tutorial/__snapshots__/instruction_set.test.js.snap
index 0f8dc642b9dcf..33da089084488 100644
--- a/src/core_plugins/kibana/public/home/components/tutorial/__snapshots__/instruction_set.test.js.snap
+++ b/src/core_plugins/kibana/public/home/components/tutorial/__snapshots__/instruction_set.test.js.snap
@@ -55,8 +55,6 @@ exports[`render 1`] = `
}
paramValues={Object {}}
replaceTemplateStrings={[Function]}
- textPost={undefined}
- textPre={undefined}
/>,
"key": 0,
"title": "step 1",
@@ -70,8 +68,6 @@ exports[`render 1`] = `
}
paramValues={Object {}}
replaceTemplateStrings={[Function]}
- textPost={undefined}
- textPre={undefined}
/>,
"key": 1,
"title": "step 2",
@@ -137,8 +133,6 @@ exports[`statusCheckState checking status 1`] = `
}
paramValues={Object {}}
replaceTemplateStrings={[Function]}
- textPost={undefined}
- textPre={undefined}
/>,
"key": 0,
"title": "step 1",
@@ -152,14 +146,12 @@ exports[`statusCheckState checking status 1`] = `
}
paramValues={Object {}}
replaceTemplateStrings={[Function]}
- textPost={undefined}
- textPre={undefined}
/>,
"key": 1,
"title": "step 2",
},
Object {
- "children":
+ "children":
- ,
+ ,
"key": "checkStatusStep",
"status": "incomplete",
"title": "custom title",
@@ -262,8 +254,6 @@ exports[`statusCheckState failed status check - error 1`] = `
}
paramValues={Object {}}
replaceTemplateStrings={[Function]}
- textPost={undefined}
- textPre={undefined}
/>,
"key": 0,
"title": "step 1",
@@ -277,14 +267,12 @@ exports[`statusCheckState failed status check - error 1`] = `
}
paramValues={Object {}}
replaceTemplateStrings={[Function]}
- textPost={undefined}
- textPre={undefined}
/>,
"key": 1,
"title": "step 2",
},
Object {
- "children":
+ "children":
- ,
+ ,
"key": "checkStatusStep",
"status": "danger",
"title": "custom title",
@@ -392,8 +380,6 @@ exports[`statusCheckState failed status check - no data 1`] = `
}
paramValues={Object {}}
replaceTemplateStrings={[Function]}
- textPost={undefined}
- textPre={undefined}
/>,
"key": 0,
"title": "step 1",
@@ -407,14 +393,12 @@ exports[`statusCheckState failed status check - no data 1`] = `
}
paramValues={Object {}}
replaceTemplateStrings={[Function]}
- textPost={undefined}
- textPre={undefined}
/>,
"key": 1,
"title": "step 2",
},
Object {
- "children":
+ "children":
- ,
+ ,
"key": "checkStatusStep",
"status": "warning",
"title": "custom title",
@@ -522,8 +506,6 @@ exports[`statusCheckState initial state - no check has been attempted 1`] = `
}
paramValues={Object {}}
replaceTemplateStrings={[Function]}
- textPost={undefined}
- textPre={undefined}
/>,
"key": 0,
"title": "step 1",
@@ -537,14 +519,12 @@ exports[`statusCheckState initial state - no check has been attempted 1`] = `
}
paramValues={Object {}}
replaceTemplateStrings={[Function]}
- textPost={undefined}
- textPre={undefined}
/>,
"key": 1,
"title": "step 2",
},
Object {
- "children":
+ "children":
- ,
+ ,
"key": "checkStatusStep",
"status": "incomplete",
"title": "custom title",
@@ -647,8 +627,6 @@ exports[`statusCheckState successful status check 1`] = `
}
paramValues={Object {}}
replaceTemplateStrings={[Function]}
- textPost={undefined}
- textPre={undefined}
/>,
"key": 0,
"title": "step 1",
@@ -662,14 +640,12 @@ exports[`statusCheckState successful status check 1`] = `
}
paramValues={Object {}}
replaceTemplateStrings={[Function]}
- textPost={undefined}
- textPre={undefined}
/>,
"key": 1,
"title": "step 2",
},
Object {
- "children":
+ "children":
- ,
+ ,
"key": "checkStatusStep",
"status": "complete",
"title": "custom title",
diff --git a/src/core_plugins/kibana/public/home/components/tutorial/__snapshots__/saved_objects_installer.test.js.snap b/src/core_plugins/kibana/public/home/components/tutorial/__snapshots__/saved_objects_installer.test.js.snap
index 6a0f123c22907..f5ae01470c77e 100644
--- a/src/core_plugins/kibana/public/home/components/tutorial/__snapshots__/saved_objects_installer.test.js.snap
+++ b/src/core_plugins/kibana/public/home/components/tutorial/__snapshots__/saved_objects_installer.test.js.snap
@@ -130,6 +130,84 @@ exports[`bulkCreate should display error message when bulkCreate request fails 1
},
],
],
+ "results": Array [
+ Object {
+ "isThrow": false,
+ "value": "Load Kibana objects",
+ },
+ Object {
+ "isThrow": false,
+ "value": "Imports index pattern, visualizations and pre-defined dashboards.",
+ },
+ Object {
+ "isThrow": false,
+ "value": "Load Kibana objects",
+ },
+ Object {
+ "isThrow": false,
+ "value": "Load Kibana objects",
+ },
+ Object {
+ "isThrow": false,
+ "value": "Imports index pattern, visualizations and pre-defined dashboards.",
+ },
+ Object {
+ "isThrow": false,
+ "value": "Load Kibana objects",
+ },
+ Object {
+ "isThrow": false,
+ "value": "Imports index pattern, visualizations and pre-defined dashboards.",
+ },
+ Object {
+ "isThrow": false,
+ "value": "Load Kibana objects",
+ },
+ Object {
+ "isThrow": false,
+ "value": "{savedObjectsLength} saved objects successfully added",
+ },
+ Object {
+ "isThrow": false,
+ "value": "Imports index pattern, visualizations and pre-defined dashboards.",
+ },
+ Object {
+ "isThrow": false,
+ "value": "Load Kibana objects",
+ },
+ Object {
+ "isThrow": false,
+ "value": "Load Kibana objects",
+ },
+ Object {
+ "isThrow": false,
+ "value": "Imports index pattern, visualizations and pre-defined dashboards.",
+ },
+ Object {
+ "isThrow": false,
+ "value": "Load Kibana objects",
+ },
+ Object {
+ "isThrow": false,
+ "value": "Imports index pattern, visualizations and pre-defined dashboards.",
+ },
+ Object {
+ "isThrow": false,
+ "value": "Load Kibana objects",
+ },
+ Object {
+ "isThrow": false,
+ "value": "Request failed, Error: {message}",
+ },
+ Object {
+ "isThrow": false,
+ "value": "Imports index pattern, visualizations and pre-defined dashboards.",
+ },
+ Object {
+ "isThrow": false,
+ "value": "Load Kibana objects",
+ },
+ ],
},
"formatNumber": [MockFunction],
"formatPlural": [MockFunction],
@@ -155,7 +233,7 @@ exports[`bulkCreate should display error message when bulkCreate request fails 1
steps={
Array [
Object {
- "children":
+ "children":
- ,
+ ,
"key": "installStep",
"status": "incomplete",
"title": "Load Kibana objects",
@@ -439,6 +517,52 @@ exports[`bulkCreate should display success message when bulkCreate is successful
},
],
],
+ "results": Array [
+ Object {
+ "isThrow": false,
+ "value": "Load Kibana objects",
+ },
+ Object {
+ "isThrow": false,
+ "value": "Imports index pattern, visualizations and pre-defined dashboards.",
+ },
+ Object {
+ "isThrow": false,
+ "value": "Load Kibana objects",
+ },
+ Object {
+ "isThrow": false,
+ "value": "Load Kibana objects",
+ },
+ Object {
+ "isThrow": false,
+ "value": "Imports index pattern, visualizations and pre-defined dashboards.",
+ },
+ Object {
+ "isThrow": false,
+ "value": "Load Kibana objects",
+ },
+ Object {
+ "isThrow": false,
+ "value": "Imports index pattern, visualizations and pre-defined dashboards.",
+ },
+ Object {
+ "isThrow": false,
+ "value": "Load Kibana objects",
+ },
+ Object {
+ "isThrow": false,
+ "value": "{savedObjectsLength} saved objects successfully added",
+ },
+ Object {
+ "isThrow": false,
+ "value": "Imports index pattern, visualizations and pre-defined dashboards.",
+ },
+ Object {
+ "isThrow": false,
+ "value": "Load Kibana objects",
+ },
+ ],
},
"formatNumber": [MockFunction],
"formatPlural": [MockFunction],
@@ -464,7 +588,7 @@ exports[`bulkCreate should display success message when bulkCreate is successful
steps={
Array [
Object {
- "children":
+ "children":
- ,
+ ,
"key": "installStep",
"status": "complete",
"title": "Load Kibana objects",
@@ -716,7 +840,7 @@ exports[`renders 1`] = `
steps={
Array [
Object {
- "children":
+ "children":
- ,
+ ,
"key": "installStep",
"status": "incomplete",
"title": "Load Kibana objects",
diff --git a/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/lib/__tests__/get_indices.test.js b/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/lib/__tests__/get_indices.test.js
index 150753fd34e36..f5dcccf6da50b 100644
--- a/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/lib/__tests__/get_indices.test.js
+++ b/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/lib/__tests__/get_indices.test.js
@@ -80,7 +80,7 @@ describe('getIndices', () => {
it('should throw exceptions', async () => {
const es = {
- search: () => { throw 'Fail'; }
+ search: () => { throw new Error('Fail'); }
};
await expect(getIndices(es, 'kibana', 1)).rejects.toThrow();
diff --git a/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/lib/get_indices.js b/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/lib/get_indices.js
index 9a2a712d82c13..7638daf1c5720 100644
--- a/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/lib/get_indices.js
+++ b/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/lib/get_indices.js
@@ -42,7 +42,7 @@ export async function getIndices(es, rawPattern, limit) {
// We need to always provide a limit and not rely on the default
if (!limit) {
- throw '`getIndices()` was called without the required `limit` parameter.';
+ throw new Error('`getIndices()` was called without the required `limit` parameter.');
}
const params = {
diff --git a/src/core_plugins/kibana/public/management/sections/indices/edit_index_pattern/source_filters_table/__tests__/__snapshots__/source_filters_table.test.js.snap b/src/core_plugins/kibana/public/management/sections/indices/edit_index_pattern/source_filters_table/__tests__/__snapshots__/source_filters_table.test.js.snap
index 0206f5c9774df..3452fd16456e7 100644
--- a/src/core_plugins/kibana/public/management/sections/indices/edit_index_pattern/source_filters_table/__tests__/__snapshots__/source_filters_table.test.js.snap
+++ b/src/core_plugins/kibana/public/management/sections/indices/edit_index_pattern/source_filters_table/__tests__/__snapshots__/source_filters_table.test.js.snap
@@ -18,6 +18,12 @@ exports[`SourceFiltersTable should add a filter 1`] = `
"calls": Array [
Array [],
],
+ "results": Array [
+ Object {
+ "isThrow": false,
+ "value": undefined,
+ },
+ ],
},
"sourceFilters": Array [
Object {
@@ -106,6 +112,12 @@ exports[`SourceFiltersTable should remove a filter 1`] = `
"calls": Array [
Array [],
],
+ "results": Array [
+ Object {
+ "isThrow": false,
+ "value": undefined,
+ },
+ ],
},
"sourceFilters": Array [
Object {
@@ -308,6 +320,12 @@ exports[`SourceFiltersTable should update a filter 1`] = `
"calls": Array [
Array [],
],
+ "results": Array [
+ Object {
+ "isThrow": false,
+ "value": undefined,
+ },
+ ],
},
"sourceFilters": Array [
Object {
diff --git a/src/core_plugins/kibana/public/management/sections/settings/components/field/__snapshots__/field.test.js.snap b/src/core_plugins/kibana/public/management/sections/settings/components/field/__snapshots__/field.test.js.snap
index 881e228173b31..1c0861de51b3d 100644
--- a/src/core_plugins/kibana/public/management/sections/settings/components/field/__snapshots__/field.test.js.snap
+++ b/src/core_plugins/kibana/public/management/sections/settings/components/field/__snapshots__/field.test.js.snap
@@ -18,7 +18,7 @@ exports[`Field for array setting should render as read only with help text if ov
+
-
+
@@ -34,15 +34,15 @@ exports[`Field for array setting should render as read only with help text if ov
grow={true}
size="xs"
>
-
+
Default:
default_value
-
+
-
-
+
+
}
fullWidth={false}
gutterSize="l"
@@ -119,7 +119,7 @@ exports[`Field for array setting should render custom setting icon if it is cust
+
-
+
}
fullWidth={false}
gutterSize="l"
@@ -202,7 +202,7 @@ exports[`Field for array setting should render default value if there is no user
+
-
+
}
fullWidth={false}
gutterSize="l"
@@ -280,7 +280,7 @@ exports[`Field for array setting should render user value if there is user value
+
-
+
@@ -296,15 +296,15 @@ exports[`Field for array setting should render user value if there is user value
grow={true}
size="xs"
>
-
+
Default:
default_value
-
+
-
-
+
+
}
fullWidth={false}
gutterSize="l"
@@ -389,7 +389,7 @@ exports[`Field for boolean setting should render as read only with help text if
+
-
+
@@ -405,15 +405,15 @@ exports[`Field for boolean setting should render as read only with help text if
grow={true}
size="xs"
>
-
+
Default:
true
-
+
-
-
+
+
}
fullWidth={false}
gutterSize="l"
@@ -488,7 +488,7 @@ exports[`Field for boolean setting should render custom setting icon if it is cu
+
-
+
}
fullWidth={false}
gutterSize="l"
@@ -569,7 +569,7 @@ exports[`Field for boolean setting should render default value if there is no us
+
-
+
}
fullWidth={false}
gutterSize="l"
@@ -645,7 +645,7 @@ exports[`Field for boolean setting should render user value if there is user val
+
-
+
@@ -661,15 +661,15 @@ exports[`Field for boolean setting should render user value if there is user val
grow={true}
size="xs"
>
-
+
Default:
true
-
+
-
-
+
+
}
fullWidth={false}
gutterSize="l"
@@ -752,7 +752,7 @@ exports[`Field for image setting should render as read only with help text if ov
+
-
+
@@ -768,15 +768,15 @@ exports[`Field for image setting should render as read only with help text if ov
grow={true}
size="xs"
>
-
+
Default:
null
-
+
-
-
+
+
}
fullWidth={false}
gutterSize="l"
@@ -850,7 +850,7 @@ exports[`Field for image setting should render custom setting icon if it is cust
+
-
+
}
fullWidth={false}
gutterSize="l"
@@ -932,7 +932,7 @@ exports[`Field for image setting should render default value if there is no user
+
-
+
}
fullWidth={false}
gutterSize="l"
@@ -1009,7 +1009,7 @@ exports[`Field for image setting should render user value if there is user value
+
-
+
@@ -1025,15 +1025,15 @@ exports[`Field for image setting should render user value if there is user value
grow={true}
size="xs"
>
-
+
Default:
null
-
+
-
-
+
+
}
fullWidth={false}
gutterSize="l"
@@ -1126,7 +1126,7 @@ exports[`Field for json setting should render as read only with help text if ove
+
-
+
@@ -1142,7 +1142,7 @@ exports[`Field for json setting should render as read only with help text if ove
grow={true}
size="xs"
>
-
+
Default:
{}
-
+
-
-
+
+
}
fullWidth={false}
gutterSize="l"
@@ -1247,7 +1247,7 @@ exports[`Field for json setting should render custom setting icon if it is custo
+
-
+
}
fullWidth={false}
gutterSize="l"
@@ -1346,7 +1346,7 @@ exports[`Field for json setting should render default value if there is no user
+
-
+
@@ -1362,7 +1362,7 @@ exports[`Field for json setting should render default value if there is no user
grow={true}
size="xs"
>
-
+
Default:
{}
-
+
-
-
+
+
}
fullWidth={false}
gutterSize="l"
@@ -1475,7 +1475,7 @@ exports[`Field for json setting should render user value if there is user value
+
-
+
@@ -1491,7 +1491,7 @@ exports[`Field for json setting should render user value if there is user value
grow={true}
size="xs"
>
-
+
Default:
{}
-
+
-
-
+
+
}
fullWidth={false}
gutterSize="l"
@@ -1604,7 +1604,7 @@ exports[`Field for markdown setting should render as read only with help text if
+
-
+
@@ -1620,15 +1620,15 @@ exports[`Field for markdown setting should render as read only with help text if
grow={true}
size="xs"
>
-
+
Default:
null
-
+
-
-
+
+
}
fullWidth={false}
gutterSize="l"
@@ -1721,7 +1721,7 @@ exports[`Field for markdown setting should render custom setting icon if it is c
+
-
+
}
fullWidth={false}
gutterSize="l"
@@ -1820,7 +1820,7 @@ exports[`Field for markdown setting should render default value if there is no u
+
-
+
}
fullWidth={false}
gutterSize="l"
@@ -1914,7 +1914,7 @@ exports[`Field for markdown setting should render user value if there is user va
+
-
+
@@ -1930,15 +1930,15 @@ exports[`Field for markdown setting should render user value if there is user va
grow={true}
size="xs"
>
-
+
Default:
null
-
+
-
-
+
+
}
fullWidth={false}
gutterSize="l"
@@ -2039,7 +2039,7 @@ exports[`Field for number setting should render as read only with help text if o
+
-
+
@@ -2055,15 +2055,15 @@ exports[`Field for number setting should render as read only with help text if o
grow={true}
size="xs"
>
-
+
Default:
5
-
+
-
-
+
+
}
fullWidth={false}
gutterSize="l"
@@ -2140,7 +2140,7 @@ exports[`Field for number setting should render custom setting icon if it is cus
+
-
+
}
fullWidth={false}
gutterSize="l"
@@ -2223,7 +2223,7 @@ exports[`Field for number setting should render default value if there is no use
+
-
+
}
fullWidth={false}
gutterSize="l"
@@ -2301,7 +2301,7 @@ exports[`Field for number setting should render user value if there is user valu
+
-
+
@@ -2317,15 +2317,15 @@ exports[`Field for number setting should render user value if there is user valu
grow={true}
size="xs"
>
-
+
Default:
5
-
+
-
-
+
+
}
fullWidth={false}
gutterSize="l"
@@ -2410,7 +2410,7 @@ exports[`Field for select setting should render as read only with help text if o
+
-
+
@@ -2426,15 +2426,15 @@ exports[`Field for select setting should render as read only with help text if o
grow={true}
size="xs"
>
-
+
Default:
orange
-
+
-
-
+
+
}
fullWidth={false}
gutterSize="l"
@@ -2528,7 +2528,7 @@ exports[`Field for select setting should render custom setting icon if it is cus
+
-
+
}
fullWidth={false}
gutterSize="l"
@@ -2628,7 +2628,7 @@ exports[`Field for select setting should render default value if there is no use
+
-
+
}
fullWidth={false}
gutterSize="l"
@@ -2723,7 +2723,7 @@ exports[`Field for select setting should render user value if there is user valu
+
-
+
@@ -2739,15 +2739,15 @@ exports[`Field for select setting should render user value if there is user valu
grow={true}
size="xs"
>
-
+
Default:
orange
-
+
-
-
+
+
}
fullWidth={false}
gutterSize="l"
@@ -2849,7 +2849,7 @@ exports[`Field for string setting should render as read only with help text if o
+
-
+
@@ -2865,15 +2865,15 @@ exports[`Field for string setting should render as read only with help text if o
grow={true}
size="xs"
>
-
+
Default:
null
-
+
-
-
+
+
}
fullWidth={false}
gutterSize="l"
@@ -2950,7 +2950,7 @@ exports[`Field for string setting should render custom setting icon if it is cus
+
-
+
}
fullWidth={false}
gutterSize="l"
@@ -3033,7 +3033,7 @@ exports[`Field for string setting should render default value if there is no use
+
-
+
}
fullWidth={false}
gutterSize="l"
@@ -3111,7 +3111,7 @@ exports[`Field for string setting should render user value if there is user valu
+
-
+
@@ -3127,15 +3127,15 @@ exports[`Field for string setting should render user value if there is user valu
grow={true}
size="xs"
>
-
+
Default:
null
-
+
-
-
+
+
}
fullWidth={false}
gutterSize="l"
diff --git a/src/dev/jest/config.js b/src/dev/jest/config.js
index 99b22b14c2d71..4bb11a6d43785 100644
--- a/src/dev/jest/config.js
+++ b/src/dev/jest/config.js
@@ -96,4 +96,6 @@ export default {
'default',
'/src/dev/jest/junit_reporter.js',
],
+ // TODO: prevent tests from making web requests that rely on this setting, see https://github.com/facebook/jest/pull/6792
+ testURL: 'about:blank',
};
diff --git a/src/dev/jest/ts_transform.ts b/src/dev/jest/ts_transform.ts
index 60f0b11ec94a4..ed366bcd091a0 100644
--- a/src/dev/jest/ts_transform.ts
+++ b/src/dev/jest/ts_transform.ts
@@ -17,8 +17,7 @@
* under the License.
*/
-import { getCacheKey, install, process } from 'ts-jest';
-import { JestConfig, TransformOptions } from 'ts-jest/dist/jest-types';
+import TsJest from 'ts-jest';
import { getTsProjectForAbsolutePath } from '../typescript';
@@ -26,11 +25,11 @@ const DEFAULT_TS_CONFIG_PATH = require.resolve('../../../tsconfig.json');
const DEFAULT_BROWSER_TS_CONFIG_PATH = require.resolve('../../../tsconfig.browser.json');
function extendJestConfigJSON(jestConfigJSON: string, filePath: string) {
- const jestConfig = JSON.parse(jestConfigJSON) as JestConfig;
+ const jestConfig = JSON.parse(jestConfigJSON) as jest.ProjectConfig;
return JSON.stringify(extendJestConfig(jestConfig, filePath));
}
-function extendJestConfig(jestConfig: JestConfig, filePath: string) {
+function extendJestConfig(jestConfig: jest.ProjectConfig, filePath: string) {
let tsConfigFile = getTsProjectForAbsolutePath(filePath).tsConfigPath;
// swap ts config file for jest tests
@@ -51,25 +50,25 @@ function extendJestConfig(jestConfig: JestConfig, filePath: string) {
}
module.exports = {
+ canInstrument: true,
+
process(
src: string,
- filePath: string,
- jestConfig: JestConfig,
- transformOptions: TransformOptions
+ filePath: jest.Path,
+ jestConfig: jest.ProjectConfig,
+ transformOptions: jest.TransformOptions
) {
const extendedConfig = extendJestConfig(jestConfig, filePath);
- return process(src, filePath, extendedConfig, transformOptions);
+ return TsJest.process(src, filePath, extendedConfig, transformOptions);
},
getCacheKey(
src: string,
filePath: string,
jestConfigJSON: string,
- transformOptions: TransformOptions
+ transformOptions: jest.TransformOptions
) {
const extendedConfigJSON = extendJestConfigJSON(jestConfigJSON, filePath);
- return getCacheKey(src, filePath, extendedConfigJSON, transformOptions);
+ return TsJest.getCacheKey!(src, filePath, extendedConfigJSON, transformOptions);
},
-
- install,
};
diff --git a/src/ui/public/field_editor/components/scripting_help/__snapshots__/help_flyout.test.js.snap b/src/ui/public/field_editor/components/scripting_help/__snapshots__/help_flyout.test.js.snap
index 3e7298b2f0839..8a0efdef72011 100644
--- a/src/ui/public/field_editor/components/scripting_help/__snapshots__/help_flyout.test.js.snap
+++ b/src/ui/public/field_editor/components/scripting_help/__snapshots__/help_flyout.test.js.snap
@@ -34,7 +34,6 @@ exports[`ScriptingHelpFlyout should render normally 1`] = `
indexPattern={Object {}}
lang="painless"
name="myScriptedField"
- script={undefined}
/>,
"data-test-subj": "testTab",
"id": "test",
diff --git a/src/ui/public/share/components/__snapshots__/share_context_menu.test.js.snap b/src/ui/public/share/components/__snapshots__/share_context_menu.test.js.snap
index e4157f40f2ce9..5708a29a93db4 100644
--- a/src/ui/public/share/components/__snapshots__/share_context_menu.test.js.snap
+++ b/src/ui/public/share/components/__snapshots__/share_context_menu.test.js.snap
@@ -8,7 +8,6 @@ exports[`should only render permalink panel when there are no other panels 1`] =
Object {
"content": ,
"id": 1,
@@ -27,7 +26,6 @@ exports[`should render context menu panel when there are more than one panel 1`]
Object {
"content": ,
"id": 1,
@@ -37,7 +35,6 @@ exports[`should render context menu panel when there are more than one panel 1`]
"content": ,
"id": 2,
diff --git a/src/ui/ui_settings/public/__snapshots__/ui_settings_api.test.js.snap b/src/ui/ui_settings/public/__snapshots__/ui_settings_api.test.js.snap
deleted file mode 100644
index 6b461a38df274..0000000000000
--- a/src/ui/ui_settings/public/__snapshots__/ui_settings_api.test.js.snap
+++ /dev/null
@@ -1,126 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`#batchSet Buffers are always clear of previously buffered changes: buffered bar=foo 1`] = `
-Array [
- Object {
- "body": Object {
- "changes": Object {
- "bar": "foo",
- },
- },
- "method": "POST",
- "path": "/api/kibana/settings",
- },
-]
-`;
-
-exports[`#batchSet Buffers are always clear of previously buffered changes: buffered baz=box 1`] = `
-Array [
- Object {
- "body": Object {
- "changes": Object {
- "baz": "box",
- },
- },
- "method": "POST",
- "path": "/api/kibana/settings",
- },
-]
-`;
-
-exports[`#batchSet Buffers are always clear of previously buffered changes: unbuffered foo=bar 1`] = `
-Array [
- Array [
- Object {
- "body": Object {
- "changes": Object {
- "foo": "bar",
- },
- },
- "method": "POST",
- "path": "/api/kibana/settings",
- },
- ],
-]
-`;
-
-exports[`#batchSet Overwrites previously buffered values with new values for the same key: buffered bar=null 1`] = `
-Array [
- Array [
- Object {
- "body": Object {
- "changes": Object {
- "bar": null,
- },
- },
- "method": "POST",
- "path": "/api/kibana/settings",
- },
- ],
-]
-`;
-
-exports[`#batchSet Overwrites previously buffered values with new values for the same key: unbuffered foo=bar 1`] = `
-Array [
- Array [
- Object {
- "body": Object {
- "changes": Object {
- "foo": "bar",
- },
- },
- "method": "POST",
- "path": "/api/kibana/settings",
- },
- ],
-]
-`;
-
-exports[`#batchSet buffers changes while first request is in progress, sends buffered changes after first request completes: buffered foo=baz bar=bug 1`] = `
-Array [
- Array [
- Object {
- "body": Object {
- "changes": Object {
- "bar": "bug",
- "foo": "baz",
- },
- },
- "method": "POST",
- "path": "/api/kibana/settings",
- },
- ],
-]
-`;
-
-exports[`#batchSet buffers changes while first request is in progress, sends buffered changes after first request completes: unbuffered foo=bar 1`] = `
-Array [
- Array [
- Object {
- "body": Object {
- "changes": Object {
- "foo": "bar",
- },
- },
- "method": "POST",
- "path": "/api/kibana/settings",
- },
- ],
-]
-`;
-
-exports[`#batchSet sends a single change immediately: unbuffered foo=bar 1`] = `
-Array [
- Array [
- Object {
- "body": Object {
- "changes": Object {
- "foo": "bar",
- },
- },
- "method": "POST",
- "path": "/api/kibana/settings",
- },
- ],
-]
-`;
diff --git a/src/ui/ui_settings/public/ui_settings_api.test.js b/src/ui/ui_settings/public/ui_settings_api.test.js
deleted file mode 100644
index c25db836b59bb..0000000000000
--- a/src/ui/ui_settings/public/ui_settings_api.test.js
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { createUiSettingsApi } from './ui_settings_api';
-import { sendRequest } from './send_request';
-
-jest.mock('./send_request', () => {
- let resolve;
- const sendRequest = jest.fn(() => new Promise((res) => {
- resolve = res;
- }));
-
- return {
- sendRequest,
-
- resolveMockedSendRequest(value = {}) {
- resolve(value);
- },
-
- async resolveMockedSendRequestAndWaitForNext(value = {}) {
- const currentCallCount = sendRequest.mock.calls.length;
- resolve(value);
-
- const waitStart = Date.now();
- while (sendRequest.mock.calls.length === currentCallCount) {
- await new Promise(resolve => {
- setImmediate(resolve);
- });
-
- if (Date.now() - waitStart > 10000) {
- throw new Error('Waiting for subsequent call to sendRequest() timed out after 10 seconds');
- }
- }
- },
- };
-});
-
-beforeEach(() => {
- sendRequest.mockRestore();
- jest.clearAllMocks();
-});
-
-describe('#batchSet', () => {
- it('sends a single change immediately', () => {
- const uiSettingsApi = createUiSettingsApi();
- const { sendRequest } = require('./send_request');
-
- uiSettingsApi.batchSet('foo', 'bar');
-
- expect(sendRequest).toHaveBeenCalledTimes(1);
- expect(sendRequest.mock.calls).toMatchSnapshot('unbuffered foo=bar');
- });
-
- it('buffers changes while first request is in progress, sends buffered changes after first request completes', async () => {
- const uiSettingsApi = createUiSettingsApi();
- const { sendRequest, resolveMockedSendRequestAndWaitForNext } = require('./send_request');
-
- uiSettingsApi.batchSet('foo', 'bar');
- expect(sendRequest).toHaveBeenCalledTimes(1);
- expect(sendRequest.mock.calls).toMatchSnapshot('unbuffered foo=bar');
- sendRequest.mock.calls.length = 0;
-
- uiSettingsApi.batchSet('foo', 'baz');
- uiSettingsApi.batchSet('bar', 'bug');
- expect(sendRequest).not.toHaveBeenCalled();
-
- await resolveMockedSendRequestAndWaitForNext();
-
- expect(sendRequest).toHaveBeenCalledTimes(1);
- expect(sendRequest.mock.calls).toMatchSnapshot('buffered foo=baz bar=bug');
- });
-
- it('Overwrites previously buffered values with new values for the same key', async () => {
- const uiSettingsApi = createUiSettingsApi();
- const { sendRequest, resolveMockedSendRequestAndWaitForNext } = require('./send_request');
-
- uiSettingsApi.batchSet('foo', 'bar');
- expect(sendRequest).toHaveBeenCalledTimes(1);
- expect(sendRequest.mock.calls).toMatchSnapshot('unbuffered foo=bar');
- sendRequest.mock.calls.length = 0;
-
- // if changes were sent to the API now they would be { bar: 'foo' }
- uiSettingsApi.batchSet('bar', 'foo');
- // these changes override the previous one, we should now send { bar: null }
- uiSettingsApi.batchSet('bar', null);
-
- await resolveMockedSendRequestAndWaitForNext();
-
- expect(sendRequest).toHaveBeenCalledTimes(1);
- expect(sendRequest.mock.calls).toMatchSnapshot('buffered bar=null');
- });
-
- it('Buffers are always clear of previously buffered changes', async () => {
- const uiSettingsApi = createUiSettingsApi();
- const { sendRequest, resolveMockedSendRequestAndWaitForNext } = require('./send_request');
-
- uiSettingsApi.batchSet('foo', 'bar');
- expect(sendRequest).toHaveBeenCalledTimes(1);
- expect(sendRequest.mock.calls).toMatchSnapshot('unbuffered foo=bar');
- sendRequest.mock.calls.length = 0;
-
- // buffer a change
- uiSettingsApi.batchSet('bar', 'foo');
-
- // flush the buffer and wait for next request to start
- await resolveMockedSendRequestAndWaitForNext();
-
- // buffer another change
- uiSettingsApi.batchSet('baz', 'box');
-
- // flush the buffer and wait for next request to start
- await resolveMockedSendRequestAndWaitForNext();
-
- expect(sendRequest).toHaveBeenCalledTimes(2);
- expect(sendRequest.mock.calls[0]).toMatchSnapshot('buffered bar=foo');
- expect(sendRequest.mock.calls[1]).toMatchSnapshot('buffered baz=box');
- });
-});
diff --git a/x-pack/dev-tools/jest/create_jest_config.js b/x-pack/dev-tools/jest/create_jest_config.js
index ae5526190a224..305b26917bcda 100644
--- a/x-pack/dev-tools/jest/create_jest_config.js
+++ b/x-pack/dev-tools/jest/create_jest_config.js
@@ -50,6 +50,8 @@ export function createJestConfig({
reportName: 'X-Pack Jest Tests',
rootDirectory: xPackKibanaDirectory,
}]
- ]
+ ],
+ // TODO: prevent tests from making web requests that rely on this setting, see https://github.com/facebook/jest/pull/6792
+ testURL: 'about:blank',
};
}
diff --git a/x-pack/package.json b/x-pack/package.json
index 0a8682a857071..fed84badfa99c 100644
--- a/x-pack/package.json
+++ b/x-pack/package.json
@@ -25,14 +25,14 @@
"@kbn/es": "link:../packages/kbn-es",
"@kbn/plugin-helpers": "link:../packages/kbn-plugin-helpers",
"@kbn/test": "link:../packages/kbn-test",
- "@types/jest": "^22.2.3",
+ "@types/jest": "^23.3.1",
"@types/pngjs": "^3.3.1",
"abab": "^1.0.4",
"ansi-colors": "^3.0.5",
"ansicolors": "0.3.2",
"aws-sdk": "2.2.33",
"axios": "^0.18.0",
- "babel-jest": "^22.4.3",
+ "babel-jest": "^23.4.2",
"chalk": "^2.4.1",
"chance": "1.0.10",
"checksum": "0.1.1",
@@ -48,9 +48,9 @@
"gulp": "3.9.1",
"gulp-mocha": "2.2.0",
"hapi": "14.2.0",
- "jest": "^22.4.3",
- "jest-cli": "^22.4.3",
- "jest-styled-components": "^5.0.1",
+ "jest": "^23.5.0",
+ "jest-cli": "^23.5.0",
+ "jest-styled-components": "^6.1.1",
"mocha": "3.3.0",
"mustache": "^2.3.0",
"mutation-observer": "^1.0.3",
diff --git a/x-pack/plugins/ml/public/settings/filter_lists/list/__snapshots__/table.test.js.snap b/x-pack/plugins/ml/public/settings/filter_lists/list/__snapshots__/table.test.js.snap
index cc3079080ecdd..5dc2235cc2a02 100644
--- a/x-pack/plugins/ml/public/settings/filter_lists/list/__snapshots__/table.test.js.snap
+++ b/x-pack/plugins/ml/public/settings/filter_lists/list/__snapshots__/table.test.js.snap
@@ -172,7 +172,6 @@ exports[`Filter Lists Table renders with filter lists supplied 1`] = `
,
,
],
}
diff --git a/x-pack/yarn.lock b/x-pack/yarn.lock
index 686d8b5eb9885..f4fc8b3fae93e 100644
--- a/x-pack/yarn.lock
+++ b/x-pack/yarn.lock
@@ -150,9 +150,9 @@
dependencies:
"@types/node" "*"
-"@types/jest@^22.2.3":
- version "22.2.3"
- resolved "https://registry.yarnpkg.com/@types/jest/-/jest-22.2.3.tgz#0157c0316dc3722c43a7b71de3fdf3acbccef10d"
+"@types/jest@^23.3.1":
+ version "23.3.1"
+ resolved "https://registry.yarnpkg.com/@types/jest/-/jest-23.3.1.tgz#a4319aedb071d478e6f407d1c4578ec8156829cf"
"@types/loglevel@^1.5.3":
version "1.5.3"
@@ -721,12 +721,12 @@ babel-helpers@^6.24.1:
babel-runtime "^6.22.0"
babel-template "^6.24.1"
-babel-jest@^22.4.3:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-22.4.3.tgz#4b7a0b6041691bbd422ab49b3b73654a49a6627a"
+babel-jest@^23.4.2:
+ version "23.4.2"
+ resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-23.4.2.tgz#f276de67798a5d68f2d6e87ff518c2f6e1609877"
dependencies:
- babel-plugin-istanbul "^4.1.5"
- babel-preset-jest "^22.4.3"
+ babel-plugin-istanbul "^4.1.6"
+ babel-preset-jest "^23.2.0"
babel-messages@^6.23.0:
version "6.23.0"
@@ -740,17 +740,18 @@ babel-plugin-check-es2015-constants@^6.22.0:
dependencies:
babel-runtime "^6.22.0"
-babel-plugin-istanbul@^4.1.5:
- version "4.1.5"
- resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.5.tgz#6760cdd977f411d3e175bb064f2bc327d99b2b6e"
+babel-plugin-istanbul@^4.1.6:
+ version "4.1.6"
+ resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.6.tgz#36c59b2192efce81c5b378321b74175add1c9a45"
dependencies:
+ babel-plugin-syntax-object-rest-spread "^6.13.0"
find-up "^2.1.0"
- istanbul-lib-instrument "^1.7.5"
- test-exclude "^4.1.1"
+ istanbul-lib-instrument "^1.10.1"
+ test-exclude "^4.2.1"
-babel-plugin-jest-hoist@^22.4.3:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-22.4.3.tgz#7d8bcccadc2667f96a0dcc6afe1891875ee6c14a"
+babel-plugin-jest-hoist@^23.2.0:
+ version "23.2.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-23.2.0.tgz#e61fae05a1ca8801aadee57a6d66b8cefaf44167"
babel-plugin-syntax-object-rest-spread@^6.13.0:
version "6.13.0"
@@ -966,11 +967,11 @@ babel-preset-es2015@^6.24.1:
babel-plugin-transform-es2015-unicode-regex "^6.24.1"
babel-plugin-transform-regenerator "^6.24.1"
-babel-preset-jest@^22.4.3:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-22.4.3.tgz#e92eef9813b7026ab4ca675799f37419b5a44156"
+babel-preset-jest@^23.2.0:
+ version "23.2.0"
+ resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-23.2.0.tgz#8ec7a03a138f001a1a8fb1e8113652bf1a55da46"
dependencies:
- babel-plugin-jest-hoist "^22.4.3"
+ babel-plugin-jest-hoist "^23.2.0"
babel-plugin-syntax-object-rest-spread "^6.13.0"
babel-register@^6.26.0:
@@ -1002,7 +1003,7 @@ babel-template@^6.16.0, babel-template@^6.24.1, babel-template@^6.26.0:
babylon "^6.18.0"
lodash "^4.17.4"
-babel-traverse@^6.18.0, babel-traverse@^6.24.1, babel-traverse@^6.26.0:
+babel-traverse@^6.0.0, babel-traverse@^6.18.0, babel-traverse@^6.24.1, babel-traverse@^6.26.0:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee"
dependencies:
@@ -1016,7 +1017,7 @@ babel-traverse@^6.18.0, babel-traverse@^6.24.1, babel-traverse@^6.26.0:
invariant "^2.2.2"
lodash "^4.17.4"
-babel-types@^6.18.0, babel-types@^6.19.0, babel-types@^6.24.1, babel-types@^6.26.0:
+babel-types@^6.0.0, babel-types@^6.18.0, babel-types@^6.19.0, babel-types@^6.24.1, babel-types@^6.26.0:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497"
dependencies:
@@ -1179,7 +1180,13 @@ browser-process-hrtime@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-0.1.2.tgz#425d68a58d3447f02a04aa894187fce8af8b7b8e"
-browser-resolve@^1.11.2, browser-resolve@^1.8.1:
+browser-resolve@^1.11.3:
+ version "1.11.3"
+ resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.3.tgz#9b7cbb3d0f510e4cb86bdbd796124d28b5890af6"
+ dependencies:
+ resolve "1.1.7"
+
+browser-resolve@^1.8.1:
version "1.11.2"
resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.2.tgz#8ff09b0a2c421718a1051c260b32e48f442938ce"
dependencies:
@@ -2358,16 +2365,16 @@ expect.js@0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/expect.js/-/expect.js-0.3.1.tgz#b0a59a0d2eff5437544ebf0ceaa6015841d09b5b"
-expect@^22.4.3:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/expect/-/expect-22.4.3.tgz#d5a29d0a0e1fb2153557caef2674d4547e914674"
+expect@^23.5.0:
+ version "23.5.0"
+ resolved "https://registry.yarnpkg.com/expect/-/expect-23.5.0.tgz#18999a0eef8f8acf99023fde766d9c323c2562ed"
dependencies:
ansi-styles "^3.2.0"
- jest-diff "^22.4.3"
- jest-get-type "^22.4.3"
- jest-matcher-utils "^22.4.3"
- jest-message-util "^22.4.3"
- jest-regex-util "^22.4.3"
+ jest-diff "^23.5.0"
+ jest-get-type "^22.1.0"
+ jest-matcher-utils "^23.5.0"
+ jest-message-util "^23.4.0"
+ jest-regex-util "^23.3.0"
extend-shallow@^1.1.2:
version "1.1.4"
@@ -3559,7 +3566,7 @@ invariant@^2.0.0, invariant@^2.2.0, invariant@^2.2.1, invariant@^2.2.2:
dependencies:
loose-envify "^1.0.0"
-invariant@^2.1.1:
+invariant@^2.1.1, invariant@^2.2.4:
version "2.2.4"
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
dependencies:
@@ -3889,66 +3896,66 @@ isstream@~0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
-istanbul-api@^1.1.14:
- version "1.2.1"
- resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-1.2.1.tgz#0c60a0515eb11c7d65c6b50bba2c6e999acd8620"
+istanbul-api@^1.3.1:
+ version "1.3.7"
+ resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-1.3.7.tgz#a86c770d2b03e11e3f778cd7aedd82d2722092aa"
dependencies:
async "^2.1.4"
fileset "^2.0.2"
- istanbul-lib-coverage "^1.1.1"
- istanbul-lib-hook "^1.1.0"
- istanbul-lib-instrument "^1.9.1"
- istanbul-lib-report "^1.1.2"
- istanbul-lib-source-maps "^1.2.2"
- istanbul-reports "^1.1.3"
+ istanbul-lib-coverage "^1.2.1"
+ istanbul-lib-hook "^1.2.2"
+ istanbul-lib-instrument "^1.10.2"
+ istanbul-lib-report "^1.1.5"
+ istanbul-lib-source-maps "^1.2.6"
+ istanbul-reports "^1.5.1"
js-yaml "^3.7.0"
mkdirp "^0.5.1"
once "^1.4.0"
-istanbul-lib-coverage@^1.1.1:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-1.1.1.tgz#73bfb998885299415c93d38a3e9adf784a77a9da"
+istanbul-lib-coverage@^1.2.0, istanbul-lib-coverage@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.1.tgz#ccf7edcd0a0bb9b8f729feeb0930470f9af664f0"
-istanbul-lib-hook@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-1.1.0.tgz#8538d970372cb3716d53e55523dd54b557a8d89b"
+istanbul-lib-hook@^1.2.2:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-1.2.2.tgz#bc6bf07f12a641fbf1c85391d0daa8f0aea6bf86"
dependencies:
append-transform "^0.4.0"
-istanbul-lib-instrument@^1.7.5, istanbul-lib-instrument@^1.8.0, istanbul-lib-instrument@^1.9.1:
- version "1.9.1"
- resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.9.1.tgz#250b30b3531e5d3251299fdd64b0b2c9db6b558e"
+istanbul-lib-instrument@^1.10.1, istanbul-lib-instrument@^1.10.2:
+ version "1.10.2"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.2.tgz#1f55ed10ac3c47f2bdddd5307935126754d0a9ca"
dependencies:
babel-generator "^6.18.0"
babel-template "^6.16.0"
babel-traverse "^6.18.0"
babel-types "^6.18.0"
babylon "^6.18.0"
- istanbul-lib-coverage "^1.1.1"
+ istanbul-lib-coverage "^1.2.1"
semver "^5.3.0"
-istanbul-lib-report@^1.1.2:
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-1.1.2.tgz#922be27c13b9511b979bd1587359f69798c1d425"
+istanbul-lib-report@^1.1.5:
+ version "1.1.5"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-1.1.5.tgz#f2a657fc6282f96170aaf281eb30a458f7f4170c"
dependencies:
- istanbul-lib-coverage "^1.1.1"
+ istanbul-lib-coverage "^1.2.1"
mkdirp "^0.5.1"
path-parse "^1.0.5"
supports-color "^3.1.2"
-istanbul-lib-source-maps@^1.2.1, istanbul-lib-source-maps@^1.2.2:
- version "1.2.2"
- resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.2.tgz#750578602435f28a0c04ee6d7d9e0f2960e62c1c"
+istanbul-lib-source-maps@^1.2.4, istanbul-lib-source-maps@^1.2.6:
+ version "1.2.6"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.6.tgz#37b9ff661580f8fca11232752ee42e08c6675d8f"
dependencies:
debug "^3.1.0"
- istanbul-lib-coverage "^1.1.1"
+ istanbul-lib-coverage "^1.2.1"
mkdirp "^0.5.1"
rimraf "^2.6.1"
source-map "^0.5.3"
-istanbul-reports@^1.1.3:
- version "1.1.3"
- resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-1.1.3.tgz#3b9e1e8defb6d18b1d425da8e8b32c5a163f2d10"
+istanbul-reports@^1.5.1:
+ version "1.5.1"
+ resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-1.5.1.tgz#97e4dbf3b515e8c484caea15d6524eebd3ff4e1a"
dependencies:
handlebars "^4.0.3"
@@ -3974,15 +3981,15 @@ jclass@^1.0.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/jclass/-/jclass-1.2.1.tgz#eaafeec0dd6a5bf8b3ea43c04e010c637638768b"
-jest-changed-files@^22.4.3:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-22.4.3.tgz#8882181e022c38bd46a2e4d18d44d19d90a90fb2"
+jest-changed-files@^23.4.2:
+ version "23.4.2"
+ resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-23.4.2.tgz#1eed688370cd5eebafe4ae93d34bb3b64968fe83"
dependencies:
throat "^4.0.0"
-jest-cli@^22.4.3:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-22.4.3.tgz#bf16c4a5fb7edc3fa5b9bb7819e34139e88a72c7"
+jest-cli@^23.5.0:
+ version "23.5.0"
+ resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-23.5.0.tgz#d316b8e34a38a610a1efc4f0403d8ef8a55e4492"
dependencies:
ansi-escapes "^3.0.0"
chalk "^2.0.1"
@@ -3991,129 +3998,143 @@ jest-cli@^22.4.3:
graceful-fs "^4.1.11"
import-local "^1.0.0"
is-ci "^1.0.10"
- istanbul-api "^1.1.14"
- istanbul-lib-coverage "^1.1.1"
- istanbul-lib-instrument "^1.8.0"
- istanbul-lib-source-maps "^1.2.1"
- jest-changed-files "^22.4.3"
- jest-config "^22.4.3"
- jest-environment-jsdom "^22.4.3"
- jest-get-type "^22.4.3"
- jest-haste-map "^22.4.3"
- jest-message-util "^22.4.3"
- jest-regex-util "^22.4.3"
- jest-resolve-dependencies "^22.4.3"
- jest-runner "^22.4.3"
- jest-runtime "^22.4.3"
- jest-snapshot "^22.4.3"
- jest-util "^22.4.3"
- jest-validate "^22.4.3"
- jest-worker "^22.4.3"
+ istanbul-api "^1.3.1"
+ istanbul-lib-coverage "^1.2.0"
+ istanbul-lib-instrument "^1.10.1"
+ istanbul-lib-source-maps "^1.2.4"
+ jest-changed-files "^23.4.2"
+ jest-config "^23.5.0"
+ jest-environment-jsdom "^23.4.0"
+ jest-get-type "^22.1.0"
+ jest-haste-map "^23.5.0"
+ jest-message-util "^23.4.0"
+ jest-regex-util "^23.3.0"
+ jest-resolve-dependencies "^23.5.0"
+ jest-runner "^23.5.0"
+ jest-runtime "^23.5.0"
+ jest-snapshot "^23.5.0"
+ jest-util "^23.4.0"
+ jest-validate "^23.5.0"
+ jest-watcher "^23.4.0"
+ jest-worker "^23.2.0"
micromatch "^2.3.11"
node-notifier "^5.2.1"
+ prompts "^0.1.9"
realpath-native "^1.0.0"
rimraf "^2.5.4"
slash "^1.0.0"
string-length "^2.0.0"
strip-ansi "^4.0.0"
which "^1.2.12"
- yargs "^10.0.3"
+ yargs "^11.0.0"
-jest-config@^22.4.3:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-22.4.3.tgz#0e9d57db267839ea31309119b41dc2fa31b76403"
+jest-config@^23.5.0:
+ version "23.5.0"
+ resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-23.5.0.tgz#3770fba03f7507ee15f3b8867c742e48f31a9773"
dependencies:
+ babel-core "^6.0.0"
+ babel-jest "^23.4.2"
chalk "^2.0.1"
glob "^7.1.1"
- jest-environment-jsdom "^22.4.3"
- jest-environment-node "^22.4.3"
- jest-get-type "^22.4.3"
- jest-jasmine2 "^22.4.3"
- jest-regex-util "^22.4.3"
- jest-resolve "^22.4.3"
- jest-util "^22.4.3"
- jest-validate "^22.4.3"
- pretty-format "^22.4.3"
-
-jest-diff@^22.4.3:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-22.4.3.tgz#e18cc3feff0aeef159d02310f2686d4065378030"
+ jest-environment-jsdom "^23.4.0"
+ jest-environment-node "^23.4.0"
+ jest-get-type "^22.1.0"
+ jest-jasmine2 "^23.5.0"
+ jest-regex-util "^23.3.0"
+ jest-resolve "^23.5.0"
+ jest-util "^23.4.0"
+ jest-validate "^23.5.0"
+ micromatch "^2.3.11"
+ pretty-format "^23.5.0"
+
+jest-diff@^23.5.0:
+ version "23.5.0"
+ resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-23.5.0.tgz#250651a433dd0050290a07642946cc9baaf06fba"
dependencies:
chalk "^2.0.1"
diff "^3.2.0"
- jest-get-type "^22.4.3"
- pretty-format "^22.4.3"
+ jest-get-type "^22.1.0"
+ pretty-format "^23.5.0"
-jest-docblock@^22.4.3:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-22.4.3.tgz#50886f132b42b280c903c592373bb6e93bb68b19"
+jest-docblock@^23.2.0:
+ version "23.2.0"
+ resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-23.2.0.tgz#f085e1f18548d99fdd69b20207e6fd55d91383a7"
dependencies:
detect-newline "^2.1.0"
-jest-environment-jsdom@^22.4.3:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-22.4.3.tgz#d67daa4155e33516aecdd35afd82d4abf0fa8a1e"
+jest-each@^23.5.0:
+ version "23.5.0"
+ resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-23.5.0.tgz#77f7e2afe6132a80954b920006e78239862b10ba"
dependencies:
- jest-mock "^22.4.3"
- jest-util "^22.4.3"
+ chalk "^2.0.1"
+ pretty-format "^23.5.0"
+
+jest-environment-jsdom@^23.4.0:
+ version "23.4.0"
+ resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-23.4.0.tgz#056a7952b3fea513ac62a140a2c368c79d9e6023"
+ dependencies:
+ jest-mock "^23.2.0"
+ jest-util "^23.4.0"
jsdom "^11.5.1"
-jest-environment-node@^22.4.3:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-22.4.3.tgz#54c4eaa374c83dd52a9da8759be14ebe1d0b9129"
+jest-environment-node@^23.4.0:
+ version "23.4.0"
+ resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-23.4.0.tgz#57e80ed0841dea303167cce8cd79521debafde10"
dependencies:
- jest-mock "^22.4.3"
- jest-util "^22.4.3"
+ jest-mock "^23.2.0"
+ jest-util "^23.4.0"
-jest-get-type@^22.4.3:
+jest-get-type@^22.1.0:
version "22.4.3"
resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-22.4.3.tgz#e3a8504d8479342dd4420236b322869f18900ce4"
-jest-haste-map@^22.4.3:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-22.4.3.tgz#25842fa2ba350200767ac27f658d58b9d5c2e20b"
+jest-haste-map@^23.5.0:
+ version "23.5.0"
+ resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-23.5.0.tgz#d4ca618188bd38caa6cb20349ce6610e194a8065"
dependencies:
fb-watchman "^2.0.0"
graceful-fs "^4.1.11"
- jest-docblock "^22.4.3"
- jest-serializer "^22.4.3"
- jest-worker "^22.4.3"
+ invariant "^2.2.4"
+ jest-docblock "^23.2.0"
+ jest-serializer "^23.0.1"
+ jest-worker "^23.2.0"
micromatch "^2.3.11"
sane "^2.0.0"
-jest-jasmine2@^22.4.3:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-22.4.3.tgz#4daf64cd14c793da9db34a7c7b8dcfe52a745965"
+jest-jasmine2@^23.5.0:
+ version "23.5.0"
+ resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-23.5.0.tgz#05fe7f1788e650eeb5a03929e6461ea2e9f3db53"
dependencies:
+ babel-traverse "^6.0.0"
chalk "^2.0.1"
co "^4.6.0"
- expect "^22.4.3"
- graceful-fs "^4.1.11"
+ expect "^23.5.0"
is-generator-fn "^1.0.0"
- jest-diff "^22.4.3"
- jest-matcher-utils "^22.4.3"
- jest-message-util "^22.4.3"
- jest-snapshot "^22.4.3"
- jest-util "^22.4.3"
- source-map-support "^0.5.0"
-
-jest-leak-detector@^22.4.3:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-22.4.3.tgz#2b7b263103afae8c52b6b91241a2de40117e5b35"
+ jest-diff "^23.5.0"
+ jest-each "^23.5.0"
+ jest-matcher-utils "^23.5.0"
+ jest-message-util "^23.4.0"
+ jest-snapshot "^23.5.0"
+ jest-util "^23.4.0"
+ pretty-format "^23.5.0"
+
+jest-leak-detector@^23.5.0:
+ version "23.5.0"
+ resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-23.5.0.tgz#14ac2a785bd625160a2ea968fd5d98b7dcea3e64"
dependencies:
- pretty-format "^22.4.3"
+ pretty-format "^23.5.0"
-jest-matcher-utils@^22.4.3:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-22.4.3.tgz#4632fe428ebc73ebc194d3c7b65d37b161f710ff"
+jest-matcher-utils@^23.5.0:
+ version "23.5.0"
+ resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-23.5.0.tgz#0e2ea67744cab78c9ab15011c4d888bdd3e49e2a"
dependencies:
chalk "^2.0.1"
- jest-get-type "^22.4.3"
- pretty-format "^22.4.3"
+ jest-get-type "^22.1.0"
+ pretty-format "^23.5.0"
-jest-message-util@^22.4.3:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-22.4.3.tgz#cf3d38aafe4befddbfc455e57d65d5239e399eb7"
+jest-message-util@^23.4.0:
+ version "23.4.0"
+ resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-23.4.0.tgz#17610c50942349508d01a3d1e0bda2c079086a9f"
dependencies:
"@babel/code-frame" "^7.0.0-beta.35"
chalk "^2.0.1"
@@ -4121,123 +4142,140 @@ jest-message-util@^22.4.3:
slash "^1.0.0"
stack-utils "^1.0.1"
-jest-mock@^22.4.3:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-22.4.3.tgz#f63ba2f07a1511772cdc7979733397df770aabc7"
+jest-mock@^23.2.0:
+ version "23.2.0"
+ resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-23.2.0.tgz#ad1c60f29e8719d47c26e1138098b6d18b261134"
-jest-regex-util@^22.4.3:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-22.4.3.tgz#a826eb191cdf22502198c5401a1fc04de9cef5af"
+jest-regex-util@^23.3.0:
+ version "23.3.0"
+ resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-23.3.0.tgz#5f86729547c2785c4002ceaa8f849fe8ca471bc5"
-jest-resolve-dependencies@^22.4.3:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-22.4.3.tgz#e2256a5a846732dc3969cb72f3c9ad7725a8195e"
+jest-resolve-dependencies@^23.5.0:
+ version "23.5.0"
+ resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-23.5.0.tgz#10c4d135beb9d2256de1fedc7094916c3ad74af7"
dependencies:
- jest-regex-util "^22.4.3"
+ jest-regex-util "^23.3.0"
+ jest-snapshot "^23.5.0"
-jest-resolve@^22.4.3:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-22.4.3.tgz#0ce9d438c8438229aa9b916968ec6b05c1abb4ea"
+jest-resolve@^23.5.0:
+ version "23.5.0"
+ resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-23.5.0.tgz#3b8e7f67e84598f0caf63d1530bd8534a189d0e6"
dependencies:
- browser-resolve "^1.11.2"
+ browser-resolve "^1.11.3"
chalk "^2.0.1"
+ realpath-native "^1.0.0"
-jest-runner@^22.4.3:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-22.4.3.tgz#298ddd6a22b992c64401b4667702b325e50610c3"
+jest-runner@^23.5.0:
+ version "23.5.0"
+ resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-23.5.0.tgz#570f7a044da91648b5bb9b6baacdd511076c71d7"
dependencies:
exit "^0.1.2"
- jest-config "^22.4.3"
- jest-docblock "^22.4.3"
- jest-haste-map "^22.4.3"
- jest-jasmine2 "^22.4.3"
- jest-leak-detector "^22.4.3"
- jest-message-util "^22.4.3"
- jest-runtime "^22.4.3"
- jest-util "^22.4.3"
- jest-worker "^22.4.3"
+ graceful-fs "^4.1.11"
+ jest-config "^23.5.0"
+ jest-docblock "^23.2.0"
+ jest-haste-map "^23.5.0"
+ jest-jasmine2 "^23.5.0"
+ jest-leak-detector "^23.5.0"
+ jest-message-util "^23.4.0"
+ jest-runtime "^23.5.0"
+ jest-util "^23.4.0"
+ jest-worker "^23.2.0"
+ source-map-support "^0.5.6"
throat "^4.0.0"
-jest-runtime@^22.4.3:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-22.4.3.tgz#b69926c34b851b920f666c93e86ba2912087e3d0"
+jest-runtime@^23.5.0:
+ version "23.5.0"
+ resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-23.5.0.tgz#eb503525a196dc32f2f9974e3482d26bdf7b63ce"
dependencies:
babel-core "^6.0.0"
- babel-jest "^22.4.3"
- babel-plugin-istanbul "^4.1.5"
+ babel-plugin-istanbul "^4.1.6"
chalk "^2.0.1"
convert-source-map "^1.4.0"
exit "^0.1.2"
+ fast-json-stable-stringify "^2.0.0"
graceful-fs "^4.1.11"
- jest-config "^22.4.3"
- jest-haste-map "^22.4.3"
- jest-regex-util "^22.4.3"
- jest-resolve "^22.4.3"
- jest-util "^22.4.3"
- jest-validate "^22.4.3"
- json-stable-stringify "^1.0.1"
+ jest-config "^23.5.0"
+ jest-haste-map "^23.5.0"
+ jest-message-util "^23.4.0"
+ jest-regex-util "^23.3.0"
+ jest-resolve "^23.5.0"
+ jest-snapshot "^23.5.0"
+ jest-util "^23.4.0"
+ jest-validate "^23.5.0"
micromatch "^2.3.11"
realpath-native "^1.0.0"
slash "^1.0.0"
strip-bom "3.0.0"
write-file-atomic "^2.1.0"
- yargs "^10.0.3"
+ yargs "^11.0.0"
-jest-serializer@^22.4.3:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-22.4.3.tgz#a679b81a7f111e4766235f4f0c46d230ee0f7436"
+jest-serializer@^23.0.1:
+ version "23.0.1"
+ resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-23.0.1.tgz#a3776aeb311e90fe83fab9e533e85102bd164165"
-jest-snapshot@^22.4.3:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-22.4.3.tgz#b5c9b42846ffb9faccb76b841315ba67887362d2"
+jest-snapshot@^23.5.0:
+ version "23.5.0"
+ resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-23.5.0.tgz#cc368ebd8513e1175e2a7277f37a801b7358ae79"
dependencies:
+ babel-types "^6.0.0"
chalk "^2.0.1"
- jest-diff "^22.4.3"
- jest-matcher-utils "^22.4.3"
+ jest-diff "^23.5.0"
+ jest-matcher-utils "^23.5.0"
+ jest-message-util "^23.4.0"
+ jest-resolve "^23.5.0"
mkdirp "^0.5.1"
natural-compare "^1.4.0"
- pretty-format "^22.4.3"
+ pretty-format "^23.5.0"
+ semver "^5.5.0"
-jest-styled-components@^5.0.1:
- version "5.0.1"
- resolved "https://registry.yarnpkg.com/jest-styled-components/-/jest-styled-components-5.0.1.tgz#386c5a161a0f5e783444d624bfc18c6d228d1277"
+jest-styled-components@^6.1.1:
+ version "6.1.1"
+ resolved "https://registry.yarnpkg.com/jest-styled-components/-/jest-styled-components-6.1.1.tgz#2b9b6e3ded212b43ea71df72e82d55b856e9d1ed"
dependencies:
css "^2.2.1"
-jest-util@^22.4.3:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-22.4.3.tgz#c70fec8eec487c37b10b0809dc064a7ecf6aafac"
+jest-util@^23.4.0:
+ version "23.4.0"
+ resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-23.4.0.tgz#4d063cb927baf0a23831ff61bec2cbbf49793561"
dependencies:
callsites "^2.0.0"
chalk "^2.0.1"
graceful-fs "^4.1.11"
is-ci "^1.0.10"
- jest-message-util "^22.4.3"
+ jest-message-util "^23.4.0"
mkdirp "^0.5.1"
+ slash "^1.0.0"
source-map "^0.6.0"
-jest-validate@^22.4.3:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-22.4.3.tgz#0780954a5a7daaeec8d3c10834b9280865976b30"
+jest-validate@^23.5.0:
+ version "23.5.0"
+ resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-23.5.0.tgz#f5df8f761cf43155e1b2e21d6e9de8a2852d0231"
dependencies:
chalk "^2.0.1"
- jest-config "^22.4.3"
- jest-get-type "^22.4.3"
+ jest-get-type "^22.1.0"
leven "^2.1.0"
- pretty-format "^22.4.3"
+ pretty-format "^23.5.0"
-jest-worker@^22.4.3:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-22.4.3.tgz#5c421417cba1c0abf64bf56bd5fb7968d79dd40b"
+jest-watcher@^23.4.0:
+ version "23.4.0"
+ resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-23.4.0.tgz#d2e28ce74f8dad6c6afc922b92cabef6ed05c91c"
+ dependencies:
+ ansi-escapes "^3.0.0"
+ chalk "^2.0.1"
+ string-length "^2.0.0"
+
+jest-worker@^23.2.0:
+ version "23.2.0"
+ resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-23.2.0.tgz#faf706a8da36fae60eb26957257fa7b5d8ea02b9"
dependencies:
merge-stream "^1.0.1"
-jest@^22.4.3:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/jest/-/jest-22.4.3.tgz#2261f4b117dc46d9a4a1a673d2150958dee92f16"
+jest@^23.5.0:
+ version "23.5.0"
+ resolved "https://registry.yarnpkg.com/jest/-/jest-23.5.0.tgz#80de353d156ea5ea4a7332f7962ac79135fbc62e"
dependencies:
import-local "^1.0.0"
- jest-cli "^22.4.3"
+ jest-cli "^23.5.0"
joi@10.x.x:
version "10.6.0"
@@ -4445,6 +4483,10 @@ kind-of@^6.0.0, kind-of@^6.0.2:
version "6.0.2"
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051"
+kleur@^2.0.1:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/kleur/-/kleur-2.0.2.tgz#b704f4944d95e255d038f0cb05fb8a602c55a300"
+
lazy-cache@^1.0.3:
version "1.0.4"
resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e"
@@ -5913,9 +5955,9 @@ preserve@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
-pretty-format@^22.4.3:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-22.4.3.tgz#f873d780839a9c02e9664c8a082e9ee79eaac16f"
+pretty-format@^23.5.0:
+ version "23.5.0"
+ resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-23.5.0.tgz#0f9601ad9da70fe690a269cd3efca732c210687c"
dependencies:
ansi-regex "^3.0.0"
ansi-styles "^3.2.0"
@@ -5954,6 +5996,13 @@ promise@^7.1.1:
dependencies:
asap "~2.0.3"
+prompts@^0.1.9:
+ version "0.1.14"
+ resolved "https://registry.yarnpkg.com/prompts/-/prompts-0.1.14.tgz#a8e15c612c5c9ec8f8111847df3337c9cbd443b2"
+ dependencies:
+ kleur "^2.0.1"
+ sisteransi "^0.1.1"
+
prop-types@15.5.8:
version "15.5.8"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.5.8.tgz#6b7b2e141083be38c8595aa51fc55775c7199394"
@@ -7076,6 +7125,10 @@ sinon@^5.0.7:
supports-color "^5.1.0"
type-detect "^4.0.5"
+sisteransi@^0.1.1:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-0.1.1.tgz#5431447d5f7d1675aac667ccd0b865a4994cb3ce"
+
slash@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55"
@@ -7150,10 +7203,11 @@ source-map-support@^0.4.15:
dependencies:
source-map "^0.5.6"
-source-map-support@^0.5.0:
- version "0.5.0"
- resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.0.tgz#2018a7ad2bdf8faf2691e5fddab26bed5a2bacab"
+source-map-support@^0.5.6:
+ version "0.5.9"
+ resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.9.tgz#41bc953b2534267ea2d605bccfa7bfa3111ced5f"
dependencies:
+ buffer-from "^1.0.0"
source-map "^0.6.0"
source-map-url@^0.4.0:
@@ -7565,9 +7619,9 @@ temp@^0.8.3:
os-tmpdir "^1.0.0"
rimraf "~2.2.6"
-test-exclude@^4.1.1:
- version "4.1.1"
- resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-4.1.1.tgz#4d84964b0966b0087ecc334a2ce002d3d9341e26"
+test-exclude@^4.2.1:
+ version "4.2.3"
+ resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-4.2.3.tgz#a9a5e64474e4398339245a0a769ad7c2f4a97c20"
dependencies:
arrify "^1.0.1"
micromatch "^2.3.11"
@@ -8296,9 +8350,9 @@ yargs-parser@^5.0.0:
dependencies:
camelcase "^3.0.0"
-yargs-parser@^8.1.0:
- version "8.1.0"
- resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-8.1.0.tgz#f1376a33b6629a5d063782944da732631e966950"
+yargs-parser@^9.0.2:
+ version "9.0.2"
+ resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-9.0.2.tgz#9ccf6a43460fe4ed40a9bb68f48d43b8a68cc077"
dependencies:
camelcase "^4.1.0"
@@ -8320,9 +8374,9 @@ yargs@4.7.1:
y18n "^3.2.1"
yargs-parser "^2.4.0"
-yargs@^10.0.3:
- version "10.1.1"
- resolved "https://registry.yarnpkg.com/yargs/-/yargs-10.1.1.tgz#5fe1ea306985a099b33492001fa19a1e61efe285"
+yargs@^11.0.0:
+ version "11.1.0"
+ resolved "http://registry.npmjs.org/yargs/-/yargs-11.1.0.tgz#90b869934ed6e871115ea2ff58b03f4724ed2d77"
dependencies:
cliui "^4.0.0"
decamelize "^1.1.1"
@@ -8335,7 +8389,7 @@ yargs@^10.0.3:
string-width "^2.0.0"
which-module "^2.0.0"
y18n "^3.2.1"
- yargs-parser "^8.1.0"
+ yargs-parser "^9.0.2"
yargs@^7.0.0:
version "7.1.0"
diff --git a/yarn.lock b/yarn.lock
index 800284455806d..6050e14540324 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -394,9 +394,9 @@
dependencies:
"@types/node" "*"
-"@types/jest@^22.2.3":
- version "22.2.3"
- resolved "https://registry.yarnpkg.com/@types/jest/-/jest-22.2.3.tgz#0157c0316dc3722c43a7b71de3fdf3acbccef10d"
+"@types/jest@^23.3.1":
+ version "23.3.1"
+ resolved "https://registry.yarnpkg.com/@types/jest/-/jest-23.3.1.tgz#a4319aedb071d478e6f407d1c4578ec8156829cf"
"@types/joi@*":
version "13.3.0"
@@ -922,10 +922,6 @@ array-equal@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93"
-array-filter@~0.0.0:
- version "0.0.1"
- resolved "https://registry.yarnpkg.com/array-filter/-/array-filter-0.0.1.tgz#7da8cf2e26628ed732803581fd21f67cacd2eeec"
-
array-find-index@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1"
@@ -941,18 +937,10 @@ array-includes@^3.0.3:
define-properties "^1.1.2"
es-abstract "^1.7.0"
-array-map@~0.0.0:
- version "0.0.0"
- resolved "https://registry.yarnpkg.com/array-map/-/array-map-0.0.0.tgz#88a2bab73d1cf7bcd5c1b118a003f66f665fa662"
-
array-parallel@~0.1.3:
version "0.1.3"
resolved "https://registry.yarnpkg.com/array-parallel/-/array-parallel-0.1.3.tgz#8f785308926ed5aa478c47e64d1b334b6c0c947d"
-array-reduce@~0.0.0:
- version "0.0.0"
- resolved "https://registry.yarnpkg.com/array-reduce/-/array-reduce-0.0.0.tgz#173899d3ffd1c7d9383e4479525dbe278cab5f2b"
-
array-series@~0.1.5:
version "0.1.5"
resolved "https://registry.yarnpkg.com/array-series/-/array-series-0.1.5.tgz#df5d37bfc5c2ef0755e2aa4f92feae7d4b5a972f"
@@ -1210,30 +1198,6 @@ babel-core@^6.0.0, babel-core@^6.18.0, babel-core@^6.26.0:
slash "^1.0.0"
source-map "^0.5.6"
-babel-core@^6.26.3:
- version "6.26.3"
- resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.3.tgz#b2e2f09e342d0f0c88e2f02e067794125e75c207"
- dependencies:
- babel-code-frame "^6.26.0"
- babel-generator "^6.26.0"
- babel-helpers "^6.24.1"
- babel-messages "^6.23.0"
- babel-register "^6.26.0"
- babel-runtime "^6.26.0"
- babel-template "^6.26.0"
- babel-traverse "^6.26.0"
- babel-types "^6.26.0"
- babylon "^6.18.0"
- convert-source-map "^1.5.1"
- debug "^2.6.9"
- json5 "^0.5.1"
- lodash "^4.17.4"
- minimatch "^3.0.4"
- path-is-absolute "^1.0.1"
- private "^0.1.8"
- slash "^1.0.0"
- source-map "^0.5.7"
-
babel-eslint@8.1.2:
version "8.1.2"
resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-8.1.2.tgz#a39230b0c20ecbaa19a35d5633bf9b9ca2c8116f"
@@ -1367,12 +1331,12 @@ babel-helpers@^6.16.0, babel-helpers@^6.24.1:
babel-runtime "^6.22.0"
babel-template "^6.24.1"
-babel-jest@^22.4.3:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-22.4.3.tgz#4b7a0b6041691bbd422ab49b3b73654a49a6627a"
+babel-jest@^23.4.2:
+ version "23.4.2"
+ resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-23.4.2.tgz#f276de67798a5d68f2d6e87ff518c2f6e1609877"
dependencies:
- babel-plugin-istanbul "^4.1.5"
- babel-preset-jest "^22.4.3"
+ babel-plugin-istanbul "^4.1.6"
+ babel-preset-jest "^23.2.0"
babel-loader@7.1.2:
version "7.1.2"
@@ -1398,14 +1362,6 @@ babel-plugin-check-es2015-constants@^6.22.0:
dependencies:
babel-runtime "^6.22.0"
-babel-plugin-istanbul@^4.1.5:
- version "4.1.5"
- resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.5.tgz#6760cdd977f411d3e175bb064f2bc327d99b2b6e"
- dependencies:
- find-up "^2.1.0"
- istanbul-lib-instrument "^1.7.5"
- test-exclude "^4.1.1"
-
babel-plugin-istanbul@^4.1.6:
version "4.1.6"
resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.6.tgz#36c59b2192efce81c5b378321b74175add1c9a45"
@@ -1415,9 +1371,9 @@ babel-plugin-istanbul@^4.1.6:
istanbul-lib-instrument "^1.10.1"
test-exclude "^4.2.1"
-babel-plugin-jest-hoist@^22.4.3:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-22.4.3.tgz#7d8bcccadc2667f96a0dcc6afe1891875ee6c14a"
+babel-plugin-jest-hoist@^23.2.0:
+ version "23.2.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-23.2.0.tgz#e61fae05a1ca8801aadee57a6d66b8cefaf44167"
babel-plugin-syntax-async-functions@^6.8.0:
version "6.13.0"
@@ -1576,15 +1532,6 @@ babel-plugin-transform-es2015-modules-commonjs@^6.23.0, babel-plugin-transform-e
babel-template "^6.26.0"
babel-types "^6.26.0"
-babel-plugin-transform-es2015-modules-commonjs@^6.26.2:
- version "6.26.2"
- resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz#58a793863a9e7ca870bdc5a881117ffac27db6f3"
- dependencies:
- babel-plugin-transform-strict-mode "^6.24.1"
- babel-runtime "^6.26.0"
- babel-template "^6.26.0"
- babel-types "^6.26.0"
-
babel-plugin-transform-es2015-modules-systemjs@^6.23.0, babel-plugin-transform-es2015-modules-systemjs@^6.24.1:
version "6.24.1"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz#ff89a142b9119a906195f5f106ecf305d9407d23"
@@ -1800,11 +1747,11 @@ babel-preset-flow@^6.23.0:
dependencies:
babel-plugin-transform-flow-strip-types "^6.22.0"
-babel-preset-jest@^22.4.3:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-22.4.3.tgz#e92eef9813b7026ab4ca675799f37419b5a44156"
+babel-preset-jest@^23.2.0:
+ version "23.2.0"
+ resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-23.2.0.tgz#8ec7a03a138f001a1a8fb1e8113652bf1a55da46"
dependencies:
- babel-plugin-jest-hoist "^22.4.3"
+ babel-plugin-jest-hoist "^23.2.0"
babel-plugin-syntax-object-rest-spread "^6.13.0"
babel-preset-react@^6.24.1:
@@ -1842,7 +1789,7 @@ babel-register@^6.18.0, babel-register@^6.26.0:
mkdirp "^0.5.1"
source-map-support "^0.4.15"
-babel-runtime@^6.11.6, babel-runtime@^6.18.0, babel-runtime@^6.20.0, babel-runtime@^6.22.0, babel-runtime@^6.23.0, babel-runtime@^6.26.0, babel-runtime@^6.6.1, babel-runtime@^6.9.2:
+babel-runtime@^6.11.6, babel-runtime@^6.18.0, babel-runtime@^6.20.0, babel-runtime@^6.22.0, babel-runtime@^6.23.0, babel-runtime@^6.26.0, babel-runtime@^6.6.1:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
dependencies:
@@ -1859,7 +1806,7 @@ babel-template@^6.16.0, babel-template@^6.24.1, babel-template@^6.26.0:
babylon "^6.18.0"
lodash "^4.17.4"
-babel-traverse@^6.18.0, babel-traverse@^6.21.0, babel-traverse@^6.24.1, babel-traverse@^6.26.0:
+babel-traverse@^6.0.0, babel-traverse@^6.18.0, babel-traverse@^6.21.0, babel-traverse@^6.24.1, babel-traverse@^6.26.0:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee"
dependencies:
@@ -1873,7 +1820,7 @@ babel-traverse@^6.18.0, babel-traverse@^6.21.0, babel-traverse@^6.24.1, babel-tr
invariant "^2.2.2"
lodash "^4.17.4"
-babel-types@^6.18.0, babel-types@^6.19.0, babel-types@^6.21.0, babel-types@^6.24.1, babel-types@^6.26.0:
+babel-types@^6.0.0, babel-types@^6.18.0, babel-types@^6.19.0, babel-types@^6.21.0, babel-types@^6.24.1, babel-types@^6.26.0:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497"
dependencies:
@@ -2183,7 +2130,13 @@ browser-process-hrtime@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-0.1.2.tgz#425d68a58d3447f02a04aa894187fce8af8b7b8e"
-browser-resolve@^1.11.2, browser-resolve@^1.8.1:
+browser-resolve@^1.11.3:
+ version "1.11.3"
+ resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.3.tgz#9b7cbb3d0f510e4cb86bdbd796124d28b5890af6"
+ dependencies:
+ resolve "1.1.7"
+
+browser-resolve@^1.8.1:
version "1.11.2"
resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.2.tgz#8ff09b0a2c421718a1051c260b32e48f442938ce"
dependencies:
@@ -2687,7 +2640,7 @@ chokidar@1.6.0:
optionalDependencies:
fsevents "^1.0.0"
-chokidar@^1.4.1, chokidar@^1.6.0:
+chokidar@^1.4.1:
version "1.7.0"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468"
dependencies:
@@ -2883,6 +2836,10 @@ cloneable-readable@^1.0.0:
process-nextick-args "^2.0.0"
readable-stream "^2.3.5"
+closest-file-data@^0.1.4:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/closest-file-data/-/closest-file-data-0.1.4.tgz#975f87c132f299d24a0375b9f63ca3fb88f72b3a"
+
co@3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/co/-/co-3.1.0.tgz#4ea54ea5a08938153185e15210c68d9092bc1b78"
@@ -3014,7 +2971,7 @@ commondir@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
-compare-versions@3.1.0, compare-versions@^3.1.0:
+compare-versions@3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.1.0.tgz#43310256a5c555aaed4193c04d8f154cf9c6efd5"
@@ -3226,22 +3183,6 @@ cosmiconfig@^2.1.0, cosmiconfig@^2.1.1:
parse-json "^2.2.0"
require-from-string "^1.1.0"
-cpx@^1.5.0:
- version "1.5.0"
- resolved "https://registry.yarnpkg.com/cpx/-/cpx-1.5.0.tgz#185be018511d87270dedccc293171e37655ab88f"
- dependencies:
- babel-runtime "^6.9.2"
- chokidar "^1.6.0"
- duplexer "^0.1.1"
- glob "^7.0.5"
- glob2base "^0.0.12"
- minimatch "^3.0.2"
- mkdirp "^0.5.1"
- resolve "^1.1.7"
- safe-buffer "^5.0.1"
- shell-quote "^1.6.1"
- subarg "^1.0.0"
-
create-ecdh@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.0.tgz#888c723596cdf7612f6498233eebd7a35301737d"
@@ -4144,7 +4085,7 @@ duplexer3@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
-duplexer@^0.1.1, duplexer@~0.1.1:
+duplexer@~0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1"
@@ -4579,9 +4520,9 @@ eslint-plugin-import@2.8.0:
minimatch "^3.0.3"
read-pkg-up "^2.0.0"
-eslint-plugin-jest@^21.6.2:
- version "21.15.0"
- resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-21.15.0.tgz#645a3f560d3e61d99611b215adc80b4f31e6d896"
+eslint-plugin-jest@^21.22.0:
+ version "21.22.0"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-21.22.0.tgz#1b9e49b3e5ce9a3d0a51af4579991d517f33726e"
eslint-plugin-mocha@4.11.0:
version "4.11.0"
@@ -4874,16 +4815,16 @@ expect.js@0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/expect.js/-/expect.js-0.3.1.tgz#b0a59a0d2eff5437544ebf0ceaa6015841d09b5b"
-expect@^22.4.3:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/expect/-/expect-22.4.3.tgz#d5a29d0a0e1fb2153557caef2674d4547e914674"
+expect@^23.5.0:
+ version "23.5.0"
+ resolved "https://registry.yarnpkg.com/expect/-/expect-23.5.0.tgz#18999a0eef8f8acf99023fde766d9c323c2562ed"
dependencies:
ansi-styles "^3.2.0"
- jest-diff "^22.4.3"
- jest-get-type "^22.4.3"
- jest-matcher-utils "^22.4.3"
- jest-message-util "^22.4.3"
- jest-regex-util "^22.4.3"
+ jest-diff "^23.5.0"
+ jest-get-type "^22.1.0"
+ jest-matcher-utils "^23.5.0"
+ jest-message-util "^23.4.0"
+ jest-regex-util "^23.3.0"
expiry-js@0.1.7:
version "0.1.7"
@@ -5183,10 +5124,6 @@ find-cache-dir@^1.0.0:
make-dir "^1.0.0"
pkg-dir "^2.0.0"
-find-index@^0.1.1:
- version "0.1.1"
- resolved "https://registry.yarnpkg.com/find-index/-/find-index-0.1.1.tgz#675d358b2ca3892d795a1ab47232f8b6e2e0dde4"
-
find-root@^0.1.1:
version "0.1.2"
resolved "https://registry.yarnpkg.com/find-root/-/find-root-0.1.2.tgz#98d2267cff1916ccaf2743b3a0eea81d79d7dcd1"
@@ -5356,9 +5293,9 @@ fs-exists-sync@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz#982d6893af918e72d08dec9e8673ff2b5a8d6add"
-fs-extra@6.0.0:
- version "6.0.0"
- resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-6.0.0.tgz#0f0afb290bb3deb87978da816fcd3c7797f3a817"
+fs-extra@6.0.1:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-6.0.1.tgz#8abc128f7946e310135ddc93b98bddb410e7a34b"
dependencies:
graceful-fs "^4.1.2"
jsonfile "^4.0.0"
@@ -5603,12 +5540,6 @@ glob-to-regexp@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab"
-glob2base@^0.0.12:
- version "0.0.12"
- resolved "https://registry.yarnpkg.com/glob2base/-/glob2base-0.0.12.tgz#9d419b3e28f12e83a362164a277055922c9c0d56"
- dependencies:
- find-index "^0.1.1"
-
glob@6.0.4, glob@^6.0.4:
version "6.0.4"
resolved "https://registry.yarnpkg.com/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22"
@@ -6634,7 +6565,7 @@ into-stream@^3.1.0:
from2 "^2.1.1"
p-is-promise "^1.1.0"
-invariant@^2.0.0, invariant@^2.1.1, invariant@^2.2.0, invariant@^2.2.1, invariant@^2.2.2:
+invariant@^2.0.0, invariant@^2.1.1, invariant@^2.2.0, invariant@^2.2.1, invariant@^2.2.2, invariant@^2.2.4:
version "2.2.4"
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
dependencies:
@@ -7120,19 +7051,18 @@ isstream@~0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
-istanbul-api@^1.1.14:
- version "1.3.1"
- resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-1.3.1.tgz#4c3b05d18c0016d1022e079b98dc82c40f488954"
+istanbul-api@^1.3.1:
+ version "1.3.7"
+ resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-1.3.7.tgz#a86c770d2b03e11e3f778cd7aedd82d2722092aa"
dependencies:
async "^2.1.4"
- compare-versions "^3.1.0"
fileset "^2.0.2"
- istanbul-lib-coverage "^1.2.0"
- istanbul-lib-hook "^1.2.0"
- istanbul-lib-instrument "^1.10.1"
- istanbul-lib-report "^1.1.4"
- istanbul-lib-source-maps "^1.2.4"
- istanbul-reports "^1.3.0"
+ istanbul-lib-coverage "^1.2.1"
+ istanbul-lib-hook "^1.2.2"
+ istanbul-lib-instrument "^1.10.2"
+ istanbul-lib-report "^1.1.5"
+ istanbul-lib-source-maps "^1.2.6"
+ istanbul-reports "^1.5.1"
js-yaml "^3.7.0"
mkdirp "^0.5.1"
once "^1.4.0"
@@ -7146,17 +7076,21 @@ istanbul-instrumenter-loader@3.0.0:
loader-utils "^1.1.0"
schema-utils "^0.3.0"
-istanbul-lib-coverage@^1.1.1, istanbul-lib-coverage@^1.1.2, istanbul-lib-coverage@^1.2.0:
+istanbul-lib-coverage@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.0.tgz#f7d8f2e42b97e37fe796114cb0f9d68b5e3a4341"
-istanbul-lib-hook@^1.2.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-1.2.0.tgz#ae556fd5a41a6e8efa0b1002b1e416dfeaf9816c"
+istanbul-lib-coverage@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.1.tgz#ccf7edcd0a0bb9b8f729feeb0930470f9af664f0"
+
+istanbul-lib-hook@^1.2.2:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-1.2.2.tgz#bc6bf07f12a641fbf1c85391d0daa8f0aea6bf86"
dependencies:
append-transform "^0.4.0"
-istanbul-lib-instrument@^1.10.1, istanbul-lib-instrument@^1.7.3, istanbul-lib-instrument@^1.7.5, istanbul-lib-instrument@^1.8.0:
+istanbul-lib-instrument@^1.10.1, istanbul-lib-instrument@^1.7.3:
version "1.10.1"
resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.1.tgz#724b4b6caceba8692d3f1f9d0727e279c401af7b"
dependencies:
@@ -7168,38 +7102,50 @@ istanbul-lib-instrument@^1.10.1, istanbul-lib-instrument@^1.7.3, istanbul-lib-in
istanbul-lib-coverage "^1.2.0"
semver "^5.3.0"
-istanbul-lib-report@^1.1.4:
- version "1.1.4"
- resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-1.1.4.tgz#e886cdf505c4ebbd8e099e4396a90d0a28e2acb5"
+istanbul-lib-instrument@^1.10.2:
+ version "1.10.2"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.2.tgz#1f55ed10ac3c47f2bdddd5307935126754d0a9ca"
dependencies:
- istanbul-lib-coverage "^1.2.0"
+ babel-generator "^6.18.0"
+ babel-template "^6.16.0"
+ babel-traverse "^6.18.0"
+ babel-types "^6.18.0"
+ babylon "^6.18.0"
+ istanbul-lib-coverage "^1.2.1"
+ semver "^5.3.0"
+
+istanbul-lib-report@^1.1.5:
+ version "1.1.5"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-1.1.5.tgz#f2a657fc6282f96170aaf281eb30a458f7f4170c"
+ dependencies:
+ istanbul-lib-coverage "^1.2.1"
mkdirp "^0.5.1"
path-parse "^1.0.5"
supports-color "^3.1.2"
-istanbul-lib-source-maps@^1.2.1:
- version "1.2.3"
- resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.3.tgz#20fb54b14e14b3fb6edb6aca3571fd2143db44e6"
+istanbul-lib-source-maps@^1.2.4:
+ version "1.2.4"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.4.tgz#cc7ccad61629f4efff8e2f78adb8c522c9976ec7"
dependencies:
debug "^3.1.0"
- istanbul-lib-coverage "^1.1.2"
+ istanbul-lib-coverage "^1.2.0"
mkdirp "^0.5.1"
rimraf "^2.6.1"
source-map "^0.5.3"
-istanbul-lib-source-maps@^1.2.4:
- version "1.2.4"
- resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.4.tgz#cc7ccad61629f4efff8e2f78adb8c522c9976ec7"
+istanbul-lib-source-maps@^1.2.6:
+ version "1.2.6"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.6.tgz#37b9ff661580f8fca11232752ee42e08c6675d8f"
dependencies:
debug "^3.1.0"
- istanbul-lib-coverage "^1.2.0"
+ istanbul-lib-coverage "^1.2.1"
mkdirp "^0.5.1"
rimraf "^2.6.1"
source-map "^0.5.3"
-istanbul-reports@^1.3.0:
- version "1.3.0"
- resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-1.3.0.tgz#2f322e81e1d9520767597dca3c20a0cce89a3554"
+istanbul-reports@^1.5.1:
+ version "1.5.1"
+ resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-1.5.1.tgz#97e4dbf3b515e8c484caea15d6524eebd3ff4e1a"
dependencies:
handlebars "^4.0.3"
@@ -7241,15 +7187,15 @@ jclass@^1.0.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/jclass/-/jclass-1.2.1.tgz#eaafeec0dd6a5bf8b3ea43c04e010c637638768b"
-jest-changed-files@^22.4.3:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-22.4.3.tgz#8882181e022c38bd46a2e4d18d44d19d90a90fb2"
+jest-changed-files@^23.4.2:
+ version "23.4.2"
+ resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-23.4.2.tgz#1eed688370cd5eebafe4ae93d34bb3b64968fe83"
dependencies:
throat "^4.0.0"
-jest-cli@^22.4.3:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-22.4.3.tgz#bf16c4a5fb7edc3fa5b9bb7819e34139e88a72c7"
+jest-cli@^23.5.0:
+ version "23.5.0"
+ resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-23.5.0.tgz#d316b8e34a38a610a1efc4f0403d8ef8a55e4492"
dependencies:
ansi-escapes "^3.0.0"
chalk "^2.0.1"
@@ -7258,133 +7204,147 @@ jest-cli@^22.4.3:
graceful-fs "^4.1.11"
import-local "^1.0.0"
is-ci "^1.0.10"
- istanbul-api "^1.1.14"
- istanbul-lib-coverage "^1.1.1"
- istanbul-lib-instrument "^1.8.0"
- istanbul-lib-source-maps "^1.2.1"
- jest-changed-files "^22.4.3"
- jest-config "^22.4.3"
- jest-environment-jsdom "^22.4.3"
- jest-get-type "^22.4.3"
- jest-haste-map "^22.4.3"
- jest-message-util "^22.4.3"
- jest-regex-util "^22.4.3"
- jest-resolve-dependencies "^22.4.3"
- jest-runner "^22.4.3"
- jest-runtime "^22.4.3"
- jest-snapshot "^22.4.3"
- jest-util "^22.4.3"
- jest-validate "^22.4.3"
- jest-worker "^22.4.3"
+ istanbul-api "^1.3.1"
+ istanbul-lib-coverage "^1.2.0"
+ istanbul-lib-instrument "^1.10.1"
+ istanbul-lib-source-maps "^1.2.4"
+ jest-changed-files "^23.4.2"
+ jest-config "^23.5.0"
+ jest-environment-jsdom "^23.4.0"
+ jest-get-type "^22.1.0"
+ jest-haste-map "^23.5.0"
+ jest-message-util "^23.4.0"
+ jest-regex-util "^23.3.0"
+ jest-resolve-dependencies "^23.5.0"
+ jest-runner "^23.5.0"
+ jest-runtime "^23.5.0"
+ jest-snapshot "^23.5.0"
+ jest-util "^23.4.0"
+ jest-validate "^23.5.0"
+ jest-watcher "^23.4.0"
+ jest-worker "^23.2.0"
micromatch "^2.3.11"
node-notifier "^5.2.1"
+ prompts "^0.1.9"
realpath-native "^1.0.0"
rimraf "^2.5.4"
slash "^1.0.0"
string-length "^2.0.0"
strip-ansi "^4.0.0"
which "^1.2.12"
- yargs "^10.0.3"
+ yargs "^11.0.0"
-jest-config@^22.4.3:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-22.4.3.tgz#0e9d57db267839ea31309119b41dc2fa31b76403"
+jest-config@^23.5.0:
+ version "23.5.0"
+ resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-23.5.0.tgz#3770fba03f7507ee15f3b8867c742e48f31a9773"
dependencies:
+ babel-core "^6.0.0"
+ babel-jest "^23.4.2"
chalk "^2.0.1"
glob "^7.1.1"
- jest-environment-jsdom "^22.4.3"
- jest-environment-node "^22.4.3"
- jest-get-type "^22.4.3"
- jest-jasmine2 "^22.4.3"
- jest-regex-util "^22.4.3"
- jest-resolve "^22.4.3"
- jest-util "^22.4.3"
- jest-validate "^22.4.3"
- pretty-format "^22.4.3"
-
-jest-diff@^22.4.3:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-22.4.3.tgz#e18cc3feff0aeef159d02310f2686d4065378030"
+ jest-environment-jsdom "^23.4.0"
+ jest-environment-node "^23.4.0"
+ jest-get-type "^22.1.0"
+ jest-jasmine2 "^23.5.0"
+ jest-regex-util "^23.3.0"
+ jest-resolve "^23.5.0"
+ jest-util "^23.4.0"
+ jest-validate "^23.5.0"
+ micromatch "^2.3.11"
+ pretty-format "^23.5.0"
+
+jest-diff@^23.5.0:
+ version "23.5.0"
+ resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-23.5.0.tgz#250651a433dd0050290a07642946cc9baaf06fba"
dependencies:
chalk "^2.0.1"
diff "^3.2.0"
- jest-get-type "^22.4.3"
- pretty-format "^22.4.3"
+ jest-get-type "^22.1.0"
+ pretty-format "^23.5.0"
jest-docblock@^21.0.0:
version "21.2.0"
resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-21.2.0.tgz#51529c3b30d5fd159da60c27ceedc195faf8d414"
-jest-docblock@^22.4.3:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-22.4.3.tgz#50886f132b42b280c903c592373bb6e93bb68b19"
+jest-docblock@^23.2.0:
+ version "23.2.0"
+ resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-23.2.0.tgz#f085e1f18548d99fdd69b20207e6fd55d91383a7"
dependencies:
detect-newline "^2.1.0"
-jest-environment-jsdom@^22.4.3:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-22.4.3.tgz#d67daa4155e33516aecdd35afd82d4abf0fa8a1e"
+jest-each@^23.5.0:
+ version "23.5.0"
+ resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-23.5.0.tgz#77f7e2afe6132a80954b920006e78239862b10ba"
+ dependencies:
+ chalk "^2.0.1"
+ pretty-format "^23.5.0"
+
+jest-environment-jsdom@^23.4.0:
+ version "23.4.0"
+ resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-23.4.0.tgz#056a7952b3fea513ac62a140a2c368c79d9e6023"
dependencies:
- jest-mock "^22.4.3"
- jest-util "^22.4.3"
+ jest-mock "^23.2.0"
+ jest-util "^23.4.0"
jsdom "^11.5.1"
-jest-environment-node@^22.4.3:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-22.4.3.tgz#54c4eaa374c83dd52a9da8759be14ebe1d0b9129"
+jest-environment-node@^23.4.0:
+ version "23.4.0"
+ resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-23.4.0.tgz#57e80ed0841dea303167cce8cd79521debafde10"
dependencies:
- jest-mock "^22.4.3"
- jest-util "^22.4.3"
+ jest-mock "^23.2.0"
+ jest-util "^23.4.0"
-jest-get-type@^22.4.3:
+jest-get-type@^22.1.0:
version "22.4.3"
resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-22.4.3.tgz#e3a8504d8479342dd4420236b322869f18900ce4"
-jest-haste-map@^22.4.3:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-22.4.3.tgz#25842fa2ba350200767ac27f658d58b9d5c2e20b"
+jest-haste-map@^23.5.0:
+ version "23.5.0"
+ resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-23.5.0.tgz#d4ca618188bd38caa6cb20349ce6610e194a8065"
dependencies:
fb-watchman "^2.0.0"
graceful-fs "^4.1.11"
- jest-docblock "^22.4.3"
- jest-serializer "^22.4.3"
- jest-worker "^22.4.3"
+ invariant "^2.2.4"
+ jest-docblock "^23.2.0"
+ jest-serializer "^23.0.1"
+ jest-worker "^23.2.0"
micromatch "^2.3.11"
sane "^2.0.0"
-jest-jasmine2@^22.4.3:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-22.4.3.tgz#4daf64cd14c793da9db34a7c7b8dcfe52a745965"
+jest-jasmine2@^23.5.0:
+ version "23.5.0"
+ resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-23.5.0.tgz#05fe7f1788e650eeb5a03929e6461ea2e9f3db53"
dependencies:
+ babel-traverse "^6.0.0"
chalk "^2.0.1"
co "^4.6.0"
- expect "^22.4.3"
- graceful-fs "^4.1.11"
+ expect "^23.5.0"
is-generator-fn "^1.0.0"
- jest-diff "^22.4.3"
- jest-matcher-utils "^22.4.3"
- jest-message-util "^22.4.3"
- jest-snapshot "^22.4.3"
- jest-util "^22.4.3"
- source-map-support "^0.5.0"
-
-jest-leak-detector@^22.4.3:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-22.4.3.tgz#2b7b263103afae8c52b6b91241a2de40117e5b35"
+ jest-diff "^23.5.0"
+ jest-each "^23.5.0"
+ jest-matcher-utils "^23.5.0"
+ jest-message-util "^23.4.0"
+ jest-snapshot "^23.5.0"
+ jest-util "^23.4.0"
+ pretty-format "^23.5.0"
+
+jest-leak-detector@^23.5.0:
+ version "23.5.0"
+ resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-23.5.0.tgz#14ac2a785bd625160a2ea968fd5d98b7dcea3e64"
dependencies:
- pretty-format "^22.4.3"
+ pretty-format "^23.5.0"
-jest-matcher-utils@^22.4.3:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-22.4.3.tgz#4632fe428ebc73ebc194d3c7b65d37b161f710ff"
+jest-matcher-utils@^23.5.0:
+ version "23.5.0"
+ resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-23.5.0.tgz#0e2ea67744cab78c9ab15011c4d888bdd3e49e2a"
dependencies:
chalk "^2.0.1"
- jest-get-type "^22.4.3"
- pretty-format "^22.4.3"
+ jest-get-type "^22.1.0"
+ pretty-format "^23.5.0"
-jest-message-util@^22.4.3:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-22.4.3.tgz#cf3d38aafe4befddbfc455e57d65d5239e399eb7"
+jest-message-util@^23.4.0:
+ version "23.4.0"
+ resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-23.4.0.tgz#17610c50942349508d01a3d1e0bda2c079086a9f"
dependencies:
"@babel/code-frame" "^7.0.0-beta.35"
chalk "^2.0.1"
@@ -7392,121 +7352,138 @@ jest-message-util@^22.4.3:
slash "^1.0.0"
stack-utils "^1.0.1"
-jest-mock@^22.4.3:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-22.4.3.tgz#f63ba2f07a1511772cdc7979733397df770aabc7"
+jest-mock@^23.2.0:
+ version "23.2.0"
+ resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-23.2.0.tgz#ad1c60f29e8719d47c26e1138098b6d18b261134"
jest-raw-loader@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/jest-raw-loader/-/jest-raw-loader-1.0.1.tgz#ce9f56d54650f157c4a7d16d224ba5d613bcd626"
-jest-regex-util@^22.4.3:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-22.4.3.tgz#a826eb191cdf22502198c5401a1fc04de9cef5af"
+jest-regex-util@^23.3.0:
+ version "23.3.0"
+ resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-23.3.0.tgz#5f86729547c2785c4002ceaa8f849fe8ca471bc5"
-jest-resolve-dependencies@^22.4.3:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-22.4.3.tgz#e2256a5a846732dc3969cb72f3c9ad7725a8195e"
+jest-resolve-dependencies@^23.5.0:
+ version "23.5.0"
+ resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-23.5.0.tgz#10c4d135beb9d2256de1fedc7094916c3ad74af7"
dependencies:
- jest-regex-util "^22.4.3"
+ jest-regex-util "^23.3.0"
+ jest-snapshot "^23.5.0"
-jest-resolve@^22.4.3:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-22.4.3.tgz#0ce9d438c8438229aa9b916968ec6b05c1abb4ea"
+jest-resolve@^23.5.0:
+ version "23.5.0"
+ resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-23.5.0.tgz#3b8e7f67e84598f0caf63d1530bd8534a189d0e6"
dependencies:
- browser-resolve "^1.11.2"
+ browser-resolve "^1.11.3"
chalk "^2.0.1"
+ realpath-native "^1.0.0"
-jest-runner@^22.4.3:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-22.4.3.tgz#298ddd6a22b992c64401b4667702b325e50610c3"
+jest-runner@^23.5.0:
+ version "23.5.0"
+ resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-23.5.0.tgz#570f7a044da91648b5bb9b6baacdd511076c71d7"
dependencies:
exit "^0.1.2"
- jest-config "^22.4.3"
- jest-docblock "^22.4.3"
- jest-haste-map "^22.4.3"
- jest-jasmine2 "^22.4.3"
- jest-leak-detector "^22.4.3"
- jest-message-util "^22.4.3"
- jest-runtime "^22.4.3"
- jest-util "^22.4.3"
- jest-worker "^22.4.3"
+ graceful-fs "^4.1.11"
+ jest-config "^23.5.0"
+ jest-docblock "^23.2.0"
+ jest-haste-map "^23.5.0"
+ jest-jasmine2 "^23.5.0"
+ jest-leak-detector "^23.5.0"
+ jest-message-util "^23.4.0"
+ jest-runtime "^23.5.0"
+ jest-util "^23.4.0"
+ jest-worker "^23.2.0"
+ source-map-support "^0.5.6"
throat "^4.0.0"
-jest-runtime@^22.4.3:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-22.4.3.tgz#b69926c34b851b920f666c93e86ba2912087e3d0"
+jest-runtime@^23.5.0:
+ version "23.5.0"
+ resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-23.5.0.tgz#eb503525a196dc32f2f9974e3482d26bdf7b63ce"
dependencies:
babel-core "^6.0.0"
- babel-jest "^22.4.3"
- babel-plugin-istanbul "^4.1.5"
+ babel-plugin-istanbul "^4.1.6"
chalk "^2.0.1"
convert-source-map "^1.4.0"
exit "^0.1.2"
+ fast-json-stable-stringify "^2.0.0"
graceful-fs "^4.1.11"
- jest-config "^22.4.3"
- jest-haste-map "^22.4.3"
- jest-regex-util "^22.4.3"
- jest-resolve "^22.4.3"
- jest-util "^22.4.3"
- jest-validate "^22.4.3"
- json-stable-stringify "^1.0.1"
+ jest-config "^23.5.0"
+ jest-haste-map "^23.5.0"
+ jest-message-util "^23.4.0"
+ jest-regex-util "^23.3.0"
+ jest-resolve "^23.5.0"
+ jest-snapshot "^23.5.0"
+ jest-util "^23.4.0"
+ jest-validate "^23.5.0"
micromatch "^2.3.11"
realpath-native "^1.0.0"
slash "^1.0.0"
strip-bom "3.0.0"
write-file-atomic "^2.1.0"
- yargs "^10.0.3"
+ yargs "^11.0.0"
-jest-serializer@^22.4.3:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-22.4.3.tgz#a679b81a7f111e4766235f4f0c46d230ee0f7436"
+jest-serializer@^23.0.1:
+ version "23.0.1"
+ resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-23.0.1.tgz#a3776aeb311e90fe83fab9e533e85102bd164165"
-jest-snapshot@^22.4.3:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-22.4.3.tgz#b5c9b42846ffb9faccb76b841315ba67887362d2"
+jest-snapshot@^23.5.0:
+ version "23.5.0"
+ resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-23.5.0.tgz#cc368ebd8513e1175e2a7277f37a801b7358ae79"
dependencies:
+ babel-types "^6.0.0"
chalk "^2.0.1"
- jest-diff "^22.4.3"
- jest-matcher-utils "^22.4.3"
+ jest-diff "^23.5.0"
+ jest-matcher-utils "^23.5.0"
+ jest-message-util "^23.4.0"
+ jest-resolve "^23.5.0"
mkdirp "^0.5.1"
natural-compare "^1.4.0"
- pretty-format "^22.4.3"
+ pretty-format "^23.5.0"
+ semver "^5.5.0"
-jest-util@^22.4.3:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-22.4.3.tgz#c70fec8eec487c37b10b0809dc064a7ecf6aafac"
+jest-util@^23.4.0:
+ version "23.4.0"
+ resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-23.4.0.tgz#4d063cb927baf0a23831ff61bec2cbbf49793561"
dependencies:
callsites "^2.0.0"
chalk "^2.0.1"
graceful-fs "^4.1.11"
is-ci "^1.0.10"
- jest-message-util "^22.4.3"
+ jest-message-util "^23.4.0"
mkdirp "^0.5.1"
+ slash "^1.0.0"
source-map "^0.6.0"
-jest-validate@^22.4.3:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-22.4.3.tgz#0780954a5a7daaeec8d3c10834b9280865976b30"
+jest-validate@^23.5.0:
+ version "23.5.0"
+ resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-23.5.0.tgz#f5df8f761cf43155e1b2e21d6e9de8a2852d0231"
dependencies:
chalk "^2.0.1"
- jest-config "^22.4.3"
- jest-get-type "^22.4.3"
+ jest-get-type "^22.1.0"
leven "^2.1.0"
- pretty-format "^22.4.3"
+ pretty-format "^23.5.0"
-jest-worker@^22.4.3:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-22.4.3.tgz#5c421417cba1c0abf64bf56bd5fb7968d79dd40b"
+jest-watcher@^23.4.0:
+ version "23.4.0"
+ resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-23.4.0.tgz#d2e28ce74f8dad6c6afc922b92cabef6ed05c91c"
+ dependencies:
+ ansi-escapes "^3.0.0"
+ chalk "^2.0.1"
+ string-length "^2.0.0"
+
+jest-worker@^23.2.0:
+ version "23.2.0"
+ resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-23.2.0.tgz#faf706a8da36fae60eb26957257fa7b5d8ea02b9"
dependencies:
merge-stream "^1.0.1"
-jest@^22.4.3:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/jest/-/jest-22.4.3.tgz#2261f4b117dc46d9a4a1a673d2150958dee92f16"
+jest@^23.5.0:
+ version "23.5.0"
+ resolved "https://registry.yarnpkg.com/jest/-/jest-23.5.0.tgz#80de353d156ea5ea4a7332f7962ac79135fbc62e"
dependencies:
import-local "^1.0.0"
- jest-cli "^22.4.3"
+ jest-cli "^23.5.0"
jimp@0.2.28:
version "0.2.28"
@@ -7964,6 +7941,10 @@ kind-of@^6.0.0, kind-of@^6.0.2:
version "6.0.2"
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051"
+kleur@^2.0.1:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/kleur/-/kleur-2.0.2.tgz#b704f4944d95e255d038f0cb05fb8a602c55a300"
+
kopy@^8.2.0:
version "8.3.0"
resolved "https://registry.yarnpkg.com/kopy/-/kopy-8.3.0.tgz#7a476efeeed90a0d49ca57464ba2ffdbf41244ee"
@@ -8906,7 +8887,7 @@ minimist@0.0.8:
version "0.0.8"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
-minimist@1.2.0, minimist@^1.1.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0:
+minimist@1.2.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
@@ -10404,9 +10385,9 @@ prettier@^1.14.0:
version "1.14.0"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.14.0.tgz#847c235522035fd988100f1f43cf20a7d24f9372"
-pretty-format@^22.4.3:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-22.4.3.tgz#f873d780839a9c02e9664c8a082e9ee79eaac16f"
+pretty-format@^23.5.0:
+ version "23.5.0"
+ resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-23.5.0.tgz#0f9601ad9da70fe690a269cd3efca732c210687c"
dependencies:
ansi-regex "^3.0.0"
ansi-styles "^3.2.0"
@@ -10415,7 +10396,7 @@ prismjs@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-0.0.1.tgz#0fd50f4baf26e5cd33523b65bac2f0bc90f5503f"
-private@^0.1.6, private@^0.1.7, private@^0.1.8:
+private@^0.1.6, private@^0.1.7:
version "0.1.8"
resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff"
@@ -10453,6 +10434,13 @@ promise@^7.0.1, promise@^7.1.1:
dependencies:
asap "~2.0.3"
+prompts@^0.1.9:
+ version "0.1.14"
+ resolved "https://registry.yarnpkg.com/prompts/-/prompts-0.1.14.tgz#a8e15c612c5c9ec8f8111847df3337c9cbd443b2"
+ dependencies:
+ kleur "^2.0.1"
+ sisteransi "^0.1.1"
+
prop-types@15.5.8:
version "15.5.8"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.5.8.tgz#6b7b2e141083be38c8595aa51fc55775c7199394"
@@ -11698,7 +11686,7 @@ resolve@1.1.7, resolve@1.1.x, resolve@~1.1.0, resolve@~1.1.7:
version "1.1.7"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
-resolve@^1.1.5, resolve@^1.1.7, resolve@^1.2.0, resolve@^1.5.0:
+resolve@^1.1.5, resolve@^1.2.0, resolve@^1.5.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.6.0.tgz#0fbd21278b27b4004481c395349e7aba60a9ff5c"
dependencies:
@@ -12041,15 +12029,6 @@ shebang-regex@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"
-shell-quote@^1.6.1:
- version "1.6.1"
- resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.6.1.tgz#f4781949cce402697127430ea3b3c5476f481767"
- dependencies:
- array-filter "~0.0.0"
- array-map "~0.0.0"
- array-reduce "~0.0.0"
- jsonify "~0.0.0"
-
shellwords@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b"
@@ -12100,6 +12079,10 @@ sinon@^5.0.7:
supports-color "^5.1.0"
type-detect "^4.0.5"
+sisteransi@^0.1.1:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-0.1.1.tgz#5431447d5f7d1675aac667ccd0b865a4994cb3ce"
+
slash@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55"
@@ -12242,15 +12225,16 @@ source-map-support@^0.4.15, source-map-support@^0.4.2:
dependencies:
source-map "^0.5.6"
-source-map-support@^0.5.0:
- version "0.5.4"
- resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.4.tgz#54456efa89caa9270af7cd624cc2f123e51fbae8"
+source-map-support@^0.5.3:
+ version "0.5.6"
+ resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.6.tgz#4435cee46b1aab62b8e8610ce60f788091c51c13"
dependencies:
+ buffer-from "^1.0.0"
source-map "^0.6.0"
-source-map-support@^0.5.3, source-map-support@^0.5.5:
- version "0.5.6"
- resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.6.tgz#4435cee46b1aab62b8e8610ce60f788091c51c13"
+source-map-support@^0.5.6:
+ version "0.5.9"
+ resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.9.tgz#41bc953b2534267ea2d605bccfa7bfa3111ced5f"
dependencies:
buffer-from "^1.0.0"
source-map "^0.6.0"
@@ -12680,12 +12664,6 @@ stylis@^3.5.0:
version "3.5.1"
resolved "https://registry.yarnpkg.com/stylis/-/stylis-3.5.1.tgz#fd341d59f57f9aeb412bc14c9d8a8670b438e03b"
-subarg@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/subarg/-/subarg-1.0.0.tgz#f62cf17581e996b48fc965699f54c06ae268b8d2"
- dependencies:
- minimist "^1.1.0"
-
subtext@4.x.x:
version "4.4.1"
resolved "https://registry.yarnpkg.com/subtext/-/subtext-4.4.1.tgz#2fcec945de429283c3d18b151ff0fa1f1b87aec9"
@@ -12908,7 +12886,7 @@ term-size@^1.2.0:
dependencies:
execa "^0.7.0"
-test-exclude@^4.1.1, test-exclude@^4.2.1:
+test-exclude@^4.2.1:
version "4.2.1"
resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-4.2.1.tgz#dfa222f03480bca69207ca728b37d74b45f724fa"
dependencies:
@@ -13200,21 +13178,14 @@ trunc-text@1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/trunc-text/-/trunc-text-1.0.2.tgz#b582bb3ddea9c9adc25017d737c48ebdd2157406"
-ts-jest@^22.4.6:
- version "22.4.6"
- resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-22.4.6.tgz#a5d7f5e8b809626d1f4143209d301287472ec344"
+ts-jest@^23.1.4:
+ version "23.1.4"
+ resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-23.1.4.tgz#66ac1d8d3fbf8f9a98432b11aa377aa850664b2b"
dependencies:
- babel-core "^6.26.3"
- babel-plugin-istanbul "^4.1.6"
- babel-plugin-transform-es2015-modules-commonjs "^6.26.2"
- babel-preset-jest "^22.4.3"
- cpx "^1.5.0"
- fs-extra "6.0.0"
- jest-config "^22.4.3"
+ closest-file-data "^0.1.4"
+ fs-extra "6.0.1"
+ json5 "^0.5.0"
lodash "^4.17.10"
- pkg-dir "^2.0.0"
- source-map-support "^0.5.5"
- yargs "^11.0.0"
ts-loader@^3.5.0:
version "3.5.0"
@@ -14522,35 +14493,12 @@ yargs-parser@^7.0.0:
dependencies:
camelcase "^4.1.0"
-yargs-parser@^8.1.0:
- version "8.1.0"
- resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-8.1.0.tgz#f1376a33b6629a5d063782944da732631e966950"
- dependencies:
- camelcase "^4.1.0"
-
yargs-parser@^9.0.2:
version "9.0.2"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-9.0.2.tgz#9ccf6a43460fe4ed40a9bb68f48d43b8a68cc077"
dependencies:
camelcase "^4.1.0"
-yargs@^10.0.3:
- version "10.1.2"
- resolved "https://registry.yarnpkg.com/yargs/-/yargs-10.1.2.tgz#454d074c2b16a51a43e2fb7807e4f9de69ccb5c5"
- dependencies:
- cliui "^4.0.0"
- decamelize "^1.1.1"
- find-up "^2.1.0"
- get-caller-file "^1.0.1"
- os-locale "^2.0.0"
- require-directory "^2.1.1"
- require-main-filename "^1.0.1"
- set-blocking "^2.0.0"
- string-width "^2.0.0"
- which-module "^2.0.0"
- y18n "^3.2.1"
- yargs-parser "^8.1.0"
-
yargs@^11.0.0:
version "11.0.0"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-11.0.0.tgz#c052931006c5eee74610e5fc0354bedfd08a201b"
From 52060b8f16401cade358b3f1f747cf3ac87a6636 Mon Sep 17 00:00:00 2001
From: Spencer
Date: Fri, 7 Sep 2018 21:10:05 -0700
Subject: [PATCH 55/68] Migrate base path APIs and UiSettings client to new
platform (#22694)
Fixes #20697
This PR migrates the base path related methods from `ui/chrome` to `core.basePath` and the uiSettings to `core.uiSettings`. The two are not split into separate PRs because I'm kinda cramped for time right now so rather I split the changes up into two commits so that you can review them separately if you like. If you'd like I can submit them as separate PRs but the basePath PR will block the uiSettings PR either way.
There shouldn't be any API changes except one thing, since the existing implementation is so close to what we'd want from the new platform API I made the one change that is inconsistent with what we've been doing so far and moved `uiSettings.subscribe()` to `uiSettings.getUpdate$().subscribe()`. This method isn't super commonly used, but it is a breaking change that will likely impact plugins so I'll notify some folks if we decide to move forward this way. I can also make a super-light wrapper for angular that just updates this method if you prefer.
---
.../base_path/base_path_service.test.ts | 104 ++++++++
.../public/base_path/base_path_service.ts | 74 ++++++
.../public/base_path/index.ts} | 3 +-
src/core/public/core_system.test.ts | 43 +++
src/core/public/core_system.ts | 23 +-
.../injected_metadata_service.ts | 9 +
.../legacy_platform_service.test.ts.snap | 26 ++
.../legacy_platform_service.test.ts | 70 +++--
.../legacy_platform_service.ts | 15 +-
.../ui_settings_api.test.ts.snap | 168 ++++++++++++
.../ui_settings_client.test.ts.snap} | 60 ++---
.../ui_settings_service.test.ts.snap | 78 ++++++
src/core/public/ui_settings/index.ts | 21 ++
src/core/public/ui_settings/types.ts | 39 +++
.../ui_settings/ui_settings_api.test.ts | 242 +++++++++++++++++
.../public/ui_settings/ui_settings_api.ts | 163 ++++++++++++
.../ui_settings/ui_settings_client.test.ts} | 112 +++-----
.../public/ui_settings/ui_settings_client.ts | 251 ++++++++++++++++++
.../ui_settings/ui_settings_service.test.ts | 127 +++++++++
.../public/ui_settings/ui_settings_service.ts | 72 +++++
src/core/public/utils/index.ts | 20 ++
src/core/public/utils/modify_url.test.ts | 59 ++++
.../public/utils/modify_url.ts} | 39 ++-
src/ui/public/autoload/settings.js | 2 +-
src/ui/public/chrome/api/__tests__/nav.js | 46 +---
src/ui/public/chrome/api/base_path.test.ts | 67 +++++
.../chrome/api/base_path.ts} | 34 +--
src/ui/public/chrome/api/nav.js | 23 --
src/ui/public/chrome/api/ui_settings.js | 20 +-
src/ui/public/chrome/chrome.js | 2 +
src/ui/public/config/config.js | 2 +-
.../notify/app_redirect/app_redirect.js | 4 +-
src/ui/public/registry/field_formats.js | 2 +-
.../disable_animations/disable_animations.js | 2 +-
src/ui/public/test_harness/test_harness.js | 40 +--
src/ui/public/url/index.js | 2 +-
src/ui/public/url/kibana_parsed_url.js | 2 +-
src/ui/ui_render/ui_render_mixin.js | 1 +
src/ui/ui_settings/public/ui_settings_api.js | 86 ------
.../ui_settings/public/ui_settings_client.js | 207 ---------------
src/utils/__tests__/modify_url.js | 61 -----
src/utils/index.js | 1 -
.../services/remote/interceptors.js | 2 +-
43 files changed, 1799 insertions(+), 625 deletions(-)
create mode 100644 src/core/public/base_path/base_path_service.test.ts
create mode 100644 src/core/public/base_path/base_path_service.ts
rename src/{ui/public/url/modify_url.js => core/public/base_path/index.ts} (83%)
create mode 100644 src/core/public/ui_settings/__snapshots__/ui_settings_api.test.ts.snap
rename src/{ui/ui_settings/public/__snapshots__/ui_settings_client.test.js.snap => core/public/ui_settings/__snapshots__/ui_settings_client.test.ts.snap} (87%)
create mode 100644 src/core/public/ui_settings/__snapshots__/ui_settings_service.test.ts.snap
create mode 100644 src/core/public/ui_settings/index.ts
create mode 100644 src/core/public/ui_settings/types.ts
create mode 100644 src/core/public/ui_settings/ui_settings_api.test.ts
create mode 100644 src/core/public/ui_settings/ui_settings_api.ts
rename src/{ui/ui_settings/public/ui_settings_client.test.js => core/public/ui_settings/ui_settings_client.test.ts} (82%)
create mode 100644 src/core/public/ui_settings/ui_settings_client.ts
create mode 100644 src/core/public/ui_settings/ui_settings_service.test.ts
create mode 100644 src/core/public/ui_settings/ui_settings_service.ts
create mode 100644 src/core/public/utils/index.ts
create mode 100644 src/core/public/utils/modify_url.test.ts
rename src/{utils/modify_url.js => core/public/utils/modify_url.ts} (77%)
create mode 100644 src/ui/public/chrome/api/base_path.test.ts
rename src/ui/{ui_settings/public/send_request.js => public/chrome/api/base_path.ts} (54%)
delete mode 100644 src/ui/ui_settings/public/ui_settings_api.js
delete mode 100644 src/ui/ui_settings/public/ui_settings_client.js
delete mode 100644 src/utils/__tests__/modify_url.js
diff --git a/src/core/public/base_path/base_path_service.test.ts b/src/core/public/base_path/base_path_service.test.ts
new file mode 100644
index 0000000000000..ed44c322f158c
--- /dev/null
+++ b/src/core/public/base_path/base_path_service.test.ts
@@ -0,0 +1,104 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { BasePathService } from './base_path_service';
+
+function setup(options: any = {}) {
+ const injectedBasePath: string =
+ options.injectedBasePath === undefined ? '/foo/bar' : options.injectedBasePath;
+
+ const service = new BasePathService();
+
+ const injectedMetadata = {
+ getBasePath: jest.fn().mockReturnValue(injectedBasePath),
+ } as any;
+
+ const startContract = service.start({
+ injectedMetadata,
+ });
+
+ return {
+ service,
+ startContract,
+ injectedBasePath,
+ };
+}
+
+describe('startContract.get()', () => {
+ it('returns an empty string if no basePath is injected', () => {
+ const { startContract } = setup({ injectedBasePath: null });
+ expect(startContract.get()).toBe('');
+ });
+
+ it('returns the injected basePath', () => {
+ const { startContract } = setup();
+ expect(startContract.get()).toBe('/foo/bar');
+ });
+});
+
+describe('startContract.addToPath()', () => {
+ it('adds the base path to the path if it is relative and starts with a slash', () => {
+ const { startContract } = setup();
+ expect(startContract.addToPath('/a/b')).toBe('/foo/bar/a/b');
+ });
+
+ it('leaves the query string and hash of path unchanged', () => {
+ const { startContract } = setup();
+ expect(startContract.addToPath('/a/b?x=y#c/d/e')).toBe('/foo/bar/a/b?x=y#c/d/e');
+ });
+
+ it('returns the path unchanged if it does not start with a slash', () => {
+ const { startContract } = setup();
+ expect(startContract.addToPath('a/b')).toBe('a/b');
+ });
+
+ it('returns the path unchanged it it has a hostname', () => {
+ const { startContract } = setup();
+ expect(startContract.addToPath('http://localhost:5601/a/b')).toBe('http://localhost:5601/a/b');
+ });
+});
+
+describe('startContract.removeFromPath()', () => {
+ it('removes the basePath if relative path starts with it', () => {
+ const { startContract } = setup();
+ expect(startContract.removeFromPath('/foo/bar/a/b')).toBe('/a/b');
+ });
+
+ it('leaves query string and hash intact', () => {
+ const { startContract } = setup();
+ expect(startContract.removeFromPath('/foo/bar/a/b?c=y#1234')).toBe('/a/b?c=y#1234');
+ });
+
+ it('ignores urls with hostnames', () => {
+ const { startContract } = setup();
+ expect(startContract.removeFromPath('http://localhost:5601/foo/bar/a/b')).toBe(
+ 'http://localhost:5601/foo/bar/a/b'
+ );
+ });
+
+ it('returns slash if path is just basePath', () => {
+ const { startContract } = setup();
+ expect(startContract.removeFromPath('/foo/bar')).toBe('/');
+ });
+
+ it('returns full path if basePath is not its own segment', () => {
+ const { startContract } = setup();
+ expect(startContract.removeFromPath('/foo/barhop')).toBe('/foo/barhop');
+ });
+});
diff --git a/src/core/public/base_path/base_path_service.ts b/src/core/public/base_path/base_path_service.ts
new file mode 100644
index 0000000000000..bd6f665abdf9e
--- /dev/null
+++ b/src/core/public/base_path/base_path_service.ts
@@ -0,0 +1,74 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { InjectedMetadataStartContract } from '../injected_metadata';
+import { modifyUrl } from '../utils';
+
+interface Deps {
+ injectedMetadata: InjectedMetadataStartContract;
+}
+
+export class BasePathService {
+ public start({ injectedMetadata }: Deps) {
+ const basePath = injectedMetadata.getBasePath() || '';
+
+ return {
+ /**
+ * Get the current basePath as defined by the server
+ */
+ get() {
+ return basePath;
+ },
+
+ /**
+ * Add the current basePath to a path string.
+ * @param path A relative url including the leading `/`, otherwise it will be returned without modification
+ */
+ addToPath(path: string) {
+ return modifyUrl(path, parts => {
+ if (!parts.hostname && parts.pathname && parts.pathname.startsWith('/')) {
+ parts.pathname = `${basePath}${parts.pathname}`;
+ }
+ });
+ },
+
+ /**
+ * Remove the basePath from a path that starts with it
+ * @param path A relative url that starts with the basePath, which will be stripped
+ */
+ removeFromPath(path: string) {
+ if (!basePath) {
+ return path;
+ }
+
+ if (path === basePath) {
+ return '/';
+ }
+
+ if (path.startsWith(basePath + '/')) {
+ return path.slice(basePath.length);
+ }
+
+ return path;
+ },
+ };
+ }
+}
+
+export type BasePathStartContract = ReturnType;
diff --git a/src/ui/public/url/modify_url.js b/src/core/public/base_path/index.ts
similarity index 83%
rename from src/ui/public/url/modify_url.js
rename to src/core/public/base_path/index.ts
index 33a17f7ac531c..13ff2350cab84 100644
--- a/src/ui/public/url/modify_url.js
+++ b/src/core/public/base_path/index.ts
@@ -17,5 +17,4 @@
* under the License.
*/
-// we select the modify_url directly so the other utils, which are not browser compatible, are not included
-export { modifyUrl } from '../../../utils/modify_url';
+export { BasePathService, BasePathStartContract } from './base_path_service';
diff --git a/src/core/public/core_system.test.ts b/src/core/public/core_system.test.ts
index d867c8f49b6a0..64f71cd4fb00c 100644
--- a/src/core/public/core_system.test.ts
+++ b/src/core/public/core_system.test.ts
@@ -17,11 +17,13 @@
* under the License.
*/
+import { BasePathService } from './base_path';
import { FatalErrorsService } from './fatal_errors';
import { InjectedMetadataService } from './injected_metadata';
import { LegacyPlatformService } from './legacy_platform';
import { LoadingCountService } from './loading_count';
import { NotificationsService } from './notifications';
+import { UiSettingsService } from './ui_settings';
const MockLegacyPlatformService = jest.fn(
function _MockLegacyPlatformService(this: any) {
@@ -76,6 +78,24 @@ jest.mock('./loading_count', () => ({
LoadingCountService: MockLoadingCountService,
}));
+const mockBasePathStartContract = {};
+const MockBasePathService = jest.fn(function _MockNotificationsService(this: any) {
+ this.start = jest.fn().mockReturnValue(mockBasePathStartContract);
+});
+jest.mock('./base_path', () => ({
+ BasePathService: MockBasePathService,
+}));
+
+const mockUiSettingsContract = {};
+const MockUiSettingsService = jest.fn(function _MockNotificationsService(
+ this: any
+) {
+ this.start = jest.fn().mockReturnValue(mockUiSettingsContract);
+});
+jest.mock('./ui_settings', () => ({
+ UiSettingsService: MockUiSettingsService,
+}));
+
import { CoreSystem } from './core_system';
jest.spyOn(CoreSystem.prototype, 'stop');
@@ -101,6 +121,8 @@ describe('constructor', () => {
expect(MockFatalErrorsService).toHaveBeenCalledTimes(1);
expect(MockNotificationsService).toHaveBeenCalledTimes(1);
expect(MockLoadingCountService).toHaveBeenCalledTimes(1);
+ expect(MockBasePathService).toHaveBeenCalledTimes(1);
+ expect(MockUiSettingsService).toHaveBeenCalledTimes(1);
});
it('passes injectedMetadata param to InjectedMetadataService', () => {
@@ -221,6 +243,27 @@ describe('#start()', () => {
});
});
+ it('calls basePath#start()', () => {
+ startCore();
+ const [mockInstance] = MockBasePathService.mock.instances;
+ expect(mockInstance.start).toHaveBeenCalledTimes(1);
+ expect(mockInstance.start).toHaveBeenCalledWith({
+ injectedMetadata: mockInjectedMetadataStartContract,
+ });
+ });
+
+ it('calls uiSettings#start()', () => {
+ startCore();
+ const [mockInstance] = MockUiSettingsService.mock.instances;
+ expect(mockInstance.start).toHaveBeenCalledTimes(1);
+ expect(mockInstance.start).toHaveBeenCalledWith({
+ notifications: mockNotificationStartContract,
+ loadingCount: mockLoadingCountContract,
+ injectedMetadata: mockInjectedMetadataStartContract,
+ basePath: mockBasePathStartContract,
+ });
+ });
+
it('calls fatalErrors#start()', () => {
startCore();
const [mockInstance] = MockFatalErrorsService.mock.instances;
diff --git a/src/core/public/core_system.ts b/src/core/public/core_system.ts
index db5500cb2ffdb..05c00e5a633aa 100644
--- a/src/core/public/core_system.ts
+++ b/src/core/public/core_system.ts
@@ -18,11 +18,14 @@
*/
import './core.css';
+
+import { BasePathService } from './base_path';
import { FatalErrorsService } from './fatal_errors';
import { InjectedMetadataParams, InjectedMetadataService } from './injected_metadata';
import { LegacyPlatformParams, LegacyPlatformService } from './legacy_platform';
import { LoadingCountService } from './loading_count';
import { NotificationsService } from './notifications';
+import { UiSettingsService } from './ui_settings';
interface Params {
rootDomElement: HTMLElement;
@@ -43,6 +46,8 @@ export class CoreSystem {
private readonly legacyPlatform: LegacyPlatformService;
private readonly notifications: NotificationsService;
private readonly loadingCount: LoadingCountService;
+ private readonly uiSettings: UiSettingsService;
+ private readonly basePath: BasePathService;
private readonly rootDomElement: HTMLElement;
private readonly notificationsTargetDomElement: HTMLDivElement;
@@ -71,6 +76,8 @@ export class CoreSystem {
});
this.loadingCount = new LoadingCountService();
+ this.basePath = new BasePathService();
+ this.uiSettings = new UiSettingsService();
this.legacyPlatformTargetDomElement = document.createElement('div');
this.legacyPlatform = new LegacyPlatformService({
@@ -92,7 +99,21 @@ export class CoreSystem {
const injectedMetadata = this.injectedMetadata.start();
const fatalErrors = this.fatalErrors.start();
const loadingCount = this.loadingCount.start({ fatalErrors });
- this.legacyPlatform.start({ injectedMetadata, fatalErrors, notifications, loadingCount });
+ const basePath = this.basePath.start({ injectedMetadata });
+ const uiSettings = this.uiSettings.start({
+ notifications,
+ loadingCount,
+ injectedMetadata,
+ basePath,
+ });
+ this.legacyPlatform.start({
+ injectedMetadata,
+ fatalErrors,
+ notifications,
+ loadingCount,
+ basePath,
+ uiSettings,
+ });
} catch (error) {
this.fatalErrors.add(error);
}
diff --git a/src/core/public/injected_metadata/injected_metadata_service.ts b/src/core/public/injected_metadata/injected_metadata_service.ts
index e756d99b1f854..85c3fce0ba9de 100644
--- a/src/core/public/injected_metadata/injected_metadata_service.ts
+++ b/src/core/public/injected_metadata/injected_metadata_service.ts
@@ -23,6 +23,7 @@ export interface InjectedMetadataParams {
injectedMetadata: {
version: string;
buildNumber: number;
+ basePath: string;
legacyMetadata: {
[key: string]: any;
};
@@ -42,6 +43,14 @@ export class InjectedMetadataService {
public start() {
return {
+ getBasePath: () => {
+ return this.state.basePath;
+ },
+
+ getKibanaVersion: () => {
+ return this.getKibanaVersion();
+ },
+
getLegacyMetadata: () => {
return this.state.legacyMetadata;
},
diff --git a/src/core/public/legacy_platform/__snapshots__/legacy_platform_service.test.ts.snap b/src/core/public/legacy_platform/__snapshots__/legacy_platform_service.test.ts.snap
index e012b43d5977a..8d318e8e57673 100644
--- a/src/core/public/legacy_platform/__snapshots__/legacy_platform_service.test.ts.snap
+++ b/src/core/public/legacy_platform/__snapshots__/legacy_platform_service.test.ts.snap
@@ -1,5 +1,31 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
+exports[`#start() load order useLegacyTestHarness = false loads ui/modules before ui/chrome, and both before legacy files 1`] = `
+Array [
+ "ui/metadata",
+ "ui/notify/fatal_error",
+ "ui/notify/toasts",
+ "ui/chrome/api/loading_count",
+ "ui/chrome/api/base_path",
+ "ui/chrome/api/ui_settings",
+ "ui/chrome",
+ "legacy files",
+]
+`;
+
+exports[`#start() load order useLegacyTestHarness = true loads ui/modules before ui/test_harness, and both before legacy files 1`] = `
+Array [
+ "ui/metadata",
+ "ui/notify/fatal_error",
+ "ui/notify/toasts",
+ "ui/chrome/api/loading_count",
+ "ui/chrome/api/base_path",
+ "ui/chrome/api/ui_settings",
+ "ui/test_harness",
+ "legacy files",
+]
+`;
+
exports[`#stop() destroys the angular scope and empties the targetDomElement if angular is bootstraped to targetDomElement 1`] = `
{
};
});
+const mockBasePathInit = jest.fn();
+jest.mock('ui/chrome/api/base_path', () => {
+ mockLoadOrder.push('ui/chrome/api/base_path');
+ return {
+ __newPlatformInit__: mockBasePathInit,
+ };
+});
+
+const mockUiSettingsInit = jest.fn();
+jest.mock('ui/chrome/api/ui_settings', () => {
+ mockLoadOrder.push('ui/chrome/api/ui_settings');
+ return {
+ __newPlatformInit__: mockUiSettingsInit,
+ };
+});
+
import { LegacyPlatformService } from './legacy_platform_service';
const fatalErrorsStartContract = {} as any;
@@ -77,7 +93,8 @@ const notificationsStartContract = {
toasts: {},
} as any;
-const injectedMetadataStartContract = {
+const injectedMetadataStartContract: any = {
+ getBasePath: jest.fn(),
getLegacyMetadata: jest.fn(),
};
@@ -86,6 +103,14 @@ const loadingCountStartContract = {
getCount$: jest.fn().mockImplementation(() => new Rx.Observable(observer => observer.next(0))),
};
+const basePathStartContract = {
+ get: jest.fn(),
+ addToPath: jest.fn(),
+ removeFromPath: jest.fn(),
+};
+
+const uiSettingsStartContract: any = {};
+
const defaultParams = {
targetDomElement: document.createElement('div'),
requireLegacyFiles: jest.fn(() => {
@@ -98,6 +123,8 @@ const defaultStartDeps = {
injectedMetadata: injectedMetadataStartContract,
notifications: notificationsStartContract,
loadingCount: loadingCountStartContract,
+ basePath: basePathStartContract,
+ uiSettings: uiSettingsStartContract,
};
afterEach(() => {
@@ -156,6 +183,28 @@ describe('#start()', () => {
expect(mockLoadingCountInit).toHaveBeenCalledWith(loadingCountStartContract);
});
+ it('passes basePath service to ui/chrome/api/base_path', () => {
+ const legacyPlatform = new LegacyPlatformService({
+ ...defaultParams,
+ });
+
+ legacyPlatform.start(defaultStartDeps);
+
+ expect(mockBasePathInit).toHaveBeenCalledTimes(1);
+ expect(mockBasePathInit).toHaveBeenCalledWith(basePathStartContract);
+ });
+
+ it('passes basePath service to ui/chrome/api/ui_settings', () => {
+ const legacyPlatform = new LegacyPlatformService({
+ ...defaultParams,
+ });
+
+ legacyPlatform.start(defaultStartDeps);
+
+ expect(mockUiSettingsInit).toHaveBeenCalledTimes(1);
+ expect(mockUiSettingsInit).toHaveBeenCalledWith(uiSettingsStartContract);
+ });
+
describe('useLegacyTestHarness = false', () => {
it('passes the targetDomElement to ui/chrome', () => {
const legacyPlatform = new LegacyPlatformService({
@@ -169,6 +218,7 @@ describe('#start()', () => {
expect(mockUiChromeBootstrap).toHaveBeenCalledWith(defaultParams.targetDomElement);
});
});
+
describe('useLegacyTestHarness = true', () => {
it('passes the targetDomElement to ui/test_harness', () => {
const legacyPlatform = new LegacyPlatformService({
@@ -196,14 +246,7 @@ describe('#start()', () => {
legacyPlatform.start(defaultStartDeps);
- expect(mockLoadOrder).toEqual([
- 'ui/metadata',
- 'ui/notify/fatal_error',
- 'ui/notify/toasts',
- 'ui/chrome/api/loading_count',
- 'ui/chrome',
- 'legacy files',
- ]);
+ expect(mockLoadOrder).toMatchSnapshot();
});
});
@@ -218,14 +261,7 @@ describe('#start()', () => {
legacyPlatform.start(defaultStartDeps);
- expect(mockLoadOrder).toEqual([
- 'ui/metadata',
- 'ui/notify/fatal_error',
- 'ui/notify/toasts',
- 'ui/chrome/api/loading_count',
- 'ui/test_harness',
- 'legacy files',
- ]);
+ expect(mockLoadOrder).toMatchSnapshot();
});
});
});
diff --git a/src/core/public/legacy_platform/legacy_platform_service.ts b/src/core/public/legacy_platform/legacy_platform_service.ts
index 52d2534c8b8e6..45c7cf76c3cb6 100644
--- a/src/core/public/legacy_platform/legacy_platform_service.ts
+++ b/src/core/public/legacy_platform/legacy_platform_service.ts
@@ -18,16 +18,20 @@
*/
import angular from 'angular';
+import { BasePathStartContract } from '../base_path';
import { FatalErrorsStartContract } from '../fatal_errors';
import { InjectedMetadataStartContract } from '../injected_metadata';
import { LoadingCountStartContract } from '../loading_count';
import { NotificationsStartContract } from '../notifications';
+import { UiSettingsClient } from '../ui_settings';
interface Deps {
injectedMetadata: InjectedMetadataStartContract;
fatalErrors: FatalErrorsStartContract;
notifications: NotificationsStartContract;
loadingCount: LoadingCountStartContract;
+ basePath: BasePathStartContract;
+ uiSettings: UiSettingsClient;
}
export interface LegacyPlatformParams {
@@ -46,13 +50,22 @@ export interface LegacyPlatformParams {
export class LegacyPlatformService {
constructor(private readonly params: LegacyPlatformParams) {}
- public start({ injectedMetadata, fatalErrors, notifications, loadingCount }: Deps) {
+ public start({
+ injectedMetadata,
+ fatalErrors,
+ notifications,
+ loadingCount,
+ basePath,
+ uiSettings,
+ }: Deps) {
// Inject parts of the new platform into parts of the legacy platform
// so that legacy APIs/modules can mimic their new platform counterparts
require('ui/metadata').__newPlatformInit__(injectedMetadata.getLegacyMetadata());
require('ui/notify/fatal_error').__newPlatformInit__(fatalErrors);
require('ui/notify/toasts').__newPlatformInit__(notifications.toasts);
require('ui/chrome/api/loading_count').__newPlatformInit__(loadingCount);
+ require('ui/chrome/api/base_path').__newPlatformInit__(basePath);
+ require('ui/chrome/api/ui_settings').__newPlatformInit__(uiSettings);
// Load the bootstrap module before loading the legacy platform files so that
// the bootstrap module can modify the environment a bit first
diff --git a/src/core/public/ui_settings/__snapshots__/ui_settings_api.test.ts.snap b/src/core/public/ui_settings/__snapshots__/ui_settings_api.test.ts.snap
new file mode 100644
index 0000000000000..1f69bc37b81cd
--- /dev/null
+++ b/src/core/public/ui_settings/__snapshots__/ui_settings_api.test.ts.snap
@@ -0,0 +1,168 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`#batchSet Buffers are always clear of previously buffered changes: two requests, second only sends bar, not foo 1`] = `
+Object {
+ "matched": Array [
+ Array [
+ "/foo/bar/api/kibana/settings",
+ Object {
+ "body": "{\\"changes\\":{\\"foo\\":\\"bar\\"}}",
+ "credentials": "same-origin",
+ "headers": Object {
+ "accept": "application/json",
+ "content-type": "application/json",
+ "kbn-version": "v9.9.9",
+ },
+ "method": "POST",
+ },
+ ],
+ Array [
+ "/foo/bar/api/kibana/settings",
+ Object {
+ "body": "{\\"changes\\":{\\"bar\\":\\"box\\"}}",
+ "credentials": "same-origin",
+ "headers": Object {
+ "accept": "application/json",
+ "content-type": "application/json",
+ "kbn-version": "v9.9.9",
+ },
+ "method": "POST",
+ },
+ ],
+ ],
+ "unmatched": Array [],
+}
+`;
+
+exports[`#batchSet Overwrites previously buffered values with new values for the same key: two requests, foo=d in final 1`] = `
+Object {
+ "matched": Array [
+ Array [
+ "/foo/bar/api/kibana/settings",
+ Object {
+ "body": "{\\"changes\\":{\\"foo\\":\\"a\\"}}",
+ "credentials": "same-origin",
+ "headers": Object {
+ "accept": "application/json",
+ "content-type": "application/json",
+ "kbn-version": "v9.9.9",
+ },
+ "method": "POST",
+ },
+ ],
+ Array [
+ "/foo/bar/api/kibana/settings",
+ Object {
+ "body": "{\\"changes\\":{\\"foo\\":\\"d\\"}}",
+ "credentials": "same-origin",
+ "headers": Object {
+ "accept": "application/json",
+ "content-type": "application/json",
+ "kbn-version": "v9.9.9",
+ },
+ "method": "POST",
+ },
+ ],
+ ],
+ "unmatched": Array [],
+}
+`;
+
+exports[`#batchSet buffers changes while first request is in progress, sends buffered changes after first request completes: final, includes both requests 1`] = `
+Object {
+ "matched": Array [
+ Array [
+ "/foo/bar/api/kibana/settings",
+ Object {
+ "body": "{\\"changes\\":{\\"foo\\":\\"bar\\"}}",
+ "credentials": "same-origin",
+ "headers": Object {
+ "accept": "application/json",
+ "content-type": "application/json",
+ "kbn-version": "v9.9.9",
+ },
+ "method": "POST",
+ },
+ ],
+ Array [
+ "/foo/bar/api/kibana/settings",
+ Object {
+ "body": "{\\"changes\\":{\\"box\\":\\"bar\\"}}",
+ "credentials": "same-origin",
+ "headers": Object {
+ "accept": "application/json",
+ "content-type": "application/json",
+ "kbn-version": "v9.9.9",
+ },
+ "method": "POST",
+ },
+ ],
+ ],
+ "unmatched": Array [],
+}
+`;
+
+exports[`#batchSet buffers changes while first request is in progress, sends buffered changes after first request completes: initial, only one request 1`] = `
+Object {
+ "matched": Array [
+ Array [
+ "/foo/bar/api/kibana/settings",
+ Object {
+ "body": "{\\"changes\\":{\\"foo\\":\\"bar\\"}}",
+ "credentials": "same-origin",
+ "headers": Object {
+ "accept": "application/json",
+ "content-type": "application/json",
+ "kbn-version": "v9.9.9",
+ },
+ "method": "POST",
+ },
+ ],
+ ],
+ "unmatched": Array [],
+}
+`;
+
+exports[`#batchSet rejects all promises for batched requests that fail: promise rejections 1`] = `
+Array [
+ Object {
+ "error": [Error: Request failed with status code: 400],
+ "isRejected": true,
+ },
+ Object {
+ "error": [Error: Request failed with status code: 400],
+ "isRejected": true,
+ },
+ Object {
+ "error": [Error: Request failed with status code: 400],
+ "isRejected": true,
+ },
+]
+`;
+
+exports[`#batchSet rejects on 301 1`] = `"Request failed with status code: 301"`;
+
+exports[`#batchSet rejects on 404 response 1`] = `"Request failed with status code: 404"`;
+
+exports[`#batchSet rejects on 500 1`] = `"Request failed with status code: 500"`;
+
+exports[`#batchSet sends a single change immediately: synchronous fetch 1`] = `
+Object {
+ "matched": Array [
+ Array [
+ "/foo/bar/api/kibana/settings",
+ Object {
+ "body": "{\\"changes\\":{\\"foo\\":\\"bar\\"}}",
+ "credentials": "same-origin",
+ "headers": Object {
+ "accept": "application/json",
+ "content-type": "application/json",
+ "kbn-version": "v9.9.9",
+ },
+ "method": "POST",
+ },
+ ],
+ ],
+ "unmatched": Array [],
+}
+`;
diff --git a/src/ui/ui_settings/public/__snapshots__/ui_settings_client.test.js.snap b/src/core/public/ui_settings/__snapshots__/ui_settings_client.test.ts.snap
similarity index 87%
rename from src/ui/ui_settings/public/__snapshots__/ui_settings_client.test.js.snap
rename to src/core/public/ui_settings/__snapshots__/ui_settings_client.test.ts.snap
index 8915553b36bf1..e49c546f3550c 100644
--- a/src/ui/ui_settings/public/__snapshots__/ui_settings_client.test.js.snap
+++ b/src/core/public/ui_settings/__snapshots__/ui_settings_client.test.ts.snap
@@ -20,7 +20,29 @@ You can use \`config.get(\\"throwableProperty\\", defaultValue)\`, which will ju
\`defaultValue\` when the key is unrecognized."
`;
-exports[`#overrideLocalDefault #assertUpdateAllowed() throws error when keys is overridden 1`] = `"Unable to update \\"foo\\" because its value is overridden by the Kibana server"`;
+exports[`#getUpdate$ sends { key, newValue, oldValue } notifications when config changes 1`] = `
+Array [
+ Array [
+ Object {
+ "key": "foo",
+ "newValue": "bar",
+ "oldValue": undefined,
+ },
+ ],
+]
+`;
+
+exports[`#getUpdate$ sends { key, newValue, oldValue } notifications when config changes 2`] = `
+Array [
+ Array [
+ Object {
+ "key": "foo",
+ "newValue": "baz",
+ "oldValue": "bar",
+ },
+ ],
+]
+`;
exports[`#overrideLocalDefault key has no user value calls subscriber with new and previous value: single subscriber call 1`] = `
Array [
@@ -100,39 +122,3 @@ Object {
exports[`#remove throws an error if key is overridden 1`] = `"Unable to update \\"bar\\" because its value is overridden by the Kibana server"`;
exports[`#set throws an error if key is overridden 1`] = `"Unable to update \\"foo\\" because its value is overridden by the Kibana server"`;
-
-exports[`#subscribe calls handler with { key, newValue, oldValue } when config changes 1`] = `
-Array [
- Array [
- Object {
- "key": "foo",
- "newValue": "bar",
- "oldValue": undefined,
- },
- ],
-]
-`;
-
-exports[`#subscribe calls handler with { key, newValue, oldValue } when config changes 2`] = `
-Array [
- Array [
- Object {
- "key": "foo",
- "newValue": "baz",
- "oldValue": "bar",
- },
- ],
-]
-`;
-
-exports[`#subscribe returns a subscription object which unsubs when .unsubscribe() is called 1`] = `
-Array [
- Array [
- Object {
- "key": "foo",
- "newValue": "bar",
- "oldValue": undefined,
- },
- ],
-]
-`;
diff --git a/src/core/public/ui_settings/__snapshots__/ui_settings_service.test.ts.snap b/src/core/public/ui_settings/__snapshots__/ui_settings_service.test.ts.snap
new file mode 100644
index 0000000000000..e7e42c42c8b87
--- /dev/null
+++ b/src/core/public/ui_settings/__snapshots__/ui_settings_service.test.ts.snap
@@ -0,0 +1,78 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`#start constructs UiSettingsClient and UiSettingsApi: UiSettingsApi args 1`] = `
+[MockFunction MockUiSettingsApi] {
+ "calls": Array [
+ Array [
+ Object {
+ "basePathStartContract": true,
+ },
+ "kibanaVersion",
+ ],
+ ],
+ "results": Array [
+ Object {
+ "isThrow": false,
+ "value": undefined,
+ },
+ ],
+}
+`;
+
+exports[`#start constructs UiSettingsClient and UiSettingsApi: UiSettingsClient args 1`] = `
+[MockFunction MockUiSettingsClient] {
+ "calls": Array [
+ Array [
+ Object {
+ "api": MockUiSettingsApi {
+ "getLoadingCount$": [MockFunction] {
+ "calls": Array [
+ Array [],
+ ],
+ "results": Array [
+ Object {
+ "isThrow": false,
+ "value": Object {
+ "loadingCountObservable": true,
+ },
+ },
+ ],
+ },
+ "stop": [MockFunction],
+ },
+ "defaults": Object {
+ "legacyInjectedUiSettingDefaults": true,
+ },
+ "initialSettings": Object {
+ "legacyInjectedUiSettingUserValues": true,
+ },
+ "onUpdateError": [Function],
+ },
+ ],
+ ],
+ "results": Array [
+ Object {
+ "isThrow": false,
+ "value": undefined,
+ },
+ ],
+}
+`;
+
+exports[`#start passes the uiSettings loading count to the loading count api: loadingCount.add calls 1`] = `
+[MockFunction] {
+ "calls": Array [
+ Array [
+ Object {
+ "loadingCountObservable": true,
+ },
+ ],
+ ],
+ "results": Array [
+ Object {
+ "isThrow": false,
+ "value": undefined,
+ },
+ ],
+}
+`;
diff --git a/src/core/public/ui_settings/index.ts b/src/core/public/ui_settings/index.ts
new file mode 100644
index 0000000000000..36c3d864d8119
--- /dev/null
+++ b/src/core/public/ui_settings/index.ts
@@ -0,0 +1,21 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+export { UiSettingsService, UiSettingsStartContract } from './ui_settings_service';
+export { UiSettingsClient } from './ui_settings_client';
diff --git a/src/core/public/ui_settings/types.ts b/src/core/public/ui_settings/types.ts
new file mode 100644
index 0000000000000..4fa4109c7bc26
--- /dev/null
+++ b/src/core/public/ui_settings/types.ts
@@ -0,0 +1,39 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// properties that come from legacyInjectedMetadata.uiSettings.defaults
+interface InjectedUiSettingsDefault {
+ name?: string;
+ value?: any;
+ description?: string;
+ category?: string[];
+ type?: string;
+ readOnly?: boolean;
+ options?: string[] | { [key: string]: any };
+}
+
+// properties that come from legacyInjectedMetadata.uiSettings.user
+interface InjectedUiSettingsUser {
+ userValue?: any;
+ isOverridden?: boolean;
+}
+
+export interface UiSettingsState {
+ [key: string]: InjectedUiSettingsDefault & InjectedUiSettingsUser;
+}
diff --git a/src/core/public/ui_settings/ui_settings_api.test.ts b/src/core/public/ui_settings/ui_settings_api.test.ts
new file mode 100644
index 0000000000000..75358297a5661
--- /dev/null
+++ b/src/core/public/ui_settings/ui_settings_api.test.ts
@@ -0,0 +1,242 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import fetchMock from 'fetch-mock';
+import * as Rx from 'rxjs';
+import { takeUntil, toArray } from 'rxjs/operators';
+
+import { UiSettingsApi } from './ui_settings_api';
+
+function setup() {
+ const basePath: any = {
+ addToPath: jest.fn(path => `/foo/bar${path}`),
+ };
+
+ const uiSettingsApi = new UiSettingsApi(basePath, 'v9.9.9');
+
+ return {
+ basePath,
+ uiSettingsApi,
+ };
+}
+
+async function settlePromise
(promise: Promise) {
+ try {
+ return {
+ isResolved: true,
+ result: await promise,
+ };
+ } catch (error) {
+ return {
+ isRejected: true,
+ error,
+ };
+ }
+}
+
+afterEach(() => {
+ fetchMock.restore();
+});
+
+describe('#batchSet', () => {
+ it('sends a single change immediately', () => {
+ fetchMock.mock('*', {
+ body: { settings: {} },
+ });
+
+ const { uiSettingsApi } = setup();
+ uiSettingsApi.batchSet('foo', 'bar');
+ expect(fetchMock.calls()).toMatchSnapshot('synchronous fetch');
+ });
+
+ it('buffers changes while first request is in progress, sends buffered changes after first request completes', async () => {
+ fetchMock.mock('*', {
+ body: { settings: {} },
+ });
+
+ const { uiSettingsApi } = setup();
+
+ uiSettingsApi.batchSet('foo', 'bar');
+ const finalPromise = uiSettingsApi.batchSet('box', 'bar');
+
+ expect(fetchMock.calls()).toMatchSnapshot('initial, only one request');
+ await finalPromise;
+ expect(fetchMock.calls()).toMatchSnapshot('final, includes both requests');
+ });
+
+ it('Overwrites previously buffered values with new values for the same key', async () => {
+ fetchMock.mock('*', {
+ body: { settings: {} },
+ });
+
+ const { uiSettingsApi } = setup();
+
+ uiSettingsApi.batchSet('foo', 'a');
+ uiSettingsApi.batchSet('foo', 'b');
+ uiSettingsApi.batchSet('foo', 'c');
+ await uiSettingsApi.batchSet('foo', 'd');
+
+ expect(fetchMock.calls()).toMatchSnapshot('two requests, foo=d in final');
+ });
+
+ it('Buffers are always clear of previously buffered changes', async () => {
+ fetchMock.mock('*', {
+ body: { settings: {} },
+ });
+
+ const { uiSettingsApi } = setup();
+ uiSettingsApi.batchSet('foo', 'bar');
+ uiSettingsApi.batchSet('bar', 'foo');
+ await uiSettingsApi.batchSet('bar', 'box');
+
+ expect(fetchMock.calls()).toMatchSnapshot('two requests, second only sends bar, not foo');
+ });
+
+ it('rejects on 404 response', async () => {
+ fetchMock.mock('*', {
+ status: 404,
+ body: 'not found',
+ });
+
+ const { uiSettingsApi } = setup();
+ await expect(uiSettingsApi.batchSet('foo', 'bar')).rejects.toThrowErrorMatchingSnapshot();
+ });
+
+ it('rejects on 301', async () => {
+ fetchMock.mock('*', {
+ status: 301,
+ body: 'redirect',
+ });
+
+ const { uiSettingsApi } = setup();
+ await expect(uiSettingsApi.batchSet('foo', 'bar')).rejects.toThrowErrorMatchingSnapshot();
+ });
+
+ it('rejects on 500', async () => {
+ fetchMock.mock('*', {
+ status: 500,
+ body: 'redirect',
+ });
+
+ const { uiSettingsApi } = setup();
+ await expect(uiSettingsApi.batchSet('foo', 'bar')).rejects.toThrowErrorMatchingSnapshot();
+ });
+
+ it('rejects all promises for batched requests that fail', async () => {
+ fetchMock.once('*', {
+ body: { settings: {} },
+ });
+ fetchMock.once('*', {
+ status: 400,
+ body: 'invalid',
+ });
+
+ const { uiSettingsApi } = setup();
+ // trigger the initial sync request, which enabled buffering
+ uiSettingsApi.batchSet('foo', 'bar');
+
+ // buffer some requests so they will be sent together
+ await expect(
+ Promise.all([
+ settlePromise(uiSettingsApi.batchSet('foo', 'a')),
+ settlePromise(uiSettingsApi.batchSet('bar', 'b')),
+ settlePromise(uiSettingsApi.batchSet('baz', 'c')),
+ ])
+ ).resolves.toMatchSnapshot('promise rejections');
+
+ // ensure only two requests were sent
+ expect(fetchMock.calls().matched).toHaveLength(2);
+ });
+});
+
+describe('#getLoadingCount$()', () => {
+ it('emits the current number of active requests', async () => {
+ fetchMock.once('*', {
+ body: { settings: {} },
+ });
+
+ const { uiSettingsApi } = setup();
+ const done$ = new Rx.Subject();
+ const promise = uiSettingsApi
+ .getLoadingCount$()
+ .pipe(
+ takeUntil(done$),
+ toArray()
+ )
+ .toPromise();
+
+ await uiSettingsApi.batchSet('foo', 'bar');
+ done$.next();
+
+ await expect(promise).resolves.toEqual([0, 1, 0]);
+ });
+
+ it('decrements loading count when requests fail', async () => {
+ fetchMock.once('*', {
+ body: { settings: {} },
+ });
+ fetchMock.once('*', {
+ status: 400,
+ body: 'invalid',
+ });
+
+ const { uiSettingsApi } = setup();
+ const done$ = new Rx.Subject();
+ const promise = uiSettingsApi
+ .getLoadingCount$()
+ .pipe(
+ takeUntil(done$),
+ toArray()
+ )
+ .toPromise();
+
+ await uiSettingsApi.batchSet('foo', 'bar');
+ await expect(uiSettingsApi.batchSet('foo', 'bar')).rejects.toThrowError();
+
+ done$.next();
+ await expect(promise).resolves.toEqual([0, 1, 0, 1, 0]);
+ });
+});
+
+describe('#stop', () => {
+ it('completes any loading count observables', async () => {
+ fetchMock.once('*', {
+ body: { settings: {} },
+ });
+
+ const { uiSettingsApi } = setup();
+ const promise = Promise.all([
+ uiSettingsApi
+ .getLoadingCount$()
+ .pipe(toArray())
+ .toPromise(),
+ uiSettingsApi
+ .getLoadingCount$()
+ .pipe(toArray())
+ .toPromise(),
+ ]);
+
+ const batchSetPromise = uiSettingsApi.batchSet('foo', 'bar');
+ uiSettingsApi.stop();
+
+ // both observables should emit the same values, and complete before the request is done loading
+ await expect(promise).resolves.toEqual([[0, 1], [0, 1]]);
+ await batchSetPromise;
+ });
+});
diff --git a/src/core/public/ui_settings/ui_settings_api.ts b/src/core/public/ui_settings/ui_settings_api.ts
new file mode 100644
index 0000000000000..6d43384fa6d02
--- /dev/null
+++ b/src/core/public/ui_settings/ui_settings_api.ts
@@ -0,0 +1,163 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { BehaviorSubject } from 'rxjs';
+
+import { BasePathStartContract } from '../base_path';
+import { UiSettingsState } from './types';
+
+export interface UiSettingsApiResponse {
+ settings: UiSettingsState;
+}
+
+interface Changes {
+ values: {
+ [key: string]: any;
+ };
+
+ callback(error?: Error, response?: UiSettingsApiResponse): void;
+}
+
+const NOOP_CHANGES = {
+ values: {},
+ callback: () => {
+ // noop
+ },
+};
+
+export class UiSettingsApi {
+ private pendingChanges?: Changes;
+ private sendInProgress = false;
+
+ private readonly loadingCount$ = new BehaviorSubject(0);
+
+ constructor(
+ private readonly basePath: BasePathStartContract,
+ private readonly kibanaVersion: string
+ ) {}
+
+ /**
+ * Adds a key+value that will be sent to the server ASAP. If a request is
+ * already in progress it will wait until the previous request is complete
+ * before sending the next request
+ */
+ public batchSet(key: string, value: any) {
+ return new Promise((resolve, reject) => {
+ const prev = this.pendingChanges || NOOP_CHANGES;
+
+ this.pendingChanges = {
+ values: {
+ ...prev.values,
+ [key]: value,
+ },
+
+ callback(error, resp) {
+ prev.callback(error, resp);
+
+ if (error) {
+ reject(error);
+ } else {
+ resolve(resp);
+ }
+ },
+ };
+
+ this.flushPendingChanges();
+ });
+ }
+
+ /**
+ * Gets an observable that notifies subscribers of the current number of active requests
+ */
+ public getLoadingCount$() {
+ return this.loadingCount$.asObservable();
+ }
+
+ /**
+ * Prepares the uiSettings API to be discarded
+ */
+ public stop() {
+ this.loadingCount$.complete();
+ }
+
+ /**
+ * If there are changes that need to be sent to the server and there is not already a
+ * request in progress, this method will start a request sending those changes. Once
+ * the request is complete `flushPendingChanges()` will be called again, and if the
+ * prerequisites are still true (because changes were queued while the request was in
+ * progress) then another request will be started until all pending changes have been
+ * sent to the server.
+ */
+ private async flushPendingChanges() {
+ if (!this.pendingChanges) {
+ return;
+ }
+
+ if (this.sendInProgress) {
+ return;
+ }
+
+ const changes = this.pendingChanges;
+ this.pendingChanges = undefined;
+
+ try {
+ this.sendInProgress = true;
+ changes.callback(
+ undefined,
+ await this.sendRequest('POST', '/api/kibana/settings', {
+ changes: changes.values,
+ })
+ );
+ } catch (error) {
+ changes.callback(error);
+ } finally {
+ this.sendInProgress = false;
+ this.flushPendingChanges();
+ }
+ }
+
+ /**
+ * Calls window.fetch() with the proper headers and error handling logic.
+ *
+ * TODO: migrate this to kfetch or whatever the new platform equivalent is once it exists
+ */
+ private async sendRequest(method: string, path: string, body: any) {
+ try {
+ this.loadingCount$.next(this.loadingCount$.getValue() + 1);
+ const response = await fetch(this.basePath.addToPath(path), {
+ method,
+ body: JSON.stringify(body),
+ headers: {
+ accept: 'application/json',
+ 'content-type': 'application/json',
+ 'kbn-version': this.kibanaVersion,
+ },
+ credentials: 'same-origin',
+ });
+
+ if (response.status >= 300) {
+ throw new Error(`Request failed with status code: ${response.status}`);
+ }
+
+ return await response.json();
+ } finally {
+ this.loadingCount$.next(this.loadingCount$.getValue() - 1);
+ }
+ }
+}
diff --git a/src/ui/ui_settings/public/ui_settings_client.test.js b/src/core/public/ui_settings/ui_settings_client.test.ts
similarity index 82%
rename from src/ui/ui_settings/public/ui_settings_client.test.js
rename to src/core/public/ui_settings/ui_settings_client.test.ts
index f41c9bc0018ca..53cf4b7347e1b 100644
--- a/src/ui/ui_settings/public/ui_settings_client.test.js
+++ b/src/core/public/ui_settings/ui_settings_client.test.ts
@@ -18,41 +18,26 @@
*/
import { UiSettingsClient } from './ui_settings_client';
-import { sendRequest } from './send_request';
-jest.useFakeTimers();
-jest.mock('./send_request', () => ({
- sendRequest: jest.fn(() => ({}))
-}));
-
-beforeEach(() => {
- sendRequest.mockRestore();
- jest.clearAllMocks();
-});
-
-function setup(options = {}) {
- const {
- defaults = { dateFormat: { value: 'Browser' } },
- initialSettings = {}
- } = options;
+function setup(options: { defaults?: any; initialSettings?: any } = {}) {
+ const { defaults = { dateFormat: { value: 'Browser' } }, initialSettings = {} } = options;
const batchSet = jest.fn(() => ({
- settings: {}
+ settings: {},
}));
+ const onUpdateError = jest.fn();
+
const config = new UiSettingsClient({
defaults,
initialSettings,
api: {
- batchSet
- },
- notify: {
- log: jest.fn(),
- error: jest.fn(),
- }
+ batchSet,
+ } as any,
+ onUpdateError,
});
- return { config, batchSet };
+ return { config, batchSet, onUpdateError };
}
describe('#get', () => {
@@ -88,7 +73,7 @@ describe('#get', () => {
expect(config.get('dataFormat', defaultDateFormat)).toBe(defaultDateFormat);
});
- it('throws on unknown properties that don\'t have a value yet.', () => {
+ it("throws on unknown properties that don't have a value yet.", () => {
const { config } = setup();
expect(() => config.get('throwableProperty')).toThrowErrorMatchingSnapshot();
});
@@ -129,9 +114,9 @@ describe('#set', () => {
initialSettings: {
foo: {
isOverridden: true,
- value: 'bar'
- }
- }
+ value: 'bar',
+ },
+ },
});
await expect(config.set('foo', true)).rejects.toThrowErrorMatchingSnapshot();
});
@@ -158,9 +143,9 @@ describe('#remove', () => {
initialSettings: {
bar: {
isOverridden: true,
- userValue: true
- }
- }
+ userValue: true,
+ },
+ },
});
await expect(config.remove('bar')).rejects.toThrowErrorMatchingSnapshot();
});
@@ -209,12 +194,12 @@ describe('#isCustom', () => {
});
});
-describe('#subscribe', () => {
- it('calls handler with { key, newValue, oldValue } when config changes', () => {
+describe('#getUpdate$', () => {
+ it('sends { key, newValue, oldValue } notifications when config changes', () => {
const handler = jest.fn();
const { config } = setup();
- config.subscribe(handler);
+ config.getUpdate$().subscribe(handler);
expect(handler).not.toHaveBeenCalled();
config.set('foo', 'bar');
@@ -227,21 +212,17 @@ describe('#subscribe', () => {
expect(handler.mock.calls).toMatchSnapshot();
});
- it('returns a subscription object which unsubs when .unsubscribe() is called', () => {
- const handler = jest.fn();
+ it('observables complete when client is stopped', () => {
+ const onComplete = jest.fn();
const { config } = setup();
- const subscription = config.subscribe(handler);
- expect(handler).not.toHaveBeenCalled();
-
- config.set('foo', 'bar');
- expect(handler).toHaveBeenCalledTimes(1);
- expect(handler.mock.calls).toMatchSnapshot();
- handler.mockClear();
+ config.getUpdate$().subscribe({
+ complete: onComplete,
+ });
- subscription.unsubscribe();
- config.set('foo', 'baz');
- expect(handler).not.toHaveBeenCalled();
+ expect(onComplete).not.toHaveBeenCalled();
+ config.stop();
+ expect(onComplete).toHaveBeenCalled();
});
});
@@ -267,7 +248,7 @@ describe('#overrideLocalDefault', () => {
const handler = jest.fn();
const { config } = setup();
- config.subscribe(handler);
+ config.getUpdate$().subscribe(handler);
config.overrideLocalDefault('dateFormat', 'bar');
expect(handler.mock.calls).toMatchSnapshot('single subscriber call');
});
@@ -297,7 +278,7 @@ describe('#overrideLocalDefault', () => {
const { config } = setup();
config.set('dateFormat', 'foo');
- config.subscribe(handler);
+ config.getUpdate$().subscribe(handler);
config.overrideLocalDefault('dateFormat', 'bar');
expect(handler).not.toHaveBeenCalled();
});
@@ -323,55 +304,40 @@ describe('#overrideLocalDefault', () => {
const { config } = setup();
expect(config.isOverridden('foo')).toBe(false);
});
+
it('returns false if key is no overridden', () => {
const { config } = setup({
initialSettings: {
foo: {
- userValue: 1
+ userValue: 1,
},
bar: {
isOverridden: true,
- userValue: 2
- }
- }
+ userValue: 2,
+ },
+ },
});
expect(config.isOverridden('foo')).toBe(false);
});
+
it('returns true when key is overridden', () => {
const { config } = setup({
initialSettings: {
foo: {
- userValue: 1
+ userValue: 1,
},
bar: {
isOverridden: true,
- userValue: 2
+ userValue: 2,
},
- }
+ },
});
expect(config.isOverridden('bar')).toBe(true);
});
+
it('returns false for object prototype properties', () => {
const { config } = setup();
expect(config.isOverridden('hasOwnProperty')).toBe(false);
});
});
-
- describe('#assertUpdateAllowed()', () => {
- it('returns false if no settings defined', () => {
- const { config } = setup();
- expect(config.assertUpdateAllowed('foo')).toBe(undefined);
- });
- it('throws error when keys is overridden', () => {
- const { config } = setup({
- initialSettings: {
- foo: {
- isOverridden: true,
- userValue: 'bar'
- }
- }
- });
- expect(() => config.assertUpdateAllowed('foo')).toThrowErrorMatchingSnapshot();
- });
- });
});
diff --git a/src/core/public/ui_settings/ui_settings_client.ts b/src/core/public/ui_settings/ui_settings_client.ts
new file mode 100644
index 0000000000000..3eb818ee453aa
--- /dev/null
+++ b/src/core/public/ui_settings/ui_settings_client.ts
@@ -0,0 +1,251 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { cloneDeep, defaultsDeep } from 'lodash';
+import { Subject } from 'rxjs';
+
+import { UiSettingsState } from './types';
+import { UiSettingsApi } from './ui_settings_api';
+
+interface Params {
+ api: UiSettingsApi;
+ onUpdateError: UiSettingsClient['onUpdateError'];
+ defaults: UiSettingsState;
+ initialSettings: UiSettingsState;
+}
+
+export class UiSettingsClient {
+ private readonly update$ = new Subject<{ key: string; newValue: any; oldValue: any }>();
+
+ private readonly api: UiSettingsApi;
+ private readonly onUpdateError: (error: Error) => void;
+ private readonly defaults: UiSettingsState;
+ private cache: UiSettingsState;
+
+ constructor(readonly params: Params) {
+ this.api = params.api;
+ this.onUpdateError = params.onUpdateError;
+ this.defaults = cloneDeep(params.defaults);
+ this.cache = defaultsDeep({}, this.defaults, cloneDeep(params.initialSettings));
+ }
+
+ /**
+ * Gets the metadata about all uiSettings, including the type, default value, and user value
+ * for each key.
+ */
+ public getAll() {
+ return cloneDeep(this.cache);
+ }
+
+ /**
+ * Gets the value for a specific uiSetting. If this setting has no user-defined value
+ * then the `defaultOverride` parameter is returned (and parsed if setting is of type
+ * "json" or "number). If the parameter is not defined and the key is not defined by a
+ * uiSettingDefaults then an error is thrown, otherwise the default is read
+ * from the uiSettingDefaults.
+ */
+ public get(key: string, defaultOverride?: any) {
+ const declared = this.isDeclared(key);
+
+ if (!declared && defaultOverride !== undefined) {
+ return defaultOverride;
+ }
+
+ if (!declared) {
+ throw new Error(
+ `Unexpected \`config.get("${key}")\` call on unrecognized configuration setting "${key}".
+Setting an initial value via \`config.set("${key}", value)\` before attempting to retrieve
+any custom setting value for "${key}" may fix this issue.
+You can use \`config.get("${key}", defaultValue)\`, which will just return
+\`defaultValue\` when the key is unrecognized.`
+ );
+ }
+
+ const type = this.cache[key].type;
+ const userValue = this.cache[key].userValue;
+ const defaultValue = defaultOverride !== undefined ? defaultOverride : this.cache[key].value;
+ const value = userValue == null ? defaultValue : userValue;
+
+ if (type === 'json') {
+ return JSON.parse(value);
+ }
+
+ if (type === 'number') {
+ return parseFloat(value);
+ }
+
+ return value;
+ }
+
+ /**
+ * Sets the value for a uiSetting. If the setting is not defined in the uiSettingDefaults
+ * it will be stored as a custom setting. The new value will be synchronously available via
+ * the `get()` method and sent to the server in the background. If the request to the
+ * server fails then a toast notification will be displayed and the setting will be
+ * reverted it its value before `set()` was called.
+ */
+ public async set(key: string, val: any) {
+ return await this.update(key, val);
+ }
+
+ /**
+ * Removes the user-defined value for a setting, causing it to revert to the default. This
+ * method behaves the same as calling `set(key, null)`, including the synchronization, custom
+ * setting, and error behavior of that method.
+ */
+ public async remove(key: string) {
+ return await this.update(key, null);
+ }
+
+ /**
+ * Returns true if the key is a "known" uiSetting, meaning it is either defined in the
+ * uiSettingDefaults or was previously added as a custom setting via the `set()` method.
+ */
+ public isDeclared(key: string) {
+ return key in this.cache;
+ }
+
+ /**
+ * Returns true if the setting has no user-defined value or is unknown
+ */
+ public isDefault(key: string) {
+ return !this.isDeclared(key) || this.cache[key].userValue == null;
+ }
+
+ /**
+ * Returns true if the setting is not a part of the uiSettingDefaults, but was either
+ * added directly via `set()`, or is an unknown setting found in the uiSettings saved
+ * object
+ */
+ public isCustom(key: string) {
+ return this.isDeclared(key) && !('value' in this.cache[key]);
+ }
+
+ /**
+ * Returns true if a settings value is overridden by the server. When a setting is overridden
+ * its value can not be changed via `set()` or `remove()`.
+ */
+ public isOverridden(key: string) {
+ return this.isDeclared(key) && Boolean(this.cache[key].isOverridden);
+ }
+
+ /**
+ * Overrides the default value for a setting in this specific browser tab. If the page
+ * is reloaded the default override is lost.
+ */
+ public overrideLocalDefault(key: string, newDefault: any) {
+ // capture the previous value
+ const prevDefault = this.defaults[key] ? this.defaults[key].value : undefined;
+
+ // update defaults map
+ this.defaults[key] = {
+ ...(this.defaults[key] || {}),
+ value: newDefault,
+ };
+
+ // update cached default value
+ this.cache[key] = {
+ ...(this.cache[key] || {}),
+ value: newDefault,
+ };
+
+ // don't broadcast change if userValue was already overriding the default
+ if (this.cache[key].userValue == null) {
+ this.update$.next({
+ key,
+ newValue: newDefault,
+ oldValue: prevDefault,
+ });
+ }
+ }
+
+ /**
+ * Returns an Observable that notifies subscribers of each update to the uiSettings,
+ * including the key, newValue, and oldValue of the setting that changed.
+ */
+ public getUpdate$() {
+ return this.update$.asObservable();
+ }
+
+ /**
+ * Prepares the uiSettingsClient to be discarded, completing any update$ observables
+ * that have been created.
+ */
+ public stop() {
+ this.update$.complete();
+ }
+
+ private assertUpdateAllowed(key: string) {
+ if (this.isOverridden(key)) {
+ throw new Error(
+ `Unable to update "${key}" because its value is overridden by the Kibana server`
+ );
+ }
+ }
+
+ private async update(key: string, newVal: any) {
+ this.assertUpdateAllowed(key);
+
+ const declared = this.isDeclared(key);
+ const defaults = this.defaults;
+
+ const oldVal = declared ? this.cache[key].userValue : undefined;
+
+ const unchanged = oldVal === newVal;
+ if (unchanged) {
+ return true;
+ }
+
+ const initialVal = declared ? this.get(key) : undefined;
+ this.setLocally(key, newVal);
+
+ try {
+ const { settings } = await this.api.batchSet(key, newVal);
+ this.cache = defaultsDeep({}, defaults, settings);
+ return true;
+ } catch (error) {
+ this.setLocally(key, initialVal);
+ this.onUpdateError(error);
+ return false;
+ }
+ }
+
+ private setLocally(key: string, newValue: any) {
+ this.assertUpdateAllowed(key);
+
+ if (!this.isDeclared(key)) {
+ this.cache[key] = {};
+ }
+
+ const oldValue = this.get(key);
+
+ if (newValue === null) {
+ delete this.cache[key].userValue;
+ } else {
+ const { type } = this.cache[key];
+ if (type === 'json' && typeof newValue !== 'string') {
+ this.cache[key].userValue = JSON.stringify(newValue);
+ } else {
+ this.cache[key].userValue = newValue;
+ }
+ }
+
+ this.update$.next({ key, newValue, oldValue });
+ }
+}
diff --git a/src/core/public/ui_settings/ui_settings_service.test.ts b/src/core/public/ui_settings/ui_settings_service.test.ts
new file mode 100644
index 0000000000000..2b31cedd07094
--- /dev/null
+++ b/src/core/public/ui_settings/ui_settings_service.test.ts
@@ -0,0 +1,127 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+function mockClass(
+ module: string,
+ Class: { new (...args: any[]): T },
+ setup: (instance: any, args: any[]) => void
+) {
+ const MockClass = jest.fn(function(this: any, ...args: any[]) {
+ setup(this, args);
+ });
+
+ // define the mock name which is used in some snapshots
+ MockClass.mockName(`Mock${Class.name}`);
+
+ // define the class name for the MockClass which is used in other snapshots
+ Object.defineProperty(MockClass, 'name', {
+ value: `Mock${Class.name}`,
+ });
+
+ jest.mock(module, () => ({
+ [Class.name]: MockClass,
+ }));
+
+ return MockClass;
+}
+
+// Mock the UiSettingsApi class
+import { UiSettingsApi } from './ui_settings_api';
+const MockUiSettingsApi = mockClass('./ui_settings_api', UiSettingsApi, inst => {
+ inst.stop = jest.fn();
+ inst.getLoadingCount$ = jest.fn().mockReturnValue({
+ loadingCountObservable: true,
+ });
+});
+
+// Mock the UiSettingsClient class
+import { UiSettingsClient } from './ui_settings_client';
+const MockUiSettingsClient = mockClass('./ui_settings_client', UiSettingsClient, inst => {
+ inst.stop = jest.fn();
+});
+
+// Load the service
+import { UiSettingsService } from './ui_settings_service';
+
+const loadingCountStartContract = {
+ loadingCountStartContract: true,
+ add: jest.fn(),
+};
+
+const defaultDeps: any = {
+ notifications: {
+ notificationsStartContract: true,
+ },
+ loadingCount: loadingCountStartContract,
+ injectedMetadata: {
+ injectedMetadataStartContract: true,
+ getKibanaVersion: jest.fn().mockReturnValue('kibanaVersion'),
+ getLegacyMetadata: jest.fn().mockReturnValue({
+ uiSettings: {
+ defaults: { legacyInjectedUiSettingDefaults: true },
+ user: { legacyInjectedUiSettingUserValues: true },
+ },
+ }),
+ },
+ basePath: {
+ basePathStartContract: true,
+ },
+};
+
+afterEach(() => {
+ jest.clearAllMocks();
+});
+
+describe('#start', () => {
+ it('returns an instance of UiSettingsClient', () => {
+ const start = new UiSettingsService().start(defaultDeps);
+ expect(start).toBeInstanceOf(MockUiSettingsClient);
+ });
+
+ it('constructs UiSettingsClient and UiSettingsApi', () => {
+ new UiSettingsService().start(defaultDeps);
+
+ expect(MockUiSettingsApi).toMatchSnapshot('UiSettingsApi args');
+ expect(MockUiSettingsClient).toMatchSnapshot('UiSettingsClient args');
+ });
+
+ it('passes the uiSettings loading count to the loading count api', () => {
+ new UiSettingsService().start(defaultDeps);
+
+ expect(loadingCountStartContract.add).toMatchSnapshot('loadingCount.add calls');
+ });
+});
+
+describe('#stop', () => {
+ it('runs fine if service never started', () => {
+ const service = new UiSettingsService();
+ expect(() => service.stop()).not.toThrowError();
+ });
+
+ it('stops the uiSettingsClient and uiSettingsApi', () => {
+ const service = new UiSettingsService();
+ const client = service.start(defaultDeps);
+ const [[{ api }]] = MockUiSettingsClient.mock.calls;
+ jest.spyOn(client, 'stop');
+ jest.spyOn(api, 'stop');
+ service.stop();
+ expect(api.stop).toHaveBeenCalledTimes(1);
+ expect(client.stop).toHaveBeenCalledTimes(1);
+ });
+});
diff --git a/src/core/public/ui_settings/ui_settings_service.ts b/src/core/public/ui_settings/ui_settings_service.ts
new file mode 100644
index 0000000000000..e11f903507dc4
--- /dev/null
+++ b/src/core/public/ui_settings/ui_settings_service.ts
@@ -0,0 +1,72 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { BasePathStartContract } from '../base_path';
+import { InjectedMetadataStartContract } from '../injected_metadata';
+import { LoadingCountStartContract } from '../loading_count';
+import { NotificationsStartContract } from '../notifications';
+
+import { UiSettingsApi } from './ui_settings_api';
+import { UiSettingsClient } from './ui_settings_client';
+
+interface Deps {
+ notifications: NotificationsStartContract;
+ loadingCount: LoadingCountStartContract;
+ injectedMetadata: InjectedMetadataStartContract;
+ basePath: BasePathStartContract;
+}
+
+export class UiSettingsService {
+ private uiSettingsApi?: UiSettingsApi;
+ private uiSettingsClient?: UiSettingsClient;
+
+ public start({ notifications, loadingCount, injectedMetadata, basePath }: Deps) {
+ this.uiSettingsApi = new UiSettingsApi(basePath, injectedMetadata.getKibanaVersion());
+ loadingCount.add(this.uiSettingsApi.getLoadingCount$());
+
+ // TODO: when we have time to refactor the UiSettingsClient and all consumers
+ // we should stop using the legacy format and pick a better one
+ const legacyMetadata = injectedMetadata.getLegacyMetadata();
+ this.uiSettingsClient = new UiSettingsClient({
+ api: this.uiSettingsApi,
+ onUpdateError: error => {
+ notifications.toasts.addDanger({
+ title: 'Unable to update UI setting',
+ text: error.message,
+ });
+ },
+ defaults: legacyMetadata.uiSettings.defaults,
+ initialSettings: legacyMetadata.uiSettings.user,
+ });
+
+ return this.uiSettingsClient;
+ }
+
+ public stop() {
+ if (this.uiSettingsClient) {
+ this.uiSettingsClient.stop();
+ }
+
+ if (this.uiSettingsApi) {
+ this.uiSettingsApi.stop();
+ }
+ }
+}
+
+export type UiSettingsStartContract = UiSettingsClient;
diff --git a/src/core/public/utils/index.ts b/src/core/public/utils/index.ts
new file mode 100644
index 0000000000000..17de85bbfecce
--- /dev/null
+++ b/src/core/public/utils/index.ts
@@ -0,0 +1,20 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+export { modifyUrl } from './modify_url';
diff --git a/src/core/public/utils/modify_url.test.ts b/src/core/public/utils/modify_url.test.ts
new file mode 100644
index 0000000000000..d1b7081093c28
--- /dev/null
+++ b/src/core/public/utils/modify_url.test.ts
@@ -0,0 +1,59 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { modifyUrl } from './modify_url';
+
+it('supports returning a new url spec', () => {
+ expect(modifyUrl('http://localhost', () => ({}))).toBe('');
+});
+
+it('supports modifying the passed object', () => {
+ expect(
+ modifyUrl('http://localhost', parsed => {
+ parsed.port = 9999;
+ parsed.auth = 'foo:bar';
+ })
+ ).toBe('http://foo:bar@localhost:9999/');
+});
+
+it('supports changing pathname', () => {
+ expect(
+ modifyUrl('http://localhost/some/path', parsed => {
+ parsed.pathname += '/subpath';
+ })
+ ).toBe('http://localhost/some/path/subpath');
+});
+
+it('supports changing port', () => {
+ expect(
+ modifyUrl('http://localhost:5601', parsed => {
+ parsed.port = parsed.port! + 1;
+ })
+ ).toBe('http://localhost:5602/');
+});
+
+it('supports changing protocol', () => {
+ expect(
+ modifyUrl('http://localhost', parsed => {
+ parsed.protocol = 'mail';
+ parsed.slashes = false;
+ parsed.pathname = undefined;
+ })
+ ).toBe('mail:localhost');
+});
diff --git a/src/utils/modify_url.js b/src/core/public/utils/modify_url.ts
similarity index 77%
rename from src/utils/modify_url.js
rename to src/core/public/utils/modify_url.ts
index f988d5218ebf3..15a5532226c60 100644
--- a/src/utils/modify_url.js
+++ b/src/core/public/utils/modify_url.ts
@@ -17,7 +17,29 @@
* under the License.
*/
-import { parse as parseUrl, format as formatUrl } from 'url';
+import { format as formatUrl, parse as parseUrl } from 'url';
+
+interface UrlParts {
+ protocol?: string;
+ slashes?: boolean;
+ auth?: string;
+ hostname?: string;
+ port?: number;
+ pathname?: string;
+ query: { [key: string]: string | string[] | undefined };
+ hash?: string;
+}
+
+interface UrlFormatParts {
+ protocol?: string;
+ slashes?: boolean;
+ auth?: string;
+ hostname?: string;
+ port?: string | number;
+ pathname?: string;
+ query?: { [key: string]: string | string[] | undefined };
+ hash?: string;
+}
/**
* Takes a URL and a function that takes the meaningful parts
@@ -42,17 +64,12 @@ import { parse as parseUrl, format as formatUrl } from 'url';
* lead to the modifications being ignored (depending on which
* property was modified)
* - It's not always clear wither to use path/pathname, host/hostname,
- * so this trys to add helpful constraints
+ * so this tries to add helpful constraints
*
- * @param {String} url - the url to parse
- * @param {Function} block - a function that will modify the parsed url, or return a new one
- * @return {String} the modified and reformatted url
+ * @param url the url to parse
+ * @param block a function that will modify the parsed url, or return a new one
*/
-export function modifyUrl(url, block) {
- if (typeof block !== 'function') {
- throw new TypeError('You must pass a block to define the modifications desired');
- }
-
+export function modifyUrl(url: string, block: (parts: UrlParts) => UrlFormatParts | void) {
const parsed = parseUrl(url, true);
// copy over the most specific version of each
@@ -66,7 +83,7 @@ export function modifyUrl(url, block) {
slashes: parsed.slashes,
auth: parsed.auth,
hostname: parsed.hostname,
- port: parsed.port,
+ port: parsed.port ? Number(parsed.port) : undefined,
pathname: parsed.pathname,
query: parsed.query || {},
hash: parsed.hash,
diff --git a/src/ui/public/autoload/settings.js b/src/ui/public/autoload/settings.js
index 48505037dffe4..c496839dda5d2 100644
--- a/src/ui/public/autoload/settings.js
+++ b/src/ui/public/autoload/settings.js
@@ -41,7 +41,7 @@ const uiSettings = chrome.getUiSettingsClient();
setDefaultTimezone(uiSettings.get('dateFormat:tz'));
setStartDayOfWeek(uiSettings.get('dateFormat:dow'));
-uiSettings.subscribe(({ key, newValue }) => {
+uiSettings.getUpdate$().subscribe(({ key, newValue }) => {
if (key === 'dateFormat:tz') {
setDefaultTimezone(newValue);
} else if (key === 'dateFormat:dow') {
diff --git a/src/ui/public/chrome/api/__tests__/nav.js b/src/ui/public/chrome/api/__tests__/nav.js
index ac6a9d961b77f..169c9546a4a37 100644
--- a/src/ui/public/chrome/api/__tests__/nav.js
+++ b/src/ui/public/chrome/api/__tests__/nav.js
@@ -26,7 +26,9 @@ import { KibanaParsedUrl } from '../../../url/kibana_parsed_url';
const basePath = '/someBasePath';
function init(customInternals = { basePath }) {
- const chrome = {};
+ const chrome = {
+ getBasePath: () => customInternals.basePath || '',
+ };
const internals = {
nav: [],
...customInternals,
@@ -36,48 +38,6 @@ function init(customInternals = { basePath }) {
}
describe('chrome nav apis', function () {
- describe('#getBasePath()', function () {
- it('returns the basePath', function () {
- const { chrome } = init();
- expect(chrome.getBasePath()).to.be(basePath);
- });
- });
-
- describe('#addBasePath()', function () {
- it('returns undefined when nothing is passed', function () {
- const { chrome } = init();
- expect(chrome.addBasePath()).to.be(undefined);
- });
-
- it('prepends the base path when the input is a path', function () {
- const { chrome } = init();
- expect(chrome.addBasePath('/other/path')).to.be(`${basePath}/other/path`);
- });
-
- it('ignores non-path urls', function () {
- const { chrome } = init();
- expect(chrome.addBasePath('http://github.com/elastic/kibana')).to.be('http://github.com/elastic/kibana');
- });
-
- it('includes the query string', function () {
- const { chrome } = init();
- expect(chrome.addBasePath('/app/kibana?a=b')).to.be(`${basePath}/app/kibana?a=b`);
- });
- });
-
- describe('#removeBasePath', () => {
- it ('returns the given URL as-is when no basepath is set', () => {
- const basePath = '';
- const { chrome } = init({ basePath });
- expect(chrome.removeBasePath('/app/kibana?a=b')).to.be('/app/kibana?a=b');
- });
-
- it ('returns the given URL with the basepath stripped out when basepath is set', () => {
- const { chrome } = init();
- expect(chrome.removeBasePath(`${basePath}/app/kibana?a=b`)).to.be('/app/kibana?a=b');
- });
- });
-
describe('#getNavLinkById', () => {
it ('retrieves the correct nav link, given its ID', () => {
const appUrlStore = new StubBrowserStorage();
diff --git a/src/ui/public/chrome/api/base_path.test.ts b/src/ui/public/chrome/api/base_path.test.ts
new file mode 100644
index 0000000000000..e6c0c7fb21708
--- /dev/null
+++ b/src/ui/public/chrome/api/base_path.test.ts
@@ -0,0 +1,67 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { __newPlatformInit__, initChromeBasePathApi } from './base_path';
+
+function initChrome() {
+ const chrome: any = {};
+ initChromeBasePathApi(chrome);
+ return chrome;
+}
+
+const newPlatformBasePath = {
+ get: jest.fn().mockReturnValue('get'),
+ addToPath: jest.fn().mockReturnValue('addToPath'),
+ removeFromPath: jest.fn().mockReturnValue('removeFromPath'),
+};
+__newPlatformInit__(newPlatformBasePath);
+
+beforeEach(() => {
+ jest.clearAllMocks();
+});
+
+describe('#getBasePath()', () => {
+ it('proxies to newPlatformBasePath.get()', () => {
+ const chrome = initChrome();
+ expect(newPlatformBasePath.get).not.toHaveBeenCalled();
+ expect(chrome.getBasePath()).toBe('get');
+ expect(newPlatformBasePath.get).toHaveBeenCalledTimes(1);
+ expect(newPlatformBasePath.get).toHaveBeenCalledWith();
+ });
+});
+
+describe('#addBasePath()', () => {
+ it('proxies to newPlatformBasePath.addToPath(path)', () => {
+ const chrome = initChrome();
+ expect(newPlatformBasePath.addToPath).not.toHaveBeenCalled();
+ expect(chrome.addBasePath('foo/bar')).toBe('addToPath');
+ expect(newPlatformBasePath.addToPath).toHaveBeenCalledTimes(1);
+ expect(newPlatformBasePath.addToPath).toHaveBeenCalledWith('foo/bar');
+ });
+});
+
+describe('#removeBasePath', () => {
+ it('proxies to newPlatformBasePath.removeFromPath(path)', () => {
+ const chrome = initChrome();
+ expect(newPlatformBasePath.removeFromPath).not.toHaveBeenCalled();
+ expect(chrome.removeBasePath('foo/bar')).toBe('removeFromPath');
+ expect(newPlatformBasePath.removeFromPath).toHaveBeenCalledTimes(1);
+ expect(newPlatformBasePath.removeFromPath).toHaveBeenCalledWith('foo/bar');
+ });
+});
diff --git a/src/ui/ui_settings/public/send_request.js b/src/ui/public/chrome/api/base_path.ts
similarity index 54%
rename from src/ui/ui_settings/public/send_request.js
rename to src/ui/public/chrome/api/base_path.ts
index 3f138fe0373c8..49343faa8f714 100644
--- a/src/ui/ui_settings/public/send_request.js
+++ b/src/ui/public/chrome/api/base_path.ts
@@ -17,29 +17,19 @@
* under the License.
*/
-import chrome from 'ui/chrome';
-import { metadata } from 'ui/metadata';
+import { BasePathStartContract } from '../../../../core/public/base_path';
+let newPlatformBasePath: BasePathStartContract;
-export async function sendRequest({ method, path, body }) {
- chrome.loadingCount.increment();
- try {
- const response = await fetch(chrome.addBasePath(path), {
- method,
- body: JSON.stringify(body),
- headers: {
- accept: 'application/json',
- 'content-type': 'application/json',
- 'kbn-version': metadata.version,
- },
- credentials: 'same-origin'
- });
+export function __newPlatformInit__(instance: BasePathStartContract) {
+ if (newPlatformBasePath) {
+ throw new Error('ui/chrome/api/base_path is already initialized');
+ }
- if (response.status >= 300) {
- throw new Error(`Request failed with status code: ${response.status}`);
- }
+ newPlatformBasePath = instance;
+}
- return await response.json();
- } finally {
- chrome.loadingCount.decrement();
- }
+export function initChromeBasePathApi(chrome: any) {
+ chrome.getBasePath = () => newPlatformBasePath.get();
+ chrome.addBasePath = (path: string) => newPlatformBasePath.addToPath(path);
+ chrome.removeBasePath = (path: string) => newPlatformBasePath.removeFromPath(path);
}
diff --git a/src/ui/public/chrome/api/nav.js b/src/ui/public/chrome/api/nav.js
index 5163c083d3013..04aab308396c4 100644
--- a/src/ui/public/chrome/api/nav.js
+++ b/src/ui/public/chrome/api/nav.js
@@ -18,7 +18,6 @@
*/
import { remove } from 'lodash';
-import { prependPath } from '../../url/prepend_path';
import { relativeToAbsolute } from '../../url/relative_to_absolute';
import { absoluteToParsedUrl } from '../../url/absolute_to_parsed_url';
@@ -43,28 +42,6 @@ export function initChromeNavApi(chrome, internals) {
remove(internals.nav, app => app.id !== id);
};
- chrome.getBasePath = function () {
- return internals.basePath || '';
- };
-
- /**
- *
- * @param url {string} a relative url. ex: /app/kibana#/management
- * @return {string} the relative url with the basePath prepended to it. ex: rkz/app/kibana#/management
- */
- chrome.addBasePath = function (url) {
- return prependPath(url, chrome.getBasePath());
- };
-
- chrome.removeBasePath = function (url) {
- if (!internals.basePath) {
- return url;
- }
-
- const basePathRegExp = new RegExp(`^${internals.basePath}`);
- return url.replace(basePathRegExp, '');
- };
-
function lastSubUrlKey(link) {
return `lastSubUrl:${link.url}`;
}
diff --git a/src/ui/public/chrome/api/ui_settings.js b/src/ui/public/chrome/api/ui_settings.js
index e602c67f0e57f..2ca945f0b025f 100644
--- a/src/ui/public/chrome/api/ui_settings.js
+++ b/src/ui/public/chrome/api/ui_settings.js
@@ -17,18 +17,18 @@
* under the License.
*/
-import { metadata } from '../../metadata';
-import { Notifier } from '../../notify';
-import { UiSettingsClient } from '../../../ui_settings/public/ui_settings_client';
+let newPlatformUiSettingsClient;
-export function initUiSettingsApi(chrome) {
- const uiSettings = new UiSettingsClient({
- defaults: metadata.uiSettings.defaults,
- initialSettings: metadata.uiSettings.user,
- notify: new Notifier({ location: 'Config' })
- });
+export function __newPlatformInit__(instance) {
+ if (newPlatformUiSettingsClient) {
+ throw new Error('ui/chrome/api/ui_settings already initialized');
+ }
+
+ newPlatformUiSettingsClient = instance;
+}
+export function initUiSettingsApi(chrome) {
chrome.getUiSettingsClient = function () {
- return uiSettings;
+ return newPlatformUiSettingsClient;
};
}
diff --git a/src/ui/public/chrome/chrome.js b/src/ui/public/chrome/chrome.js
index 741d1eb629b62..79787e9ea14eb 100644
--- a/src/ui/public/chrome/chrome.js
+++ b/src/ui/public/chrome/chrome.js
@@ -42,6 +42,7 @@ import { initChromeXsrfApi } from './api/xsrf';
import { initUiSettingsApi } from './api/ui_settings';
import { initLoadingCountApi } from './api/loading_count';
import { initSavedObjectClient } from './api/saved_object_client';
+import { initChromeBasePathApi } from './api/base_path';
export const chrome = {};
const internals = _.defaults(
@@ -63,6 +64,7 @@ initUiSettingsApi(chrome);
initSavedObjectClient(chrome);
appsApi(chrome, internals);
initChromeXsrfApi(chrome, internals);
+initChromeBasePathApi(chrome);
initChromeNavApi(chrome, internals);
initLoadingCountApi(chrome, internals);
initAngularApi(chrome, internals);
diff --git a/src/ui/public/config/config.js b/src/ui/public/config/config.js
index a4f668529d475..d5ac7edd1a16e 100644
--- a/src/ui/public/config/config.js
+++ b/src/ui/public/config/config.js
@@ -59,7 +59,7 @@ module.service(`config`, function ($rootScope, Promise) {
//* angular specific methods *
//////////////////////////////
- const subscription = uiSettings.subscribe(({ key, newValue, oldValue }) => {
+ const subscription = uiSettings.getUpdate$().subscribe(({ key, newValue, oldValue }) => {
const emit = () => {
$rootScope.$broadcast('change:config', newValue, oldValue, key, this);
$rootScope.$broadcast(`change:config.${key}`, newValue, oldValue, key, this);
diff --git a/src/ui/public/notify/app_redirect/app_redirect.js b/src/ui/public/notify/app_redirect/app_redirect.js
index 7fd6553c11b3f..9a283fcaad9c5 100644
--- a/src/ui/public/notify/app_redirect/app_redirect.js
+++ b/src/ui/public/notify/app_redirect/app_redirect.js
@@ -17,9 +17,7 @@
* under the License.
*/
-// Use the util instead of the export from ui/url because that module is tightly coupled with
-// Angular.
-import { modifyUrl } from '../../../../utils/modify_url';
+import { modifyUrl } from '../../../../core/public/utils';
import { toastNotifications } from '../toasts';
const APP_REDIRECT_MESSAGE_PARAM = 'app_redirect_message';
diff --git a/src/ui/public/registry/field_formats.js b/src/ui/public/registry/field_formats.js
index 45c9d87bc3b23..464b8dbc5386f 100644
--- a/src/ui/public/registry/field_formats.js
+++ b/src/ui/public/registry/field_formats.js
@@ -39,7 +39,7 @@ class FieldFormatRegistry extends IndexedArray {
init() {
this.parseDefaultTypeMap(this._uiSettings.get('format:defaultTypeMap'));
- this._uiSettings.subscribe(({ key, newValue }) => {
+ this._uiSettings.getUpdate$().subscribe(({ key, newValue }) => {
if (key === 'format:defaultTypeMap') {
this.parseDefaultTypeMap(newValue);
}
diff --git a/src/ui/public/styles/disable_animations/disable_animations.js b/src/ui/public/styles/disable_animations/disable_animations.js
index 067bbd040df33..c70aaa2165691 100644
--- a/src/ui/public/styles/disable_animations/disable_animations.js
+++ b/src/ui/public/styles/disable_animations/disable_animations.js
@@ -38,7 +38,7 @@ function updateStyleSheet() {
}
updateStyleSheet();
-uiSettings.subscribe(({ key }) => {
+uiSettings.getUpdate$().subscribe(({ key }) => {
if (key === 'accessibility:disableAnimations') {
updateStyleSheet();
}
diff --git a/src/ui/public/test_harness/test_harness.js b/src/ui/public/test_harness/test_harness.js
index 3b5144145de69..2c6203ab9e2eb 100644
--- a/src/ui/public/test_harness/test_harness.js
+++ b/src/ui/public/test_harness/test_harness.js
@@ -24,7 +24,7 @@ import { parse as parseUrl } from 'url';
import sinon from 'sinon';
import { Notifier } from '../notify';
import { metadata } from '../metadata';
-import { UiSettingsClient } from '../../ui_settings/public/ui_settings_client';
+import { UiSettingsClient } from '../../../core/public/ui_settings';
import './test_harness.less';
import 'ng_mock';
@@ -46,16 +46,25 @@ before(() => {
sinon.useFakeXMLHttpRequest();
});
-let stubUiSettings = new UiSettingsClient({
- defaults: metadata.uiSettings.defaults,
- initialSettings: {},
- notify: new Notifier({ location: 'Config' }),
- api: {
- batchSet() {
- return { settings: stubUiSettings.getAll() };
- }
+let stubUiSettings;
+function createStubUiSettings() {
+ if (stubUiSettings) {
+ stubUiSettings.stop();
}
-});
+
+ stubUiSettings = new UiSettingsClient({
+ api: {
+ async batchSet() {
+ return { settings: stubUiSettings.getAll() };
+ }
+ },
+ onUpdateError: () => {},
+ defaults: metadata.uiSettings.defaults,
+ initialSettings: {},
+ });
+}
+
+createStubUiSettings();
sinon.stub(chrome, 'getUiSettingsClient').callsFake(() => stubUiSettings);
beforeEach(function () {
@@ -68,16 +77,7 @@ beforeEach(function () {
});
afterEach(function () {
- stubUiSettings = new UiSettingsClient({
- defaults: metadata.uiSettings.defaults,
- initialSettings: {},
- notify: new Notifier({ location: 'Config' }),
- api: {
- batchSet() {
- return { settings: stubUiSettings.getAll() };
- }
- }
- });
+ createStubUiSettings();
});
// Kick off mocha, called at the end of test entry files
diff --git a/src/ui/public/url/index.js b/src/ui/public/url/index.js
index 1e0f1f62b9284..b95c477d8916c 100644
--- a/src/ui/public/url/index.js
+++ b/src/ui/public/url/index.js
@@ -19,4 +19,4 @@
export { KbnUrlProvider } from './url';
export { RedirectWhenMissingProvider } from './redirect_when_missing';
-export { modifyUrl } from './modify_url';
+export { modifyUrl } from '../../../core/public/utils';
diff --git a/src/ui/public/url/kibana_parsed_url.js b/src/ui/public/url/kibana_parsed_url.js
index 683af3b4bb2cc..ef84440ef7af8 100644
--- a/src/ui/public/url/kibana_parsed_url.js
+++ b/src/ui/public/url/kibana_parsed_url.js
@@ -20,7 +20,7 @@
import { parse } from 'url';
import { prependPath } from './prepend_path';
-import { modifyUrl } from '../../../utils';
+import { modifyUrl } from '../../../core/public/utils';
/**
* Represents the pieces that make up a url in Kibana, offering some helpful functionality for
diff --git a/src/ui/ui_render/ui_render_mixin.js b/src/ui/ui_render/ui_render_mixin.js
index 629affb6d0ff6..5929f5bc0e73c 100644
--- a/src/ui/ui_render/ui_render_mixin.js
+++ b/src/ui/ui_render/ui_render_mixin.js
@@ -149,6 +149,7 @@ export function uiRenderMixin(kbnServer, server, config) {
injectedMetadata: {
version: kbnServer.version,
buildNumber: config.get('pkg.buildNum'),
+ basePath,
legacyMetadata: await getLegacyKibanaPayload({
app,
translations,
diff --git a/src/ui/ui_settings/public/ui_settings_api.js b/src/ui/ui_settings/public/ui_settings_api.js
deleted file mode 100644
index efa667d30af2d..0000000000000
--- a/src/ui/ui_settings/public/ui_settings_api.js
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { sendRequest } from './send_request';
-
-const NOOP_CHANGES = {
- values: {},
- callback: () => {},
-};
-
-export function createUiSettingsApi() {
- let pendingChanges = null;
- let sendInProgress = false;
-
- async function flushPendingChanges() {
- if (!pendingChanges) {
- return;
- }
-
- if (sendInProgress) {
- return;
- }
-
- const changes = pendingChanges;
- pendingChanges = null;
-
- try {
- sendInProgress = true;
- changes.callback(null, await sendRequest({
- method: 'POST',
- path: '/api/kibana/settings',
- body: {
- changes: changes.values
- },
- }));
- } catch (error) {
- changes.callback(error);
- } finally {
- sendInProgress = false;
- flushPendingChanges();
- }
- }
-
- return new class Api {
- batchSet(key, value) {
- return new Promise((resolve, reject) => {
- const prev = pendingChanges || NOOP_CHANGES;
-
- pendingChanges = {
- values: {
- ...prev.values,
- [key]: value,
- },
-
- callback(error, resp) {
- prev.callback(error, resp);
-
- if (error) {
- reject(error);
- } else {
- resolve(resp);
- }
- },
- };
-
- flushPendingChanges();
- });
- }
- };
-}
diff --git a/src/ui/ui_settings/public/ui_settings_client.js b/src/ui/ui_settings/public/ui_settings_client.js
deleted file mode 100644
index 60505065143d7..0000000000000
--- a/src/ui/ui_settings/public/ui_settings_client.js
+++ /dev/null
@@ -1,207 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { cloneDeep, defaultsDeep } from 'lodash';
-import { createUiSettingsApi } from './ui_settings_api';
-
-export class UiSettingsClient {
- constructor(options) {
- const {
- defaults,
- initialSettings,
- notify,
- api = createUiSettingsApi(),
- } = options;
-
- this._defaults = cloneDeep(defaults);
- this._cache = defaultsDeep({}, this._defaults, cloneDeep(initialSettings));
- this._api = api;
- this._notify = notify;
- this._updateObservers = new Set();
- }
-
- getAll() {
- return cloneDeep(this._cache);
- }
-
- get(key, defaultValue) {
- if (!this.isDeclared(key)) {
- // the key is not a declared setting
- // pass through the caller's desired default value
- // without persisting anything in the config document
- if (defaultValue !== undefined) {
- return defaultValue;
- }
-
- throw new Error(
- `Unexpected \`config.get("${key}")\` call on unrecognized configuration setting "${key}".
-Setting an initial value via \`config.set("${key}", value)\` before attempting to retrieve
-any custom setting value for "${key}" may fix this issue.
-You can use \`config.get("${key}", defaultValue)\`, which will just return
-\`defaultValue\` when the key is unrecognized.`
- );
- }
-
- const {
- userValue,
- value: definedDefault,
- type
- } = this._cache[key];
-
- let currentValue;
-
- if (this.isDefault(key)) {
- // honor the second parameter if it was passed
- currentValue = defaultValue === undefined ? definedDefault : defaultValue;
- } else {
- currentValue = userValue;
- }
-
- if (type === 'json') {
- return JSON.parse(currentValue);
- } else if (type === 'number') {
- return parseFloat(currentValue);
- }
-
- return currentValue;
- }
-
- async set(key, val) {
- return await this._update(key, val);
- }
-
- async remove(key) {
- return await this._update(key, null);
- }
-
- isDeclared(key) {
- return Boolean(key in this._cache);
- }
-
- isDefault(key) {
- return !this.isDeclared(key) || this._cache[key].userValue == null;
- }
-
- isCustom(key) {
- return this.isDeclared(key) && !('value' in this._cache[key]);
- }
-
- isOverridden(key) {
- return this.isDeclared(key) && Boolean(this._cache[key].isOverridden);
- }
-
- assertUpdateAllowed(key) {
- if (this.isOverridden(key)) {
- throw new Error(`Unable to update "${key}" because its value is overridden by the Kibana server`);
- }
- }
-
- overrideLocalDefault(key, newDefault) {
- // capture the previous value
- const prevDefault = this._defaults[key]
- ? this._defaults[key].value
- : undefined;
-
- // update defaults map
- this._defaults[key] = {
- ...this._defaults[key] || {},
- value: newDefault
- };
-
- // update cached default value
- this._cache[key] = {
- ...this._cache[key] || {},
- value: newDefault
- };
-
- // don't broadcast change if userValue was already overriding the default
- if (this._cache[key].userValue == null) {
- this._broadcastUpdate(key, newDefault, prevDefault);
- }
- }
-
- subscribe(observer) {
- this._updateObservers.add(observer);
-
- return {
- unsubscribe: () => {
- this._updateObservers.delete(observer);
- }
- };
- }
-
- async _update(key, value) {
- this.assertUpdateAllowed(key);
-
- const declared = this.isDeclared(key);
- const defaults = this._defaults;
-
- const oldVal = declared ? this._cache[key].userValue : undefined;
- const newVal = key in defaults && defaults[key].defaultValue === value
- ? null
- : value;
-
- const unchanged = oldVal === newVal;
- if (unchanged) {
- return true;
- }
-
- const initialVal = declared ? this.get(key) : undefined;
- this._setLocally(key, newVal);
-
- try {
- const { settings } = await this._api.batchSet(key, newVal);
- this._cache = defaultsDeep({}, defaults, settings);
- return true;
- } catch (error) {
- this._setLocally(key, initialVal);
- this._notify.error(error);
- return false;
- }
- }
-
- _setLocally(key, newValue) {
- this.assertUpdateAllowed(key);
-
- if (!this.isDeclared(key)) {
- this._cache[key] = {};
- }
-
- const oldValue = this.get(key);
-
- if (newValue === null) {
- delete this._cache[key].userValue;
- } else {
- const { type } = this._cache[key];
- if (type === 'json' && typeof newValue !== 'string') {
- this._cache[key].userValue = JSON.stringify(newValue);
- } else {
- this._cache[key].userValue = newValue;
- }
- }
-
- this._broadcastUpdate(key, newValue, oldValue);
- }
-
- _broadcastUpdate(key, newValue, oldValue) {
- for (const observer of this._updateObservers) {
- observer({ key, newValue, oldValue });
- }
- }
-}
diff --git a/src/utils/__tests__/modify_url.js b/src/utils/__tests__/modify_url.js
deleted file mode 100644
index 9aa7ba1fb9960..0000000000000
--- a/src/utils/__tests__/modify_url.js
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import expect from 'expect.js';
-
-import { modifyUrl } from '../modify_url';
-
-describe('modifyUrl()', () => {
- it('throws an error with invalid input', () => {
- expect(() => modifyUrl(1, () => {})).to.throwError();
- expect(() => modifyUrl(undefined, () => {})).to.throwError();
- expect(() => modifyUrl('http://localhost')).to.throwError(); // no block
- });
-
- it('supports returning a new url spec', () => {
- expect(modifyUrl('http://localhost', () => ({}))).to.eql('');
- });
-
- it('supports modifying the passed object', () => {
- expect(modifyUrl('http://localhost', parsed => {
- parsed.port = 9999;
- parsed.auth = 'foo:bar';
- })).to.eql('http://foo:bar@localhost:9999/');
- });
-
- it('supports changing pathname', () => {
- expect(modifyUrl('http://localhost/some/path', parsed => {
- parsed.pathname += '/subpath';
- })).to.eql('http://localhost/some/path/subpath');
- });
-
- it('supports changing port', () => {
- expect(modifyUrl('http://localhost:5601', parsed => {
- parsed.port = (parsed.port * 1) + 1;
- })).to.eql('http://localhost:5602/');
- });
-
- it('supports changing protocol', () => {
- expect(modifyUrl('http://localhost', parsed => {
- parsed.protocol = 'mail';
- parsed.slashes = false;
- parsed.pathname = null;
- })).to.eql('mail:localhost');
- });
-});
diff --git a/src/utils/index.js b/src/utils/index.js
index f79690b4392ea..cef2c5a61c511 100644
--- a/src/utils/index.js
+++ b/src/utils/index.js
@@ -24,7 +24,6 @@ export { fromRoot } from './from_root';
export { pkg } from './package_json';
export { unset } from './unset';
export { encodeQueryComponent } from './encode_query_component';
-export { modifyUrl } from './modify_url';
export { getFlattenedObject } from './get_flattened_object';
export { watchStdioForLine } from './watch_stdio_for_line';
export { IS_KIBANA_DISTRIBUTABLE } from './artifact_type';
diff --git a/test/functional/services/remote/interceptors.js b/test/functional/services/remote/interceptors.js
index 2b2b5792eac9f..936c8f50bef3b 100644
--- a/test/functional/services/remote/interceptors.js
+++ b/test/functional/services/remote/interceptors.js
@@ -17,7 +17,7 @@
* under the License.
*/
-import { modifyUrl } from '../../../../src/utils';
+import { modifyUrl } from '../../../../src/core/utils';
export const createRemoteInterceptors = remote => ({
// inject _t=Date query param on navigation
From 412c430c2e19e054358805065f1c79ae8bede4c4 Mon Sep 17 00:00:00 2001
From: Court Ewing
Date: Sat, 8 Sep 2018 08:15:03 -0400
Subject: [PATCH 56/68] api/export: calculate content length (#22154) (#22847)
---
src/core_plugins/kibana/server/routes/api/export/index.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/core_plugins/kibana/server/routes/api/export/index.js b/src/core_plugins/kibana/server/routes/api/export/index.js
index 832369c1f0f46..34f45e9a6b2da 100644
--- a/src/core_plugins/kibana/server/routes/api/export/index.js
+++ b/src/core_plugins/kibana/server/routes/api/export/index.js
@@ -45,7 +45,7 @@ export function exportApi(server) {
reply(json)
.header('Content-Disposition', `attachment; filename="${filename}"`)
.header('Content-Type', 'application/json')
- .header('Content-Length', json.length);
+ .header('Content-Length', Buffer.byteLength(json, 'utf8'));
})
.catch(err => reply(Boom.boomify(err, { statusCode: 400 })));
}
From 6efab016185a8732e6fc0e85ea8dc86fd829ead3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?=
Date: Sat, 8 Sep 2018 14:49:52 +0200
Subject: [PATCH 57/68] [APM] Fix ML links (#22820)
---
.../DynamicBaseline/Flyout.js | 26 ++++++++---
.../app/TransactionOverview/view.js | 16 +++----
.../__test__/__snapshots__/url.test.js.snap | 21 +++++++++
.../apm/public/utils/__test__/url.test.js | 44 +++++++++++++------
x-pack/plugins/apm/public/utils/url.js | 23 ++++++++--
5 files changed, 96 insertions(+), 34 deletions(-)
diff --git a/x-pack/plugins/apm/public/components/app/TransactionOverview/DynamicBaseline/Flyout.js b/x-pack/plugins/apm/public/components/app/TransactionOverview/DynamicBaseline/Flyout.js
index 1231e60e5bd80..51399b169f311 100644
--- a/x-pack/plugins/apm/public/components/app/TransactionOverview/DynamicBaseline/Flyout.js
+++ b/x-pack/plugins/apm/public/components/app/TransactionOverview/DynamicBaseline/Flyout.js
@@ -21,7 +21,7 @@ import {
EuiSpacer,
EuiBetaBadge
} from '@elastic/eui';
-import { getMlJobUrl, KibanaLink } from '../../../../utils/url';
+import { KibanaLink, ViewMLJob } from '../../../../utils/url';
export default class DynamicBaselineFlyout extends Component {
state = {
@@ -65,9 +65,13 @@ export default class DynamicBaselineFlyout extends Component {
There's already a job running for anomaly detection on{' '}
{serviceName} ({transactionType}
).{' '}
-
+
View existing job
-
+
)
});
@@ -82,9 +86,13 @@ export default class DynamicBaselineFlyout extends Component {
The analysis is now running for {serviceName} ({transactionType}
). It might take a while before results are added to the response
times graph.{' '}
-
+
View job
-
+
)
});
@@ -125,9 +133,13 @@ export default class DynamicBaselineFlyout extends Component {
There is currently a job running for {serviceName} (
{transactionType}
).{' '}
-
+
View existing job
-
+
diff --git a/x-pack/plugins/apm/public/components/app/TransactionOverview/view.js b/x-pack/plugins/apm/public/components/app/TransactionOverview/view.js
index b250afe837965..470d1988113bf 100644
--- a/x-pack/plugins/apm/public/components/app/TransactionOverview/view.js
+++ b/x-pack/plugins/apm/public/components/app/TransactionOverview/view.js
@@ -13,7 +13,7 @@ import { get } from 'lodash';
import { HeaderContainer, HeaderMedium } from '../../shared/UIComponents';
import TabNavigation from '../../shared/TabNavigation';
import Charts from '../../shared/charts/TransactionCharts';
-import { getMlJobUrl, KibanaLink } from '../../../utils/url';
+import { ViewMLJob } from '../../../utils/url';
import List from './List';
import { units, px, fontSizes } from '../../../style/variables';
import { OverviewChartsRequest } from '../../../store/reactReduxRequest/overviewCharts';
@@ -75,15 +75,13 @@ class TransactionOverview extends Component {
Machine Learning:{' '}
-
- View Job
-
+ View job
+
) : null;
diff --git a/x-pack/plugins/apm/public/utils/__test__/__snapshots__/url.test.js.snap b/x-pack/plugins/apm/public/utils/__test__/__snapshots__/url.test.js.snap
index 2252f7f40ce6b..6430ae0cc5804 100644
--- a/x-pack/plugins/apm/public/utils/__test__/__snapshots__/url.test.js.snap
+++ b/x-pack/plugins/apm/public/utils/__test__/__snapshots__/url.test.js.snap
@@ -18,3 +18,24 @@ exports[`RelativeLinkComponent should render correct markup 1`] = `
Go to Discover
`;
+
+exports[`ViewMLJob should render component 1`] = `
+
+ View Job
+
+`;
diff --git a/x-pack/plugins/apm/public/utils/__test__/url.test.js b/x-pack/plugins/apm/public/utils/__test__/url.test.js
index 28a8a6bbd469f..6980d2c3b1620 100644
--- a/x-pack/plugins/apm/public/utils/__test__/url.test.js
+++ b/x-pack/plugins/apm/public/utils/__test__/url.test.js
@@ -6,9 +6,8 @@
import React from 'react';
import { Router } from 'react-router-dom';
-import { mount } from 'enzyme';
+import { mount, shallow } from 'enzyme';
import createHistory from 'history/createMemoryHistory';
-
import {
toQuery,
fromQuery,
@@ -16,7 +15,7 @@ import {
RelativeLinkComponent,
encodeKibanaSearchParams,
decodeKibanaSearchParams,
- getMlJobUrl
+ ViewMLJob
} from '../url';
import { toJson } from '../testHelpers';
@@ -221,22 +220,39 @@ describe('KibanaLinkComponent', () => {
});
});
-describe('getMlJobUrl', () => {
- it('should have correct url', () => {
- const serviceName = 'myServiceName';
- const transactionType = 'myTransactionType';
+describe('ViewMLJob', () => {
+ it('should render component', () => {
const location = { search: '' };
- expect(getMlJobUrl(serviceName, transactionType, location)).toBe(
- '/app/ml#/timeseriesexplorer/?_g=(ml:(jobIds:!(myServiceName-myTransactionType-high_mean_response_time)))&_a=!n'
+ const wrapper = shallow(
+
);
+
+ expect(toJson(wrapper)).toMatchSnapshot();
});
- it('should not contain basePath', () => {
- const serviceName = 'myServiceName';
- const transactionType = 'myTransactionType';
+ it('should have correct path props', () => {
const location = { search: '' };
- expect(getMlJobUrl(serviceName, transactionType, location)).toBe(
- '/app/ml#/timeseriesexplorer/?_g=(ml:(jobIds:!(myServiceName-myTransactionType-high_mean_response_time)))&_a=!n'
+ const wrapper = shallow(
+
);
+
+ expect(wrapper.prop('pathname')).toBe('/app/ml');
+ expect(wrapper.prop('hash')).toBe('/timeseriesexplorer');
+ expect(wrapper.prop('query')).toEqual({
+ _a: null,
+ _g: {
+ ml: {
+ jobIds: ['myServiceName-myTransactionType-high_mean_response_time']
+ }
+ }
+ });
});
});
diff --git a/x-pack/plugins/apm/public/utils/url.js b/x-pack/plugins/apm/public/utils/url.js
index 5a9f21c3b2560..8b1ab93bffe60 100644
--- a/x-pack/plugins/apm/public/utils/url.js
+++ b/x-pack/plugins/apm/public/utils/url.js
@@ -16,9 +16,17 @@ import { EuiLink } from '@elastic/eui';
import createHistory from 'history/createHashHistory';
import chrome from 'ui/chrome';
-export function getMlJobUrl(serviceName, transactionType, location) {
+export function ViewMLJob({
+ serviceName,
+ transactionType,
+ location,
+ children = 'View Job'
+}) {
const { _g, _a } = decodeKibanaSearchParams(location.search);
- const nextSearch = encodeKibanaSearchParams({
+
+ const pathname = '/app/ml';
+ const hash = '/timeseriesexplorer';
+ const query = {
_g: {
..._g,
ml: {
@@ -26,9 +34,16 @@ export function getMlJobUrl(serviceName, transactionType, location) {
}
},
_a
- });
+ };
- return `/app/ml#/timeseriesexplorer/?${nextSearch}`;
+ return (
+
+ );
}
export function toQuery(search) {
From 012c601ccf166cdc906eedf15d94376ad1b6b636 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?=
Date: Sat, 8 Sep 2018 16:53:56 +0200
Subject: [PATCH 58/68] [APM] Minor cleanup (#22827)
---
x-pack/plugins/apm/common/constants.js | 2 +
.../__snapshots__/DetailView.test.js.snap | 312 +++++++++---------
.../app/ErrorGroupDetails/DetailView/index.js | 24 +-
.../__test__/__snapshots__/List.test.js.snap | 8 +-
.../Watcher/WatcherFlyOut.js | 12 +-
.../StickyTransactionProperties.js | 57 ++++
.../TransactionDetails/Transaction/view.js | 34 +-
.../components/app/TransactionDetails/view.js | 4 +-
.../DynamicBaseline/Flyout.js | 8 +-
.../TransactionOverview.test.js.snap | 2 +-
.../app/TransactionOverview/index.js | 2 +-
.../app/TransactionOverview/view.js | 8 +-
.../components/shared/ContextProperties.js | 111 -------
.../StickyProperties/StickyProperties.test.js | 52 +++
.../StickyProperties.test.js.snap | 168 ++++++++++
.../shared/StickyProperties/index.js | 116 +++++++
.../public/services/__test__/callApi.test.js | 70 ++--
.../apm/public/services/rest/callApi.js | 14 +-
...sCharts.js => transactionDetailsCharts.js} | 8 +-
...Charts.js => transactionOverviewCharts.js} | 8 +-
.../plugins/apm/public/utils/testHelpers.js | 4 +-
21 files changed, 649 insertions(+), 375 deletions(-)
create mode 100644 x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/StickyTransactionProperties.js
delete mode 100644 x-pack/plugins/apm/public/components/shared/ContextProperties.js
create mode 100644 x-pack/plugins/apm/public/components/shared/StickyProperties/StickyProperties.test.js
create mode 100644 x-pack/plugins/apm/public/components/shared/StickyProperties/__snapshots__/StickyProperties.test.js.snap
create mode 100644 x-pack/plugins/apm/public/components/shared/StickyProperties/index.js
rename x-pack/plugins/apm/public/store/reactReduxRequest/{detailsCharts.js => transactionDetailsCharts.js} (86%)
rename x-pack/plugins/apm/public/store/reactReduxRequest/{overviewCharts.js => transactionOverviewCharts.js} (86%)
diff --git a/x-pack/plugins/apm/common/constants.js b/x-pack/plugins/apm/common/constants.js
index 7bb25a4a2813a..e0627338bbc2f 100644
--- a/x-pack/plugins/apm/common/constants.js
+++ b/x-pack/plugins/apm/common/constants.js
@@ -31,4 +31,6 @@ export const ERROR_LOG_MESSAGE = 'error.log.message';
export const ERROR_EXC_MESSAGE = 'error.exception.message';
export const ERROR_EXC_HANDLED = 'error.exception.handled';
+export const REQUEST_URL_FULL = 'context.request.url.full';
+
export const USER_ID = 'context.user.id';
diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/__test__/__snapshots__/DetailView.test.js.snap b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/__test__/__snapshots__/DetailView.test.js.snap
index 1c47d1665a5da..8b76e15a9da9f 100644
--- a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/__test__/__snapshots__/DetailView.test.js.snap
+++ b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/__test__/__snapshots__/DetailView.test.js.snap
@@ -24,12 +24,6 @@ exports[`DetailView should render with data 1`] = `
margin-bottom: 16px;
}
-.c8 {
- width: 33%;
- margin-bottom: 16px;
- width: 66%;
-}
-
.c5 {
margin-bottom: 8px;
font-size: 12px;
@@ -40,22 +34,22 @@ exports[`DetailView should render with data 1`] = `
cursor: help;
}
+.c7 {
+ color: #999999;
+}
+
.c6 {
display: inline-block;
line-height: 16px;
}
-.c7 {
- color: #999999;
-}
-
-.c9 {
+.c8 {
display: inline-block;
+ line-height: 16px;
max-width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
- line-height: 16px;
}
.c2 {
@@ -64,12 +58,12 @@ exports[`DetailView should render with data 1`] = `
margin: 0;
}
-.c14 {
+.c13 {
margin: 24px 0;
font-size: 14px;
}
-.c11 {
+.c10 {
display: inline-block;
font-size: 16px;
padding: 16px 20px;
@@ -82,7 +76,7 @@ exports[`DetailView should render with data 1`] = `
border-bottom: 2px solid #006E8A;
}
-.c12 {
+.c11 {
display: inline-block;
font-size: 16px;
padding: 16px 20px;
@@ -94,12 +88,12 @@ exports[`DetailView should render with data 1`] = `
user-select: none;
}
-.c19 {
+.c18 {
position: relative;
border-radius: 0 0 5px 5px;
}
-.c20 {
+.c19 {
position: absolute;
width: 100%;
height: 18px;
@@ -108,7 +102,7 @@ exports[`DetailView should render with data 1`] = `
background-color: #FCF2E6;
}
-.c21 {
+.c20 {
position: absolute;
top: 0;
left: 0;
@@ -116,7 +110,7 @@ exports[`DetailView should render with data 1`] = `
background: #f5f5f5;
}
-.c22 {
+.c21 {
position: relative;
min-width: 42px;
padding-left: 8px;
@@ -127,11 +121,11 @@ exports[`DetailView should render with data 1`] = `
border-right: 1px solid #d9d9d9;
}
-.c22:last-of-type {
+.c21:last-of-type {
border-radius: 0 0 0 5px;
}
-.c23 {
+.c22 {
position: relative;
min-width: 42px;
padding-left: 8px;
@@ -143,22 +137,22 @@ exports[`DetailView should render with data 1`] = `
background-color: #FCF2E6;
}
-.c23:last-of-type {
+.c22:last-of-type {
border-radius: 0 0 0 5px;
}
-.c24 {
+.c23 {
overflow: auto;
margin: 0 0 0 42px;
padding: 0;
background-color: #ffffff;
}
-.c24:last-of-type {
+.c23:last-of-type {
border-radius: 0 0 5px 0;
}
-.c25 {
+.c24 {
margin: 0;
color: inherit;
background: inherit;
@@ -169,7 +163,7 @@ exports[`DetailView should render with data 1`] = `
line-height: 18px;
}
-.c26 {
+.c25 {
position: relative;
padding: 0;
margin: 0;
@@ -177,18 +171,18 @@ exports[`DetailView should render with data 1`] = `
z-index: 2;
}
-.c16 {
+.c15 {
color: #999999;
padding: 8px;
border-bottom: 1px solid #d9d9d9;
border-radius: 5px 5px 0 0;
}
-.c18 {
+.c17 {
font-weight: bold;
}
-.c15 {
+.c14 {
margin: 0 0 24px 0;
position: relative;
font-family: "SFMono-Regular",Consolas,"Liberation Mono",Menlo,Courier,monospace;
@@ -197,11 +191,11 @@ exports[`DetailView should render with data 1`] = `
background: #f5f5f5;
}
-.c15 .c17 {
+.c14 .c16 {
color: #000000;
}
-.c27 {
+.c26 {
margin: 0 0 24px 0;
-webkit-user-select: none;
-moz-user-select: none;
@@ -233,12 +227,12 @@ exports[`DetailView should render with data 1`] = `
margin-bottom: 16px;
}
-.c10 {
+.c9 {
padding: 0 24px;
border-bottom: 1px solid #d9d9d9;
}
-.c13 {
+.c12 {
padding: 24px 24px 0;
}
@@ -339,19 +333,19 @@ exports[`DetailView should render with data 1`] = `
- 1515508740
+ 1337 minutes ago (mocking 1515508740)
(
- 1515508740
+ 1st of January (mocking 1515508740)
)
@@ -435,66 +429,66 @@ exports[`DetailView should render with data 1`] = `
Exception stacktrace
Request
Response
System
Service
Process
User
Tags
@@ -502,146 +496,146 @@ exports[`DetailView should render with data 1`] = `
Stacktraces
server/coffee.js
in
<anonymous>
at
line
9
2
.
3
.
4
.
5
.
6
.
7
.
8
.
9
.
10
.
11
.
12
.
13
.
14
.
15
.
16
.
app.get(
res.send(
}
res.send(
}
})
app.get(
server.js
in
<anonymous>
at
line
27
20
.
21
.
22
.
23
.
24
.
25
.
26
.
27
.
28
.
29
.
30
.
31
.
32
.
33
.
34
.
app.use(
app.use(express.static(
app.use(
apm.setTag(
apm.setTag(
apm.setTag(
apm.setTag(
next()
})
app.use(
app.use(
app.get(
res.sendFile(path.resolve(__dirname,
})
@@ -1798,7 +1792,7 @@ exports[`DetailView should render with data 1`] = `
-
+
{tabs.map(key => {
diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/__snapshots__/List.test.js.snap b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/__snapshots__/List.test.js.snap
index 56a2847ca4824..ee53c815949ff 100644
--- a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/__snapshots__/List.test.js.snap
+++ b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/__snapshots__/List.test.js.snap
@@ -569,7 +569,7 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = `
- 1515578797
+ 1337 minutes ago (mocking 1515578797)
@@ -650,7 +650,7 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = `
- 1515578797
+ 1337 minutes ago (mocking 1515578797)
@@ -731,7 +731,7 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = `
- 1515578796
+ 1337 minutes ago (mocking 1515578796)
@@ -812,7 +812,7 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = `
- 1515578773
+ 1337 minutes ago (mocking 1515578773)
diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/Watcher/WatcherFlyOut.js b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/Watcher/WatcherFlyOut.js
index 6ede807a49add..78199377a8d23 100644
--- a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/Watcher/WatcherFlyOut.js
+++ b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/Watcher/WatcherFlyOut.js
@@ -199,17 +199,17 @@ export default class WatcherFlyout extends Component {
};
render() {
- const userTimezoneSetting = getUserTimezone();
+ if (!this.props.isOpen) {
+ return null;
+ }
+ const userTimezoneSetting = getUserTimezone();
const dailyTime = this.state.daily;
-
const inputTime = `${dailyTime}Z`; // Add tz to make into UTC
const inputFormat = 'HH:mmZ'; // Parse as 24 hour w. tz
-
const dailyTimeFormatted = moment(inputTime, inputFormat)
.tz(userTimezoneSetting)
.format('HH:mm'); // Format as 24h
-
const dailyTime12HourFormatted = moment(inputTime, inputFormat)
.tz(userTimezoneSetting)
.format('hh:mm A (z)'); // Format as 12h w. tz
@@ -397,7 +397,7 @@ export default class WatcherFlyout extends Component {
);
- const flyout = (
+ return (
@@ -421,8 +421,6 @@ export default class WatcherFlyout extends Component {
);
-
- return {this.props.isOpen && flyout} ;
}
}
diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/StickyTransactionProperties.js b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/StickyTransactionProperties.js
new file mode 100644
index 0000000000000..55cedaedec4f8
--- /dev/null
+++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/StickyTransactionProperties.js
@@ -0,0 +1,57 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import PropTypes from 'prop-types';
+import { get } from 'lodash';
+import { StickyProperties } from '../../../shared/StickyProperties';
+import {
+ TRANSACTION_DURATION,
+ TRANSACTION_RESULT,
+ USER_ID,
+ REQUEST_URL_FULL
+} from '../../../../../common/constants';
+import { asTime } from '../../../../utils/formatters';
+
+export default function StickyTransactionProperties({ transaction }) {
+ const timestamp = get(transaction, '@timestamp');
+ const url = get(transaction, REQUEST_URL_FULL, 'N/A');
+ const duration = get(transaction, TRANSACTION_DURATION);
+ const stickyProperties = [
+ {
+ label: 'Timestamp',
+ fieldName: '@timestamp',
+ val: timestamp
+ },
+ {
+ fieldName: REQUEST_URL_FULL,
+ label: 'URL',
+ val: url,
+ truncated: true
+ },
+ {
+ label: 'Duration',
+ fieldName: TRANSACTION_DURATION,
+ val: duration ? asTime(duration) : 'N/A'
+ },
+ {
+ label: 'Result',
+ fieldName: TRANSACTION_RESULT,
+ val: get(transaction, TRANSACTION_RESULT, 'N/A')
+ },
+ {
+ label: 'User ID',
+ fieldName: USER_ID,
+ val: get(transaction, USER_ID, 'N/A')
+ }
+ ];
+
+ return ;
+}
+
+StickyTransactionProperties.propTypes = {
+ transaction: PropTypes.object.isRequired
+};
diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/view.js b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/view.js
index ac6d9e4fe86a7..0844cc054cad8 100644
--- a/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/view.js
+++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/view.js
@@ -17,7 +17,7 @@ import {
import { Tab, HeaderMedium } from '../../../shared/UIComponents';
import { isEmpty, capitalize, get, sortBy, last } from 'lodash';
-import { ContextProperties } from '../../../shared/ContextProperties';
+import StickyTransactionProperties from './StickyTransactionProperties';
import {
PropertiesTable,
getPropertyTabNames
@@ -28,12 +28,9 @@ import {
TRANSACTION_ID,
PROCESSOR_EVENT,
SERVICE_AGENT_NAME,
- TRANSACTION_DURATION,
- TRANSACTION_RESULT,
- USER_ID
+ TRANSACTION_DURATION
} from '../../../../../common/constants';
import { fromQuery, toQuery, history } from '../../../../utils/url';
-import { asTime } from '../../../../utils/formatters';
import EmptyMessage from '../../../shared/EmptyMessage';
const Container = styled.div`
@@ -129,27 +126,6 @@ function Transaction({ transaction, location, urlParams }) {
);
}
- const timestamp = get(transaction, '@timestamp');
- const url = get(transaction, 'context.request.url.full', 'N/A');
- const duration = get(transaction, TRANSACTION_DURATION);
- const stickyProperties = [
- {
- label: 'Duration',
- fieldName: TRANSACTION_DURATION,
- val: duration ? asTime(duration) : 'N/A'
- },
- {
- label: 'Result',
- fieldName: TRANSACTION_RESULT,
- val: get(transaction, TRANSACTION_RESULT, 'N/A')
- },
- {
- label: 'User ID',
- fieldName: USER_ID,
- val: get(transaction, USER_ID, 'N/A')
- }
- ];
-
const agentName = get(transaction, SERVICE_AGENT_NAME);
const tabs = getTabs(transaction);
const currentTab = getCurrentTab(tabs, urlParams.detailTab);
@@ -181,11 +157,7 @@ function Transaction({ transaction, location, urlParams }) {
-
+
{[DEFAULT_TAB, ...tabs].map(key => {
diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/view.js b/x-pack/plugins/apm/public/components/app/TransactionDetails/view.js
index d8fa4ae9a3c11..d1c48d31ba5cf 100644
--- a/x-pack/plugins/apm/public/components/app/TransactionDetails/view.js
+++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/view.js
@@ -9,7 +9,7 @@ import { EuiSpacer } from '@elastic/eui';
import { HeaderLarge } from '../../shared/UIComponents';
import Transaction from './Transaction';
import Distribution from './Distribution';
-import { DetailsChartsRequest } from '../../../store/reactReduxRequest/detailsCharts';
+import { TransactionDetailsChartsRequest } from '../../../store/reactReduxRequest/transactionDetailsCharts';
import Charts from '../../shared/charts/TransactionCharts';
import { TransactionDistributionRequest } from '../../../store/reactReduxRequest/transactionDistribution';
import { TransactionDetailsRequest } from '../../../store/reactReduxRequest/transactionDetails';
@@ -24,7 +24,7 @@ function TransactionDetails({ urlParams, location }) {
- (
diff --git a/x-pack/plugins/apm/public/components/app/TransactionOverview/DynamicBaseline/Flyout.js b/x-pack/plugins/apm/public/components/app/TransactionOverview/DynamicBaseline/Flyout.js
index 51399b169f311..076bc403d3138 100644
--- a/x-pack/plugins/apm/public/components/app/TransactionOverview/DynamicBaseline/Flyout.js
+++ b/x-pack/plugins/apm/public/components/app/TransactionOverview/DynamicBaseline/Flyout.js
@@ -109,7 +109,11 @@ export default class DynamicBaselineFlyout extends Component {
} = this.props;
const { isLoading, hasIndexPattern } = this.state;
- const flyout = (
+ if (!isOpen) {
+ return null;
+ }
+
+ return (
@@ -211,8 +215,6 @@ export default class DynamicBaselineFlyout extends Component {
);
-
- return {isOpen && flyout} ;
}
}
diff --git a/x-pack/plugins/apm/public/components/app/TransactionOverview/__jest__/__snapshots__/TransactionOverview.test.js.snap b/x-pack/plugins/apm/public/components/app/TransactionOverview/__jest__/__snapshots__/TransactionOverview.test.js.snap
index b825ec8428732..32c4a7023e8c5 100644
--- a/x-pack/plugins/apm/public/components/app/TransactionOverview/__jest__/__snapshots__/TransactionOverview.test.js.snap
+++ b/x-pack/plugins/apm/public/components/app/TransactionOverview/__jest__/__snapshots__/TransactionOverview.test.js.snap
@@ -20,7 +20,7 @@ exports[`TransactionOverview should not call loadTransactionList without any pro
transactionType="request"
/>
-
- (
-
-
-
-
- Timestamp
-
-
-
- {timeAgo}{' '}
- ({timestampFull})
-
-
-
-
-
- URL
-
-
-
- {url}
-
-
- {stickyProperties &&
- stickyProperties.map(({ label, val, fieldName }, i) => (
-
- {fieldName ? (
-
-
- {label}
-
-
- ) : (
- {label}
- )}
- {String(val)}
-
- ))}
-
- );
-}
diff --git a/x-pack/plugins/apm/public/components/shared/StickyProperties/StickyProperties.test.js b/x-pack/plugins/apm/public/components/shared/StickyProperties/StickyProperties.test.js
new file mode 100644
index 0000000000000..b35069a44f219
--- /dev/null
+++ b/x-pack/plugins/apm/public/components/shared/StickyProperties/StickyProperties.test.js
@@ -0,0 +1,52 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import { StickyProperties } from './index';
+import { mount } from 'enzyme';
+import { USER_ID, REQUEST_URL_FULL } from '../../../../common/constants';
+import { toJson, mockMoment } from '../../../utils/testHelpers';
+
+describe('StickyProperties', () => {
+ beforeEach(mockMoment);
+
+ it('should render', () => {
+ const stickyProperties = [
+ {
+ label: 'Timestamp',
+ fieldName: '@timestamp',
+ val: 1536405447640
+ },
+ {
+ fieldName: REQUEST_URL_FULL,
+ label: 'URL',
+ val: 'https://www.elastic.co/test',
+ truncated: true
+ },
+ {
+ label: 'Request method',
+ fieldName: 'context.request.method',
+ val: 'GET'
+ },
+ {
+ label: 'Handled',
+ fieldName: 'error.exception.handled',
+ val: 'true'
+ },
+ {
+ label: 'User ID',
+ fieldName: USER_ID,
+ val: 1337
+ }
+ ];
+
+ const wrapper = mount(
+
+ );
+
+ expect(toJson(wrapper)).toMatchSnapshot();
+ });
+});
diff --git a/x-pack/plugins/apm/public/components/shared/StickyProperties/__snapshots__/StickyProperties.test.js.snap b/x-pack/plugins/apm/public/components/shared/StickyProperties/__snapshots__/StickyProperties.test.js.snap
new file mode 100644
index 0000000000000..e71121fbe578e
--- /dev/null
+++ b/x-pack/plugins/apm/public/components/shared/StickyProperties/__snapshots__/StickyProperties.test.js.snap
@@ -0,0 +1,168 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`StickyProperties should render 1`] = `
+.c0 {
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ padding: 0 24px;
+ width: 100%;
+ -webkit-box-pack: start;
+ -webkit-justify-content: flex-start;
+ -ms-flex-pack: start;
+ justify-content: flex-start;
+ -webkit-flex-wrap: wrap;
+ -ms-flex-wrap: wrap;
+ flex-wrap: wrap;
+}
+
+.c1 {
+ width: 33%;
+ margin-bottom: 16px;
+}
+
+.c2 {
+ margin-bottom: 8px;
+ font-size: 12px;
+ color: #999999;
+}
+
+.c2 span {
+ cursor: help;
+}
+
+.c4 {
+ color: #999999;
+}
+
+.c3 {
+ display: inline-block;
+ line-height: 16px;
+}
+
+.c5 {
+ display: inline-block;
+ line-height: 16px;
+ max-width: 100%;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+
+
+
+
+ Timestamp
+
+
+
+ 1337 minutes ago (mocking 1536405447)
+
+
+ (
+ 1st of January (mocking 1536405447)
+ )
+
+
+
+
+
+
+ URL
+
+
+
+ https://www.elastic.co/test
+
+
+
+
+
+ Request method
+
+
+
+ GET
+
+
+
+
+
+ Handled
+
+
+
+ true
+
+
+
+
+
+ User ID
+
+
+
+ 1337
+
+
+
+`;
diff --git a/x-pack/plugins/apm/public/components/shared/StickyProperties/index.js b/x-pack/plugins/apm/public/components/shared/StickyProperties/index.js
new file mode 100644
index 0000000000000..3c690e3031c6a
--- /dev/null
+++ b/x-pack/plugins/apm/public/components/shared/StickyProperties/index.js
@@ -0,0 +1,116 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import styled from 'styled-components';
+import moment from 'moment';
+
+import {
+ unit,
+ units,
+ px,
+ fontSizes,
+ colors,
+ truncate
+} from '../../../style/variables';
+
+import TooltipOverlay, { fieldNameHelper } from '../../shared/TooltipOverlay';
+
+const PropertiesContainer = styled.div`
+ display: flex;
+ padding: 0 ${px(units.plus)};
+ width: 100%;
+ justify-content: flex-start;
+ flex-wrap: wrap;
+`;
+
+const Property = styled.div`
+ width: 33%;
+ margin-bottom: ${px(unit)};
+`;
+
+const PropertyLabel = styled.div`
+ margin-bottom: ${px(units.half)};
+ font-size: ${fontSizes.small};
+ color: ${colors.gray3};
+
+ span {
+ cursor: help;
+ }
+`;
+
+const PropertyValueDimmed = styled.span`
+ color: ${colors.gray3};
+`;
+
+const PropertyValue = styled.div`
+ display: inline-block;
+ line-height: ${px(unit)};
+`;
+
+const PropertyValueTruncated = styled.span`
+ display: inline-block;
+ line-height: ${px(unit)};
+ ${truncate('100%')};
+`;
+
+function TimestampValue({ timestamp }) {
+ const time = moment(timestamp);
+ const timeAgo = timestamp ? time.fromNow() : 'N/A';
+ const timestampFull = timestamp
+ ? time.format('MMMM Do YYYY, HH:mm:ss.SSS')
+ : 'N/A';
+
+ return (
+
+ {timeAgo} ({timestampFull})
+
+ );
+}
+
+function getPropertyLabel({ fieldName, label }) {
+ if (fieldName) {
+ return (
+
+
+ {label}
+
+
+ );
+ }
+
+ return {label} ;
+}
+
+function getPropertyValue({ val, fieldName, truncated = false }) {
+ if (fieldName === '@timestamp') {
+ return ;
+ }
+
+ if (truncated) {
+ return (
+
+ {String(val)}
+
+ );
+ }
+
+ return {String(val)} ;
+}
+
+export function StickyProperties({ stickyProperties }) {
+ return (
+
+ {stickyProperties &&
+ stickyProperties.map((prop, i) => (
+
+ {getPropertyLabel(prop)}
+ {getPropertyValue(prop)}
+
+ ))}
+
+ );
+}
diff --git a/x-pack/plugins/apm/public/services/__test__/callApi.test.js b/x-pack/plugins/apm/public/services/__test__/callApi.test.js
index 79a988a4099a3..7e5439b2e6eb4 100644
--- a/x-pack/plugins/apm/public/services/__test__/callApi.test.js
+++ b/x-pack/plugins/apm/public/services/__test__/callApi.test.js
@@ -23,39 +23,57 @@ describe('callApi', () => {
});
describe('callApi', () => {
- describe('debug param', () => {
- describe('when apm_debug is true', () => {
- beforeEach(() => {
- sessionStorage.setItem('apm_debug', 'true');
- });
-
- it('should add debug param for APM endpoints', async () => {
- await callApi({ pathname: `/api/apm/status/server` });
-
- expect(kfetchSpy).toHaveBeenCalledWith(
- { pathname: '/api/apm/status/server', query: { _debug: true } },
- { camelcase: true }
- );
- });
-
- it('should not add debug param for non-APM endpoints', async () => {
- await callApi({ pathname: `/api/kibana` });
-
- expect(kfetchSpy).toHaveBeenCalledWith(
- { pathname: '/api/kibana' },
- { camelcase: true }
- );
- });
+ describe('apm_debug', () => {
+ beforeEach(() => {
+ sessionStorage.setItem('apm_debug', 'true');
+ });
+
+ it('should add debug param for APM endpoints', async () => {
+ await callApi({ pathname: `/api/apm/status/server` });
+
+ expect(kfetchSpy).toHaveBeenCalledWith(
+ { pathname: '/api/apm/status/server', query: { _debug: true } },
+ expect.any(Object)
+ );
+ });
+
+ it('should not add debug param for non-APM endpoints', async () => {
+ await callApi({ pathname: `/api/kibana` });
+
+ expect(kfetchSpy).toHaveBeenCalledWith(
+ { pathname: '/api/kibana' },
+ expect.any(Object)
+ );
+ });
+ });
+
+ describe('prependBasePath', () => {
+ it('should be true by default', async () => {
+ await callApi({ pathname: `/api/kibana` });
+
+ expect(kfetchSpy).toHaveBeenCalledWith(
+ { pathname: '/api/kibana' },
+ { prependBasePath: true }
+ );
+ });
+
+ it('should respect settings', async () => {
+ await callApi({ pathname: `/api/kibana` }, { prependBasePath: false });
+
+ expect(kfetchSpy).toHaveBeenCalledWith(
+ { pathname: '/api/kibana' },
+ { prependBasePath: false }
+ );
});
});
describe('camelcase', () => {
- it('camelcase param should be true by default', async () => {
+ it('should be true by default', async () => {
const res = await callApi({ pathname: `/api/kibana` });
expect(kfetchSpy).toHaveBeenCalledWith(
{ pathname: '/api/kibana' },
- { camelcase: true }
+ expect.any(Object)
);
expect(res).toEqual({ myKey: 'hello world' });
@@ -69,7 +87,7 @@ describe('callApi', () => {
expect(kfetchSpy).toHaveBeenCalledWith(
{ pathname: '/api/kibana' },
- { camelcase: false }
+ expect.any(Object)
);
expect(res).toEqual({ my_key: 'hello world' });
diff --git a/x-pack/plugins/apm/public/services/rest/callApi.js b/x-pack/plugins/apm/public/services/rest/callApi.js
index 636278471f6f9..d33bea0644d5d 100644
--- a/x-pack/plugins/apm/public/services/rest/callApi.js
+++ b/x-pack/plugins/apm/public/services/rest/callApi.js
@@ -27,13 +27,11 @@ function fetchOptionsWithDebug(fetchOptions) {
};
}
-export async function callApi(fetchOptions, kibanaOptions) {
- const combinedKibanaOptions = {
- camelcase: true,
- ...kibanaOptions
- };
-
+export async function callApi(
+ fetchOptions,
+ { camelcase = true, prependBasePath = true } = {}
+) {
const combinedFetchOptions = fetchOptionsWithDebug(fetchOptions);
- const res = await kfetch(combinedFetchOptions, combinedKibanaOptions);
- return combinedKibanaOptions.camelcase ? camelizeKeys(res) : res;
+ const res = await kfetch(combinedFetchOptions, { prependBasePath });
+ return camelcase ? camelizeKeys(res) : res;
}
diff --git a/x-pack/plugins/apm/public/store/reactReduxRequest/detailsCharts.js b/x-pack/plugins/apm/public/store/reactReduxRequest/transactionDetailsCharts.js
similarity index 86%
rename from x-pack/plugins/apm/public/store/reactReduxRequest/detailsCharts.js
rename to x-pack/plugins/apm/public/store/reactReduxRequest/transactionDetailsCharts.js
index 0c38eb3f8f1e0..484555bf86328 100644
--- a/x-pack/plugins/apm/public/store/reactReduxRequest/detailsCharts.js
+++ b/x-pack/plugins/apm/public/store/reactReduxRequest/transactionDetailsCharts.js
@@ -12,7 +12,7 @@ import { Request } from 'react-redux-request';
import { loadCharts } from '../../services/rest/apm';
import { createInitialDataSelector } from './helpers';
-const ID = 'detailsCharts';
+const ID = 'transactionDetailsCharts';
const INITIAL_DATA = {
totalHits: 0,
dates: [],
@@ -23,7 +23,7 @@ const INITIAL_DATA = {
const withInitialData = createInitialDataSelector(INITIAL_DATA);
-export const getDetailsCharts = createSelector(
+export const getTransactionDetailsCharts = createSelector(
getUrlParams,
state => withInitialData(state.reactReduxRequest[ID]),
(urlParams, detailCharts) => {
@@ -34,7 +34,7 @@ export const getDetailsCharts = createSelector(
}
);
-export function DetailsChartsRequest({ urlParams, render }) {
+export function TransactionDetailsChartsRequest({ urlParams, render }) {
const {
serviceName,
start,
@@ -55,7 +55,7 @@ export function DetailsChartsRequest({ urlParams, render }) {
args={[
{ serviceName, start, end, transactionType, transactionName, kuery }
]}
- selector={getDetailsCharts}
+ selector={getTransactionDetailsCharts}
render={render}
/>
);
diff --git a/x-pack/plugins/apm/public/store/reactReduxRequest/overviewCharts.js b/x-pack/plugins/apm/public/store/reactReduxRequest/transactionOverviewCharts.js
similarity index 86%
rename from x-pack/plugins/apm/public/store/reactReduxRequest/overviewCharts.js
rename to x-pack/plugins/apm/public/store/reactReduxRequest/transactionOverviewCharts.js
index 347a5fa86303f..a5313c7efda10 100644
--- a/x-pack/plugins/apm/public/store/reactReduxRequest/overviewCharts.js
+++ b/x-pack/plugins/apm/public/store/reactReduxRequest/transactionOverviewCharts.js
@@ -12,7 +12,7 @@ import { getUrlParams } from '../urlParams';
import { Request } from 'react-redux-request';
import { loadCharts } from '../../services/rest/apm';
-const ID = 'overviewCharts';
+const ID = 'transactionOverviewCharts';
const INITIAL_DATA = {
totalHits: 0,
dates: [],
@@ -21,7 +21,7 @@ const INITIAL_DATA = {
overallAvgDuration: null
};
-export const getOverviewCharts = createSelector(
+export const getTransactionOverviewCharts = createSelector(
getUrlParams,
state => state.reactReduxRequest[ID],
(urlParams, overviewCharts = {}) => {
@@ -41,7 +41,7 @@ export function hasDynamicBaseline(state) {
);
}
-export function OverviewChartsRequest({ urlParams, render }) {
+export function TransactionOverviewChartsRequest({ urlParams, render }) {
const { serviceName, start, end, transactionType, kuery } = urlParams;
if (!(serviceName && start && end && transactionType)) {
@@ -53,7 +53,7 @@ export function OverviewChartsRequest({ urlParams, render }) {
id={ID}
fn={loadCharts}
args={[{ serviceName, start, end, transactionType, kuery }]}
- selector={getOverviewCharts}
+ selector={getTransactionOverviewCharts}
render={render}
/>
);
diff --git a/x-pack/plugins/apm/public/utils/testHelpers.js b/x-pack/plugins/apm/public/utils/testHelpers.js
index 756f7f41305a4..6cc1de8b648e6 100644
--- a/x-pack/plugins/apm/public/utils/testHelpers.js
+++ b/x-pack/plugins/apm/public/utils/testHelpers.js
@@ -69,11 +69,11 @@ export function mountWithStore(Component, storeState = {}) {
export function mockMoment() {
// avoid timezone issues
jest.spyOn(moment.prototype, 'format').mockImplementation(function() {
- return this.unix();
+ return `1st of January (mocking ${this.unix()})`;
});
// convert relative time to absolute time to avoid timing issues
jest.spyOn(moment.prototype, 'fromNow').mockImplementation(function() {
- return this.unix();
+ return `1337 minutes ago (mocking ${this.unix()})`;
});
}
From 45e4791efd405b2691c4008d6c3a050e0e7bbdcc Mon Sep 17 00:00:00 2001
From: Marco Vettorello
Date: Mon, 10 Sep 2018 09:43:31 +0200
Subject: [PATCH 59/68] Fix _source formatting (#22800)
* Fix _source formatting
* Update unit test
* Add functional test
* Fix CI error, move functional test to the end
---
.../kibana/common/field_formats/types/source.js | 2 +-
.../public/field_formats/__tests__/_source.js | 5 +++--
test/functional/apps/visualize/_data_table.js | 14 ++++++++++++++
3 files changed, 18 insertions(+), 3 deletions(-)
diff --git a/src/core_plugins/kibana/common/field_formats/types/source.js b/src/core_plugins/kibana/common/field_formats/types/source.js
index 81a6eb51eb20d..61509a746c163 100644
--- a/src/core_plugins/kibana/common/field_formats/types/source.js
+++ b/src/core_plugins/kibana/common/field_formats/types/source.js
@@ -48,7 +48,7 @@ export function createSourceFormat(FieldFormat) {
SourceFormat.prototype._convert = {
text: (value) => toJson(value),
html: function sourceToHtml(source, field, hit) {
- if (!field) return this.getConverterFor('text')(source, field, hit);
+ if (!field) return _.escape(this.getConverterFor('text')(source));
const highlights = (hit && hit.highlight) || {};
const formatted = field.indexPattern.formatHit(hit);
diff --git a/src/core_plugins/kibana/public/field_formats/__tests__/_source.js b/src/core_plugins/kibana/public/field_formats/__tests__/_source.js
index 09a84ae775430..4726c3993771e 100644
--- a/src/core_plugins/kibana/public/field_formats/__tests__/_source.js
+++ b/src/core_plugins/kibana/public/field_formats/__tests__/_source.js
@@ -43,8 +43,9 @@ describe('_source formatting', function () {
}));
it('should use the text content type if a field is not passed', function () {
- const hit = _.first(hits);
- expect(convertHtml(hit._source)).to.be(`${JSON.stringify(hit._source)} `);
+ const hit = { 'foo': 'bar', 'number': 42, 'hello': 'World ', 'also': 'with "quotes" or \'single quotes\'' };
+ // eslint-disable-next-line
+ expect(convertHtml(hit)).to.be('{"foo":"bar","number":42,"hello":"<h1>World</h1>","also":"with \\"quotes\\" or 'single quotes'"} ');
});
it('uses the _source, field, and hit to create a ', function () {
diff --git a/test/functional/apps/visualize/_data_table.js b/test/functional/apps/visualize/_data_table.js
index d6940437f24ee..9901df941bef1 100644
--- a/test/functional/apps/visualize/_data_table.js
+++ b/test/functional/apps/visualize/_data_table.js
@@ -178,5 +178,19 @@ export default function ({ getService, getPageObjects }) {
'2015-09-20', '4,757',
]);
});
+
+ it('should show correct data for a data table with top hits', async () => {
+ await PageObjects.visualize.navigateToNewVisualization();
+ await PageObjects.visualize.clickDataTable();
+ await PageObjects.visualize.clickNewSearch();
+ await PageObjects.header.setAbsoluteRange(fromTime, toTime);
+ await PageObjects.visualize.clickMetricEditor();
+ await PageObjects.visualize.selectAggregation('Top Hit', 'metrics');
+ await PageObjects.visualize.selectField('_source', 'metrics');
+ await PageObjects.visualize.clickGo();
+ const data = await PageObjects.visualize.getTableVisData();
+ log.debug(data);
+ expect(data.length).to.be.greaterThan(0);
+ });
});
}
From 0a870b2d973a23e7c80c27f29b981cf14bfa639b Mon Sep 17 00:00:00 2001
From: Larry Gregory
Date: Mon, 10 Sep 2018 07:27:50 -0400
Subject: [PATCH 60/68] Manage telemetry opt-in via a dedicated document
(#22268)
---
.../advanced_settings.test.js.snap | 60 ++++++++
.../sections/settings/advanced_settings.js | 15 +-
.../components/default_component_registry.js | 3 +
.../settings/components/field/field.js | 60 ++++----
.../form/__snapshots__/form.test.js.snap | 2 +
.../sections/settings/components/form/form.js | 22 ++-
.../settings/components/form/form.test.js | 24 ++-
.../__snapshots__/page_footer.test.js.snap | 3 +
.../settings/components/page_footer/index.js | 20 +++
.../components/page_footer/page_footer.js | 20 +++
.../page_footer/page_footer.test.js | 28 ++++
src/ui/public/management/index.js | 7 +
x-pack/plugins/xpack_main/common/constants.js | 7 +-
x-pack/plugins/xpack_main/index.js | 7 +-
x-pack/plugins/xpack_main/mappings.json | 9 ++
.../xpack_main/public/components/index.js | 3 +
.../opt_in_details_component.test.js.snap | 56 +++++++
.../__snapshots__/telemetry_form.test.js.snap | 74 ++++++++++
.../telemetry}/opt_in_details_component.js | 15 +-
.../opt_in_details_component.test.js | 14 ++
.../components/telemetry/telemetry_form.js | 138 ++++++++++++++++++
.../components/telemetry/telemetry_form.less | 3 +
.../telemetry/telemetry_form.test.js | 47 ++++++
.../public/hacks/__tests__/telemetry.js | 5 +-
.../xpack_main/public/hacks/telemetry.js | 11 +-
.../welcome_banner/__tests__/click_banner.js | 77 +++++++---
.../__tests__/handle_old_settings.js | 138 ++++++++++++++----
.../__tests__/should_show_banner.js | 40 ++++-
.../hacks/welcome_banner/click_banner.js | 17 ++-
.../welcome_banner/handle_old_settings.js | 26 +++-
.../hacks/welcome_banner/inject_banner.js | 8 +-
.../welcome_banner/opt_in_banner_component.js | 12 +-
.../hacks/welcome_banner/render_banner.js | 6 +-
.../welcome_banner/should_show_banner.js | 7 +-
.../public/services/telemetry_opt_in.js | 40 +++++
.../public/services/telemetry_opt_in.test.js | 89 +++++++++++
.../public/views/management/index.js | 7 +
.../public/views/management/management.js | 22 +++
.../lib/__tests__/replace_injected_vars.js | 74 +++++++++-
.../server/lib/get_telemetry_opt_in.js | 19 +++
.../server/lib/replace_injected_vars.js | 11 +-
.../routes/api/v1/telemetry/telemetry.js | 38 ++++-
.../apis/privileges/index.js | 3 +
43 files changed, 1130 insertions(+), 157 deletions(-)
create mode 100644 src/core_plugins/kibana/public/management/sections/settings/components/page_footer/__snapshots__/page_footer.test.js.snap
create mode 100644 src/core_plugins/kibana/public/management/sections/settings/components/page_footer/index.js
create mode 100644 src/core_plugins/kibana/public/management/sections/settings/components/page_footer/page_footer.js
create mode 100644 src/core_plugins/kibana/public/management/sections/settings/components/page_footer/page_footer.test.js
create mode 100644 x-pack/plugins/xpack_main/mappings.json
create mode 100644 x-pack/plugins/xpack_main/public/components/telemetry/__snapshots__/opt_in_details_component.test.js.snap
create mode 100644 x-pack/plugins/xpack_main/public/components/telemetry/__snapshots__/telemetry_form.test.js.snap
rename x-pack/plugins/xpack_main/public/{hacks/welcome_banner => components/telemetry}/opt_in_details_component.js (87%)
create mode 100644 x-pack/plugins/xpack_main/public/components/telemetry/opt_in_details_component.test.js
create mode 100644 x-pack/plugins/xpack_main/public/components/telemetry/telemetry_form.js
create mode 100644 x-pack/plugins/xpack_main/public/components/telemetry/telemetry_form.less
create mode 100644 x-pack/plugins/xpack_main/public/components/telemetry/telemetry_form.test.js
create mode 100644 x-pack/plugins/xpack_main/public/services/telemetry_opt_in.js
create mode 100644 x-pack/plugins/xpack_main/public/services/telemetry_opt_in.test.js
create mode 100644 x-pack/plugins/xpack_main/public/views/management/index.js
create mode 100644 x-pack/plugins/xpack_main/public/views/management/management.js
create mode 100644 x-pack/plugins/xpack_main/server/lib/get_telemetry_opt_in.js
diff --git a/src/core_plugins/kibana/public/management/sections/settings/__snapshots__/advanced_settings.test.js.snap b/src/core_plugins/kibana/public/management/sections/settings/__snapshots__/advanced_settings.test.js.snap
index f219c199b1d87..85abe0571d116 100644
--- a/src/core_plugins/kibana/public/management/sections/settings/__snapshots__/advanced_settings.test.js.snap
+++ b/src/core_plugins/kibana/public/management/sections/settings/__snapshots__/advanced_settings.test.js.snap
@@ -302,6 +302,27 @@ exports[`AdvancedSettings should render normally 1`] = `
],
}
}
+ showNoResultsMessage={true}
+ />
+
`;
@@ -420,6 +441,45 @@ exports[`AdvancedSettings should render specific setting if given setting key 1`
],
}
}
+ showNoResultsMessage={true}
+ />
+
`;
diff --git a/src/core_plugins/kibana/public/management/sections/settings/advanced_settings.js b/src/core_plugins/kibana/public/management/sections/settings/advanced_settings.js
index 2c67da99b6bf7..7ce4341f59ed8 100644
--- a/src/core_plugins/kibana/public/management/sections/settings/advanced_settings.js
+++ b/src/core_plugins/kibana/public/management/sections/settings/advanced_settings.js
@@ -35,7 +35,7 @@ import { Form } from './components/form';
import { getAriaName, toEditableConfig, DEFAULT_CATEGORY } from './lib';
import './advanced_settings.less';
-import { registerDefaultComponents, PAGE_TITLE_COMPONENT } from './components/default_component_registry';
+import { registerDefaultComponents, PAGE_TITLE_COMPONENT, PAGE_FOOTER_COMPONENT } from './components/default_component_registry';
import { getSettingsComponent } from './components/component_registry';
export class AdvancedSettings extends Component {
@@ -51,6 +51,7 @@ export class AdvancedSettings extends Component {
this.init(config);
this.state = {
query: parsedQuery,
+ footerQueryMatched: false,
filteredSettings: this.mapSettings(Query.execute(parsedQuery, this.settings)),
};
@@ -129,14 +130,22 @@ export class AdvancedSettings extends Component {
clearQuery = () => {
this.setState({
query: Query.parse(''),
+ footerQueryMatched: false,
filteredSettings: this.groupedSettings,
});
}
+ onFooterQueryMatchChange = (matched) => {
+ this.setState({
+ footerQueryMatched: matched
+ });
+ }
+
render() {
- const { filteredSettings, query } = this.state;
+ const { filteredSettings, query, footerQueryMatched } = this.state;
const PageTitle = getSettingsComponent(PAGE_TITLE_COMPONENT);
+ const PageFooter = getSettingsComponent(PAGE_FOOTER_COMPONENT);
return (
@@ -162,7 +171,9 @@ export class AdvancedSettings extends Component {
clearQuery={this.clearQuery}
save={this.saveConfig}
clear={this.clearConfig}
+ showNoResultsMessage={!footerQueryMatched}
/>
+
);
}
diff --git a/src/core_plugins/kibana/public/management/sections/settings/components/default_component_registry.js b/src/core_plugins/kibana/public/management/sections/settings/components/default_component_registry.js
index b88fb11d63bbc..221f8c2f82bf8 100644
--- a/src/core_plugins/kibana/public/management/sections/settings/components/default_component_registry.js
+++ b/src/core_plugins/kibana/public/management/sections/settings/components/default_component_registry.js
@@ -19,9 +19,12 @@
import { tryRegisterSettingsComponent } from './component_registry';
import { PageTitle } from './page_title';
+import { PageFooter } from './page_footer';
export const PAGE_TITLE_COMPONENT = 'advanced_settings_page_title';
+export const PAGE_FOOTER_COMPONENT = 'advanced_settings_page_footer';
export function registerDefaultComponents() {
tryRegisterSettingsComponent(PAGE_TITLE_COMPONENT, PageTitle);
+ tryRegisterSettingsComponent(PAGE_FOOTER_COMPONENT, PageFooter);
}
\ No newline at end of file
diff --git a/src/core_plugins/kibana/public/management/sections/settings/components/field/field.js b/src/core_plugins/kibana/public/management/sections/settings/components/field/field.js
index 68ff872e9ddfe..c21456953c17a 100644
--- a/src/core_plugins/kibana/public/management/sections/settings/components/field/field.js
+++ b/src/core_plugins/kibana/public/management/sections/settings/components/field/field.js
@@ -87,7 +87,7 @@ export class Field extends PureComponent {
getEditableValue(type, value, defVal) {
const val = (value === null || value === undefined) ? defVal : value;
- switch(type) {
+ switch (type) {
case 'array':
return val.join(', ');
case 'boolean':
@@ -102,10 +102,10 @@ export class Field extends PureComponent {
}
getDisplayedDefaultValue(type, defVal) {
- if(defVal === undefined || defVal === null || defVal === '') {
+ if (defVal === undefined || defVal === null || defVal === '') {
return 'null';
}
- switch(type) {
+ switch (type) {
case 'array':
return defVal.join(', ');
default:
@@ -193,7 +193,7 @@ export class Field extends PureComponent {
}
onImageChange = async (files) => {
- if(!files.length) {
+ if (!files.length) {
this.clearError();
this.setState({
unsavedValue: null,
@@ -212,18 +212,18 @@ export class Field extends PureComponent {
changeImage: true,
unsavedValue: base64Image,
});
- } catch(err) {
+ } catch (err) {
toastNotifications.addDanger('Image could not be saved');
this.cancelChangeImage();
}
}
getImageAsBase64(file) {
- if(!file instanceof File) {
+ if (!file instanceof File) {
return null;
}
- const reader = new FileReader();
+ const reader = new FileReader();
reader.readAsDataURL(file);
return new Promise((resolve, reject) => {
@@ -245,7 +245,7 @@ export class Field extends PureComponent {
cancelChangeImage = () => {
const { savedValue } = this.state;
- if(this.changeImageForm) {
+ if (this.changeImageForm) {
this.changeImageForm.fileInput.value = null;
this.changeImageForm.handleChange();
}
@@ -268,14 +268,14 @@ export class Field extends PureComponent {
const { name, defVal, type } = this.props.setting;
const { changeImage, savedValue, unsavedValue, isJsonArray } = this.state;
- if(savedValue === unsavedValue) {
+ if (savedValue === unsavedValue) {
return;
}
let valueToSave = unsavedValue;
let isSameValue = false;
- switch(type) {
+ switch (type) {
case 'array':
valueToSave = valueToSave.split(',').map(val => val.trim());
isSameValue = valueToSave.join(',') === defVal.join(',');
@@ -295,10 +295,10 @@ export class Field extends PureComponent {
await this.props.save(name, valueToSave);
}
- if(changeImage) {
+ if (changeImage) {
this.cancelChangeImage();
}
- } catch(e) {
+ } catch (e) {
toastNotifications.addDanger(`Unable to save ${name}`);
}
this.setLoading(false);
@@ -311,7 +311,7 @@ export class Field extends PureComponent {
await this.props.clear(name);
this.cancelChangeImage();
this.clearError();
- } catch(e) {
+ } catch (e) {
toastNotifications.addDanger(`Unable to reset ${name}`);
}
this.setLoading(false);
@@ -321,7 +321,7 @@ export class Field extends PureComponent {
const { loading, changeImage, unsavedValue } = this.state;
const { name, value, type, options, isOverridden } = setting;
- switch(type) {
+ switch (type) {
case 'boolean':
return (
);
case 'image':
- if(!isDefaultValue(setting) && !changeImage) {
+ if (!isDefaultValue(setting) && !changeImage) {
return (
{setting.name}
@@ -438,7 +438,7 @@ export class Field extends PureComponent {
const defaultLink = this.renderResetToDefaultLink(setting);
const imageLink = this.renderChangeImageLink(setting);
- if(defaultLink || imageLink) {
+ if (defaultLink || imageLink) {
return (
{defaultLink}
@@ -462,8 +462,12 @@ export class Field extends PureComponent {
}
renderDescription(setting) {
- return (
-
+ let description;
+
+ if (React.isValidElement(setting.description)) {
+ description = setting.description;
+ } else {
+ description = (
+ );
+ }
+
+ return (
+
+ {description}
{this.renderDefaultValue(setting)}
);
@@ -478,14 +488,14 @@ export class Field extends PureComponent {
renderDefaultValue(setting) {
const { type, defVal } = setting;
- if(isDefaultValue(setting)) {
+ if (isDefaultValue(setting)) {
return;
}
return (
- { type === 'json' ? (
+ {type === 'json' ? (
Default:
) : (
- Default: {this.getDisplayedDefaultValue(type, defVal)}
+ Default: {this.getDisplayedDefaultValue(type, defVal)}
- ) }
+ )}
);
@@ -508,7 +518,7 @@ export class Field extends PureComponent {
renderResetToDefaultLink(setting) {
const { ariaName, name } = setting;
- if(isDefaultValue(setting)) {
+ if (isDefaultValue(setting)) {
return;
}
return (
@@ -528,7 +538,7 @@ export class Field extends PureComponent {
renderChangeImageLink(setting) {
const { changeImage } = this.state;
const { type, value, ariaName, name } = setting;
- if(type !== 'image' || !value || changeImage) {
+ if (type !== 'image' || !value || changeImage) {
return;
}
return (
diff --git a/src/core_plugins/kibana/public/management/sections/settings/components/form/__snapshots__/form.test.js.snap b/src/core_plugins/kibana/public/management/sections/settings/components/form/__snapshots__/form.test.js.snap
index 627f5d864d1d2..7d51699e975e7 100644
--- a/src/core_plugins/kibana/public/management/sections/settings/components/form/__snapshots__/form.test.js.snap
+++ b/src/core_plugins/kibana/public/management/sections/settings/components/form/__snapshots__/form.test.js.snap
@@ -1,5 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
+exports[`Form should not render no settings message when instructed not to 1`] = ` `;
+
exports[`Form should render no settings message when there are no settings 1`] = `
@@ -95,12 +96,23 @@ export class Form extends PureComponent {
);
}
+ maybeRenderNoSettings(clearQuery) {
+ if (this.props.showNoResultsMessage) {
+ return (
+
+ No settings found (Clear search)
+
+ );
+ }
+ return null;
+ }
+
render() {
const { settings, categories, categoryCounts, clearQuery } = this.props;
const currentCategories = [];
categories.forEach(category => {
- if(settings[category] && settings[category].length) {
+ if (settings[category] && settings[category].length) {
currentCategories.push(category);
}
});
@@ -112,11 +124,7 @@ export class Form extends PureComponent {
return (
this.renderCategory(category, settings[category], categoryCounts[category]) // fix this
);
- }) : (
-
- No settings found (Clear search)
-
- )
+ }) : this.maybeRenderNoSettings(clearQuery)
}
);
diff --git a/src/core_plugins/kibana/public/management/sections/settings/components/form/form.test.js b/src/core_plugins/kibana/public/management/sections/settings/components/form/form.test.js
index cd3b3f2db5fb3..fddaae79ec44e 100644
--- a/src/core_plugins/kibana/public/management/sections/settings/components/form/form.test.js
+++ b/src/core_plugins/kibana/public/management/sections/settings/components/form/form.test.js
@@ -69,9 +69,9 @@ const categoryCounts = {
dashboard: 1,
'x-pack': 10,
};
-const save = () => {};
-const clear = () => {};
-const clearQuery = () => {};
+const save = () => { };
+const clear = () => { };
+const clearQuery = () => { };
describe('Form', () => {
it('should render normally', async () => {
@@ -83,6 +83,7 @@ describe('Form', () => {
save={save}
clear={clear}
clearQuery={clearQuery}
+ showNoResultsMessage={true}
/>
);
@@ -98,6 +99,23 @@ describe('Form', () => {
save={save}
clear={clear}
clearQuery={clearQuery}
+ showNoResultsMessage={true}
+ />
+ );
+
+ expect(component).toMatchSnapshot();
+ });
+
+ it('should not render no settings message when instructed not to', async () => {
+ const component = shallow(
+
);
diff --git a/src/core_plugins/kibana/public/management/sections/settings/components/page_footer/__snapshots__/page_footer.test.js.snap b/src/core_plugins/kibana/public/management/sections/settings/components/page_footer/__snapshots__/page_footer.test.js.snap
new file mode 100644
index 0000000000000..eea1003c8eb95
--- /dev/null
+++ b/src/core_plugins/kibana/public/management/sections/settings/components/page_footer/__snapshots__/page_footer.test.js.snap
@@ -0,0 +1,3 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`PageFooter should render normally 1`] = `""`;
diff --git a/src/core_plugins/kibana/public/management/sections/settings/components/page_footer/index.js b/src/core_plugins/kibana/public/management/sections/settings/components/page_footer/index.js
new file mode 100644
index 0000000000000..2fae89ceb0380
--- /dev/null
+++ b/src/core_plugins/kibana/public/management/sections/settings/components/page_footer/index.js
@@ -0,0 +1,20 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+export { PageFooter } from './page_footer';
\ No newline at end of file
diff --git a/src/core_plugins/kibana/public/management/sections/settings/components/page_footer/page_footer.js b/src/core_plugins/kibana/public/management/sections/settings/components/page_footer/page_footer.js
new file mode 100644
index 0000000000000..e55fbbae3b5f8
--- /dev/null
+++ b/src/core_plugins/kibana/public/management/sections/settings/components/page_footer/page_footer.js
@@ -0,0 +1,20 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+export const PageFooter = () => null;
\ No newline at end of file
diff --git a/src/core_plugins/kibana/public/management/sections/settings/components/page_footer/page_footer.test.js b/src/core_plugins/kibana/public/management/sections/settings/components/page_footer/page_footer.test.js
new file mode 100644
index 0000000000000..e4ac6af0a88fe
--- /dev/null
+++ b/src/core_plugins/kibana/public/management/sections/settings/components/page_footer/page_footer.test.js
@@ -0,0 +1,28 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import React from 'react';
+import { shallow } from 'enzyme';
+
+import { PageFooter } from './page_footer';
+
+describe('PageFooter', () => {
+ it('should render normally', () => {
+ expect(shallow( )).toMatchSnapshot();
+ });
+});
\ No newline at end of file
diff --git a/src/ui/public/management/index.js b/src/ui/public/management/index.js
index 616600f1ac1de..62f9850c839f5 100644
--- a/src/ui/public/management/index.js
+++ b/src/ui/public/management/index.js
@@ -19,8 +19,15 @@
import { ManagementSection } from './section';
+export {
+ PAGE_TITLE_COMPONENT,
+ PAGE_FOOTER_COMPONENT,
+} from '../../../core_plugins/kibana/public/management/sections/settings/components/default_component_registry';
+
export { registerSettingsComponent } from '../../../core_plugins/kibana/public/management/sections/settings/components/component_registry';
+export { Field } from '../../../core_plugins/kibana/public/management/sections/settings/components/field/field';
+
export const management = new ManagementSection('management', {
display: 'Management'
});
diff --git a/x-pack/plugins/xpack_main/common/constants.js b/x-pack/plugins/xpack_main/common/constants.js
index d939038a3986b..d59a4ab9e5a4a 100644
--- a/x-pack/plugins/xpack_main/common/constants.js
+++ b/x-pack/plugins/xpack_main/common/constants.js
@@ -14,7 +14,7 @@ export const CONFIG_TELEMETRY = 'telemetry:optIn';
* @type {string}
*/
export const CONFIG_TELEMETRY_DESC = (
- 'Help us improve the Elastic Stack by providing basic feature usage statistics? We will never share this data outside of Elastic.'
+ 'Help us improve the Elastic Stack by providing usage statistics for basic features. We will not share this data outside of Elastic.'
);
/**
@@ -53,3 +53,8 @@ export const REPORT_INTERVAL_MS = 86400000;
* Key for the localStorage service
*/
export const LOCALSTORAGE_KEY = 'xpack.data';
+
+/**
+ * Link to the Elastic Telemetry privacy statement.
+ */
+export const PRIVACY_STATEMENT_URL = `https://www.elastic.co/legal/telemetry-privacy-statement`;
diff --git a/x-pack/plugins/xpack_main/index.js b/x-pack/plugins/xpack_main/index.js
index 1fe2393f61d36..6e6ed25893ced 100644
--- a/x-pack/plugins/xpack_main/index.js
+++ b/x-pack/plugins/xpack_main/index.js
@@ -22,6 +22,7 @@ import {
CONFIG_TELEMETRY_DESC,
} from './common/constants';
import { settingsRoute } from './server/routes/api/v1/settings';
+import mappings from './mappings.json';
export { callClusterFactory } from './server/lib/call_cluster_factory';
@@ -65,11 +66,13 @@ export const xpackMain = (kibana) => {
},
uiExports: {
+ managementSections: ['plugins/xpack_main/views/management'],
uiSettingDefaults: {
[CONFIG_TELEMETRY]: {
name: 'Telemetry opt-in',
description: CONFIG_TELEMETRY_DESC,
- value: false
+ value: false,
+ readonly: true,
},
[XPACK_DEFAULT_ADMIN_EMAIL_UI_SETTING]: {
name: 'Admin email',
@@ -84,6 +87,7 @@ export const xpackMain = (kibana) => {
return {
telemetryUrl: config.get('xpack.xpack_main.telemetry.url'),
telemetryEnabled: isTelemetryEnabled(config),
+ telemetryOptedIn: null,
};
},
hacks: [
@@ -101,6 +105,7 @@ export const xpackMain = (kibana) => {
raw: true,
});
},
+ mappings,
},
init(server) {
diff --git a/x-pack/plugins/xpack_main/mappings.json b/x-pack/plugins/xpack_main/mappings.json
new file mode 100644
index 0000000000000..d83f7f5967630
--- /dev/null
+++ b/x-pack/plugins/xpack_main/mappings.json
@@ -0,0 +1,9 @@
+{
+ "telemetry": {
+ "properties": {
+ "enabled": {
+ "type": "boolean"
+ }
+ }
+ }
+}
diff --git a/x-pack/plugins/xpack_main/public/components/index.js b/x-pack/plugins/xpack_main/public/components/index.js
index c18cf4665f4ac..c8dc260717da0 100644
--- a/x-pack/plugins/xpack_main/public/components/index.js
+++ b/x-pack/plugins/xpack_main/public/components/index.js
@@ -11,3 +11,6 @@ export { AddLicense } from '../../../license_management/public/sections/license_
* For to link to management
*/
export { BASE_PATH as MANAGEMENT_BASE_PATH } from '../../../license_management/common/constants';
+
+export { TelemetryForm } from './telemetry/telemetry_form';
+export { OptInExampleFlyout } from './telemetry/opt_in_details_component';
\ No newline at end of file
diff --git a/x-pack/plugins/xpack_main/public/components/telemetry/__snapshots__/opt_in_details_component.test.js.snap b/x-pack/plugins/xpack_main/public/components/telemetry/__snapshots__/opt_in_details_component.test.js.snap
new file mode 100644
index 0000000000000..19a9bbb169b35
--- /dev/null
+++ b/x-pack/plugins/xpack_main/public/components/telemetry/__snapshots__/opt_in_details_component.test.js.snap
@@ -0,0 +1,56 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`OptInDetailsComponent renders as expected 1`] = `
+
+
+
+
+
+ Cluster statistics
+
+
+
+
+ This is an example of the basic cluster statistics that we’ll collect. It includes the number of indices, shards, and nodes. It also includes high-level usage statistics, such as whether monitoring is turned on.
+
+
+
+
+
+
+
+
+
+
+
+
+`;
diff --git a/x-pack/plugins/xpack_main/public/components/telemetry/__snapshots__/telemetry_form.test.js.snap b/x-pack/plugins/xpack_main/public/components/telemetry/__snapshots__/telemetry_form.test.js.snap
new file mode 100644
index 0000000000000..a24872ec6c4fd
--- /dev/null
+++ b/x-pack/plugins/xpack_main/public/components/telemetry/__snapshots__/telemetry_form.test.js.snap
@@ -0,0 +1,74 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`TelemetryForm renders as expected 1`] = `
+
+
+
+
+
+
+
+ Usage Data
+
+
+
+
+
+
+
+ Help us improve the Elastic Stack by providing usage statistics for basic features. We will not share this data outside of Elastic.
+
+
+
+ See an example of what we collect
+
+
+
+
+ Read our usage data privacy statement
+
+
+ ,
+ "type": "boolean",
+ "value": false,
+ }
+ }
+ />
+
+
+
+`;
diff --git a/x-pack/plugins/xpack_main/public/hacks/welcome_banner/opt_in_details_component.js b/x-pack/plugins/xpack_main/public/components/telemetry/opt_in_details_component.js
similarity index 87%
rename from x-pack/plugins/xpack_main/public/hacks/welcome_banner/opt_in_details_component.js
rename to x-pack/plugins/xpack_main/public/components/telemetry/opt_in_details_component.js
index f980a10604fbd..678b30d261466 100644
--- a/x-pack/plugins/xpack_main/public/hacks/welcome_banner/opt_in_details_component.js
+++ b/x-pack/plugins/xpack_main/public/components/telemetry/opt_in_details_component.js
@@ -58,7 +58,7 @@ export class OptInExampleFlyout extends Component {
return (
-
+
);
@@ -79,7 +79,7 @@ export class OptInExampleFlyout extends Component {
return (
- { JSON.stringify(data, null, 2) }
+ {JSON.stringify(data, null, 2)}
);
}
@@ -90,21 +90,22 @@ export class OptInExampleFlyout extends Component {
- Cluster Statistics
+ Cluster statistics
- This is an example of the basic cluster statistics we’ll gather, which includes number of indexes,
- number of shards, number of nodes, and high-level usage statistics, such as whether monitoring is enabled.
+ This is an example of the basic cluster statistics that we’ll collect.
+ It includes the number of indices, shards, and nodes.
+ It also includes high-level usage statistics, such as whether monitoring is turned on.
- { this.renderBody(this.state) }
+ {this.renderBody(this.state)}
diff --git a/x-pack/plugins/xpack_main/public/components/telemetry/opt_in_details_component.test.js b/x-pack/plugins/xpack_main/public/components/telemetry/opt_in_details_component.test.js
new file mode 100644
index 0000000000000..a649f932e025f
--- /dev/null
+++ b/x-pack/plugins/xpack_main/public/components/telemetry/opt_in_details_component.test.js
@@ -0,0 +1,14 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import React from 'react';
+import { shallow } from 'enzyme';
+import { OptInExampleFlyout } from './opt_in_details_component';
+
+describe('OptInDetailsComponent', () => {
+ it('renders as expected', () => {
+ expect(shallow( ({ data: [] }))} onClose={jest.fn()} />)).toMatchSnapshot();
+ });
+});
\ No newline at end of file
diff --git a/x-pack/plugins/xpack_main/public/components/telemetry/telemetry_form.js b/x-pack/plugins/xpack_main/public/components/telemetry/telemetry_form.js
new file mode 100644
index 0000000000000..1a7826f56d0d2
--- /dev/null
+++ b/x-pack/plugins/xpack_main/public/components/telemetry/telemetry_form.js
@@ -0,0 +1,138 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { Component, Fragment } from 'react';
+import PropTypes from 'prop-types';
+import {
+ EuiPanel,
+ EuiForm,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiLink,
+ EuiSpacer,
+ EuiText,
+} from '@elastic/eui';
+import { CONFIG_TELEMETRY_DESC, PRIVACY_STATEMENT_URL } from '../../../common/constants';
+import { OptInExampleFlyout } from './opt_in_details_component';
+import './telemetry_form.less';
+import { Field } from 'ui/management';
+
+const SEARCH_TERMS = ['telemetry', 'usage', 'data', 'usage data'];
+
+export class TelemetryForm extends Component {
+ static propTypes = {
+ telemetryOptInProvider: PropTypes.object.isRequired,
+ query: PropTypes.object,
+ onQueryMatchChange: PropTypes.func.isRequired,
+ };
+
+ state = {
+ processing: false,
+ showExample: false,
+ queryMatches: null,
+ }
+
+ componentWillReceiveProps(nextProps) {
+ const {
+ query
+ } = nextProps;
+
+ const searchTerm = (query.text || '').toLowerCase();
+ const searchTermMatches = SEARCH_TERMS.some(term => term.indexOf(searchTerm) >= 0);
+
+ if (searchTermMatches !== this.state.queryMatches) {
+ this.setState({
+ queryMatches: searchTermMatches
+ }, () => {
+ this.props.onQueryMatchChange(searchTermMatches);
+ });
+ }
+ }
+
+ render() {
+ const {
+ telemetryOptInProvider,
+ } = this.props;
+
+ const {
+ showExample,
+ queryMatches,
+ } = this.state;
+
+ if (queryMatches !== null && !queryMatches) {
+ return null;
+ }
+
+ return (
+
+ {showExample &&
+ telemetryOptInProvider.fetchExample()} onClose={this.toggleExample} />
+ }
+
+
+
+
+
+ Usage Data
+
+
+
+
+
+
+
+
+ );
+ }
+
+ renderDescription = () => (
+
+ {CONFIG_TELEMETRY_DESC}
+ See an example of what we collect
+
+
+ Read our usage data privacy statement
+
+
+
+ )
+
+ toggleOptIn = async () => {
+ const newOptInValue = !this.props.telemetryOptInProvider.getOptIn();
+
+ return new Promise((resolve, reject) => {
+ this.setState({
+ enabled: newOptInValue,
+ processing: true
+ }, () => {
+ this.props.telemetryOptInProvider.setOptIn(newOptInValue).then(() => {
+ this.setState({ processing: false });
+ resolve();
+ }, (e) => {
+ // something went wrong
+ this.setState({ processing: false });
+ reject(e);
+ });
+ });
+ });
+
+ }
+
+ toggleExample = () => {
+ this.setState({
+ showExample: !this.state.showExample
+ });
+ }
+}
\ No newline at end of file
diff --git a/x-pack/plugins/xpack_main/public/components/telemetry/telemetry_form.less b/x-pack/plugins/xpack_main/public/components/telemetry/telemetry_form.less
new file mode 100644
index 0000000000000..041b0c9461e23
--- /dev/null
+++ b/x-pack/plugins/xpack_main/public/components/telemetry/telemetry_form.less
@@ -0,0 +1,3 @@
+.telemetryForm {
+ margin: 10px 6px 6px 6px;
+}
\ No newline at end of file
diff --git a/x-pack/plugins/xpack_main/public/components/telemetry/telemetry_form.test.js b/x-pack/plugins/xpack_main/public/components/telemetry/telemetry_form.test.js
new file mode 100644
index 0000000000000..53717aa0b15a2
--- /dev/null
+++ b/x-pack/plugins/xpack_main/public/components/telemetry/telemetry_form.test.js
@@ -0,0 +1,47 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import { shallow } from 'enzyme';
+import { TelemetryForm } from './telemetry_form';
+import { TelemetryOptInProvider } from '../../services/telemetry_opt_in';
+
+const buildTelemetryOptInProvider = () => {
+ const mockHttp = {
+ post: jest.fn()
+ };
+
+ function mockNotifier() {
+ this.notify = jest.fn();
+ }
+
+ const mockInjector = {
+ get: (key) => {
+ switch (key) {
+ case '$http':
+ return mockHttp;
+ case 'Notifier':
+ return mockNotifier;
+ default:
+ return null;
+ }
+ }
+ };
+
+ const chrome = {
+ addBasePath: (url) => url
+ };
+
+ return new TelemetryOptInProvider(mockInjector, chrome);
+};
+
+describe('TelemetryForm', () => {
+ it('renders as expected', () => {
+ expect(shallow(
+ )
+ ).toMatchSnapshot();
+ });
+});
\ No newline at end of file
diff --git a/x-pack/plugins/xpack_main/public/hacks/__tests__/telemetry.js b/x-pack/plugins/xpack_main/public/hacks/__tests__/telemetry.js
index e027530cc08fe..20227b40359ae 100644
--- a/x-pack/plugins/xpack_main/public/hacks/__tests__/telemetry.js
+++ b/x-pack/plugins/xpack_main/public/hacks/__tests__/telemetry.js
@@ -13,6 +13,7 @@ uiModules.get('kibana')
// disable stat reporting while running tests,
// MockInjector used in these tests is not impacted
.constant('telemetryEnabled', false)
+ .constant('telemetryOptedIn', null)
.constant('telemetryUrl', 'not.a.valid.url.0');
const getMockInjector = ({ allowReport, lastReport }) => {
@@ -21,9 +22,7 @@ const getMockInjector = ({ allowReport, lastReport }) => {
get: sinon.stub().returns({ lastReport: lastReport }),
set: sinon.stub()
});
- get.withArgs('config').returns({
- get: () => allowReport
- });
+ get.withArgs('telemetryOptedIn').returns(allowReport);
const mockHttp = (req) => {
return req;
};
diff --git a/x-pack/plugins/xpack_main/public/hacks/telemetry.js b/x-pack/plugins/xpack_main/public/hacks/telemetry.js
index 66abbdbaee9a0..4b03f1c951118 100644
--- a/x-pack/plugins/xpack_main/public/hacks/telemetry.js
+++ b/x-pack/plugins/xpack_main/public/hacks/telemetry.js
@@ -6,7 +6,6 @@
import Promise from 'bluebird';
import {
- CONFIG_TELEMETRY,
REPORT_INTERVAL_MS,
LOCALSTORAGE_KEY,
} from '../../common/constants';
@@ -19,9 +18,9 @@ export class Telemetry {
*/
constructor($injector, fetchTelemetry) {
this._storage = $injector.get('localStorage');
- this._config = $injector.get('config');
this._$http = $injector.get('$http');
this._telemetryUrl = $injector.get('telemetryUrl');
+ this._telemetryOptedIn = $injector.get('telemetryOptedIn');
this._attributes = this._storage.get(LOCALSTORAGE_KEY) || {};
this._fetchTelemetry = fetchTelemetry;
}
@@ -42,8 +41,8 @@ export class Telemetry {
* Check time interval passage
*/
_checkReportStatus() {
- // check if opt-in for telemetry is enabled in config
- if (this._config.get(CONFIG_TELEMETRY, false)) {
+ // check if opt-in for telemetry is enabled
+ if (this._telemetryOptedIn) {
// If the last report is empty it means we've never sent telemetry and
// now is the time to send it.
if (!this._get('lastReport')) {
@@ -78,13 +77,13 @@ export class Telemetry {
});
})
.then(response => {
- // we sent a report, so we need to record and store the current time stamp
+ // we sent a report, so we need to record and store the current time stamp
this._set('lastReport', Date.now());
this._saveToBrowser();
return response;
})
.catch(() => {
- // no ajaxErrorHandlers for telemetry
+ // no ajaxErrorHandlers for telemetry
return Promise.resolve(null);
});
}
diff --git a/x-pack/plugins/xpack_main/public/hacks/welcome_banner/__tests__/click_banner.js b/x-pack/plugins/xpack_main/public/hacks/welcome_banner/__tests__/click_banner.js
index 21b3ad9ccafe3..3ef092bde18ec 100644
--- a/x-pack/plugins/xpack_main/public/hacks/welcome_banner/__tests__/click_banner.js
+++ b/x-pack/plugins/xpack_main/public/hacks/welcome_banner/__tests__/click_banner.js
@@ -6,11 +6,54 @@
import expect from 'expect.js';
import sinon from 'sinon';
+import { uiModules } from 'ui/modules';
+
+uiModules.get('kibana')
+ // disable stat reporting while running tests,
+ // MockInjector used in these tests is not impacted
+ .constant('Notifier', function mockNotifier() { this.notify = sinon.stub(); })
+ .constant('telemetryOptedIn', null);
-import { CONFIG_TELEMETRY } from '../../../../common/constants';
import {
clickBanner,
} from '../click_banner';
+import { TelemetryOptInProvider } from '../../../services/telemetry_opt_in';
+
+const getMockInjector = ({ simulateFailure }) => {
+ const get = sinon.stub();
+
+ get.withArgs('telemetryOptedIn').returns(null);
+ get.withArgs('Notifier').returns(function mockNotifier() { this.notify = sinon.stub(); });
+
+ const mockHttp = {
+ post: sinon.stub()
+ };
+
+ if (simulateFailure) {
+ mockHttp.post.returns(Promise.reject(new Error('something happened')));
+ } else {
+ mockHttp.post.returns(Promise.resolve({}));
+ }
+
+ get.withArgs('$http').returns(mockHttp);
+
+ return { get };
+};
+
+const getTelemetryOptInProvider = ({ simulateFailure = false, simulateError = false } = {}) => {
+ const injector = getMockInjector({ simulateFailure });
+ const chrome = {
+ addBasePath: (url) => url
+ };
+
+ const provider = new TelemetryOptInProvider(injector, chrome);
+
+ if (simulateError) {
+ provider.setOptIn = () => Promise.reject('unhandled error');
+ }
+
+ return provider;
+};
describe('click_banner', () => {
@@ -18,17 +61,15 @@ describe('click_banner', () => {
const banners = {
remove: sinon.spy()
};
- const config = {
- set: sinon.stub()
- };
+
+ const telemetryOptInProvider = getTelemetryOptInProvider();
+
const bannerId = 'bruce-banner';
const optIn = true;
- config.set.withArgs(CONFIG_TELEMETRY, true).returns(Promise.resolve(true));
-
- await clickBanner(bannerId, config, optIn, { _banners: banners });
+ await clickBanner(bannerId, telemetryOptInProvider, optIn, { _banners: banners });
- expect(config.set.calledOnce).to.be(true);
+ expect(telemetryOptInProvider.getOptIn()).to.be(optIn);
expect(banners.remove.calledOnce).to.be(true);
expect(banners.remove.calledWith(bannerId)).to.be(true);
});
@@ -40,17 +81,13 @@ describe('click_banner', () => {
const banners = {
remove: sinon.spy()
};
- const config = {
- set: sinon.stub()
- };
+ const telemetryOptInProvider = getTelemetryOptInProvider({ simulateFailure: true });
const bannerId = 'bruce-banner';
const optIn = true;
- config.set.withArgs(CONFIG_TELEMETRY, true).returns(Promise.resolve(false));
-
- await clickBanner(bannerId, config, optIn, { _banners: banners, _toastNotifications: toastNotifications });
+ await clickBanner(bannerId, telemetryOptInProvider, optIn, { _banners: banners, _toastNotifications: toastNotifications });
- expect(config.set.calledOnce).to.be(true);
+ expect(telemetryOptInProvider.getOptIn()).to.be(null);
expect(toastNotifications.addDanger.calledOnce).to.be(true);
expect(banners.remove.notCalled).to.be(true);
});
@@ -62,17 +99,13 @@ describe('click_banner', () => {
const banners = {
remove: sinon.spy()
};
- const config = {
- set: sinon.stub()
- };
+ const telemetryOptInProvider = getTelemetryOptInProvider({ simulateError: true });
const bannerId = 'bruce-banner';
const optIn = false;
- config.set.withArgs(CONFIG_TELEMETRY, false).returns(Promise.reject());
-
- await clickBanner(bannerId, config, optIn, { _banners: banners, _toastNotifications: toastNotifications });
+ await clickBanner(bannerId, telemetryOptInProvider, optIn, { _banners: banners, _toastNotifications: toastNotifications });
- expect(config.set.calledOnce).to.be(true);
+ expect(telemetryOptInProvider.getOptIn()).to.be(null);
expect(toastNotifications.addDanger.calledOnce).to.be(true);
expect(banners.remove.notCalled).to.be(true);
});
diff --git a/x-pack/plugins/xpack_main/public/hacks/welcome_banner/__tests__/handle_old_settings.js b/x-pack/plugins/xpack_main/public/hacks/welcome_banner/__tests__/handle_old_settings.js
index 178bfb9cdfa2a..3ceb31cb2eb30 100644
--- a/x-pack/plugins/xpack_main/public/hacks/welcome_banner/__tests__/handle_old_settings.js
+++ b/x-pack/plugins/xpack_main/public/hacks/welcome_banner/__tests__/handle_old_settings.js
@@ -9,67 +9,143 @@ import sinon from 'sinon';
import { CONFIG_TELEMETRY } from '../../../../common/constants';
import { handleOldSettings } from '../handle_old_settings';
+import { TelemetryOptInProvider } from '../../../services/telemetry_opt_in';
+
+const getTelemetryOptInProvider = (enabled, { simulateFailure = false } = {}) => {
+ const $http = {
+ post: async () => {
+ if (simulateFailure) {
+ return Promise.reject(new Error('something happened'));
+ }
+ return {};
+ }
+ };
+
+ const chrome = {
+ addBasePath: url => url
+ };
+
+ const $injector = {
+ get: (key) => {
+ if (key === '$http') {
+ return $http;
+ }
+ if (key === 'telemetryOptedIn') {
+ return enabled;
+ }
+ if (key === 'Notifier') {
+ return function mockNotifier() {
+ this.notify = sinon.stub();
+ };
+ }
+ throw new Error(`unexpected mock injector usage for ${key}`);
+ }
+ };
+
+ return new TelemetryOptInProvider($injector, chrome);
+};
describe('handle_old_settings', () => {
- it('re-uses old setting and stays opted in', async () => {
+ it('re-uses old "allowReport" setting and stays opted in', async () => {
const config = {
get: sinon.stub(),
remove: sinon.spy(),
set: sinon.stub(),
};
+ const telemetryOptInProvider = getTelemetryOptInProvider(null);
+ expect(telemetryOptInProvider.getOptIn()).to.be(null);
+
config.get.withArgs('xPackMonitoring:allowReport', null).returns(true);
config.set.withArgs(CONFIG_TELEMETRY, true).returns(Promise.resolve(true));
- expect(await handleOldSettings(config)).to.be(false);
+ expect(await handleOldSettings(config, telemetryOptInProvider)).to.be(false);
+
+ expect(config.get.calledTwice).to.be(true);
+ expect(config.set.called).to.be(false);
- expect(config.get.calledOnce).to.be(true);
- expect(config.set.calledOnce).to.be(true);
- expect(config.set.getCall(0).args).to.eql([ CONFIG_TELEMETRY, true ]);
- expect(config.remove.calledTwice).to.be(true);
+ expect(config.remove.calledThrice).to.be(true);
expect(config.remove.getCall(0).args[0]).to.be('xPackMonitoring:allowReport');
expect(config.remove.getCall(1).args[0]).to.be('xPackMonitoring:showBanner');
+ expect(config.remove.getCall(2).args[0]).to.be(CONFIG_TELEMETRY);
+
+ expect(telemetryOptInProvider.getOptIn()).to.be(true);
});
- it('re-uses old setting and stays opted out', async () => {
+ it('re-uses old "telemetry:optIn" setting and stays opted in', async () => {
const config = {
get: sinon.stub(),
remove: sinon.spy(),
set: sinon.stub(),
};
+ const telemetryOptInProvider = getTelemetryOptInProvider(null);
+ expect(telemetryOptInProvider.getOptIn()).to.be(null);
+
config.get.withArgs('xPackMonitoring:allowReport', null).returns(false);
- config.set.withArgs(CONFIG_TELEMETRY, false).returns(Promise.resolve(true));
+ config.get.withArgs(CONFIG_TELEMETRY, null).returns(true);
+
+ expect(await handleOldSettings(config, telemetryOptInProvider)).to.be(false);
- expect(await handleOldSettings(config)).to.be(false);
+ expect(config.get.calledTwice).to.be(true);
+ expect(config.set.called).to.be(false);
- expect(config.get.calledOnce).to.be(true);
- expect(config.set.calledOnce).to.be(true);
- expect(config.set.getCall(0).args).to.eql([ CONFIG_TELEMETRY, false ]);
- expect(config.remove.calledTwice).to.be(true);
+ expect(config.remove.calledThrice).to.be(true);
expect(config.remove.getCall(0).args[0]).to.be('xPackMonitoring:allowReport');
expect(config.remove.getCall(1).args[0]).to.be('xPackMonitoring:showBanner');
+ expect(config.remove.getCall(2).args[0]).to.be(CONFIG_TELEMETRY);
+
+ expect(telemetryOptInProvider.getOptIn()).to.be(true);
});
- it('re-uses old setting and stays opted out', async () => {
+ it('re-uses old "allowReport" setting and stays opted out', async () => {
const config = {
get: sinon.stub(),
remove: sinon.spy(),
set: sinon.stub(),
};
+ const telemetryOptInProvider = getTelemetryOptInProvider(null);
+ expect(telemetryOptInProvider.getOptIn()).to.be(null);
+
config.get.withArgs('xPackMonitoring:allowReport', null).returns(false);
config.set.withArgs(CONFIG_TELEMETRY, false).returns(Promise.resolve(true));
- expect(await handleOldSettings(config)).to.be(false);
+ expect(await handleOldSettings(config, telemetryOptInProvider)).to.be(false);
+
+ expect(config.get.calledTwice).to.be(true);
+ expect(config.set.called).to.be(false);
+ expect(config.remove.calledThrice).to.be(true);
+ expect(config.remove.getCall(0).args[0]).to.be('xPackMonitoring:allowReport');
+ expect(config.remove.getCall(1).args[0]).to.be('xPackMonitoring:showBanner');
+ expect(config.remove.getCall(2).args[0]).to.be(CONFIG_TELEMETRY);
+
+ expect(telemetryOptInProvider.getOptIn()).to.be(false);
+ });
+
+ it('re-uses old "telemetry:optIn" setting and stays opted out', async () => {
+ const config = {
+ get: sinon.stub(),
+ remove: sinon.spy(),
+ set: sinon.stub(),
+ };
+
+ const telemetryOptInProvider = getTelemetryOptInProvider(null);
+
+ config.get.withArgs(CONFIG_TELEMETRY, null).returns(false);
+ config.get.withArgs('xPackMonitoring:allowReport', null).returns(true);
+
+ expect(await handleOldSettings(config, telemetryOptInProvider)).to.be(false);
- expect(config.get.calledOnce).to.be(true);
- expect(config.set.calledOnce).to.be(true);
- expect(config.set.getCall(0).args).to.eql([ CONFIG_TELEMETRY, false ]);
- expect(config.remove.calledTwice).to.be(true);
+ expect(config.get.calledTwice).to.be(true);
+ expect(config.set.called).to.be(false);
+ expect(config.remove.calledThrice).to.be(true);
expect(config.remove.getCall(0).args[0]).to.be('xPackMonitoring:allowReport');
expect(config.remove.getCall(1).args[0]).to.be('xPackMonitoring:showBanner');
+ expect(config.remove.getCall(2).args[0]).to.be(CONFIG_TELEMETRY);
+
+ expect(telemetryOptInProvider.getOptIn()).to.be(false);
});
it('acknowledges users old setting even if re-setting fails', async () => {
@@ -78,15 +154,17 @@ describe('handle_old_settings', () => {
set: sinon.stub(),
};
+ const telemetryOptInProvider = getTelemetryOptInProvider(null, { simulateFailure: true });
+
config.get.withArgs('xPackMonitoring:allowReport', null).returns(false);
+ //todo: make the new version of this fail!
config.set.withArgs(CONFIG_TELEMETRY, false).returns(Promise.resolve(false));
// note: because it doesn't remove the old settings _and_ returns false, there's no risk of suddenly being opted in
- expect(await handleOldSettings(config)).to.be(false);
+ expect(await handleOldSettings(config, telemetryOptInProvider)).to.be(false);
- expect(config.get.calledOnce).to.be(true);
- expect(config.set.calledOnce).to.be(true);
- expect(config.set.getCall(0).args).to.eql([ CONFIG_TELEMETRY, false ]);
+ expect(config.get.calledTwice).to.be(true);
+ expect(config.set.called).to.be(false);
});
it('removes show banner setting and presents user with choice', async () => {
@@ -95,12 +173,14 @@ describe('handle_old_settings', () => {
remove: sinon.spy(),
};
+ const telemetryOptInProvider = getTelemetryOptInProvider(null);
+
config.get.withArgs('xPackMonitoring:allowReport', null).returns(null);
config.get.withArgs('xPackMonitoring:showBanner', null).returns(false);
- expect(await handleOldSettings(config)).to.be(true);
+ expect(await handleOldSettings(config, telemetryOptInProvider)).to.be(true);
- expect(config.get.calledTwice).to.be(true);
+ expect(config.get.calledThrice).to.be(true);
expect(config.remove.calledOnce).to.be(true);
expect(config.remove.getCall(0).args[0]).to.be('xPackMonitoring:showBanner');
});
@@ -110,12 +190,14 @@ describe('handle_old_settings', () => {
get: sinon.stub(),
};
+ const telemetryOptInProvider = getTelemetryOptInProvider(null);
+
config.get.withArgs('xPackMonitoring:allowReport', null).returns(null);
config.get.withArgs('xPackMonitoring:showBanner', null).returns(null);
- expect(await handleOldSettings(config)).to.be(true);
+ expect(await handleOldSettings(config, telemetryOptInProvider)).to.be(true);
- expect(config.get.calledTwice).to.be(true);
+ expect(config.get.calledThrice).to.be(true);
});
-});
\ No newline at end of file
+});
diff --git a/x-pack/plugins/xpack_main/public/hacks/welcome_banner/__tests__/should_show_banner.js b/x-pack/plugins/xpack_main/public/hacks/welcome_banner/__tests__/should_show_banner.js
index a215d21e7cdbf..33fde83241f8a 100644
--- a/x-pack/plugins/xpack_main/public/hacks/welcome_banner/__tests__/should_show_banner.js
+++ b/x-pack/plugins/xpack_main/public/hacks/welcome_banner/__tests__/should_show_banner.js
@@ -9,11 +9,37 @@ import sinon from 'sinon';
import { CONFIG_TELEMETRY } from '../../../../common/constants';
import { shouldShowBanner } from '../should_show_banner';
+import { TelemetryOptInProvider } from '../../../services/telemetry_opt_in';
+
+const getMockInjector = ({ telemetryEnabled }) => {
+ const get = sinon.stub();
+
+ get.withArgs('telemetryOptedIn').returns(telemetryEnabled);
+ get.withArgs('Notifier').returns(function mockNotifier() { this.notify = sinon.stub(); });
+
+ const mockHttp = {
+ post: sinon.stub()
+ };
+
+ get.withArgs('$http').returns(mockHttp);
+
+ return { get };
+};
+
+const getTelemetryOptInProvider = ({ telemetryEnabled = null } = {}) => {
+ const injector = getMockInjector({ telemetryEnabled });
+ const chrome = {
+ addBasePath: (url) => url
+ };
+
+ return new TelemetryOptInProvider(injector, chrome);
+};
describe('should_show_banner', () => {
it('returns whatever handleOldSettings does when telemetry opt-in setting is unset', async () => {
const config = { get: sinon.stub() };
+ const telemetryOptInProvider = getTelemetryOptInProvider();
const handleOldSettingsTrue = sinon.stub();
const handleOldSettingsFalse = sinon.stub();
@@ -21,13 +47,13 @@ describe('should_show_banner', () => {
handleOldSettingsTrue.returns(Promise.resolve(true));
handleOldSettingsFalse.returns(Promise.resolve(false));
- const showBannerTrue = await shouldShowBanner(config, { _handleOldSettings: handleOldSettingsTrue });
- const showBannerFalse = await shouldShowBanner(config, { _handleOldSettings: handleOldSettingsFalse });
+ const showBannerTrue = await shouldShowBanner(telemetryOptInProvider, config, { _handleOldSettings: handleOldSettingsTrue });
+ const showBannerFalse = await shouldShowBanner(telemetryOptInProvider, config, { _handleOldSettings: handleOldSettingsFalse });
expect(showBannerTrue).to.be(true);
expect(showBannerFalse).to.be(false);
- expect(config.get.calledTwice).to.be(true);
+ expect(config.get.callCount).to.be(0);
expect(handleOldSettingsTrue.calledOnce).to.be(true);
expect(handleOldSettingsFalse.calledOnce).to.be(true);
});
@@ -35,17 +61,17 @@ describe('should_show_banner', () => {
it('returns false if telemetry opt-in setting is set to true', async () => {
const config = { get: sinon.stub() };
- config.get.withArgs(CONFIG_TELEMETRY, null).returns(true);
+ const telemetryOptInProvider = getTelemetryOptInProvider({ telemetryEnabled: true });
- expect(await shouldShowBanner(config)).to.be(false);
+ expect(await shouldShowBanner(telemetryOptInProvider, config)).to.be(false);
});
it('returns false if telemetry opt-in setting is set to false', async () => {
const config = { get: sinon.stub() };
- config.get.withArgs(CONFIG_TELEMETRY, null).returns(false);
+ const telemetryOptInProvider = getTelemetryOptInProvider({ telemetryEnabled: false });
- expect(await shouldShowBanner(config)).to.be(false);
+ expect(await shouldShowBanner(telemetryOptInProvider, config)).to.be(false);
});
});
\ No newline at end of file
diff --git a/x-pack/plugins/xpack_main/public/hacks/welcome_banner/click_banner.js b/x-pack/plugins/xpack_main/public/hacks/welcome_banner/click_banner.js
index ce0f2a98b416c..efc07f49c5864 100644
--- a/x-pack/plugins/xpack_main/public/hacks/welcome_banner/click_banner.js
+++ b/x-pack/plugins/xpack_main/public/hacks/welcome_banner/click_banner.js
@@ -12,22 +12,25 @@ import {
} from 'ui/notify';
import { EuiText } from '@elastic/eui';
-import { CONFIG_TELEMETRY } from '../../../common/constants';
-
/**
* Handle clicks from the user on the opt-in banner.
*
* @param {String} bannerId Banner ID to close upon success.
- * @param {Object} config Advanced settings configuration to set opt-in.
+ * @param {Object} telemetryOptInProvider the telemetry opt-in provider
* @param {Boolean} optIn {@code true} to opt into telemetry.
* @param {Object} _banners Singleton banners. Can be overridden for tests.
* @param {Object} _toastNotifications Singleton toast notifications. Can be overridden for tests.
*/
-export async function clickBanner(bannerId, config, optIn, { _banners = banners, _toastNotifications = toastNotifications } = { }) {
+export async function clickBanner(
+ bannerId,
+ telemetryOptInProvider,
+ optIn,
+ { _banners = banners, _toastNotifications = toastNotifications } = {}) {
+
let set = false;
try {
- set = await config.set(CONFIG_TELEMETRY, Boolean(optIn));
+ set = await telemetryOptInProvider.setOptIn(optIn);
} catch (err) {
// set is already false
console.log('Unexpected error while trying to save setting.', err);
@@ -37,10 +40,10 @@ export async function clickBanner(bannerId, config, optIn, { _banners = banners,
_banners.remove(bannerId);
} else {
_toastNotifications.addDanger({
- title: 'Advanced Setting Error',
+ title: 'Telemetry Error',
text: (
- Unable to save advanced setting.
+ Unable to save telemetry preference.
Check that Kibana and Elasticsearch are still running, then try again.
diff --git a/x-pack/plugins/xpack_main/public/hacks/welcome_banner/handle_old_settings.js b/x-pack/plugins/xpack_main/public/hacks/welcome_banner/handle_old_settings.js
index fba5a22d5b16f..4188676bad1e0 100644
--- a/x-pack/plugins/xpack_main/public/hacks/welcome_banner/handle_old_settings.js
+++ b/x-pack/plugins/xpack_main/public/hacks/welcome_banner/handle_old_settings.js
@@ -14,19 +14,31 @@ import { CONFIG_TELEMETRY } from '../../../common/constants';
* @param {Object} config The advanced settings config object.
* @return {Boolean} {@code true} if the banner should still be displayed. {@code false} if the banner should not be displayed.
*/
-export async function handleOldSettings(config) {
+export async function handleOldSettings(config, telemetryOptInProvider) {
const CONFIG_ALLOW_REPORT = 'xPackMonitoring:allowReport';
const CONFIG_SHOW_BANNER = 'xPackMonitoring:showBanner';
- const oldSetting = config.get(CONFIG_ALLOW_REPORT, null);
+ const oldAllowReportSetting = config.get(CONFIG_ALLOW_REPORT, null);
+ const oldTelemetrySetting = config.get(CONFIG_TELEMETRY, null);
+
+ let legacyOptInValue = null;
+
+ if (typeof oldTelemetrySetting === 'boolean') {
+ legacyOptInValue = oldTelemetrySetting;
+ } else if (typeof oldAllowReportSetting === 'boolean') {
+ legacyOptInValue = oldAllowReportSetting;
+ }
+
+ if (legacyOptInValue !== null) {
+ try {
+ await telemetryOptInProvider.setOptIn(legacyOptInValue);
- if (oldSetting !== null) {
- if (await config.set(CONFIG_TELEMETRY, Boolean(oldSetting))) {
// delete old keys once we've successfully changed the setting (if it fails, we just wait until next time)
config.remove(CONFIG_ALLOW_REPORT);
config.remove(CONFIG_SHOW_BANNER);
+ config.remove(CONFIG_TELEMETRY);
+ } finally {
+ return false;
}
-
- return false;
}
const oldShowSetting = config.get(CONFIG_SHOW_BANNER, null);
@@ -36,4 +48,4 @@ export async function handleOldSettings(config) {
}
return true;
-}
\ No newline at end of file
+}
diff --git a/x-pack/plugins/xpack_main/public/hacks/welcome_banner/inject_banner.js b/x-pack/plugins/xpack_main/public/hacks/welcome_banner/inject_banner.js
index 7fd8a5027d407..d3142fd3e2dac 100644
--- a/x-pack/plugins/xpack_main/public/hacks/welcome_banner/inject_banner.js
+++ b/x-pack/plugins/xpack_main/public/hacks/welcome_banner/inject_banner.js
@@ -9,6 +9,7 @@ import { PathProvider } from 'plugins/xpack_main/services/path';
import { fetchTelemetry } from '../fetch_telemetry';
import { renderBanner } from './render_banner';
import { shouldShowBanner } from './should_show_banner';
+import { TelemetryOptInProvider } from '../../services/telemetry_opt_in';
/**
* Add the Telemetry opt-in banner if the user has not already made a decision.
@@ -21,6 +22,7 @@ import { shouldShowBanner } from './should_show_banner';
async function asyncInjectBanner($injector) {
const telemetryEnabled = $injector.get('telemetryEnabled');
const Private = $injector.get('Private');
+ const telemetryOptInProvider = Private(TelemetryOptInProvider);
const config = $injector.get('config');
// no banner if the server config has telemetry disabled
@@ -39,10 +41,10 @@ async function asyncInjectBanner($injector) {
}
// determine if the banner should be displayed
- if (await shouldShowBanner(config)) {
- const $http = $injector.get('$http');
+ if (await shouldShowBanner(telemetryOptInProvider, config)) {
+ const $http = $injector.get("$http");
- renderBanner(config, () => fetchTelemetry($http));
+ renderBanner(telemetryOptInProvider, () => fetchTelemetry($http));
}
}
diff --git a/x-pack/plugins/xpack_main/public/hacks/welcome_banner/opt_in_banner_component.js b/x-pack/plugins/xpack_main/public/hacks/welcome_banner/opt_in_banner_component.js
index 805a9541191f5..30d19f4c32478 100644
--- a/x-pack/plugins/xpack_main/public/hacks/welcome_banner/opt_in_banner_component.js
+++ b/x-pack/plugins/xpack_main/public/hacks/welcome_banner/opt_in_banner_component.js
@@ -17,8 +17,8 @@ import {
EuiText,
} from '@elastic/eui';
-import { CONFIG_TELEMETRY_DESC } from '../../../common/constants';
-import { OptInExampleFlyout } from './opt_in_details_component';
+import { CONFIG_TELEMETRY_DESC, PRIVACY_STATEMENT_URL } from '../../../common/constants';
+import { OptInExampleFlyout } from '../../components';
/**
* React component for displaying the Telemetry opt-in banner.
@@ -63,7 +63,7 @@ export class OptInBanner extends Component {
)} or read our {(
telemetry privacy statement
@@ -84,7 +84,7 @@ export class OptInBanner extends Component {
} else {
title = (
- { CONFIG_TELEMETRY_DESC } {(
+ {CONFIG_TELEMETRY_DESC} {(
this.setState({ showDetails: true })}>
Read more
@@ -95,8 +95,8 @@ export class OptInBanner extends Component {
return (
- { details }
- { flyoutDetails }
+ {details}
+ {flyoutDetails}
diff --git a/x-pack/plugins/xpack_main/public/hacks/welcome_banner/render_banner.js b/x-pack/plugins/xpack_main/public/hacks/welcome_banner/render_banner.js
index 59ccced7ac714..1fa1287cc2d98 100644
--- a/x-pack/plugins/xpack_main/public/hacks/welcome_banner/render_banner.js
+++ b/x-pack/plugins/xpack_main/public/hacks/welcome_banner/render_banner.js
@@ -14,15 +14,15 @@ import { OptInBanner } from './opt_in_banner_component';
/**
* Render the Telemetry Opt-in banner.
*
- * @param {Object} config The advanced settings config.
+ * @param {Object} telemetryOptInProvider The telemetry opt-in provider.
* @param {Function} fetchTelemetry Function to pull telemetry on demand.
* @param {Object} _banners Banners singleton, which can be overridden for tests.
*/
-export function renderBanner(config, fetchTelemetry, { _banners = banners } = { }) {
+export function renderBanner(telemetryOptInProvider, fetchTelemetry, { _banners = banners } = {}) {
const bannerId = _banners.add({
component: (
clickBanner(bannerId, config, optIn)}
+ optInClick={optIn => clickBanner(bannerId, telemetryOptInProvider, optIn)}
fetchTelemetry={fetchTelemetry}
/>
),
diff --git a/x-pack/plugins/xpack_main/public/hacks/welcome_banner/should_show_banner.js b/x-pack/plugins/xpack_main/public/hacks/welcome_banner/should_show_banner.js
index 4e224173997b6..5685132a95061 100644
--- a/x-pack/plugins/xpack_main/public/hacks/welcome_banner/should_show_banner.js
+++ b/x-pack/plugins/xpack_main/public/hacks/welcome_banner/should_show_banner.js
@@ -4,7 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { CONFIG_TELEMETRY } from '../../../common/constants';
import { handleOldSettings } from './handle_old_settings';
/**
@@ -16,6 +15,6 @@ import { handleOldSettings } from './handle_old_settings';
* @param {Object} _handleOldSettings handleOldSettings function, but overridable for tests.
* @return {Boolean} {@code true} if the banner should be displayed. {@code false} otherwise.
*/
-export async function shouldShowBanner(config, { _handleOldSettings = handleOldSettings } = { }) {
- return config.get(CONFIG_TELEMETRY, null) === null && await _handleOldSettings(config);
-}
\ No newline at end of file
+export async function shouldShowBanner(telemetryOptInProvider, config, { _handleOldSettings = handleOldSettings } = {}) {
+ return telemetryOptInProvider.getOptIn() === null && await _handleOldSettings(config, telemetryOptInProvider);
+}
diff --git a/x-pack/plugins/xpack_main/public/services/telemetry_opt_in.js b/x-pack/plugins/xpack_main/public/services/telemetry_opt_in.js
new file mode 100644
index 0000000000000..b4070e4587f87
--- /dev/null
+++ b/x-pack/plugins/xpack_main/public/services/telemetry_opt_in.js
@@ -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;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import moment from 'moment';
+
+export function TelemetryOptInProvider($injector, chrome) {
+
+ const Notifier = $injector.get('Notifier');
+ const notify = new Notifier();
+ let currentOptInStatus = $injector.get('telemetryOptedIn');
+
+ return {
+ getOptIn: () => currentOptInStatus,
+ setOptIn: async (enabled) => {
+ const $http = $injector.get('$http');
+
+ try {
+ await $http.post(chrome.addBasePath('/api/telemetry/v1/optIn'), { enabled });
+ currentOptInStatus = enabled;
+ } catch (error) {
+ notify.error(error);
+ return false;
+ }
+
+ return true;
+ },
+ fetchExample: async () => {
+ const $http = $injector.get('$http');
+ return $http.post(chrome.addBasePath(`/api/telemetry/v1/clusters/_stats`), {
+ timeRange: {
+ min: moment().subtract(20, 'minutes').toISOString(),
+ max: moment().toISOString()
+ }
+ });
+ }
+ };
+}
diff --git a/x-pack/plugins/xpack_main/public/services/telemetry_opt_in.test.js b/x-pack/plugins/xpack_main/public/services/telemetry_opt_in.test.js
new file mode 100644
index 0000000000000..60c08e5a338e5
--- /dev/null
+++ b/x-pack/plugins/xpack_main/public/services/telemetry_opt_in.test.js
@@ -0,0 +1,89 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import { TelemetryOptInProvider } from "./telemetry_opt_in";
+
+describe('TelemetryOptInProvider', () => {
+ const setup = ({ optedIn, simulatePostError }) => {
+ const mockHttp = {
+ post: jest.fn(async () => {
+ if (simulatePostError) {
+ return Promise.reject("Something happened");
+ }
+ })
+ };
+
+ const mockChrome = {
+ addBasePath: (url) => url
+ };
+
+ class MockNotifier {
+ constructor() {
+ this.error = jest.fn();
+ }
+ }
+
+ const mockInjector = {
+ get: (key) => {
+ switch (key) {
+ case 'telemetryOptedIn': {
+ return optedIn;
+ }
+ case 'Notifier': {
+ return MockNotifier;
+ }
+ case '$http': {
+ return mockHttp;
+ }
+ default:
+ throw new Error('unexpected injector request: ' + key);
+ }
+ }
+ };
+
+ const provider = new TelemetryOptInProvider(mockInjector, mockChrome);
+ return {
+ provider,
+ mockHttp,
+ };
+ };
+
+
+ it('should return the current opt-in status', () => {
+ const { provider: optedInProvider } = setup({ optedIn: true });
+ expect(optedInProvider.getOptIn()).toEqual(true);
+
+ const { provider: optedOutProvider } = setup({ optedIn: false });
+ expect(optedOutProvider.getOptIn()).toEqual(false);
+ });
+
+ it('should allow an opt-out to take place', async () => {
+ const { provider, mockHttp } = setup({ optedIn: true });
+ await provider.setOptIn(false);
+
+ expect(mockHttp.post).toHaveBeenCalledWith(`/api/telemetry/v1/optIn`, { enabled: false });
+
+ expect(provider.getOptIn()).toEqual(false);
+ });
+
+ it('should allow an opt-in to take place', async () => {
+ const { provider, mockHttp } = setup({ optedIn: false });
+ await provider.setOptIn(true);
+
+ expect(mockHttp.post).toHaveBeenCalledWith(`/api/telemetry/v1/optIn`, { enabled: true });
+
+ expect(provider.getOptIn()).toEqual(true);
+ });
+
+ it('should gracefully handle errors', async () => {
+ const { provider, mockHttp } = setup({ optedIn: false, simulatePostError: true });
+ await provider.setOptIn(true);
+
+ expect(mockHttp.post).toHaveBeenCalledWith(`/api/telemetry/v1/optIn`, { enabled: true });
+
+ // opt-in change should not be reflected
+ expect(provider.getOptIn()).toEqual(false);
+ });
+});
\ No newline at end of file
diff --git a/x-pack/plugins/xpack_main/public/views/management/index.js b/x-pack/plugins/xpack_main/public/views/management/index.js
new file mode 100644
index 0000000000000..0ed6fe09ef80a
--- /dev/null
+++ b/x-pack/plugins/xpack_main/public/views/management/index.js
@@ -0,0 +1,7 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import './management';
diff --git a/x-pack/plugins/xpack_main/public/views/management/management.js b/x-pack/plugins/xpack_main/public/views/management/management.js
new file mode 100644
index 0000000000000..8c244f8ae933f
--- /dev/null
+++ b/x-pack/plugins/xpack_main/public/views/management/management.js
@@ -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;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import React from 'react';
+import routes from 'ui/routes';
+
+import { registerSettingsComponent, PAGE_FOOTER_COMPONENT } from 'ui/management';
+import { TelemetryOptInProvider } from '../../services/telemetry_opt_in';
+import { TelemetryForm } from '../../components';
+
+routes.defaults(/\/management/, {
+ resolve: {
+ telemetryManagementSection: function (Private) {
+ const telemetryOptInProvider = Private(TelemetryOptInProvider);
+ const Component = (props) => ;
+
+ registerSettingsComponent(PAGE_FOOTER_COMPONENT, Component, true);
+ }
+ }
+});
diff --git a/x-pack/plugins/xpack_main/server/lib/__tests__/replace_injected_vars.js b/x-pack/plugins/xpack_main/server/lib/__tests__/replace_injected_vars.js
index 283f3e09b9d60..f75ec5678f1c6 100644
--- a/x-pack/plugins/xpack_main/server/lib/__tests__/replace_injected_vars.js
+++ b/x-pack/plugins/xpack_main/server/lib/__tests__/replace_injected_vars.js
@@ -9,15 +9,40 @@ import expect from 'expect.js';
import { replaceInjectedVars } from '../replace_injected_vars';
+const buildRequest = (telemetryOptedIn = null) => {
+ const get = sinon.stub();
+ if (telemetryOptedIn === null) {
+ get.withArgs('telemetry', 'telemetry').returns(Promise.reject(new Error('not found exception')));
+ } else {
+ get.withArgs('telemetry', 'telemetry').returns(Promise.resolve({ attributes: { enabled: telemetryOptedIn } }));
+ }
+
+ return {
+ getSavedObjectsClient: () => {
+ return {
+ get,
+ create: sinon.stub(),
+
+ errors: {
+ isNotFoundError: (error) => {
+ return error.message === 'not found exception';
+ }
+ }
+ };
+ }
+ };
+};
+
describe('replaceInjectedVars uiExport', () => {
it('sends xpack info if request is authenticated and license is not basic', async () => {
const originalInjectedVars = { a: 1 };
- const request = {};
+ const request = buildRequest();
const server = mockServer();
const newVars = await replaceInjectedVars(originalInjectedVars, request, server);
expect(newVars).to.eql({
a: 1,
+ telemetryOptedIn: null,
xpackInitialInfo: {
b: 1
}
@@ -29,13 +54,14 @@ describe('replaceInjectedVars uiExport', () => {
it('sends the xpack info if security plugin is disabled', async () => {
const originalInjectedVars = { a: 1 };
- const request = {};
+ const request = buildRequest();
const server = mockServer();
delete server.plugins.security;
const newVars = await replaceInjectedVars(originalInjectedVars, request, server);
expect(newVars).to.eql({
a: 1,
+ telemetryOptedIn: null,
xpackInitialInfo: {
b: 1
}
@@ -44,13 +70,46 @@ describe('replaceInjectedVars uiExport', () => {
it('sends the xpack info if xpack license is basic', async () => {
const originalInjectedVars = { a: 1 };
- const request = {};
+ const request = buildRequest();
+ const server = mockServer();
+ server.plugins.xpack_main.info.license.isOneOf.returns(true);
+
+ const newVars = await replaceInjectedVars(originalInjectedVars, request, server);
+ expect(newVars).to.eql({
+ a: 1,
+ telemetryOptedIn: null,
+ xpackInitialInfo: {
+ b: 1
+ }
+ });
+ });
+
+ it('respects the telemetry opt-in document when opted-out', async () => {
+ const originalInjectedVars = { a: 1 };
+ const request = buildRequest(false);
+ const server = mockServer();
+ server.plugins.xpack_main.info.license.isOneOf.returns(true);
+
+ const newVars = await replaceInjectedVars(originalInjectedVars, request, server);
+ expect(newVars).to.eql({
+ a: 1,
+ telemetryOptedIn: false,
+ xpackInitialInfo: {
+ b: 1
+ }
+ });
+ });
+
+ it('respects the telemetry opt-in document when opted-in', async () => {
+ const originalInjectedVars = { a: 1 };
+ const request = buildRequest(true);
const server = mockServer();
server.plugins.xpack_main.info.license.isOneOf.returns(true);
const newVars = await replaceInjectedVars(originalInjectedVars, request, server);
expect(newVars).to.eql({
a: 1,
+ telemetryOptedIn: true,
xpackInitialInfo: {
b: 1
}
@@ -59,7 +118,7 @@ describe('replaceInjectedVars uiExport', () => {
it('sends the originalInjectedVars if not authenticated', async () => {
const originalInjectedVars = { a: 1 };
- const request = {};
+ const request = buildRequest();
const server = mockServer();
server.plugins.security.isAuthenticated.returns(false);
@@ -69,7 +128,7 @@ describe('replaceInjectedVars uiExport', () => {
it('sends the originalInjectedVars if xpack info is unavailable', async () => {
const originalInjectedVars = { a: 1 };
- const request = {};
+ const request = buildRequest();
const server = mockServer();
server.plugins.xpack_main.info.isAvailable.returns(false);
@@ -79,7 +138,7 @@ describe('replaceInjectedVars uiExport', () => {
it('sends the originalInjectedVars (with xpackInitialInfo = undefined) if security is disabled, xpack info is unavailable', async () => {
const originalInjectedVars = { a: 1 };
- const request = {};
+ const request = buildRequest();
const server = mockServer();
delete server.plugins.security;
server.plugins.xpack_main.info.isAvailable.returns(false);
@@ -87,13 +146,14 @@ describe('replaceInjectedVars uiExport', () => {
const newVars = await replaceInjectedVars(originalInjectedVars, request, server);
expect(newVars).to.eql({
a: 1,
+ telemetryOptedIn: null,
xpackInitialInfo: undefined
});
});
it('sends the originalInjectedVars if the license check result is not available', async () => {
const originalInjectedVars = { a: 1 };
- const request = {};
+ const request = buildRequest();
const server = mockServer();
server.plugins.xpack_main.info.feature().getLicenseCheckResults.returns(undefined);
diff --git a/x-pack/plugins/xpack_main/server/lib/get_telemetry_opt_in.js b/x-pack/plugins/xpack_main/server/lib/get_telemetry_opt_in.js
new file mode 100644
index 0000000000000..1f9d6c5849e2f
--- /dev/null
+++ b/x-pack/plugins/xpack_main/server/lib/get_telemetry_opt_in.js
@@ -0,0 +1,19 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export async function getTelemetryOptIn(request) {
+ const savedObjectsClient = request.getSavedObjectsClient();
+
+ try {
+ const { attributes } = await savedObjectsClient.get('telemetry', 'telemetry');
+ return attributes.enabled;
+ } catch (error) {
+ if (savedObjectsClient.errors.isNotFoundError(error)) {
+ return null;
+ }
+ throw error;
+ }
+}
diff --git a/x-pack/plugins/xpack_main/server/lib/replace_injected_vars.js b/x-pack/plugins/xpack_main/server/lib/replace_injected_vars.js
index 178795fd88ab6..990e4e1a7d53a 100644
--- a/x-pack/plugins/xpack_main/server/lib/replace_injected_vars.js
+++ b/x-pack/plugins/xpack_main/server/lib/replace_injected_vars.js
@@ -4,16 +4,19 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import { getTelemetryOptIn } from "./get_telemetry_opt_in";
+
export async function replaceInjectedVars(originalInjectedVars, request, server) {
const xpackInfo = server.plugins.xpack_main.info;
- const withXpackInfo = () => ({
+ const withXpackInfo = async () => ({
...originalInjectedVars,
+ telemetryOptedIn: await getTelemetryOptIn(request),
xpackInitialInfo: xpackInfo.isAvailable() ? xpackInfo.toJSON() : undefined
});
// security feature is disabled
if (!server.plugins.security) {
- return withXpackInfo();
+ return await withXpackInfo();
}
// not enough license info to make decision one way or another
@@ -23,7 +26,7 @@ export async function replaceInjectedVars(originalInjectedVars, request, server)
// authentication is not a thing you can do
if (xpackInfo.license.isOneOf('basic')) {
- return withXpackInfo();
+ return await withXpackInfo();
}
// request is not authenticated
@@ -32,5 +35,5 @@ export async function replaceInjectedVars(originalInjectedVars, request, server)
}
// plugin enabled, license is appropriate, request is authenticated
- return withXpackInfo();
+ return await withXpackInfo();
}
diff --git a/x-pack/plugins/xpack_main/server/routes/api/v1/telemetry/telemetry.js b/x-pack/plugins/xpack_main/server/routes/api/v1/telemetry/telemetry.js
index 00f1695fa789b..9700b527698f3 100644
--- a/x-pack/plugins/xpack_main/server/routes/api/v1/telemetry/telemetry.js
+++ b/x-pack/plugins/xpack_main/server/routes/api/v1/telemetry/telemetry.js
@@ -17,7 +17,7 @@ import { getAllStats, getLocalStats } from '../../../../lib/telemetry';
* @param {String} end The end time of the request.
* @return {Promise} An array of telemetry objects.
*/
-export async function getTelemetry(req, config, start, end, { _getAllStats = getAllStats, _getLocalStats = getLocalStats } = { }) {
+export async function getTelemetry(req, config, start, end, { _getAllStats = getAllStats, _getLocalStats = getLocalStats } = {}) {
let response = [];
if (config.get('xpack.monitoring.enabled')) {
@@ -26,13 +26,43 @@ export async function getTelemetry(req, config, start, end, { _getAllStats = get
if (!Array.isArray(response) || response.length === 0) {
// return it as an array for a consistent API response
- response = [ await _getLocalStats(req) ];
+ response = [await _getLocalStats(req)];
}
return response;
}
export function telemetryRoute(server) {
+ /**
+ * Change Telemetry Opt-In preference.
+ */
+ server.route({
+ method: 'POST',
+ path: '/api/telemetry/v1/optIn',
+ config: {
+ validate: {
+ payload: Joi.object({
+ enabled: Joi.bool().required()
+ })
+ }
+ },
+ handler: async (req, reply) => {
+ const savedObjectsClient = req.getSavedObjectsClient();
+ try {
+ await savedObjectsClient.create('telemetry', {
+ enabled: req.payload.enabled
+ }, {
+ id: 'telemetry',
+ overwrite: true,
+ });
+ } catch (err) {
+ return reply(wrap(err));
+ }
+ reply({}).code(200);
+ }
+ });
+
+
/**
* Telemetry Data
*
@@ -61,10 +91,10 @@ export function telemetryRoute(server) {
reply(await getTelemetry(req, config, start, end));
} catch (err) {
if (config.get('env.dev')) {
- // don't ignore errors when running in dev mode
+ // don't ignore errors when running in dev mode
reply(wrap(err));
} else {
- // ignore errors, return empty set and a 200
+ // ignore errors, return empty set and a 200
reply([]).code(200);
}
}
diff --git a/x-pack/test/rbac_api_integration/apis/privileges/index.js b/x-pack/test/rbac_api_integration/apis/privileges/index.js
index 9563a1b65540f..12ca92f3fe33c 100644
--- a/x-pack/test/rbac_api_integration/apis/privileges/index.js
+++ b/x-pack/test/rbac_api_integration/apis/privileges/index.js
@@ -35,6 +35,9 @@ export default function ({ getService }) {
'action:saved_objects/timelion-sheet/get',
'action:saved_objects/timelion-sheet/bulk_get',
'action:saved_objects/timelion-sheet/find',
+ 'action:saved_objects/telemetry/get',
+ 'action:saved_objects/telemetry/bulk_get',
+ 'action:saved_objects/telemetry/find',
'action:saved_objects/graph-workspace/get',
'action:saved_objects/graph-workspace/bulk_get',
'action:saved_objects/graph-workspace/find',
From 2d0b1ed9d31d6a391121165540e10819e8f0f2e6 Mon Sep 17 00:00:00 2001
From: Larry Gregory
Date: Mon, 10 Sep 2018 07:40:15 -0400
Subject: [PATCH 61/68] update snapshot
---
.../space_selector/__snapshots__/space_selector.test.js.snap | 3 ---
1 file changed, 3 deletions(-)
diff --git a/x-pack/plugins/spaces/public/views/space_selector/__snapshots__/space_selector.test.js.snap b/x-pack/plugins/spaces/public/views/space_selector/__snapshots__/space_selector.test.js.snap
index 21662970fe240..9f747481e626e 100644
--- a/x-pack/plugins/spaces/public/views/space_selector/__snapshots__/space_selector.test.js.snap
+++ b/x-pack/plugins/spaces/public/views/space_selector/__snapshots__/space_selector.test.js.snap
@@ -3,11 +3,9 @@
exports[`it renders without crashing 1`] = `
Date: Fri, 7 Sep 2018 16:07:20 -0400
Subject: [PATCH 62/68] update public api to use SpacesClient
---
.../api/__fixtures__/create_test_handler.ts | 61 ++++++++++++++++---
.../server/routes/api/public/delete.test.ts | 5 +-
.../spaces/server/routes/api/public/delete.ts | 20 +++---
.../server/routes/api/public/get.test.ts | 1 +
.../spaces/server/routes/api/public/get.ts | 27 ++++----
.../server/routes/api/public/post.test.ts | 9 +--
.../spaces/server/routes/api/public/post.ts | 13 ++--
.../server/routes/api/public/put.test.ts | 15 +++--
.../spaces/server/routes/api/public/put.ts | 17 +++---
.../server/routes/api/v1/spaces.test.ts | 1 +
.../spaces/server/routes/api/v1/spaces.ts | 12 +++-
.../server/routes/lib/get_space_by_id.ts | 11 +++-
12 files changed, 131 insertions(+), 61 deletions(-)
diff --git a/x-pack/plugins/spaces/server/routes/api/__fixtures__/create_test_handler.ts b/x-pack/plugins/spaces/server/routes/api/__fixtures__/create_test_handler.ts
index a184f21076d4f..8276b9d3478bc 100644
--- a/x-pack/plugins/spaces/server/routes/api/__fixtures__/create_test_handler.ts
+++ b/x-pack/plugins/spaces/server/routes/api/__fixtures__/create_test_handler.ts
@@ -6,6 +6,7 @@
// @ts-ignore
import { Server } from 'hapi';
+import { SpacesClient } from '../../../lib/spaces_client';
import { createSpaces } from './create_spaces';
export interface TestConfig {
@@ -17,13 +18,14 @@ export interface TestOptions {
testConfig?: TestConfig;
payload?: any;
preCheckLicenseImpl?: (req: any, reply: any) => any;
+ expectSpacesClientCall?: boolean;
}
export type TeardownFn = () => void;
export interface RequestRunnerResult {
server: any;
- mockSavedObjectsClient: any;
+ mockSavedObjectsRepository: any;
response: any;
}
@@ -56,6 +58,7 @@ export function createTestHandler(initApiFn: (server: any, preCheckLicenseImpl:
testConfig = {},
payload,
preCheckLicenseImpl = defaultPreCheckLicenseImpl,
+ expectSpacesClientCall = true,
} = options;
let pre = jest.fn();
@@ -89,10 +92,13 @@ export function createTestHandler(initApiFn: (server: any, preCheckLicenseImpl:
server.decorate('request', 'getBasePath', jest.fn());
server.decorate('request', 'setBasePath', jest.fn());
- // Mock server.getSavedObjectsClient()
- const mockSavedObjectsClient = {
+ const mockSavedObjectsRepository = {
get: jest.fn((type, id) => {
- return spaces.filter(s => s.id === id)[0];
+ const result = spaces.filter(s => s.id === id);
+ if (!result.length) {
+ throw new Error(`not found: [${type}:${id}]`);
+ }
+ return result[0];
}),
find: jest.fn(() => {
return {
@@ -102,20 +108,46 @@ export function createTestHandler(initApiFn: (server: any, preCheckLicenseImpl:
}),
create: jest.fn(() => ({})),
update: jest.fn(() => ({})),
- delete: jest.fn(),
+ delete: jest.fn((type: string, id: string) => {
+ return {};
+ }),
errors: {
- isNotFoundError: jest.fn(() => true),
+ isNotFoundError: jest.fn((e: any) => e.message.startsWith('not found:')),
+ },
+ };
+
+ server.savedObject = {
+ SavedObjectsClient: {
+ errors: {
+ isNotFoundError: jest.fn((e: any) => e.message.startsWith('not found:')),
+ },
},
};
- server.decorate('request', 'getSavedObjectsClient', () => mockSavedObjectsClient);
+ server.plugins.spaces = {
+ spacesClient: {
+ getScopedClient: jest.fn((req: any) => {
+ return new SpacesClient(
+ null,
+ mockSavedObjectsRepository,
+ mockSavedObjectsRepository,
+ req
+ );
+ }),
+ },
+ };
teardowns.push(() => server.stop());
+ const headers = {
+ authorization: 'foo',
+ };
+
const testRun = async () => {
const response = await server.inject({
method,
url: path,
+ headers,
payload,
});
@@ -125,12 +157,25 @@ export function createTestHandler(initApiFn: (server: any, preCheckLicenseImpl:
expect(pre).not.toHaveBeenCalled();
}
+ if (expectSpacesClientCall) {
+ expect(server.plugins.spaces.spacesClient.getScopedClient).toHaveBeenCalledWith(
+ expect.objectContaining({
+ headers: expect.objectContaining({
+ authorization: headers.authorization,
+ }),
+ })
+ );
+ } else {
+ expect(server.plugins.spaces.spacesClient.getScopedClient).not.toHaveBeenCalled();
+ }
+
return response;
};
return {
server,
- mockSavedObjectsClient,
+ headers,
+ mockSavedObjectsRepository,
response: await testRun(),
};
};
diff --git a/x-pack/plugins/spaces/server/routes/api/public/delete.test.ts b/x-pack/plugins/spaces/server/routes/api/public/delete.test.ts
index 523b5a2cb2e7b..21948e28c56d6 100644
--- a/x-pack/plugins/spaces/server/routes/api/public/delete.test.ts
+++ b/x-pack/plugins/spaces/server/routes/api/public/delete.test.ts
@@ -52,6 +52,7 @@ describe('Spaces Public API', () => {
const { response } = await request('DELETE', '/api/spaces/space/a-space', {
preCheckLicenseImpl: (req: any, reply: any) =>
reply(Boom.forbidden('test forbidden message')),
+ expectSpacesClientCall: false,
});
const { statusCode, payload } = response;
@@ -62,12 +63,12 @@ describe('Spaces Public API', () => {
});
});
- test('DELETE spaces/{id} pretends to delete a non-existent space', async () => {
+ test('DELETE spaces/{id} throws when deleting a non-existent space', async () => {
const { response } = await request('DELETE', '/api/spaces/space/not-a-space');
const { statusCode } = response;
- expect(statusCode).toEqual(204);
+ expect(statusCode).toEqual(404);
});
test(`'DELETE spaces/{id}' cannot delete reserved spaces`, async () => {
diff --git a/x-pack/plugins/spaces/server/routes/api/public/delete.ts b/x-pack/plugins/spaces/server/routes/api/public/delete.ts
index 9937b786ccfa7..06062fbda952e 100644
--- a/x-pack/plugins/spaces/server/routes/api/public/delete.ts
+++ b/x-pack/plugins/spaces/server/routes/api/public/delete.ts
@@ -5,31 +5,29 @@
*/
import Boom from 'boom';
-import { isReservedSpace } from '../../../../common/is_reserved_space';
import { wrapError } from '../../../lib/errors';
-import { getSpaceById } from '../../lib';
+import { SpacesClient } from '../../../lib/spaces_client';
export function initDeleteSpacesApi(server: any, routePreCheckLicenseFn: any) {
server.route({
method: 'DELETE',
path: '/api/spaces/space/{id}',
async handler(request: any, reply: any) {
- const client = request.getSavedObjectsClient();
+ const { SavedObjectsClient } = server.savedObject;
+ const spacesClient: SpacesClient = server.plugins.spaces.spacesClient.getScopedClient(
+ request
+ );
const id = request.params.id;
let result;
try {
- const existingSpace = await getSpaceById(client, id);
- if (isReservedSpace(existingSpace)) {
- return reply(
- wrapError(Boom.badRequest('This Space cannot be deleted because it is reserved.'))
- );
- }
-
- result = await client.delete('space', id);
+ result = await spacesClient.delete(id);
} catch (error) {
+ if (SavedObjectsClient.errors.isNotFoundError(error)) {
+ return reply(Boom.notFound());
+ }
return reply(wrapError(error));
}
diff --git a/x-pack/plugins/spaces/server/routes/api/public/get.test.ts b/x-pack/plugins/spaces/server/routes/api/public/get.test.ts
index 4d04759b283a8..ad3e758853e01 100644
--- a/x-pack/plugins/spaces/server/routes/api/public/get.test.ts
+++ b/x-pack/plugins/spaces/server/routes/api/public/get.test.ts
@@ -56,6 +56,7 @@ describe('GET spaces', () => {
const { response } = await request('GET', '/api/spaces/space', {
preCheckLicenseImpl: (req: any, reply: any) =>
reply(Boom.forbidden('test forbidden message')),
+ expectSpacesClientCall: false,
});
const { statusCode, payload } = response;
diff --git a/x-pack/plugins/spaces/server/routes/api/public/get.ts b/x-pack/plugins/spaces/server/routes/api/public/get.ts
index 95f1d273a8f6b..c319b1a79c54e 100644
--- a/x-pack/plugins/spaces/server/routes/api/public/get.ts
+++ b/x-pack/plugins/spaces/server/routes/api/public/get.ts
@@ -5,25 +5,23 @@
*/
import Boom from 'boom';
+import { Space } from '../../../../common/model/space';
import { wrapError } from '../../../lib/errors';
-import { convertSavedObjectToSpace } from '../../lib';
+import { SpacesClient } from '../../../lib/spaces_client';
export function initGetSpacesApi(server: any, routePreCheckLicenseFn: any) {
server.route({
method: 'GET',
path: '/api/spaces/space',
async handler(request: any, reply: any) {
- const client = request.getSavedObjectsClient();
+ const spacesClient: SpacesClient = server.plugins.spaces.spacesClient.getScopedClient(
+ request
+ );
- let spaces;
+ let spaces: Space[];
try {
- const result = await client.find({
- type: 'space',
- sortField: 'name.keyword',
- });
-
- spaces = result.saved_objects.map(convertSavedObjectToSpace);
+ spaces = await spacesClient.getAll();
} catch (error) {
return reply(wrapError(error));
}
@@ -41,14 +39,15 @@ export function initGetSpacesApi(server: any, routePreCheckLicenseFn: any) {
async handler(request: any, reply: any) {
const spaceId = request.params.id;
- const client = request.getSavedObjectsClient();
+ const { SavedObjectsClient } = server.savedObject;
+ const spacesClient: SpacesClient = server.plugins.spaces.spacesClient.getScopedClient(
+ request
+ );
try {
- const response = await client.get('space', spaceId);
-
- return reply(convertSavedObjectToSpace(response));
+ return reply(await spacesClient.get(spaceId));
} catch (error) {
- if (client.errors.isNotFoundError(error)) {
+ if (SavedObjectsClient.errors.isNotFoundError(error)) {
return reply(Boom.notFound());
}
return reply(wrapError(error));
diff --git a/x-pack/plugins/spaces/server/routes/api/public/post.test.ts b/x-pack/plugins/spaces/server/routes/api/public/post.test.ts
index f97931d36ed66..91e3b4c49fd84 100644
--- a/x-pack/plugins/spaces/server/routes/api/public/post.test.ts
+++ b/x-pack/plugins/spaces/server/routes/api/public/post.test.ts
@@ -48,18 +48,18 @@ describe('Spaces Public API', () => {
description: 'with a description',
};
- const { mockSavedObjectsClient, response } = await request('POST', '/api/spaces/space', {
+ const { mockSavedObjectsRepository, response } = await request('POST', '/api/spaces/space', {
payload,
});
const { statusCode } = response;
expect(statusCode).toEqual(200);
- expect(mockSavedObjectsClient.create).toHaveBeenCalledTimes(1);
- expect(mockSavedObjectsClient.create).toHaveBeenCalledWith(
+ expect(mockSavedObjectsRepository.create).toHaveBeenCalledTimes(1);
+ expect(mockSavedObjectsRepository.create).toHaveBeenCalledWith(
'space',
{ name: 'my new space', description: 'with a description' },
- { id: 'my-space-id', overwrite: false }
+ { id: 'my-space-id' }
);
});
@@ -73,6 +73,7 @@ describe('Spaces Public API', () => {
const { response } = await request('POST', '/api/spaces/space', {
preCheckLicenseImpl: (req: any, reply: any) =>
reply(Boom.forbidden('test forbidden message')),
+ expectSpacesClientCall: false,
payload,
});
diff --git a/x-pack/plugins/spaces/server/routes/api/public/post.ts b/x-pack/plugins/spaces/server/routes/api/public/post.ts
index fd51390af5023..4fdfa1d605fd3 100644
--- a/x-pack/plugins/spaces/server/routes/api/public/post.ts
+++ b/x-pack/plugins/spaces/server/routes/api/public/post.ts
@@ -5,9 +5,9 @@
*/
import Boom from 'boom';
-import { omit } from 'lodash';
import { wrapError } from '../../../lib/errors';
import { spaceSchema } from '../../../lib/space_schema';
+import { SpacesClient } from '../../../lib/spaces_client';
import { getSpaceById } from '../../lib';
export function initPostSpacesApi(server: any, routePreCheckLicenseFn: any) {
@@ -15,13 +15,16 @@ export function initPostSpacesApi(server: any, routePreCheckLicenseFn: any) {
method: 'POST',
path: '/api/spaces/space',
async handler(request: any, reply: any) {
- const client = request.getSavedObjectsClient();
+ const { SavedObjectsClient } = server.savedObject;
+ const spacesClient: SpacesClient = server.plugins.spaces.spacesClient.getScopedClient(
+ request
+ );
- const space = omit(request.payload, ['id', '_reserved']);
+ const space = request.payload;
const id = request.payload.id;
- const existingSpace = await getSpaceById(client, id);
+ const existingSpace = await getSpaceById(spacesClient, id, SavedObjectsClient.errors);
if (existingSpace) {
return reply(
Boom.conflict(
@@ -31,7 +34,7 @@ export function initPostSpacesApi(server: any, routePreCheckLicenseFn: any) {
}
try {
- return reply(await client.create('space', { ...space }, { id, overwrite: false }));
+ return reply(await spacesClient.create({ ...space }));
} catch (error) {
return reply(wrapError(error));
}
diff --git a/x-pack/plugins/spaces/server/routes/api/public/put.test.ts b/x-pack/plugins/spaces/server/routes/api/public/put.test.ts
index 2af4fc9bbeaf3..e02fb58da1d61 100644
--- a/x-pack/plugins/spaces/server/routes/api/public/put.test.ts
+++ b/x-pack/plugins/spaces/server/routes/api/public/put.test.ts
@@ -46,15 +46,19 @@ describe('Spaces Public API', () => {
description: 'with a description',
};
- const { mockSavedObjectsClient, response } = await request('PUT', '/api/spaces/space/a-space', {
- payload,
- });
+ const { mockSavedObjectsRepository, response } = await request(
+ 'PUT',
+ '/api/spaces/space/a-space',
+ {
+ payload,
+ }
+ );
const { statusCode } = response;
expect(statusCode).toEqual(200);
- expect(mockSavedObjectsClient.update).toHaveBeenCalledTimes(1);
- expect(mockSavedObjectsClient.update).toHaveBeenCalledWith('space', 'a-space', {
+ expect(mockSavedObjectsRepository.update).toHaveBeenCalledTimes(1);
+ expect(mockSavedObjectsRepository.update).toHaveBeenCalledWith('space', 'a-space', {
name: 'my updated space',
description: 'with a description',
});
@@ -70,6 +74,7 @@ describe('Spaces Public API', () => {
const { response } = await request('PUT', '/api/spaces/space/a-space', {
preCheckLicenseImpl: (req: any, reply: any) =>
reply(Boom.forbidden('test forbidden message')),
+ expectSpacesClientCall: false,
payload,
});
diff --git a/x-pack/plugins/spaces/server/routes/api/public/put.ts b/x-pack/plugins/spaces/server/routes/api/public/put.ts
index 093d7c777e786..746a9e294ca13 100644
--- a/x-pack/plugins/spaces/server/routes/api/public/put.ts
+++ b/x-pack/plugins/spaces/server/routes/api/public/put.ts
@@ -9,19 +9,23 @@ import { omit } from 'lodash';
import { Space } from '../../../../common/model/space';
import { wrapError } from '../../../lib/errors';
import { spaceSchema } from '../../../lib/space_schema';
-import { convertSavedObjectToSpace, getSpaceById } from '../../lib';
+import { SpacesClient } from '../../../lib/spaces_client';
+import { getSpaceById } from '../../lib';
export function initPutSpacesApi(server: any, routePreCheckLicenseFn: any) {
server.route({
method: 'PUT',
path: '/api/spaces/space/{id}',
async handler(request: any, reply: any) {
- const client = request.getSavedObjectsClient();
+ const { SavedObjectsClient } = server.savedObject;
+ const spacesClient: SpacesClient = server.plugins.spaces.spacesClient.getScopedClient(
+ request
+ );
const space: Space = omit(request.payload, ['id']);
const id = request.params.id;
- const existingSpace = await getSpaceById(client, id);
+ const existingSpace = await getSpaceById(spacesClient, id, SavedObjectsClient.errors);
if (existingSpace) {
space._reserved = existingSpace._reserved;
@@ -29,15 +33,14 @@ export function initPutSpacesApi(server: any, routePreCheckLicenseFn: any) {
return reply(Boom.notFound(`Unable to find space with ID ${id}`));
}
- let result;
+ let result: Space;
try {
- result = await client.update('space', id, { ...space });
+ result = await spacesClient.update(id, { ...space });
} catch (error) {
return reply(wrapError(error));
}
- const updatedSpace = convertSavedObjectToSpace(result);
- return reply(updatedSpace);
+ return reply(result);
},
config: {
validate: {
diff --git a/x-pack/plugins/spaces/server/routes/api/v1/spaces.test.ts b/x-pack/plugins/spaces/server/routes/api/v1/spaces.test.ts
index bbdab1be34910..0758ceb32746c 100644
--- a/x-pack/plugins/spaces/server/routes/api/v1/spaces.test.ts
+++ b/x-pack/plugins/spaces/server/routes/api/v1/spaces.test.ts
@@ -56,6 +56,7 @@ describe('Spaces API', () => {
const { response } = await request('POST', '/api/spaces/v1/space/a-space/select', {
preCheckLicenseImpl: (req: any, reply: any) =>
reply(Boom.forbidden('test forbidden message')),
+ expectSpacesClientCall: false,
});
const { statusCode, payload } = response;
diff --git a/x-pack/plugins/spaces/server/routes/api/v1/spaces.ts b/x-pack/plugins/spaces/server/routes/api/v1/spaces.ts
index 0233cb76b96d8..ae3332b42b88c 100644
--- a/x-pack/plugins/spaces/server/routes/api/v1/spaces.ts
+++ b/x-pack/plugins/spaces/server/routes/api/v1/spaces.ts
@@ -7,6 +7,7 @@
import Boom from 'boom';
import { Space } from '../../../../common/model/space';
import { wrapError } from '../../../lib/errors';
+import { SpacesClient } from '../../../lib/spaces_client';
import { addSpaceIdToPath } from '../../../lib/spaces_url_parser';
import { getSpaceById } from '../../lib';
@@ -15,12 +16,19 @@ export function initPrivateSpacesApi(server: any, routePreCheckLicenseFn: any) {
method: 'POST',
path: '/api/spaces/v1/space/{id}/select',
async handler(request: any, reply: any) {
- const client = request.getSavedObjectsClient();
+ const { SavedObjectsClient } = server.savedObject;
+ const spacesClient: SpacesClient = server.plugins.spaces.spacesClient.getScopedClient(
+ request
+ );
const id = request.params.id;
try {
- const existingSpace: Space | null = await getSpaceById(client, id);
+ const existingSpace: Space | null = await getSpaceById(
+ spacesClient,
+ id,
+ SavedObjectsClient.errors
+ );
if (!existingSpace) {
return reply(Boom.notFound());
}
diff --git a/x-pack/plugins/spaces/server/routes/lib/get_space_by_id.ts b/x-pack/plugins/spaces/server/routes/lib/get_space_by_id.ts
index 4143c09a79a93..eaa789b32c39b 100644
--- a/x-pack/plugins/spaces/server/routes/lib/get_space_by_id.ts
+++ b/x-pack/plugins/spaces/server/routes/lib/get_space_by_id.ts
@@ -4,14 +4,19 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { Space } from '../../../common/model/space';
+import { SpacesClient } from '../../lib/spaces_client';
import { convertSavedObjectToSpace } from './convert_saved_object_to_space';
-export async function getSpaceById(client: any, spaceId: string): Promise
{
+export async function getSpaceById(
+ client: SpacesClient,
+ spaceId: string,
+ errors: any
+): Promise {
try {
- const existingSpace = await client.get('space', spaceId);
+ const existingSpace = await client.get(spaceId);
return convertSavedObjectToSpace(existingSpace);
} catch (error) {
- if (client.errors.isNotFoundError(error)) {
+ if (errors.isNotFoundError(error)) {
return null;
}
throw error;
From 81b785b20edc8b911be3ab37baa72a9e28b0bbdf Mon Sep 17 00:00:00 2001
From: Larry Gregory
Date: Fri, 7 Sep 2018 16:14:46 -0400
Subject: [PATCH 63/68] fix
---
.../server/routes/api/__fixtures__/create_test_handler.ts | 2 +-
x-pack/plugins/spaces/server/routes/api/public/delete.ts | 2 +-
x-pack/plugins/spaces/server/routes/api/public/get.ts | 2 +-
x-pack/plugins/spaces/server/routes/api/public/post.ts | 2 +-
x-pack/plugins/spaces/server/routes/api/public/put.ts | 2 +-
x-pack/plugins/spaces/server/routes/api/v1/spaces.ts | 2 +-
6 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/x-pack/plugins/spaces/server/routes/api/__fixtures__/create_test_handler.ts b/x-pack/plugins/spaces/server/routes/api/__fixtures__/create_test_handler.ts
index 8276b9d3478bc..d18c4909af1a4 100644
--- a/x-pack/plugins/spaces/server/routes/api/__fixtures__/create_test_handler.ts
+++ b/x-pack/plugins/spaces/server/routes/api/__fixtures__/create_test_handler.ts
@@ -116,7 +116,7 @@ export function createTestHandler(initApiFn: (server: any, preCheckLicenseImpl:
},
};
- server.savedObject = {
+ server.savedObjects = {
SavedObjectsClient: {
errors: {
isNotFoundError: jest.fn((e: any) => e.message.startsWith('not found:')),
diff --git a/x-pack/plugins/spaces/server/routes/api/public/delete.ts b/x-pack/plugins/spaces/server/routes/api/public/delete.ts
index 06062fbda952e..080c765dd4a44 100644
--- a/x-pack/plugins/spaces/server/routes/api/public/delete.ts
+++ b/x-pack/plugins/spaces/server/routes/api/public/delete.ts
@@ -13,7 +13,7 @@ export function initDeleteSpacesApi(server: any, routePreCheckLicenseFn: any) {
method: 'DELETE',
path: '/api/spaces/space/{id}',
async handler(request: any, reply: any) {
- const { SavedObjectsClient } = server.savedObject;
+ const { SavedObjectsClient } = server.savedObjects;
const spacesClient: SpacesClient = server.plugins.spaces.spacesClient.getScopedClient(
request
);
diff --git a/x-pack/plugins/spaces/server/routes/api/public/get.ts b/x-pack/plugins/spaces/server/routes/api/public/get.ts
index c319b1a79c54e..ae3a083c50123 100644
--- a/x-pack/plugins/spaces/server/routes/api/public/get.ts
+++ b/x-pack/plugins/spaces/server/routes/api/public/get.ts
@@ -39,7 +39,7 @@ export function initGetSpacesApi(server: any, routePreCheckLicenseFn: any) {
async handler(request: any, reply: any) {
const spaceId = request.params.id;
- const { SavedObjectsClient } = server.savedObject;
+ const { SavedObjectsClient } = server.savedObjects;
const spacesClient: SpacesClient = server.plugins.spaces.spacesClient.getScopedClient(
request
);
diff --git a/x-pack/plugins/spaces/server/routes/api/public/post.ts b/x-pack/plugins/spaces/server/routes/api/public/post.ts
index 4fdfa1d605fd3..f71e9a6f57788 100644
--- a/x-pack/plugins/spaces/server/routes/api/public/post.ts
+++ b/x-pack/plugins/spaces/server/routes/api/public/post.ts
@@ -15,7 +15,7 @@ export function initPostSpacesApi(server: any, routePreCheckLicenseFn: any) {
method: 'POST',
path: '/api/spaces/space',
async handler(request: any, reply: any) {
- const { SavedObjectsClient } = server.savedObject;
+ const { SavedObjectsClient } = server.savedObjects;
const spacesClient: SpacesClient = server.plugins.spaces.spacesClient.getScopedClient(
request
);
diff --git a/x-pack/plugins/spaces/server/routes/api/public/put.ts b/x-pack/plugins/spaces/server/routes/api/public/put.ts
index 746a9e294ca13..34f3614725234 100644
--- a/x-pack/plugins/spaces/server/routes/api/public/put.ts
+++ b/x-pack/plugins/spaces/server/routes/api/public/put.ts
@@ -17,7 +17,7 @@ export function initPutSpacesApi(server: any, routePreCheckLicenseFn: any) {
method: 'PUT',
path: '/api/spaces/space/{id}',
async handler(request: any, reply: any) {
- const { SavedObjectsClient } = server.savedObject;
+ const { SavedObjectsClient } = server.savedObjects;
const spacesClient: SpacesClient = server.plugins.spaces.spacesClient.getScopedClient(
request
);
diff --git a/x-pack/plugins/spaces/server/routes/api/v1/spaces.ts b/x-pack/plugins/spaces/server/routes/api/v1/spaces.ts
index ae3332b42b88c..6f09d1831bff9 100644
--- a/x-pack/plugins/spaces/server/routes/api/v1/spaces.ts
+++ b/x-pack/plugins/spaces/server/routes/api/v1/spaces.ts
@@ -16,7 +16,7 @@ export function initPrivateSpacesApi(server: any, routePreCheckLicenseFn: any) {
method: 'POST',
path: '/api/spaces/v1/space/{id}/select',
async handler(request: any, reply: any) {
- const { SavedObjectsClient } = server.savedObject;
+ const { SavedObjectsClient } = server.savedObjects;
const spacesClient: SpacesClient = server.plugins.spaces.spacesClient.getScopedClient(
request
);
From b3b04f3df8305d08a02f9eeba22bca32dbd463c2 Mon Sep 17 00:00:00 2001
From: Larry Gregory
Date: Fri, 7 Sep 2018 17:20:45 -0400
Subject: [PATCH 64/68] test and api fixes
---
.../api/__fixtures__/create_test_handler.ts | 18 +++++++++++++-----
.../server/routes/api/public/post.test.ts | 3 +--
.../spaces/server/routes/api/public/post.ts | 17 ++++-------------
.../spaces/server/routes/api/public/put.ts | 15 ++++-----------
.../common/suites/create.ts | 11 ++++-------
.../common/suites/delete.ts | 7 +++----
.../common/suites/get.ts | 5 ++---
.../common/suites/get_all.ts | 2 +-
.../common/suites/select.ts | 3 +--
.../common/suites/update.ts | 5 ++---
.../security_and_spaces/apis/get.ts | 12 ++++++------
.../security_and_spaces/apis/select.ts | 2 +-
.../spaces_only/apis/get.ts | 2 +-
.../spaces_only/apis/select.ts | 2 +-
14 files changed, 44 insertions(+), 60 deletions(-)
diff --git a/x-pack/plugins/spaces/server/routes/api/__fixtures__/create_test_handler.ts b/x-pack/plugins/spaces/server/routes/api/__fixtures__/create_test_handler.ts
index d18c4909af1a4..1da60f49a7e3a 100644
--- a/x-pack/plugins/spaces/server/routes/api/__fixtures__/create_test_handler.ts
+++ b/x-pack/plugins/spaces/server/routes/api/__fixtures__/create_test_handler.ts
@@ -106,20 +106,28 @@ export function createTestHandler(initApiFn: (server: any, preCheckLicenseImpl:
saved_objects: spaces,
};
}),
- create: jest.fn(() => ({})),
- update: jest.fn(() => ({})),
+ create: jest.fn((type, attributes, { id }) => {
+ if (spaces.find(s => s.id === id)) {
+ throw new Error('conflict');
+ }
+ return {};
+ }),
+ update: jest.fn((type, id) => {
+ if (!spaces.find(s => s.id === id)) {
+ throw new Error('not found: during update');
+ }
+ return {};
+ }),
delete: jest.fn((type: string, id: string) => {
return {};
}),
- errors: {
- isNotFoundError: jest.fn((e: any) => e.message.startsWith('not found:')),
- },
};
server.savedObjects = {
SavedObjectsClient: {
errors: {
isNotFoundError: jest.fn((e: any) => e.message.startsWith('not found:')),
+ isConflictError: jest.fn((e: any) => e.message.startsWith('conflict')),
},
},
};
diff --git a/x-pack/plugins/spaces/server/routes/api/public/post.test.ts b/x-pack/plugins/spaces/server/routes/api/public/post.test.ts
index 91e3b4c49fd84..b554d5fc67354 100644
--- a/x-pack/plugins/spaces/server/routes/api/public/post.test.ts
+++ b/x-pack/plugins/spaces/server/routes/api/public/post.test.ts
@@ -99,8 +99,7 @@ describe('Spaces Public API', () => {
expect(statusCode).toEqual(409);
expect(JSON.parse(responsePayload)).toEqual({
error: 'Conflict',
- message:
- 'A space with the identifier a-space already exists. Please choose a different identifier',
+ message: 'A space with the identifier a-space already exists.',
statusCode: 409,
});
});
diff --git a/x-pack/plugins/spaces/server/routes/api/public/post.ts b/x-pack/plugins/spaces/server/routes/api/public/post.ts
index f71e9a6f57788..a4c1e04a73831 100644
--- a/x-pack/plugins/spaces/server/routes/api/public/post.ts
+++ b/x-pack/plugins/spaces/server/routes/api/public/post.ts
@@ -8,7 +8,6 @@ import Boom from 'boom';
import { wrapError } from '../../../lib/errors';
import { spaceSchema } from '../../../lib/space_schema';
import { SpacesClient } from '../../../lib/spaces_client';
-import { getSpaceById } from '../../lib';
export function initPostSpacesApi(server: any, routePreCheckLicenseFn: any) {
server.route({
@@ -22,20 +21,12 @@ export function initPostSpacesApi(server: any, routePreCheckLicenseFn: any) {
const space = request.payload;
- const id = request.payload.id;
-
- const existingSpace = await getSpaceById(spacesClient, id, SavedObjectsClient.errors);
- if (existingSpace) {
- return reply(
- Boom.conflict(
- `A space with the identifier ${id} already exists. Please choose a different identifier`
- )
- );
- }
-
try {
- return reply(await spacesClient.create({ ...space }));
+ return reply(await spacesClient.create(space));
} catch (error) {
+ if (SavedObjectsClient.errors.isConflictError(error)) {
+ return reply(Boom.conflict(`A space with the identifier ${space.id} already exists.`));
+ }
return reply(wrapError(error));
}
},
diff --git a/x-pack/plugins/spaces/server/routes/api/public/put.ts b/x-pack/plugins/spaces/server/routes/api/public/put.ts
index 34f3614725234..dea7e3a79d5c0 100644
--- a/x-pack/plugins/spaces/server/routes/api/public/put.ts
+++ b/x-pack/plugins/spaces/server/routes/api/public/put.ts
@@ -5,12 +5,10 @@
*/
import Boom from 'boom';
-import { omit } from 'lodash';
import { Space } from '../../../../common/model/space';
import { wrapError } from '../../../lib/errors';
import { spaceSchema } from '../../../lib/space_schema';
import { SpacesClient } from '../../../lib/spaces_client';
-import { getSpaceById } from '../../lib';
export function initPutSpacesApi(server: any, routePreCheckLicenseFn: any) {
server.route({
@@ -22,21 +20,16 @@ export function initPutSpacesApi(server: any, routePreCheckLicenseFn: any) {
request
);
- const space: Space = omit(request.payload, ['id']);
+ const space: Space = request.payload;
const id = request.params.id;
- const existingSpace = await getSpaceById(spacesClient, id, SavedObjectsClient.errors);
-
- if (existingSpace) {
- space._reserved = existingSpace._reserved;
- } else {
- return reply(Boom.notFound(`Unable to find space with ID ${id}`));
- }
-
let result: Space;
try {
result = await spacesClient.update(id, { ...space });
} catch (error) {
+ if (SavedObjectsClient.errors.isNotFoundError(error)) {
+ return reply(Boom.notFound());
+ }
return reply(wrapError(error));
}
diff --git a/x-pack/test/spaces_api_integration/common/suites/create.ts b/x-pack/test/spaces_api_integration/common/suites/create.ts
index 51a71b43271b3..c3caeeef8210a 100644
--- a/x-pack/test/spaces_api_integration/common/suites/create.ts
+++ b/x-pack/test/spaces_api_integration/common/suites/create.ts
@@ -37,7 +37,7 @@ export function createTestSuiteFactory(esArchiver: any, supertest: SuperTest {
return supertest
- .post(`${getUrlPrefix(spaceId)}/api/spaces/v1/space`)
+ .post(`${getUrlPrefix(spaceId)}/api/spaces/space`)
.auth(auth.username, auth.password)
.send({
name: 'marketing',
@@ -52,7 +52,7 @@ export function createTestSuiteFactory(esArchiver: any, supertest: SuperTest {
it(`should return ${tests.alreadyExists.statusCode}`, async () => {
return supertest
- .post(`${getUrlPrefix(spaceId)}/api/spaces/v1/space`)
+ .post(`${getUrlPrefix(spaceId)}/api/spaces/space`)
.auth(auth.username, auth.password)
.send({
name: 'space_1',
@@ -68,7 +68,7 @@ export function createTestSuiteFactory(esArchiver: any, supertest: SuperTest {
it(`should return ${tests.reservedSpecified.statusCode} and ignore _reserved`, async () => {
return supertest
- .post(`${getUrlPrefix(spaceId)}/api/spaces/v1/space`)
+ .post(`${getUrlPrefix(spaceId)}/api/spaces/space`)
.auth(auth.username, auth.password)
.send({
name: 'reserved space',
@@ -87,13 +87,10 @@ export function createTestSuiteFactory(esArchiver: any, supertest: SuperTest {
- const spaceId = 'space_1';
expect(resp.body).to.only.have.keys(['error', 'message', 'statusCode']);
expect(resp.body.error).to.equal('Conflict');
expect(resp.body.statusCode).to.equal(409);
- expect(resp.body.message).to.match(
- new RegExp(`\\[doc]\\[space:${spaceId}]: version conflict, document already exists.*`)
- );
+ expect(resp.body.message).to.match(new RegExp(`A space with the identifier .*`));
};
const expectRbacForbiddenResponse = (resp: any) => {
diff --git a/x-pack/test/spaces_api_integration/common/suites/delete.ts b/x-pack/test/spaces_api_integration/common/suites/delete.ts
index edc6c2b35dd96..412ac6774ef42 100644
--- a/x-pack/test/spaces_api_integration/common/suites/delete.ts
+++ b/x-pack/test/spaces_api_integration/common/suites/delete.ts
@@ -36,7 +36,7 @@ export function deleteTestSuiteFactory(esArchiver: any, supertest: SuperTest {
return supertest
- .delete(`${getUrlPrefix(spaceId)}/api/spaces/v1/space/space_2`)
+ .delete(`${getUrlPrefix(spaceId)}/api/spaces/space/space_2`)
.auth(auth.username, auth.password)
.expect(tests.exists.statusCode)
.then(tests.exists.response);
@@ -45,7 +45,7 @@ export function deleteTestSuiteFactory(esArchiver: any, supertest: SuperTest {
it(`should return ${tests.reservedSpace.statusCode}`, async () => {
return supertest
- .delete(`${getUrlPrefix(spaceId)}/api/spaces/v1/space/default`)
+ .delete(`${getUrlPrefix(spaceId)}/api/spaces/space/default`)
.auth(auth.username, auth.password)
.expect(tests.reservedSpace.statusCode)
.then(tests.reservedSpace.response);
@@ -55,7 +55,7 @@ export function deleteTestSuiteFactory(esArchiver: any, supertest: SuperTest {
it(`should return ${tests.doesntExist.statusCode}`, async () => {
return supertest
- .delete(`${getUrlPrefix(spaceId)}/api/spaces/v1/space/space_3`)
+ .delete(`${getUrlPrefix(spaceId)}/api/spaces/space/space_3`)
.auth(auth.username, auth.password)
.expect(tests.doesntExist.statusCode)
.then(tests.doesntExist.response);
@@ -78,7 +78,6 @@ export function deleteTestSuiteFactory(esArchiver: any, supertest: SuperTest)
it(`should return ${tests.default.statusCode}`, async () => {
return supertest
- .get(`${getUrlPrefix(currentSpaceId)}/api/spaces/v1/space/${spaceId}`)
+ .get(`${getUrlPrefix(currentSpaceId)}/api/spaces/space/${spaceId}`)
.auth(auth.username, auth.password)
.expect(tests.default.statusCode)
.then(tests.default.response);
@@ -73,11 +73,10 @@ export function getTestSuiteFactory(esArchiver: any, supertest: SuperAgent)
expect(resp.body).to.eql('');
};
- const createExpectNotFoundResult = (spaceId: string) => (resp: any) => {
+ const createExpectNotFoundResult = () => (resp: any) => {
expect(resp.body).to.eql({
error: 'Not Found',
statusCode: 404,
- message: `Saved object [space/${spaceId}] not found`,
});
};
diff --git a/x-pack/test/spaces_api_integration/common/suites/get_all.ts b/x-pack/test/spaces_api_integration/common/suites/get_all.ts
index 69702fba7dc6c..a6e56be272b37 100644
--- a/x-pack/test/spaces_api_integration/common/suites/get_all.ts
+++ b/x-pack/test/spaces_api_integration/common/suites/get_all.ts
@@ -34,7 +34,7 @@ export function getAllTestSuiteFactory(esArchiver: any, supertest: SuperTest {
return supertest
- .get(`${getUrlPrefix(spaceId)}/api/spaces/v1/spaces`)
+ .get(`${getUrlPrefix(spaceId)}/api/spaces/space`)
.auth(auth.username, auth.password)
.expect(tests.exists.statusCode)
.then(tests.exists.response);
diff --git a/x-pack/test/spaces_api_integration/common/suites/select.ts b/x-pack/test/spaces_api_integration/common/suites/select.ts
index e5fb91cbc4061..b2f86a77f7865 100644
--- a/x-pack/test/spaces_api_integration/common/suites/select.ts
+++ b/x-pack/test/spaces_api_integration/common/suites/select.ts
@@ -77,11 +77,10 @@ export function selectTestSuiteFactory(esArchiver: any, supertest: SuperTest (resp: any) => {
+ const createExpectNotFoundResult = () => (resp: any) => {
expect(resp.body).to.eql({
error: 'Not Found',
statusCode: 404,
- message: `Saved object [space/${spaceId}] not found`,
});
};
const createExpectRbacForbidden = (spaceId: any) => (resp: any) => {
diff --git a/x-pack/test/spaces_api_integration/common/suites/update.ts b/x-pack/test/spaces_api_integration/common/suites/update.ts
index 95bcb14a287de..f77d543ed084d 100644
--- a/x-pack/test/spaces_api_integration/common/suites/update.ts
+++ b/x-pack/test/spaces_api_integration/common/suites/update.ts
@@ -35,7 +35,7 @@ export function updateTestSuiteFactory(esArchiver: any, supertest: SuperTest {
return supertest
- .put(`${getUrlPrefix(spaceId)}/api/spaces/v1/space/space_1`)
+ .put(`${getUrlPrefix(spaceId)}/api/spaces/space/space_1`)
.auth(auth.username, auth.password)
.send({
name: 'space 1',
@@ -51,7 +51,7 @@ export function updateTestSuiteFactory(esArchiver: any, supertest: SuperTest {
it(`should return ${tests.newSpace.statusCode}`, async () => {
return supertest
- .put(`${getUrlPrefix(spaceId)}/api/spaces/v1/space/marketing`)
+ .put(`${getUrlPrefix(spaceId)}/api/spaces/space/marketing`)
.auth(auth.username, auth.password)
.send({
name: 'marketing',
@@ -72,7 +72,6 @@ export function updateTestSuiteFactory(esArchiver: any, supertest: SuperTest
Date: Mon, 10 Sep 2018 09:38:22 -0400
Subject: [PATCH 65/68] fix tests
---
.../spaces_api_integration/common/suites/update.ts | 2 ++
.../security_and_spaces/apis/create.ts | 2 +-
.../security_and_spaces/apis/delete.ts | 6 +++---
.../security_and_spaces/apis/get.ts | 2 +-
.../security_and_spaces/apis/select.ts | 4 ++--
.../spaces_api_integration/spaces_only/apis/update.ts | 11 +++--------
6 files changed, 12 insertions(+), 15 deletions(-)
diff --git a/x-pack/test/spaces_api_integration/common/suites/update.ts b/x-pack/test/spaces_api_integration/common/suites/update.ts
index f77d543ed084d..bb35be94ac3ef 100644
--- a/x-pack/test/spaces_api_integration/common/suites/update.ts
+++ b/x-pack/test/spaces_api_integration/common/suites/update.ts
@@ -5,12 +5,14 @@
*/
import expect from 'expect.js';
import { SuperTest } from 'supertest';
+import { Space } from '../../../../plugins/spaces/common/model/space';
import { getUrlPrefix } from '../lib/space_test_utils';
import { DescribeFn, TestDefinitionAuthentication } from '../lib/types';
interface UpdateTest {
statusCode: number;
response: (resp: any) => void;
+ space?: Space;
}
interface UpdateTests {
diff --git a/x-pack/test/spaces_api_integration/security_and_spaces/apis/create.ts b/x-pack/test/spaces_api_integration/security_and_spaces/apis/create.ts
index 4084e777e2c70..760fd1d6cd9b4 100644
--- a/x-pack/test/spaces_api_integration/security_and_spaces/apis/create.ts
+++ b/x-pack/test/spaces_api_integration/security_and_spaces/apis/create.ts
@@ -16,8 +16,8 @@ export default function createSpacesOnlySuite({ getService }: TestInvoker) {
const {
createTest,
- expectReservedSpecifiedResult,
expectNewSpaceResult,
+ expectReservedSpecifiedResult,
expectConflictResponse,
expectRbacForbiddenResponse,
createExpectLegacyForbiddenResponse,
diff --git a/x-pack/test/spaces_api_integration/security_and_spaces/apis/delete.ts b/x-pack/test/spaces_api_integration/security_and_spaces/apis/delete.ts
index 93a04d8bc51e0..3e43defaa910c 100644
--- a/x-pack/test/spaces_api_integration/security_and_spaces/apis/delete.ts
+++ b/x-pack/test/spaces_api_integration/security_and_spaces/apis/delete.ts
@@ -16,11 +16,11 @@ export default function deleteSpaceTestSuite({ getService }: TestInvoker) {
const {
deleteTest,
+ createExpectLegacyForbidden,
+ expectRbacForbidden,
expectEmptyResult,
- expectReservedSpaceResult,
expectNotFoundResult,
- expectRbacForbidden,
- createExpectLegacyForbidden,
+ expectReservedSpaceResult,
} = deleteTestSuiteFactory(esArchiver, supertestWithoutAuth);
describe('delete', () => {
diff --git a/x-pack/test/spaces_api_integration/security_and_spaces/apis/get.ts b/x-pack/test/spaces_api_integration/security_and_spaces/apis/get.ts
index 46b1db80fafb8..94aaaaff6d9c9 100644
--- a/x-pack/test/spaces_api_integration/security_and_spaces/apis/get.ts
+++ b/x-pack/test/spaces_api_integration/security_and_spaces/apis/get.ts
@@ -18,8 +18,8 @@ export default function getSpaceTestSuite({ getService }: TestInvoker) {
getTest,
createExpectResults,
createExpectNotFoundResult,
- createExpectLegacyForbidden,
createExpectRbacForbidden,
+ createExpectLegacyForbidden,
nonExistantSpaceId,
} = getTestSuiteFactory(esArchiver, supertestWithoutAuth);
diff --git a/x-pack/test/spaces_api_integration/security_and_spaces/apis/select.ts b/x-pack/test/spaces_api_integration/security_and_spaces/apis/select.ts
index 2c3e9636ba112..9f323001e5506 100644
--- a/x-pack/test/spaces_api_integration/security_and_spaces/apis/select.ts
+++ b/x-pack/test/spaces_api_integration/security_and_spaces/apis/select.ts
@@ -16,10 +16,10 @@ export default function selectSpaceTestSuite({ getService }: TestInvoker) {
const {
selectTest,
+ nonExistantSpaceId,
createExpectSpaceResponse,
createExpectRbacForbidden,
createExpectNotFoundResult,
- nonExistantSpaceId,
createExpectLegacyForbidden,
} = selectTestSuiteFactory(esArchiver, supertestWithoutAuth);
@@ -346,7 +346,7 @@ export default function selectSpaceTestSuite({ getService }: TestInvoker) {
tests: {
default: {
statusCode: 403,
- response: createExpectRbacForbidden(nonExistantSpaceId),
+ response: createExpectRbacForbidden(scenario.otherSpaceId),
},
},
});
diff --git a/x-pack/test/spaces_api_integration/spaces_only/apis/update.ts b/x-pack/test/spaces_api_integration/spaces_only/apis/update.ts
index e3d2368ce8d3c..30e9a96ecf2a1 100644
--- a/x-pack/test/spaces_api_integration/spaces_only/apis/update.ts
+++ b/x-pack/test/spaces_api_integration/spaces_only/apis/update.ts
@@ -13,7 +13,7 @@ export default function updateSpaceTestSuite({ getService }: TestInvoker) {
const supertestWithoutAuth = getService('supertestWithoutAuth');
const esArchiver = getService('esArchiver');
- const { updateTest, createExpectResult, createExpectNotFoundResult } = updateTestSuiteFactory(
+ const { updateTest, expectAlreadyExistsResult, expectNewSpaceNotFound } = updateTestSuiteFactory(
esArchiver,
supertestWithoutAuth
);
@@ -39,12 +39,7 @@ export default function updateSpaceTestSuite({ getService }: TestInvoker) {
_reserved: true,
},
statusCode: 200,
- response: createExpectResult({
- name: 'space 1',
- id: 'space_1',
- description: 'a description',
- color: '#5c5959',
- }),
+ response: expectAlreadyExistsResult,
},
newSpace: {
space: {
@@ -54,7 +49,7 @@ export default function updateSpaceTestSuite({ getService }: TestInvoker) {
color: '#5c5959',
},
statusCode: 404,
- response: createExpectNotFoundResult('marketing'),
+ response: expectNewSpaceNotFound,
},
},
});
From 493b3dfae8d91cdc831b416d83b25413317bb71e Mon Sep 17 00:00:00 2001
From: Larry Gregory
Date: Mon, 10 Sep 2018 09:45:10 -0400
Subject: [PATCH 66/68] fix failing test
---
src/ui/public/chrome/api/__tests__/nav.js | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/ui/public/chrome/api/__tests__/nav.js b/src/ui/public/chrome/api/__tests__/nav.js
index 169c9546a4a37..1ca5f2689590d 100644
--- a/src/ui/public/chrome/api/__tests__/nav.js
+++ b/src/ui/public/chrome/api/__tests__/nav.js
@@ -27,6 +27,7 @@ const basePath = '/someBasePath';
function init(customInternals = { basePath }) {
const chrome = {
+ addBasePath: (path) => path,
getBasePath: () => customInternals.basePath || '',
};
const internals = {
@@ -39,7 +40,7 @@ function init(customInternals = { basePath }) {
describe('chrome nav apis', function () {
describe('#getNavLinkById', () => {
- it ('retrieves the correct nav link, given its ID', () => {
+ it('retrieves the correct nav link, given its ID', () => {
const appUrlStore = new StubBrowserStorage();
const nav = [
{ id: 'kibana:discover', title: 'Discover' }
@@ -52,7 +53,7 @@ describe('chrome nav apis', function () {
expect(navLink).to.eql(nav[0]);
});
- it ('throws an error if the nav link with the given ID is not found', () => {
+ it('throws an error if the nav link with the given ID is not found', () => {
const appUrlStore = new StubBrowserStorage();
const nav = [
{ id: 'kibana:discover', title: 'Discover' }
From 778f69fb84559e1366a2e7ff5586f1b226bf5c89 Mon Sep 17 00:00:00 2001
From: Larry Gregory
Date: Mon, 10 Sep 2018 09:53:07 -0400
Subject: [PATCH 67/68] fix getSortingParameters when an array of length 1 is
specified
---
.../service/lib/search_dsl/sorting_params.js | 33 +++++++++++--------
.../lib/search_dsl/sorting_params.test.js | 15 +++++++++
2 files changed, 34 insertions(+), 14 deletions(-)
diff --git a/src/server/saved_objects/service/lib/search_dsl/sorting_params.js b/src/server/saved_objects/service/lib/search_dsl/sorting_params.js
index b977924ff62c5..bf22e2818939c 100644
--- a/src/server/saved_objects/service/lib/search_dsl/sorting_params.js
+++ b/src/server/saved_objects/service/lib/search_dsl/sorting_params.js
@@ -26,24 +26,29 @@ export function getSortingParams(mappings, type, sortField, sortOrder) {
return {};
}
+ let typeField = type;
+
if (Array.isArray(type)) {
- const rootField = getProperty(mappings, sortField);
- if (!rootField) {
- throw Boom.badRequest(`Unable to sort multiple types by field ${sortField}, not a root property`);
- }
+ if (type.length === 1) {
+ typeField = type[0];
+ } else {
+ const rootField = getProperty(mappings, sortField);
+ if (!rootField) {
+ throw Boom.badRequest(`Unable to sort multiple types by field ${sortField}, not a root property`);
+ }
- return {
- sort: [{
- [sortField]: {
- order: sortOrder,
- unmapped_type: rootField.type
- }
- }]
- };
+ return {
+ sort: [{
+ [sortField]: {
+ order: sortOrder,
+ unmapped_type: rootField.type
+ }
+ }]
+ };
+ }
}
-
- const key = `${type}.${sortField}`;
+ const key = `${typeField}.${sortField}`;
const field = getProperty(mappings, key);
if (!field) {
throw Boom.badRequest(`Unknown sort field ${sortField}`);
diff --git a/src/server/saved_objects/service/lib/search_dsl/sorting_params.test.js b/src/server/saved_objects/service/lib/search_dsl/sorting_params.test.js
index a7ff1041f313a..71833f1395659 100644
--- a/src/server/saved_objects/service/lib/search_dsl/sorting_params.test.js
+++ b/src/server/saved_objects/service/lib/search_dsl/sorting_params.test.js
@@ -138,6 +138,21 @@ describe('searchDsl/getSortParams', () => {
});
});
});
+ describe('sortField is multi-field with single type as array', () => {
+ it('returns correct params', () => {
+ expect(getSortingParams(MAPPINGS, ['saved'], 'title.raw'))
+ .toEqual({
+ sort: [
+ {
+ 'saved.title.raw': {
+ order: undefined,
+ unmapped_type: 'keyword'
+ }
+ }
+ ]
+ });
+ });
+ });
describe('sortField is root multi-field with multiple types', () => {
it('returns correct params', () => {
expect(getSortingParams(MAPPINGS, ['saved', 'pending'], 'type.raw'))
From 1a76bfe31ea6de3f8d0234c6a4ba5bd998a4e64e Mon Sep 17 00:00:00 2001
From: Larry Gregory
Date: Mon, 10 Sep 2018 10:47:42 -0400
Subject: [PATCH 68/68] make basePath space aware again
---
src/ui/ui_render/ui_render_mixin.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/ui/ui_render/ui_render_mixin.js b/src/ui/ui_render/ui_render_mixin.js
index e2f586921d2e4..4ee7c38db788f 100644
--- a/src/ui/ui_render/ui_render_mixin.js
+++ b/src/ui/ui_render/ui_render_mixin.js
@@ -139,7 +139,7 @@ export function uiRenderMixin(kbnServer, server, config) {
try {
const request = reply.request;
const translations = await server.getUiTranslations();
- const basePath = config.get('server.basePath');
+ const basePath = request.getBasePath();
return reply.view('ui_app', {
uiPublicUrl: `${basePath}/ui`,