function getProps(
props?: Partial
): { props: DashboardViewportProps; options: DashboardContainerOptions } {
- const embeddableFactories = new Map();
- embeddableFactories.set(
+ const { setup, doStart } = embeddablePluginMock.createInstance();
+ setup.registerEmbeddableFactory(
CONTACT_CARD_EMBEDDABLE,
- new ContactCardEmbeddableFactory({}, (() => null) as any, {} as any)
+ new ContactCardEmbeddableFactory((() => null) as any, {} as any)
);
+ const start = doStart();
const options: DashboardContainerOptions = {
application: {} as any,
embeddable: {
getTriggerCompatibleActions: (() => []) as any,
- getEmbeddableFactories: (() => []) as any,
- getEmbeddableFactory: (id: string) => embeddableFactories.get(id),
+ getEmbeddableFactories: start.getEmbeddableFactories,
+ getEmbeddableFactory: start.getEmbeddableFactory,
} as any,
notifications: {} as any,
overlays: {} as any,
diff --git a/src/plugins/dashboard/public/tests/dashboard_container.test.tsx b/src/plugins/dashboard/public/tests/dashboard_container.test.tsx
index a81d80b440e04..1c72ad34e5446 100644
--- a/src/plugins/dashboard/public/tests/dashboard_container.test.tsx
+++ b/src/plugins/dashboard/public/tests/dashboard_container.test.tsx
@@ -52,7 +52,7 @@ test('DashboardContainer in edit mode shows edit mode actions', async () => {
uiActionsSetup.attachAction(CONTEXT_MENU_TRIGGER, editModeAction);
setup.registerEmbeddableFactory(
CONTACT_CARD_EMBEDDABLE,
- new ContactCardEmbeddableFactory({} as any, (() => null) as any, {} as any)
+ new ContactCardEmbeddableFactory((() => null) as any, {} as any)
);
const start = doStart();
diff --git a/src/plugins/embeddable/public/index.ts b/src/plugins/embeddable/public/index.ts
index eca74af4ec253..23275fbe8e8f0 100644
--- a/src/plugins/embeddable/public/index.ts
+++ b/src/plugins/embeddable/public/index.ts
@@ -38,6 +38,7 @@ export {
EmbeddableChildPanel,
EmbeddableChildPanelProps,
EmbeddableContext,
+ EmbeddableFactoryDefinition,
EmbeddableFactory,
EmbeddableFactoryNotFoundError,
EmbeddableFactoryRenderer,
diff --git a/src/plugins/embeddable/public/lib/actions/edit_panel_action.test.tsx b/src/plugins/embeddable/public/lib/actions/edit_panel_action.test.tsx
index 9aeaf34f3311b..ce733bba6dda5 100644
--- a/src/plugins/embeddable/public/lib/actions/edit_panel_action.test.tsx
+++ b/src/plugins/embeddable/public/lib/actions/edit_panel_action.test.tsx
@@ -18,14 +18,14 @@
*/
import { EditPanelAction } from './edit_panel_action';
-import { EmbeddableFactory, Embeddable, EmbeddableInput } from '../embeddables';
+import { Embeddable, EmbeddableInput } from '../embeddables';
import { ViewMode } from '../types';
import { ContactCardEmbeddable } from '../test_samples';
-import { EmbeddableStart } from '../../plugin';
+import { embeddablePluginMock } from '../../mocks';
-const embeddableFactories = new Map();
-const getFactory = ((id: string) =>
- embeddableFactories.get(id)) as EmbeddableStart['getEmbeddableFactory'];
+const { doStart } = embeddablePluginMock.createInstance();
+const start = doStart();
+const getFactory = start.getEmbeddableFactory;
class EditableEmbeddable extends Embeddable {
public readonly type = 'EDITABLE_EMBEDDABLE';
@@ -83,9 +83,7 @@ test('is not compatible when edit url is not available', async () => {
});
test('is not visible when edit url is available but in view mode', async () => {
- embeddableFactories.clear();
- const action = new EditPanelAction((type =>
- embeddableFactories.get(type)) as EmbeddableStart['getEmbeddableFactory']);
+ const action = new EditPanelAction(getFactory);
expect(
await action.isCompatible({
embeddable: new EditableEmbeddable(
@@ -100,9 +98,7 @@ test('is not visible when edit url is available but in view mode', async () => {
});
test('is not compatible when edit url is available, in edit mode, but not editable', async () => {
- embeddableFactories.clear();
- const action = new EditPanelAction((type =>
- embeddableFactories.get(type)) as EmbeddableStart['getEmbeddableFactory']);
+ const action = new EditPanelAction(getFactory);
expect(
await action.isCompatible({
embeddable: new EditableEmbeddable(
diff --git a/src/plugins/embeddable/public/lib/containers/embeddable_child_panel.test.tsx b/src/plugins/embeddable/public/lib/containers/embeddable_child_panel.test.tsx
index 07915ce59e6ca..9e47da5cea032 100644
--- a/src/plugins/embeddable/public/lib/containers/embeddable_child_panel.test.tsx
+++ b/src/plugins/embeddable/public/lib/containers/embeddable_child_panel.test.tsx
@@ -20,7 +20,6 @@
import React from 'react';
import { nextTick } from 'test_utils/enzyme_helpers';
import { EmbeddableChildPanel } from './embeddable_child_panel';
-import { EmbeddableFactory } from '../embeddables';
import { CONTACT_CARD_EMBEDDABLE } from '../test_samples/embeddables/contact_card/contact_card_embeddable_factory';
import { SlowContactCardEmbeddableFactory } from '../test_samples/embeddables/contact_card/slow_contact_card_embeddable_factory';
import { HelloWorldContainer } from '../test_samples/embeddables/hello_world_container';
@@ -32,16 +31,17 @@ import {
// eslint-disable-next-line
import { inspectorPluginMock } from 'src/plugins/inspector/public/mocks';
import { mount } from 'enzyme';
+import { embeddablePluginMock } from '../../mocks';
test('EmbeddableChildPanel renders an embeddable when it is done loading', async () => {
const inspector = inspectorPluginMock.createStartContract();
-
- const embeddableFactories = new Map();
- embeddableFactories.set(
+ const { setup, doStart } = embeddablePluginMock.createInstance();
+ setup.registerEmbeddableFactory(
CONTACT_CARD_EMBEDDABLE,
new SlowContactCardEmbeddableFactory({ execAction: (() => null) as any })
);
- const getEmbeddableFactory = (id: string) => embeddableFactories.get(id);
+ const start = doStart();
+ const getEmbeddableFactory = start.getEmbeddableFactory;
const container = new HelloWorldContainer({ id: 'hello', panels: {} }, {
getEmbeddableFactory,
@@ -63,8 +63,8 @@ test('EmbeddableChildPanel renders an embeddable when it is done loading', async
container={container}
embeddableId={newEmbeddable.id}
getActions={() => Promise.resolve([])}
- getAllEmbeddableFactories={(() => []) as any}
- getEmbeddableFactory={(() => undefined) as any}
+ getAllEmbeddableFactories={start.getEmbeddableFactories}
+ getEmbeddableFactory={getEmbeddableFactory}
notifications={{} as any}
overlays={{} as any}
inspector={inspector}
diff --git a/src/plugins/embeddable/public/lib/embeddables/default_embeddable_factory_provider.ts b/src/plugins/embeddable/public/lib/embeddables/default_embeddable_factory_provider.ts
new file mode 100644
index 0000000000000..570a78fc41ea9
--- /dev/null
+++ b/src/plugins/embeddable/public/lib/embeddables/default_embeddable_factory_provider.ts
@@ -0,0 +1,52 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { SavedObjectAttributes } from 'kibana/public';
+import { EmbeddableFactoryDefinition } from './embeddable_factory_definition';
+import { EmbeddableInput, EmbeddableOutput, IEmbeddable } from './i_embeddable';
+import { EmbeddableFactory } from './embeddable_factory';
+import { IContainer } from '..';
+
+export const defaultEmbeddableFactoryProvider = <
+ I extends EmbeddableInput = EmbeddableInput,
+ O extends EmbeddableOutput = EmbeddableOutput,
+ E extends IEmbeddable = IEmbeddable,
+ T extends SavedObjectAttributes = SavedObjectAttributes
+>(
+ def: EmbeddableFactoryDefinition
+): EmbeddableFactory => {
+ const factory: EmbeddableFactory = {
+ isContainerType: def.isContainerType ?? false,
+ canCreateNew: def.canCreateNew ? def.canCreateNew.bind(def) : () => true,
+ getDefaultInput: def.getDefaultInput ? def.getDefaultInput.bind(def) : () => ({}),
+ getExplicitInput: def.getExplicitInput
+ ? def.getExplicitInput.bind(def)
+ : () => Promise.resolve({}),
+ createFromSavedObject:
+ def.createFromSavedObject ??
+ ((savedObjectId: string, input: Partial, parent?: IContainer) => {
+ throw new Error(`Creation from saved object not supported by type ${def.type}`);
+ }),
+ create: def.create.bind(def),
+ type: def.type,
+ isEditable: def.isEditable.bind(def),
+ getDisplayName: def.getDisplayName.bind(def),
+ savedObjectMetaData: def.savedObjectMetaData,
+ };
+ return factory;
+};
diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx b/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx
index eb10c16806640..a135484ff61be 100644
--- a/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx
+++ b/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx
@@ -158,6 +158,8 @@ export abstract class Embeddable<
*/
public destroy(): void {
this.destoyed = true;
+ this.input$.complete();
+ this.output$.complete();
if (this.parentSubscription) {
this.parentSubscription.unsubscribe();
}
diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable_factory.ts b/src/plugins/embeddable/public/lib/embeddables/embeddable_factory.ts
index 81f7f35c900c9..7949b6fb8ba27 100644
--- a/src/plugins/embeddable/public/lib/embeddables/embeddable_factory.ts
+++ b/src/plugins/embeddable/public/lib/embeddables/embeddable_factory.ts
@@ -22,32 +22,21 @@ import { SavedObjectMetaData } from '../../../../saved_objects/public';
import { EmbeddableInput, EmbeddableOutput, IEmbeddable } from './i_embeddable';
import { ErrorEmbeddable } from './error_embeddable';
import { IContainer } from '../containers/i_container';
+import { PropertySpec } from '../types';
export interface EmbeddableInstanceConfiguration {
id: string;
savedObjectId?: string;
}
-export interface PropertySpec {
- displayName: string;
- accessPath: string;
- id: string;
- description: string;
- value?: string;
-}
-
export interface OutputSpec {
[key: string]: PropertySpec;
}
-export interface EmbeddableFactoryOptions {
- savedObjectMetaData?: SavedObjectMetaData;
-}
-
/**
- * The EmbeddableFactory creates and initializes an embeddable instance
+ * EmbeddableFactories create and initialize an embeddable instance
*/
-export abstract class EmbeddableFactory<
+export interface EmbeddableFactory<
TEmbeddableInput extends EmbeddableInput = EmbeddableInput,
TEmbeddableOutput extends EmbeddableOutput = EmbeddableOutput,
TEmbeddable extends IEmbeddable = IEmbeddable<
@@ -58,9 +47,15 @@ export abstract class EmbeddableFactory<
> {
// A unique identified for this factory, which will be used to map an embeddable spec to
// a factory that can generate an instance of it.
- public abstract readonly type: string;
+ readonly type: string;
+
+ /**
+ * Returns whether the current user should be allowed to edit this type of
+ * embeddable. Most of the time this should be based off the capabilities service, hence it's async.
+ */
+ readonly isEditable: () => Promise;
- public readonly savedObjectMetaData?: SavedObjectMetaData;
+ readonly savedObjectMetaData?: SavedObjectMetaData;
/**
* True if is this factory create embeddables that are Containers. Used in the add panel to
@@ -68,31 +63,19 @@ export abstract class EmbeddableFactory<
* supported right now, but once nested containers are officially supported we can probably get
* rid of this interface.
*/
- public readonly isContainerType: boolean = false;
-
- constructor({ savedObjectMetaData }: EmbeddableFactoryOptions = {}) {
- this.savedObjectMetaData = savedObjectMetaData;
- }
-
- /**
- * Returns whether the current user should be allowed to edit this type of
- * embeddable. Most of the time this should be based off the capabilities service, hence it's async.
- */
- public abstract async isEditable(): Promise;
+ readonly isContainerType: boolean;
/**
* Returns a display name for this type of embeddable. Used in "Create new... " options
* in the add panel for containers.
*/
- public abstract getDisplayName(): string;
+ getDisplayName(): string;
/**
* If false, this type of embeddable can't be created with the "createNew" functionality. Instead,
* use createFromSavedObject, where an existing saved object must first exist.
*/
- public canCreateNew() {
- return true;
- }
+ canCreateNew(): boolean;
/**
* Can be used to get any default input, to be passed in to during the creation process. Default
@@ -100,18 +83,14 @@ export abstract class EmbeddableFactory<
* default input parameters.
* @param partial
*/
- public getDefaultInput(partial: Partial): Partial {
- return {};
- }
+ getDefaultInput(partial: Partial): Partial;
/**
* Can be used to request explicit input from the user, to be passed in to `EmbeddableFactory:create`.
* Explicit input is stored on the parent container for this embeddable. It overrides any inherited
* input passed down from the parent container.
*/
- public async getExplicitInput(): Promise> {
- return {};
- }
+ getExplicitInput(): Promise>;
/**
* Creates a new embeddable instance based off the saved object id.
@@ -120,13 +99,11 @@ export abstract class EmbeddableFactory<
* range of the parent container.
* @param parent
*/
- public createFromSavedObject(
+ createFromSavedObject(
savedObjectId: string,
input: Partial,
parent?: IContainer
- ): Promise {
- throw new Error(`Creation from saved object not supported by type ${this.type}`);
- }
+ ): Promise;
/**
* Resolves to undefined if a new Embeddable cannot be directly created and the user will instead be redirected
@@ -134,7 +111,7 @@ export abstract class EmbeddableFactory<
*
* This will likely change in future iterations when we improve in place editing capabilities.
*/
- public abstract create(
+ create(
initialInput: TEmbeddableInput,
parent?: IContainer
): Promise;
diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable_factory_definition.ts b/src/plugins/embeddable/public/lib/embeddables/embeddable_factory_definition.ts
new file mode 100644
index 0000000000000..b8985f7311ea9
--- /dev/null
+++ b/src/plugins/embeddable/public/lib/embeddables/embeddable_factory_definition.ts
@@ -0,0 +1,44 @@
+/*
+ * 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 { SavedObjectAttributes } from 'kibana/server';
+import { IEmbeddable } from './i_embeddable';
+import { EmbeddableFactory } from './embeddable_factory';
+import { EmbeddableInput, EmbeddableOutput } from '..';
+
+export type EmbeddableFactoryDefinition<
+ I extends EmbeddableInput = EmbeddableInput,
+ O extends EmbeddableOutput = EmbeddableOutput,
+ E extends IEmbeddable = IEmbeddable,
+ T extends SavedObjectAttributes = SavedObjectAttributes
+> =
+ // Required parameters
+ Pick, 'create' | 'type' | 'isEditable' | 'getDisplayName'> &
+ // Optional parameters
+ Partial<
+ Pick<
+ EmbeddableFactory,
+ | 'createFromSavedObject'
+ | 'isContainerType'
+ | 'getExplicitInput'
+ | 'savedObjectMetaData'
+ | 'canCreateNew'
+ | 'getDefaultInput'
+ >
+ >;
diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable_factory_renderer.test.tsx b/src/plugins/embeddable/public/lib/embeddables/embeddable_factory_renderer.test.tsx
index 51b83ea0ecaa3..e27045495af5b 100644
--- a/src/plugins/embeddable/public/lib/embeddables/embeddable_factory_renderer.test.tsx
+++ b/src/plugins/embeddable/public/lib/embeddables/embeddable_factory_renderer.test.tsx
@@ -21,22 +21,22 @@ import {
HELLO_WORLD_EMBEDDABLE,
HelloWorldEmbeddableFactory,
} from '../../../../../../examples/embeddable_examples/public';
-import { EmbeddableFactory } from './embeddable_factory';
import { EmbeddableFactoryRenderer } from './embeddable_factory_renderer';
import { mount } from 'enzyme';
import { nextTick } from 'test_utils/enzyme_helpers';
// @ts-ignore
import { findTestSubject } from '@elastic/eui/lib/test';
-import { EmbeddableStart } from '../../plugin';
+import { embeddablePluginMock } from '../../mocks';
test('EmbeddableFactoryRenderer renders an embeddable', async () => {
- const embeddableFactories = new Map();
- embeddableFactories.set(HELLO_WORLD_EMBEDDABLE, new HelloWorldEmbeddableFactory());
- const getEmbeddableFactory = (id: string) => embeddableFactories.get(id);
+ const { setup, doStart } = embeddablePluginMock.createInstance();
+ setup.registerEmbeddableFactory(HELLO_WORLD_EMBEDDABLE, new HelloWorldEmbeddableFactory());
+
+ const getEmbeddableFactory = doStart().getEmbeddableFactory;
const component = mount(
diff --git a/src/plugins/embeddable/public/lib/embeddables/index.ts b/src/plugins/embeddable/public/lib/embeddables/index.ts
index 2175c3a59aa58..4d6ab37a50c05 100644
--- a/src/plugins/embeddable/public/lib/embeddables/index.ts
+++ b/src/plugins/embeddable/public/lib/embeddables/index.ts
@@ -18,11 +18,9 @@
*/
export { EmbeddableOutput, EmbeddableInput, IEmbeddable } from './i_embeddable';
export { Embeddable } from './embeddable';
-export {
- EmbeddableInstanceConfiguration,
- EmbeddableFactory,
- OutputSpec,
-} from './embeddable_factory';
+export * from './embeddable_factory';
+export * from './embeddable_factory_definition';
+export * from './default_embeddable_factory_provider';
export { ErrorEmbeddable, isErrorEmbeddable } from './error_embeddable';
export { withEmbeddableSubscription } from './with_subscription';
export { EmbeddableFactoryRenderer } from './embeddable_factory_renderer';
diff --git a/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx b/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx
index 757d4e6bfddef..649677dc67c7d 100644
--- a/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx
+++ b/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx
@@ -27,7 +27,7 @@ import { I18nProvider } from '@kbn/i18n/react';
import { CONTEXT_MENU_TRIGGER } from '../triggers';
import { Action, UiActionsStart, ActionType } from 'src/plugins/ui_actions/public';
import { Trigger, ViewMode } from '../types';
-import { EmbeddableFactory, isErrorEmbeddable } from '../embeddables';
+import { isErrorEmbeddable } from '../embeddables';
import { EmbeddablePanel } from './embeddable_panel';
import { createEditModeAction } from '../test_samples/actions';
import {
@@ -43,26 +43,25 @@ import {
// eslint-disable-next-line
import { inspectorPluginMock } from 'src/plugins/inspector/public/mocks';
import { EuiBadge } from '@elastic/eui';
+import { embeddablePluginMock } from '../../mocks';
const actionRegistry = new Map>();
const triggerRegistry = new Map();
-const embeddableFactories = new Map();
-const getEmbeddableFactory = (id: string) => embeddableFactories.get(id);
+
+const { setup, doStart } = embeddablePluginMock.createInstance();
const editModeAction = createEditModeAction();
const trigger: Trigger = {
id: CONTEXT_MENU_TRIGGER,
};
-const embeddableFactory = new ContactCardEmbeddableFactory(
- {} as any,
- (() => null) as any,
- {} as any
-);
+const embeddableFactory = new ContactCardEmbeddableFactory((() => null) as any, {} as any);
actionRegistry.set(editModeAction.id, editModeAction);
triggerRegistry.set(trigger.id, trigger);
-embeddableFactories.set(embeddableFactory.type, embeddableFactory);
+setup.registerEmbeddableFactory(embeddableFactory.type, embeddableFactory);
+const start = doStart();
+const getEmbeddableFactory = start.getEmbeddableFactory;
test('HelloWorldContainer initializes embeddables', async done => {
const container = new HelloWorldContainer(
{
@@ -157,8 +156,8 @@ test('HelloWorldContainer in view mode hides edit mode actions', async () => {
Promise.resolve([])}
- getAllEmbeddableFactories={(() => []) as any}
- getEmbeddableFactory={(() => undefined) as any}
+ getAllEmbeddableFactories={start.getEmbeddableFactories}
+ getEmbeddableFactory={start.getEmbeddableFactory}
notifications={{} as any}
overlays={{} as any}
inspector={inspector}
@@ -195,8 +194,8 @@ const renderInEditModeAndOpenContextMenu = async (
[]) as any}
- getEmbeddableFactory={(() => undefined) as any}
+ getAllEmbeddableFactories={start.getEmbeddableFactories}
+ getEmbeddableFactory={start.getEmbeddableFactory}
notifications={{} as any}
overlays={{} as any}
inspector={inspector}
@@ -293,8 +292,8 @@ test('HelloWorldContainer in edit mode shows edit mode actions', async () => {
Promise.resolve([])}
- getAllEmbeddableFactories={(() => []) as any}
- getEmbeddableFactory={(() => undefined) as any}
+ getAllEmbeddableFactories={start.getEmbeddableFactories}
+ getEmbeddableFactory={start.getEmbeddableFactory}
notifications={{} as any}
overlays={{} as any}
inspector={inspector}
@@ -355,8 +354,8 @@ test('Updates when hidePanelTitles is toggled', async () => {
Promise.resolve([])}
- getAllEmbeddableFactories={(() => []) as any}
- getEmbeddableFactory={(() => undefined) as any}
+ getAllEmbeddableFactories={start.getEmbeddableFactories}
+ getEmbeddableFactory={start.getEmbeddableFactory}
notifications={{} as any}
overlays={{} as any}
inspector={inspector}
@@ -407,8 +406,8 @@ test('Check when hide header option is false', async () => {
Promise.resolve([])}
- getAllEmbeddableFactories={(() => []) as any}
- getEmbeddableFactory={(() => undefined) as any}
+ getAllEmbeddableFactories={start.getEmbeddableFactories}
+ getEmbeddableFactory={start.getEmbeddableFactory}
notifications={{} as any}
overlays={{} as any}
inspector={inspector}
@@ -444,8 +443,8 @@ test('Check when hide header option is true', async () => {
Promise.resolve([])}
- getAllEmbeddableFactories={(() => []) as any}
- getEmbeddableFactory={(() => undefined) as any}
+ getAllEmbeddableFactories={start.getEmbeddableFactories}
+ getEmbeddableFactory={start.getEmbeddableFactory}
notifications={{} as any}
overlays={{} as any}
inspector={inspector}
diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_action.test.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_action.test.tsx
index 8ee8c8dad9df3..74b08535bf27a 100644
--- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_action.test.tsx
+++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_action.test.tsx
@@ -19,7 +19,6 @@
import { ViewMode, EmbeddableOutput, isErrorEmbeddable } from '../../../../';
import { AddPanelAction } from './add_panel_action';
-import { EmbeddableFactory } from '../../../../embeddables';
import {
FILTERABLE_EMBEDDABLE,
FilterableEmbeddable,
@@ -31,11 +30,12 @@ import { FilterableContainer } from '../../../../test_samples/embeddables/filter
import { coreMock } from '../../../../../../../../core/public/mocks';
import { ContactCardEmbeddable } from '../../../../test_samples';
import { esFilters, Filter } from '../../../../../../../../plugins/data/public';
-import { EmbeddableStart } from 'src/plugins/embeddable/public/plugin';
+import { EmbeddableStart } from '../../../../../plugin';
+import { embeddablePluginMock } from '../../../../../mocks';
-const embeddableFactories = new Map();
-embeddableFactories.set(FILTERABLE_EMBEDDABLE, new FilterableEmbeddableFactory());
-const getFactory = (id: string) => embeddableFactories.get(id);
+const { setup, doStart } = embeddablePluginMock.createInstance();
+setup.registerEmbeddableFactory(FILTERABLE_EMBEDDABLE, new FilterableEmbeddableFactory());
+const getFactory = doStart().getEmbeddableFactory;
let container: FilterableContainer;
let embeddable: FilterableEmbeddable;
diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.test.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.test.tsx
index 2fa21e40ca0f0..282b0f05891e0 100644
--- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.test.tsx
+++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.test.tsx
@@ -31,7 +31,7 @@ import { ReactWrapper } from 'enzyme';
import { coreMock } from '../../../../../../../../core/public/mocks';
// @ts-ignore
import { findTestSubject } from '@elastic/eui/lib/test';
-import { EmbeddableStart } from 'src/plugins/embeddable/public/plugin';
+import { embeddablePluginMock } from '../../../../../mocks';
function DummySavedObjectFinder(props: { children: React.ReactNode }) {
return (
@@ -43,10 +43,10 @@ function DummySavedObjectFinder(props: { children: React.ReactNode }) {
}
test('createNewEmbeddable() add embeddable to container', async () => {
+ const { setup, doStart } = embeddablePluginMock.createInstance();
const core = coreMock.createStart();
const { overlays } = core;
const contactCardEmbeddableFactory = new ContactCardEmbeddableFactory(
- {},
(() => null) as any,
overlays
);
@@ -55,7 +55,9 @@ test('createNewEmbeddable() add embeddable to container', async () => {
firstName: 'foo',
lastName: 'bar',
} as any);
- const getEmbeddableFactory = (id: string) => contactCardEmbeddableFactory;
+ setup.registerEmbeddableFactory(CONTACT_CARD_EMBEDDABLE, contactCardEmbeddableFactory);
+ const start = doStart();
+ const getEmbeddableFactory = start.getEmbeddableFactory;
const input: ContainerInput<{ firstName: string; lastName: string }> = {
id: '1',
panels: {},
@@ -66,8 +68,8 @@ test('createNewEmbeddable() add embeddable to container', async () => {
new Set([contactCardEmbeddableFactory]).values()}
+ getFactory={getEmbeddableFactory}
+ getAllFactories={start.getEmbeddableFactories}
notifications={core.notifications}
SavedObjectFinder={() => null}
/>
@@ -88,10 +90,10 @@ test('createNewEmbeddable() add embeddable to container', async () => {
});
test('selecting embeddable in "Create new ..." list calls createNewEmbeddable()', async () => {
+ const { setup, doStart } = embeddablePluginMock.createInstance();
const core = coreMock.createStart();
const { overlays } = core;
const contactCardEmbeddableFactory = new ContactCardEmbeddableFactory(
- {},
(() => null) as any,
overlays
);
@@ -100,8 +102,10 @@ test('selecting embeddable in "Create new ..." list calls createNewEmbeddable()'
firstName: 'foo',
lastName: 'bar',
} as any);
- const getEmbeddableFactory = ((id: string) =>
- contactCardEmbeddableFactory) as EmbeddableStart['getEmbeddableFactory'];
+
+ setup.registerEmbeddableFactory(CONTACT_CARD_EMBEDDABLE, contactCardEmbeddableFactory);
+ const start = doStart();
+ const getEmbeddableFactory = start.getEmbeddableFactory;
const input: ContainerInput<{ firstName: string; lastName: string }> = {
id: '1',
panels: {},
@@ -113,7 +117,7 @@ test('selecting embeddable in "Create new ..." list calls createNewEmbeddable()'
container={container}
onClose={onClose}
getFactory={getEmbeddableFactory}
- getAllFactories={() => new Set([contactCardEmbeddableFactory]).values()}
+ getAllFactories={start.getEmbeddableFactories}
notifications={core.notifications}
SavedObjectFinder={props => }
/>
diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.tsx
index 95eeb63710c32..06c47bd1bcad8 100644
--- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.tsx
+++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.tsx
@@ -121,15 +121,16 @@ export class AddPanelFlyout extends React.Component {
public render() {
const SavedObjectFinder = this.props.SavedObjectFinder;
+ const metaData = [...this.props.getAllFactories()]
+ .filter(
+ embeddableFactory =>
+ Boolean(embeddableFactory.savedObjectMetaData) && !embeddableFactory.isContainerType
+ )
+ .map(({ savedObjectMetaData }) => savedObjectMetaData as any);
const savedObjectsFinder = (
- Boolean(embeddableFactory.savedObjectMetaData) && !embeddableFactory.isContainerType
- )
- .map(({ savedObjectMetaData }) => savedObjectMetaData as any)}
+ savedObjectMetaData={metaData}
showFilter={true}
noItemsMessage={i18n.translate('embeddableApi.addPanel.noMatchingObjectsMessage', {
defaultMessage: 'No matching objects found.',
diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/customize_title/customize_panel_action.test.ts b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/customize_title/customize_panel_action.test.ts
index 3f7c917cd1617..2f66d8eb0d619 100644
--- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/customize_title/customize_panel_action.test.ts
+++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/customize_title/customize_panel_action.test.ts
@@ -32,18 +32,19 @@ import {
ContactCardEmbeddableFactory,
} from '../../../../test_samples/embeddables/contact_card/contact_card_embeddable_factory';
import { HelloWorldContainer } from '../../../../test_samples/embeddables/hello_world_container';
-import { EmbeddableFactory } from '../../../../embeddables';
+import { embeddablePluginMock } from '../../../../../mocks';
let container: Container;
let embeddable: ContactCardEmbeddable;
function createHelloWorldContainer(input = { id: '123', panels: {} }) {
- const embeddableFactories = new Map();
- const getEmbeddableFactory = (id: string) => embeddableFactories.get(id);
- embeddableFactories.set(
+ const { setup, doStart } = embeddablePluginMock.createInstance();
+ setup.registerEmbeddableFactory(
CONTACT_CARD_EMBEDDABLE,
- new ContactCardEmbeddableFactory({}, (() => {}) as any, {} as any)
+ new ContactCardEmbeddableFactory((() => {}) as any, {} as any)
);
+ const getEmbeddableFactory = doStart().getEmbeddableFactory;
+
return new HelloWorldContainer(input, { getEmbeddableFactory } as any);
}
diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/inspect_panel_action.test.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/inspect_panel_action.test.tsx
index e19acda8419da..ee31127cb5a40 100644
--- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/inspect_panel_action.test.tsx
+++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/inspect_panel_action.test.tsx
@@ -28,20 +28,16 @@ import {
} from '../../../test_samples';
// eslint-disable-next-line
import { inspectorPluginMock } from 'src/plugins/inspector/public/mocks';
-import {
- EmbeddableFactory,
- EmbeddableOutput,
- isErrorEmbeddable,
- ErrorEmbeddable,
-} from '../../../embeddables';
+import { EmbeddableOutput, isErrorEmbeddable, ErrorEmbeddable } from '../../../embeddables';
import { of } from '../../../../tests/helpers';
import { esFilters } from '../../../../../../../plugins/data/public';
-import { EmbeddableStart } from 'src/plugins/embeddable/public/plugin';
+import { embeddablePluginMock } from '../../../../mocks';
+import { EmbeddableStart } from '../../../../plugin';
-const setup = async () => {
- const embeddableFactories = new Map();
- embeddableFactories.set(FILTERABLE_EMBEDDABLE, new FilterableEmbeddableFactory());
- const getFactory = (id: string) => embeddableFactories.get(id);
+const setupTests = async () => {
+ const { setup, doStart } = embeddablePluginMock.createInstance();
+ setup.registerEmbeddableFactory(FILTERABLE_EMBEDDABLE, new FilterableEmbeddableFactory());
+ const getFactory = doStart().getEmbeddableFactory;
const container = new FilterableContainer(
{
id: 'hello',
@@ -79,7 +75,7 @@ test('Is compatible when inspector adapters are available', async () => {
const inspector = inspectorPluginMock.createStartContract();
inspector.isAvailable.mockImplementation(() => true);
- const { embeddable } = await setup();
+ const { embeddable } = await setupTests();
const inspectAction = new InspectPanelAction(inspector);
expect(await inspectAction.isCompatible({ embeddable })).toBe(true);
@@ -114,7 +110,7 @@ test('Executes when inspector adapters are available', async () => {
const inspector = inspectorPluginMock.createStartContract();
inspector.isAvailable.mockImplementation(() => true);
- const { embeddable } = await setup();
+ const { embeddable } = await setupTests();
const inspectAction = new InspectPanelAction(inspector);
expect(inspector.open).toHaveBeenCalledTimes(0);
diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/remove_panel_action.test.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/remove_panel_action.test.tsx
index f4d5aa148373b..dea4a88bda082 100644
--- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/remove_panel_action.test.tsx
+++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/remove_panel_action.test.tsx
@@ -19,7 +19,6 @@
import { EmbeddableOutput, isErrorEmbeddable } from '../../../';
import { RemovePanelAction } from './remove_panel_action';
-import { EmbeddableFactory } from '../../../embeddables';
import { EmbeddableStart } from '../../../../plugin';
import {
FILTERABLE_EMBEDDABLE,
@@ -31,11 +30,11 @@ import { FilterableContainer } from '../../../test_samples/embeddables/filterabl
import { ViewMode } from '../../../types';
import { ContactCardEmbeddable } from '../../../test_samples/embeddables/contact_card/contact_card_embeddable';
import { esFilters, Filter } from '../../../../../../../plugins/data/public';
+import { embeddablePluginMock } from '../../../../mocks';
-const embeddableFactories = new Map();
-embeddableFactories.set(FILTERABLE_EMBEDDABLE, new FilterableEmbeddableFactory());
-const getFactory = (id: string) => embeddableFactories.get(id);
-
+const { setup, doStart } = embeddablePluginMock.createInstance();
+setup.registerEmbeddableFactory(FILTERABLE_EMBEDDABLE, new FilterableEmbeddableFactory());
+const getFactory = doStart().getEmbeddableFactory;
let container: FilterableContainer;
let embeddable: FilterableEmbeddable;
diff --git a/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_embeddable_factory.tsx b/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_embeddable_factory.tsx
index 20a5a8112f4d3..f977329562b9b 100644
--- a/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_embeddable_factory.tsx
+++ b/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_embeddable_factory.tsx
@@ -23,24 +23,21 @@ import { UiActionsStart } from 'src/plugins/ui_actions/public';
import { CoreStart } from 'src/core/public';
import { toMountPoint } from '../../../../../../kibana_react/public';
-import { EmbeddableFactory } from '../../../embeddables';
+import { EmbeddableFactoryDefinition } from '../../../embeddables';
import { Container } from '../../../containers';
import { ContactCardEmbeddable, ContactCardEmbeddableInput } from './contact_card_embeddable';
import { ContactCardInitializer } from './contact_card_initializer';
-import { EmbeddableFactoryOptions } from '../../../embeddables/embeddable_factory';
export const CONTACT_CARD_EMBEDDABLE = 'CONTACT_CARD_EMBEDDABLE';
-export class ContactCardEmbeddableFactory extends EmbeddableFactory {
+export class ContactCardEmbeddableFactory
+ implements EmbeddableFactoryDefinition {
public readonly type = CONTACT_CARD_EMBEDDABLE;
constructor(
- options: EmbeddableFactoryOptions,
private readonly execTrigger: UiActionsStart['executeTriggerActions'],
private readonly overlays: CoreStart['overlays']
- ) {
- super(options);
- }
+ ) {}
public async isEditable() {
return true;
@@ -52,7 +49,7 @@ export class ContactCardEmbeddableFactory extends EmbeddableFactory> {
+ public getExplicitInput = (): Promise> => {
return new Promise(resolve => {
const modalSession = this.overlays.openModal(
toMountPoint(
@@ -72,9 +69,9 @@ export class ContactCardEmbeddableFactory extends EmbeddableFactory {
return new ContactCardEmbeddable(
initialInput,
{
@@ -82,5 +79,5 @@ export class ContactCardEmbeddableFactory extends EmbeddableFactory {
+export class SlowContactCardEmbeddableFactory
+ implements EmbeddableFactoryDefinition {
private loadTickCount = 0;
public readonly type = CONTACT_CARD_EMBEDDABLE;
constructor(private readonly options: SlowContactCardEmbeddableFactoryOptions) {
- super();
if (options.loadTickCount) {
this.loadTickCount = options.loadTickCount;
}
@@ -48,10 +46,10 @@ export class SlowContactCardEmbeddableFactory extends EmbeddableFactory<
return 'slow to load contact card';
}
- public async create(initialInput: ContactCardEmbeddableInput, parent?: Container) {
+ public create = async (initialInput: ContactCardEmbeddableInput, parent?: Container) => {
for (let i = 0; i < this.loadTickCount; i++) {
await Promise.resolve();
}
return new ContactCardEmbeddable(initialInput, { execAction: this.options.execAction }, parent);
- }
+ };
}
diff --git a/src/plugins/embeddable/public/lib/test_samples/embeddables/filterable_container_factory.ts b/src/plugins/embeddable/public/lib/test_samples/embeddables/filterable_container_factory.ts
index 3488f6a2e038d..f27c7e8b011fd 100644
--- a/src/plugins/embeddable/public/lib/test_samples/embeddables/filterable_container_factory.ts
+++ b/src/plugins/embeddable/public/lib/test_samples/embeddables/filterable_container_factory.ts
@@ -18,24 +18,21 @@
*/
import { i18n } from '@kbn/i18n';
-import { Container, EmbeddableFactory } from '../..';
+import { Container, EmbeddableFactoryDefinition } from '../..';
import {
FilterableContainer,
FilterableContainerInput,
FILTERABLE_CONTAINER,
} from './filterable_container';
-import { EmbeddableFactoryOptions } from '../../embeddables/embeddable_factory';
import { EmbeddableStart } from '../../../plugin';
-export class FilterableContainerFactory extends EmbeddableFactory {
+export class FilterableContainerFactory
+ implements EmbeddableFactoryDefinition {
public readonly type = FILTERABLE_CONTAINER;
constructor(
- private readonly getFactory: EmbeddableStart['getEmbeddableFactory'],
- options: EmbeddableFactoryOptions = {}
- ) {
- super(options);
- }
+ private readonly getFactory: () => Promise
+ ) {}
public getDisplayName() {
return i18n.translate('embeddableApi.samples.filterableContainer.displayName', {
@@ -47,7 +44,8 @@ export class FilterableContainerFactory extends EmbeddableFactory {
+ const getEmbeddableFactory = await this.getFactory();
+ return new FilterableContainer(initialInput, getEmbeddableFactory, parent);
+ };
}
diff --git a/src/plugins/embeddable/public/lib/test_samples/embeddables/filterable_embeddable_factory.ts b/src/plugins/embeddable/public/lib/test_samples/embeddables/filterable_embeddable_factory.ts
index f37a16ea86c43..4c941ee22abfa 100644
--- a/src/plugins/embeddable/public/lib/test_samples/embeddables/filterable_embeddable_factory.ts
+++ b/src/plugins/embeddable/public/lib/test_samples/embeddables/filterable_embeddable_factory.ts
@@ -23,10 +23,11 @@ import {
FilterableEmbeddableInput,
FILTERABLE_EMBEDDABLE,
} from './filterable_embeddable';
-import { EmbeddableFactory } from '../../embeddables';
+import { EmbeddableFactoryDefinition } from '../../embeddables';
import { IContainer } from '../../containers';
-export class FilterableEmbeddableFactory extends EmbeddableFactory {
+export class FilterableEmbeddableFactory
+ implements EmbeddableFactoryDefinition {
public readonly type = FILTERABLE_EMBEDDABLE;
public async isEditable() {
diff --git a/src/plugins/embeddable/public/mocks.ts b/src/plugins/embeddable/public/mocks.ts
index ba2f78e42e10e..2ee05d8316ace 100644
--- a/src/plugins/embeddable/public/mocks.ts
+++ b/src/plugins/embeddable/public/mocks.ts
@@ -30,6 +30,7 @@ export type Start = jest.Mocked;
const createSetupContract = (): Setup => {
const setupContract: Setup = {
registerEmbeddableFactory: jest.fn(),
+ setCustomEmbeddableFactoryProvider: jest.fn(),
};
return setupContract;
};
diff --git a/src/plugins/embeddable/public/plugin.test.ts b/src/plugins/embeddable/public/plugin.test.ts
index c334411004e2c..804f3e2e8a7b4 100644
--- a/src/plugins/embeddable/public/plugin.test.ts
+++ b/src/plugins/embeddable/public/plugin.test.ts
@@ -18,6 +18,9 @@
*/
import { coreMock } from '../../../core/public/mocks';
import { testPlugin } from './tests/test_plugin';
+import { EmbeddableFactoryProvider } from './types';
+import { defaultEmbeddableFactoryProvider } from './lib';
+import { HelloWorldEmbeddable } from '../../../../examples/embeddable_examples/public';
test('cannot register embeddable factory with the same ID', async () => {
const coreSetup = coreMock.createSetup();
@@ -33,3 +36,75 @@ test('cannot register embeddable factory with the same ID', async () => {
'Embeddable factory [embeddableFactoryId = ID] already registered in Embeddables API.'
);
});
+
+test('can set custom embeddable factory provider', async () => {
+ const coreSetup = coreMock.createSetup();
+ const coreStart = coreMock.createStart();
+ const { setup, doStart } = testPlugin(coreSetup, coreStart);
+
+ const customProvider: EmbeddableFactoryProvider = def => ({
+ ...defaultEmbeddableFactoryProvider(def),
+ getDisplayName: () => 'Intercepted!',
+ });
+
+ setup.setCustomEmbeddableFactoryProvider(customProvider);
+ setup.registerEmbeddableFactory('test', {
+ type: 'test',
+ create: () => Promise.resolve(undefined),
+ getDisplayName: () => 'Test',
+ isEditable: () => Promise.resolve(true),
+ });
+
+ const start = doStart();
+ const factory = start.getEmbeddableFactory('test');
+ expect(factory!.getDisplayName()).toEqual('Intercepted!');
+});
+
+test('custom embeddable factory provider test for intercepting embeddable creation and destruction', async () => {
+ const coreSetup = coreMock.createSetup();
+ const coreStart = coreMock.createStart();
+ const { setup, doStart } = testPlugin(coreSetup, coreStart);
+
+ let updateCount = 0;
+ const customProvider: EmbeddableFactoryProvider = def => {
+ return {
+ ...defaultEmbeddableFactoryProvider(def),
+ create: async (input, parent) => {
+ const embeddable = await defaultEmbeddableFactoryProvider(def).create(input, parent);
+ if (embeddable) {
+ const subscription = embeddable.getInput$().subscribe(
+ () => {
+ updateCount++;
+ },
+ () => {},
+ () => {
+ subscription.unsubscribe();
+ updateCount = 0;
+ }
+ );
+ }
+ return embeddable;
+ },
+ };
+ };
+
+ setup.setCustomEmbeddableFactoryProvider(customProvider);
+ setup.registerEmbeddableFactory('test', {
+ type: 'test',
+ create: (input, parent) => Promise.resolve(new HelloWorldEmbeddable(input, parent)),
+ getDisplayName: () => 'Test',
+ isEditable: () => Promise.resolve(true),
+ });
+
+ const start = doStart();
+ const factory = start.getEmbeddableFactory('test');
+
+ const embeddable = await factory?.create({ id: '123' });
+ embeddable!.updateInput({ title: 'boo' });
+ // initial subscription, plus the second update.
+ expect(updateCount).toEqual(2);
+
+ embeddable!.destroy();
+ await new Promise(resolve => process.nextTick(resolve));
+ expect(updateCount).toEqual(0);
+});
diff --git a/src/plugins/embeddable/public/plugin.ts b/src/plugins/embeddable/public/plugin.ts
index 381665c359ffd..a483f90f76dde 100644
--- a/src/plugins/embeddable/public/plugin.ts
+++ b/src/plugins/embeddable/public/plugin.ts
@@ -18,9 +18,16 @@
*/
import { UiActionsSetup } from 'src/plugins/ui_actions/public';
import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../../core/public';
-import { EmbeddableFactoryRegistry } from './types';
+import { EmbeddableFactoryRegistry, EmbeddableFactoryProvider } from './types';
import { bootstrap } from './bootstrap';
-import { EmbeddableFactory, EmbeddableInput, EmbeddableOutput } from './lib';
+import {
+ EmbeddableFactory,
+ EmbeddableInput,
+ EmbeddableOutput,
+ defaultEmbeddableFactoryProvider,
+ IEmbeddable,
+} from './lib';
+import { EmbeddableFactoryDefinition } from './lib/embeddables/embeddable_factory_definition';
export interface EmbeddableSetupDependencies {
uiActions: UiActionsSetup;
@@ -29,21 +36,29 @@ export interface EmbeddableSetupDependencies {
export interface EmbeddableSetup {
registerEmbeddableFactory: (
id: string,
- factory: EmbeddableFactory
+ factory: EmbeddableFactoryDefinition
) => void;
+ setCustomEmbeddableFactoryProvider: (customProvider: EmbeddableFactoryProvider) => void;
}
+
export interface EmbeddableStart {
getEmbeddableFactory: <
I extends EmbeddableInput = EmbeddableInput,
- O extends EmbeddableOutput = EmbeddableOutput
+ O extends EmbeddableOutput = EmbeddableOutput,
+ E extends IEmbeddable = IEmbeddable
>(
embeddableFactoryId: string
- ) => EmbeddableFactory | undefined;
+ ) => EmbeddableFactory | undefined;
getEmbeddableFactories: () => IterableIterator;
}
export class EmbeddablePublicPlugin implements Plugin {
+ private readonly embeddableFactoryDefinitions: Map<
+ string,
+ EmbeddableFactoryDefinition
+ > = new Map();
private readonly embeddableFactories: EmbeddableFactoryRegistry = new Map();
+ private customEmbeddableFactoryProvider?: EmbeddableFactoryProvider;
constructor(initializerContext: PluginInitializerContext) {}
@@ -52,34 +67,57 @@ export class EmbeddablePublicPlugin implements Plugin {
+ if (this.customEmbeddableFactoryProvider) {
+ throw new Error(
+ 'Custom embeddable factory provider is already set, and can only be set once'
+ );
+ }
+ this.customEmbeddableFactoryProvider = provider;
+ },
};
}
- public start(core: CoreStart) {
+ public start(core: CoreStart): EmbeddableStart {
+ this.embeddableFactoryDefinitions.forEach(def => {
+ this.embeddableFactories.set(
+ def.type,
+ this.customEmbeddableFactoryProvider
+ ? this.customEmbeddableFactoryProvider(def)
+ : defaultEmbeddableFactoryProvider(def)
+ );
+ });
return {
getEmbeddableFactory: this.getEmbeddableFactory,
- getEmbeddableFactories: () => this.embeddableFactories.values(),
+ getEmbeddableFactories: () => {
+ this.ensureFactoriesExist();
+ return this.embeddableFactories.values();
+ },
};
}
public stop() {}
- private registerEmbeddableFactory = (embeddableFactoryId: string, factory: EmbeddableFactory) => {
- if (this.embeddableFactories.has(embeddableFactoryId)) {
+ private registerEmbeddableFactory = (
+ embeddableFactoryId: string,
+ factory: EmbeddableFactoryDefinition
+ ) => {
+ if (this.embeddableFactoryDefinitions.has(embeddableFactoryId)) {
throw new Error(
`Embeddable factory [embeddableFactoryId = ${embeddableFactoryId}] already registered in Embeddables API.`
);
}
-
- this.embeddableFactories.set(embeddableFactoryId, factory);
+ this.embeddableFactoryDefinitions.set(embeddableFactoryId, factory);
};
private getEmbeddableFactory = <
I extends EmbeddableInput = EmbeddableInput,
- O extends EmbeddableOutput = EmbeddableOutput
+ O extends EmbeddableOutput = EmbeddableOutput,
+ E extends IEmbeddable = IEmbeddable
>(
embeddableFactoryId: string
- ) => {
+ ): EmbeddableFactory => {
+ this.ensureFactoryExists(embeddableFactoryId);
const factory = this.embeddableFactories.get(embeddableFactoryId);
if (!factory) {
@@ -88,6 +126,24 @@ export class EmbeddablePublicPlugin implements Plugin;
+ return factory as EmbeddableFactory;
};
+
+ // These two functions are only to support legacy plugins registering factories after the start lifecycle.
+ private ensureFactoriesExist() {
+ this.embeddableFactoryDefinitions.forEach(def => this.ensureFactoryExists(def.type));
+ }
+
+ private ensureFactoryExists(type: string) {
+ if (!this.embeddableFactories.get(type)) {
+ const def = this.embeddableFactoryDefinitions.get(type);
+ if (!def) return;
+ this.embeddableFactories.set(
+ type,
+ this.customEmbeddableFactoryProvider
+ ? this.customEmbeddableFactoryProvider(def)
+ : defaultEmbeddableFactoryProvider(def)
+ );
+ }
+ }
}
diff --git a/src/plugins/embeddable/public/tests/apply_filter_action.test.ts b/src/plugins/embeddable/public/tests/apply_filter_action.test.ts
index 6beef35bbe136..54f3ac2887f6c 100644
--- a/src/plugins/embeddable/public/tests/apply_filter_action.test.ts
+++ b/src/plugins/embeddable/public/tests/apply_filter_action.test.ts
@@ -36,13 +36,13 @@ import { esFilters } from '../../../../plugins/data/public';
test('ApplyFilterAction applies the filter to the root of the container tree', async () => {
const { doStart, setup } = testPlugin();
- const api = doStart();
- const factory1 = new FilterableContainerFactory(api.getEmbeddableFactory);
const factory2 = new FilterableEmbeddableFactory();
-
- setup.registerEmbeddableFactory(factory1.type, factory1);
+ const factory1 = new FilterableContainerFactory(async () => await api.getEmbeddableFactory);
setup.registerEmbeddableFactory(factory2.type, factory2);
+ setup.registerEmbeddableFactory(factory1.type, factory1);
+
+ const api = doStart();
const applyFilterAction = createFilterAction();
@@ -63,7 +63,9 @@ test('ApplyFilterAction applies the filter to the root of the container tree', a
FilterableContainer
>(FILTERABLE_CONTAINER, { panels: {}, id: 'Node2' });
- if (isErrorEmbeddable(node1) || isErrorEmbeddable(node2)) throw new Error();
+ if (isErrorEmbeddable(node1) || isErrorEmbeddable(node2)) {
+ throw new Error();
+ }
const embeddable = await node2.addNewEmbeddable<
FilterableEmbeddableInput,
@@ -94,9 +96,11 @@ test('ApplyFilterAction applies the filter to the root of the container tree', a
test('ApplyFilterAction is incompatible if the root container does not accept a filter as input', async () => {
const { doStart, coreStart, setup } = testPlugin();
- const api = doStart();
const inspector = inspectorPluginMock.createStartContract();
+ const factory = new FilterableEmbeddableFactory();
+ setup.registerEmbeddableFactory(factory.type, factory);
+ const api = doStart();
const applyFilterAction = createFilterAction();
const parent = new HelloWorldContainer(
{ id: 'root', panels: {} },
@@ -110,10 +114,6 @@ test('ApplyFilterAction is incompatible if the root container does not accept a
SavedObjectFinder: () => null,
}
);
-
- const factory = new FilterableEmbeddableFactory();
- setup.registerEmbeddableFactory(factory.type, factory);
-
const embeddable = await parent.addNewEmbeddable<
FilterableContainerInput,
EmbeddableOutput,
@@ -130,12 +130,12 @@ test('ApplyFilterAction is incompatible if the root container does not accept a
test('trying to execute on incompatible context throws an error ', async () => {
const { doStart, coreStart, setup } = testPlugin();
- const api = doStart();
const inspector = inspectorPluginMock.createStartContract();
const factory = new FilterableEmbeddableFactory();
setup.registerEmbeddableFactory(factory.type, factory);
+ const api = doStart();
const applyFilterAction = createFilterAction();
const parent = new HelloWorldContainer(
{ id: 'root', panels: {} },
diff --git a/src/plugins/embeddable/public/tests/container.test.ts b/src/plugins/embeddable/public/tests/container.test.ts
index 1ee52f4749135..87076399465d3 100644
--- a/src/plugins/embeddable/public/tests/container.test.ts
+++ b/src/plugins/embeddable/public/tests/container.test.ts
@@ -56,8 +56,6 @@ async function creatHelloWorldContainerAndEmbeddable(
const coreSetup = coreMock.createSetup();
const coreStart = coreMock.createStart();
const { setup, doStart, uiActions } = testPlugin(coreSetup, coreStart);
- const start = doStart();
-
const filterableFactory = new FilterableEmbeddableFactory();
const slowContactCardFactory = new SlowContactCardEmbeddableFactory({
execAction: uiActions.executeTriggerActions,
@@ -68,6 +66,8 @@ async function creatHelloWorldContainerAndEmbeddable(
setup.registerEmbeddableFactory(slowContactCardFactory.type, slowContactCardFactory);
setup.registerEmbeddableFactory(helloWorldFactory.type, helloWorldFactory);
+ const start = doStart();
+
const container = new HelloWorldContainer(containerInput, {
getActions: uiActions.getTriggerCompatibleActions,
getEmbeddableFactory: start.getEmbeddableFactory,
@@ -563,6 +563,13 @@ test('Container changes made directly after adding a new embeddable are propagat
const coreSetup = coreMock.createSetup();
const coreStart = coreMock.createStart();
const { setup, doStart, uiActions } = testPlugin(coreSetup, coreStart);
+
+ const factory = new SlowContactCardEmbeddableFactory({
+ loadTickCount: 3,
+ execAction: uiActions.executeTriggerActions,
+ });
+ setup.registerEmbeddableFactory(factory.type, factory);
+
const start = doStart();
const container = new HelloWorldContainer(
@@ -582,12 +589,6 @@ test('Container changes made directly after adding a new embeddable are propagat
}
);
- const factory = new SlowContactCardEmbeddableFactory({
- loadTickCount: 3,
- execAction: uiActions.executeTriggerActions,
- });
- setup.registerEmbeddableFactory(factory.type, factory);
-
const subscription = Rx.merge(container.getOutput$(), container.getInput$())
.pipe(skip(2))
.subscribe(() => {
@@ -759,12 +760,13 @@ test('untilEmbeddableLoaded resolves with undefined if child is subsequently rem
coreMock.createSetup(),
coreMock.createStart()
);
- const start = doStart();
const factory = new SlowContactCardEmbeddableFactory({
loadTickCount: 3,
execAction: uiActions.executeTriggerActions,
});
setup.registerEmbeddableFactory(factory.type, factory);
+
+ const start = doStart();
const container = new HelloWorldContainer(
{
id: 'hello',
@@ -799,12 +801,12 @@ test('adding a panel then subsequently removing it before its loaded removes the
coreMock.createSetup(),
coreMock.createStart()
);
- const start = doStart();
const factory = new SlowContactCardEmbeddableFactory({
loadTickCount: 1,
execAction: uiActions.executeTriggerActions,
});
setup.registerEmbeddableFactory(factory.type, factory);
+ const start = doStart();
const container = new HelloWorldContainer(
{
id: 'hello',
diff --git a/src/plugins/embeddable/public/tests/customize_panel_modal.test.tsx b/src/plugins/embeddable/public/tests/customize_panel_modal.test.tsx
index 99d5a7c747d15..19e461b8bde7e 100644
--- a/src/plugins/embeddable/public/tests/customize_panel_modal.test.tsx
+++ b/src/plugins/embeddable/public/tests/customize_panel_modal.test.tsx
@@ -47,15 +47,14 @@ beforeEach(async () => {
coreMock.createSetup(),
coreMock.createStart()
);
- api = doStart();
const contactCardFactory = new ContactCardEmbeddableFactory(
- {},
uiActions.executeTriggerActions,
{} as any
);
setup.registerEmbeddableFactory(contactCardFactory.type, contactCardFactory);
+ api = doStart();
container = new HelloWorldContainer(
{ id: '123', panels: {} },
{
diff --git a/src/plugins/embeddable/public/tests/explicit_input.test.ts b/src/plugins/embeddable/public/tests/explicit_input.test.ts
index f0a7219531b59..0e03db3ec8358 100644
--- a/src/plugins/embeddable/public/tests/explicit_input.test.ts
+++ b/src/plugins/embeddable/public/tests/explicit_input.test.ts
@@ -41,7 +41,6 @@ const { setup, doStart, coreStart, uiActions } = testPlugin(
coreMock.createSetup(),
coreMock.createStart()
);
-const start = doStart();
setup.registerEmbeddableFactory(FILTERABLE_EMBEDDABLE, new FilterableEmbeddableFactory());
const factory = new SlowContactCardEmbeddableFactory({
@@ -51,6 +50,8 @@ const factory = new SlowContactCardEmbeddableFactory({
setup.registerEmbeddableFactory(CONTACT_CARD_EMBEDDABLE, factory);
setup.registerEmbeddableFactory(HELLO_WORLD_EMBEDDABLE, new HelloWorldEmbeddableFactory());
+const start = doStart();
+
test('Explicit embeddable input mapped to undefined will default to inherited', async () => {
const derivedFilter: Filter = {
$state: { store: esFilters.FilterStateStore.APP_STATE },
diff --git a/src/plugins/embeddable/public/tests/get_embeddable_factories.test.ts b/src/plugins/embeddable/public/tests/get_embeddable_factories.test.ts
index 1a222cd548de7..1989d6356cbd1 100644
--- a/src/plugins/embeddable/public/tests/get_embeddable_factories.test.ts
+++ b/src/plugins/embeddable/public/tests/get_embeddable_factories.test.ts
@@ -35,16 +35,16 @@ test('returns empty list if there are no embeddable factories', () => {
test('returns existing embeddable factories', () => {
const { setup, doStart } = testPlugin();
- const start = doStart();
- const { length } = [...start.getEmbeddableFactories()];
- const factory1 = new FilterableContainerFactory(start.getEmbeddableFactory);
- const factory2 = new ContactCardEmbeddableFactory({} as any, (() => null) as any, {} as any);
+ const factory1 = new FilterableContainerFactory(async () => await start.getEmbeddableFactory);
+ const factory2 = new ContactCardEmbeddableFactory((() => null) as any, {} as any);
setup.registerEmbeddableFactory(factory1.type, factory1);
setup.registerEmbeddableFactory(factory2.type, factory2);
+ const start = doStart();
+
const list = [...start.getEmbeddableFactories()];
- expect(list.length - length).toBe(2);
+ expect(list.length).toBe(2);
expect(!!list.find(({ type }) => factory1.type === type)).toBe(true);
expect(!!list.find(({ type }) => factory2.type === type)).toBe(true);
});
diff --git a/src/plugins/embeddable/public/types.ts b/src/plugins/embeddable/public/types.ts
index 7a879c389f3c4..2d112b2359818 100644
--- a/src/plugins/embeddable/public/types.ts
+++ b/src/plugins/embeddable/public/types.ts
@@ -17,6 +17,22 @@
* under the License.
*/
-import { EmbeddableFactory } from './lib/embeddables';
+import { SavedObjectAttributes } from 'kibana/public';
+import {
+ EmbeddableFactory,
+ EmbeddableInput,
+ EmbeddableOutput,
+ IEmbeddable,
+ EmbeddableFactoryDefinition,
+} from './lib/embeddables';
export type EmbeddableFactoryRegistry = Map;
+
+export type EmbeddableFactoryProvider = <
+ I extends EmbeddableInput = EmbeddableInput,
+ O extends EmbeddableOutput = EmbeddableOutput,
+ E extends IEmbeddable = IEmbeddable,
+ T extends SavedObjectAttributes = SavedObjectAttributes
+>(
+ def: EmbeddableFactoryDefinition
+) => EmbeddableFactory;
diff --git a/src/plugins/es_ui_shared/public/request/np_ready_request.ts b/src/plugins/es_ui_shared/public/request/np_ready_request.ts
index 6771abd64df7e..06af698f2ce02 100644
--- a/src/plugins/es_ui_shared/public/request/np_ready_request.ts
+++ b/src/plugins/es_ui_shared/public/request/np_ready_request.ts
@@ -43,7 +43,7 @@ export interface UseRequestResponse {
isInitialRequest: boolean;
isLoading: boolean;
error: E | null;
- data: D | null;
+ data?: D | null;
sendRequest: (...args: any[]) => Promise>;
}
diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/form_data_provider.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/form_data_provider.ts
index a8d24984cec7c..0509b8081c35b 100644
--- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/form_data_provider.ts
+++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/form_data_provider.ts
@@ -28,9 +28,9 @@ interface Props {
}
export const FormDataProvider = React.memo(({ children, pathsToWatch }: Props) => {
- const [formData, setFormData] = useState({});
- const previousRawData = useRef({});
const form = useFormContext();
+ const previousRawData = useRef(form.__formData$.current.value);
+ const [formData, setFormData] = useState(previousRawData.current);
useEffect(() => {
const subscription = form.subscribe(({ data: { raw } }) => {
@@ -41,6 +41,7 @@ export const FormDataProvider = React.memo(({ children, pathsToWatch }: Props) =
const valuesToWatchArray = Array.isArray(pathsToWatch)
? (pathsToWatch as string[])
: ([pathsToWatch] as string[]);
+
if (valuesToWatchArray.some(value => previousRawData.current[value] !== raw[value])) {
previousRawData.current = raw;
setFormData(raw);
diff --git a/src/plugins/visualizations/public/embeddable/create_vis_embeddable_from_object.ts b/src/plugins/visualizations/public/embeddable/create_vis_embeddable_from_object.ts
new file mode 100644
index 0000000000000..bf2d174f594b2
--- /dev/null
+++ b/src/plugins/visualizations/public/embeddable/create_vis_embeddable_from_object.ts
@@ -0,0 +1,69 @@
+/*
+ * 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 { Vis } from '../types';
+import { VisualizeInput, VisualizeEmbeddable } from './visualize_embeddable';
+import { IContainer, ErrorEmbeddable } from '../../../../plugins/embeddable/public';
+import { DisabledLabEmbeddable } from './disabled_lab_embeddable';
+import {
+ getSavedVisualizationsLoader,
+ getUISettings,
+ getHttp,
+ getTimeFilter,
+ getCapabilities,
+} from '../services';
+
+export const createVisEmbeddableFromObject = async (
+ vis: Vis,
+ input: Partial & { id: string },
+ parent?: IContainer
+): Promise => {
+ const savedVisualizations = getSavedVisualizationsLoader();
+
+ try {
+ const visId = vis.id as string;
+
+ const editUrl = visId
+ ? getHttp().basePath.prepend(`/app/kibana${savedVisualizations.urlFor(visId)}`)
+ : '';
+ const isLabsEnabled = getUISettings().get('visualize:enableLabs');
+
+ if (!isLabsEnabled && vis.type.stage === 'experimental') {
+ return new DisabledLabEmbeddable(vis.title, input);
+ }
+
+ const indexPattern = vis.data.indexPattern;
+ const indexPatterns = indexPattern ? [indexPattern] : [];
+ const editable = getCapabilities().visualize.save as boolean;
+ return new VisualizeEmbeddable(
+ getTimeFilter(),
+ {
+ vis,
+ indexPatterns,
+ editUrl,
+ editable,
+ },
+ input,
+ parent
+ );
+ } catch (e) {
+ console.error(e); // eslint-disable-line no-console
+ return new ErrorEmbeddable(e, input, parent);
+ }
+};
diff --git a/src/plugins/visualizations/public/embeddable/index.ts b/src/plugins/visualizations/public/embeddable/index.ts
index 78f9827ffde3e..3753c4dbbb9ed 100644
--- a/src/plugins/visualizations/public/embeddable/index.ts
+++ b/src/plugins/visualizations/public/embeddable/index.ts
@@ -21,3 +21,4 @@ export { VisualizeEmbeddable, VisualizeInput } from './visualize_embeddable';
export { VisualizeEmbeddableFactory } from './visualize_embeddable_factory';
export { VISUALIZE_EMBEDDABLE_TYPE } from './constants';
export { VIS_EVENT_TO_TRIGGER } from './events';
+export { createVisEmbeddableFromObject } from './create_vis_embeddable_from_object';
diff --git a/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts b/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts
index 0c7e732f0b185..e64d200251797 100644
--- a/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts
+++ b/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts
@@ -33,8 +33,8 @@ import {
EmbeddableInput,
EmbeddableOutput,
Embeddable,
- Container,
EmbeddableVisTriggerContext,
+ IContainer,
} from '../../../../plugins/embeddable/public';
import { dispatchRenderComplete } from '../../../../plugins/kibana_utils/public';
import { IExpressionLoaderParams, ExpressionsStart } from '../../../../plugins/expressions/public';
@@ -89,7 +89,7 @@ export class VisualizeEmbeddable extends Embeddable {
+export class VisualizeEmbeddableFactory
+ implements
+ EmbeddableFactoryDefinition<
+ VisualizeInput,
+ VisualizeOutput | EmbeddableOutput,
+ VisualizeEmbeddable | DisabledLabEmbeddable,
+ VisualizationAttributes
+ > {
public readonly type = VISUALIZE_EMBEDDABLE_TYPE;
-
- constructor() {
- super({
- savedObjectMetaData: {
- name: i18n.translate('visualizations.savedObjectName', { defaultMessage: 'Visualization' }),
- includeFields: ['visState'],
- type: 'visualization',
- getIconForSavedObject: savedObject => {
- return (
- getTypes().get(JSON.parse(savedObject.attributes.visState).type).icon || 'visualizeApp'
- );
- },
- getTooltipForSavedObject: savedObject => {
- return `${savedObject.attributes.title} (${
- getTypes().get(JSON.parse(savedObject.attributes.visState).type).title
- })`;
- },
- showSavedObject: savedObject => {
- const typeName: string = JSON.parse(savedObject.attributes.visState).type;
- const visType = getTypes().get(typeName);
- if (!visType) {
- return false;
- }
- if (getUISettings().get('visualize:enableLabs')) {
- return true;
- }
- return visType.stage !== 'experimental';
- },
- },
- });
- }
+ public readonly savedObjectMetaData: SavedObjectMetaData = {
+ name: i18n.translate('visualizations.savedObjectName', { defaultMessage: 'Visualization' }),
+ includeFields: ['visState'],
+ type: 'visualization',
+ getIconForSavedObject: savedObject => {
+ return (
+ getTypes().get(JSON.parse(savedObject.attributes.visState).type).icon || 'visualizeApp'
+ );
+ },
+ getTooltipForSavedObject: savedObject => {
+ return `${savedObject.attributes.title} (${
+ getTypes().get(JSON.parse(savedObject.attributes.visState).type).title
+ })`;
+ },
+ showSavedObject: savedObject => {
+ const typeName: string = JSON.parse(savedObject.attributes.visState).type;
+ const visType = getTypes().get(typeName);
+ if (!visType) {
+ return false;
+ }
+ if (getUISettings().get('visualize:enableLabs')) {
+ return true;
+ }
+ return visType.stage !== 'experimental';
+ },
+ };
+ constructor() {}
public async isEditable() {
return getCapabilities().visualize.save as boolean;
@@ -93,56 +91,17 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory<
});
}
- public async createFromObject(
- vis: Vis,
- input: Partial & { id: string },
- parent?: Container
- ): Promise {
- const savedVisualizations = getSavedVisualizationsLoader();
-
- try {
- const visId = vis.id as string;
-
- const editUrl = visId
- ? getHttp().basePath.prepend(`/app/kibana${savedVisualizations.urlFor(visId)}`)
- : '';
- const isLabsEnabled = getUISettings().get('visualize:enableLabs');
-
- if (!isLabsEnabled && vis.type.stage === 'experimental') {
- return new DisabledLabEmbeddable(vis.title, input);
- }
-
- const indexPattern = vis.data.indexPattern;
- const indexPatterns = indexPattern ? [indexPattern] : [];
- const editable = await this.isEditable();
- return new VisualizeEmbeddable(
- getTimeFilter(),
- {
- vis,
- indexPatterns,
- editUrl,
- editable,
- },
- input,
- parent
- );
- } catch (e) {
- console.error(e); // eslint-disable-line no-console
- return new ErrorEmbeddable(e, input, parent);
- }
- }
-
public async createFromSavedObject(
savedObjectId: string,
input: Partial & { id: string },
- parent?: Container
+ parent?: IContainer
): Promise {
const savedVisualizations = getSavedVisualizationsLoader();
try {
const savedObject = await savedVisualizations.get(savedObjectId);
const vis = new Vis(savedObject.visState.type, await convertToSerializedVis(savedObject));
- return this.createFromObject(vis, input, parent);
+ return createVisEmbeddableFromObject(vis, input, parent);
} catch (e) {
console.error(e); // eslint-disable-line no-console
return new ErrorEmbeddable(e, input, parent);
diff --git a/src/plugins/visualizations/public/mocks.ts b/src/plugins/visualizations/public/mocks.ts
index f4983a4313c4d..2aa346423297a 100644
--- a/src/plugins/visualizations/public/mocks.ts
+++ b/src/plugins/visualizations/public/mocks.ts
@@ -43,6 +43,9 @@ const createStartContract = (): VisualizationsStart => ({
createVis: jest.fn(),
convertFromSerializedVis: jest.fn(),
convertToSerializedVis: jest.fn(),
+ __LEGACY: {
+ createVisEmbeddableFromObject: jest.fn(),
+ },
});
const createInstance = async () => {
diff --git a/src/plugins/visualizations/public/plugin.ts b/src/plugins/visualizations/public/plugin.ts
index d3e7b759a4416..216defcee9016 100644
--- a/src/plugins/visualizations/public/plugin.ts
+++ b/src/plugins/visualizations/public/plugin.ts
@@ -37,7 +37,11 @@ import {
setChrome,
setOverlays,
} from './services';
-import { VISUALIZE_EMBEDDABLE_TYPE, VisualizeEmbeddableFactory } from './embeddable';
+import {
+ VISUALIZE_EMBEDDABLE_TYPE,
+ VisualizeEmbeddableFactory,
+ createVisEmbeddableFromObject,
+} from './embeddable';
import { ExpressionsSetup, ExpressionsStart } from '../../../plugins/expressions/public';
import { EmbeddableSetup } from '../../../plugins/embeddable/public';
import { visualization as visualizationFunction } from './expressions/visualization_function';
@@ -69,6 +73,7 @@ export interface VisualizationsStart extends TypesStart {
convertToSerializedVis: typeof convertToSerializedVis;
convertFromSerializedVis: typeof convertFromSerializedVis;
showNewVisModal: typeof showNewVisModal;
+ __LEGACY: { createVisEmbeddableFromObject: typeof createVisEmbeddableFromObject };
}
export interface VisualizationsSetupDeps {
@@ -163,6 +168,7 @@ export class VisualizationsPlugin
convertToSerializedVis,
convertFromSerializedVis,
savedVisualizationsLoader,
+ __LEGACY: { createVisEmbeddableFromObject },
};
}
diff --git a/test/functional/apps/visualize/_vertical_bar_chart.js b/test/functional/apps/visualize/_vertical_bar_chart.js
index 9bb220a11a86a..d5f4c45f8bdbc 100644
--- a/test/functional/apps/visualize/_vertical_bar_chart.js
+++ b/test/functional/apps/visualize/_vertical_bar_chart.js
@@ -54,6 +54,25 @@ export default function({ getService, getPageObjects }) {
});
});
+ describe('bar charts range on x axis', () => {
+ it('should individual bars for each configured range', async function() {
+ await PageObjects.visualize.navigateToNewVisualization();
+ await PageObjects.visualize.clickVerticalBarChart();
+ await PageObjects.visualize.clickNewSearch();
+ await PageObjects.timePicker.setDefaultAbsoluteRange();
+ await PageObjects.visEditor.clickBucket('X-axis');
+ log.debug('Aggregation = Date Range');
+ await PageObjects.visEditor.selectAggregation('Date Range');
+ log.debug('Field = @timestamp');
+ await PageObjects.visEditor.selectField('@timestamp');
+ await PageObjects.visEditor.clickAddDateRange();
+ await PageObjects.visEditor.setDateRangeByIndex('1', 'now-2w/w', 'now-1w/w');
+ await PageObjects.visEditor.clickGo();
+ const bottomLabels = await PageObjects.visChart.getXAxisLabels();
+ expect(bottomLabels.length).to.be(2);
+ });
+ });
+
// FLAKY: https://github.com/elastic/kibana/issues/22322
describe.skip('vertical bar chart flaky part', function() {
const vizName1 = 'Visualization VerticalBarChart';
diff --git a/test/functional/page_objects/visualize_editor_page.ts b/test/functional/page_objects/visualize_editor_page.ts
index b1c3e924b3c1b..41c12170cf4dc 100644
--- a/test/functional/page_objects/visualize_editor_page.ts
+++ b/test/functional/page_objects/visualize_editor_page.ts
@@ -103,6 +103,15 @@ export function VisualizeEditorPageProvider({ getService, getPageObjects }: FtrP
await radioBtn.click();
}
+ public async clickAddDateRange() {
+ await testSubjects.click(`visEditorAddDateRange`);
+ }
+
+ public async setDateRangeByIndex(index: string, from: string, to: string) {
+ await testSubjects.setValue(`visEditorDateRange${index}__from`, from);
+ await testSubjects.setValue(`visEditorDateRange${index}__to`, to);
+ }
+
/**
* Adds new bucket
* @param bucketName bucket name, like 'X-axis', 'Split rows', 'Split series'
diff --git a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/app/dashboard_container_example.tsx b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/app/dashboard_container_example.tsx
index f8625e4490e51..fd07416cadbc5 100644
--- a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/app/dashboard_container_example.tsx
+++ b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/app/dashboard_container_example.tsx
@@ -29,7 +29,6 @@ import {
import {
DASHBOARD_CONTAINER_TYPE,
DashboardContainer,
- DashboardContainerFactory,
DashboardContainerInput,
} from '../../../../../../../../src/plugins/dashboard/public';
@@ -70,8 +69,9 @@ export class DashboardContainerExample extends React.Component {
this.mounted = true;
const dashboardFactory = this.props.getEmbeddableFactory<
DashboardContainerInput,
- ContainerOutput
- >(DASHBOARD_CONTAINER_TYPE) as DashboardContainerFactory;
+ ContainerOutput,
+ DashboardContainer
+ >(DASHBOARD_CONTAINER_TYPE);
if (dashboardFactory) {
this.container = await dashboardFactory.create(dashboardInput);
if (this.mounted) {
diff --git a/x-pack/legacy/plugins/apm/e2e/package.json b/x-pack/legacy/plugins/apm/e2e/package.json
index 57500dfe3fdc8..150ad5bb13d0b 100644
--- a/x-pack/legacy/plugins/apm/e2e/package.json
+++ b/x-pack/legacy/plugins/apm/e2e/package.json
@@ -13,6 +13,7 @@
"@types/cypress-cucumber-preprocessor": "^1.14.1",
"@types/js-yaml": "^3.12.1",
"@types/node": "^10.12.11",
+ "axios": "^0.19.2",
"cypress": "^4.2.0",
"cypress-cucumber-preprocessor": "^2.0.1",
"js-yaml": "^3.13.1",
@@ -21,6 +22,7 @@
"p-retry": "^4.2.0",
"ts-loader": "^6.2.2",
"typescript": "3.8.3",
+ "yargs": "^15.3.1",
"wait-on": "^4.0.1",
"webpack": "^4.42.1"
}
diff --git a/x-pack/legacy/plugins/apm/e2e/run-e2e.sh b/x-pack/legacy/plugins/apm/e2e/run-e2e.sh
index 5e55dc1eb834d..7c17c14dc9601 100755
--- a/x-pack/legacy/plugins/apm/e2e/run-e2e.sh
+++ b/x-pack/legacy/plugins/apm/e2e/run-e2e.sh
@@ -75,6 +75,13 @@ if [ $? -ne 0 ]; then
exit 1
fi
+#
+# Cypress
+##################################################
+echo "\n${bold}Cypress (logs: ${TMP_DIR}/e2e-yarn.log)${normal}"
+echo "Installing cypress dependencies "
+yarn &> ${TMP_DIR}/e2e-yarn.log
+
#
# Static mock data
##################################################
@@ -99,13 +106,6 @@ if [ $? -ne 0 ]; then
exit 1
fi
-#
-# Cypress
-##################################################
-echo "\n${bold}Cypress (logs: ${TMP_DIR}/e2e-yarn.log)${normal}"
-echo "Installing cypress dependencies "
-yarn &> ${TMP_DIR}/e2e-yarn.log
-
#
# Wait for Kibana to start
##################################################
diff --git a/x-pack/legacy/plugins/apm/e2e/yarn.lock b/x-pack/legacy/plugins/apm/e2e/yarn.lock
index b7b531a9c73c0..c023c64eb1cf4 100644
--- a/x-pack/legacy/plugins/apm/e2e/yarn.lock
+++ b/x-pack/legacy/plugins/apm/e2e/yarn.lock
@@ -1288,7 +1288,7 @@ ansi-styles@^3.2.1:
dependencies:
color-convert "^1.9.0"
-ansi-styles@^4.1.0:
+ansi-styles@^4.0.0, ansi-styles@^4.1.0:
version "4.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359"
integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==
@@ -1429,6 +1429,13 @@ aws4@^1.8.0:
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.9.1.tgz#7e33d8f7d449b3f673cd72deb9abdc552dbe528e"
integrity sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==
+axios@^0.19.2:
+ version "0.19.2"
+ resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27"
+ integrity sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==
+ dependencies:
+ follow-redirects "1.5.10"
+
babel-loader@^8.0.2:
version "8.0.6"
resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.0.6.tgz#e33bdb6f362b03f4bb141a0c21ab87c501b70dfb"
@@ -1845,6 +1852,11 @@ cachedir@2.3.0:
resolved "https://registry.yarnpkg.com/cachedir/-/cachedir-2.3.0.tgz#0c75892a052198f0b21c7c1804d8331edfcae0e8"
integrity sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw==
+camelcase@^5.0.0:
+ version "5.3.1"
+ resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
+ integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
+
caniuse-lite@^1.0.30001023:
version "1.0.30001027"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001027.tgz#283e2ef17d94889cc216a22c6f85303d78ca852d"
@@ -2010,6 +2022,15 @@ cli-truncate@^0.2.1:
slice-ansi "0.0.4"
string-width "^1.0.1"
+cliui@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1"
+ integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==
+ dependencies:
+ string-width "^4.2.0"
+ strip-ansi "^6.0.0"
+ wrap-ansi "^6.2.0"
+
clone@^1.0.2:
version "1.0.4"
resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e"
@@ -2417,7 +2438,7 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3:
dependencies:
ms "2.0.0"
-debug@3.1.0:
+debug@3.1.0, debug@=3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
@@ -2438,6 +2459,11 @@ debug@^3.0.1, debug@^3.1.0:
dependencies:
ms "^2.1.1"
+decamelize@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
+ integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
+
decode-uri-component@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
@@ -2621,6 +2647,11 @@ elliptic@^6.0.0:
minimalistic-assert "^1.0.0"
minimalistic-crypto-utils "^1.0.0"
+emoji-regex@^8.0.0:
+ version "8.0.0"
+ resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
+ integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
+
emojis-list@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389"
@@ -2933,6 +2964,14 @@ find-up@^3.0.0:
dependencies:
locate-path "^3.0.0"
+find-up@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19"
+ integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==
+ dependencies:
+ locate-path "^5.0.0"
+ path-exists "^4.0.0"
+
flush-write-stream@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8"
@@ -2951,6 +2990,13 @@ folktale@2.3.2:
resolved "https://registry.yarnpkg.com/folktale/-/folktale-2.3.2.tgz#38231b039e5ef36989920cbf805bf6b227bf4fd4"
integrity sha512-+8GbtQBwEqutP0v3uajDDoN64K2ehmHd0cjlghhxh0WpcfPzAIjPA03e1VvHlxL02FVGR0A6lwXsNQKn3H1RNQ==
+follow-redirects@1.5.10:
+ version "1.5.10"
+ resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a"
+ integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==
+ dependencies:
+ debug "=3.1.0"
+
for-in@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
@@ -3041,6 +3087,11 @@ get-assigned-identifiers@^1.2.0:
resolved "https://registry.yarnpkg.com/get-assigned-identifiers/-/get-assigned-identifiers-1.2.0.tgz#6dbf411de648cbaf8d9169ebb0d2d576191e2ff1"
integrity sha512-mBBwmeGTrxEMO4pMaaf/uUEFHnYtwr8FTe8Y/mer4rcV/bye0qGm6pw1bGZFGStxC5O76c5ZAVBGnqHmOaJpdQ==
+get-caller-file@^2.0.1:
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
+ integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
+
get-func-name@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41"
@@ -3418,6 +3469,11 @@ is-fullwidth-code-point@^2.0.0:
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=
+is-fullwidth-code-point@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
+ integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
+
is-generator@^1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/is-generator/-/is-generator-1.0.3.tgz#c14c21057ed36e328db80347966c693f886389f3"
@@ -3779,6 +3835,13 @@ locate-path@^3.0.0:
p-locate "^3.0.0"
path-exists "^3.0.0"
+locate-path@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0"
+ integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==
+ dependencies:
+ p-locate "^4.1.0"
+
lodash.clonedeep@4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
@@ -4328,7 +4391,7 @@ p-finally@^1.0.0:
resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=
-p-limit@^2.0.0, p-limit@^2.2.1:
+p-limit@^2.0.0, p-limit@^2.2.0, p-limit@^2.2.1:
version "2.2.2"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.2.tgz#61279b67721f5287aa1c13a9a7fbbc48c9291b1e"
integrity sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==
@@ -4342,6 +4405,13 @@ p-locate@^3.0.0:
dependencies:
p-limit "^2.0.0"
+p-locate@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07"
+ integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==
+ dependencies:
+ p-limit "^2.2.0"
+
p-map@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175"
@@ -4428,6 +4498,11 @@ path-exists@^3.0.0:
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515"
integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=
+path-exists@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
+ integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
+
path-is-absolute@^1.0.0, path-is-absolute@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
@@ -4836,11 +4911,21 @@ request@cypress-io/request#b5af0d1fa47eec97ba980cde90a13e69a2afcd16:
tunnel-agent "^0.6.0"
uuid "^3.3.2"
+require-directory@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
+ integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I=
+
require-from-string@^2.0.1:
version "2.0.2"
resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909"
integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==
+require-main-filename@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
+ integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==
+
resolve-url@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
@@ -4982,6 +5067,11 @@ serialize-javascript@^2.1.2:
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-2.1.2.tgz#ecec53b0e0317bdc95ef76ab7074b7384785fa61"
integrity sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==
+set-blocking@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
+ integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc=
+
set-value@^2.0.0, set-value@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b"
@@ -5316,6 +5406,15 @@ string-width@^2.1.1:
is-fullwidth-code-point "^2.0.0"
strip-ansi "^4.0.0"
+string-width@^4.1.0, string-width@^4.2.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5"
+ integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==
+ dependencies:
+ emoji-regex "^8.0.0"
+ is-fullwidth-code-point "^3.0.0"
+ strip-ansi "^6.0.0"
+
string_decoder@^1.0.0, string_decoder@^1.1.1:
version "1.3.0"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
@@ -5856,6 +5955,11 @@ webpack@^4.42.1:
watchpack "^1.6.0"
webpack-sources "^1.4.1"
+which-module@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
+ integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=
+
which@^1.2.9:
version "1.3.1"
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
@@ -5878,6 +5982,15 @@ wrap-ansi@^3.0.1:
string-width "^2.1.1"
strip-ansi "^4.0.0"
+wrap-ansi@^6.2.0:
+ version "6.2.0"
+ resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53"
+ integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==
+ dependencies:
+ ansi-styles "^4.0.0"
+ string-width "^4.1.0"
+ strip-ansi "^6.0.0"
+
wrappy@1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
@@ -5903,6 +6016,31 @@ yallist@^3.0.2:
resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==
+yargs-parser@^18.1.1:
+ version "18.1.2"
+ resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.2.tgz#2f482bea2136dbde0861683abea7756d30b504f1"
+ integrity sha512-hlIPNR3IzC1YuL1c2UwwDKpXlNFBqD1Fswwh1khz5+d8Cq/8yc/Mn0i+rQXduu8hcrFKvO7Eryk+09NecTQAAQ==
+ dependencies:
+ camelcase "^5.0.0"
+ decamelize "^1.2.0"
+
+yargs@^15.3.1:
+ version "15.3.1"
+ resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.3.1.tgz#9505b472763963e54afe60148ad27a330818e98b"
+ integrity sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==
+ dependencies:
+ cliui "^6.0.0"
+ decamelize "^1.2.0"
+ find-up "^4.1.0"
+ get-caller-file "^2.0.1"
+ require-directory "^2.1.1"
+ require-main-filename "^2.0.0"
+ set-blocking "^2.0.0"
+ string-width "^4.2.0"
+ which-module "^2.0.0"
+ y18n "^4.0.0"
+ yargs-parser "^18.1.1"
+
yauzl@2.10.0:
version "2.10.0"
resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"
diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_service/embeddable/embeddable_factory.ts b/x-pack/legacy/plugins/lens/public/editor_frame_service/embeddable/embeddable_factory.ts
index 1caea1b4b728f..99a59c756e228 100644
--- a/x-pack/legacy/plugins/lens/public/editor_frame_service/embeddable/embeddable_factory.ts
+++ b/x-pack/legacy/plugins/lens/public/editor_frame_service/embeddable/embeddable_factory.ts
@@ -18,7 +18,7 @@ import {
} from '../../../../../../../src/plugins/data/public';
import { ReactExpressionRendererType } from '../../../../../../../src/plugins/expressions/public';
import {
- EmbeddableFactory as AbstractEmbeddableFactory,
+ EmbeddableFactoryDefinition,
ErrorEmbeddable,
EmbeddableInput,
IContainer,
@@ -36,25 +36,22 @@ interface StartServices {
indexPatternService: IndexPatternsContract;
}
-export class EmbeddableFactory extends AbstractEmbeddableFactory {
+export class EmbeddableFactory implements EmbeddableFactoryDefinition {
type = DOC_TYPE;
+ savedObjectMetaData = {
+ name: i18n.translate('xpack.lens.lensSavedObjectLabel', {
+ defaultMessage: 'Lens Visualization',
+ }),
+ type: DOC_TYPE,
+ getIconForSavedObject: () => 'lensApp',
+ };
- constructor(private getStartServices: () => Promise) {
- super({
- savedObjectMetaData: {
- name: i18n.translate('xpack.lens.lensSavedObjectLabel', {
- defaultMessage: 'Lens Visualization',
- }),
- type: DOC_TYPE,
- getIconForSavedObject: () => 'lensApp',
- },
- });
- }
+ constructor(private getStartServices: () => Promise) {}
- public async isEditable() {
+ public isEditable = async () => {
const { capabilities } = await this.getStartServices();
return capabilities.visualize.save as boolean;
- }
+ };
canCreateNew() {
return false;
@@ -66,11 +63,11 @@ export class EmbeddableFactory extends AbstractEmbeddableFactory {
});
}
- async createFromSavedObject(
+ createFromSavedObject = async (
savedObjectId: string,
input: Partial & { id: string },
parent?: IContainer
- ) {
+ ) => {
const {
savedObjectsClient,
coreHttp,
@@ -111,7 +108,7 @@ export class EmbeddableFactory extends AbstractEmbeddableFactory {
input,
parent
);
- }
+ };
async create(input: EmbeddableInput) {
return new ErrorEmbeddable('Lens can only be created from a saved object', input);
diff --git a/x-pack/legacy/plugins/maps/public/actions/map_actions.js b/x-pack/legacy/plugins/maps/public/actions/map_actions.js
index 415630d9f730b..aa55cf0808ef2 100644
--- a/x-pack/legacy/plugins/maps/public/actions/map_actions.js
+++ b/x-pack/legacy/plugins/maps/public/actions/map_actions.js
@@ -174,9 +174,16 @@ export function removeTrackedLayerStateForSelectedLayer() {
export function replaceLayerList(newLayerList) {
return (dispatch, getState) => {
- getLayerListRaw(getState()).forEach(({ id }) => {
- dispatch(removeLayerFromLayerList(id));
- });
+ const isMapReady = getMapReady(getState());
+ if (!isMapReady) {
+ dispatch({
+ type: CLEAR_WAITING_FOR_MAP_READY_LAYER_LIST,
+ });
+ } else {
+ getLayerListRaw(getState()).forEach(({ id }) => {
+ dispatch(removeLayerFromLayerList(id));
+ });
+ }
newLayerList.forEach(layerDescriptor => {
dispatch(addLayer(layerDescriptor));
diff --git a/x-pack/legacy/plugins/maps/public/connected_components/map/mb/view.js b/x-pack/legacy/plugins/maps/public/connected_components/map/mb/view.js
index fdc8ad2176d08..2995ea039e7a8 100644
--- a/x-pack/legacy/plugins/maps/public/connected_components/map/mb/view.js
+++ b/x-pack/legacy/plugins/maps/public/connected_components/map/mb/view.js
@@ -235,12 +235,12 @@ export class MBMapContainer extends React.Component {
//clamping ot -89/89 latitudes since Mapboxgl does not seem to handle bounds that contain the poles (logs errors to the console when using -90/90)
const lnLatBounds = new mapboxgl.LngLatBounds(
new mapboxgl.LngLat(
- clampToLonBounds(goto.bounds.min_lon),
- clampToLatBounds(goto.bounds.min_lat)
+ clampToLonBounds(goto.bounds.minLon),
+ clampToLatBounds(goto.bounds.minLat)
),
new mapboxgl.LngLat(
- clampToLonBounds(goto.bounds.max_lon),
- clampToLatBounds(goto.bounds.max_lat)
+ clampToLonBounds(goto.bounds.maxLon),
+ clampToLatBounds(goto.bounds.maxLat)
)
);
//maxZoom ensure we're not zooming in too far on single points or small shapes
diff --git a/x-pack/legacy/plugins/maps/public/embeddable/README.md b/x-pack/legacy/plugins/maps/public/embeddable/README.md
index 1de327702fb87..8ce3794e2ed2c 100644
--- a/x-pack/legacy/plugins/maps/public/embeddable/README.md
+++ b/x-pack/legacy/plugins/maps/public/embeddable/README.md
@@ -30,17 +30,15 @@
### Creating a Map embeddable from state
```
const factory = new MapEmbeddableFactory();
-const state = {
- layerList: [], // where layerList is same as saved object layerListJSON property (unstringified)
- title: 'my map',
-}
const input = {
hideFilterActions: true,
isLayerTOCOpen: false,
openTOCDetails: ['tfi3f', 'edh66'],
mapCenter: { lat: 0.0, lon: 0.0, zoom: 7 }
}
-const mapEmbeddable = await factory.createFromState(state, input, parent);
+const mapEmbeddable = await factory.create(input, parent);
+// where layerList is same as saved object layerListJSON property (unstringified))
+mapEmbeddable.setLayerList([]);
```
#### Customize tooltip
@@ -62,7 +60,9 @@ const renderTooltipContent = ({ addFilters, closeTooltip, features, isLocked, lo
return Custom tooltip content
;
}
-const mapEmbeddable = await factory.createFromState(state, input, parent, renderTooltipContent);
+const mapEmbeddable = await factory.create(input, parent)
+mapEmbeddable.setLayerList(layerList);
+mapEmbeddable.setRenderTooltipContent(renderTooltipContent);
```
@@ -80,7 +80,10 @@ const eventHandlers = {
},
}
-const mapEmbeddable = await factory.createFromState(state, input, parent, renderTooltipContent, eventHandlers);
+const mapEmbeddable = await factory.create(input, parent);
+mapEmbeddable.setLayerList(layerList);
+mapEmbeddable.setRenderTooltipContent(renderTooltipContent);
+mapEmbeddable.setEventHandlers(eventHandlers);
```
@@ -90,55 +93,13 @@ Geojson sources will not update unless you modify `__featureCollection` property
```
const factory = new MapEmbeddableFactory();
-const state = {
- layerList: [
- {
- 'id': 'gaxya',
- 'label': 'My geospatial data',
- 'minZoom': 0,
- 'maxZoom': 24,
- 'alpha': 1,
- 'sourceDescriptor': {
- 'id': 'b7486',
- 'type': 'GEOJSON_FILE',
- '__featureCollection': {
- "type": "FeatureCollection",
- "features": [
- {
- "type": "Feature",
- "geometry": {
- "type": "Polygon",
- "coordinates": [
- [
- [0, 0], [10, 10], [10, 0], [0, 0]
- ]
- ]
- },
- "properties": {
- "name": "null island",
- "another_prop": "something else interesting"
- }
- }
- ]
- }
- },
- 'visible': true,
- 'style': {
- 'type': 'VECTOR',
- 'properties': {}
- },
- 'type': 'VECTOR'
- }
- ],
- title: 'my map',
-}
const input = {
hideFilterActions: true,
isLayerTOCOpen: false,
openTOCDetails: ['tfi3f', 'edh66'],
mapCenter: { lat: 0.0, lon: 0.0, zoom: 7 }
}
-const mapEmbeddable = await factory.createFromState(state, input, parent);
+const mapEmbeddable = await factory.create(input, parent);
mapEmbeddable.setLayerList([
{
diff --git a/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable.tsx b/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable.tsx
index 3c9069c7a836f..9544e8714f265 100644
--- a/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable.tsx
+++ b/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable.tsx
@@ -129,6 +129,14 @@ export class MapEmbeddable extends Embeddable this.onContainerStateChanged(input));
}
+ setRenderTooltipContent = (renderTooltipContent: RenderToolTipContent) => {
+ this._renderTooltipContent = renderTooltipContent;
+ };
+
+ setEventHandlers = (eventHandlers: EventHandlers) => {
+ this._eventHandlers = eventHandlers;
+ };
+
getInspectorAdapters() {
return getInspectorAdapters(this._store.getState());
}
diff --git a/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable_factory.ts b/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable_factory.ts
index b9cb66f831281..5a036ed47fb62 100644
--- a/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable_factory.ts
+++ b/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable_factory.ts
@@ -14,8 +14,7 @@ import { IIndexPattern } from 'src/plugins/data/public';
import { MapEmbeddable, MapEmbeddableInput } from './map_embeddable';
import { getIndexPatternService } from '../kibana_services';
import {
- EmbeddableFactory,
- ErrorEmbeddable,
+ EmbeddableFactoryDefinition,
IContainer,
} from '../../../../../../src/plugins/embeddable/public';
@@ -28,25 +27,17 @@ import { getInitialLayers } from '../angular/get_initial_layers';
import { mergeInputWithSavedMap } from './merge_input_with_saved_map';
import '../angular/services/gis_map_saved_object_loader';
import { bindSetupCoreAndPlugins, bindStartCoreAndPlugins } from '../plugin';
-import { RenderToolTipContent } from '../layers/tooltips/tooltip_property';
-import {
- EventHandlers,
- // eslint-disable-next-line @kbn/eslint/no-restricted-paths
-} from '../../../../../plugins/maps/public/reducers/non_serializable_instances';
-export class MapEmbeddableFactory extends EmbeddableFactory {
+export class MapEmbeddableFactory implements EmbeddableFactoryDefinition {
type = MAP_SAVED_OBJECT_TYPE;
-
+ savedObjectMetaData = {
+ name: i18n.translate('xpack.maps.mapSavedObjectLabel', {
+ defaultMessage: 'Map',
+ }),
+ type: MAP_SAVED_OBJECT_TYPE,
+ getIconForSavedObject: () => APP_ICON,
+ };
constructor() {
- super({
- savedObjectMetaData: {
- name: i18n.translate('xpack.maps.mapSavedObjectLabel', {
- defaultMessage: 'Map',
- }),
- type: MAP_SAVED_OBJECT_TYPE,
- getIconForSavedObject: () => APP_ICON,
- },
- });
// Init required services. Necessary while in legacy
bindSetupCoreAndPlugins(npSetup.core, npSetup.plugins);
bindStartCoreAndPlugins(npStart.core, npStart.plugins);
@@ -103,11 +94,11 @@ export class MapEmbeddableFactory extends EmbeddableFactory {
return await savedObjectLoader.get(savedObjectId);
}
- async createFromSavedObject(
+ createFromSavedObject = async (
savedObjectId: string,
input: MapEmbeddableInput,
parent?: IContainer
- ) {
+ ) => {
const savedMap = await this._fetchSavedMap(savedObjectId);
const layerList = getInitialLayers(savedMap.layerListJSON);
const indexPatterns = await this._getIndexPatterns(layerList);
@@ -135,39 +126,23 @@ export class MapEmbeddableFactory extends EmbeddableFactory {
}
return embeddable;
- }
+ };
- async createFromState(
- state: { title?: string; layerList?: unknown[] },
- input: MapEmbeddableInput,
- parent: IContainer,
- renderTooltipContent: RenderToolTipContent,
- eventHandlers: EventHandlers
- ) {
- const layerList = state && state.layerList ? state.layerList : getInitialLayers();
+ create = async (input: MapEmbeddableInput, parent?: IContainer) => {
+ const layerList = getInitialLayers();
const indexPatterns = await this._getIndexPatterns(layerList);
return new MapEmbeddable(
{
layerList,
- title: state && state.title ? state.title : '',
+ title: input.title ?? '',
indexPatterns,
editable: false,
},
input,
- parent,
- renderTooltipContent,
- eventHandlers
- );
- }
-
- async create(input: MapEmbeddableInput) {
- window.location.href = chrome.addBasePath(createMapPath(''));
- return new ErrorEmbeddable(
- 'Maps can only be created with createFromSavedObject or createFromState',
- input
+ parent
);
- }
+ };
}
npSetup.plugins.embeddable.registerEmbeddableFactory(
diff --git a/x-pack/legacy/plugins/maps/public/layers/layer.d.ts b/x-pack/legacy/plugins/maps/public/layers/layer.d.ts
index 777566298e607..de59642ede8ab 100644
--- a/x-pack/legacy/plugins/maps/public/layers/layer.d.ts
+++ b/x-pack/legacy/plugins/maps/public/layers/layer.d.ts
@@ -3,12 +3,13 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
-import { LayerDescriptor } from '../../common/descriptor_types';
+import { LayerDescriptor, MapExtent, MapFilters } from '../../common/descriptor_types';
import { ISource } from './sources/source';
import { DataRequest } from './util/data_request';
import { SyncContext } from '../actions/map_actions';
export interface ILayer {
+ getBounds(mapFilters: MapFilters): Promise;
getDataRequest(id: string): DataRequest | undefined;
getDisplayName(source?: ISource): Promise;
getId(): string;
@@ -25,6 +26,7 @@ export interface ILayerArguments {
export class AbstractLayer implements ILayer {
constructor(layerArguments: ILayerArguments);
+ getBounds(mapFilters: MapFilters): Promise;
getDataRequest(id: string): DataRequest | undefined;
getDisplayName(source?: ISource): Promise;
getId(): string;
diff --git a/x-pack/legacy/plugins/maps/public/layers/layer.js b/x-pack/legacy/plugins/maps/public/layers/layer.js
index d162e342dfd1a..e9616be89b601 100644
--- a/x-pack/legacy/plugins/maps/public/layers/layer.js
+++ b/x-pack/legacy/plugins/maps/public/layers/layer.js
@@ -320,12 +320,12 @@ export class AbstractLayer {
return sourceDataRequest && sourceDataRequest.hasData();
}
- async getBounds() {
+ async getBounds(/* mapFilters: MapFilters */) {
return {
- min_lon: -180,
- max_lon: 180,
- min_lat: -89,
- max_lat: 89,
+ minLon: -180,
+ maxLon: 180,
+ minLat: -89,
+ maxLat: 89,
};
}
diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_source.js
index bf04a73cfba77..441d52d23398a 100644
--- a/x-pack/legacy/plugins/maps/public/layers/sources/es_source.js
+++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_source.js
@@ -175,10 +175,10 @@ export class AbstractESSource extends AbstractVectorSource {
}
return {
- min_lon: esBounds.top_left.lon,
- max_lon: esBounds.bottom_right.lon,
- min_lat: esBounds.bottom_right.lat,
- max_lat: esBounds.top_left.lat,
+ minLon: esBounds.top_left.lon,
+ maxLon: esBounds.bottom_right.lon,
+ minLat: esBounds.bottom_right.lat,
+ maxLat: esBounds.top_left.lat,
};
}
diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/vector_source.d.ts b/x-pack/legacy/plugins/maps/public/layers/sources/vector_source.d.ts
index 7a747da244233..1400654297e01 100644
--- a/x-pack/legacy/plugins/maps/public/layers/sources/vector_source.d.ts
+++ b/x-pack/legacy/plugins/maps/public/layers/sources/vector_source.d.ts
@@ -8,7 +8,11 @@
import { FeatureCollection } from 'geojson';
import { AbstractSource, ISource } from './source';
import { IField } from '../fields/field';
-import { ESSearchSourceResponseMeta } from '../../../common/descriptor_types';
+import {
+ ESSearchSourceResponseMeta,
+ MapExtent,
+ VectorSourceRequestMeta,
+} from '../../../common/descriptor_types';
export type GeoJsonFetchMeta = ESSearchSourceResponseMeta;
@@ -18,6 +22,7 @@ export type GeoJsonWithMeta = {
};
export interface IVectorSource extends ISource {
+ getBoundsForFilters(searchFilters: VectorSourceRequestMeta): MapExtent;
getGeoJsonWithMeta(
layerName: 'string',
searchFilters: unknown[],
@@ -29,6 +34,7 @@ export interface IVectorSource extends ISource {
}
export class AbstractVectorSource extends AbstractSource implements IVectorSource {
+ getBoundsForFilters(searchFilters: VectorSourceRequestMeta): MapExtent;
getGeoJsonWithMeta(
layerName: 'string',
searchFilters: unknown[],
diff --git a/x-pack/legacy/plugins/maps/public/layers/vector_layer.js b/x-pack/legacy/plugins/maps/public/layers/vector_layer.js
index 6b89554546330..d606420909281 100644
--- a/x-pack/legacy/plugins/maps/public/layers/vector_layer.js
+++ b/x-pack/legacy/plugins/maps/public/layers/vector_layer.js
@@ -167,10 +167,10 @@ export class VectorLayer extends AbstractLayer {
features: visibleFeatures,
});
return {
- min_lon: bbox[0],
- min_lat: bbox[1],
- max_lon: bbox[2],
- max_lat: bbox[3],
+ minLon: bbox[0],
+ minLat: bbox[1],
+ maxLon: bbox[2],
+ maxLat: bbox[3],
};
}
diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.tsx
index c7e368da1338f..cbb4006bbf933 100644
--- a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.tsx
@@ -10,7 +10,10 @@ import { createPortalNode, InPortal } from 'react-reverse-portal';
import styled, { css } from 'styled-components';
import { npStart } from 'ui/new_platform';
-import { EmbeddablePanel } from '../../../../../../../src/plugins/embeddable/public';
+import {
+ EmbeddablePanel,
+ ErrorEmbeddable,
+} from '../../../../../../../src/plugins/embeddable/public';
import { DEFAULT_INDEX_KEY } from '../../../common/constants';
import { getIndexPatternTitleIdMapping } from '../../hooks/api/helpers';
import { useIndexPatterns } from '../../hooks/use_index_patterns';
@@ -84,7 +87,9 @@ export const EmbeddedMapComponent = ({
setQuery,
startDate,
}: EmbeddedMapProps) => {
- const [embeddable, setEmbeddable] = React.useState(null);
+ const [embeddable, setEmbeddable] = React.useState(
+ undefined
+ );
const [isLoading, setIsLoading] = useState(true);
const [isError, setIsError] = useState(false);
const [isIndexError, setIsIndexError] = useState(false);
diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.test.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.test.tsx
index 0ffb13cd66028..f4e6ee5f878a6 100644
--- a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.test.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.test.tsx
@@ -20,8 +20,10 @@ jest.mock('ui/new_platform');
const { npStart } = createUiNewPlatformMock();
npStart.plugins.embeddable.getEmbeddableFactory = jest.fn().mockImplementation(() => ({
- createFromState: () => ({
+ create: () => ({
reload: jest.fn(),
+ setRenderTooltipContent: jest.fn(),
+ setLayerList: jest.fn(),
}),
}));
diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.tsx
index 56211c9ff8935..0c7a1212ba280 100644
--- a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.tsx
@@ -11,10 +11,20 @@ import minimatch from 'minimatch';
import { IndexPatternMapping, SetQuery } from './types';
import { getLayerList } from './map_config';
import { MAP_SAVED_OBJECT_TYPE } from '../../../../../../plugins/maps/public';
-import { MapEmbeddable, RenderTooltipContentParams } from '../../../../maps/public';
+import {
+ MapEmbeddable,
+ RenderTooltipContentParams,
+ MapEmbeddableInput,
+} from '../../../../maps/public';
import * as i18n from './translations';
import { Query, Filter } from '../../../../../../../src/plugins/data/public';
-import { EmbeddableStart, ViewMode } from '../../../../../../../src/plugins/embeddable/public';
+import {
+ EmbeddableStart,
+ isErrorEmbeddable,
+ EmbeddableOutput,
+ ViewMode,
+ ErrorEmbeddable,
+} from '../../../../../../../src/plugins/embeddable/public';
import { IndexPatternSavedObject } from '../../hooks/types';
/**
@@ -40,14 +50,19 @@ export const createEmbeddable = async (
setQuery: SetQuery,
portalNode: PortalNode,
embeddableApi: EmbeddableStart
-): Promise => {
- const factory = embeddableApi.getEmbeddableFactory(MAP_SAVED_OBJECT_TYPE);
+): Promise => {
+ const factory = embeddableApi.getEmbeddableFactory<
+ MapEmbeddableInput,
+ EmbeddableOutput,
+ MapEmbeddable
+ >(MAP_SAVED_OBJECT_TYPE);
- const state = {
- layerList: getLayerList(indexPatterns),
+ if (!factory) {
+ throw new Error('Map embeddable factory undefined');
+ }
+
+ const input: MapEmbeddableInput = {
title: i18n.MAP_TITLE,
- };
- const input = {
id: uuid.v4(),
filters,
hidePanelTitles: true,
@@ -86,13 +101,16 @@ export const createEmbeddable = async (
return ;
};
- // @ts-ignore method added in https://github.com/elastic/kibana/pull/43878
- const embeddableObject = await factory.createFromState(
- state,
- input,
- undefined,
- renderTooltipContent
- );
+ const embeddableObject = await factory.create(input);
+
+ if (!embeddableObject) {
+ throw new Error('Map embeddable is undefined');
+ }
+
+ if (!isErrorEmbeddable(embeddableObject)) {
+ embeddableObject.setRenderTooltipContent(renderTooltipContent);
+ embeddableObject.setLayerList(getLayerList(indexPatterns));
+ }
// Wire up to app refresh action
setQuery({
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/rules_notification_alert_type.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/rules_notification_alert_type.ts
index 32e64138ff6e0..e74da583e9193 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/rules_notification_alert_type.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/rules_notification_alert_type.ts
@@ -20,7 +20,7 @@ export const rulesNotificationAlertType = ({
logger: Logger;
}): NotificationAlertTypeDefinition => ({
id: NOTIFICATIONS_ID,
- name: 'SIEM Notifications',
+ name: 'SIEM notification',
actionGroups: siemRuleActionGroups,
defaultActionGroupId: 'default',
validate: {
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts
index 78b0cd84eeda3..91905722fbca3 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts
@@ -41,7 +41,7 @@ export const signalRulesAlertType = ({
}): SignalRuleAlertTypeDefinition => {
return {
id: SIGNALS_ID,
- name: 'SIEM Signals',
+ name: 'SIEM signal',
actionGroups: siemRuleActionGroups,
defaultActionGroupId: 'default',
validate: {
diff --git a/x-pack/legacy/plugins/uptime/common/constants/index.ts b/x-pack/legacy/plugins/uptime/common/constants/index.ts
index 19f2de3c6f0f4..74783cf46550f 100644
--- a/x-pack/legacy/plugins/uptime/common/constants/index.ts
+++ b/x-pack/legacy/plugins/uptime/common/constants/index.ts
@@ -8,7 +8,6 @@ export { ACTION_GROUP_DEFINITIONS } from './alerts';
export { CHART_FORMAT_LIMITS } from './chart_format_limits';
export { CLIENT_DEFAULTS } from './client_defaults';
export { CONTEXT_DEFAULTS } from './context_defaults';
-export { INDEX_NAMES } from './index_names';
export * from './capabilities';
export { PLUGIN } from './plugin';
export { QUERY, STATES } from './query';
diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/embedded_map.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/embedded_map.tsx
index 89227cdd56457..85d0b1b593704 100644
--- a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/embedded_map.tsx
+++ b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/embedded_map.tsx
@@ -9,7 +9,12 @@ import uuid from 'uuid';
import styled from 'styled-components';
import { npStart } from 'ui/new_platform';
-import { ViewMode } from '../../../../../../../../../src/plugins/embeddable/public';
+import {
+ ViewMode,
+ EmbeddableOutput,
+ ErrorEmbeddable,
+ isErrorEmbeddable,
+} from '../../../../../../../../../src/plugins/embeddable/public';
import * as i18n from './translations';
import { MapEmbeddable, MapEmbeddableInput } from '../../../../../../maps/public';
import { MAP_SAVED_OBJECT_TYPE } from '../../../../../../../../plugins/maps/public';
@@ -45,9 +50,13 @@ const EmbeddedPanel = styled.div`
export const EmbeddedMap = React.memo(({ upPoints, downPoints }: EmbeddedMapProps) => {
const { colors } = useContext(UptimeThemeContext);
- const [embeddable, setEmbeddable] = useState();
+ const [embeddable, setEmbeddable] = useState();
const embeddableRoot: React.RefObject = useRef(null);
- const factory = npStart.plugins.embeddable.getEmbeddableFactory(MAP_SAVED_OBJECT_TYPE);
+ const factory = npStart.plugins.embeddable.getEmbeddableFactory<
+ MapEmbeddableInput,
+ EmbeddableOutput,
+ MapEmbeddable
+ >(MAP_SAVED_OBJECT_TYPE);
const input: MapEmbeddableInput = {
id: uuid.v4(),
@@ -76,12 +85,17 @@ export const EmbeddedMap = React.memo(({ upPoints, downPoints }: EmbeddedMapProp
useEffect(() => {
async function setupEmbeddable() {
- const mapState = {
- layerList: getLayerList(upPoints, downPoints, colors),
+ if (!factory) {
+ throw new Error('Map embeddable not found.');
+ }
+ const embeddableObject = await factory.create({
+ ...input,
title: i18n.MAP_TITLE,
- };
- // @ts-ignore
- const embeddableObject = await factory.createFromState(mapState, input, undefined);
+ });
+
+ if (embeddableObject && !isErrorEmbeddable(embeddableObject)) {
+ embeddableObject.setLayerList(getLayerList(upPoints, downPoints, colors));
+ }
setEmbeddable(embeddableObject);
}
@@ -93,7 +107,7 @@ export const EmbeddedMap = React.memo(({ upPoints, downPoints }: EmbeddedMapProp
// update map layers based on points
useEffect(() => {
- if (embeddable) {
+ if (embeddable && !isErrorEmbeddable(embeddable)) {
embeddable.setLayerList(getLayerList(upPoints, downPoints, colors));
}
}, [upPoints, downPoints, embeddable, colors]);
diff --git a/x-pack/legacy/plugins/uptime/public/components/monitor_details/ml/__tests__/__snapshots__/ml_job_link.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/monitor_details/ml/__tests__/__snapshots__/ml_job_link.test.tsx.snap
index 85988af04a939..9957f13fc1334 100644
--- a/x-pack/legacy/plugins/uptime/public/components/monitor_details/ml/__tests__/__snapshots__/ml_job_link.test.tsx.snap
+++ b/x-pack/legacy/plugins/uptime/public/components/monitor_details/ml/__tests__/__snapshots__/ml_job_link.test.tsx.snap
@@ -3,7 +3,7 @@
exports[`ML JobLink renders without errors 1`] = `
diff --git a/x-pack/legacy/plugins/uptime/public/components/monitor_details/ml/ml_flyout_container.tsx b/x-pack/legacy/plugins/uptime/public/components/monitor_details/ml/ml_flyout_container.tsx
index fad12de94fd81..9eed24e2810d8 100644
--- a/x-pack/legacy/plugins/uptime/public/components/monitor_details/ml/ml_flyout_container.tsx
+++ b/x-pack/legacy/plugins/uptime/public/components/monitor_details/ml/ml_flyout_container.tsx
@@ -12,6 +12,7 @@ import {
hasMLJobSelector,
hasNewMLJobSelector,
isMLJobCreatingSelector,
+ selectDynamicSettings,
} from '../../../state/selectors';
import { createMLJobAction, getExistingMLJobAction } from '../../../state/actions';
import { MLJobLink } from './ml_job_link';
@@ -24,6 +25,7 @@ import { MLFlyoutView } from './ml_flyout';
import { ML_JOB_ID } from '../../../../common/constants';
import { UptimeRefreshContext, UptimeSettingsContext } from '../../../contexts';
import { useUrlParams } from '../../../hooks';
+import { getDynamicSettings } from '../../../state/actions/dynamic_settings';
interface Props {
onClose: () => void;
@@ -48,13 +50,13 @@ const showMLJobNotification = (
),
- toastLifeTimeMs: 5000,
+ toastLifeTimeMs: 10000,
});
} else {
- notifications.toasts.warning({
+ notifications.toasts.danger({
title: {labels.JOB_CREATION_FAILED}
,
body: message ?? {labels.JOB_CREATION_FAILED_MESSAGE}
,
- toastLifeTimeMs: 5000,
+ toastLifeTimeMs: 10000,
});
}
};
@@ -65,6 +67,12 @@ export const MachineLearningFlyout: React.FC = ({ onClose }) => {
const dispatch = useDispatch();
const { data: hasMLJob, error } = useSelector(hasNewMLJobSelector);
const isMLJobCreating = useSelector(isMLJobCreatingSelector);
+ const { settings } = useSelector(selectDynamicSettings);
+ useEffect(() => {
+ // Attempt to load or refresh the dynamic settings
+ dispatch(getDynamicSettings({}));
+ }, [dispatch]);
+ const heartbeatIndices = settings?.heartbeatIndices || '';
const { basePath } = useContext(UptimeSettingsContext);
const { refreshApp } = useContext(UptimeRefreshContext);
@@ -72,9 +80,12 @@ export const MachineLearningFlyout: React.FC = ({ onClose }) => {
let { monitorId } = useParams();
monitorId = atob(monitorId || '');
- const createMLJob = () => dispatch(createMLJobAction.get({ monitorId: monitorId as string }));
+ const canCreateMLJob = useSelector(canCreateMLJobSelector) && heartbeatIndices !== '';
- const canCreateMLJob = useSelector(canCreateMLJobSelector);
+ // This function is a noop in the form's disabled state
+ const createMLJob = heartbeatIndices
+ ? () => dispatch(createMLJobAction.get({ monitorId: monitorId as string, heartbeatIndices }))
+ : () => null;
const { data: uptimeJobs } = useSelector(hasMLJobSelector);
@@ -108,7 +119,7 @@ export const MachineLearningFlyout: React.FC = ({ onClose }) => {
basePath,
{ to: dateRangeEnd, from: dateRangeStart },
false,
- error?.body?.message
+ error?.message || error?.body?.message
);
}
setIsCreatingJob(false);
@@ -130,9 +141,9 @@ export const MachineLearningFlyout: React.FC = ({ onClose }) => {
useEffect(() => {
if (hasExistingMLJob) {
setIsCreatingJob(true);
- dispatch(createMLJobAction.get({ monitorId: monitorId as string }));
+ dispatch(createMLJobAction.get({ monitorId: monitorId as string, heartbeatIndices }));
}
- }, [dispatch, hasExistingMLJob, monitorId]);
+ }, [dispatch, hasExistingMLJob, heartbeatIndices, monitorId]);
if (hasExistingMLJob) {
return null;
diff --git a/x-pack/legacy/plugins/uptime/public/lib/alert_types/__tests__/monitor_status.test.ts b/x-pack/legacy/plugins/uptime/public/lib/alert_types/__tests__/monitor_status.test.ts
index 34b330e9ca1b0..81402c00e484e 100644
--- a/x-pack/legacy/plugins/uptime/public/lib/alert_types/__tests__/monitor_status.test.ts
+++ b/x-pack/legacy/plugins/uptime/public/lib/alert_types/__tests__/monitor_status.test.ts
@@ -174,7 +174,7 @@ describe('monitor status alert type', () => {
{{context.downMonitorsWithGeo}}",
"iconClass": "uptimeApp",
"id": "xpack.uptime.alerts.monitorStatus",
- "name": "Uptime Monitor Status",
+ "name": "Uptime monitor status",
"validate": [Function],
}
`);
diff --git a/x-pack/legacy/plugins/uptime/public/lib/alert_types/monitor_status.tsx b/x-pack/legacy/plugins/uptime/public/lib/alert_types/monitor_status.tsx
index fde25ea30734f..d059274159c7f 100644
--- a/x-pack/legacy/plugins/uptime/public/lib/alert_types/monitor_status.tsx
+++ b/x-pack/legacy/plugins/uptime/public/lib/alert_types/monitor_status.tsx
@@ -61,7 +61,7 @@ export const initMonitorStatusAlertType: AlertTypeInitializer = ({
autocomplete,
}): AlertTypeModel => ({
id: 'xpack.uptime.alerts.monitorStatus',
- name: 'Uptime Monitor Status',
+ name: 'Uptime monitor status',
iconClass: 'uptimeApp',
alertParamsExpression: params => {
return ;
diff --git a/x-pack/legacy/plugins/uptime/public/state/actions/ml_anomaly.ts b/x-pack/legacy/plugins/uptime/public/state/actions/ml_anomaly.ts
index 9a8e4036f2cff..2e83490b71b54 100644
--- a/x-pack/legacy/plugins/uptime/public/state/actions/ml_anomaly.ts
+++ b/x-pack/legacy/plugins/uptime/public/state/actions/ml_anomaly.ts
@@ -8,7 +8,12 @@ import { createAction } from 'redux-actions';
import { createAsyncAction } from './utils';
import { PrivilegesResponse } from '../../../../../../plugins/ml/common/types/privileges';
import { AnomaliesTableRecord } from '../../../../../../plugins/ml/common/types/anomalies';
-import { CreateMLJobSuccess, DeleteJobResults, MonitorIdParam } from './types';
+import {
+ CreateMLJobSuccess,
+ DeleteJobResults,
+ MonitorIdParam,
+ HeartbeatIndicesParam,
+} from './types';
import { JobExistResult } from '../../../../../../plugins/ml/common/types/data_recognizer';
export const resetMLState = createAction('RESET_ML_STATE');
@@ -17,9 +22,10 @@ export const getExistingMLJobAction = createAsyncAction(
- 'CREATE_ML_JOB'
-);
+export const createMLJobAction = createAsyncAction<
+ MonitorIdParam & HeartbeatIndicesParam,
+ CreateMLJobSuccess | null
+>('CREATE_ML_JOB');
export const getMLCapabilitiesAction = createAsyncAction(
'GET_ML_CAPABILITIES'
diff --git a/x-pack/legacy/plugins/uptime/public/state/actions/types.ts b/x-pack/legacy/plugins/uptime/public/state/actions/types.ts
index 236b263414a26..41381afd31453 100644
--- a/x-pack/legacy/plugins/uptime/public/state/actions/types.ts
+++ b/x-pack/legacy/plugins/uptime/public/state/actions/types.ts
@@ -22,6 +22,10 @@ export interface MonitorIdParam {
monitorId: string;
}
+export interface HeartbeatIndicesParam {
+ heartbeatIndices: string;
+}
+
export interface QueryParams {
monitorId: string;
dateStart: string;
diff --git a/x-pack/legacy/plugins/uptime/public/state/api/ml_anomaly.ts b/x-pack/legacy/plugins/uptime/public/state/api/ml_anomaly.ts
index 80b97783769b5..bcd2582fe18b9 100644
--- a/x-pack/legacy/plugins/uptime/public/state/api/ml_anomaly.ts
+++ b/x-pack/legacy/plugins/uptime/public/state/api/ml_anomaly.ts
@@ -7,13 +7,18 @@
import moment from 'moment';
import { apiService } from './utils';
import { AnomalyRecords, AnomalyRecordsParams } from '../actions';
-import { API_URLS, INDEX_NAMES, ML_JOB_ID, ML_MODULE_ID } from '../../../common/constants';
+import { API_URLS, ML_JOB_ID, ML_MODULE_ID } from '../../../common/constants';
import { PrivilegesResponse } from '../../../../../../plugins/ml/common/types/privileges';
-import { CreateMLJobSuccess, DeleteJobResults, MonitorIdParam } from '../actions/types';
+import {
+ CreateMLJobSuccess,
+ DeleteJobResults,
+ MonitorIdParam,
+ HeartbeatIndicesParam,
+} from '../actions/types';
import { DataRecognizerConfigResponse } from '../../../../../../plugins/ml/common/types/modules';
import { JobExistResult } from '../../../../../../plugins/ml/common/types/data_recognizer';
-export const getMLJobId = (monitorId: string) => `${monitorId}_${ML_JOB_ID}`;
+export const getMLJobId = (monitorId: string) => `${monitorId}_${ML_JOB_ID}`.toLowerCase();
export const getMLCapabilities = async (): Promise => {
return await apiService.get(API_URLS.ML_CAPABILITIES);
@@ -25,23 +30,27 @@ export const getExistingJobs = async (): Promise => {
export const createMLJob = async ({
monitorId,
-}: MonitorIdParam): Promise => {
+ heartbeatIndices,
+}: MonitorIdParam & HeartbeatIndicesParam): Promise => {
const url = API_URLS.ML_SETUP_MODULE + ML_MODULE_ID;
+ // ML App doesn't support upper case characters in job name
+ const lowerCaseMonitorId = monitorId.toLowerCase();
+
const data = {
- prefix: `${monitorId}_`,
+ prefix: `${lowerCaseMonitorId}_`,
useDedicatedIndex: false,
startDatafeed: true,
start: moment()
.subtract(24, 'h')
.valueOf(),
- indexPatternName: INDEX_NAMES.HEARTBEAT,
+ indexPatternName: heartbeatIndices,
query: {
bool: {
filter: [
{
term: {
- 'monitor.id': monitorId,
+ 'monitor.id': lowerCaseMonitorId,
},
},
],
@@ -50,11 +59,17 @@ export const createMLJob = async ({
};
const response: DataRecognizerConfigResponse = await apiService.post(url, data);
- if (response?.jobs?.[0]?.id === getMLJobId(monitorId) && response?.jobs?.[0]?.success) {
- return {
- count: 1,
- jobId: response?.jobs?.[0]?.id,
- };
+ if (response?.jobs?.[0]?.id === getMLJobId(monitorId)) {
+ const jobResponse = response.jobs[0];
+ if (jobResponse.success) {
+ return {
+ count: 1,
+ jobId: jobResponse.id,
+ };
+ } else {
+ const { error } = jobResponse;
+ throw new Error(error?.msg);
+ }
} else {
return null;
}
diff --git a/x-pack/legacy/plugins/xpack_main/index.js b/x-pack/legacy/plugins/xpack_main/index.js
index 4cf32b971b57e..6ce457ffbec05 100644
--- a/x-pack/legacy/plugins/xpack_main/index.js
+++ b/x-pack/legacy/plugins/xpack_main/index.js
@@ -23,11 +23,6 @@ export const xpackMain = kibana => {
config(Joi) {
return Joi.object({
enabled: Joi.boolean().default(true),
- telemetry: Joi.object({
- config: Joi.string().default(),
- enabled: Joi.boolean().default(),
- url: Joi.string().default(),
- }).default(), // deprecated
}).default();
},
diff --git a/x-pack/plugins/advanced_ui_actions/public/custom_time_range_action.test.ts b/x-pack/plugins/advanced_ui_actions/public/custom_time_range_action.test.ts
index 91b829a020048..10860fe471a3b 100644
--- a/x-pack/plugins/advanced_ui_actions/public/custom_time_range_action.test.ts
+++ b/x-pack/plugins/advanced_ui_actions/public/custom_time_range_action.test.ts
@@ -10,9 +10,7 @@ import { skip } from 'rxjs/operators';
import * as Rx from 'rxjs';
import { mount } from 'enzyme';
-import { EmbeddableFactory } from '../../../../src/plugins/embeddable/public';
import { TimeRangeEmbeddable, TimeRangeContainer, TIME_RANGE_EMBEDDABLE } from './test_helpers';
-import { TimeRangeEmbeddableFactory } from './test_helpers/time_range_embeddable_factory';
import { CustomTimeRangeAction } from './custom_time_range_action';
/* eslint-disable */
import {
@@ -21,7 +19,6 @@ import {
/* eslint-enable */
import {
- HelloWorldEmbeddableFactory,
HelloWorldEmbeddable,
HELLO_WORLD_EMBEDDABLE,
} from '../../../../examples/embeddable_examples/public';
@@ -38,9 +35,6 @@ const createOpenModalMock = () => {
};
test('Custom time range action prevents embeddable from using container time', async done => {
- const embeddableFactories = new Map();
- embeddableFactories.set(TIME_RANGE_EMBEDDABLE, new TimeRangeEmbeddableFactory());
-
const container = new TimeRangeContainer(
{
timeRange: { from: 'now-15m', to: 'now' },
@@ -105,9 +99,6 @@ test('Custom time range action prevents embeddable from using container time', a
});
test('Removing custom time range action resets embeddable back to container time', async done => {
- const embeddableFactories = new Map();
- embeddableFactories.set(TIME_RANGE_EMBEDDABLE, new TimeRangeEmbeddableFactory());
-
const container = new TimeRangeContainer(
{
timeRange: { from: 'now-15m', to: 'now' },
@@ -182,9 +173,6 @@ test('Removing custom time range action resets embeddable back to container time
});
test('Cancelling custom time range action leaves state alone', async done => {
- const embeddableFactories = new Map();
- embeddableFactories.set(TIME_RANGE_EMBEDDABLE, new TimeRangeEmbeddableFactory());
-
const container = new TimeRangeContainer(
{
timeRange: { from: 'now-15m', to: 'now' },
@@ -244,8 +232,6 @@ test('Cancelling custom time range action leaves state alone', async done => {
});
test(`badge is compatible with embeddable that inherits from parent`, async () => {
- const embeddableFactories = new Map();
- embeddableFactories.set(TIME_RANGE_EMBEDDABLE, new TimeRangeEmbeddableFactory());
const container = new TimeRangeContainer(
{
timeRange: { from: 'now-15m', to: 'now' },
@@ -279,8 +265,6 @@ test(`badge is compatible with embeddable that inherits from parent`, async () =
// TODO: uncomment when https://github.com/elastic/kibana/issues/43271 is fixed.
// test('Embeddable that does not use time range in a container that has time range is incompatible', async () => {
-// const embeddableFactories = new Map();
-// embeddableFactories.set(HELLO_WORLD_EMBEDDABLE, new HelloWorldEmbeddableFactory());
// const container = new TimeRangeContainer(
// {
// timeRange: { from: 'now-15m', to: 'now' },
@@ -315,8 +299,6 @@ test(`badge is compatible with embeddable that inherits from parent`, async () =
// });
test('Attempting to execute on incompatible embeddable throws an error', async () => {
- const embeddableFactories = new Map();
- embeddableFactories.set(HELLO_WORLD_EMBEDDABLE, new HelloWorldEmbeddableFactory());
const container = new HelloWorldContainer(
{
panels: {
diff --git a/x-pack/plugins/advanced_ui_actions/public/custom_time_range_badge.test.ts b/x-pack/plugins/advanced_ui_actions/public/custom_time_range_badge.test.ts
index d2b9fa9ac1655..3bf763470f002 100644
--- a/x-pack/plugins/advanced_ui_actions/public/custom_time_range_badge.test.ts
+++ b/x-pack/plugins/advanced_ui_actions/public/custom_time_range_badge.test.ts
@@ -9,17 +9,12 @@ import { findTestSubject } from '@elastic/eui/lib/test';
import { skip } from 'rxjs/operators';
import * as Rx from 'rxjs';
import { mount } from 'enzyme';
-import { EmbeddableFactory } from '../../../../src/plugins/embeddable/public';
import { TimeRangeEmbeddable, TimeRangeContainer, TIME_RANGE_EMBEDDABLE } from './test_helpers';
-import { TimeRangeEmbeddableFactory } from './test_helpers/time_range_embeddable_factory';
import { CustomTimeRangeBadge } from './custom_time_range_badge';
import { ReactElement } from 'react';
import { nextTick } from 'test_utils/enzyme_helpers';
test('Removing custom time range from badge resets embeddable back to container time', async done => {
- const embeddableFactories = new Map();
- embeddableFactories.set(TIME_RANGE_EMBEDDABLE, new TimeRangeEmbeddableFactory());
-
const container = new TimeRangeContainer(
{
timeRange: { from: 'now-15m', to: 'now' },
@@ -79,8 +74,6 @@ test('Removing custom time range from badge resets embeddable back to container
});
test(`badge is not compatible with embeddable that inherits from parent`, async () => {
- const embeddableFactories = new Map();
- embeddableFactories.set(TIME_RANGE_EMBEDDABLE, new TimeRangeEmbeddableFactory());
const container = new TimeRangeContainer(
{
timeRange: { from: 'now-15m', to: 'now' },
@@ -113,8 +106,6 @@ test(`badge is not compatible with embeddable that inherits from parent`, async
});
test(`badge is compatible with embeddable that has custom time range`, async () => {
- const embeddableFactories = new Map();
- embeddableFactories.set(TIME_RANGE_EMBEDDABLE, new TimeRangeEmbeddableFactory());
const container = new TimeRangeContainer(
{
timeRange: { from: 'now-15m', to: 'now' },
@@ -148,8 +139,6 @@ test(`badge is compatible with embeddable that has custom time range`, async ()
});
test('Attempting to execute on incompatible embeddable throws an error', async () => {
- const embeddableFactories = new Map();
- embeddableFactories.set(TIME_RANGE_EMBEDDABLE, new TimeRangeEmbeddableFactory());
const container = new TimeRangeContainer(
{
timeRange: { from: 'now-15m', to: 'now' },
diff --git a/x-pack/plugins/advanced_ui_actions/public/test_helpers/time_range_embeddable_factory.ts b/x-pack/plugins/advanced_ui_actions/public/test_helpers/time_range_embeddable_factory.ts
index 311d3357476b9..e8d9451f9f2a6 100644
--- a/x-pack/plugins/advanced_ui_actions/public/test_helpers/time_range_embeddable_factory.ts
+++ b/x-pack/plugins/advanced_ui_actions/public/test_helpers/time_range_embeddable_factory.ts
@@ -7,7 +7,7 @@
import {
EmbeddableInput,
IContainer,
- EmbeddableFactory,
+ EmbeddableFactoryDefinition,
} from '../../../../../src/plugins/embeddable/public';
import { TimeRange } from '../../../../../src/plugins/data/public';
import { TIME_RANGE_EMBEDDABLE, TimeRangeEmbeddable } from './time_range_embeddable';
@@ -16,7 +16,8 @@ interface EmbeddableTimeRangeInput extends EmbeddableInput {
timeRange: TimeRange;
}
-export class TimeRangeEmbeddableFactory extends EmbeddableFactory {
+export class TimeRangeEmbeddableFactory
+ implements EmbeddableFactoryDefinition {
public readonly type = TIME_RANGE_EMBEDDABLE;
public async isEditable() {
diff --git a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/alert_type.test.ts b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/alert_type.test.ts
index 5c15c398dbdcd..315e4800d4c73 100644
--- a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/alert_type.test.ts
+++ b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/alert_type.test.ts
@@ -20,7 +20,7 @@ describe('alertType', () => {
it('alert type creation structure is the expected value', async () => {
expect(alertType.id).toBe('.index-threshold');
- expect(alertType.name).toBe('Index Threshold');
+ expect(alertType.name).toBe('Index threshold');
expect(alertType.actionGroups).toEqual([{ id: 'threshold met', name: 'Threshold Met' }]);
expect(alertType.actionVariables).toMatchInlineSnapshot(`
diff --git a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/alert_type.ts b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/alert_type.ts
index 6d27f8a99dd4b..4d79efc7c9478 100644
--- a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/alert_type.ts
+++ b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/alert_type.ts
@@ -22,7 +22,7 @@ export function getAlertType(service: Service): AlertType {
const { logger } = service;
const alertTypeName = i18n.translate('xpack.alertingBuiltins.indexThreshold.alertTypeTitle', {
- defaultMessage: 'Index Threshold',
+ defaultMessage: 'Index threshold',
});
const actionGroupName = i18n.translate(
diff --git a/x-pack/plugins/alerting_builtins/server/plugin.test.ts b/x-pack/plugins/alerting_builtins/server/plugin.test.ts
index 6bcf0379d5abe..f93041fa3c142 100644
--- a/x-pack/plugins/alerting_builtins/server/plugin.test.ts
+++ b/x-pack/plugins/alerting_builtins/server/plugin.test.ts
@@ -37,7 +37,7 @@ describe('AlertingBuiltins Plugin', () => {
},
],
"id": ".index-threshold",
- "name": "Index Threshold",
+ "name": "Index threshold",
}
`);
});
diff --git a/x-pack/plugins/apm/common/agent_configuration/runtime_types/duration_rt.test.ts b/x-pack/plugins/apm/common/agent_configuration/runtime_types/duration_rt.test.ts
index 6b81031542c34..a83ee9262cad6 100644
--- a/x-pack/plugins/apm/common/agent_configuration/runtime_types/duration_rt.test.ts
+++ b/x-pack/plugins/apm/common/agent_configuration/runtime_types/duration_rt.test.ts
@@ -25,7 +25,7 @@ describe('durationRt', () => {
});
describe('It should accept', () => {
- ['1s', '2m', '3h'].map(input => {
+ ['1000ms', '2s', '3m'].map(input => {
it(`${JSON.stringify(input)}`, () => {
expect(isRight(durationRt.decode(input))).toBe(true);
});
diff --git a/x-pack/plugins/apm/common/agent_configuration/runtime_types/duration_rt.ts b/x-pack/plugins/apm/common/agent_configuration/runtime_types/duration_rt.ts
index 99e6a57089dee..383fd69be9a78 100644
--- a/x-pack/plugins/apm/common/agent_configuration/runtime_types/duration_rt.ts
+++ b/x-pack/plugins/apm/common/agent_configuration/runtime_types/duration_rt.ts
@@ -8,7 +8,7 @@ import * as t from 'io-ts';
import { either } from 'fp-ts/lib/Either';
import { amountAndUnitToObject } from '../amount_and_unit';
-export const DURATION_UNITS = ['s', 'm', 'h'];
+export const DURATION_UNITS = ['ms', 's', 'm'];
export const durationRt = new t.Type(
'durationRt',
diff --git a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/__snapshots__/index.test.ts.snap b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/__snapshots__/index.test.ts.snap
index 0c585bec22f6c..81adf76ac4ce9 100644
--- a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/__snapshots__/index.test.ts.snap
+++ b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/__snapshots__/index.test.ts.snap
@@ -17,9 +17,9 @@ Array [
"key": "api_request_time",
"type": "duration",
"units": Array [
+ "ms",
"s",
"m",
- "h",
],
"validationError": "Please specify an integer and a unit",
"validationName": "durationRt",
@@ -82,9 +82,9 @@ Array [
"key": "profiling_inferred_spans_min_duration",
"type": "duration",
"units": Array [
+ "ms",
"s",
"m",
- "h",
],
"validationError": "Please specify an integer and a unit",
"validationName": "durationRt",
@@ -93,9 +93,9 @@ Array [
"key": "profiling_inferred_spans_sampling_interval",
"type": "duration",
"units": Array [
+ "ms",
"s",
"m",
- "h",
],
"validationError": "Please specify an integer and a unit",
"validationName": "durationRt",
@@ -109,9 +109,9 @@ Array [
"key": "server_timeout",
"type": "duration",
"units": Array [
+ "ms",
"s",
"m",
- "h",
],
"validationError": "Please specify an integer and a unit",
"validationName": "durationRt",
@@ -120,9 +120,9 @@ Array [
"key": "span_frames_min_duration",
"type": "duration",
"units": Array [
+ "ms",
"s",
"m",
- "h",
],
"validationError": "Please specify an integer and a unit",
"validationName": "durationRt",
@@ -137,9 +137,9 @@ Array [
"key": "stress_monitor_cpu_duration_threshold",
"type": "duration",
"units": Array [
+ "ms",
"s",
"m",
- "h",
],
"validationError": "Please specify an integer and a unit",
"validationName": "durationRt",
diff --git a/x-pack/plugins/apm/common/alert_types.ts b/x-pack/plugins/apm/common/alert_types.ts
index 51e1f88512965..8a342fab71e66 100644
--- a/x-pack/plugins/apm/common/alert_types.ts
+++ b/x-pack/plugins/apm/common/alert_types.ts
@@ -14,7 +14,7 @@ export enum AlertType {
export const ALERT_TYPES_CONFIG = {
[AlertType.ErrorRate]: {
name: i18n.translate('xpack.apm.errorRateAlert.name', {
- defaultMessage: 'Error rate threshold'
+ defaultMessage: 'Error rate'
}),
actionGroups: [
{
@@ -28,7 +28,7 @@ export const ALERT_TYPES_CONFIG = {
},
[AlertType.TransactionDuration]: {
name: i18n.translate('xpack.apm.transactionDurationAlert.name', {
- defaultMessage: 'Transaction duration threshold'
+ defaultMessage: 'Transaction duration'
}),
actionGroups: [
{
diff --git a/x-pack/plugins/endpoint/common/generate_data.ts b/x-pack/plugins/endpoint/common/generate_data.ts
index 75351bb3bf07d..430ba1d422b96 100644
--- a/x-pack/plugins/endpoint/common/generate_data.ts
+++ b/x-pack/plugins/endpoint/common/generate_data.ts
@@ -83,6 +83,11 @@ const OTHER_EVENT_CATEGORIES: EventInfo[] = [
];
interface HostInfo {
+ elastic: {
+ agent: {
+ id: string;
+ };
+ };
agent: {
version: string;
id: string;
@@ -116,6 +121,11 @@ export class EndpointDocGenerator {
version: this.randomVersion(),
id: this.seededUUIDv4(),
},
+ elastic: {
+ agent: {
+ id: this.seededUUIDv4(),
+ },
+ },
host: {
id: this.seededUUIDv4(),
hostname: this.randomHostname(),
diff --git a/x-pack/plugins/endpoint/common/types.ts b/x-pack/plugins/endpoint/common/types.ts
index b3eb518e35ae3..565f47e7a0d6f 100644
--- a/x-pack/plugins/endpoint/common/types.ts
+++ b/x-pack/plugins/endpoint/common/types.ts
@@ -257,6 +257,11 @@ export type HostMetadata = Immutable<{
event: {
created: number;
};
+ elastic: {
+ agent: {
+ id: string;
+ };
+ };
endpoint: {
policy: {
id: string;
diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/factory.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/factory.ts
index c8e038869efcd..601f8a6bdc2c1 100644
--- a/x-pack/plugins/endpoint/public/embeddables/resolver/factory.ts
+++ b/x-pack/plugins/endpoint/public/embeddables/resolver/factory.ts
@@ -6,13 +6,13 @@
import { i18n } from '@kbn/i18n';
import {
- EmbeddableFactory,
IContainer,
EmbeddableInput,
+ EmbeddableFactoryDefinition,
} from '../../../../../../src/plugins/embeddable/public';
import { ResolverEmbeddable } from './embeddable';
-export class ResolverEmbeddableFactory extends EmbeddableFactory {
+export class ResolverEmbeddableFactory implements EmbeddableFactoryDefinition {
public readonly type = 'resolver';
public async isEditable() {
diff --git a/x-pack/plugins/endpoint/server/test_data/all_metadata_data.json b/x-pack/plugins/endpoint/server/test_data/all_metadata_data.json
index 3c824185ec083..3c8486aa127ea 100644
--- a/x-pack/plugins/endpoint/server/test_data/all_metadata_data.json
+++ b/x-pack/plugins/endpoint/server/test_data/all_metadata_data.json
@@ -23,6 +23,11 @@
"event" : {
"created" : "2020-01-23T21:56:55.336Z"
},
+ "elastic": {
+ "agent": {
+ "id": "56a75650-3c8a-4e4f-ac17-6dd729c650e2"
+ }
+ },
"endpoint" : {
"policy" : {
"id" : "C2A9093E-E289-4C0A-AA44-8C32A414FA7A"
@@ -73,6 +78,11 @@
"event" : {
"created" : "2020-01-23T21:56:55.336Z"
},
+ "elastic": {
+ "agent": {
+ "id": "56a75650-3c8a-4e4f-ac17-6dd729c650e2"
+ }
+ },
"endpoint" : {
"policy" : {
"id" : "C2A9093E-E289-4C0A-AA44-8C32A414FA7A"
@@ -115,6 +125,11 @@
"event" : {
"created" : "2020-01-23T21:56:55.336Z"
},
+ "elastic": {
+ "agent": {
+ "id": "c2d84d8f-d355-40de-8b54-5d318d4d1312"
+ }
+ },
"endpoint" : {
"policy" : {
"id" : "C2A9093E-E289-4C0A-AA44-8C32A414FA7A"
@@ -165,6 +180,11 @@
"event" : {
"created" : "2020-01-23T21:56:55.336Z"
},
+ "elastic": {
+ "agent": {
+ "id": "c2d84d8f-d355-40de-8b54-5d318d4d1312"
+ }
+ },
"endpoint" : {
"policy" : {
"id" : "C2A9093E-E289-4C0A-AA44-8C32A414FA7A"
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/home.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/helpers/home.helpers.ts
index 7e3e1fba9c44a..397a78354f470 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/home.helpers.ts
+++ b/x-pack/plugins/index_management/__jest__/client_integration/helpers/home.helpers.ts
@@ -16,7 +16,7 @@ import {
import { IndexManagementHome } from '../../../public/application/sections/home'; // eslint-disable-line @kbn/eslint/no-restricted-paths
import { BASE_PATH } from '../../../common/constants';
import { indexManagementStore } from '../../../public/application/store'; // eslint-disable-line @kbn/eslint/no-restricted-paths
-import { Template } from '../../../common/types';
+import { TemplateDeserialized } from '../../../common';
import { WithAppDependencies, services } from './setup_environment';
const testBedConfig: TestBedConfig = {
@@ -36,10 +36,13 @@ export interface IdxMgmtHomeTestBed extends TestBed {
selectHomeTab: (tab: 'indicesTab' | 'templatesTab') => void;
selectDetailsTab: (tab: 'summary' | 'settings' | 'mappings' | 'aliases') => void;
clickReloadButton: () => void;
- clickTemplateAction: (name: Template['name'], action: 'edit' | 'clone' | 'delete') => void;
+ clickTemplateAction: (
+ name: TemplateDeserialized['name'],
+ action: 'edit' | 'clone' | 'delete'
+ ) => void;
clickTemplateAt: (index: number) => void;
clickCloseDetailsButton: () => void;
- clickActionMenu: (name: Template['name']) => void;
+ clickActionMenu: (name: TemplateDeserialized['name']) => void;
};
}
@@ -78,7 +81,7 @@ export const setup = async (): Promise => {
find('reloadButton').simulate('click');
};
- const clickActionMenu = async (templateName: Template['name']) => {
+ const clickActionMenu = async (templateName: TemplateDeserialized['name']) => {
const { component } = testBed;
// When a table has > 2 actions, EUI displays an overflow menu with an id "-actions"
@@ -87,7 +90,7 @@ export const setup = async (): Promise => {
};
const clickTemplateAction = (
- templateName: Template['name'],
+ templateName: TemplateDeserialized['name'],
action: 'edit' | 'clone' | 'delete'
) => {
const actions = ['edit', 'clone', 'delete'];
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/template_form.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/helpers/template_form.helpers.ts
index 9d4eb631a1c40..520b62083e7d3 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/template_form.helpers.ts
+++ b/x-pack/plugins/index_management/__jest__/client_integration/helpers/template_form.helpers.ts
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { TestBed, SetupFunc, UnwrapPromise } from '../../../../../test_utils';
-import { Template } from '../../../common/types';
+import { TemplateDeserialized } from '../../../common';
import { nextTick } from './index';
interface MappingField {
@@ -62,8 +62,8 @@ export const formSetup = async (initTestBed: SetupFunc) => {
indexPatterns,
order,
version,
- }: Partial = {}) => {
- const { form, find, component } = testBed;
+ }: Partial = {}) => {
+ const { form, find, waitFor } = testBed;
if (name) {
form.setInputValue('nameField.input', name);
@@ -88,12 +88,11 @@ export const formSetup = async (initTestBed: SetupFunc) => {
}
clickNextButton();
- await nextTick();
- component.update();
+ await waitFor('stepSettings');
};
const completeStepTwo = async (settings?: string) => {
- const { find, component } = testBed;
+ const { find, component, waitFor } = testBed;
if (settings) {
find('mockCodeEditor').simulate('change', {
@@ -104,42 +103,41 @@ export const formSetup = async (initTestBed: SetupFunc) => {
}
clickNextButton();
- await nextTick();
- component.update();
+ await waitFor('stepMappings');
};
const completeStepThree = async (mappingFields?: MappingField[]) => {
- const { component } = testBed;
+ const { waitFor } = testBed;
if (mappingFields) {
for (const field of mappingFields) {
const { name, type } = field;
await addMappingField(name, type);
}
- } else {
- await nextTick();
}
- await nextTick(50); // hooks updates cycles are tricky, adding some latency is needed
clickNextButton();
- await nextTick(50);
- component.update();
+ await waitFor('stepAliases');
};
- const completeStepFour = async (aliases?: string) => {
- const { find, component } = testBed;
+ const completeStepFour = async (aliases?: string, waitForNextStep = true) => {
+ const { find, component, waitFor } = testBed;
if (aliases) {
find('mockCodeEditor').simulate('change', {
jsonString: aliases,
}); // Using mocked EuiCodeEditor
- await nextTick(50);
+ await nextTick();
component.update();
}
clickNextButton();
- await nextTick(50);
- component.update();
+
+ if (waitForNextStep) {
+ await waitFor('summaryTab');
+ } else {
+ component.update();
+ }
};
const selectSummaryTab = (tab: 'summary' | 'request') => {
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/home.test.ts
index 9e8af02b74631..a987535e0c291 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/home.test.ts
+++ b/x-pack/plugins/index_management/__jest__/client_integration/home.test.ts
@@ -115,11 +115,13 @@ describe('', () => {
const template1 = fixtures.getTemplate({
name: `a${getRandomString()}`,
indexPatterns: ['template1Pattern1*', 'template1Pattern2'],
- settings: {
- index: {
- number_of_shards: '1',
- lifecycle: {
- name: 'my_ilm_policy',
+ template: {
+ settings: {
+ index: {
+ number_of_shards: '1',
+ lifecycle: {
+ name: 'my_ilm_policy',
+ },
},
},
},
@@ -302,7 +304,10 @@ describe('', () => {
const templateId = rows[0].columns[2].value;
- const { name: templateName } = template1;
+ const {
+ name: templateName,
+ _kbnMeta: { formatVersion },
+ } = template1;
await actions.clickTemplateAction(templateName, 'delete');
const modal = document.body.querySelector(
@@ -327,8 +332,11 @@ describe('', () => {
const latestRequest = server.requests[server.requests.length - 1];
- expect(latestRequest.method).toBe('DELETE');
- expect(latestRequest.url).toBe(`${API_BASE_PATH}/templates/${template1.name}`);
+ expect(latestRequest.method).toBe('POST');
+ expect(latestRequest.url).toBe(`${API_BASE_PATH}/delete-templates`);
+ expect(JSON.parse(JSON.parse(latestRequest.requestBody).body)).toEqual({
+ templates: [{ name: template1.name, formatVersion }],
+ });
});
});
@@ -396,24 +404,26 @@ describe('', () => {
const template = fixtures.getTemplate({
name: `a${getRandomString()}`,
indexPatterns: ['template1Pattern1*', 'template1Pattern2'],
- settings: {
- index: {
- number_of_shards: '1',
- },
- },
- mappings: {
- _source: {
- enabled: false,
+ template: {
+ settings: {
+ index: {
+ number_of_shards: '1',
+ },
},
- properties: {
- created_at: {
- type: 'date',
- format: 'EEE MMM dd HH:mm:ss Z yyyy',
+ mappings: {
+ _source: {
+ enabled: false,
+ },
+ properties: {
+ created_at: {
+ type: 'date',
+ format: 'EEE MMM dd HH:mm:ss Z yyyy',
+ },
},
},
- },
- aliases: {
- alias1: {},
+ aliases: {
+ alias1: {},
+ },
},
});
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/template_clone.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/template_clone.test.tsx
index 5d895c8e98624..17e19bf881dee 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/template_clone.test.tsx
+++ b/x-pack/plugins/index_management/__jest__/client_integration/template_clone.test.tsx
@@ -54,21 +54,17 @@ describe('', () => {
const templateToClone = getTemplate({
name: TEMPLATE_NAME,
indexPatterns: ['indexPattern1'],
- mappings: {
- ...MAPPINGS,
- _meta: {},
- _source: {},
+ template: {
+ mappings: MAPPINGS,
},
});
beforeEach(async () => {
httpRequestsMockHelpers.setLoadTemplateResponse(templateToClone);
- testBed = await setup();
-
await act(async () => {
- await nextTick();
- testBed.component.update();
+ testBed = await setup();
+ await testBed.waitFor('templateForm');
});
});
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/template_create.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/template_create.test.tsx
index 981067c09f8aa..ad8e8c22a87fa 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/template_create.test.tsx
+++ b/x-pack/plugins/index_management/__jest__/client_integration/template_create.test.tsx
@@ -6,6 +6,7 @@
import React from 'react';
import { act } from 'react-dom/test-utils';
+import { DEFAULT_INDEX_TEMPLATE_VERSION_FORMAT } from '../../common';
import { setupEnvironment, pageHelpers, nextTick } from './helpers';
import { TemplateFormTestBed } from './helpers/template_form.helpers';
import {
@@ -71,6 +72,7 @@ describe('', () => {
beforeEach(async () => {
await act(async () => {
testBed = await setup();
+ await testBed.waitFor('templateForm');
});
});
@@ -100,6 +102,7 @@ describe('', () => {
beforeEach(async () => {
await act(async () => {
testBed = await setup();
+ await testBed.waitFor('templateForm');
});
});
@@ -209,7 +212,7 @@ describe('', () => {
await act(async () => {
// Complete step 4 (aliases) with invalid json
- await actions.completeStepFour('{ invalidJsonString ');
+ await actions.completeStepFour('{ invalidJsonString ', false);
});
expect(form.getErrorsMessages()).toContain('Invalid JSON format.');
@@ -221,6 +224,7 @@ describe('', () => {
beforeEach(async () => {
await act(async () => {
testBed = await setup();
+ await testBed.waitFor('templateForm');
const { actions } = testBed;
@@ -275,6 +279,7 @@ describe('', () => {
it('should render a warning message if a wildcard is used as an index pattern', async () => {
await act(async () => {
testBed = await setup();
+ await testBed.waitFor('templateForm');
const { actions } = testBed;
// Complete step 1 (logistics)
@@ -308,6 +313,7 @@ describe('', () => {
await act(async () => {
testBed = await setup();
+ await testBed.waitFor('templateForm');
const { actions } = testBed;
// Complete step 1 (logistics)
@@ -323,7 +329,6 @@ describe('', () => {
await actions.completeStepThree(MAPPING_FIELDS);
// Complete step 4 (aliases)
- await nextTick(100);
await actions.completeStepFour(JSON.stringify(ALIASES));
});
});
@@ -338,29 +343,34 @@ describe('', () => {
const latestRequest = server.requests[server.requests.length - 1];
- const expected = JSON.stringify({
+ const expected = {
isManaged: false,
name: TEMPLATE_NAME,
indexPatterns: DEFAULT_INDEX_PATTERNS,
- settings: SETTINGS,
- mappings: {
- ...MAPPINGS,
- properties: {
- [BOOLEAN_MAPPING_FIELD.name]: {
- type: BOOLEAN_MAPPING_FIELD.type,
- },
- [TEXT_MAPPING_FIELD.name]: {
- type: TEXT_MAPPING_FIELD.type,
- },
- [KEYWORD_MAPPING_FIELD.name]: {
- type: KEYWORD_MAPPING_FIELD.type,
+ template: {
+ settings: SETTINGS,
+ mappings: {
+ ...MAPPINGS,
+ properties: {
+ [BOOLEAN_MAPPING_FIELD.name]: {
+ type: BOOLEAN_MAPPING_FIELD.type,
+ },
+ [TEXT_MAPPING_FIELD.name]: {
+ type: TEXT_MAPPING_FIELD.type,
+ },
+ [KEYWORD_MAPPING_FIELD.name]: {
+ type: KEYWORD_MAPPING_FIELD.type,
+ },
},
},
+ aliases: ALIASES,
},
- aliases: ALIASES,
- });
+ _kbnMeta: {
+ formatVersion: DEFAULT_INDEX_TEMPLATE_VERSION_FORMAT,
+ },
+ };
- expect(JSON.parse(latestRequest.requestBody).body).toEqual(expected);
+ expect(JSON.parse(JSON.parse(latestRequest.requestBody).body)).toEqual(expected);
});
it('should surface the API errors from the put HTTP request', async () => {
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/template_edit.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/template_edit.test.tsx
index 537b0d8ef4156..5b10ff226022d 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/template_edit.test.tsx
+++ b/x-pack/plugins/index_management/__jest__/client_integration/template_edit.test.tsx
@@ -99,17 +99,17 @@ describe('', () => {
const templateToEdit = fixtures.getTemplate({
name: TEMPLATE_NAME,
indexPatterns: ['indexPattern1'],
- mappings: MAPPING,
+ template: {
+ mappings: MAPPING,
+ },
});
beforeEach(async () => {
httpRequestsMockHelpers.setLoadTemplateResponse(templateToEdit);
- testBed = await setup();
-
await act(async () => {
- await nextTick();
- testBed.component.update();
+ testBed = await setup();
+ await testBed.waitFor('templateForm');
});
});
@@ -128,10 +128,9 @@ describe('', () => {
expect(nameInput.props().disabled).toEqual(true);
});
- // TODO: Flakey test
- describe.skip('form payload', () => {
+ describe('form payload', () => {
beforeEach(async () => {
- const { actions, component, find, form } = testBed;
+ const { actions } = testBed;
await act(async () => {
// Complete step 1 (logistics)
@@ -141,20 +140,32 @@ describe('', () => {
// Step 2 (index settings)
await actions.completeStepTwo(JSON.stringify(SETTINGS));
+ });
+ });
+
+ it('should send the correct payload with changed values', async () => {
+ const { actions, component, find, form } = testBed;
- // Step 3 (mappings)
- // Select the first field to edit
- actions.clickEditButtonAtField(0);
+ await act(async () => {
+ // Make some changes to the mappings (step 3)
+
+ actions.clickEditButtonAtField(0); // Select the first field to edit
await nextTick();
component.update();
- // verify edit field flyout
- expect(find('mappingsEditorFieldEdit').length).toEqual(1);
- // change field name
+ });
+
+ // verify edit field flyout
+ expect(find('mappingsEditorFieldEdit').length).toEqual(1);
+
+ await act(async () => {
+ // change the field name
form.setInputValue('nameParameterInput', UPDATED_MAPPING_TEXT_FIELD_NAME);
+
// Save changes
actions.clickEditFieldUpdateButton();
await nextTick();
component.update();
+
// Proceed to the next step
actions.clickNextButton();
await nextTick(50);
@@ -162,19 +173,13 @@ describe('', () => {
// Step 4 (aliases)
await actions.completeStepFour(JSON.stringify(ALIASES));
- });
- });
- it('should send the correct payload with changed values', async () => {
- const { actions } = testBed;
-
- await act(async () => {
+ // Submit the form
actions.clickSubmitButton();
await nextTick();
});
const latestRequest = server.requests[server.requests.length - 1];
-
const { version, order } = templateToEdit;
const expected = {
@@ -182,27 +187,31 @@ describe('', () => {
version,
order,
indexPatterns: UPDATED_INDEX_PATTERN,
- mappings: {
- ...MAPPING,
- _meta: {},
- _source: {},
- properties: {
- [UPDATED_MAPPING_TEXT_FIELD_NAME]: {
- type: 'text',
- store: false,
- index: true,
- fielddata: false,
- eager_global_ordinals: false,
- index_phrases: false,
- norms: true,
- index_options: 'positions',
+ template: {
+ mappings: {
+ ...MAPPING,
+ properties: {
+ [UPDATED_MAPPING_TEXT_FIELD_NAME]: {
+ type: 'text',
+ store: false,
+ index: true,
+ fielddata: false,
+ eager_global_ordinals: false,
+ index_phrases: false,
+ norms: true,
+ index_options: 'positions',
+ },
},
},
+ settings: SETTINGS,
+ aliases: ALIASES,
},
isManaged: false,
- settings: SETTINGS,
- aliases: ALIASES,
+ _kbnMeta: {
+ formatVersion: templateToEdit._kbnMeta.formatVersion,
+ },
};
+
expect(JSON.parse(JSON.parse(latestRequest.requestBody).body)).toEqual(expected);
});
});
diff --git a/x-pack/plugins/index_management/common/constants/index.ts b/x-pack/plugins/index_management/common/constants/index.ts
index d1700f0e611c0..966e2e8e64838 100644
--- a/x-pack/plugins/index_management/common/constants/index.ts
+++ b/x-pack/plugins/index_management/common/constants/index.ts
@@ -9,6 +9,7 @@ export { BASE_PATH } from './base_path';
export { API_BASE_PATH } from './api_base_path';
export { INVALID_INDEX_PATTERN_CHARS, INVALID_TEMPLATE_NAME_CHARS } from './invalid_characters';
export * from './index_statuses';
+export { DEFAULT_INDEX_TEMPLATE_VERSION_FORMAT } from './index_templates';
export {
UIM_APP_NAME,
diff --git a/x-pack/plugins/index_management/common/constants/index_templates.ts b/x-pack/plugins/index_management/common/constants/index_templates.ts
new file mode 100644
index 0000000000000..788e96ee895ed
--- /dev/null
+++ b/x-pack/plugins/index_management/common/constants/index_templates.ts
@@ -0,0 +1,12 @@
+/*
+ * 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.
+ */
+
+/**
+ * Up until the end of the 8.x release cycle we need to support both
+ * V1 and V2 index template formats. This constant keeps track of whether
+ * we create V1 or V2 index template format in the UI.
+ */
+export const DEFAULT_INDEX_TEMPLATE_VERSION_FORMAT = 1;
diff --git a/x-pack/plugins/index_management/common/index.ts b/x-pack/plugins/index_management/common/index.ts
index 0cc4ba79711ce..459eda7552c85 100644
--- a/x-pack/plugins/index_management/common/index.ts
+++ b/x-pack/plugins/index_management/common/index.ts
@@ -4,4 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export { PLUGIN, API_BASE_PATH } from './constants';
+export { PLUGIN, API_BASE_PATH, DEFAULT_INDEX_TEMPLATE_VERSION_FORMAT } from './constants';
+
+export { getTemplateParameter } from './lib';
+
+export * from './types';
diff --git a/x-pack/plugins/index_management/common/lib/index.ts b/x-pack/plugins/index_management/common/lib/index.ts
index 83b22d8d72e92..33f7fbe45182e 100644
--- a/x-pack/plugins/index_management/common/lib/index.ts
+++ b/x-pack/plugins/index_management/common/lib/index.ts
@@ -5,6 +5,8 @@
*/
export {
deserializeTemplateList,
- deserializeTemplate,
- serializeTemplate,
+ deserializeV1Template,
+ serializeV1Template,
} from './template_serialization';
+
+export { getTemplateParameter } from './utils';
diff --git a/x-pack/plugins/index_management/common/lib/template_serialization.ts b/x-pack/plugins/index_management/common/lib/template_serialization.ts
index b7d3410c775d4..33a83d1e9335b 100644
--- a/x-pack/plugins/index_management/common/lib/template_serialization.ts
+++ b/x-pack/plugins/index_management/common/lib/template_serialization.ts
@@ -3,46 +3,25 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
-import { Template, TemplateEs, TemplateListItem } from '../types';
+import {
+ TemplateDeserialized,
+ TemplateV1Serialized,
+ TemplateV2Serialized,
+ TemplateListItem,
+} from '../types';
const hasEntries = (data: object = {}) => Object.entries(data).length > 0;
-export function deserializeTemplateList(
- indexTemplatesByName: any,
- managedTemplatePrefix?: string
-): TemplateListItem[] {
- const indexTemplateNames: string[] = Object.keys(indexTemplatesByName);
-
- const deserializedTemplates: TemplateListItem[] = indexTemplateNames.map((name: string) => {
- const {
- version,
- order,
- index_patterns: indexPatterns = [],
- settings = {},
- aliases = {},
- mappings = {},
- } = indexTemplatesByName[name];
-
- return {
- name,
- version,
- order,
- indexPatterns: indexPatterns.sort(),
- hasSettings: hasEntries(settings),
- hasAliases: hasEntries(aliases),
- hasMappings: hasEntries(mappings),
- ilmPolicy: settings && settings.index && settings.index.lifecycle,
- isManaged: Boolean(managedTemplatePrefix && name.startsWith(managedTemplatePrefix)),
- };
- });
-
- return deserializedTemplates;
-}
-
-export function serializeTemplate(template: Template): TemplateEs {
- const { name, version, order, indexPatterns, settings, aliases, mappings } = template;
+export function serializeV1Template(template: TemplateDeserialized): TemplateV1Serialized {
+ const {
+ name,
+ version,
+ order,
+ indexPatterns,
+ template: { settings, aliases, mappings } = {} as TemplateDeserialized['template'],
+ } = template;
- const serializedTemplate: TemplateEs = {
+ const serializedTemplate: TemplateV1Serialized = {
name,
version,
order,
@@ -55,31 +34,88 @@ export function serializeTemplate(template: Template): TemplateEs {
return serializedTemplate;
}
-export function deserializeTemplate(
- templateEs: TemplateEs,
+export function serializeV2Template(template: TemplateDeserialized): TemplateV2Serialized {
+ const { aliases, mappings, settings, ...templateV1serialized } = serializeV1Template(template);
+
+ return {
+ ...templateV1serialized,
+ template: {
+ aliases,
+ mappings,
+ settings,
+ },
+ priority: template.priority,
+ composed_of: template.composedOf,
+ };
+}
+
+export function deserializeV2Template(
+ templateEs: TemplateV2Serialized,
managedTemplatePrefix?: string
-): Template {
+): TemplateDeserialized {
const {
name,
version,
order,
index_patterns: indexPatterns,
- settings,
- aliases,
- mappings,
+ template,
+ priority,
+ composed_of: composedOf,
} = templateEs;
+ const { settings } = template;
- const deserializedTemplate: Template = {
+ const deserializedTemplate: TemplateDeserialized = {
name,
version,
order,
indexPatterns: indexPatterns.sort(),
- settings,
- aliases,
- mappings,
+ template,
ilmPolicy: settings && settings.index && settings.index.lifecycle,
isManaged: Boolean(managedTemplatePrefix && name.startsWith(managedTemplatePrefix)),
+ priority,
+ composedOf,
+ _kbnMeta: {
+ formatVersion: 2,
+ },
};
return deserializedTemplate;
}
+
+export function deserializeV1Template(
+ templateEs: TemplateV1Serialized,
+ managedTemplatePrefix?: string
+): TemplateDeserialized {
+ const { settings, aliases, mappings, ...rest } = templateEs;
+
+ const deserializedTemplateV2 = deserializeV2Template(
+ { ...rest, template: { aliases, settings, mappings } },
+ managedTemplatePrefix
+ );
+
+ return {
+ ...deserializedTemplateV2,
+ _kbnMeta: {
+ formatVersion: 1,
+ },
+ };
+}
+
+export function deserializeTemplateList(
+ indexTemplatesByName: { [key: string]: Omit },
+ managedTemplatePrefix?: string
+): TemplateListItem[] {
+ return Object.entries(indexTemplatesByName).map(([name, templateSerialized]) => {
+ const {
+ template: { mappings, settings, aliases },
+ ...deserializedTemplate
+ } = deserializeV1Template({ name, ...templateSerialized }, managedTemplatePrefix);
+
+ return {
+ ...deserializedTemplate,
+ hasSettings: hasEntries(settings),
+ hasAliases: hasEntries(aliases),
+ hasMappings: hasEntries(mappings),
+ };
+ });
+}
diff --git a/x-pack/plugins/index_management/common/lib/utils.test.ts b/x-pack/plugins/index_management/common/lib/utils.test.ts
new file mode 100644
index 0000000000000..221d1b009cede
--- /dev/null
+++ b/x-pack/plugins/index_management/common/lib/utils.test.ts
@@ -0,0 +1,35 @@
+/*
+ * 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 { TemplateV1Serialized, TemplateV2Serialized } from '../types';
+import { getTemplateVersion } from './utils';
+
+describe('utils', () => {
+ describe('getTemplateVersion', () => {
+ test('should detect v1 template', () => {
+ const template = {
+ name: 'my_template',
+ index_patterns: ['logs*'],
+ mappings: {
+ properties: {},
+ },
+ };
+ expect(getTemplateVersion(template as TemplateV1Serialized)).toBe(1);
+ });
+
+ test('should detect v2 template', () => {
+ const template = {
+ name: 'my_template',
+ index_patterns: ['logs*'],
+ template: {
+ mappings: {
+ properties: {},
+ },
+ },
+ };
+ expect(getTemplateVersion(template as TemplateV2Serialized)).toBe(2);
+ });
+ });
+});
diff --git a/x-pack/plugins/index_management/common/lib/utils.ts b/x-pack/plugins/index_management/common/lib/utils.ts
new file mode 100644
index 0000000000000..eee35dc1ab467
--- /dev/null
+++ b/x-pack/plugins/index_management/common/lib/utils.ts
@@ -0,0 +1,29 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { TemplateDeserialized, TemplateV1Serialized, TemplateV2Serialized } from '../types';
+
+/**
+ * Helper to get the format version of an index template.
+ * v1 will be supported up until 9.x but marked as deprecated from 7.8
+ * v2 will be supported from 7.8
+ */
+export const getTemplateVersion = (
+ template: TemplateDeserialized | TemplateV1Serialized | TemplateV2Serialized
+): 1 | 2 => {
+ return {}.hasOwnProperty.call(template, 'template') ? 2 : 1;
+};
+
+export const getTemplateParameter = (
+ template: TemplateV1Serialized | TemplateV2Serialized,
+ setting: 'aliases' | 'settings' | 'mappings'
+) => {
+ const formatVersion = getTemplateVersion(template);
+
+ return formatVersion === 1
+ ? (template as TemplateV1Serialized)[setting]
+ : (template as TemplateV2Serialized).template[setting];
+};
diff --git a/x-pack/legacy/plugins/uptime/common/constants/index_names.ts b/x-pack/plugins/index_management/common/types/aliases.ts
similarity index 79%
rename from x-pack/legacy/plugins/uptime/common/constants/index_names.ts
rename to x-pack/plugins/index_management/common/types/aliases.ts
index 9f33d280a1268..76aae8585c065 100644
--- a/x-pack/legacy/plugins/uptime/common/constants/index_names.ts
+++ b/x-pack/plugins/index_management/common/types/aliases.ts
@@ -4,6 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export const INDEX_NAMES = {
- HEARTBEAT: 'heartbeat-8*',
-};
+export interface Aliases {
+ [key: string]: any;
+}
diff --git a/x-pack/plugins/index_management/common/types/index.ts b/x-pack/plugins/index_management/common/types/index.ts
index 922cb63b70da3..b467f020978a5 100644
--- a/x-pack/plugins/index_management/common/types/index.ts
+++ b/x-pack/plugins/index_management/common/types/index.ts
@@ -4,4 +4,10 @@
* you may not use this file except in compliance with the Elastic License.
*/
+export * from './aliases';
+
+export * from './indices';
+
+export * from './mappings';
+
export * from './templates';
diff --git a/x-pack/plugins/index_management/common/types/indices.ts b/x-pack/plugins/index_management/common/types/indices.ts
new file mode 100644
index 0000000000000..ecf5ba21fe60c
--- /dev/null
+++ b/x-pack/plugins/index_management/common/types/indices.ts
@@ -0,0 +1,43 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+interface IndexModule {
+ number_of_shards: number | string;
+ codec: string;
+ routing_partition_size: number;
+ load_fixed_bitset_filters_eagerly: boolean;
+ shard: {
+ check_on_startup: boolean | 'checksum';
+ };
+ number_of_replicas: number;
+ auto_expand_replicas: false | string;
+ lifecycle: LifecycleModule;
+}
+
+interface AnalysisModule {
+ analyzer: {
+ [key: string]: {
+ type: string;
+ tokenizer: string;
+ char_filter?: string[];
+ filter?: string[];
+ position_increment_gap?: number;
+ };
+ };
+}
+
+interface LifecycleModule {
+ name: string;
+ rollover_alias?: string;
+ parse_origination_date?: boolean;
+ origination_date?: number;
+}
+
+export interface IndexSettings {
+ index?: Partial;
+ analysis?: AnalysisModule;
+ [key: string]: any;
+}
diff --git a/x-pack/plugins/index_management/common/types/mappings.ts b/x-pack/plugins/index_management/common/types/mappings.ts
new file mode 100644
index 0000000000000..0bd3e38ed07a5
--- /dev/null
+++ b/x-pack/plugins/index_management/common/types/mappings.ts
@@ -0,0 +1,11 @@
+/*
+ * 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.
+ */
+
+// TODO: Move mappings type from Mappings editor here
+
+export interface Mappings {
+ [key: string]: any;
+}
diff --git a/x-pack/plugins/index_management/common/types/templates.ts b/x-pack/plugins/index_management/common/types/templates.ts
index e31c10a775156..c37088982f207 100644
--- a/x-pack/plugins/index_management/common/types/templates.ts
+++ b/x-pack/plugins/index_management/common/types/templates.ts
@@ -4,6 +4,39 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import { IndexSettings } from './indices';
+import { Aliases } from './aliases';
+import { Mappings } from './mappings';
+
+// Template serialized (from Elasticsearch)
+interface TemplateBaseSerialized {
+ name: string;
+ index_patterns: string[];
+ version?: number;
+ order?: number;
+}
+
+export interface TemplateV1Serialized extends TemplateBaseSerialized {
+ settings?: IndexSettings;
+ aliases?: Aliases;
+ mappings?: Mappings;
+}
+
+export interface TemplateV2Serialized extends TemplateBaseSerialized {
+ template: {
+ settings?: IndexSettings;
+ aliases?: Aliases;
+ mappings?: Mappings;
+ };
+ priority?: number;
+ composed_of?: string[];
+}
+
+/**
+ * Interface for the template list in our UI table
+ * we don't include the mappings, settings and aliases
+ * to reduce the payload size sent back to the client.
+ */
export interface TemplateListItem {
name: string;
indexPatterns: string[];
@@ -16,37 +49,35 @@ export interface TemplateListItem {
name: string;
};
isManaged: boolean;
+ _kbnMeta: {
+ formatVersion: IndexTemplateFormatVersion;
+ };
}
-export interface Template {
+
+/**
+ * TemplateDeserialized falls back to index template V2 format
+ * The UI will only be dealing with this interface, conversion from and to V1 format
+ * is done server side.
+ */
+export interface TemplateDeserialized {
name: string;
indexPatterns: string[];
+ isManaged: boolean;
+ template: {
+ settings?: IndexSettings;
+ aliases?: Aliases;
+ mappings?: Mappings;
+ };
+ _kbnMeta: {
+ formatVersion: IndexTemplateFormatVersion;
+ };
version?: number;
+ priority?: number;
order?: number;
- settings?: object;
- aliases?: object;
- mappings?: object;
ilmPolicy?: {
name: string;
};
- isManaged: boolean;
+ composedOf?: string[];
}
-export interface TemplateEs {
- name: string;
- index_patterns: string[];
- version?: number;
- order?: number;
- settings?: {
- [key: string]: any;
- index?: {
- [key: string]: any;
- lifecycle?: {
- name: string;
- };
- };
- };
- aliases?: {
- [key: string]: any;
- };
- mappings?: object;
-}
+export type IndexTemplateFormatVersion = 1 | 2;
diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/load_mappings/load_mappings_provider.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/load_mappings/load_mappings_provider.test.tsx
index b66a42fc50ef4..da4b8e6f6eef2 100644
--- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/load_mappings/load_mappings_provider.test.tsx
+++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/load_mappings/load_mappings_provider.test.tsx
@@ -38,22 +38,20 @@ const setup = (props: any) =>
defaultProps: props,
})();
-const openModalWithJsonContent = ({ find, component }: TestBed) => async (json: any) => {
- find('load-json-button').simulate('click');
- component.update();
-
+const openModalWithJsonContent = ({ find, waitFor }: TestBed) => async (json: any) => {
// Set the mappings to load
- // @ts-ignore
await act(async () => {
+ find('load-json-button').simulate('click');
+ await waitFor('mockCodeEditor');
+
find('mockCodeEditor').simulate('change', {
jsonString: JSON.stringify(json),
});
- await nextTick(300); // There is a debounce in the JsonEditor that we need to wait for
+ await nextTick(500); // There is a debounce in the JsonEditor that we need to wait for
});
};
-// FLAKY: https://github.com/elastic/kibana/issues/59030
-describe.skip('', () => {
+describe('', () => {
test('it should forward valid mapping definition', async () => {
const mappingsToLoad = {
properties: {
diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_state.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_state.tsx
index 247bd183baddf..a9d26b953b96e 100644
--- a/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_state.tsx
+++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_state.tsx
@@ -111,13 +111,14 @@ export const MappingsState = React.memo(({ children, onUpdate, defaultValue }: P
let nextState = state;
if (
+ state.fieldForm &&
state.documentFields.status === 'creatingField' &&
isValid &&
!bypassFieldFormValidation
) {
// If the form field is valid and we are creating a new field that has some data
// we automatically add the field to our state.
- const fieldFormData = state.fieldForm!.data.format() as Field;
+ const fieldFormData = state.fieldForm.data.format() as Field;
if (Object.keys(fieldFormData).length !== 0) {
nextState = addFieldToState(fieldFormData, state);
dispatch({ type: 'field.add', value: fieldFormData });
diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/reducer.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/reducer.ts
index 1e6733b1632d7..2f30363c1ff58 100644
--- a/x-pack/plugins/index_management/public/application/components/mappings_editor/reducer.ts
+++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/reducer.ts
@@ -33,11 +33,21 @@ export interface MappingsConfiguration {
}
export interface MappingsTemplates {
- dynamic_templates: Template[];
+ dynamic_templates: DynamicTemplate[];
}
-interface Template {
- [key: string]: any;
+interface DynamicTemplate {
+ [key: string]: {
+ mapping: {
+ [key: string]: any;
+ };
+ match_mapping_type?: string;
+ match?: string;
+ unmatch?: string;
+ match_pattern?: string;
+ path_match?: string;
+ path_unmatch?: string;
+ };
}
export interface MappingsFields {
diff --git a/x-pack/plugins/index_management/public/application/components/template_delete_modal.tsx b/x-pack/plugins/index_management/public/application/components/template_delete_modal.tsx
index 4e36643dbe117..b80e51d8d139f 100644
--- a/x-pack/plugins/index_management/public/application/components/template_delete_modal.tsx
+++ b/x-pack/plugins/index_management/public/application/components/template_delete_modal.tsx
@@ -4,28 +4,27 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import React, { Fragment, useState } from 'react';
import { EuiConfirmModal, EuiOverlayMask, EuiCallOut, EuiCheckbox, EuiBadge } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
-import React, { Fragment, useState } from 'react';
+
+import { IndexTemplateFormatVersion } from '../../../common';
import { deleteTemplates } from '../services/api';
import { notificationService } from '../services/notification';
-import { Template } from '../../../common/types';
export const TemplateDeleteModal = ({
templatesToDelete,
callback,
}: {
- templatesToDelete: Array;
+ templatesToDelete: Array<{ name: string; formatVersion: IndexTemplateFormatVersion }>;
callback: (data?: { hasDeletedTemplates: boolean }) => void;
}) => {
const [isDeleteConfirmed, setIsDeleteConfirmed] = useState(false);
const numTemplatesToDelete = templatesToDelete.length;
- const hasSystemTemplate = Boolean(
- templatesToDelete.find(templateName => templateName.startsWith('.'))
- );
+ const hasSystemTemplate = Boolean(templatesToDelete.find(({ name }) => name.startsWith('.')));
const handleDeleteTemplates = () => {
deleteTemplates(templatesToDelete).then(({ data: { templatesDeleted, errors }, error }) => {
@@ -38,7 +37,7 @@ export const TemplateDeleteModal = ({
'xpack.idxMgmt.deleteTemplatesModal.successDeleteSingleNotificationMessageText',
{
defaultMessage: "Deleted template '{templateName}'",
- values: { templateName: templatesToDelete[0] },
+ values: { templateName: templatesToDelete[0].name },
}
)
: i18n.translate(
@@ -120,10 +119,10 @@ export const TemplateDeleteModal = ({
- {templatesToDelete.map(template => (
- -
- {template}
- {template.startsWith('.') ? (
+ {templatesToDelete.map(({ name }) => (
+
-
+ {name}
+ {name.startsWith('.') ? (
{' '}
diff --git a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_aliases.tsx b/x-pack/plugins/index_management/public/application/components/template_form/steps/step_aliases.tsx
index 8628b6d8b8d74..50a32787c7a04 100644
--- a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_aliases.tsx
+++ b/x-pack/plugins/index_management/public/application/components/template_form/steps/step_aliases.tsx
@@ -29,7 +29,7 @@ export const StepAliases: React.FunctionComponent = ({
}) => {
const { content, setContent, error } = useJsonStep({
prop: 'aliases',
- defaultValue: template.aliases,
+ defaultValue: template?.template.aliases,
setDataGetter,
onStepValidityChange,
});
diff --git a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_mappings.tsx b/x-pack/plugins/index_management/public/application/components/template_form/steps/step_mappings.tsx
index d51d512429ea4..cf9b57dcbcb14 100644
--- a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_mappings.tsx
+++ b/x-pack/plugins/index_management/public/application/components/template_form/steps/step_mappings.tsx
@@ -15,7 +15,7 @@ import {
EuiText,
} from '@elastic/eui';
import { documentationService } from '../../../services/documentation';
-import { StepProps } from '../types';
+import { StepProps, DataGetterFunc } from '../types';
import { MappingsEditor, OnUpdateHandler, LoadMappingsFromJsonButton } from '../../mappings_editor';
export const StepMappings: React.FunctionComponent = ({
@@ -23,16 +23,23 @@ export const StepMappings: React.FunctionComponent = ({
setDataGetter,
onStepValidityChange,
}) => {
- const [mappings, setMappings] = useState(template.mappings);
+ const [mappings, setMappings] = useState(template?.template.mappings);
const onMappingsEditorUpdate = useCallback(
({ isValid, getData, validate }) => {
onStepValidityChange(isValid);
- setDataGetter(async () => {
+
+ const dataGetterFunc: DataGetterFunc = async () => {
const isMappingsValid = isValid === undefined ? await validate() : isValid;
const data = getData(isMappingsValid);
- return Promise.resolve({ isValid: isMappingsValid, data: { mappings: data } });
- });
+ return {
+ isValid: isMappingsValid,
+ data: { mappings: data },
+ path: 'template',
+ };
+ };
+
+ setDataGetter(dataGetterFunc);
},
[setDataGetter, onStepValidityChange]
);
@@ -96,7 +103,7 @@ export const StepMappings: React.FunctionComponent = ({
diff --git a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_review.tsx b/x-pack/plugins/index_management/public/application/components/template_form/steps/step_review.tsx
index 09da43b83c3c5..0cb2ae9fbcd92 100644
--- a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_review.tsx
+++ b/x-pack/plugins/index_management/public/application/components/template_form/steps/step_review.tsx
@@ -22,8 +22,11 @@ import {
import { FormattedMessage } from '@kbn/i18n/react';
import { serializers } from '../../../../shared_imports';
-import { serializeTemplate } from '../../../../../common/lib/template_serialization';
-import { Template } from '../../../../../common/types';
+import {
+ serializeV1Template,
+ serializeV2Template,
+} from '../../../../../common/lib/template_serialization';
+import { TemplateDeserialized, getTemplateParameter } from '../../../../../common';
import { StepProps } from '../types';
const { stripEmptyFields } = serializers;
@@ -52,16 +55,25 @@ const getDescriptionText = (data: any) => {
};
export const StepReview: React.FunctionComponent = ({ template, updateCurrentStep }) => {
- const { name, indexPatterns, version, order } = template;
+ const {
+ name,
+ indexPatterns,
+ version,
+ order,
+ _kbnMeta: { formatVersion },
+ } = template!;
+
+ const serializedTemplate =
+ formatVersion === 1
+ ? serializeV1Template(stripEmptyFields(template!) as TemplateDeserialized)
+ : serializeV2Template(stripEmptyFields(template!) as TemplateDeserialized);
- const serializedTemplate = serializeTemplate(stripEmptyFields(template) as Template);
// Name not included in ES request body
delete serializedTemplate.name;
- const {
- mappings: serializedMappings,
- settings: serializedSettings,
- aliases: serializedAliases,
- } = serializedTemplate;
+
+ const serializedMappings = getTemplateParameter(serializedTemplate, 'mappings');
+ const serializedSettings = getTemplateParameter(serializedTemplate, 'settings');
+ const serializedAliases = getTemplateParameter(serializedTemplate, 'aliases');
const numIndexPatterns = indexPatterns!.length;
diff --git a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_settings.tsx b/x-pack/plugins/index_management/public/application/components/template_form/steps/step_settings.tsx
index cead652c9f6fc..7c1ee6388a618 100644
--- a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_settings.tsx
+++ b/x-pack/plugins/index_management/public/application/components/template_form/steps/step_settings.tsx
@@ -29,7 +29,7 @@ export const StepSettings: React.FunctionComponent = ({
}) => {
const { content, setContent, error } = useJsonStep({
prop: 'settings',
- defaultValue: template.settings,
+ defaultValue: template?.template.settings,
setDataGetter,
onStepValidityChange,
});
diff --git a/x-pack/plugins/index_management/public/application/components/template_form/steps/use_json_step.ts b/x-pack/plugins/index_management/public/application/components/template_form/steps/use_json_step.ts
index fbe479ea0cf23..25dbe784db3a1 100644
--- a/x-pack/plugins/index_management/public/application/components/template_form/steps/use_json_step.ts
+++ b/x-pack/plugins/index_management/public/application/components/template_form/steps/use_json_step.ts
@@ -8,7 +8,7 @@ import { useEffect, useState, useCallback } from 'react';
import { i18n } from '@kbn/i18n';
import { isJSON } from '../../../../shared_imports';
-import { StepProps } from '../types';
+import { StepProps, DataGetterFunc } from '../types';
interface Parameters {
prop: 'settings' | 'mappings' | 'aliases';
@@ -44,11 +44,13 @@ export const useJsonStep = ({
return isValid;
}, [content]);
- const dataGetter = useCallback(() => {
+ const dataGetter = useCallback(() => {
const isValid = validateContent();
const value = isValid && content.trim() !== '' ? JSON.parse(content) : {};
- const data = { [prop]: value };
- return Promise.resolve({ isValid, data });
+ // If no key has been added to the JSON object, we strip it out so an empty object is not sent in the request
+ const data = { [prop]: Object.keys(value).length > 0 ? value : undefined };
+
+ return Promise.resolve({ isValid, data, path: 'template' });
}, [content, validateContent, prop]);
useEffect(() => {
diff --git a/x-pack/plugins/index_management/public/application/components/template_form/template_form.tsx b/x-pack/plugins/index_management/public/application/components/template_form/template_form.tsx
index 58be9b2c63365..f6193bc71aa91 100644
--- a/x-pack/plugins/index_management/public/application/components/template_form/template_form.tsx
+++ b/x-pack/plugins/index_management/public/application/components/template_form/template_form.tsx
@@ -15,7 +15,7 @@ import {
} from '@elastic/eui';
import { serializers } from '../../../shared_imports';
-import { Template } from '../../../../common/types';
+import { TemplateDeserialized, DEFAULT_INDEX_TEMPLATE_VERSION_FORMAT } from '../../../../common';
import { TemplateSteps } from './template_steps';
import { StepAliases, StepLogistics, StepMappings, StepSettings, StepReview } from './steps';
import { StepProps, DataGetterFunc } from './types';
@@ -24,11 +24,11 @@ import { SectionError } from '../section_error';
const { stripEmptyFields } = serializers;
interface Props {
- onSave: (template: Template) => void;
+ onSave: (template: TemplateDeserialized) => void;
clearSaveError: () => void;
isSaving: boolean;
saveError: any;
- defaultValue?: Template;
+ defaultValue?: TemplateDeserialized;
isEditing?: boolean;
}
@@ -47,7 +47,15 @@ const stepComponentMap: { [key: number]: React.FunctionComponent } =
};
export const TemplateForm: React.FunctionComponent = ({
- defaultValue = { isManaged: false },
+ defaultValue = {
+ name: '',
+ indexPatterns: [],
+ template: {},
+ isManaged: false,
+ _kbnMeta: {
+ formatVersion: DEFAULT_INDEX_TEMPLATE_VERSION_FORMAT,
+ },
+ },
onSave,
isSaving,
saveError,
@@ -63,7 +71,7 @@ export const TemplateForm: React.FunctionComponent = ({
5: defaultValidation,
});
- const template = useRef>(defaultValue);
+ const template = useRef(defaultValue);
const stepsDataGetters = useRef>({});
const lastStep = Object.keys(stepComponentMap).length;
@@ -91,17 +99,31 @@ export const TemplateForm: React.FunctionComponent = ({
);
const validateAndGetDataFromCurrentStep = async () => {
- const validateAndGetData = stepsDataGetters.current[currentStep];
+ const validateAndGetStepData = stepsDataGetters.current[currentStep];
- if (!validateAndGetData) {
+ if (!validateAndGetStepData) {
throw new Error(`No data getter has been set for step "${currentStep}"`);
}
- const { isValid, data } = await validateAndGetData();
+ const { isValid, data, path } = await validateAndGetStepData();
if (isValid) {
- // Update the template object
- template.current = { ...template.current, ...data };
+ // Update the template object with the current step data
+ if (path) {
+ // We only update a "slice" of the template
+ const sliceToUpdate = template.current[path as keyof TemplateDeserialized];
+
+ if (sliceToUpdate === null || typeof sliceToUpdate !== 'object') {
+ return { isValid, data };
+ }
+
+ template.current = {
+ ...template.current,
+ [path]: { ...sliceToUpdate, ...data },
+ };
+ } else {
+ template.current = { ...template.current, ...data };
+ }
}
return { isValid, data };
@@ -111,9 +133,9 @@ export const TemplateForm: React.FunctionComponent = ({
// All steps needs validation, except for the last step
const shouldValidate = currentStep !== lastStep;
- let isValid = isStepValid;
if (shouldValidate) {
- isValid = isValid === false ? false : (await validateAndGetDataFromCurrentStep()).isValid;
+ const isValid =
+ isStepValid === false ? false : (await validateAndGetDataFromCurrentStep()).isValid;
// If step is invalid do not let user proceed
if (!isValid) {
@@ -222,7 +244,10 @@ export const TemplateForm: React.FunctionComponent = ({
fill
color="secondary"
iconType="check"
- onClick={onSave.bind(null, stripEmptyFields(template.current) as Template)}
+ onClick={onSave.bind(
+ null,
+ stripEmptyFields(template.current!) as TemplateDeserialized
+ )}
data-test-subj="submitButton"
isLoading={isSaving}
>
diff --git a/x-pack/plugins/index_management/public/application/components/template_form/types.ts b/x-pack/plugins/index_management/public/application/components/template_form/types.ts
index 9385f0c9f738b..5db53e91ed261 100644
--- a/x-pack/plugins/index_management/public/application/components/template_form/types.ts
+++ b/x-pack/plugins/index_management/public/application/components/template_form/types.ts
@@ -4,14 +4,21 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { Template } from '../../../../common/types';
+import { TemplateDeserialized } from '../../../../common';
export interface StepProps {
- template: Partial;
+ template?: TemplateDeserialized;
setDataGetter: (dataGetter: DataGetterFunc) => void;
updateCurrentStep: (step: number) => void;
onStepValidityChange: (isValid: boolean | undefined) => void;
isEditing?: boolean;
}
-export type DataGetterFunc = () => Promise<{ isValid: boolean; data: any }>;
+export type DataGetterFunc = () => Promise<{
+ /** Is the step data valid or not */
+ isValid: boolean;
+ /** The current step data (can be invalid) */
+ data: any;
+ /** Optional "slice" of the complete object the step is updating */
+ path?: string;
+}>;
diff --git a/x-pack/plugins/index_management/public/application/lib/index_templates.ts b/x-pack/plugins/index_management/public/application/lib/index_templates.ts
new file mode 100644
index 0000000000000..7129e536287c1
--- /dev/null
+++ b/x-pack/plugins/index_management/public/application/lib/index_templates.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import { parse } from 'query-string';
+import { Location } from 'history';
+
+export const getFormatVersionFromQueryparams = (location: Location): 1 | 2 | undefined => {
+ const { v: version } = parse(location.search.substring(1));
+
+ if (!Boolean(version) || typeof version !== 'string') {
+ return undefined;
+ }
+
+ return +version as 1 | 2;
+};
diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_aliases.tsx b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_aliases.tsx
index 421119bd8df96..fa7d734ad0d2b 100644
--- a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_aliases.tsx
+++ b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_aliases.tsx
@@ -7,14 +7,16 @@
import React from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiCodeBlock, EuiCallOut } from '@elastic/eui';
-import { Template } from '../../../../../../../common/types';
+import { TemplateDeserialized } from '../../../../../../../common';
interface Props {
- templateDetails: Template;
+ templateDetails: TemplateDeserialized;
}
export const TabAliases: React.FunctionComponent = ({ templateDetails }) => {
- const { aliases } = templateDetails;
+ const {
+ template: { aliases },
+ } = templateDetails;
if (aliases && Object.keys(aliases).length) {
return (
diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_mappings.tsx b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_mappings.tsx
index 83f2e67fb12c5..6e0257c6b377b 100644
--- a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_mappings.tsx
+++ b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_mappings.tsx
@@ -7,14 +7,16 @@
import React from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiCodeBlock, EuiCallOut } from '@elastic/eui';
-import { Template } from '../../../../../../../common/types';
+import { TemplateDeserialized } from '../../../../../../../common';
interface Props {
- templateDetails: Template;
+ templateDetails: TemplateDeserialized;
}
export const TabMappings: React.FunctionComponent = ({ templateDetails }) => {
- const { mappings } = templateDetails;
+ const {
+ template: { mappings },
+ } = templateDetails;
if (mappings && Object.keys(mappings).length) {
return (
diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_settings.tsx b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_settings.tsx
index 8b2a431bee65a..8f75c2cb77801 100644
--- a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_settings.tsx
+++ b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_settings.tsx
@@ -7,14 +7,16 @@
import React from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiCodeBlock, EuiCallOut } from '@elastic/eui';
-import { Template } from '../../../../../../../common/types';
+import { TemplateDeserialized } from '../../../../../../../common';
interface Props {
- templateDetails: Template;
+ templateDetails: TemplateDeserialized;
}
export const TabSettings: React.FunctionComponent = ({ templateDetails }) => {
- const { settings } = templateDetails;
+ const {
+ template: { settings },
+ } = templateDetails;
if (settings && Object.keys(settings).length) {
return (
diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_summary.tsx b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_summary.tsx
index 99f5db54b4ba2..9ce29ab746a2f 100644
--- a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_summary.tsx
+++ b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_summary.tsx
@@ -14,11 +14,11 @@ import {
EuiText,
EuiTitle,
} from '@elastic/eui';
-import { Template } from '../../../../../../../common/types';
+import { TemplateDeserialized } from '../../../../../../../common';
import { getILMPolicyPath } from '../../../../../services/navigation';
interface Props {
- templateDetails: Template;
+ templateDetails: TemplateDeserialized;
}
const NoneDescriptionText = () => (
@@ -35,6 +35,7 @@ export const TabSummary: React.FunctionComponent = ({ templateDetails })
return (
+ {/* Index patterns */}
= ({ templateDetails })
indexPatterns.toString()
)}
+
+ {/* // ILM Policy */}
= ({ templateDetails })
)}
+
+ {/* // Order */}
= ({ templateDetails })
{order || order === 0 ? order : }
+
+ {/* // Version */}
void;
- editTemplate: (templateName: Template['name']) => void;
- cloneTemplate: (templateName: Template['name']) => void;
+ editTemplate: (name: string, formatVersion: IndexTemplateFormatVersion) => void;
+ cloneTemplate: (name: string, formatVersion: IndexTemplateFormatVersion) => void;
reload: () => Promise;
}
@@ -79,7 +79,7 @@ const TABS = [
];
const tabToComponentMap: {
- [key: string]: React.FunctionComponent<{ templateDetails: Template }>;
+ [key: string]: React.FunctionComponent<{ templateDetails: TemplateDeserialized }>;
} = {
[SUMMARY_TAB_ID]: TabSummary,
[SETTINGS_TAB_ID]: TabSettings,
@@ -95,7 +95,7 @@ const tabToUiMetricMap: { [key: string]: string } = {
};
export const TemplateDetails: React.FunctionComponent = ({
- templateName,
+ template: { name: templateName, formatVersion },
onClose,
editTemplate,
cloneTemplate,
@@ -103,10 +103,14 @@ export const TemplateDetails: React.FunctionComponent = ({
}) => {
const { uiMetricService } = useServices();
const decodedTemplateName = decodePath(templateName);
- const { error, data: templateDetails, isLoading } = useLoadIndexTemplate(decodedTemplateName);
- // TS complains if we use destructuring here. Fixed in 3.6.0 (https://github.com/microsoft/TypeScript/pull/31711).
- const isManaged = templateDetails ? templateDetails.isManaged : undefined;
- const [templateToDelete, setTemplateToDelete] = useState>([]);
+ const { error, data: templateDetails, isLoading } = useLoadIndexTemplate(
+ decodedTemplateName,
+ formatVersion
+ );
+ const isManaged = templateDetails?.isManaged;
+ const [templateToDelete, setTemplateToDelete] = useState<
+ Array<{ name: string; formatVersion: IndexTemplateFormatVersion }>
+ >([]);
const [activeTab, setActiveTab] = useState(SUMMARY_TAB_ID);
const [isPopoverOpen, setIsPopOverOpen] = useState(false);
@@ -275,7 +279,7 @@ export const TemplateDetails: React.FunctionComponent = ({
defaultMessage: 'Edit',
}),
icon: 'pencil',
- onClick: () => editTemplate(decodedTemplateName),
+ onClick: () => editTemplate(templateName, formatVersion),
disabled: isManaged,
},
{
@@ -283,7 +287,7 @@ export const TemplateDetails: React.FunctionComponent = ({
defaultMessage: 'Clone',
}),
icon: 'copy',
- onClick: () => cloneTemplate(decodedTemplateName),
+ onClick: () => cloneTemplate(templateName, formatVersion),
},
{
name: i18n.translate(
@@ -293,7 +297,8 @@ export const TemplateDetails: React.FunctionComponent = ({
}
),
icon: 'trash',
- onClick: () => setTemplateToDelete([decodedTemplateName]),
+ onClick: () =>
+ setTemplateToDelete([{ name: decodedTemplateName, formatVersion }]),
disabled: isManaged,
},
],
diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_list.tsx b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_list.tsx
index ffdb224f16271..1e84202639ee8 100644
--- a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_list.tsx
+++ b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_list.tsx
@@ -16,31 +16,35 @@ import {
EuiFlexItem,
EuiFlexGroup,
} from '@elastic/eui';
+
+import { UIM_TEMPLATE_LIST_LOAD } from '../../../../../common/constants';
+import { IndexTemplateFormatVersion } from '../../../../../common';
import { SectionError, SectionLoading, Error } from '../../../components';
-import { TemplateTable } from './template_table';
import { useLoadIndexTemplates } from '../../../services/api';
-import { Template } from '../../../../../common/types';
import { useServices } from '../../../app_context';
import {
getTemplateEditLink,
getTemplateListLink,
getTemplateCloneLink,
} from '../../../services/routing';
-import { UIM_TEMPLATE_LIST_LOAD } from '../../../../../common/constants';
+import { getFormatVersionFromQueryparams } from '../../../lib/index_templates';
+import { TemplateTable } from './template_table';
import { TemplateDetails } from './template_details';
interface MatchParams {
- templateName?: Template['name'];
+ templateName?: string;
}
export const TemplateList: React.FunctionComponent> = ({
match: {
params: { templateName },
},
+ location,
history,
}) => {
const { uiMetricService } = useServices();
const { error, isLoading, data: templates, sendRequest: reload } = useLoadIndexTemplates();
+ const queryParamsFormatVersion = getFormatVersionFromQueryparams(location);
let content;
@@ -48,8 +52,7 @@ export const TemplateList: React.FunctionComponent
- templates ? templates.filter((template: Template) => !template.name.startsWith('.')) : [],
+ () => (templates ? templates.filter(template => !template.name.startsWith('.')) : []),
[templates]
);
@@ -57,12 +60,12 @@ export const TemplateList: React.FunctionComponent {
- history.push(getTemplateEditLink(name));
+ const editTemplate = (name: string, formatVersion: IndexTemplateFormatVersion) => {
+ history.push(getTemplateEditLink(name, formatVersion));
};
- const cloneTemplate = (name: Template['name']) => {
- history.push(getTemplateCloneLink(name));
+ const cloneTemplate = (name: string, formatVersion: IndexTemplateFormatVersion) => {
+ history.push(getTemplateCloneLink(name, formatVersion));
};
// Track component loaded
@@ -149,9 +152,12 @@ export const TemplateList: React.FunctionComponent
{content}
- {templateName && (
+ {templateName && queryParamsFormatVersion !== undefined && (
Promise;
- editTemplate: (name: Template['name']) => void;
- cloneTemplate: (name: Template['name']) => void;
+ editTemplate: (name: string, formatVersion: IndexTemplateFormatVersion) => void;
+ cloneTemplate: (name: string, formatVersion: IndexTemplateFormatVersion) => void;
}
export const TemplateTable: React.FunctionComponent = ({
@@ -30,7 +30,9 @@ export const TemplateTable: React.FunctionComponent = ({
}) => {
const { uiMetricService } = useServices();
const [selection, setSelection] = useState([]);
- const [templatesToDelete, setTemplatesToDelete] = useState>([]);
+ const [templatesToDelete, setTemplatesToDelete] = useState<
+ Array<{ name: string; formatVersion: IndexTemplateFormatVersion }>
+ >([]);
const columns: Array> = [
{
@@ -40,11 +42,11 @@ export const TemplateTable: React.FunctionComponent = ({
}),
truncateText: true,
sortable: true,
- render: (name: TemplateListItem['name']) => {
+ render: (name: TemplateListItem['name'], item: TemplateListItem) => {
return (
/* eslint-disable-next-line @elastic/eui/href-or-on-click */
uiMetricService.trackMetric('click', UIM_TEMPLATE_SHOW_DETAILS_CLICK)}
>
@@ -133,10 +135,10 @@ export const TemplateTable: React.FunctionComponent = ({
}),
icon: 'pencil',
type: 'icon',
- onClick: ({ name }: Template) => {
- editTemplate(name);
+ onClick: ({ name, _kbnMeta: { formatVersion } }: TemplateListItem) => {
+ editTemplate(name, formatVersion);
},
- enabled: ({ isManaged }: Template) => !isManaged,
+ enabled: ({ isManaged }: TemplateListItem) => !isManaged,
},
{
type: 'icon',
@@ -147,8 +149,8 @@ export const TemplateTable: React.FunctionComponent