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/dashboard/bwc_shared_urls.js b/test/functional/apps/dashboard/bwc_shared_urls.js
index fb1e580135e5a..b56cb658b80bb 100644
--- a/test/functional/apps/dashboard/bwc_shared_urls.js
+++ b/test/functional/apps/dashboard/bwc_shared_urls.js
@@ -135,6 +135,27 @@ export default function({ getService, getPageObjects }) {
await dashboardExpect.selectedLegendColorCount('#000000', 5);
});
+
+ it('back button works for old dashboards after state migrations', async () => {
+ await PageObjects.dashboard.preserveCrossAppState();
+ const oldId = await PageObjects.dashboard.getDashboardIdFromCurrentUrl();
+ await PageObjects.dashboard.waitForRenderComplete();
+ await dashboardExpect.selectedLegendColorCount('#000000', 5);
+
+ const url = `${kibanaBaseUrl}#/dashboard?${urlQuery}`;
+ log.debug(`Navigating to ${url}`);
+ await browser.get(url);
+ await PageObjects.header.waitUntilLoadingHasFinished();
+ await PageObjects.dashboard.waitForRenderComplete();
+ await dashboardExpect.selectedLegendColorCount('#F9D9F9', 5);
+ await browser.goBack();
+
+ await PageObjects.header.waitUntilLoadingHasFinished();
+ const newId = await PageObjects.dashboard.getDashboardIdFromCurrentUrl();
+ expect(newId).to.be.equal(oldId);
+ await PageObjects.dashboard.waitForRenderComplete();
+ await dashboardExpect.selectedLegendColorCount('#000000', 5);
+ });
});
});
}
diff --git a/test/functional/apps/dashboard/dashboard_time.js b/test/functional/apps/dashboard/dashboard_time.js
index 2e7b7f9a6dbb1..5a2628f42ded5 100644
--- a/test/functional/apps/dashboard/dashboard_time.js
+++ b/test/functional/apps/dashboard/dashboard_time.js
@@ -91,6 +91,20 @@ export default function({ getPageObjects, getService }) {
expect(time.start).to.equal('~ an hour ago');
expect(time.end).to.equal('now');
});
+
+ it('should use saved time, if time is missing in global state, but _g is present in the url', async function() {
+ const currentUrl = await browser.getCurrentUrl();
+ const kibanaBaseUrl = currentUrl.substring(0, currentUrl.indexOf('#'));
+ const id = await PageObjects.dashboard.getDashboardIdFromCurrentUrl();
+
+ await PageObjects.dashboard.gotoDashboardLandingPage();
+
+ const urlWithGlobalTime = `${kibanaBaseUrl}#/dashboard/${id}?_g=(filters:!())`;
+ await browser.get(urlWithGlobalTime, false);
+ const time = await PageObjects.timePicker.getTimeConfig();
+ expect(time.start).to.equal(PageObjects.timePicker.defaultStartTime);
+ expect(time.end).to.equal(PageObjects.timePicker.defaultEndTime);
+ });
});
// If the user has time stored with a dashboard, it's supposed to override the current time settings
diff --git a/test/functional/apps/management/_kibana_settings.js b/test/functional/apps/management/_kibana_settings.js
index c99368ba4e859..97337d4573e2a 100644
--- a/test/functional/apps/management/_kibana_settings.js
+++ b/test/functional/apps/management/_kibana_settings.js
@@ -46,6 +46,18 @@ export default function({ getService, getPageObjects }) {
});
describe('state:storeInSessionStorage', () => {
+ async function getStateFromUrl() {
+ const currentUrl = await browser.getCurrentUrl();
+ let match = currentUrl.match(/(.*)?_g=(.*)&_a=(.*)/);
+ if (match) return [match[2], match[3]];
+ match = currentUrl.match(/(.*)?_a=(.*)&_g=(.*)/);
+ if (match) return [match[3], match[2]];
+
+ if (!match) {
+ throw new Error('State in url is missing or malformed');
+ }
+ }
+
it('defaults to null', async () => {
await PageObjects.settings.clickKibanaSettings();
const storeInSessionStorage = await PageObjects.settings.getAdvancedSettingCheckbox(
@@ -58,10 +70,7 @@ export default function({ getService, getPageObjects }) {
await PageObjects.common.navigateToApp('dashboard');
await PageObjects.dashboard.clickNewDashboard();
await PageObjects.timePicker.setDefaultAbsoluteRange();
- const currentUrl = await browser.getCurrentUrl();
- const urlPieces = currentUrl.match(/(.*)?_g=(.*)&_a=(.*)/);
- const globalState = urlPieces[2];
- const appState = urlPieces[3];
+ const [globalState, appState] = await getStateFromUrl();
// We don't have to be exact, just need to ensure it's greater than when the hashed variation is being used,
// which is less than 20 characters.
@@ -83,10 +92,7 @@ export default function({ getService, getPageObjects }) {
await PageObjects.common.navigateToApp('dashboard');
await PageObjects.dashboard.clickNewDashboard();
await PageObjects.timePicker.setDefaultAbsoluteRange();
- const currentUrl = await browser.getCurrentUrl();
- const urlPieces = currentUrl.match(/(.*)?_g=(.*)&_a=(.*)/);
- const globalState = urlPieces[2];
- const appState = urlPieces[3];
+ const [globalState, appState] = await getStateFromUrl();
// We don't have to be exact, just need to ensure it's less than the unhashed version, which will be
// greater than 20 characters with the default state plus a time.
@@ -100,10 +106,7 @@ export default function({ getService, getPageObjects }) {
await PageObjects.settings.clickKibanaSettings();
await PageObjects.settings.toggleAdvancedSettingCheckbox('state:storeInSessionStorage');
await PageObjects.header.clickDashboard();
- const currentUrl = await browser.getCurrentUrl();
- const urlPieces = currentUrl.match(/(.*)?_g=(.*)&_a=(.*)/);
- const globalState = urlPieces[2];
- const appState = urlPieces[3];
+ const [globalState, appState] = await getStateFromUrl();
// We don't have to be exact, just need to ensure it's greater than when the hashed variation is being used,
// which is less than 20 characters.
expect(globalState.length).to.be.greaterThan(20);
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/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/timeline/routes/schemas/schemas.ts b/x-pack/legacy/plugins/siem/server/lib/timeline/routes/schemas/schemas.ts
index 6552f973a66fa..fc87a775a9e68 100644
--- a/x-pack/legacy/plugins/siem/server/lib/timeline/routes/schemas/schemas.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/timeline/routes/schemas/schemas.ts
@@ -139,9 +139,9 @@ export const kqlQuery = Joi.object({
kuery: Joi.object({
kind: allowEmptyString,
expression: allowEmptyString,
- }),
+ }).allow(null),
serializedQuery: allowEmptyString,
- }),
+ }).allow(null),
});
export const pinnedEventIds = Joi.array()
.items(allowEmptyString)
diff --git a/x-pack/legacy/plugins/spaces/index.ts b/x-pack/legacy/plugins/spaces/index.ts
index 757c1eb557c54..8d44c17018255 100644
--- a/x-pack/legacy/plugins/spaces/index.ts
+++ b/x-pack/legacy/plugins/spaces/index.ts
@@ -12,9 +12,7 @@ import { SpacesServiceSetup } from '../../../plugins/spaces/server';
import { SpacesPluginSetup } from '../../../plugins/spaces/server';
// @ts-ignore
import { AuditLogger } from '../../server/lib/audit_logger';
-import mappings from './mappings.json';
import { wrapError } from './server/lib/errors';
-import { migrateToKibana660 } from './server/lib/migrations';
// @ts-ignore
import { watchStatusAndLicenseToInitialize } from '../../server/lib/watch_status_and_license_to_initialize';
import { initEnterSpaceView } from './server/routes/views';
@@ -39,18 +37,6 @@ export const spaces = (kibana: Record) =>
managementSections: [],
apps: [],
hacks: ['plugins/spaces/legacy'],
- mappings,
- migrations: {
- space: {
- '6.6.0': migrateToKibana660,
- },
- },
- savedObjectSchemas: {
- space: {
- isNamespaceAgnostic: true,
- hidden: true,
- },
- },
home: [],
injectDefaultVars(server: Server) {
return {
@@ -100,7 +86,6 @@ export const spaces = (kibana: Record) =>
const { registerLegacyAPI, createDefaultSpace } = spacesPlugin.__legacyCompat;
registerLegacyAPI({
- savedObjects: server.savedObjects,
auditLogger: {
create: (pluginId: string) =>
new AuditLogger(server, pluginId, server.config(), server.plugins.xpack_main.info),
diff --git a/x-pack/legacy/plugins/spaces/mappings.json b/x-pack/legacy/plugins/spaces/mappings.json
deleted file mode 100644
index dc73dc2871885..0000000000000
--- a/x-pack/legacy/plugins/spaces/mappings.json
+++ /dev/null
@@ -1,34 +0,0 @@
-{
- "space": {
- "properties": {
- "name": {
- "type": "text",
- "fields": {
- "keyword": {
- "type": "keyword",
- "ignore_above": 2048
- }
- }
- },
- "description": {
- "type": "text"
- },
- "initials": {
- "type": "keyword"
- },
- "color": {
- "type": "keyword"
- },
- "disabledFeatures": {
- "type": "keyword"
- },
- "imageUrl": {
- "type": "text",
- "index": false
- },
- "_reserved": {
- "type": "boolean"
- }
- }
- }
-}
diff --git a/x-pack/legacy/plugins/spaces/server/lib/migrations/migrate_6x.test.ts b/x-pack/legacy/plugins/spaces/server/lib/migrations/migrate_6x.test.ts
deleted file mode 100644
index 964eb8137685f..0000000000000
--- a/x-pack/legacy/plugins/spaces/server/lib/migrations/migrate_6x.test.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { migrateToKibana660 } from './migrate_6x';
-
-describe('migrateTo660', () => {
- it('adds a "disabledFeatures" attribute initialized as an empty array', () => {
- expect(
- migrateToKibana660({
- id: 'space:foo',
- attributes: {},
- })
- ).toEqual({
- id: 'space:foo',
- attributes: {
- disabledFeatures: [],
- },
- });
- });
-
- it('does not initialize "disabledFeatures" if the property already exists', () => {
- // This scenario shouldn't happen organically. Protecting against defects in the migration.
- expect(
- migrateToKibana660({
- id: 'space:foo',
- attributes: {
- disabledFeatures: ['foo', 'bar', 'baz'],
- },
- })
- ).toEqual({
- id: 'space:foo',
- attributes: {
- disabledFeatures: ['foo', 'bar', 'baz'],
- },
- });
- });
-});
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 0a3226bc064a8..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
@@ -50,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,
});
}
};
@@ -119,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);
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 0b31a74a40133..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
@@ -18,7 +18,7 @@ import {
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);
@@ -34,8 +34,11 @@ export const createMLJob = async ({
}: 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()
@@ -47,7 +50,7 @@ export const createMLJob = async ({
filter: [
{
term: {
- 'monitor.id': monitorId,
+ 'monitor.id': lowerCaseMonitorId,
},
},
],
@@ -56,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/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/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 81adf76ac4ce9..4b74b07fc8e27 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
@@ -167,12 +167,6 @@ Array [
"validationError": "Must be a number between 0.000 and 1",
"validationName": "numberFloatRt",
},
- Object {
- "key": "trace_methods_duration_threshold",
- "type": "integer",
- "validationError": "Must be an integer",
- "validationName": "integerRt",
- },
Object {
"key": "transaction_max_spans",
"max": 32000,
diff --git a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/general_settings.ts b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/general_settings.ts
index cfe4aa01a4a99..152db37a1bff3 100644
--- a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/general_settings.ts
+++ b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/general_settings.ts
@@ -28,7 +28,7 @@ export const generalSettings: RawSettingDefinition[] = [
'The maximum total compressed size of the request body which is sent to the APM Server intake api via a chunked encoding (HTTP streaming).\nNote that a small overshoot is possible.\n\nAllowed byte units are `b`, `kb` and `mb`. `1kb` is equal to `1024b`.'
}
),
- excludeAgents: ['js-base', 'rum-js', 'dotnet']
+ excludeAgents: ['js-base', 'rum-js', 'dotnet', 'go', 'nodejs']
},
// API Request Time
@@ -46,7 +46,7 @@ export const generalSettings: RawSettingDefinition[] = [
"Maximum time to keep an HTTP request to the APM Server open for.\n\nNOTE: This value has to be lower than the APM Server's `read_timeout` setting."
}
),
- excludeAgents: ['js-base', 'rum-js', 'dotnet']
+ excludeAgents: ['js-base', 'rum-js', 'dotnet', 'go', 'nodejs']
},
// Capture body
@@ -89,7 +89,7 @@ export const generalSettings: RawSettingDefinition[] = [
'If set to `true`, the agent will capture request and response headers, including cookies.\n\nNOTE: Setting this to `false` reduces network bandwidth, disk space and object allocations.'
}
),
- excludeAgents: ['js-base', 'rum-js']
+ excludeAgents: ['js-base', 'rum-js', 'nodejs']
},
// LOG_LEVEL
@@ -103,7 +103,7 @@ export const generalSettings: RawSettingDefinition[] = [
description: i18n.translate('xpack.apm.agentConfig.logLevel.description', {
defaultMessage: 'Sets the logging level for the agent'
}),
- excludeAgents: ['js-base', 'rum-js', 'python']
+ includeAgents: ['dotnet', 'ruby']
},
// Recording
@@ -117,7 +117,8 @@ export const generalSettings: RawSettingDefinition[] = [
description: i18n.translate('xpack.apm.agentConfig.recording.description', {
defaultMessage:
'When recording, the agent instruments incoming HTTP requests, tracks errors, and collects and sends metrics. When inactive, the agent works as a noop, not collecting data and not communicating with the APM Server except for polling for updated configuration. As this is a reversible switch, agent threads are not being killed when inactivated, but they will be mostly idle in this state, so the overhead should be negligible. You can use this setting to dynamically control whether Elastic APM is enabled or disabled.'
- })
+ }),
+ excludeAgents: ['nodejs']
},
// SERVER_TIMEOUT
@@ -135,7 +136,7 @@ export const generalSettings: RawSettingDefinition[] = [
'If a request to the APM Server takes longer than the configured timeout,\nthe request is cancelled and the event (exception or transaction) is discarded.\nSet to 0 to disable timeouts.\n\nWARNING: If timeouts are disabled or set to a high value, your app could experience memory issues if the APM Server times out.'
}
),
- includeAgents: ['nodejs', 'java', 'go']
+ includeAgents: ['java']
},
// SPAN_FRAMES_MIN_DURATION
@@ -171,7 +172,7 @@ export const generalSettings: RawSettingDefinition[] = [
'Setting it to 0 will disable stack trace collection. Any positive integer value will be used as the maximum number of frames to collect. Setting it -1 means that all frames will be collected.'
}
),
- includeAgents: ['nodejs', 'java', 'dotnet', 'go']
+ includeAgents: ['java', 'dotnet', 'go']
},
// Transaction max spans
diff --git a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/index.test.ts b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/index.test.ts
index b0255d2d828bb..7fa44b8c85f41 100644
--- a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/index.test.ts
+++ b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/index.test.ts
@@ -43,13 +43,9 @@ describe('filterByAgent', () => {
describe('options per agent', () => {
it('go', () => {
expect(getSettingKeysForAgent('go')).toEqual([
- 'api_request_size',
- 'api_request_time',
'capture_body',
'capture_headers',
- 'log_level',
'recording',
- 'server_timeout',
'span_frames_min_duration',
'stack_trace_limit',
'transaction_max_spans',
@@ -65,7 +61,6 @@ describe('filterByAgent', () => {
'capture_headers',
'circuit_breaker_enabled',
'enable_log_correlation',
- 'log_level',
'profiling_inferred_spans_enabled',
'profiling_inferred_spans_excluded_classes',
'profiling_inferred_spans_included_classes',
@@ -80,7 +75,6 @@ describe('filterByAgent', () => {
'stress_monitor_gc_stress_threshold',
'stress_monitor_system_cpu_relief_threshold',
'stress_monitor_system_cpu_stress_threshold',
- 'trace_methods_duration_threshold',
'transaction_max_spans',
'transaction_sample_rate'
]);
@@ -102,14 +96,7 @@ describe('filterByAgent', () => {
it('nodejs', () => {
expect(getSettingKeysForAgent('nodejs')).toEqual([
- 'api_request_size',
- 'api_request_time',
'capture_body',
- 'capture_headers',
- 'log_level',
- 'recording',
- 'server_timeout',
- 'stack_trace_limit',
'transaction_max_spans',
'transaction_sample_rate'
]);
@@ -158,8 +145,6 @@ describe('filterByAgent', () => {
it('"All" services (no agent name)', () => {
expect(getSettingKeysForAgent(undefined)).toEqual([
'capture_body',
- 'capture_headers',
- 'recording',
'transaction_max_spans',
'transaction_sample_rate'
]);
diff --git a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/java_settings.ts b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/java_settings.ts
index 7331b6c5dcbf5..bb050076b9f9a 100644
--- a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/java_settings.ts
+++ b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/java_settings.ts
@@ -26,26 +26,6 @@ export const javaSettings: RawSettingDefinition[] = [
includeAgents: ['java']
},
- // TRACE_METHODS_DURATION_THRESHOLD
- {
- key: 'trace_methods_duration_threshold',
- type: 'integer',
- label: i18n.translate(
- 'xpack.apm.agentConfig.traceMethodsDurationThreshold.label',
- {
- defaultMessage: 'Trace methods duration threshold'
- }
- ),
- description: i18n.translate(
- 'xpack.apm.agentConfig.traceMethodsDurationThreshold.description',
- {
- defaultMessage:
- 'If trace_methods config option is set, provides a threshold to limit spans based on duration. When set to a value greater than 0, spans representing methods traced based on trace_methods will be discarded by default.'
- }
- ),
- includeAgents: ['java']
- },
-
/*
* Circuit-Breaker
**/
diff --git a/x-pack/plugins/apm/common/service_map.test.ts b/x-pack/plugins/apm/common/service_map.test.ts
new file mode 100644
index 0000000000000..40b220ffe68f1
--- /dev/null
+++ b/x-pack/plugins/apm/common/service_map.test.ts
@@ -0,0 +1,97 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { License } from '../../licensing/common/license';
+import * as serviceMap from './service_map';
+
+describe('service map helpers', () => {
+ describe('isValidPlatinumLicense', () => {
+ describe('with an expired license', () => {
+ it('returns false', () => {
+ const license = new License({
+ license: {
+ uid: 'test uid',
+ expiryDateInMillis: 0,
+ mode: 'platinum',
+ type: 'platinum',
+ status: 'expired'
+ },
+ signature: 'test signature'
+ });
+
+ expect(serviceMap.isValidPlatinumLicense(license)).toEqual(false);
+ });
+ });
+
+ describe('with a basic license', () => {
+ it('returns false', () => {
+ const license = new License({
+ license: {
+ uid: 'test uid',
+ expiryDateInMillis: 0,
+ mode: 'basic',
+ type: 'basic',
+ status: 'active'
+ },
+ signature: 'test signature'
+ });
+
+ expect(serviceMap.isValidPlatinumLicense(license)).toEqual(false);
+ });
+ });
+
+ describe('with a platinum license', () => {
+ it('returns true', () => {
+ const license = new License({
+ license: {
+ uid: 'test uid',
+ expiryDateInMillis: 0,
+ mode: 'platinum',
+ type: 'platinum',
+ status: 'active'
+ },
+ signature: 'test signature'
+ });
+
+ expect(serviceMap.isValidPlatinumLicense(license)).toEqual(true);
+ });
+ });
+
+ describe('with an enterprise license', () => {
+ it('returns true', () => {
+ const license = new License({
+ license: {
+ uid: 'test uid',
+ expiryDateInMillis: 0,
+ mode: 'enterprise',
+ type: 'enterprise',
+ status: 'active'
+ },
+ signature: 'test signature'
+ });
+
+ expect(serviceMap.isValidPlatinumLicense(license)).toEqual(true);
+ });
+ });
+
+ describe('with a trial license', () => {
+ it('returns true', () => {
+ const license = new License({
+ license: {
+ uid: 'test uid',
+ expiryDateInMillis: 0,
+ mode: 'trial',
+ type: 'trial',
+ status: 'active'
+ },
+ signature: 'test signature'
+ });
+
+ expect(serviceMap.isValidPlatinumLicense(license)).toEqual(true);
+ });
+ });
+ });
+});
diff --git a/x-pack/plugins/apm/common/service_map.ts b/x-pack/plugins/apm/common/service_map.ts
index 4a8608199c037..75c1c945c5d26 100644
--- a/x-pack/plugins/apm/common/service_map.ts
+++ b/x-pack/plugins/apm/common/service_map.ts
@@ -45,10 +45,7 @@ export interface ServiceNodeMetrics {
}
export function isValidPlatinumLicense(license: ILicense) {
- return (
- license.isActive &&
- (license.type === 'platinum' || license.type === 'trial')
- );
+ return license.isActive && license.hasAtLeast('platinum');
}
export const invalidLicenseMessage = i18n.translate(
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/applications/endpoint/models/policy.ts b/x-pack/plugins/endpoint/public/applications/endpoint/models/policy.ts
index e1ac9defc858e..9ac53f9be609f 100644
--- a/x-pack/plugins/endpoint/public/applications/endpoint/models/policy.ts
+++ b/x-pack/plugins/endpoint/public/applications/endpoint/models/policy.ts
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { PolicyConfig } from '../types';
+import { PolicyConfig, ProtectionModes } from '../types';
/**
* Generate a new Policy model.
@@ -19,7 +19,7 @@ export const generatePolicy = (): PolicyConfig => {
network: true,
},
malware: {
- mode: 'prevent',
+ mode: ProtectionModes.prevent,
},
logging: {
stdout: 'debug',
@@ -44,7 +44,7 @@ export const generatePolicy = (): PolicyConfig => {
process: true,
},
malware: {
- mode: 'detect',
+ mode: ProtectionModes.detect,
},
logging: {
stdout: 'debug',
diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/types.ts b/x-pack/plugins/endpoint/public/applications/endpoint/types.ts
index 4215edb4d6810..d4f6d2457254e 100644
--- a/x-pack/plugins/endpoint/public/applications/endpoint/types.ts
+++ b/x-pack/plugins/endpoint/public/applications/endpoint/types.ts
@@ -123,10 +123,8 @@ export interface PolicyConfig {
process: boolean;
network: boolean;
};
- /** malware mode can be detect, prevent or prevent and notify user */
- malware: {
- mode: string;
- };
+ /** malware mode can be off, detect, prevent or prevent and notify user */
+ malware: MalwareFields;
logging: {
stdout: string;
file: string;
@@ -137,9 +135,7 @@ export interface PolicyConfig {
events: {
process: boolean;
};
- malware: {
- mode: string;
- };
+ malware: MalwareFields;
logging: {
stdout: string;
file: string;
@@ -209,6 +205,44 @@ export enum EventingFields {
network = 'network',
}
+/**
+ * Returns the keys of an object whose values meet a criteria.
+ * Ex) interface largeNestedObject = {
+ * a: {
+ * food: Foods;
+ * toiletPaper: true;
+ * };
+ * b: {
+ * food: Foods;
+ * streamingServices: Streams;
+ * };
+ * c: {};
+ * }
+ *
+ * type hasFoods = KeysByValueCriteria;
+ * The above type will be: [a, b] only, and will not include c.
+ *
+ */
+export type KeysByValueCriteria = {
+ [K in keyof O]: O[K] extends Criteria ? K : never;
+}[keyof O];
+
+/** Returns an array of the policy OSes that have a malware protection field */
+
+export type MalwareProtectionOSes = KeysByValueCriteria;
+/** Policy: Malware protection fields */
+export interface MalwareFields {
+ mode: ProtectionModes;
+}
+
+/** Policy protection mode options */
+export enum ProtectionModes {
+ detect = 'detect',
+ prevent = 'prevent',
+ preventNotify = 'preventNotify',
+ off = 'off',
+}
+
export interface GlobalState {
readonly hostList: HostListState;
readonly alertList: AlertListState;
diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/metadata/host_accordion.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/metadata/host_accordion.tsx
index edaba3725e027..e332c96192fab 100644
--- a/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/metadata/host_accordion.tsx
+++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/metadata/host_accordion.tsx
@@ -6,32 +6,60 @@
import React, { memo, useMemo } from 'react';
import { i18n } from '@kbn/i18n';
import { EuiAccordion, EuiDescriptionList } from '@elastic/eui';
-import { Immutable, AlertData } from '../../../../../../../common/types';
+import { EuiHealth } from '@elastic/eui';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { Immutable, AlertDetails } from '../../../../../../../common/types';
-export const HostAccordion = memo(({ alertData }: { alertData: Immutable }) => {
+export const HostAccordion = memo(({ alertData }: { alertData: Immutable }) => {
const columns = useMemo(() => {
return [
{
- title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.hostName', {
- defaultMessage: 'Host Name',
+ title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.hostNameCurrent', {
+ defaultMessage: 'Host Name (Current)',
+ }),
+ description: alertData.state.host_metadata.host.hostname,
+ },
+ {
+ title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.hostNameOriginal', {
+ defaultMessage: 'Host Name (At time of alert)',
}),
description: alertData.host.hostname,
},
{
- title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.hostIP', {
- defaultMessage: 'Host IP',
+ title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.hostIPCurrent', {
+ defaultMessage: 'Host IP (Current)',
+ }),
+ description: alertData.state.host_metadata.host.ip.join(', '),
+ },
+ {
+ title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.hostIPOriginal', {
+ defaultMessage: 'Host IP (At time of alert)',
}),
description: alertData.host.ip.join(', '),
},
{
- title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.status', {
- defaultMessage: 'Status',
+ title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.currentStatus', {
+ defaultMessage: 'Current Status',
+ }),
+ description: (
+
+ {' '}
+
+
+ ),
+ },
+ {
+ title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.osCurrent', {
+ defaultMessage: 'OS (Current)',
}),
- description: 'TODO',
+ description: alertData.state.host_metadata.host.os.name,
},
{
- title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.os', {
- defaultMessage: 'OS',
+ title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.osOriginal', {
+ defaultMessage: 'OS (At time of alert)',
}),
description: alertData.host.os.name,
},
diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_details.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_details.tsx
index f2c79155f3c23..2dba301bf4537 100644
--- a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_details.tsx
+++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_details.tsx
@@ -35,6 +35,7 @@ import { AppAction } from '../../types';
import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public';
import { AgentsSummary } from './agents_summary';
import { VerticalDivider } from './vertical_divider';
+import { MalwareProtections } from './policy_forms/protections/malware';
export const PolicyDetails = React.memo(() => {
const dispatch = useDispatch<(action: AppAction) => void>();
@@ -181,6 +182,17 @@ export const PolicyDetails = React.memo(() => {
headerLeft={headerLeftContent}
headerRight={headerRightContent}
>
+
+
+
+
+
+
+
+
= React.memo(({ type, supportedOss, children, id, selectedEventing, totalEventing }) => {
+ /** Takes a react component to be put on the right corner of the card */
+ rightCorner: React.ReactNode;
+}> = React.memo(({ type, supportedOss, children, id, rightCorner }) => {
const typeTitle = () => {
return (
@@ -63,32 +62,11 @@ export const ConfigForm: React.FC<{
{supportedOss.join(', ')}
-
-
-
-
-
+ {rightCorner}
);
};
- const events = () => {
- return (
-
-
-
-
-
- );
- };
-
return (
- {events()}
-
{children}
>
}
diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/eventing/windows.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/eventing/windows.tsx
index e92e22fc97fe6..7bec2c4c742d2 100644
--- a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/eventing/windows.tsx
+++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/eventing/windows.tsx
@@ -6,6 +6,8 @@
import React, { useMemo } from 'react';
import { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { EuiTitle, EuiText, EuiSpacer } from '@elastic/eui';
import { EventingCheckbox } from './checkbox';
import { OS, EventingFields } from '../../../../types';
import { usePolicyDetailsSelector } from '../../policy_hooks';
@@ -16,6 +18,9 @@ import {
import { ConfigForm } from '../config_form';
export const WindowsEventing = React.memo(() => {
+ const selected = usePolicyDetailsSelector(selectedWindowsEventing);
+ const total = usePolicyDetailsSelector(totalWindowsEventing);
+
const checkboxes = useMemo(
() => [
{
@@ -37,21 +42,43 @@ export const WindowsEventing = React.memo(() => {
);
const renderCheckboxes = () => {
- return checkboxes.map((item, index) => {
- return (
-
- );
- });
+ return (
+ <>
+
+
+
+
+
+
+ {checkboxes.map((item, index) => {
+ return (
+
+ );
+ })}
+ >
+ );
};
- const selected = usePolicyDetailsSelector(selectedWindowsEventing);
- const total = usePolicyDetailsSelector(totalWindowsEventing);
+ const collectionsEnabled = () => {
+ return (
+
+
+
+ );
+ };
return (
{
i18n.translate('xpack.endpoint.policy.details.windows', { defaultMessage: 'Windows' }),
]}
id="windowsEventingForm"
+ rightCorner={collectionsEnabled()}
children={renderCheckboxes()}
- selectedEventing={selected}
- totalEventing={total}
/>
);
});
diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/protections/malware.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/protections/malware.tsx
new file mode 100644
index 0000000000000..66b22178607b9
--- /dev/null
+++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/protections/malware.tsx
@@ -0,0 +1,180 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { useCallback, useMemo } from 'react';
+import { useDispatch } from 'react-redux';
+import styled from 'styled-components';
+import { EuiRadio, EuiSwitch, EuiTitle, EuiSpacer } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { htmlIdGenerator } from '@elastic/eui';
+import { Immutable } from '../../../../../../../common/types';
+import { OS, ProtectionModes, MalwareProtectionOSes } from '../../../../types';
+import { ConfigForm } from '../config_form';
+import { policyConfig } from '../../../../store/policy_details/selectors';
+import { usePolicyDetailsSelector } from '../../policy_hooks';
+import { clone } from '../../../../models/policy_details_config';
+
+const ProtectionRadioGroup = styled.div`
+ display: flex;
+ .policyDetailsProtectionRadio {
+ margin-right: ${props => props.theme.eui.euiSizeXXL};
+ }
+`;
+
+const OSes: Immutable = [OS.windows, OS.mac];
+const protection = 'malware';
+
+const ProtectionRadio = React.memo(({ id, label }: { id: ProtectionModes; label: string }) => {
+ const policyDetailsConfig = usePolicyDetailsSelector(policyConfig);
+ const dispatch = useDispatch();
+ // currently just taking windows.malware, but both windows.malware and mac.malware should be the same value
+ const selected = policyDetailsConfig && policyDetailsConfig.windows.malware.mode;
+
+ const handleRadioChange = useCallback(() => {
+ if (policyDetailsConfig) {
+ const newPayload = clone(policyDetailsConfig);
+ for (const os of OSes) {
+ newPayload[os][protection].mode = id;
+ }
+ dispatch({
+ type: 'userChangedPolicyConfig',
+ payload: { policyConfig: newPayload },
+ });
+ }
+ }, [dispatch, id, policyDetailsConfig]);
+
+ /**
+ * Passing an arbitrary id because EuiRadio
+ * requires an id if label is passed
+ */
+
+ return (
+ htmlIdGenerator()(), [])}
+ checked={selected === id}
+ onChange={handleRadioChange}
+ disabled={selected === ProtectionModes.off}
+ />
+ );
+});
+
+/** The Malware Protections form for policy details
+ * which will configure for all relevant OSes.
+ */
+export const MalwareProtections = React.memo(() => {
+ const policyDetailsConfig = usePolicyDetailsSelector(policyConfig);
+ const dispatch = useDispatch();
+ // currently just taking windows.malware, but both windows.malware and mac.malware should be the same value
+ const selected = policyDetailsConfig && policyDetailsConfig.windows.malware.mode;
+
+ const radios: Array<{
+ id: ProtectionModes;
+ label: string;
+ protection: 'malware';
+ }> = useMemo(() => {
+ return [
+ {
+ id: ProtectionModes.detect,
+ label: i18n.translate('xpack.endpoint.policy.details.detect', { defaultMessage: 'Detect' }),
+ protection: 'malware',
+ },
+ {
+ id: ProtectionModes.prevent,
+ label: i18n.translate('xpack.endpoint.policy.details.prevent', {
+ defaultMessage: 'Prevent',
+ }),
+ protection: 'malware',
+ },
+ {
+ id: ProtectionModes.preventNotify,
+ label: i18n.translate('xpack.endpoint.policy.details.preventAndNotify', {
+ defaultMessage: 'Prevent and notify user',
+ }),
+ protection: 'malware',
+ },
+ ];
+ }, []);
+
+ const handleSwitchChange = useCallback(
+ event => {
+ if (policyDetailsConfig) {
+ const newPayload = clone(policyDetailsConfig);
+ if (event.target.checked === false) {
+ for (const os of OSes) {
+ newPayload[os][protection].mode = ProtectionModes.off;
+ }
+ } else {
+ for (const os of OSes) {
+ newPayload[os][protection].mode = ProtectionModes.prevent;
+ }
+ }
+ dispatch({
+ type: 'userChangedPolicyConfig',
+ payload: { policyConfig: newPayload },
+ });
+ }
+ },
+ [dispatch, policyDetailsConfig]
+ );
+
+ const RadioButtons = () => {
+ return (
+ <>
+
+
+
+
+
+
+
+ {radios.map(radio => {
+ return (
+
+ );
+ })}
+
+ >
+ );
+ };
+
+ const ProtectionSwitch = () => {
+ return (
+
+ );
+ };
+
+ return (
+
+ );
+});
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/event_log/generated/mappings.json b/x-pack/plugins/event_log/generated/mappings.json
index 50e7fdd5a9048..d0e4652c2828e 100644
--- a/x-pack/plugins/event_log/generated/mappings.json
+++ b/x-pack/plugins/event_log/generated/mappings.json
@@ -8,7 +8,7 @@
"ignore_above": 1024,
"type": "keyword",
"meta": {
- "isArray": true
+ "isArray": "true"
}
},
"message": {
diff --git a/x-pack/plugins/event_log/scripts/create_schemas.js b/x-pack/plugins/event_log/scripts/create_schemas.js
index b46f7f295ddc7..2432a27e5c70d 100755
--- a/x-pack/plugins/event_log/scripts/create_schemas.js
+++ b/x-pack/plugins/event_log/scripts/create_schemas.js
@@ -117,7 +117,7 @@ function augmentMappings(mappings, multiValuedProperties) {
const fullProp = replaceDotWithProperties(prop);
const metaPropName = `${fullProp}.meta`;
const meta = lodash.get(mappings.properties, metaPropName) || {};
- meta.isArray = true;
+ meta.isArray = 'true';
lodash.set(mappings.properties, metaPropName, meta);
}
}
@@ -127,7 +127,7 @@ function generateSchemaLines(lineWriter, prop, mappings) {
if (mappings == null) return;
if (StringTypes.has(mappings.type)) {
- if (mappings.meta && mappings.meta.isArray) {
+ if (mappings.meta && mappings.meta.isArray === 'true') {
lineWriter.addLine(`${propKey}: ecsStringMulti(),`);
} else {
lineWriter.addLine(`${propKey}: ecsString(),`);
diff --git a/x-pack/plugins/event_log/server/types.ts b/x-pack/plugins/event_log/server/types.ts
index 4cace59752f36..f606bb2be6c6c 100644
--- a/x-pack/plugins/event_log/server/types.ts
+++ b/x-pack/plugins/event_log/server/types.ts
@@ -13,7 +13,7 @@ import { IEvent } from '../generated/schemas';
export const ConfigSchema = schema.object({
enabled: schema.boolean({ defaultValue: true }),
logEntries: schema.boolean({ defaultValue: false }),
- indexEntries: schema.boolean({ defaultValue: false }),
+ indexEntries: schema.boolean({ defaultValue: true }),
});
export type IEventLogConfig = TypeOf;
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/spaces/server/lib/migrations/migrate_6x.ts b/x-pack/plugins/index_management/common/types/aliases.ts
similarity index 57%
rename from x-pack/legacy/plugins/spaces/server/lib/migrations/migrate_6x.ts
rename to x-pack/plugins/index_management/common/types/aliases.ts
index 0c080a8dabb0a..76aae8585c065 100644
--- a/x-pack/legacy/plugins/spaces/server/lib/migrations/migrate_6x.ts
+++ b/x-pack/plugins/index_management/common/types/aliases.ts
@@ -4,9 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export function migrateToKibana660(doc: Record) {
- if (!doc.attributes.hasOwnProperty('disabledFeatures')) {
- doc.attributes.disabledFeatures = [];
- }
- return doc;
+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