+ );
+};
diff --git a/src/plugins/vis_type_vega/public/vega_inspector/components/spec_viewer.tsx b/src/plugins/vis_type_vega/public/vega_inspector/components/spec_viewer.tsx
new file mode 100644
index 0000000000000..54f7974960aa2
--- /dev/null
+++ b/src/plugins/vis_type_vega/public/vega_inspector/components/spec_viewer.tsx
@@ -0,0 +1,97 @@
+/*
+ * 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, { useEffect, useState } from 'react';
+import { i18n } from '@kbn/i18n';
+
+import {
+ EuiFlexItem,
+ EuiFlexGroup,
+ EuiCopy,
+ EuiButtonEmpty,
+ EuiSpacer,
+ CommonProps,
+} from '@elastic/eui';
+import { VegaAdapter } from '../vega_adapter';
+import { CodeEditor } from '../../../../kibana_react/public';
+
+interface SpecViewerProps extends CommonProps {
+ vegaAdapter: VegaAdapter;
+}
+
+const copyToClipboardLabel = i18n.translate(
+ 'visTypeVega.inspector.specViewer.copyToClipboardLabel',
+ {
+ defaultMessage: 'Copy to clipboard',
+ }
+);
+
+export const SpecViewer = ({ vegaAdapter, ...rest }: SpecViewerProps) => {
+ const [spec, setSpec] = useState();
+
+ useEffect(() => {
+ const subscription = vegaAdapter.getSpecSubscription().subscribe((data) => {
+ if (data) {
+ setSpec(data);
+ }
+ });
+ return () => {
+ subscription.unsubscribe();
+ };
+ }, [vegaAdapter]);
+
+ if (!spec) {
+ return null;
+ }
+
+ return (
+
+
+
+
+
+
+ {}}
+ options={{
+ readOnly: true,
+ lineNumbers: 'off',
+ fontSize: 12,
+ minimap: {
+ enabled: false,
+ },
+ scrollBeyondLastLine: false,
+ wordWrap: 'on',
+ wrappingIndent: 'indent',
+ automaticLayout: true,
+ }}
+ />
+
+
+ );
+};
diff --git a/src/plugins/vis_type_vega/public/vega_inspector/index.ts b/src/plugins/vis_type_vega/public/vega_inspector/index.ts
new file mode 100644
index 0000000000000..24da27d2d742d
--- /dev/null
+++ b/src/plugins/vis_type_vega/public/vega_inspector/index.ts
@@ -0,0 +1,24 @@
+/*
+ * 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 {
+ createInspectorAdapters,
+ getVegaInspectorView,
+ VegaInspectorAdapters,
+} from './vega_inspector';
diff --git a/src/plugins/vis_type_vega/public/vega_inspector/vega_adapter.ts b/src/plugins/vis_type_vega/public/vega_inspector/vega_adapter.ts
new file mode 100644
index 0000000000000..e4c536af40591
--- /dev/null
+++ b/src/plugins/vis_type_vega/public/vega_inspector/vega_adapter.ts
@@ -0,0 +1,148 @@
+/*
+ * 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 { Observable, ReplaySubject, fromEventPattern, merge, timer } from 'rxjs';
+import { map, switchMap, filter, debounce } from 'rxjs/operators';
+import { View, Runtime, Spec } from 'vega';
+import { i18n } from '@kbn/i18n';
+import { Assign } from '@kbn/utility-types';
+
+interface DebugValues {
+ view: View;
+ spec: Spec;
+}
+
+export interface VegaRuntimeData {
+ columns: Array<{
+ id: string;
+ }>;
+ data: Array>;
+}
+
+export type InspectDataSets = Assign;
+export type InspectSignalsSets = VegaRuntimeData;
+
+const vegaAdapterSignalLabel = i18n.translate('visTypeVega.inspector.vegaAdapter.signal', {
+ defaultMessage: 'Signal',
+});
+
+const vegaAdapterValueLabel = i18n.translate('visTypeVega.inspector.vegaAdapter.value', {
+ defaultMessage: 'Value',
+});
+
+/** Get Runtime Scope for Vega View
+ * @link https://vega.github.io/vega/docs/api/debugging/#scope
+ **/
+const getVegaRuntimeScope = (debugValues: DebugValues) =>
+ (debugValues.view as any)._runtime as Runtime;
+
+const serializeColumns = (item: Record, columns: string[]) => {
+ const nonSerializableFieldLabel = '(..)';
+
+ return columns.reduce((row: Record, column) => {
+ try {
+ const cell = item[column];
+ row[column] = typeof cell === 'object' ? JSON.stringify(cell) : `${cell}`;
+ } catch (e) {
+ row[column] = nonSerializableFieldLabel;
+ }
+ return row;
+ }, {});
+};
+
+export class VegaAdapter {
+ private debugValuesSubject = new ReplaySubject();
+
+ bindInspectValues(debugValues: DebugValues) {
+ this.debugValuesSubject.next(debugValues);
+ }
+
+ getDataSetsSubscription(): Observable {
+ return this.debugValuesSubject.pipe(
+ filter((debugValues) => Boolean(debugValues)),
+ map((debugValues) => {
+ const runtimeScope = getVegaRuntimeScope(debugValues);
+
+ return Object.keys(runtimeScope.data || []).reduce((acc: InspectDataSets[], key) => {
+ const value = runtimeScope.data[key].values.value;
+
+ if (value && value[0]) {
+ const columns = Object.keys(value[0]);
+ acc.push({
+ id: key,
+ columns: columns.map((column) => ({ id: column, schema: 'json' })),
+ data: value.map((item: Record) => serializeColumns(item, columns)),
+ });
+ }
+ return acc;
+ }, []);
+ })
+ );
+ }
+
+ getSignalsSetsSubscription(): Observable {
+ const signalsListener = this.debugValuesSubject.pipe(
+ filter((debugValues) => Boolean(debugValues)),
+ switchMap((debugValues) => {
+ const runtimeScope = getVegaRuntimeScope(debugValues);
+
+ return merge(
+ ...Object.keys(runtimeScope.signals).map((key: string) =>
+ fromEventPattern(
+ (handler) => debugValues.view.addSignalListener(key, handler),
+ (handler) => debugValues.view.removeSignalListener(key, handler)
+ )
+ )
+ ).pipe(
+ debounce((val) => timer(350)),
+ map(() => debugValues)
+ );
+ })
+ );
+
+ return merge(this.debugValuesSubject, signalsListener).pipe(
+ filter((debugValues) => Boolean(debugValues)),
+ map((debugValues) => {
+ const runtimeScope = getVegaRuntimeScope(debugValues);
+
+ return {
+ columns: [
+ { id: vegaAdapterSignalLabel, schema: 'text' },
+ { id: vegaAdapterValueLabel, schema: 'json' },
+ ],
+ data: Object.keys(runtimeScope.signals).map((key: string) =>
+ serializeColumns(
+ {
+ [vegaAdapterSignalLabel]: key,
+ [vegaAdapterValueLabel]: runtimeScope.signals[key].value,
+ },
+ [vegaAdapterSignalLabel, vegaAdapterValueLabel]
+ )
+ ),
+ };
+ })
+ );
+ }
+
+ getSpecSubscription(): Observable {
+ return this.debugValuesSubject.pipe(
+ filter((debugValues) => Boolean(debugValues)),
+ map((debugValues) => JSON.stringify(debugValues.spec, null, 2))
+ );
+ }
+}
diff --git a/src/plugins/vis_type_vega/public/vega_inspector/vega_data_inspector.scss b/src/plugins/vis_type_vega/public/vega_inspector/vega_data_inspector.scss
new file mode 100644
index 0000000000000..487f505657d3b
--- /dev/null
+++ b/src/plugins/vis_type_vega/public/vega_inspector/vega_data_inspector.scss
@@ -0,0 +1,18 @@
+.vgaVegaDataInspector,
+.vgaVegaDataInspector__specViewer {
+ height: 100%;
+}
+
+.vgaVegaDataInspector {
+ // TODO: EUI needs to provide props to pass down from EuiTabbedContent to tabs and content
+ display: flex;
+ flex-direction: column;
+
+ [role='tablist'] {
+ flex-shrink: 0;
+ }
+
+ [role='tabpanel'] {
+ flex-grow: 1;
+ }
+}
diff --git a/src/plugins/vis_type_vega/public/vega_inspector/vega_data_inspector.tsx b/src/plugins/vis_type_vega/public/vega_inspector/vega_data_inspector.tsx
new file mode 100644
index 0000000000000..3b9427c96e62a
--- /dev/null
+++ b/src/plugins/vis_type_vega/public/vega_inspector/vega_data_inspector.tsx
@@ -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 './vega_data_inspector.scss';
+
+import React from 'react';
+import { EuiTabbedContent } from '@elastic/eui';
+
+import { i18n } from '@kbn/i18n';
+import { VegaInspectorAdapters } from './vega_inspector';
+import { DataViewer, SignalViewer, SpecViewer } from './components';
+import { InspectorViewProps } from '../../../inspector/public';
+
+export type VegaDataInspectorProps = InspectorViewProps;
+
+const dataSetsLabel = i18n.translate('visTypeVega.inspector.dataSetsLabel', {
+ defaultMessage: 'Data sets',
+});
+
+const signalValuesLabel = i18n.translate('visTypeVega.inspector.signalValuesLabel', {
+ defaultMessage: 'Signal values',
+});
+
+const specLabel = i18n.translate('visTypeVega.inspector.specLabel', {
+ defaultMessage: 'Spec',
+});
+
+export const VegaDataInspector = ({ adapters }: VegaDataInspectorProps) => {
+ const tabs = [
+ {
+ id: 'data-viewer--id',
+ name: dataSetsLabel,
+ content: ,
+ },
+ {
+ id: 'signal-viewer--id',
+ name: signalValuesLabel,
+ content: ,
+ },
+ {
+ id: 'spec-viewer--id',
+ name: specLabel,
+ content: (
+
+ ),
+ },
+ ];
+
+ return (
+
+ );
+};
diff --git a/src/plugins/vis_type_vega/public/vega_inspector/vega_inspector.tsx b/src/plugins/vis_type_vega/public/vega_inspector/vega_inspector.tsx
new file mode 100644
index 0000000000000..83d9e467646a6
--- /dev/null
+++ b/src/plugins/vis_type_vega/public/vega_inspector/vega_inspector.tsx
@@ -0,0 +1,57 @@
+/*
+ * 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 { i18n } from '@kbn/i18n';
+import { IUiSettingsClient } from 'kibana/public';
+import { VegaAdapter } from './vega_adapter';
+import { VegaDataInspector, VegaDataInspectorProps } from './vega_data_inspector';
+import { KibanaContextProvider } from '../../../kibana_react/public';
+import { Adapters, RequestAdapter, InspectorViewDescription } from '../../../inspector/public';
+
+export interface VegaInspectorAdapters extends Adapters {
+ requests: RequestAdapter;
+ vega: VegaAdapter;
+}
+
+const vegaDebugLabel = i18n.translate('visTypeVega.inspector.vegaDebugLabel', {
+ defaultMessage: 'Vega debug',
+});
+
+interface VegaInspectorViewDependencies {
+ uiSettings: IUiSettingsClient;
+}
+
+export const getVegaInspectorView = (dependencies: VegaInspectorViewDependencies) =>
+ ({
+ title: vegaDebugLabel,
+ shouldShow(adapters) {
+ return Boolean(adapters.vega);
+ },
+ component: (props) => (
+
+
+
+ ),
+ } as InspectorViewDescription);
+
+export const createInspectorAdapters = (): VegaInspectorAdapters => ({
+ requests: new RequestAdapter(),
+ vega: new VegaAdapter(),
+});
diff --git a/src/plugins/vis_type_vega/public/vega_request_handler.ts b/src/plugins/vis_type_vega/public/vega_request_handler.ts
index 997b1982d749a..c09a9466df602 100644
--- a/src/plugins/vis_type_vega/public/vega_request_handler.ts
+++ b/src/plugins/vis_type_vega/public/vega_request_handler.ts
@@ -25,6 +25,7 @@ import { TimeCache } from './data_model/time_cache';
import { VegaVisualizationDependencies } from './plugin';
import { VisParams } from './vega_fn';
import { getData, getInjectedMetadata } from './services';
+import { VegaInspectorAdapters } from './vega_inspector';
interface VegaRequestHandlerParams {
query: Query;
@@ -33,9 +34,14 @@ interface VegaRequestHandlerParams {
visParams: VisParams;
}
+interface VegaRequestHandlerContext {
+ abortSignal?: AbortSignal;
+ inspectorAdapters?: VegaInspectorAdapters;
+}
+
export function createVegaRequestHandler(
{ plugins: { data }, core: { uiSettings }, serviceSettings }: VegaVisualizationDependencies,
- abortSignal?: AbortSignal
+ context: VegaRequestHandlerContext = {}
) {
let searchAPI: SearchAPI;
const { timefilter } = data.query.timefilter;
@@ -54,7 +60,8 @@ export function createVegaRequestHandler(
search: getData().search,
injectedMetadata: getInjectedMetadata(),
},
- abortSignal
+ context.abortSignal,
+ context.inspectorAdapters
);
}
diff --git a/src/plugins/vis_type_vega/public/vega_type.ts b/src/plugins/vis_type_vega/public/vega_type.ts
index 5825661f9001c..d69eb3cfba282 100644
--- a/src/plugins/vis_type_vega/public/vega_type.ts
+++ b/src/plugins/vis_type_vega/public/vega_type.ts
@@ -23,9 +23,10 @@ import { VegaVisualizationDependencies } from './plugin';
import { VegaVisEditor } from './components';
import { createVegaRequestHandler } from './vega_request_handler';
-// @ts-ignore
+// @ts-expect-error
import { createVegaVisualization } from './vega_visualization';
import { getDefaultSpec } from './default_spec';
+import { createInspectorAdapters } from './vega_inspector';
export const createVegaTypeDefinition = (dependencies: VegaVisualizationDependencies) => {
const requestHandler = createVegaRequestHandler(dependencies);
@@ -54,5 +55,6 @@ export const createVegaTypeDefinition = (dependencies: VegaVisualizationDependen
showFilterBar: true,
},
stage: 'experimental',
+ inspectorAdapters: createInspectorAdapters,
};
};
diff --git a/src/plugins/vis_type_vega/public/vega_view/vega_base_view.js b/src/plugins/vis_type_vega/public/vega_view/vega_base_view.js
index 55c3606bf5e45..8f88d5c5b2056 100644
--- a/src/plugins/vis_type_vega/public/vega_view/vega_base_view.js
+++ b/src/plugins/vis_type_vega/public/vega_view/vega_base_view.js
@@ -364,6 +364,11 @@ export class VegaBaseView {
* Set global debug variable to simplify vega debugging in console. Show info message first time
*/
setDebugValues(view, spec, vlspec) {
+ this._parser.searchAPI.inspectorAdapters?.vega.bindInspectValues({
+ view,
+ spec: vlspec || spec,
+ });
+
if (window) {
if (window.VEGA_DEBUG === undefined && console) {
console.log('%cWelcome to Kibana Vega Plugin!', 'font-size: 16px; font-weight: bold;');
diff --git a/src/plugins/vis_type_vega/public/vega_view/vega_map_view.js b/src/plugins/vis_type_vega/public/vega_view/vega_map_view.js
index 6908fd13a9ca1..78ae2efdbdda5 100644
--- a/src/plugins/vis_type_vega/public/vega_view/vega_map_view.js
+++ b/src/plugins/vis_type_vega/public/vega_view/vega_map_view.js
@@ -142,7 +142,7 @@ export class VegaMapView extends VegaBaseView {
});
const vegaView = vegaMapLayer.getVegaView();
- this.setDebugValues(vegaView, this._parser.spec, this._parser.vlspec);
await this.setView(vegaView);
+ this.setDebugValues(vegaView, this._parser.spec, this._parser.vlspec);
}
}
diff --git a/src/plugins/vis_type_vega/public/vega_view/vega_view.js b/src/plugins/vis_type_vega/public/vega_view/vega_view.js
index e3455b97b7fe2..98c972ef84ccb 100644
--- a/src/plugins/vis_type_vega/public/vega_view/vega_view.js
+++ b/src/plugins/vis_type_vega/public/vega_view/vega_view.js
@@ -26,7 +26,6 @@ export class VegaView extends VegaBaseView {
if (!this._$container) return;
const view = new vega.View(vega.parse(this._parser.spec), this._vegaViewConfig);
- this.setDebugValues(view, this._parser.spec, this._parser.vlspec);
view.warn = this.onWarn.bind(this);
view.error = this.onError.bind(this);
@@ -36,5 +35,6 @@ export class VegaView extends VegaBaseView {
if (this._parser.useHover) view.hover();
await this.setView(view);
+ this.setDebugValues(view, this._parser.spec, this._parser.vlspec);
}
}
diff --git a/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts b/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts
index 2f9cda32fccdc..749926e1abd00 100644
--- a/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts
+++ b/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts
@@ -34,6 +34,7 @@ import {
EmbeddableOutput,
Embeddable,
IContainer,
+ Adapters,
} from '../../../../plugins/embeddable/public';
import { dispatchRenderComplete } from '../../../../plugins/kibana_utils/public';
import {
@@ -78,8 +79,6 @@ export interface VisualizeOutput extends EmbeddableOutput {
type ExpressionLoader = InstanceType;
-const visTypesWithoutInspector = ['markdown', 'input_control_vis', 'metrics', 'vega', 'timelion'];
-
export class VisualizeEmbeddable extends Embeddable {
private handler?: ExpressionLoader;
private timefilter: TimefilterContract;
@@ -96,6 +95,7 @@ export class VisualizeEmbeddable extends Embeddable {
- if (!this.handler || visTypesWithoutInspector.includes(this.vis.type.name)) {
+ if (!this.handler || (this.inspectorAdapters && !Object.keys(this.inspectorAdapters).length)) {
return undefined;
}
return this.handler.inspect();
@@ -349,6 +356,7 @@ export class VisualizeEmbeddable extends Embeddable Adapters);
}
export class BaseVisType {
@@ -63,6 +65,7 @@ export class BaseVisType {
hierarchicalData: boolean | unknown;
setup?: unknown;
useCustomNoDataScreen: boolean;
+ inspectorAdapters?: Adapters | (() => Adapters);
constructor(opts: BaseVisTypeOptions) {
if (!opts.icon && !opts.image) {
@@ -98,6 +101,7 @@ export class BaseVisType {
this.requiresSearch = this.requestHandler !== 'none';
this.hierarchicalData = opts.hierarchicalData || false;
this.useCustomNoDataScreen = opts.useCustomNoDataScreen || false;
+ this.inspectorAdapters = opts.inspectorAdapters;
}
public get schemas() {
diff --git a/tasks/test.js b/tasks/test.js
index 96ec4d91db325..09821b97fe2e8 100644
--- a/tasks/test.js
+++ b/tasks/test.js
@@ -48,7 +48,7 @@ module.exports = function (grunt) {
grunt.task.run(['run:karmaTestServer', ...ciShardTasks]);
});
- grunt.registerTask('test:coverage', ['run:testCoverageServer', 'karma:coverage']);
+ grunt.registerTask('test:coverage', ['run:karmaTestCoverageServer', 'karma:coverage']);
grunt.registerTask('test:quick', [
'checkPlugins',
diff --git a/test/functional/apps/discover/_discover_histogram.js b/test/functional/apps/discover/_discover_histogram.ts
similarity index 61%
rename from test/functional/apps/discover/_discover_histogram.js
rename to test/functional/apps/discover/_discover_histogram.ts
index e53c953f1514e..5c78bfccbb966 100644
--- a/test/functional/apps/discover/_discover_histogram.js
+++ b/test/functional/apps/discover/_discover_histogram.ts
@@ -18,14 +18,12 @@
*/
import expect from '@kbn/expect';
+import { FtrProviderContext } from '../../ftr_provider_context';
-export default function ({ getService, getPageObjects }) {
- const log = getService('log');
+export default function ({ getService, getPageObjects }: FtrProviderContext) {
const esArchiver = getService('esArchiver');
- const browser = getService('browser');
const elasticChart = getService('elasticChart');
const kibanaServer = getService('kibanaServer');
- const security = getService('security');
const PageObjects = getPageObjects(['settings', 'common', 'discover', 'header', 'timePicker']);
const defaultSettings = {
defaultIndex: 'long-window-logstash-*',
@@ -33,63 +31,43 @@ export default function ({ getService, getPageObjects }) {
};
describe('discover histogram', function describeIndexTests() {
- before(async function () {
- log.debug('load kibana index with default index pattern');
- await PageObjects.common.navigateToApp('settings');
- await security.testUser.setRoles([
- 'kibana_admin',
- 'test_logstash_reader',
- 'long_window_logstash',
- ]);
+ before(async () => {
await esArchiver.loadIfNeeded('logstash_functional');
await esArchiver.load('long_window_logstash');
- await esArchiver.load('visualize');
- await esArchiver.load('discover');
-
- log.debug('create long_window_logstash index pattern');
- // NOTE: long_window_logstash load does NOT create index pattern
- await PageObjects.settings.createIndexPattern('long-window-logstash-*');
+ await esArchiver.load('long_window_logstash_index_pattern');
await kibanaServer.uiSettings.replace(defaultSettings);
- await browser.refresh();
-
- log.debug('discover');
await PageObjects.common.navigateToApp('discover');
- await PageObjects.discover.selectIndexPattern('long-window-logstash-*');
- // NOTE: For some reason without setting this relative time, the abs times will not fetch data.
- await PageObjects.timePicker.setCommonlyUsedTime('Last_1 year');
});
after(async () => {
await esArchiver.unload('long_window_logstash');
- await esArchiver.unload('visualize');
- await esArchiver.unload('discover');
- await security.testUser.restoreDefaults();
+ await esArchiver.unload('long_window_logstash_index_pattern');
});
+ async function prepareTest(fromTime: string, toTime: string, interval: string) {
+ await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime);
+ await PageObjects.discover.waitUntilSearchingHasFinished();
+ await PageObjects.discover.setChartInterval(interval);
+ await PageObjects.header.waitUntilLoadingHasFinished();
+ }
+
it('should visualize monthly data with different day intervals', async () => {
const fromTime = 'Nov 01, 2017 @ 00:00:00.000';
const toTime = 'Mar 21, 2018 @ 00:00:00.000';
- await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime);
- await PageObjects.discover.setChartInterval('Month');
- await PageObjects.header.waitUntilLoadingHasFinished();
+ await prepareTest(fromTime, toTime, 'Month');
const chartCanvasExist = await elasticChart.canvasExists();
expect(chartCanvasExist).to.be(true);
});
it('should visualize weekly data with within DST changes', async () => {
const fromTime = 'Mar 01, 2018 @ 00:00:00.000';
const toTime = 'May 01, 2018 @ 00:00:00.000';
- await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime);
- await PageObjects.discover.setChartInterval('Week');
- await PageObjects.header.waitUntilLoadingHasFinished();
+ await prepareTest(fromTime, toTime, 'Week');
const chartCanvasExist = await elasticChart.canvasExists();
expect(chartCanvasExist).to.be(true);
});
- it('should visualize monthly data with different years Scaled to 30 days', async () => {
+ it('should visualize monthly data with different years scaled to 30 days', async () => {
const fromTime = 'Jan 01, 2010 @ 00:00:00.000';
const toTime = 'Mar 21, 2019 @ 00:00:00.000';
-
- await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime);
- await PageObjects.discover.setChartInterval('Day');
- await PageObjects.header.waitUntilLoadingHasFinished();
+ await prepareTest(fromTime, toTime, 'Day');
const chartCanvasExist = await elasticChart.canvasExists();
expect(chartCanvasExist).to.be(true);
const chartIntervalIconTip = await PageObjects.discover.getChartIntervalWarningIcon();
diff --git a/test/functional/apps/visualize/_vega_chart.js b/test/functional/apps/visualize/_vega_chart.js
index 4442e1f969b4b..c530c6f823133 100644
--- a/test/functional/apps/visualize/_vega_chart.js
+++ b/test/functional/apps/visualize/_vega_chart.js
@@ -22,7 +22,6 @@ import expect from '@kbn/expect';
export default function ({ getService, getPageObjects }) {
const PageObjects = getPageObjects(['timePicker', 'visualize', 'visChart', 'vegaChart']);
const filterBar = getService('filterBar');
- const inspector = getService('inspector');
const log = getService('log');
describe('vega chart in visualize app', () => {
@@ -35,10 +34,6 @@ export default function ({ getService, getPageObjects }) {
describe('vega chart', () => {
describe('initial render', () => {
- it('should not have inspector enabled', async function () {
- await inspector.expectIsNotEnabled();
- });
-
it.skip('should have some initial vega spec text', async function () {
const vegaSpec = await PageObjects.vegaChart.getSpec();
expect(vegaSpec).to.contain('{').and.to.contain('data');
diff --git a/test/functional/fixtures/es_archiver/long_window_logstash_index_pattern/data.json b/test/functional/fixtures/es_archiver/long_window_logstash_index_pattern/data.json
new file mode 100644
index 0000000000000..75aa6f06bb11a
--- /dev/null
+++ b/test/functional/fixtures/es_archiver/long_window_logstash_index_pattern/data.json
@@ -0,0 +1,17 @@
+{
+ "type": "doc",
+ "value": {
+ "id": "index-pattern:long-window-logstash-*",
+ "index": ".kibana",
+ "source": {
+ "index-pattern": {
+ "fields": "[{\"name\":\"referer\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"agent\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:image:width\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"xss.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"headings.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.user.lastname\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:tag.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"geo.dest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:section.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"utc_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:card\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.char\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"clientip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image:height\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"host\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"machine.ram\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"links\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"id\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@tags.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"phpmemory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:card.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"ip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:modified_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:site_name.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"request.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:tag\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"agent.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"spaces\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:site.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"headings\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"_source\",\"type\":\"_source\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"relatedContent.og:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"request\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"index.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"extension\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"memory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"_index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"relatedContent.twitter:site\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"geo.coordinates\",\"type\":\"geo_point\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"meta.related\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"response.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@message.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"machine.os\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:section\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"xss\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"links.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"geo.srcdest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"extension.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"machine.os.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@tags\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"host.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:type.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"geo.src\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"spaces.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image:height.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:site_name\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"@message\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@timestamp\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"bytes\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"response\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.user.firstname\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:image:width.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:published_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"_id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"_type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false}]",
+ "timeFieldName": "@timestamp",
+ "title": "long-window-logstash-*"
+ },
+ "type": "index-pattern"
+ }
+ }
+}
+
+
diff --git a/test/functional/services/remote/webdriver.ts b/test/functional/services/remote/webdriver.ts
index 0611c80f59b92..09fede7fe2546 100644
--- a/test/functional/services/remote/webdriver.ts
+++ b/test/functional/services/remote/webdriver.ts
@@ -21,10 +21,9 @@ import { resolve } from 'path';
import Fs from 'fs';
import * as Rx from 'rxjs';
-import { mergeMap, map, takeUntil } from 'rxjs/operators';
+import { mergeMap, map, takeUntil, catchError } from 'rxjs/operators';
import { Lifecycle } from '@kbn/test/src/functional_test_runner/lib/lifecycle';
import { ToolingLog } from '@kbn/dev-utils';
-import { delay } from 'bluebird';
import chromeDriver from 'chromedriver';
// @ts-ignore types not available
import geckoDriver from 'geckodriver';
@@ -337,25 +336,33 @@ export async function initWebDriver(
edgePaths = await installDriver();
}
- return await Promise.race([
- (async () => {
- await delay(2 * MINUTE);
- throw new Error('remote failed to start within 2 minutes');
- })(),
-
- (async () => {
- while (true) {
- const command = await Promise.race([
- delay(30 * SECOND),
- attemptToCreateCommand(log, browserType, lifecycle, config),
- ]);
+ return await Rx.race(
+ Rx.timer(2 * MINUTE).pipe(
+ map(() => {
+ throw new Error('remote failed to start within 2 minutes');
+ })
+ ),
+ Rx.race(
+ Rx.defer(async () => {
+ const command = await attemptToCreateCommand(log, browserType, lifecycle, config);
if (!command) {
- continue;
+ throw new Error('remote creation aborted');
}
-
return command;
- }
- })(),
- ]);
+ }),
+ Rx.timer(30 * SECOND).pipe(
+ map(() => {
+ throw new Error('remote failed to start within 30 seconds');
+ })
+ )
+ ).pipe(
+ catchError((error, resubscribe) => {
+ log.warning('Failure while creating webdriver instance');
+ log.warning(error);
+ log.warning('...retrying...');
+ return resubscribe;
+ })
+ )
+ ).toPromise();
}
diff --git a/vars/githubCommitStatus.groovy b/vars/githubCommitStatus.groovy
index 17d3c234f6928..248d226169a61 100644
--- a/vars/githubCommitStatus.groovy
+++ b/vars/githubCommitStatus.groovy
@@ -1,39 +1,47 @@
-def shouldCreateStatuses() {
- return !githubPr.isPr() && buildState.get('checkoutInfo')
+def defaultCommit() {
+ if (buildState.has('checkoutInfo')) {
+ return buildState.get('checkoutInfo').commit
+ }
}
-def onStart() {
+def onStart(commit = defaultCommit(), context = 'kibana-ci') {
catchError {
- if (!shouldCreateStatuses()) {
+ if (githubPr.isPr() || !commit) {
return
}
- def checkoutInfo = buildState.get('checkoutInfo')
- create(checkoutInfo.commit, 'pending', 'Build started.')
+ create(commit, 'pending', 'Build started.', context)
}
}
-def onFinish() {
+def onFinish(commit = defaultCommit(), context = 'kibana-ci') {
catchError {
- if (!shouldCreateStatuses()) {
+ if (githubPr.isPr() || !commit) {
return
}
- def checkoutInfo = buildState.get('checkoutInfo')
def status = buildUtils.getBuildStatus()
if (status == 'SUCCESS' || status == 'UNSTABLE') {
- create(checkoutInfo.commit, 'success', 'Build completed successfully.')
+ create(commit, 'success', 'Build completed successfully.', context)
} else if(status == 'ABORTED') {
- create(checkoutInfo.commit, 'error', 'Build aborted or timed out.')
+ create(commit, 'error', 'Build aborted or timed out.', context)
} else {
- create(checkoutInfo.commit, 'error', 'Build failed.')
+ create(commit, 'error', 'Build failed.', context)
}
}
}
+def trackBuild(commit, context, Closure closure) {
+ onStart(commit, context)
+ catchError {
+ closure()
+ }
+ onFinish(commit, context)
+}
+
// state: error|failure|pending|success
-def create(sha, state, description, context = 'kibana-ci') {
+def create(sha, state, description, context) {
withGithubCredentials {
return githubApi.post("repos/elastic/kibana/statuses/${sha}", [
state: state,
diff --git a/x-pack/plugins/actions/server/authorization/audit_logger.test.ts b/x-pack/plugins/actions/server/authorization/audit_logger.test.ts
index 6d3e69b822c96..d700abdaa70ff 100644
--- a/x-pack/plugins/actions/server/authorization/audit_logger.test.ts
+++ b/x-pack/plugins/actions/server/authorization/audit_logger.test.ts
@@ -26,7 +26,7 @@ describe(`#constructor`, () => {
});
describe(`#actionsAuthorizationFailure`, () => {
- test('logs auth failure with consumer scope', () => {
+ test('logs auth failure', () => {
const auditLogger = createMockAuditLogger();
const actionsAuditLogger = new ActionsAuthorizationAuditLogger(auditLogger);
const username = 'foo-user';
@@ -47,56 +47,10 @@ describe(`#actionsAuthorizationFailure`, () => {
]
`);
});
-
- test('logs auth failure with producer scope', () => {
- const auditLogger = createMockAuditLogger();
- const actionsAuditLogger = new ActionsAuthorizationAuditLogger(auditLogger);
- const username = 'foo-user';
- const actionTypeId = 'action-type-id';
-
- const operation = 'create';
-
- actionsAuditLogger.actionsAuthorizationFailure(username, operation, actionTypeId);
-
- expect(auditLogger.log.mock.calls[0]).toMatchInlineSnapshot(`
- Array [
- "actions_authorization_failure",
- "foo-user Unauthorized to create a \\"action-type-id\\" action",
- Object {
- "actionTypeId": "action-type-id",
- "operation": "create",
- "username": "foo-user",
- },
- ]
- `);
- });
});
describe(`#savedObjectsAuthorizationSuccess`, () => {
- test('logs auth success with consumer scope', () => {
- const auditLogger = createMockAuditLogger();
- const actionsAuditLogger = new ActionsAuthorizationAuditLogger(auditLogger);
- const username = 'foo-user';
- const actionTypeId = 'action-type-id';
-
- const operation = 'create';
-
- actionsAuditLogger.actionsAuthorizationSuccess(username, operation, actionTypeId);
-
- expect(auditLogger.log.mock.calls[0]).toMatchInlineSnapshot(`
- Array [
- "actions_authorization_success",
- "foo-user Authorized to create a \\"action-type-id\\" action",
- Object {
- "actionTypeId": "action-type-id",
- "operation": "create",
- "username": "foo-user",
- },
- ]
- `);
- });
-
- test('logs auth success with producer scope', () => {
+ test('logs auth success', () => {
const auditLogger = createMockAuditLogger();
const actionsAuditLogger = new ActionsAuthorizationAuditLogger(auditLogger);
const username = 'foo-user';
diff --git a/x-pack/plugins/actions/server/lib/action_executor.test.ts b/x-pack/plugins/actions/server/lib/action_executor.test.ts
index c8e6669275e11..65fd0646c639e 100644
--- a/x-pack/plugins/actions/server/lib/action_executor.test.ts
+++ b/x-pack/plugins/actions/server/lib/action_executor.test.ts
@@ -9,15 +9,17 @@ import { schema } from '@kbn/config-schema';
import { ActionExecutor } from './action_executor';
import { actionTypeRegistryMock } from '../action_type_registry.mock';
import { encryptedSavedObjectsMock } from '../../../encrypted_saved_objects/server/mocks';
-import { loggingSystemMock, savedObjectsClientMock } from '../../../../../src/core/server/mocks';
+import { loggingSystemMock } from '../../../../../src/core/server/mocks';
import { eventLoggerMock } from '../../../event_log/server/mocks';
import { spacesServiceMock } from '../../../spaces/server/spaces_service/spaces_service.mock';
import { ActionType } from '../types';
-import { actionsMock } from '../mocks';
+import { actionsMock, actionsClientMock } from '../mocks';
+import { pick } from 'lodash';
const actionExecutor = new ActionExecutor({ isESOUsingEphemeralEncryptionKey: false });
const services = actionsMock.createServices();
-const savedObjectsClientWithHidden = savedObjectsClientMock.create();
+
+const actionsClient = actionsClientMock.create();
const encryptedSavedObjectsClient = encryptedSavedObjectsMock.createClient();
const actionTypeRegistry = actionTypeRegistryMock.create();
@@ -30,11 +32,12 @@ const executeParams = {
};
const spacesMock = spacesServiceMock.createSetupContract();
+const getActionsClientWithRequest = jest.fn();
actionExecutor.initialize({
logger: loggingSystemMock.create().get(),
spaces: spacesMock,
getServices: () => services,
- getScopedSavedObjectsClient: () => savedObjectsClientWithHidden,
+ getActionsClientWithRequest,
actionTypeRegistry,
encryptedSavedObjectsClient,
eventLogger: eventLoggerMock.create(),
@@ -44,6 +47,7 @@ actionExecutor.initialize({
beforeEach(() => {
jest.resetAllMocks();
spacesMock.getSpaceId.mockReturnValue('some-namespace');
+ getActionsClientWithRequest.mockResolvedValue(actionsClient);
});
test('successfully executes', async () => {
@@ -67,7 +71,13 @@ test('successfully executes', async () => {
},
references: [],
};
- savedObjectsClientWithHidden.get.mockResolvedValueOnce(actionSavedObject);
+ const actionResult = {
+ id: actionSavedObject.id,
+ name: actionSavedObject.id,
+ ...pick(actionSavedObject.attributes, 'actionTypeId', 'config'),
+ isPreconfigured: false,
+ };
+ actionsClient.get.mockResolvedValueOnce(actionResult);
encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce(actionSavedObject);
actionTypeRegistry.get.mockReturnValueOnce(actionType);
await actionExecutor.execute(executeParams);
@@ -108,7 +118,13 @@ test('provides empty config when config and / or secrets is empty', async () =>
},
references: [],
};
- savedObjectsClientWithHidden.get.mockResolvedValueOnce(actionSavedObject);
+ const actionResult = {
+ id: actionSavedObject.id,
+ name: actionSavedObject.id,
+ actionTypeId: actionSavedObject.attributes.actionTypeId,
+ isPreconfigured: false,
+ };
+ actionsClient.get.mockResolvedValueOnce(actionResult);
encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce(actionSavedObject);
actionTypeRegistry.get.mockReturnValueOnce(actionType);
await actionExecutor.execute(executeParams);
@@ -138,7 +154,13 @@ test('throws an error when config is invalid', async () => {
},
references: [],
};
- savedObjectsClientWithHidden.get.mockResolvedValueOnce(actionSavedObject);
+ const actionResult = {
+ id: actionSavedObject.id,
+ name: actionSavedObject.id,
+ actionTypeId: actionSavedObject.attributes.actionTypeId,
+ isPreconfigured: false,
+ };
+ actionsClient.get.mockResolvedValueOnce(actionResult);
encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce(actionSavedObject);
actionTypeRegistry.get.mockReturnValueOnce(actionType);
@@ -171,7 +193,13 @@ test('throws an error when params is invalid', async () => {
},
references: [],
};
- savedObjectsClientWithHidden.get.mockResolvedValueOnce(actionSavedObject);
+ const actionResult = {
+ id: actionSavedObject.id,
+ name: actionSavedObject.id,
+ actionTypeId: actionSavedObject.attributes.actionTypeId,
+ isPreconfigured: false,
+ };
+ actionsClient.get.mockResolvedValueOnce(actionResult);
encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce(actionSavedObject);
actionTypeRegistry.get.mockReturnValueOnce(actionType);
@@ -185,7 +213,7 @@ test('throws an error when params is invalid', async () => {
});
test('throws an error when failing to load action through savedObjectsClient', async () => {
- savedObjectsClientWithHidden.get.mockRejectedValueOnce(new Error('No access'));
+ actionsClient.get.mockRejectedValueOnce(new Error('No access'));
await expect(actionExecutor.execute(executeParams)).rejects.toThrowErrorMatchingInlineSnapshot(
`"No access"`
);
@@ -206,7 +234,13 @@ test('throws an error if actionType is not enabled', async () => {
},
references: [],
};
- savedObjectsClientWithHidden.get.mockResolvedValueOnce(actionSavedObject);
+ const actionResult = {
+ id: actionSavedObject.id,
+ name: actionSavedObject.id,
+ actionTypeId: actionSavedObject.attributes.actionTypeId,
+ isPreconfigured: false,
+ };
+ actionsClient.get.mockResolvedValueOnce(actionResult);
encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce(actionSavedObject);
actionTypeRegistry.get.mockReturnValueOnce(actionType);
actionTypeRegistry.ensureActionTypeEnabled.mockImplementationOnce(() => {
@@ -240,7 +274,13 @@ test('should not throws an error if actionType is preconfigured', async () => {
},
references: [],
};
- savedObjectsClientWithHidden.get.mockResolvedValueOnce(actionSavedObject);
+ const actionResult = {
+ id: actionSavedObject.id,
+ name: actionSavedObject.id,
+ ...pick(actionSavedObject.attributes, 'actionTypeId', 'config', 'secrets'),
+ isPreconfigured: false,
+ };
+ actionsClient.get.mockResolvedValueOnce(actionResult);
encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce(actionSavedObject);
actionTypeRegistry.get.mockReturnValueOnce(actionType);
actionTypeRegistry.ensureActionTypeEnabled.mockImplementationOnce(() => {
@@ -268,7 +308,7 @@ test('throws an error when passing isESOUsingEphemeralEncryptionKey with value o
customActionExecutor.initialize({
logger: loggingSystemMock.create().get(),
spaces: spacesMock,
- getScopedSavedObjectsClient: () => savedObjectsClientWithHidden,
+ getActionsClientWithRequest,
getServices: () => services,
actionTypeRegistry,
encryptedSavedObjectsClient,
diff --git a/x-pack/plugins/actions/server/lib/action_executor.ts b/x-pack/plugins/actions/server/lib/action_executor.ts
index 250bfc2752f1b..0e63cc8f5956e 100644
--- a/x-pack/plugins/actions/server/lib/action_executor.ts
+++ b/x-pack/plugins/actions/server/lib/action_executor.ts
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { Logger, KibanaRequest, SavedObjectsClientContract } from '../../../../../src/core/server';
+import { Logger, KibanaRequest } from '../../../../../src/core/server';
import { validateParams, validateConfig, validateSecrets } from './validate_with_schema';
import {
ActionTypeExecutorResult,
@@ -15,14 +15,15 @@ import {
} from '../types';
import { EncryptedSavedObjectsClient } from '../../../encrypted_saved_objects/server';
import { SpacesServiceSetup } from '../../../spaces/server';
-import { EVENT_LOG_ACTIONS } from '../plugin';
+import { EVENT_LOG_ACTIONS, PluginStartContract } from '../plugin';
import { IEvent, IEventLogger, SAVED_OBJECT_REL_PRIMARY } from '../../../event_log/server';
+import { ActionsClient } from '../actions_client';
export interface ActionExecutorContext {
logger: Logger;
spaces?: SpacesServiceSetup;
getServices: GetServicesFunction;
- getScopedSavedObjectsClient: (req: KibanaRequest) => SavedObjectsClientContract;
+ getActionsClientWithRequest: PluginStartContract['getActionsClientWithRequest'];
encryptedSavedObjectsClient: EncryptedSavedObjectsClient;
actionTypeRegistry: ActionTypeRegistryContract;
eventLogger: IEventLogger;
@@ -76,7 +77,7 @@ export class ActionExecutor {
actionTypeRegistry,
eventLogger,
preconfiguredActions,
- getScopedSavedObjectsClient,
+ getActionsClientWithRequest,
} = this.actionExecutorContext!;
const services = getServices(request);
@@ -84,7 +85,7 @@ export class ActionExecutor {
const namespace = spaceId && spaceId !== 'default' ? { namespace: spaceId } : {};
const { actionTypeId, name, config, secrets } = await getActionInfo(
- getScopedSavedObjectsClient(request),
+ await getActionsClientWithRequest(request),
encryptedSavedObjectsClient,
preconfiguredActions,
actionId,
@@ -196,7 +197,7 @@ interface ActionInfo {
}
async function getActionInfo(
- savedObjectsClient: SavedObjectsClientContract,
+ actionsClient: PublicMethodsOf,
encryptedSavedObjectsClient: EncryptedSavedObjectsClient,
preconfiguredActions: PreConfiguredAction[],
actionId: string,
@@ -217,9 +218,7 @@ async function getActionInfo(
// if not pre-configured action, should be a saved object
// ensure user can read the action before processing
- const {
- attributes: { actionTypeId, config, name },
- } = await savedObjectsClient.get('action', actionId);
+ const { actionTypeId, config, name } = await actionsClient.get({ id: actionId });
const {
attributes: { secrets },
diff --git a/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts b/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts
index 06cb84ad79a89..78522682054e1 100644
--- a/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts
+++ b/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts
@@ -15,6 +15,7 @@ import { encryptedSavedObjectsMock } from '../../../encrypted_saved_objects/serv
import { savedObjectsClientMock, loggingSystemMock } from 'src/core/server/mocks';
import { eventLoggerMock } from '../../../event_log/server/mocks';
import { ActionTypeDisabledError } from './errors';
+import { actionsClientMock } from '../mocks';
const spaceIdToNamespace = jest.fn();
const actionTypeRegistry = actionTypeRegistryMock.create();
@@ -59,7 +60,7 @@ const actionExecutorInitializerParams = {
logger: loggingSystemMock.create().get(),
getServices: jest.fn().mockReturnValue(services),
actionTypeRegistry,
- getScopedSavedObjectsClient: () => savedObjectsClientMock.create(),
+ getActionsClientWithRequest: jest.fn(async () => actionsClientMock.create()),
encryptedSavedObjectsClient: mockedEncryptedSavedObjectsClient,
eventLogger: eventLoggerMock.create(),
preconfiguredActions: [],
diff --git a/x-pack/plugins/actions/server/plugin.ts b/x-pack/plugins/actions/server/plugin.ts
index 62bd1058774de..3eedd69410d11 100644
--- a/x-pack/plugins/actions/server/plugin.ts
+++ b/x-pack/plugins/actions/server/plugin.ts
@@ -249,10 +249,32 @@ export class ActionsPlugin implements Plugin, Plugi
includedHiddenTypes,
});
- const getScopedSavedObjectsClient = (request: KibanaRequest) =>
- core.savedObjects.getScopedClient(request, {
- includedHiddenTypes,
+ const getActionsClientWithRequest = async (request: KibanaRequest) => {
+ if (isESOUsingEphemeralEncryptionKey === true) {
+ throw new Error(
+ `Unable to create actions client due to the Encrypted Saved Objects plugin using an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in kibana.yml`
+ );
+ }
+ return new ActionsClient({
+ savedObjectsClient: core.savedObjects.getScopedClient(request, {
+ excludedWrappers: ['security'],
+ includedHiddenTypes,
+ }),
+ actionTypeRegistry: actionTypeRegistry!,
+ defaultKibanaIndex: await kibanaIndex,
+ scopedClusterClient: core.elasticsearch.legacy.client.asScoped(request),
+ preconfiguredActions,
+ request,
+ authorization: instantiateAuthorization(request),
+ actionExecutor: actionExecutor!,
+ executionEnqueuer: createExecutionEnqueuerFunction({
+ taskManager: plugins.taskManager,
+ actionTypeRegistry: actionTypeRegistry!,
+ isESOUsingEphemeralEncryptionKey: isESOUsingEphemeralEncryptionKey!,
+ preconfiguredActions,
+ }),
});
+ };
const getScopedSavedObjectsClientWithoutAccessToActions = (request: KibanaRequest) =>
core.savedObjects.getScopedClient(request);
@@ -261,7 +283,7 @@ export class ActionsPlugin implements Plugin, Plugi
logger,
eventLogger: this.eventLogger!,
spaces: this.spaces,
- getScopedSavedObjectsClient,
+ getActionsClientWithRequest,
getServices: this.getServicesFactory(
getScopedSavedObjectsClientWithoutAccessToActions,
core.elasticsearch
@@ -277,7 +299,7 @@ export class ActionsPlugin implements Plugin, Plugi
encryptedSavedObjectsClient,
getBasePath: this.getBasePath,
spaceIdToNamespace: this.spaceIdToNamespace,
- getScopedSavedObjectsClient,
+ getScopedSavedObjectsClient: core.savedObjects.getScopedClient,
});
scheduleActionsTelemetry(this.telemetryLogger, plugins.taskManager);
@@ -292,29 +314,7 @@ export class ActionsPlugin implements Plugin, Plugi
getActionsAuthorizationWithRequest(request: KibanaRequest) {
return instantiateAuthorization(request);
},
- async getActionsClientWithRequest(request: KibanaRequest) {
- if (isESOUsingEphemeralEncryptionKey === true) {
- throw new Error(
- `Unable to create actions client due to the Encrypted Saved Objects plugin using an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in kibana.yml`
- );
- }
- return new ActionsClient({
- savedObjectsClient: getScopedSavedObjectsClient(request),
- actionTypeRegistry: actionTypeRegistry!,
- defaultKibanaIndex: await kibanaIndex,
- scopedClusterClient: core.elasticsearch.legacy.client.asScoped(request),
- preconfiguredActions,
- request,
- authorization: instantiateAuthorization(request),
- actionExecutor: actionExecutor!,
- executionEnqueuer: createExecutionEnqueuerFunction({
- taskManager: plugins.taskManager,
- actionTypeRegistry: actionTypeRegistry!,
- isESOUsingEphemeralEncryptionKey: isESOUsingEphemeralEncryptionKey!,
- preconfiguredActions,
- }),
- });
- },
+ getActionsClientWithRequest,
preconfiguredActions,
};
}
@@ -364,7 +364,10 @@ export class ActionsPlugin implements Plugin, Plugi
);
}
return new ActionsClient({
- savedObjectsClient: savedObjects.getScopedClient(request, { includedHiddenTypes }),
+ savedObjectsClient: savedObjects.getScopedClient(request, {
+ excludedWrappers: ['security'],
+ includedHiddenTypes,
+ }),
actionTypeRegistry: actionTypeRegistry!,
defaultKibanaIndex,
scopedClusterClient: context.core.elasticsearch.legacy.client,
diff --git a/x-pack/plugins/apm/common/anomaly_detection.ts b/x-pack/plugins/apm/common/anomaly_detection.ts
index 1fd927d82f186..9e0a3e3d0d889 100644
--- a/x-pack/plugins/apm/common/anomaly_detection.ts
+++ b/x-pack/plugins/apm/common/anomaly_detection.ts
@@ -4,9 +4,59 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import { i18n } from '@kbn/i18n';
+
export interface ServiceAnomalyStats {
transactionType?: string;
anomalyScore?: number;
actualValue?: number;
jobId?: string;
}
+
+export const MLErrorMessages: Record = {
+ INSUFFICIENT_LICENSE: i18n.translate(
+ 'xpack.apm.anomaly_detection.error.insufficient_license',
+ {
+ defaultMessage:
+ 'You must have a platinum license to use Anomaly Detection',
+ }
+ ),
+ MISSING_READ_PRIVILEGES: i18n.translate(
+ 'xpack.apm.anomaly_detection.error.missing_read_privileges',
+ {
+ defaultMessage:
+ 'You must have "read" privileges to Machine Learning in order to view Anomaly Detection jobs',
+ }
+ ),
+ MISSING_WRITE_PRIVILEGES: i18n.translate(
+ 'xpack.apm.anomaly_detection.error.missing_write_privileges',
+ {
+ defaultMessage:
+ 'You must have "write" privileges to Machine Learning and APM in order to create Anomaly Detection jobs',
+ }
+ ),
+ ML_NOT_AVAILABLE: i18n.translate(
+ 'xpack.apm.anomaly_detection.error.not_available',
+ {
+ defaultMessage: 'Machine learning is not available',
+ }
+ ),
+ ML_NOT_AVAILABLE_IN_SPACE: i18n.translate(
+ 'xpack.apm.anomaly_detection.error.not_available_in_space',
+ {
+ defaultMessage: 'Machine learning is not available in the selected space',
+ }
+ ),
+ UNEXPECTED: i18n.translate('xpack.apm.anomaly_detection.error.unexpected', {
+ defaultMessage: 'An unexpected error occurred',
+ }),
+};
+
+export enum ErrorCode {
+ INSUFFICIENT_LICENSE = 'INSUFFICIENT_LICENSE',
+ MISSING_READ_PRIVILEGES = 'MISSING_READ_PRIVILEGES',
+ MISSING_WRITE_PRIVILEGES = 'MISSING_WRITE_PRIVILEGES',
+ ML_NOT_AVAILABLE = 'ML_NOT_AVAILABLE',
+ ML_NOT_AVAILABLE_IN_SPACE = 'ML_NOT_AVAILABLE_IN_SPACE',
+ UNEXPECTED = 'UNEXPECTED',
+}
diff --git a/x-pack/plugins/apm/common/ml_job_constants.test.ts b/x-pack/plugins/apm/common/ml_job_constants.test.ts
deleted file mode 100644
index 96e3ba826d201..0000000000000
--- a/x-pack/plugins/apm/common/ml_job_constants.test.ts
+++ /dev/null
@@ -1,41 +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 { getSeverity, severity } from './ml_job_constants';
-
-describe('ml_job_constants', () => {
- describe('getSeverity', () => {
- describe('when score is undefined', () => {
- it('returns undefined', () => {
- expect(getSeverity(undefined)).toEqual(undefined);
- });
- });
-
- describe('when score < 25', () => {
- it('returns warning', () => {
- expect(getSeverity(10)).toEqual(severity.warning);
- });
- });
-
- describe('when score is between 25 and 50', () => {
- it('returns minor', () => {
- expect(getSeverity(40)).toEqual(severity.minor);
- });
- });
-
- describe('when score is between 50 and 75', () => {
- it('returns major', () => {
- expect(getSeverity(60)).toEqual(severity.major);
- });
- });
-
- describe('when score is 75 or more', () => {
- it('returns critical', () => {
- expect(getSeverity(100)).toEqual(severity.critical);
- });
- });
- });
-});
diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx
index aa95918939dfa..80c749e58c88c 100644
--- a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx
@@ -12,7 +12,6 @@ import d3 from 'd3';
import { scaleUtc } from 'd3-scale';
import { mean } from 'lodash';
import React from 'react';
-import { px } from '../../../../style/variables';
import { asRelativeDateTimeRange } from '../../../../utils/formatters';
import { getTimezoneOffsetInMs } from '../../../shared/charts/CustomPlot/getTimezoneOffsetInMs';
// @ts-ignore
@@ -89,7 +88,7 @@ export function ErrorDistribution({ distribution, title }: Props) {
{title} bucket.x}
diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/AnomalyDetection.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/AnomalyDetection.tsx
index 410ba8b5027fb..b3d19e1aab2cc 100644
--- a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/AnomalyDetection.tsx
+++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/AnomalyDetection.tsx
@@ -19,9 +19,9 @@ import { fontSize, px } from '../../../../style/variables';
import { asInteger, asDuration } from '../../../../utils/formatters';
import { MLJobLink } from '../../../shared/Links/MachineLearningLinks/MLJobLink';
import { getSeverityColor, popoverWidth } from '../cytoscapeOptions';
-import { getSeverity } from '../../../../../common/ml_job_constants';
import { TRANSACTION_REQUEST } from '../../../../../common/transaction_types';
import { ServiceAnomalyStats } from '../../../../../common/anomaly_detection';
+import { getSeverity } from './getSeverity';
const HealthStatusTitle = styled(EuiTitle)`
display: inline;
diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/getSeverity.test.ts b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/getSeverity.test.ts
new file mode 100644
index 0000000000000..52b7d54236db6
--- /dev/null
+++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/getSeverity.test.ts
@@ -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 { getSeverity, severity } from './getSeverity';
+
+describe('getSeverity', () => {
+ describe('when score is undefined', () => {
+ it('returns undefined', () => {
+ expect(getSeverity(undefined)).toEqual(undefined);
+ });
+ });
+
+ describe('when score < 25', () => {
+ it('returns warning', () => {
+ expect(getSeverity(10)).toEqual(severity.warning);
+ });
+ });
+
+ describe('when score is between 25 and 50', () => {
+ it('returns minor', () => {
+ expect(getSeverity(40)).toEqual(severity.minor);
+ });
+ });
+
+ describe('when score is between 50 and 75', () => {
+ it('returns major', () => {
+ expect(getSeverity(60)).toEqual(severity.major);
+ });
+ });
+
+ describe('when score is 75 or more', () => {
+ it('returns critical', () => {
+ expect(getSeverity(100)).toEqual(severity.critical);
+ });
+ });
+});
diff --git a/x-pack/plugins/apm/common/ml_job_constants.ts b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/getSeverity.ts
similarity index 80%
rename from x-pack/plugins/apm/common/ml_job_constants.ts
rename to x-pack/plugins/apm/public/components/app/ServiceMap/Popover/getSeverity.ts
index b8c2546bd0c84..f4eb2033e9231 100644
--- a/x-pack/plugins/apm/common/ml_job_constants.ts
+++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/getSeverity.ts
@@ -11,6 +11,8 @@ export enum severity {
warning = 'warning',
}
+// TODO: Replace with `getSeverity` from:
+// https://github.com/elastic/kibana/blob/0f964f66916480f2de1f4b633e5afafc08cf62a0/x-pack/plugins/ml/common/util/anomaly_utils.ts#L129
export function getSeverity(score?: number) {
if (typeof score !== 'number') {
return undefined;
diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/__stories__/generate_service_map_elements.ts b/x-pack/plugins/apm/public/components/app/ServiceMap/__stories__/generate_service_map_elements.ts
index e7d55cd570710..012256db3ab98 100644
--- a/x-pack/plugins/apm/public/components/app/ServiceMap/__stories__/generate_service_map_elements.ts
+++ b/x-pack/plugins/apm/public/components/app/ServiceMap/__stories__/generate_service_map_elements.ts
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { getSeverity } from '../../../../../common/ml_job_constants';
+import { getSeverity } from '../Popover/getSeverity';
export function generateServiceMapElements(size: number): any[] {
const services = range(size).map((i) => {
diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts b/x-pack/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts
index dfcfbee1806a4..4a271019e06db 100644
--- a/x-pack/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts
+++ b/x-pack/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts
@@ -10,9 +10,9 @@ import {
SPAN_DESTINATION_SERVICE_RESOURCE,
} from '../../../../common/elasticsearch_fieldnames';
import { EuiTheme } from '../../../../../observability/public';
-import { severity, getSeverity } from '../../../../common/ml_job_constants';
import { defaultIcon, iconForNode } from './icons';
import { ServiceAnomalyStats } from '../../../../common/anomaly_detection';
+import { severity, getSeverity } from './Popover/getSeverity';
export const popoverWidth = 280;
diff --git a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/add_environments.tsx b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/add_environments.tsx
index 4c056d48f4b14..c9328c4988e5f 100644
--- a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/add_environments.tsx
+++ b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/add_environments.tsx
@@ -17,8 +17,10 @@ import {
EuiFlexGroup,
EuiFlexItem,
EuiFormRow,
+ EuiEmptyPrompt,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
+import { MLErrorMessages } from '../../../../../common/anomaly_detection';
import { useFetcher, FETCH_STATUS } from '../../../../hooks/useFetcher';
import { useApmPluginContext } from '../../../../hooks/useApmPluginContext';
import { createJobs } from './create_jobs';
@@ -34,7 +36,9 @@ export const AddEnvironments = ({
onCreateJobSuccess,
onCancel,
}: Props) => {
- const { toasts } = useApmPluginContext().core.notifications;
+ const { notifications, application } = useApmPluginContext().core;
+ const canCreateJob = !!application.capabilities.ml.canCreateJob;
+ const { toasts } = notifications;
const { data = [], status } = useFetcher(
(callApmApi) =>
callApmApi({
@@ -56,6 +60,17 @@ export const AddEnvironments = ({
Array>
>([]);
+ if (!canCreateJob) {
+ return (
+
+ {MLErrorMessages.MISSING_WRITE_PRIVILEGES}>}
+ />
+
+ );
+ }
+
const isLoading =
status === FETCH_STATUS.PENDING || status === FETCH_STATUS.LOADING;
return (
diff --git a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/create_jobs.ts b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/create_jobs.ts
index 614632a5a3b09..acea38732b40a 100644
--- a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/create_jobs.ts
+++ b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/create_jobs.ts
@@ -6,8 +6,19 @@
import { i18n } from '@kbn/i18n';
import { NotificationsStart } from 'kibana/public';
+import { MLErrorMessages } from '../../../../../common/anomaly_detection';
import { callApmApi } from '../../../../services/rest/createCallApmApi';
+const errorToastTitle = i18n.translate(
+ 'xpack.apm.anomalyDetection.createJobs.failed.title',
+ { defaultMessage: 'Anomaly detection jobs could not be created' }
+);
+
+const successToastTitle = i18n.translate(
+ 'xpack.apm.anomalyDetection.createJobs.succeeded.title',
+ { defaultMessage: 'Anomaly detection jobs created' }
+);
+
export async function createJobs({
environments,
toasts,
@@ -16,7 +27,7 @@ export async function createJobs({
toasts: NotificationsStart['toasts'];
}) {
try {
- await callApmApi({
+ const res = await callApmApi({
pathname: '/api/apm/settings/anomaly-detection/jobs',
method: 'POST',
params: {
@@ -24,41 +35,50 @@ export async function createJobs({
},
});
+ // a known error occurred
+ if (res?.errorCode) {
+ toasts.addDanger({
+ title: errorToastTitle,
+ text: MLErrorMessages[res.errorCode],
+ });
+ return false;
+ }
+
+ // job created successfully
toasts.addSuccess({
- title: i18n.translate(
- 'xpack.apm.anomalyDetection.createJobs.succeeded.title',
- { defaultMessage: 'Anomaly detection jobs created' }
- ),
- text: i18n.translate(
- 'xpack.apm.anomalyDetection.createJobs.succeeded.text',
- {
- defaultMessage:
- 'Anomaly detection jobs successfully created for APM service environments [{environments}]. It will take some time for machine learning to start analyzing traffic for anomalies.',
- values: { environments: environments.join(', ') },
- }
- ),
+ title: successToastTitle,
+ text: getSuccessToastMessage(environments),
});
return true;
+
+ // an unknown/unexpected error occurred
} catch (error) {
toasts.addDanger({
- title: i18n.translate(
- 'xpack.apm.anomalyDetection.createJobs.failed.title',
- {
- defaultMessage: 'Anomaly detection jobs could not be created',
- }
- ),
- text: i18n.translate(
- 'xpack.apm.anomalyDetection.createJobs.failed.text',
- {
- defaultMessage:
- 'Something went wrong when creating one ore more anomaly detection jobs for APM service environments [{environments}]. Error: "{errorMessage}"',
- values: {
- environments: environments.join(', '),
- errorMessage: error.message,
- },
- }
- ),
+ title: errorToastTitle,
+ text: getErrorToastMessage(environments, error),
});
return false;
}
}
+
+function getSuccessToastMessage(environments: string[]) {
+ return i18n.translate(
+ 'xpack.apm.anomalyDetection.createJobs.succeeded.text',
+ {
+ defaultMessage:
+ 'Anomaly detection jobs successfully created for APM service environments [{environments}]. It will take some time for machine learning to start analyzing traffic for anomalies.',
+ values: { environments: environments.join(', ') },
+ }
+ );
+}
+
+function getErrorToastMessage(environments: string[], error: Error) {
+ return i18n.translate('xpack.apm.anomalyDetection.createJobs.failed.text', {
+ defaultMessage:
+ 'Something went wrong when creating one ore more anomaly detection jobs for APM service environments [{environments}]. Error: "{errorMessage}"',
+ values: {
+ environments: environments.join(', '),
+ errorMessage: error.message,
+ },
+ });
+}
diff --git a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/index.tsx
index f02350fafbabb..abbe1e2c83c7b 100644
--- a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/index.tsx
@@ -7,7 +7,9 @@
import React, { useState } from 'react';
import { EuiTitle, EuiSpacer, EuiText } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
-import { EuiPanel } from '@elastic/eui';
+import { EuiPanel, EuiEmptyPrompt } from '@elastic/eui';
+import { MLErrorMessages } from '../../../../../common/anomaly_detection';
+import { useApmPluginContext } from '../../../../hooks/useApmPluginContext';
import { JobsList } from './jobs_list';
import { AddEnvironments } from './add_environments';
import { useFetcher } from '../../../../hooks/useFetcher';
@@ -16,24 +18,31 @@ import { useLicense } from '../../../../hooks/useLicense';
import { APIReturnType } from '../../../../services/rest/createCallApmApi';
export type AnomalyDetectionApiResponse = APIReturnType<
- '/api/apm/settings/anomaly-detection'
+ '/api/apm/settings/anomaly-detection',
+ 'GET'
>;
const DEFAULT_VALUE: AnomalyDetectionApiResponse = {
jobs: [],
hasLegacyJobs: false,
+ errorCode: undefined,
};
export const AnomalyDetection = () => {
+ const plugin = useApmPluginContext();
+ const canGetJobs = !!plugin.core.application.capabilities.ml.canGetJobs;
const license = useLicense();
const hasValidLicense = license?.isActive && license?.hasAtLeast('platinum');
const [viewAddEnvironments, setViewAddEnvironments] = useState(false);
const { refetch, data = DEFAULT_VALUE, status } = useFetcher(
- (callApmApi) =>
- callApmApi({ pathname: `/api/apm/settings/anomaly-detection` }),
- [],
+ (callApmApi) => {
+ if (canGetJobs) {
+ return callApmApi({ pathname: `/api/apm/settings/anomaly-detection` });
+ }
+ },
+ [canGetJobs],
{ preservePreviousData: false, showToastOnError: false }
);
@@ -53,6 +62,17 @@ export const AnomalyDetection = () => {
);
}
+ if (!canGetJobs) {
+ return (
+
+ {MLErrorMessages.MISSING_READ_PRIVILEGES}>}
+ />
+
+ );
+ }
+
return (
<>
@@ -83,9 +103,8 @@ export const AnomalyDetection = () => {
/>
) : (
{
setViewAddEnvironments(true);
}}
diff --git a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/jobs_list.tsx b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/jobs_list.tsx
index 5954b82f3b9e7..67227f99cb5f1 100644
--- a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/jobs_list.tsx
+++ b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/jobs_list.tsx
@@ -16,6 +16,10 @@ import {
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
+import {
+ MLErrorMessages,
+ ErrorCode,
+} from '../../../../../common/anomaly_detection';
import { FETCH_STATUS } from '../../../../hooks/useFetcher';
import { ITableColumn, ManagedTable } from '../../../shared/ManagedTable';
import { LoadingStatePrompt } from '../../../shared/LoadingStatePrompt';
@@ -57,21 +61,12 @@ const columns: Array> = [
];
interface Props {
+ data: AnomalyDetectionApiResponse;
status: FETCH_STATUS;
onAddEnvironments: () => void;
- jobs: Jobs;
- hasLegacyJobs: boolean;
}
-export const JobsList = ({
- status,
- onAddEnvironments,
- jobs,
- hasLegacyJobs,
-}: Props) => {
- const isLoading =
- status === FETCH_STATUS.PENDING || status === FETCH_STATUS.LOADING;
-
- const hasFetchFailure = status === FETCH_STATUS.FAILURE;
+export const JobsList = ({ data, status, onAddEnvironments }: Props) => {
+ const { jobs, hasLegacyJobs, errorCode } = data;
return (
@@ -120,15 +115,10 @@ export const JobsList = ({
- ) : hasFetchFailure ? (
-
- ) : (
-
- )
- }
+ noItemsMessage={getNoItemsMessage({
+ status,
+ errorCode,
+ })}
columns={columns}
items={jobs}
/>
@@ -139,28 +129,36 @@ export const JobsList = ({
);
};
-function EmptyStatePrompt() {
- return (
- <>
- {i18n.translate(
- 'xpack.apm.settings.anomalyDetection.jobList.emptyListText',
- {
- defaultMessage: 'No anomaly detection jobs.',
- }
- )}
- >
- );
-}
+function getNoItemsMessage({
+ status,
+ errorCode,
+}: {
+ status: FETCH_STATUS;
+ errorCode?: ErrorCode;
+}) {
+ // loading state
+ const isLoading =
+ status === FETCH_STATUS.PENDING || status === FETCH_STATUS.LOADING;
+ if (isLoading) {
+ return ;
+ }
-function FailureStatePrompt() {
- return (
- <>
- {i18n.translate(
- 'xpack.apm.settings.anomalyDetection.jobList.failedFetchText',
- {
- defaultMessage: 'Unabled to fetch anomaly detection jobs.',
- }
- )}
- >
+ // A known error occured. Show specific error message
+ if (errorCode) {
+ return MLErrorMessages[errorCode];
+ }
+
+ // An unexpected error occurred. Show default error message
+ if (status === FETCH_STATUS.FAILURE) {
+ return i18n.translate(
+ 'xpack.apm.settings.anomalyDetection.jobList.failedFetchText',
+ { defaultMessage: 'Unabled to fetch anomaly detection jobs.' }
+ );
+ }
+
+ // no errors occurred
+ return i18n.translate(
+ 'xpack.apm.settings.anomalyDetection.jobList.emptyListText',
+ { defaultMessage: 'No anomaly detection jobs.' }
);
}
diff --git a/x-pack/plugins/apm/public/components/shared/Links/apm/AnomalyDetectionSetupLink.test.tsx b/x-pack/plugins/apm/public/components/shared/Links/apm/AnomalyDetectionSetupLink.test.tsx
index 268d8bd7ea823..2149cb676f0d8 100644
--- a/x-pack/plugins/apm/public/components/shared/Links/apm/AnomalyDetectionSetupLink.test.tsx
+++ b/x-pack/plugins/apm/public/components/shared/Links/apm/AnomalyDetectionSetupLink.test.tsx
@@ -6,37 +6,48 @@
import { showAlert } from './AnomalyDetectionSetupLink';
+const dataWithJobs = {
+ hasLegacyJobs: false,
+ jobs: [
+ { job_id: 'job1', environment: 'staging' },
+ { job_id: 'job2', environment: 'production' },
+ ],
+};
+const dataWithoutJobs = ({ jobs: [] } as unknown) as any;
+
describe('#showAlert', () => {
describe('when an environment is selected', () => {
it('should return true when there are no jobs', () => {
- const result = showAlert([], 'testing');
+ const result = showAlert(dataWithoutJobs, 'testing');
expect(result).toBe(true);
});
it('should return true when environment is not included in the jobs', () => {
- const result = showAlert(
- [{ environment: 'staging' }, { environment: 'production' }],
- 'testing'
- );
+ const result = showAlert(dataWithJobs, 'testing');
expect(result).toBe(true);
});
it('should return false when environment is included in the jobs', () => {
- const result = showAlert(
- [{ environment: 'staging' }, { environment: 'production' }],
- 'staging'
- );
+ const result = showAlert(dataWithJobs, 'staging');
expect(result).toBe(false);
});
});
+
describe('there is no environment selected (All)', () => {
it('should return true when there are no jobs', () => {
- const result = showAlert([], undefined);
+ const result = showAlert(dataWithoutJobs, undefined);
expect(result).toBe(true);
});
it('should return false when there are any number of jobs', () => {
- const result = showAlert(
- [{ environment: 'staging' }, { environment: 'production' }],
- undefined
- );
+ const result = showAlert(dataWithJobs, undefined);
+ expect(result).toBe(false);
+ });
+ });
+
+ describe('when a known error occurred', () => {
+ it('should return false', () => {
+ const data = ({
+ errorCode: 'MISSING_READ_PRIVILEGES',
+ } as unknown) as any;
+ const result = showAlert(data, undefined);
expect(result).toBe(false);
});
});
diff --git a/x-pack/plugins/apm/public/components/shared/Links/apm/AnomalyDetectionSetupLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/apm/AnomalyDetectionSetupLink.tsx
index 6f3a5df480d7e..e989244d43148 100644
--- a/x-pack/plugins/apm/public/components/shared/Links/apm/AnomalyDetectionSetupLink.tsx
+++ b/x-pack/plugins/apm/public/components/shared/Links/apm/AnomalyDetectionSetupLink.tsx
@@ -6,16 +6,25 @@
import React from 'react';
import { EuiButtonEmpty, EuiToolTip, EuiIcon } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
+import { ErrorCode } from '../../../../../common/anomaly_detection';
+import { APIReturnType } from '../../../../services/rest/createCallApmApi';
import { APMLink } from './APMLink';
import { getEnvironmentLabel } from '../../../../../common/environment_filter_values';
import { useUrlParams } from '../../../../hooks/useUrlParams';
import { useFetcher, FETCH_STATUS } from '../../../../hooks/useFetcher';
+export type AnomalyDetectionApiResponse = APIReturnType<
+ '/api/apm/settings/anomaly-detection',
+ 'GET'
+>;
+
+const DEFAULT_DATA = { jobs: [], hasLegacyJobs: false, errorCode: undefined };
+
export function AnomalyDetectionSetupLink() {
const { uiFilters } = useUrlParams();
const environment = uiFilters.environment;
- const { data = { jobs: [], hasLegacyJobs: false }, status } = useFetcher(
+ const { data = DEFAULT_DATA, status } = useFetcher(
(callApmApi) =>
callApmApi({ pathname: `/api/apm/settings/anomaly-detection` }),
[],
@@ -28,7 +37,7 @@ export function AnomalyDetectionSetupLink() {
{ANOMALY_DETECTION_LINK_LABEL}
- {isFetchSuccess && showAlert(data.jobs, environment) && (
+ {isFetchSuccess && showAlert(data, environment) && (
@@ -59,9 +68,14 @@ const ANOMALY_DETECTION_LINK_LABEL = i18n.translate(
);
export function showAlert(
- jobs: Array<{ environment: string }> = [],
+ { jobs = [], errorCode }: AnomalyDetectionApiResponse,
environment: string | undefined
) {
+ // don't show warning if the user is missing read privileges
+ if (errorCode === ErrorCode.MISSING_READ_PRIVILEGES) {
+ return false;
+ }
+
return (
// No job exists, or
jobs.length === 0 ||
diff --git a/x-pack/plugins/apm/server/lib/anomaly_detection/anomaly_detection_error.ts b/x-pack/plugins/apm/server/lib/anomaly_detection/anomaly_detection_error.ts
new file mode 100644
index 0000000000000..993dcf4c5354b
--- /dev/null
+++ b/x-pack/plugins/apm/server/lib/anomaly_detection/anomaly_detection_error.ts
@@ -0,0 +1,16 @@
+/*
+ * 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 { ErrorCode, MLErrorMessages } from '../../../common/anomaly_detection';
+
+export class AnomalyDetectionError extends Error {
+ constructor(public code: ErrorCode) {
+ super(MLErrorMessages[code]);
+
+ this.name = this.constructor.name;
+ Error.captureStackTrace(this, this.constructor);
+ }
+}
diff --git a/x-pack/plugins/apm/server/lib/anomaly_detection/create_anomaly_detection_jobs.ts b/x-pack/plugins/apm/server/lib/anomaly_detection/create_anomaly_detection_jobs.ts
index c387c5152b1c5..e5338ac9f5797 100644
--- a/x-pack/plugins/apm/server/lib/anomaly_detection/create_anomaly_detection_jobs.ts
+++ b/x-pack/plugins/apm/server/lib/anomaly_detection/create_anomaly_detection_jobs.ts
@@ -7,6 +7,7 @@
import { Logger } from 'kibana/server';
import uuid from 'uuid/v4';
import { snakeCase } from 'lodash';
+import { ErrorCode } from '../../../common/anomaly_detection';
import { PromiseReturnType } from '../../../../observability/typings/common';
import { Setup } from '../helpers/setup_request';
import {
@@ -15,6 +16,7 @@ import {
} from '../../../common/elasticsearch_fieldnames';
import { APM_ML_JOB_GROUP, ML_MODULE_ID_APM_TRANSACTION } from './constants';
import { getEnvironmentUiFilterES } from '../helpers/convert_ui_filters/get_environment_ui_filter_es';
+import { AnomalyDetectionError } from './anomaly_detection_error';
export type CreateAnomalyDetectionJobsAPIResponse = PromiseReturnType<
typeof createAnomalyDetectionJobs
@@ -25,21 +27,20 @@ export async function createAnomalyDetectionJobs(
logger: Logger
) {
const { ml, indices } = setup;
+
if (!ml) {
- logger.warn('Anomaly detection plugin is not available.');
- return [];
+ throw new AnomalyDetectionError(ErrorCode.ML_NOT_AVAILABLE);
}
+
const mlCapabilities = await ml.mlSystem.mlCapabilities();
if (!mlCapabilities.mlFeatureEnabledInSpace) {
- logger.warn('Anomaly detection feature is not enabled for the space.');
- return [];
+ throw new AnomalyDetectionError(ErrorCode.ML_NOT_AVAILABLE_IN_SPACE);
}
+
if (!mlCapabilities.isPlatinumOrTrialLicense) {
- logger.warn(
- 'Unable to create anomaly detection jobs due to insufficient license.'
- );
- return [];
+ throw new AnomalyDetectionError(ErrorCode.INSUFFICIENT_LICENSE);
}
+
logger.info(
`Creating ML anomaly detection jobs for environments: [${environments}].`
);
@@ -59,9 +60,8 @@ export async function createAnomalyDetectionJobs(
`Failed to create anomaly detection ML jobs for: [${failedJobIds}]:`
);
failedJobs.forEach(({ error }) => logger.error(JSON.stringify(error)));
- throw new Error(
- `Failed to create anomaly detection ML jobs for: [${failedJobIds}].`
- );
+
+ throw new AnomalyDetectionError(ErrorCode.UNEXPECTED);
}
return jobResponses;
@@ -70,11 +70,11 @@ export async function createAnomalyDetectionJobs(
async function createAnomalyDetectionJob({
ml,
environment,
- indexPatternName = 'apm-*-transaction-*',
+ indexPatternName,
}: {
ml: Required['ml'];
environment: string;
- indexPatternName?: string | undefined;
+ indexPatternName: string;
}) {
const randomToken = uuid().substr(-4);
diff --git a/x-pack/plugins/apm/server/lib/anomaly_detection/get_anomaly_detection_jobs.ts b/x-pack/plugins/apm/server/lib/anomaly_detection/get_anomaly_detection_jobs.ts
index 13b30f159eed1..62d4243a06028 100644
--- a/x-pack/plugins/apm/server/lib/anomaly_detection/get_anomaly_detection_jobs.ts
+++ b/x-pack/plugins/apm/server/lib/anomaly_detection/get_anomaly_detection_jobs.ts
@@ -5,8 +5,10 @@
*/
import { Logger } from 'kibana/server';
+import { ErrorCode } from '../../../common/anomaly_detection';
import { Setup } from '../helpers/setup_request';
import { getMlJobsWithAPMGroup } from './get_ml_jobs_with_apm_group';
+import { AnomalyDetectionError } from './anomaly_detection_error';
export async function getAnomalyDetectionJobs(setup: Setup, logger: Logger) {
const { ml } = setup;
@@ -15,14 +17,12 @@ export async function getAnomalyDetectionJobs(setup: Setup, logger: Logger) {
}
const mlCapabilities = await ml.mlSystem.mlCapabilities();
- if (
- !(
- mlCapabilities.mlFeatureEnabledInSpace &&
- mlCapabilities.isPlatinumOrTrialLicense
- )
- ) {
- logger.warn('Anomaly detection integration is not availble for this user.');
- return [];
+ if (!mlCapabilities.mlFeatureEnabledInSpace) {
+ throw new AnomalyDetectionError(ErrorCode.ML_NOT_AVAILABLE_IN_SPACE);
+ }
+
+ if (!mlCapabilities.isPlatinumOrTrialLicense) {
+ throw new AnomalyDetectionError(ErrorCode.INSUFFICIENT_LICENSE);
}
const response = await getMlJobsWithAPMGroup(ml);
diff --git a/x-pack/plugins/apm/server/routes/settings/anomaly_detection.ts b/x-pack/plugins/apm/server/routes/settings/anomaly_detection.ts
index 4d564b773e397..218d47fcf9bb4 100644
--- a/x-pack/plugins/apm/server/routes/settings/anomaly_detection.ts
+++ b/x-pack/plugins/apm/server/routes/settings/anomaly_detection.ts
@@ -5,12 +5,32 @@
*/
import * as t from 'io-ts';
+import { ErrorCode } from '../../../common/anomaly_detection';
+import { PromiseReturnType } from '../../../typings/common';
+import { InsufficientMLCapabilities } from '../../../../ml/server';
import { createRoute } from '../create_route';
import { getAnomalyDetectionJobs } from '../../lib/anomaly_detection/get_anomaly_detection_jobs';
import { createAnomalyDetectionJobs } from '../../lib/anomaly_detection/create_anomaly_detection_jobs';
import { setupRequest } from '../../lib/helpers/setup_request';
import { getAllEnvironments } from '../../lib/environments/get_all_environments';
import { hasLegacyJobs } from '../../lib/anomaly_detection/has_legacy_jobs';
+import { AnomalyDetectionError } from '../../lib/anomaly_detection/anomaly_detection_error';
+
+type Jobs = PromiseReturnType;
+
+function getMlErrorCode(e: Error) {
+ // Missing privileges
+ if (e instanceof InsufficientMLCapabilities) {
+ return ErrorCode.MISSING_READ_PRIVILEGES;
+ }
+
+ if (e instanceof AnomalyDetectionError) {
+ return e.code;
+ }
+
+ // unexpected error
+ return ErrorCode.UNEXPECTED;
+}
// get ML anomaly detection jobs for each environment
export const anomalyDetectionJobsRoute = createRoute(() => ({
@@ -18,14 +38,25 @@ export const anomalyDetectionJobsRoute = createRoute(() => ({
path: '/api/apm/settings/anomaly-detection',
handler: async ({ context, request }) => {
const setup = await setupRequest(context, request);
- const [jobs, legacyJobs] = await Promise.all([
- getAnomalyDetectionJobs(setup, context.logger),
- hasLegacyJobs(setup),
- ]);
- return {
- jobs,
- hasLegacyJobs: legacyJobs,
- };
+
+ try {
+ const [jobs, legacyJobs] = await Promise.all([
+ getAnomalyDetectionJobs(setup, context.logger),
+ hasLegacyJobs(setup),
+ ]);
+ return {
+ jobs,
+ hasLegacyJobs: legacyJobs,
+ };
+ } catch (e) {
+ const mlErrorCode = getMlErrorCode(e);
+ context.logger.warn(`Error while retrieving ML jobs: "${e.message}"`);
+ return {
+ jobs: [] as Jobs,
+ hasLegacyJobs: false,
+ errorCode: mlErrorCode,
+ };
+ }
},
}));
@@ -44,11 +75,16 @@ export const createAnomalyDetectionJobsRoute = createRoute(() => ({
handler: async ({ context, request }) => {
const { environments } = context.params.body;
const setup = await setupRequest(context, request);
- return await createAnomalyDetectionJobs(
- setup,
- environments,
- context.logger
- );
+
+ try {
+ await createAnomalyDetectionJobs(setup, environments, context.logger);
+ } catch (e) {
+ const mlErrorCode = getMlErrorCode(e);
+ context.logger.warn(`Error while creating ML job: "${e.message}"`);
+ return {
+ errorCode: mlErrorCode,
+ };
+ }
},
}));
diff --git a/x-pack/plugins/canvas/.gitignore b/x-pack/plugins/canvas/.gitignore
index 1c6258670c59c..d47bd8acf6be2 100644
--- a/x-pack/plugins/canvas/.gitignore
+++ b/x-pack/plugins/canvas/.gitignore
@@ -57,4 +57,4 @@ canvas_plugin/*
webpack_stats.json
# Don't commit storybook builds
-storybook
+storybook/build
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/advanced_filter/component/__examples__/__snapshots__/advanced_filter.examples.storyshot b/x-pack/plugins/canvas/canvas_plugin_src/renderers/advanced_filter/component/__examples__/__snapshots__/advanced_filter.stories.storyshot
similarity index 100%
rename from x-pack/plugins/canvas/canvas_plugin_src/renderers/advanced_filter/component/__examples__/__snapshots__/advanced_filter.examples.storyshot
rename to x-pack/plugins/canvas/canvas_plugin_src/renderers/advanced_filter/component/__examples__/__snapshots__/advanced_filter.stories.storyshot
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/advanced_filter/component/__examples__/advanced_filter.examples.tsx b/x-pack/plugins/canvas/canvas_plugin_src/renderers/advanced_filter/component/__examples__/advanced_filter.stories.tsx
similarity index 100%
rename from x-pack/plugins/canvas/canvas_plugin_src/renderers/advanced_filter/component/__examples__/advanced_filter.examples.tsx
rename to x-pack/plugins/canvas/canvas_plugin_src/renderers/advanced_filter/component/__examples__/advanced_filter.stories.tsx
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/dropdown_filter/component/__examples__/__snapshots__/dropdown_filter.examples.storyshot b/x-pack/plugins/canvas/canvas_plugin_src/renderers/dropdown_filter/component/__examples__/__snapshots__/dropdown_filter.stories.storyshot
similarity index 100%
rename from x-pack/plugins/canvas/canvas_plugin_src/renderers/dropdown_filter/component/__examples__/__snapshots__/dropdown_filter.examples.storyshot
rename to x-pack/plugins/canvas/canvas_plugin_src/renderers/dropdown_filter/component/__examples__/__snapshots__/dropdown_filter.stories.storyshot
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/dropdown_filter/component/__examples__/dropdown_filter.examples.tsx b/x-pack/plugins/canvas/canvas_plugin_src/renderers/dropdown_filter/component/__examples__/dropdown_filter.stories.tsx
similarity index 100%
rename from x-pack/plugins/canvas/canvas_plugin_src/renderers/dropdown_filter/component/__examples__/dropdown_filter.examples.tsx
rename to x-pack/plugins/canvas/canvas_plugin_src/renderers/dropdown_filter/component/__examples__/dropdown_filter.stories.tsx
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/__examples__/__snapshots__/time_filter.examples.storyshot b/x-pack/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/__examples__/__snapshots__/time_filter.examples.storyshot
deleted file mode 100644
index d555fbbe0ce92..0000000000000
--- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/__examples__/__snapshots__/time_filter.examples.storyshot
+++ /dev/null
@@ -1,134 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Storyshots renderers/TimeFilter default 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- →
-
-
-
-
-
-
-
-
-
-
-
-
-
-`;
diff --git a/x-pack/plugins/canvas/public/components/asset_manager/__examples__/__snapshots__/asset.examples.storyshot b/x-pack/plugins/canvas/public/components/asset_manager/__examples__/__snapshots__/asset.stories.storyshot
similarity index 100%
rename from x-pack/plugins/canvas/public/components/asset_manager/__examples__/__snapshots__/asset.examples.storyshot
rename to x-pack/plugins/canvas/public/components/asset_manager/__examples__/__snapshots__/asset.stories.storyshot
diff --git a/x-pack/plugins/canvas/public/components/asset_manager/__examples__/__snapshots__/asset_manager.examples.storyshot b/x-pack/plugins/canvas/public/components/asset_manager/__examples__/__snapshots__/asset_manager.examples.storyshot
deleted file mode 100644
index 9f91668e602a4..0000000000000
--- a/x-pack/plugins/canvas/public/components/asset_manager/__examples__/__snapshots__/asset_manager.examples.storyshot
+++ /dev/null
@@ -1,37 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Storyshots components/Assets/AssetManager no assets 1`] = `
-
-`;
-
-exports[`Storyshots components/Assets/AssetManager two assets 1`] = `
-
-`;
diff --git a/x-pack/plugins/canvas/public/components/asset_manager/__examples__/asset.examples.tsx b/x-pack/plugins/canvas/public/components/asset_manager/__examples__/asset.stories.tsx
similarity index 100%
rename from x-pack/plugins/canvas/public/components/asset_manager/__examples__/asset.examples.tsx
rename to x-pack/plugins/canvas/public/components/asset_manager/__examples__/asset.stories.tsx
diff --git a/x-pack/plugins/canvas/public/components/color_picker/__examples__/color_picker.stories.tsx b/x-pack/plugins/canvas/public/components/color_picker/__examples__/color_picker.stories.tsx
index 0a7ed75ee728e..c5d42be2d201e 100644
--- a/x-pack/plugins/canvas/public/components/color_picker/__examples__/color_picker.stories.tsx
+++ b/x-pack/plugins/canvas/public/components/color_picker/__examples__/color_picker.stories.tsx
@@ -5,7 +5,7 @@
*/
import { action } from '@storybook/addon-actions';
-import { boolean, withKnobs } from '@storybook/addon-knobs';
+import { boolean } from '@storybook/addon-knobs';
import { storiesOf } from '@storybook/react';
import React from 'react';
import { ColorPicker } from '../color_picker';
@@ -54,7 +54,6 @@ class Interactive extends React.Component<
}
storiesOf('components/Color/ColorPicker', module)
- .addDecorator(withKnobs)
.addParameters({
info: {
inline: true,
diff --git a/x-pack/plugins/canvas/public/components/custom_element_modal/__examples__/__snapshots__/custom_element_modal.examples.storyshot b/x-pack/plugins/canvas/public/components/custom_element_modal/__examples__/__snapshots__/custom_element_modal.stories.storyshot
similarity index 100%
rename from x-pack/plugins/canvas/public/components/custom_element_modal/__examples__/__snapshots__/custom_element_modal.examples.storyshot
rename to x-pack/plugins/canvas/public/components/custom_element_modal/__examples__/__snapshots__/custom_element_modal.stories.storyshot
diff --git a/x-pack/plugins/canvas/public/components/custom_element_modal/__examples__/custom_element_modal.examples.tsx b/x-pack/plugins/canvas/public/components/custom_element_modal/__examples__/custom_element_modal.stories.tsx
similarity index 100%
rename from x-pack/plugins/canvas/public/components/custom_element_modal/__examples__/custom_element_modal.examples.tsx
rename to x-pack/plugins/canvas/public/components/custom_element_modal/__examples__/custom_element_modal.stories.tsx
diff --git a/x-pack/plugins/canvas/public/components/debug/__examples__/__snapshots__/debug.examples.storyshot b/x-pack/plugins/canvas/public/components/debug/__examples__/__snapshots__/debug.stories.storyshot
similarity index 100%
rename from x-pack/plugins/canvas/public/components/debug/__examples__/__snapshots__/debug.examples.storyshot
rename to x-pack/plugins/canvas/public/components/debug/__examples__/__snapshots__/debug.stories.storyshot
diff --git a/x-pack/plugins/canvas/public/components/debug/__examples__/debug.examples.tsx b/x-pack/plugins/canvas/public/components/debug/__examples__/debug.stories.tsx
similarity index 100%
rename from x-pack/plugins/canvas/public/components/debug/__examples__/debug.examples.tsx
rename to x-pack/plugins/canvas/public/components/debug/__examples__/debug.stories.tsx
diff --git a/x-pack/plugins/canvas/public/components/element_card/__examples__/__snapshots__/element_controls.examples.storyshot b/x-pack/plugins/canvas/public/components/element_card/__examples__/__snapshots__/element_controls.examples.storyshot
deleted file mode 100644
index 5e076ba76a9c1..0000000000000
--- a/x-pack/plugins/canvas/public/components/element_card/__examples__/__snapshots__/element_controls.examples.storyshot
+++ /dev/null
@@ -1,98 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Storyshots components/ElementTypes/ElementControls has two buttons 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-`;
diff --git a/x-pack/plugins/canvas/public/components/expression_input/__examples__/__snapshots__/expression_input.examples.storyshot b/x-pack/plugins/canvas/public/components/expression_input/__examples__/__snapshots__/expression_input.stories.storyshot
similarity index 100%
rename from x-pack/plugins/canvas/public/components/expression_input/__examples__/__snapshots__/expression_input.examples.storyshot
rename to x-pack/plugins/canvas/public/components/expression_input/__examples__/__snapshots__/expression_input.stories.storyshot
diff --git a/x-pack/plugins/canvas/public/components/expression_input/__examples__/expression_input.examples.tsx b/x-pack/plugins/canvas/public/components/expression_input/__examples__/expression_input.stories.tsx
similarity index 100%
rename from x-pack/plugins/canvas/public/components/expression_input/__examples__/expression_input.examples.tsx
rename to x-pack/plugins/canvas/public/components/expression_input/__examples__/expression_input.stories.tsx
diff --git a/x-pack/plugins/canvas/public/components/workpad_header/edit_menu/__examples__/__snapshots__/edit_menu.examples.storyshot b/x-pack/plugins/canvas/public/components/workpad_header/edit_menu/__examples__/__snapshots__/edit_menu.stories.storyshot
similarity index 100%
rename from x-pack/plugins/canvas/public/components/workpad_header/edit_menu/__examples__/__snapshots__/edit_menu.examples.storyshot
rename to x-pack/plugins/canvas/public/components/workpad_header/edit_menu/__examples__/__snapshots__/edit_menu.stories.storyshot
diff --git a/x-pack/plugins/canvas/public/components/workpad_header/edit_menu/__examples__/edit_menu.examples.tsx b/x-pack/plugins/canvas/public/components/workpad_header/edit_menu/__examples__/edit_menu.stories.tsx
similarity index 100%
rename from x-pack/plugins/canvas/public/components/workpad_header/edit_menu/__examples__/edit_menu.examples.tsx
rename to x-pack/plugins/canvas/public/components/workpad_header/edit_menu/__examples__/edit_menu.stories.tsx
diff --git a/x-pack/plugins/canvas/public/components/workpad_header/element_menu/__examples__/__snapshots__/element_menu.examples.storyshot b/x-pack/plugins/canvas/public/components/workpad_header/element_menu/__examples__/__snapshots__/element_menu.stories.storyshot
similarity index 100%
rename from x-pack/plugins/canvas/public/components/workpad_header/element_menu/__examples__/__snapshots__/element_menu.examples.storyshot
rename to x-pack/plugins/canvas/public/components/workpad_header/element_menu/__examples__/__snapshots__/element_menu.stories.storyshot
diff --git a/x-pack/plugins/canvas/public/components/workpad_header/element_menu/__examples__/element_menu.examples.tsx b/x-pack/plugins/canvas/public/components/workpad_header/element_menu/__examples__/element_menu.stories.tsx
similarity index 100%
rename from x-pack/plugins/canvas/public/components/workpad_header/element_menu/__examples__/element_menu.examples.tsx
rename to x-pack/plugins/canvas/public/components/workpad_header/element_menu/__examples__/element_menu.stories.tsx
diff --git a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/__examples__/__snapshots__/share_menu.examples.storyshot b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/__examples__/__snapshots__/share_menu.stories.storyshot
similarity index 100%
rename from x-pack/plugins/canvas/public/components/workpad_header/share_menu/__examples__/__snapshots__/share_menu.examples.storyshot
rename to x-pack/plugins/canvas/public/components/workpad_header/share_menu/__examples__/__snapshots__/share_menu.stories.storyshot
diff --git a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/__examples__/share_menu.examples.tsx b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/__examples__/share_menu.stories.tsx
similarity index 100%
rename from x-pack/plugins/canvas/public/components/workpad_header/share_menu/__examples__/share_menu.examples.tsx
rename to x-pack/plugins/canvas/public/components/workpad_header/share_menu/__examples__/share_menu.stories.tsx
diff --git a/x-pack/plugins/canvas/public/expression_types/arg_types/container_style/__examples__/__snapshots__/extended_template.examples.storyshot b/x-pack/plugins/canvas/public/expression_types/arg_types/container_style/__examples__/__snapshots__/extended_template.stories.storyshot
similarity index 100%
rename from x-pack/plugins/canvas/public/expression_types/arg_types/container_style/__examples__/__snapshots__/extended_template.examples.storyshot
rename to x-pack/plugins/canvas/public/expression_types/arg_types/container_style/__examples__/__snapshots__/extended_template.stories.storyshot
diff --git a/x-pack/plugins/canvas/public/expression_types/arg_types/container_style/__examples__/__snapshots__/simple_template.examples.storyshot b/x-pack/plugins/canvas/public/expression_types/arg_types/container_style/__examples__/__snapshots__/simple_template.stories.storyshot
similarity index 100%
rename from x-pack/plugins/canvas/public/expression_types/arg_types/container_style/__examples__/__snapshots__/simple_template.examples.storyshot
rename to x-pack/plugins/canvas/public/expression_types/arg_types/container_style/__examples__/__snapshots__/simple_template.stories.storyshot
diff --git a/x-pack/plugins/canvas/public/expression_types/arg_types/container_style/__examples__/extended_template.examples.tsx b/x-pack/plugins/canvas/public/expression_types/arg_types/container_style/__examples__/extended_template.stories.tsx
similarity index 100%
rename from x-pack/plugins/canvas/public/expression_types/arg_types/container_style/__examples__/extended_template.examples.tsx
rename to x-pack/plugins/canvas/public/expression_types/arg_types/container_style/__examples__/extended_template.stories.tsx
diff --git a/x-pack/plugins/canvas/public/expression_types/arg_types/container_style/__examples__/simple_template.examples.tsx b/x-pack/plugins/canvas/public/expression_types/arg_types/container_style/__examples__/simple_template.stories.tsx
similarity index 100%
rename from x-pack/plugins/canvas/public/expression_types/arg_types/container_style/__examples__/simple_template.examples.tsx
rename to x-pack/plugins/canvas/public/expression_types/arg_types/container_style/__examples__/simple_template.stories.tsx
diff --git a/x-pack/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/__snapshots__/extended_template.examples.storyshot b/x-pack/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/__snapshots__/extended_template.stories.storyshot
similarity index 100%
rename from x-pack/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/__snapshots__/extended_template.examples.storyshot
rename to x-pack/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/__snapshots__/extended_template.stories.storyshot
diff --git a/x-pack/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/__snapshots__/simple_template.examples.storyshot b/x-pack/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/__snapshots__/simple_template.stories.storyshot
similarity index 100%
rename from x-pack/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/__snapshots__/simple_template.examples.storyshot
rename to x-pack/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/__snapshots__/simple_template.stories.storyshot
diff --git a/x-pack/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/extended_template.examples.tsx b/x-pack/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/extended_template.stories.tsx
similarity index 95%
rename from x-pack/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/extended_template.examples.tsx
rename to x-pack/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/extended_template.stories.tsx
index 4a300b3de8923..a831f35ad597e 100644
--- a/x-pack/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/extended_template.examples.tsx
+++ b/x-pack/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/extended_template.stories.tsx
@@ -6,7 +6,7 @@
import { action } from '@storybook/addon-actions';
import { storiesOf } from '@storybook/react';
-import { withKnobs, array, radios, boolean } from '@storybook/addon-knobs';
+import { array, radios, boolean } from '@storybook/addon-knobs';
import React from 'react';
import { ExtendedTemplate } from '../extended_template';
@@ -64,7 +64,6 @@ storiesOf('arguments/SeriesStyle', module)
.addDecorator((story) => (
{story()}
))
- .addDecorator(withKnobs)
.add('extended', () => );
storiesOf('arguments/SeriesStyle/components', module)
diff --git a/x-pack/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/simple_template.examples.tsx b/x-pack/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/simple_template.stories.tsx
similarity index 100%
rename from x-pack/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/simple_template.examples.tsx
rename to x-pack/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/simple_template.stories.tsx
diff --git a/x-pack/plugins/canvas/scripts/jest.js b/x-pack/plugins/canvas/scripts/jest.js
index b30cb02d2c99a..a91431a0141c5 100644
--- a/x-pack/plugins/canvas/scripts/jest.js
+++ b/x-pack/plugins/canvas/scripts/jest.js
@@ -60,7 +60,7 @@ run(
if (all) {
log.info('Running all available tests. This will take a while...');
} else if (storybook) {
- path = 'plugins/canvas/.storybook';
+ path = 'plugins/canvas/storybook';
log.info('Running Storybook Snapshot tests...');
} else {
log.info('Running tests. This does not include Storybook Snapshots...');
diff --git a/x-pack/plugins/canvas/scripts/storybook.js b/x-pack/plugins/canvas/scripts/storybook.js
index c9e6c6c65436c..beea1814b54d2 100644
--- a/x-pack/plugins/canvas/scripts/storybook.js
+++ b/x-pack/plugins/canvas/scripts/storybook.js
@@ -10,7 +10,7 @@ const del = require('del');
const { run } = require('@kbn/dev-utils');
const storybook = require('@storybook/react/standalone');
const execa = require('execa');
-const { DLL_OUTPUT } = require('./../.storybook/constants');
+const { DLL_OUTPUT } = require('./../storybook/constants');
const options = {
stdio: ['ignore', 'inherit', 'inherit'],
@@ -18,7 +18,7 @@ const options = {
};
const storybookOptions = {
- configDir: path.resolve(__dirname, './../.storybook'),
+ configDir: path.resolve(__dirname, './../storybook'),
mode: 'dev',
};
@@ -51,7 +51,7 @@ run(
[
'webpack',
'--config',
- 'x-pack/plugins/canvas/.storybook/webpack.dll.config.js',
+ 'x-pack/plugins/canvas/storybook/webpack.dll.config.js',
'--progress',
'--hide-modules',
'--display-entrypoints',
diff --git a/x-pack/plugins/canvas/shareable_runtime/components/__examples__/__snapshots__/canvas.examples.storyshot b/x-pack/plugins/canvas/shareable_runtime/components/__examples__/__snapshots__/canvas.stories.storyshot
similarity index 100%
rename from x-pack/plugins/canvas/shareable_runtime/components/__examples__/__snapshots__/canvas.examples.storyshot
rename to x-pack/plugins/canvas/shareable_runtime/components/__examples__/__snapshots__/canvas.stories.storyshot
diff --git a/x-pack/plugins/canvas/shareable_runtime/components/__examples__/__snapshots__/page.examples.storyshot b/x-pack/plugins/canvas/shareable_runtime/components/__examples__/__snapshots__/page.stories.storyshot
similarity index 100%
rename from x-pack/plugins/canvas/shareable_runtime/components/__examples__/__snapshots__/page.examples.storyshot
rename to x-pack/plugins/canvas/shareable_runtime/components/__examples__/__snapshots__/page.stories.storyshot
diff --git a/x-pack/plugins/canvas/shareable_runtime/components/__examples__/__snapshots__/rendered_element.examples.storyshot b/x-pack/plugins/canvas/shareable_runtime/components/__examples__/__snapshots__/rendered_element.stories.storyshot
similarity index 100%
rename from x-pack/plugins/canvas/shareable_runtime/components/__examples__/__snapshots__/rendered_element.examples.storyshot
rename to x-pack/plugins/canvas/shareable_runtime/components/__examples__/__snapshots__/rendered_element.stories.storyshot
diff --git a/x-pack/plugins/canvas/shareable_runtime/components/__examples__/canvas.examples.tsx b/x-pack/plugins/canvas/shareable_runtime/components/__examples__/canvas.stories.tsx
similarity index 100%
rename from x-pack/plugins/canvas/shareable_runtime/components/__examples__/canvas.examples.tsx
rename to x-pack/plugins/canvas/shareable_runtime/components/__examples__/canvas.stories.tsx
diff --git a/x-pack/plugins/canvas/shareable_runtime/components/__examples__/page.examples.tsx b/x-pack/plugins/canvas/shareable_runtime/components/__examples__/page.stories.tsx
similarity index 100%
rename from x-pack/plugins/canvas/shareable_runtime/components/__examples__/page.examples.tsx
rename to x-pack/plugins/canvas/shareable_runtime/components/__examples__/page.stories.tsx
diff --git a/x-pack/plugins/canvas/shareable_runtime/components/__examples__/rendered_element.examples.tsx b/x-pack/plugins/canvas/shareable_runtime/components/__examples__/rendered_element.stories.tsx
similarity index 100%
rename from x-pack/plugins/canvas/shareable_runtime/components/__examples__/rendered_element.examples.tsx
rename to x-pack/plugins/canvas/shareable_runtime/components/__examples__/rendered_element.stories.tsx
diff --git a/x-pack/plugins/canvas/shareable_runtime/components/footer/__examples__/__snapshots__/footer.components.examples.storyshot b/x-pack/plugins/canvas/shareable_runtime/components/footer/__examples__/__snapshots__/footer.components.examples.storyshot
deleted file mode 100644
index 6d783a26d8424..0000000000000
--- a/x-pack/plugins/canvas/shareable_runtime/components/footer/__examples__/__snapshots__/footer.components.examples.storyshot
+++ /dev/null
@@ -1,140 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Storyshots shareables/Footer/components PageControls 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots shareables/Footer/components Title 1`] = `
-
-
-
-
-
-
-
-
-
- This is a test title.
-
-
-
-
-
-
-`;
diff --git a/x-pack/plugins/canvas/shareable_runtime/components/footer/__examples__/__snapshots__/footer.examples.storyshot b/x-pack/plugins/canvas/shareable_runtime/components/footer/__examples__/__snapshots__/footer.stories.storyshot
similarity index 100%
rename from x-pack/plugins/canvas/shareable_runtime/components/footer/__examples__/__snapshots__/footer.examples.storyshot
rename to x-pack/plugins/canvas/shareable_runtime/components/footer/__examples__/__snapshots__/footer.stories.storyshot
diff --git a/x-pack/plugins/canvas/shareable_runtime/components/footer/__examples__/__snapshots__/page_controls.examples.storyshot b/x-pack/plugins/canvas/shareable_runtime/components/footer/__examples__/__snapshots__/page_controls.stories.storyshot
similarity index 100%
rename from x-pack/plugins/canvas/shareable_runtime/components/footer/__examples__/__snapshots__/page_controls.examples.storyshot
rename to x-pack/plugins/canvas/shareable_runtime/components/footer/__examples__/__snapshots__/page_controls.stories.storyshot
diff --git a/x-pack/plugins/canvas/shareable_runtime/components/footer/__examples__/__snapshots__/scrubber.examples.storyshot b/x-pack/plugins/canvas/shareable_runtime/components/footer/__examples__/__snapshots__/scrubber.stories.storyshot
similarity index 100%
rename from x-pack/plugins/canvas/shareable_runtime/components/footer/__examples__/__snapshots__/scrubber.examples.storyshot
rename to x-pack/plugins/canvas/shareable_runtime/components/footer/__examples__/__snapshots__/scrubber.stories.storyshot
diff --git a/x-pack/plugins/canvas/shareable_runtime/components/footer/__examples__/__snapshots__/title.examples.storyshot b/x-pack/plugins/canvas/shareable_runtime/components/footer/__examples__/__snapshots__/title.stories.storyshot
similarity index 100%
rename from x-pack/plugins/canvas/shareable_runtime/components/footer/__examples__/__snapshots__/title.examples.storyshot
rename to x-pack/plugins/canvas/shareable_runtime/components/footer/__examples__/__snapshots__/title.stories.storyshot
diff --git a/x-pack/plugins/canvas/shareable_runtime/components/footer/__examples__/footer.examples.tsx b/x-pack/plugins/canvas/shareable_runtime/components/footer/__examples__/footer.stories.tsx
similarity index 100%
rename from x-pack/plugins/canvas/shareable_runtime/components/footer/__examples__/footer.examples.tsx
rename to x-pack/plugins/canvas/shareable_runtime/components/footer/__examples__/footer.stories.tsx
diff --git a/x-pack/plugins/canvas/shareable_runtime/components/footer/__examples__/page_controls.examples.tsx b/x-pack/plugins/canvas/shareable_runtime/components/footer/__examples__/page_controls.stories.tsx
similarity index 100%
rename from x-pack/plugins/canvas/shareable_runtime/components/footer/__examples__/page_controls.examples.tsx
rename to x-pack/plugins/canvas/shareable_runtime/components/footer/__examples__/page_controls.stories.tsx
diff --git a/x-pack/plugins/canvas/shareable_runtime/components/footer/__examples__/scrubber.examples.tsx b/x-pack/plugins/canvas/shareable_runtime/components/footer/__examples__/scrubber.stories.tsx
similarity index 100%
rename from x-pack/plugins/canvas/shareable_runtime/components/footer/__examples__/scrubber.examples.tsx
rename to x-pack/plugins/canvas/shareable_runtime/components/footer/__examples__/scrubber.stories.tsx
diff --git a/x-pack/plugins/canvas/shareable_runtime/components/footer/__examples__/title.examples.tsx b/x-pack/plugins/canvas/shareable_runtime/components/footer/__examples__/title.stories.tsx
similarity index 100%
rename from x-pack/plugins/canvas/shareable_runtime/components/footer/__examples__/title.examples.tsx
rename to x-pack/plugins/canvas/shareable_runtime/components/footer/__examples__/title.stories.tsx
diff --git a/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/__examples__/__snapshots__/autoplay_settings.examples.storyshot b/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/__examples__/__snapshots__/autoplay_settings.stories.storyshot
similarity index 100%
rename from x-pack/plugins/canvas/shareable_runtime/components/footer/settings/__examples__/__snapshots__/autoplay_settings.examples.storyshot
rename to x-pack/plugins/canvas/shareable_runtime/components/footer/settings/__examples__/__snapshots__/autoplay_settings.stories.storyshot
diff --git a/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/__examples__/__snapshots__/settings.components.examples.storyshot b/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/__examples__/__snapshots__/settings.components.examples.storyshot
deleted file mode 100644
index 1922bd84b174c..0000000000000
--- a/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/__examples__/__snapshots__/settings.components.examples.storyshot
+++ /dev/null
@@ -1,479 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Storyshots shareables/Settings/components AutoplaySettings, autoplay disabled 1`] = `
-