diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.chartactioncontext.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.chartactioncontext.md
index 1c9fc27d53f19..9447c8a4e50a7 100644
--- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.chartactioncontext.md
+++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.chartactioncontext.md
@@ -7,5 +7,5 @@
Signature:
```typescript
-export declare type ChartActionContext = ValueClickContext | RangeSelectContext;
+export declare type ChartActionContext = ValueClickContext | RangeSelectContext | RowClickContext;
```
diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.isembeddable.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.isembeddable.md
new file mode 100644
index 0000000000000..ea8d3870dc055
--- /dev/null
+++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.isembeddable.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-embeddable-public](./kibana-plugin-plugins-embeddable-public.md) > [isEmbeddable](./kibana-plugin-plugins-embeddable-public.isembeddable.md)
+
+## isEmbeddable variable
+
+Signature:
+
+```typescript
+isEmbeddable: (x: unknown) => x is IEmbeddable
+```
diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.isrowclicktriggercontext.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.isrowclicktriggercontext.md
new file mode 100644
index 0000000000000..91e0f988db69c
--- /dev/null
+++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.isrowclicktriggercontext.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-embeddable-public](./kibana-plugin-plugins-embeddable-public.md) > [isRowClickTriggerContext](./kibana-plugin-plugins-embeddable-public.isrowclicktriggercontext.md)
+
+## isRowClickTriggerContext variable
+
+Signature:
+
+```typescript
+isRowClickTriggerContext: (context: ChartActionContext) => context is RowClickContext
+```
diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.md
index 06f792837e4fe..f1ea605703e59 100644
--- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.md
+++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.md
@@ -78,7 +78,9 @@
| [defaultEmbeddableFactoryProvider](./kibana-plugin-plugins-embeddable-public.defaultembeddablefactoryprovider.md) | |
| [EmbeddableRenderer](./kibana-plugin-plugins-embeddable-public.embeddablerenderer.md) | Helper react component to render an embeddable Can be used if you have an embeddable object or an embeddable factory Supports updating input by passing input
prop |
| [isContextMenuTriggerContext](./kibana-plugin-plugins-embeddable-public.iscontextmenutriggercontext.md) | |
+| [isEmbeddable](./kibana-plugin-plugins-embeddable-public.isembeddable.md) | |
| [isRangeSelectTriggerContext](./kibana-plugin-plugins-embeddable-public.israngeselecttriggercontext.md) | |
+| [isRowClickTriggerContext](./kibana-plugin-plugins-embeddable-public.isrowclicktriggercontext.md) | |
| [isValueClickTriggerContext](./kibana-plugin-plugins-embeddable-public.isvalueclicktriggercontext.md) | |
| [PANEL\_BADGE\_TRIGGER](./kibana-plugin-plugins-embeddable-public.panel_badge_trigger.md) | |
| [PANEL\_NOTIFICATION\_TRIGGER](./kibana-plugin-plugins-embeddable-public.panel_notification_trigger.md) | |
diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionrenderhandler._constructor_.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionrenderhandler._constructor_.md
index fcccd3f6b9618..1565202e84674 100644
--- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionrenderhandler._constructor_.md
+++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionrenderhandler._constructor_.md
@@ -9,7 +9,7 @@ Constructs a new instance of the `ExpressionRenderHandler` class
Signature:
```typescript
-constructor(element: HTMLElement, { onRenderError, renderMode }?: Partial);
+constructor(element: HTMLElement, { onRenderError, renderMode, hasCompatibleActions, }?: ExpressionRenderHandlerParams);
```
## Parameters
@@ -17,5 +17,5 @@ constructor(element: HTMLElement, { onRenderError, renderMode }?: PartialHTMLElement | |
-| { onRenderError, renderMode } | Partial<ExpressionRenderHandlerParams>
| |
+| { onRenderError, renderMode, hasCompatibleActions, } | ExpressionRenderHandlerParams
| |
diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionrenderhandler.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionrenderhandler.md
index 12c663273bd8c..d65c06bdaed83 100644
--- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionrenderhandler.md
+++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionrenderhandler.md
@@ -14,7 +14,7 @@ export declare class ExpressionRenderHandler
| Constructor | Modifiers | Description |
| --- | --- | --- |
-| [(constructor)(element, { onRenderError, renderMode })](./kibana-plugin-plugins-expressions-public.expressionrenderhandler._constructor_.md) | | Constructs a new instance of the ExpressionRenderHandler
class |
+| [(constructor)(element, { onRenderError, renderMode, hasCompatibleActions, })](./kibana-plugin-plugins-expressions-public.expressionrenderhandler._constructor_.md) | | Constructs a new instance of the ExpressionRenderHandler
class |
## Properties
@@ -24,7 +24,7 @@ export declare class ExpressionRenderHandler
| [events$](./kibana-plugin-plugins-expressions-public.expressionrenderhandler.events_.md) | | Observable<ExpressionRendererEvent>
| |
| [getElement](./kibana-plugin-plugins-expressions-public.expressionrenderhandler.getelement.md) | | () => HTMLElement
| |
| [handleRenderError](./kibana-plugin-plugins-expressions-public.expressionrenderhandler.handlerendererror.md) | | (error: ExpressionRenderError) => void
| |
-| [render](./kibana-plugin-plugins-expressions-public.expressionrenderhandler.render.md) | | (data: any, uiState?: any) => Promise<void>
| |
+| [render](./kibana-plugin-plugins-expressions-public.expressionrenderhandler.render.md) | | (value: any, uiState?: any) => Promise<void>
| |
| [render$](./kibana-plugin-plugins-expressions-public.expressionrenderhandler.render_.md) | | Observable<number>
| |
| [update$](./kibana-plugin-plugins-expressions-public.expressionrenderhandler.update_.md) | | Observable<UpdateValue | null>
| |
diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionrenderhandler.render.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionrenderhandler.render.md
index dec17d60ffd14..87f378fd58344 100644
--- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionrenderhandler.render.md
+++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionrenderhandler.render.md
@@ -7,5 +7,5 @@
Signature:
```typescript
-render: (data: any, uiState?: any) => Promise;
+render: (value: any, uiState?: any) => Promise;
```
diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.hascompatibleactions.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.hascompatibleactions.md
new file mode 100644
index 0000000000000..4d2b76cb323fb
--- /dev/null
+++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.hascompatibleactions.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-expressions-public](./kibana-plugin-plugins-expressions-public.md) > [IExpressionLoaderParams](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.md) > [hasCompatibleActions](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.hascompatibleactions.md)
+
+## IExpressionLoaderParams.hasCompatibleActions property
+
+Signature:
+
+```typescript
+hasCompatibleActions?: ExpressionRenderHandlerParams['hasCompatibleActions'];
+```
diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.md
index 54eecad0deb50..22a73fff039e6 100644
--- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.md
+++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.md
@@ -19,6 +19,7 @@ export interface IExpressionLoaderParams
| [customRenderers](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.customrenderers.md) | []
| |
| [debug](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.debug.md) | boolean
| |
| [disableCaching](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.disablecaching.md) | boolean
| |
+| [hasCompatibleActions](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.hascompatibleactions.md) | ExpressionRenderHandlerParams['hasCompatibleActions']
| |
| [inspectorAdapters](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.inspectoradapters.md) | Adapters
| |
| [onRenderError](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.onrendererror.md) | RenderErrorHandlerFnType
| |
| [renderMode](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.rendermode.md) | RenderMode
| |
diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.hascompatibleactions.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.hascompatibleactions.md
new file mode 100644
index 0000000000000..d178af55ae2d9
--- /dev/null
+++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.hascompatibleactions.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-expressions-public](./kibana-plugin-plugins-expressions-public.md) > [IInterpreterRenderHandlers](./kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.md) > [hasCompatibleActions](./kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.hascompatibleactions.md)
+
+## IInterpreterRenderHandlers.hasCompatibleActions property
+
+Signature:
+
+```typescript
+hasCompatibleActions?: (event: any) => Promise;
+```
diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.md
index a65e025451636..931e474a41006 100644
--- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.md
+++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.md
@@ -17,6 +17,7 @@ export interface IInterpreterRenderHandlers
| [done](./kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.done.md) | () => void
| Done increments the number of rendering successes |
| [event](./kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.event.md) | (event: any) => void
| |
| [getRenderMode](./kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.getrendermode.md) | () => RenderMode
| |
+| [hasCompatibleActions](./kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.hascompatibleactions.md) | (event: any) => Promise<boolean>
| |
| [onDestroy](./kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.ondestroy.md) | (fn: () => void) => void
| |
| [reload](./kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.reload.md) | () => void
| |
| [uiState](./kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.uistate.md) | PersistedState
| |
diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.hascompatibleactions.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.hascompatibleactions.md
new file mode 100644
index 0000000000000..55419279f5d21
--- /dev/null
+++ b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.hascompatibleactions.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-expressions-server](./kibana-plugin-plugins-expressions-server.md) > [IInterpreterRenderHandlers](./kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.md) > [hasCompatibleActions](./kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.hascompatibleactions.md)
+
+## IInterpreterRenderHandlers.hasCompatibleActions property
+
+Signature:
+
+```typescript
+hasCompatibleActions?: (event: any) => Promise;
+```
diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.md
index b1496386944fa..273703cacca06 100644
--- a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.md
+++ b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.md
@@ -17,6 +17,7 @@ export interface IInterpreterRenderHandlers
| [done](./kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.done.md) | () => void
| Done increments the number of rendering successes |
| [event](./kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.event.md) | (event: any) => void
| |
| [getRenderMode](./kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.getrendermode.md) | () => RenderMode
| |
+| [hasCompatibleActions](./kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.hascompatibleactions.md) | (event: any) => Promise<boolean>
| |
| [onDestroy](./kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.ondestroy.md) | (fn: () => void) => void
| |
| [reload](./kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.reload.md) | () => void
| |
| [uiState](./kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.uistate.md) | PersistedState
| |
diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.md
index 5e10de4e0f2a5..fd1ea7df4fb74 100644
--- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.md
+++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.md
@@ -26,6 +26,7 @@
| [Action](./kibana-plugin-plugins-ui_actions-public.action.md) | |
| [ActionContextMapping](./kibana-plugin-plugins-ui_actions-public.actioncontextmapping.md) | |
| [ActionExecutionMeta](./kibana-plugin-plugins-ui_actions-public.actionexecutionmeta.md) | During action execution we can provide additional information, for example, trigger, that caused the action execution |
+| [RowClickContext](./kibana-plugin-plugins-ui_actions-public.rowclickcontext.md) | |
| [Trigger](./kibana-plugin-plugins-ui_actions-public.trigger.md) | This is a convenience interface used to register a \*trigger\*.Trigger
specifies a named anchor to which Action
can be attached. When Trigger
is being \*called\* it creates a Context
object and passes it to the execute
method of an Action
.More than one action can be attached to a single trigger, in which case when trigger is \*called\* it first displays a context menu for user to pick a single action to execute. |
| [TriggerContextMapping](./kibana-plugin-plugins-ui_actions-public.triggercontextmapping.md) | |
| [UiActionsActionDefinition](./kibana-plugin-plugins-ui_actions-public.uiactionsactiondefinition.md) | A convenience interface used to register an action. |
@@ -42,6 +43,8 @@
| [ACTION\_VISUALIZE\_LENS\_FIELD](./kibana-plugin-plugins-ui_actions-public.action_visualize_lens_field.md) | |
| [APPLY\_FILTER\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.apply_filter_trigger.md) | |
| [applyFilterTrigger](./kibana-plugin-plugins-ui_actions-public.applyfiltertrigger.md) | |
+| [ROW\_CLICK\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.row_click_trigger.md) | |
+| [rowClickTrigger](./kibana-plugin-plugins-ui_actions-public.rowclicktrigger.md) | |
| [SELECT\_RANGE\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.select_range_trigger.md) | |
| [selectRangeTrigger](./kibana-plugin-plugins-ui_actions-public.selectrangetrigger.md) | |
| [VALUE\_CLICK\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.value_click_trigger.md) | |
diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.row_click_trigger.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.row_click_trigger.md
new file mode 100644
index 0000000000000..3541b53ab1d61
--- /dev/null
+++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.row_click_trigger.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-ui\_actions-public](./kibana-plugin-plugins-ui_actions-public.md) > [ROW\_CLICK\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.row_click_trigger.md)
+
+## ROW\_CLICK\_TRIGGER variable
+
+Signature:
+
+```typescript
+ROW_CLICK_TRIGGER = "ROW_CLICK_TRIGGER"
+```
diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.rowclickcontext.data.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.rowclickcontext.data.md
new file mode 100644
index 0000000000000..1068cc9146893
--- /dev/null
+++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.rowclickcontext.data.md
@@ -0,0 +1,15 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-ui\_actions-public](./kibana-plugin-plugins-ui_actions-public.md) > [RowClickContext](./kibana-plugin-plugins-ui_actions-public.rowclickcontext.md) > [data](./kibana-plugin-plugins-ui_actions-public.rowclickcontext.data.md)
+
+## RowClickContext.data property
+
+Signature:
+
+```typescript
+data: {
+ rowIndex: number;
+ table: Datatable;
+ columns?: string[];
+ };
+```
diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.rowclickcontext.embeddable.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.rowclickcontext.embeddable.md
new file mode 100644
index 0000000000000..e8baf44ff9cbc
--- /dev/null
+++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.rowclickcontext.embeddable.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-ui\_actions-public](./kibana-plugin-plugins-ui_actions-public.md) > [RowClickContext](./kibana-plugin-plugins-ui_actions-public.rowclickcontext.md) > [embeddable](./kibana-plugin-plugins-ui_actions-public.rowclickcontext.embeddable.md)
+
+## RowClickContext.embeddable property
+
+Signature:
+
+```typescript
+embeddable?: IEmbeddable;
+```
diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.rowclickcontext.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.rowclickcontext.md
new file mode 100644
index 0000000000000..74b55d85f10e3
--- /dev/null
+++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.rowclickcontext.md
@@ -0,0 +1,19 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-ui\_actions-public](./kibana-plugin-plugins-ui_actions-public.md) > [RowClickContext](./kibana-plugin-plugins-ui_actions-public.rowclickcontext.md)
+
+## RowClickContext interface
+
+Signature:
+
+```typescript
+export interface RowClickContext
+```
+
+## Properties
+
+| Property | Type | Description |
+| --- | --- | --- |
+| [data](./kibana-plugin-plugins-ui_actions-public.rowclickcontext.data.md) | {
rowIndex: number;
table: Datatable;
columns?: string[];
}
| |
+| [embeddable](./kibana-plugin-plugins-ui_actions-public.rowclickcontext.embeddable.md) | IEmbeddable
| |
+
diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.rowclicktrigger.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.rowclicktrigger.md
new file mode 100644
index 0000000000000..aa1097d8c0864
--- /dev/null
+++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.rowclicktrigger.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-ui\_actions-public](./kibana-plugin-plugins-ui_actions-public.md) > [rowClickTrigger](./kibana-plugin-plugins-ui_actions-public.rowclicktrigger.md)
+
+## rowClickTrigger variable
+
+Signature:
+
+```typescript
+rowClickTrigger: Trigger<'ROW_CLICK_TRIGGER'>
+```
diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.triggercontextmapping.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.triggercontextmapping.md
index 9db44d4dc7b05..2f0d22cf6dd74 100644
--- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.triggercontextmapping.md
+++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.triggercontextmapping.md
@@ -16,6 +16,7 @@ export interface TriggerContextMapping
| --- | --- | --- |
| [""](./kibana-plugin-plugins-ui_actions-public.triggercontextmapping.__.md) | TriggerContext
| |
| [FILTER\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.triggercontextmapping.filter_trigger.md) | ApplyGlobalFilterActionContext
| |
+| [ROW\_CLICK\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.triggercontextmapping.row_click_trigger.md) | RowClickContext
| |
| [SELECT\_RANGE\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.triggercontextmapping.select_range_trigger.md) | RangeSelectContext
| |
| [VALUE\_CLICK\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.triggercontextmapping.value_click_trigger.md) | ValueClickContext
| |
| [VISUALIZE\_FIELD\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.triggercontextmapping.visualize_field_trigger.md) | VisualizeFieldContext
| |
diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.triggercontextmapping.row_click_trigger.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.triggercontextmapping.row_click_trigger.md
new file mode 100644
index 0000000000000..cf253df337378
--- /dev/null
+++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.triggercontextmapping.row_click_trigger.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-ui\_actions-public](./kibana-plugin-plugins-ui_actions-public.md) > [TriggerContextMapping](./kibana-plugin-plugins-ui_actions-public.triggercontextmapping.md) > [ROW\_CLICK\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.triggercontextmapping.row_click_trigger.md)
+
+## TriggerContextMapping.ROW\_CLICK\_TRIGGER property
+
+Signature:
+
+```typescript
+[ROW_CLICK_TRIGGER]: RowClickContext;
+```
diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.addtriggeraction.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.addtriggeraction.md
index fd6ade88479af..ca999322b7a56 100644
--- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.addtriggeraction.md
+++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.addtriggeraction.md
@@ -11,5 +11,5 @@
Signature:
```typescript
-readonly addTriggerAction: (triggerId: T, action: ActionDefinition | Action) => void;
+readonly addTriggerAction: (triggerId: T, action: ActionDefinition | Action) => void;
```
diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.attachaction.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.attachaction.md
index 19f215a96b23b..e95e7e1eb38b6 100644
--- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.attachaction.md
+++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.attachaction.md
@@ -7,5 +7,5 @@
Signature:
```typescript
-readonly attachAction: (triggerId: T, actionId: string) => void;
+readonly attachAction: (triggerId: T, actionId: string) => void;
```
diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.executetriggeractions.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.executetriggeractions.md
index 1bb6ca1115248..8e7fb8b8bbf29 100644
--- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.executetriggeractions.md
+++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.executetriggeractions.md
@@ -12,5 +12,5 @@
Signature:
```typescript
-readonly executeTriggerActions: (triggerId: T, context: TriggerContext) => Promise;
+readonly executeTriggerActions: (triggerId: T, context: TriggerContext) => Promise;
```
diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettrigger.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettrigger.md
index d44dc4e43a52e..b996620686a28 100644
--- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettrigger.md
+++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettrigger.md
@@ -7,5 +7,5 @@
Signature:
```typescript
-readonly getTrigger: (triggerId: T) => TriggerContract;
+readonly getTrigger: (triggerId: T) => TriggerContract;
```
diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggeractions.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggeractions.md
index 0a9b674a45de2..f94b34ecc2d90 100644
--- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggeractions.md
+++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggeractions.md
@@ -7,5 +7,5 @@
Signature:
```typescript
-readonly getTriggerActions: (triggerId: T) => Action[];
+readonly getTriggerActions: (triggerId: T) => Action[];
```
diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggercompatibleactions.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggercompatibleactions.md
index faed81236342d..dff958608ef9e 100644
--- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggercompatibleactions.md
+++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggercompatibleactions.md
@@ -7,5 +7,5 @@
Signature:
```typescript
-readonly getTriggerCompatibleActions: (triggerId: T, context: TriggerContextMapping[T]) => Promise[]>;
+readonly getTriggerCompatibleActions: (triggerId: T, context: TriggerContextMapping[T]) => Promise[]>;
```
diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.md
index e3c5dbb92ae90..e35eb503ab62b 100644
--- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.md
+++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.md
@@ -21,17 +21,17 @@ export declare class UiActionsService
| Property | Modifiers | Type | Description |
| --- | --- | --- | --- |
| [actions](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.actions.md) | | ActionRegistry
| |
-| [addTriggerAction](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.addtriggeraction.md) | | <T extends "" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER" | "FILTER_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER">(triggerId: T, action: ActionDefinition<TriggerContextMapping[T]> | Action<TriggerContextMapping[T], "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_EXPORT_CSV">) => void
| addTriggerAction
is similar to attachAction
as it attaches action to a trigger, but it also registers the action, if it has not been registered, yet.addTriggerAction
also infers better typing of the action
argument. |
-| [attachAction](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.attachaction.md) | | <T extends "" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER" | "FILTER_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER">(triggerId: T, actionId: string) => void
| |
+| [addTriggerAction](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.addtriggeraction.md) | | <T extends "" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER" | "ROW_CLICK_TRIGGER" | "FILTER_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER">(triggerId: T, action: ActionDefinition<TriggerContextMapping[T]> | Action<TriggerContextMapping[T], "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_EXPORT_CSV">) => void
| addTriggerAction
is similar to attachAction
as it attaches action to a trigger, but it also registers the action, if it has not been registered, yet.addTriggerAction
also infers better typing of the action
argument. |
+| [attachAction](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.attachaction.md) | | <T extends "" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER" | "ROW_CLICK_TRIGGER" | "FILTER_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER">(triggerId: T, actionId: string) => void
| |
| [clear](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.clear.md) | | () => void
| Removes all registered triggers and actions. |
| [detachAction](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.detachaction.md) | | (triggerId: TriggerId, actionId: string) => void
| |
-| [executeTriggerActions](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.executetriggeractions.md) | | <T extends "" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER" | "FILTER_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER">(triggerId: T, context: TriggerContext<T>) => Promise<void>
| |
+| [executeTriggerActions](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.executetriggeractions.md) | | <T extends "" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER" | "ROW_CLICK_TRIGGER" | "FILTER_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER">(triggerId: T, context: TriggerContext<T>) => Promise<void>
| |
| [executionService](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.executionservice.md) | | UiActionsExecutionService
| |
| [fork](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.fork.md) | | () => UiActionsService
| "Fork" a separate instance of UiActionsService
that inherits all existing triggers and actions, but going forward all new triggers and actions added to this instance of UiActionsService
are only available within this instance. |
| [getAction](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.getaction.md) | | <T extends ActionDefinition<{}>>(id: string) => Action<ActionContext<T>, "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_EXPORT_CSV">
| |
-| [getTrigger](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettrigger.md) | | <T extends "" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER" | "FILTER_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER">(triggerId: T) => TriggerContract<T>
| |
-| [getTriggerActions](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggeractions.md) | | <T extends "" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER" | "FILTER_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER">(triggerId: T) => Action<TriggerContextMapping[T], "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_EXPORT_CSV">[]
| |
-| [getTriggerCompatibleActions](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggercompatibleactions.md) | | <T extends "" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER" | "FILTER_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER">(triggerId: T, context: TriggerContextMapping[T]) => Promise<Action<TriggerContextMapping[T], "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_EXPORT_CSV">[]>
| |
+| [getTrigger](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettrigger.md) | | <T extends "" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER" | "ROW_CLICK_TRIGGER" | "FILTER_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER">(triggerId: T) => TriggerContract<T>
| |
+| [getTriggerActions](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggeractions.md) | | <T extends "" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER" | "ROW_CLICK_TRIGGER" | "FILTER_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER">(triggerId: T) => Action<TriggerContextMapping[T], "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_EXPORT_CSV">[]
| |
+| [getTriggerCompatibleActions](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggercompatibleactions.md) | | <T extends "" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER" | "ROW_CLICK_TRIGGER" | "FILTER_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER">(triggerId: T, context: TriggerContextMapping[T]) => Promise<Action<TriggerContextMapping[T], "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_EXPORT_CSV">[]>
| |
| [hasAction](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.hasaction.md) | | (actionId: string) => boolean
| |
| [registerAction](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.registeraction.md) | | <A extends ActionDefinition<{}>>(definition: A) => Action<ActionContext<A>, "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_EXPORT_CSV">
| |
| [registerTrigger](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.registertrigger.md) | | (trigger: Trigger) => void
| |
diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc
index 6cd848e963431..8b50fc38167d3 100644
--- a/docs/setup/settings.asciidoc
+++ b/docs/setup/settings.asciidoc
@@ -453,11 +453,11 @@ deprecation warning at startup. This setting cannot end in a slash (`/`).
| `server.cors.enabled:`
| experimental[] Set to `true` to allow cross-origin API calls. *Default:* `false`
-| `server.cors.credentials:`
+| `server.cors.allowCredentials:`
| experimental[] Set to `true` to allow browser code to access response body whenever request performed with user credentials. *Default:* `false`
-| `server.cors.origin:`
- | experimental[] List of origins permitted to access resources. You must specify explicit hostnames and not use `*` for `server.cors.origin` when `server.cors.credentials: true`. *Default:* "*"
+| `server.cors.allowOrigin:`
+ | experimental[] List of origins permitted to access resources. You must specify explicit hostnames and not use `server.cors.allowOrigin: ["*"]` when `server.cors.allowCredentials: true`. *Default:* ["*"]
| `server.compression.referrerWhitelist:`
| Specifies an array of trusted hostnames, such as the {kib} host, or a reverse
diff --git a/docs/user/dashboard/drilldowns.asciidoc b/docs/user/dashboard/drilldowns.asciidoc
index ff2c321f667c8..3db5bd6d97ff0 100644
--- a/docs/user/dashboard/drilldowns.asciidoc
+++ b/docs/user/dashboard/drilldowns.asciidoc
@@ -233,7 +233,7 @@ image:images/url_drilldown_go_to_github.gif[Drilldown on pie chart that navigate
https://github.com/elastic/kibana/issues?q=is:issue+is:open+{{event.value}}
----
+
-The example URL navigates to {kib} issues on Github. `{{event.value}}` is substituted with a value associated with a selected pie slice. In *URL preview*, `{{event.value}}` is substituted with a <> value.
+The example URL navigates to {kib} issues on Github. `{{event.value}}` is substituted with a value associated with a selected pie slice.
+
[role="screenshot"]
image:images/url_drilldown_url_template.png[URL template input]
diff --git a/docs/user/dashboard/images/url_drilldown_url_template.png b/docs/user/dashboard/images/url_drilldown_url_template.png
index d8515afe66a80..746ce62733618 100644
Binary files a/docs/user/dashboard/images/url_drilldown_url_template.png and b/docs/user/dashboard/images/url_drilldown_url_template.png differ
diff --git a/docs/user/dashboard/url-drilldown.asciidoc b/docs/user/dashboard/url-drilldown.asciidoc
index 617fae938f8f5..df9fa2dca81fd 100644
--- a/docs/user/dashboard/url-drilldown.asciidoc
+++ b/docs/user/dashboard/url-drilldown.asciidoc
@@ -146,17 +146,7 @@ The URL drilldown template has three sources for variables:
* *Context* variables that change depending on where the drilldown is created and used. These variables are extracted from a context of a panel on a dashboard. For example, `{{context.panel.filters}}` gives access to filters that applied to the current panel.
* *Event* variables that depend on the trigger context. These variables are dynamically extracted from the interaction context when the drilldown is executed.
-[[values-in-preview]]
-A subtle but important difference between *context* and *event* variables is that *context* variables use real values in previews when creating a URL drilldown.
-For example, `{{context.panel.filters}}` are previewed with the current filters that applied to a panel.
-*Event* variables are extracted during drilldown execution from a user interaction with a panel (for example, from a pie slice that the user clicked on).
-
-Because there is no user interaction with a panel in preview, there is no interaction context to use in a preview.
-To work around this, {kib} provides a sample interaction that relies on a trigger.
-So in a preview, you might notice that `{{event.value}}` is replaced with `{{event.value}}` instead of with a sample from your data.
-Such previews can help you make sure that the structure of your URL template is valid.
-However, to ensure that the configured URL drilldown works as expected with your data, you have to save the dashboard and test in the panel.
-
+To ensure that the configured URL drilldown works as expected with your data, you have to save the dashboard and test in the panel.
You can access the full list of variables available for the current panel and selected trigger by clicking *Add variable* in the top-right corner of a URL template input.
[float]
@@ -241,6 +231,22 @@ Note:
`{{event.value}}` is a shorthand for `{{event.points.[0].value}}` +
`{{event.key}}` is a shorthand for `{{event.points.[0].key}}`
+| *Row click*
+| event.rowIndex
+| Number, representing the row that was clicked, starting from 0.
+
+|
+| event.values
+| An array of all cell values for the raw on which the action will execute.
+
+|
+| event.keys
+| An array of field names for each column.
+
+|
+| event.columnNames
+| An array of column names.
+
| *Range selection*
| event.from +
event.to
diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml
index 27d7f1af89275..95c425a81c5c9 100644
--- a/packages/kbn-optimizer/limits.yml
+++ b/packages/kbn-optimizer/limits.yml
@@ -83,10 +83,10 @@ pageLoadAssetSize:
transform: 41007
triggersActionsUi: 170001
uiActions: 97717
- uiActionsEnhanced: 349511
+ uiActionsEnhanced: 313011
upgradeAssistant: 81241
uptime: 40825
- urlDrilldown: 34174
+ urlDrilldown: 70674
urlForwarding: 32579
usageCollection: 39762
visDefaultEditor: 50178
diff --git a/src/core/server/http/__snapshots__/http_config.test.ts.snap b/src/core/server/http/__snapshots__/http_config.test.ts.snap
index a440c67944fab..9b667f888771e 100644
--- a/src/core/server/http/__snapshots__/http_config.test.ts.snap
+++ b/src/core/server/http/__snapshots__/http_config.test.ts.snap
@@ -39,9 +39,11 @@ Object {
"enabled": true,
},
"cors": Object {
- "credentials": false,
+ "allowCredentials": false,
+ "allowOrigin": Array [
+ "*",
+ ],
"enabled": false,
- "origin": "*",
},
"customResponseHeaders": Object {},
"host": "localhost",
diff --git a/src/core/server/http/http_config.test.ts b/src/core/server/http/http_config.test.ts
index f893e7783ac8f..b71763e8a2e14 100644
--- a/src/core/server/http/http_config.test.ts
+++ b/src/core/server/http/http_config.test.ts
@@ -331,51 +331,67 @@ describe('with compression', () => {
});
describe('cors', () => {
- describe('origin', () => {
+ describe('allowOrigin', () => {
it('list cannot be empty', () => {
expect(() =>
config.schema.validate({
cors: {
- origin: [],
+ allowOrigin: [],
},
})
).toThrowErrorMatchingInlineSnapshot(`
- "[cors.origin]: types that failed validation:
- - [cors.origin.0]: expected value to equal [*]
- - [cors.origin.1]: array size is [0], but cannot be smaller than [1]"
- `);
+ "[cors.allowOrigin]: types that failed validation:
+ - [cors.allowOrigin.0]: array size is [0], but cannot be smaller than [1]
+ - [cors.allowOrigin.1]: array size is [0], but cannot be smaller than [1]"
+ `);
});
it('list of valid URLs', () => {
- const origin = ['http://127.0.0.1:3000', 'https://elastic.co'];
+ const allowOrigin = ['http://127.0.0.1:3000', 'https://elastic.co'];
expect(
config.schema.validate({
- cors: { origin },
- }).cors.origin
- ).toStrictEqual(origin);
+ cors: { allowOrigin },
+ }).cors.allowOrigin
+ ).toStrictEqual(allowOrigin);
expect(() =>
config.schema.validate({
cors: {
- origin: ['*://elastic.co/*'],
+ allowOrigin: ['*://elastic.co/*'],
},
})
).toThrow();
});
it('can be configured as "*" wildcard', () => {
- expect(config.schema.validate({ cors: { origin: '*' } }).cors.origin).toBe('*');
+ expect(config.schema.validate({ cors: { allowOrigin: ['*'] } }).cors.allowOrigin).toEqual([
+ '*',
+ ]);
+ });
+
+ it('cannot mix wildcard "*" with valid URLs', () => {
+ expect(
+ () =>
+ config.schema.validate({ cors: { allowOrigin: ['*', 'https://elastic.co'] } }).cors
+ .allowOrigin
+ ).toThrowErrorMatchingInlineSnapshot(`
+ "[cors.allowOrigin]: types that failed validation:
+ - [cors.allowOrigin.0.0]: expected URI with scheme [http|https].
+ - [cors.allowOrigin.1.1]: expected value to equal [*]"
+ `);
});
});
describe('credentials', () => {
- it('cannot use wildcard origin if "credentials: true"', () => {
+ it('cannot use wildcard allowOrigin if "credentials: true"', () => {
expect(
- () => config.schema.validate({ cors: { credentials: true, origin: '*' } }).cors.origin
+ () =>
+ config.schema.validate({ cors: { allowCredentials: true, allowOrigin: ['*'] } }).cors
+ .allowOrigin
).toThrowErrorMatchingInlineSnapshot(
`"[cors]: Cannot specify wildcard origin \\"*\\" with \\"credentials: true\\". Please provide a list of allowed origins."`
);
expect(
- () => config.schema.validate({ cors: { credentials: true } }).cors.origin
+ () => config.schema.validate({ cors: { allowCredentials: true } }).cors.allowOrigin
).toThrowErrorMatchingInlineSnapshot(
`"[cors]: Cannot specify wildcard origin \\"*\\" with \\"credentials: true\\". Please provide a list of allowed origins."`
);
diff --git a/src/core/server/http/http_config.ts b/src/core/server/http/http_config.ts
index 74cdbfbedeea9..2bd296fe338ab 100644
--- a/src/core/server/http/http_config.ts
+++ b/src/core/server/http/http_config.ts
@@ -48,17 +48,20 @@ export const config = {
cors: schema.object(
{
enabled: schema.boolean({ defaultValue: false }),
- credentials: schema.boolean({ defaultValue: false }),
- origin: schema.oneOf(
- [schema.literal('*'), schema.arrayOf(hostURISchema, { minSize: 1 })],
+ allowCredentials: schema.boolean({ defaultValue: false }),
+ allowOrigin: schema.oneOf(
+ [
+ schema.arrayOf(hostURISchema, { minSize: 1 }),
+ schema.arrayOf(schema.literal('*'), { minSize: 1, maxSize: 1 }),
+ ],
{
- defaultValue: '*',
+ defaultValue: ['*'],
}
),
},
{
validate(value) {
- if (value.credentials === true && value.origin === '*') {
+ if (value.allowCredentials === true && value.allowOrigin.includes('*')) {
return 'Cannot specify wildcard origin "*" with "credentials: true". Please provide a list of allowed origins.';
}
},
@@ -168,8 +171,8 @@ export class HttpConfig {
public port: number;
public cors: {
enabled: boolean;
- credentials: boolean;
- origin: '*' | string[];
+ allowCredentials: boolean;
+ allowOrigin: string[];
};
public customResponseHeaders: Record;
public maxPayload: ByteSizeValue;
diff --git a/src/core/server/http/http_tools.test.ts b/src/core/server/http/http_tools.test.ts
index 4098b631b19d8..962c2107513b5 100644
--- a/src/core/server/http/http_tools.test.ts
+++ b/src/core/server/http/http_tools.test.ts
@@ -196,8 +196,8 @@ describe('getServerOptions', () => {
config.schema.validate({
cors: {
enabled: true,
- credentials: false,
- origin: '*',
+ allowCredentials: false,
+ allowOrigin: ['*'],
},
}),
{} as any,
@@ -206,7 +206,7 @@ describe('getServerOptions', () => {
expect(getServerOptions(httpConfig).routes?.cors).toEqual({
credentials: false,
- origin: '*',
+ origin: ['*'],
headers: ['Accept', 'Authorization', 'Content-Type', 'If-None-Match', 'kbn-xsrf'],
});
});
diff --git a/src/core/server/http/http_tools.ts b/src/core/server/http/http_tools.ts
index 61688a51345b5..8bec26f31fa26 100644
--- a/src/core/server/http/http_tools.ts
+++ b/src/core/server/http/http_tools.ts
@@ -39,8 +39,8 @@ const corsAllowedHeaders = ['Accept', 'Authorization', 'Content-Type', 'If-None-
export function getServerOptions(config: HttpConfig, { configureTLS = true } = {}) {
const cors: RouteOptionsCors | false = config.cors.enabled
? {
- credentials: config.cors.credentials,
- origin: config.cors.origin,
+ credentials: config.cors.allowCredentials,
+ origin: config.cors.allowOrigin,
headers: corsAllowedHeaders,
}
: false;
diff --git a/src/plugins/embeddable/public/index.ts b/src/plugins/embeddable/public/index.ts
index 0d2dcf208f2ef..0fc7c7965010b 100644
--- a/src/plugins/embeddable/public/index.ts
+++ b/src/plugins/embeddable/public/index.ts
@@ -54,6 +54,7 @@ export {
ErrorEmbeddable,
IContainer,
IEmbeddable,
+ isEmbeddable,
isErrorEmbeddable,
openAddPanelFlyout,
OutputSpec,
@@ -70,6 +71,7 @@ export {
isSavedObjectEmbeddableInput,
isRangeSelectTriggerContext,
isValueClickTriggerContext,
+ isRowClickTriggerContext,
isContextMenuTriggerContext,
EmbeddableStateTransfer,
EmbeddableEditorState,
diff --git a/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts b/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts
index 5a73df2e13861..a19ec2345db8d 100644
--- a/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts
+++ b/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts
@@ -33,7 +33,7 @@ export { EmbeddableInput };
export interface EmbeddableOutput {
// Whether the embeddable is actively loading.
loading?: boolean;
- // Whether the embeddable finshed loading with an error.
+ // Whether the embeddable finished loading with an error.
error?: EmbeddableError;
editUrl?: string;
editApp?: string;
diff --git a/src/plugins/embeddable/public/lib/embeddables/index.ts b/src/plugins/embeddable/public/lib/embeddables/index.ts
index 2f6de1be60c9c..4c2baa3bbf809 100644
--- a/src/plugins/embeddable/public/lib/embeddables/index.ts
+++ b/src/plugins/embeddable/public/lib/embeddables/index.ts
@@ -17,6 +17,7 @@
* under the License.
*/
export { EmbeddableOutput, EmbeddableInput, IEmbeddable } from './i_embeddable';
+export { isEmbeddable } from './is_embeddable';
export { Embeddable } from './embeddable';
export * from './embeddable_factory';
export * from './embeddable_factory_definition';
diff --git a/src/plugins/embeddable/public/lib/embeddables/is_embeddable.ts b/src/plugins/embeddable/public/lib/embeddables/is_embeddable.ts
new file mode 100644
index 0000000000000..e660fdbc4472c
--- /dev/null
+++ b/src/plugins/embeddable/public/lib/embeddables/is_embeddable.ts
@@ -0,0 +1,29 @@
+/*
+ * 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 { IEmbeddable } from './i_embeddable';
+
+export const isEmbeddable = (x: unknown): x is IEmbeddable => {
+ if (!x) return false;
+ if (typeof x !== 'object') return false;
+ if (typeof (x as IEmbeddable).id !== 'string') return false;
+ if (typeof (x as IEmbeddable).getInput !== 'function') return false;
+ if (typeof (x as IEmbeddable).supportedTriggers !== 'function') return false;
+ return true;
+};
diff --git a/src/plugins/embeddable/public/lib/triggers/triggers.ts b/src/plugins/embeddable/public/lib/triggers/triggers.ts
index b2965b55dbdfa..c3b1496b8eca8 100644
--- a/src/plugins/embeddable/public/lib/triggers/triggers.ts
+++ b/src/plugins/embeddable/public/lib/triggers/triggers.ts
@@ -19,7 +19,7 @@
import { i18n } from '@kbn/i18n';
import { Datatable } from '../../../../expressions';
-import { Trigger } from '../../../../ui_actions/public';
+import { Trigger, RowClickContext } from '../../../../ui_actions/public';
import { IEmbeddable } from '..';
export interface EmbeddableContext {
@@ -52,7 +52,8 @@ export interface RangeSelectContext {
export type ChartActionContext =
| ValueClickContext
- | RangeSelectContext;
+ | RangeSelectContext
+ | RowClickContext;
export const CONTEXT_MENU_TRIGGER = 'CONTEXT_MENU_TRIGGER';
export const contextMenuTrigger: Trigger<'CONTEXT_MENU_TRIGGER'> = {
@@ -95,6 +96,11 @@ export const isRangeSelectTriggerContext = (
context: ChartActionContext
): context is RangeSelectContext => context.data && 'range' in context.data;
+export const isRowClickTriggerContext = (context: ChartActionContext): context is RowClickContext =>
+ !!context.data &&
+ typeof context.data === 'object' &&
+ typeof (context as RowClickContext).data.rowIndex === 'number';
+
export const isContextMenuTriggerContext = (context: unknown): context is EmbeddableContext =>
!!context &&
typeof context === 'object' &&
diff --git a/src/plugins/embeddable/public/public.api.md b/src/plugins/embeddable/public/public.api.md
index e42eaaf86bdf3..4b7d60b4dc9ec 100644
--- a/src/plugins/embeddable/public/public.api.md
+++ b/src/plugins/embeddable/public/public.api.md
@@ -176,10 +176,11 @@ export class AttributeService>;
}
+// Warning: (ae-forgotten-export) The symbol "RowClickContext" needs to be exported by the entry point index.d.ts
// Warning: (ae-missing-release-tag) "ChartActionContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
-export type ChartActionContext = ValueClickContext | RangeSelectContext;
+export type ChartActionContext = ValueClickContext | RangeSelectContext | RowClickContext;
// Warning: (ae-missing-release-tag) "Container" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
@@ -726,6 +727,11 @@ export interface IEmbeddable context is EmbeddableContext;
+// Warning: (ae-missing-release-tag) "isEmbeddable" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
+//
+// @public (undocumented)
+export const isEmbeddable: (x: unknown) => x is IEmbeddable;
+
// Warning: (ae-missing-release-tag) "isErrorEmbeddable" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
@@ -741,6 +747,11 @@ export const isRangeSelectTriggerContext: (context: ChartActionContext) => conte
// @public (undocumented)
export function isReferenceOrValueEmbeddable(incoming: unknown): incoming is ReferenceOrValueEmbeddable;
+// Warning: (ae-missing-release-tag) "isRowClickTriggerContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
+//
+// @public (undocumented)
+export const isRowClickTriggerContext: (context: ChartActionContext) => context is RowClickContext;
+
// Warning: (ae-missing-release-tag) "isSavedObjectEmbeddableInput" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
diff --git a/src/plugins/expressions/common/expression_renderers/types.ts b/src/plugins/expressions/common/expression_renderers/types.ts
index dd3124c7d17ee..88aca4c07ee31 100644
--- a/src/plugins/expressions/common/expression_renderers/types.ts
+++ b/src/plugins/expressions/common/expression_renderers/types.ts
@@ -82,6 +82,7 @@ export interface IInterpreterRenderHandlers {
reload: () => void;
update: (params: any) => void;
event: (event: any) => void;
+ hasCompatibleActions?: (event: any) => Promise;
getRenderMode: () => RenderMode;
uiState?: PersistedState;
}
diff --git a/src/plugins/expressions/public/loader.ts b/src/plugins/expressions/public/loader.ts
index 983a344c0e1a1..e9e0fa18af6c3 100644
--- a/src/plugins/expressions/public/loader.ts
+++ b/src/plugins/expressions/public/loader.ts
@@ -64,6 +64,7 @@ export class ExpressionLoader {
this.renderHandler = new ExpressionRenderHandler(element, {
onRenderError: params && params.onRenderError,
renderMode: params?.renderMode,
+ hasCompatibleActions: params?.hasCompatibleActions,
});
this.render$ = this.renderHandler.render$;
this.update$ = this.renderHandler.update$;
diff --git a/src/plugins/expressions/public/public.api.md b/src/plugins/expressions/public/public.api.md
index 97ff00db0966c..6eb0e71c58e3f 100644
--- a/src/plugins/expressions/public/public.api.md
+++ b/src/plugins/expressions/public/public.api.md
@@ -532,7 +532,7 @@ export interface ExpressionRenderError extends Error {
// @public (undocumented)
export class ExpressionRenderHandler {
// Warning: (ae-forgotten-export) The symbol "ExpressionRenderHandlerParams" needs to be exported by the entry point index.d.ts
- constructor(element: HTMLElement, { onRenderError, renderMode }?: Partial);
+ constructor(element: HTMLElement, { onRenderError, renderMode, hasCompatibleActions, }?: ExpressionRenderHandlerParams);
// (undocumented)
destroy: () => void;
// (undocumented)
@@ -544,7 +544,7 @@ export class ExpressionRenderHandler {
// (undocumented)
render$: Observable;
// (undocumented)
- render: (data: any, uiState?: any) => Promise;
+ render: (value: any, uiState?: any) => Promise;
// Warning: (ae-forgotten-export) The symbol "UpdateValue" needs to be exported by the entry point index.d.ts
//
// (undocumented)
@@ -888,6 +888,8 @@ export interface IExpressionLoaderParams {
// (undocumented)
disableCaching?: boolean;
// (undocumented)
+ hasCompatibleActions?: ExpressionRenderHandlerParams['hasCompatibleActions'];
+ // (undocumented)
inspectorAdapters?: Adapters;
// Warning: (ae-forgotten-export) The symbol "RenderErrorHandlerFnType" needs to be exported by the entry point index.d.ts
//
@@ -917,6 +919,8 @@ export interface IInterpreterRenderHandlers {
// (undocumented)
getRenderMode: () => RenderMode;
// (undocumented)
+ hasCompatibleActions?: (event: any) => Promise;
+ // (undocumented)
onDestroy: (fn: () => void) => void;
// (undocumented)
reload: () => void;
diff --git a/src/plugins/expressions/public/render.test.ts b/src/plugins/expressions/public/render.test.ts
index c44683f6779c0..3fc0100db489d 100644
--- a/src/plugins/expressions/public/render.test.ts
+++ b/src/plugins/expressions/public/render.test.ts
@@ -126,6 +126,31 @@ describe('ExpressionRenderHandler', () => {
expect(getHandledError()!.message).toEqual('renderer error');
});
+ it('should pass through provided "hasCompatibleActions" to the expression renderer', async () => {
+ const hasCompatibleActions = jest.fn();
+ (getRenderersRegistry as jest.Mock).mockReturnValueOnce({ get: () => true });
+ (getRenderersRegistry as jest.Mock).mockReturnValueOnce({
+ get: () => ({
+ render: (domNode: HTMLElement, config: unknown, handlers: IInterpreterRenderHandlers) => {
+ handlers.hasCompatibleActions!({
+ foo: 'bar',
+ });
+ },
+ }),
+ });
+
+ const expressionRenderHandler = new ExpressionRenderHandler(element, {
+ onRenderError: mockMockErrorRenderFunction,
+ hasCompatibleActions,
+ });
+ expect(hasCompatibleActions).toHaveBeenCalledTimes(0);
+ await expressionRenderHandler.render({ type: 'render', as: 'something' });
+ expect(hasCompatibleActions).toHaveBeenCalledTimes(1);
+ expect(hasCompatibleActions.mock.calls[0][0]).toEqual({
+ foo: 'bar',
+ });
+ });
+
it('sends a next observable once rendering is complete', () => {
const expressionRenderHandler = new ExpressionRenderHandler(element);
expect.assertions(1);
diff --git a/src/plugins/expressions/public/render.ts b/src/plugins/expressions/public/render.ts
index 4390033b5be60..717776a2861b4 100644
--- a/src/plugins/expressions/public/render.ts
+++ b/src/plugins/expressions/public/render.ts
@@ -29,8 +29,9 @@ import { getRenderersRegistry } from './services';
export type IExpressionRendererExtraHandlers = Record;
export interface ExpressionRenderHandlerParams {
- onRenderError: RenderErrorHandlerFnType;
- renderMode: RenderMode;
+ onRenderError?: RenderErrorHandlerFnType;
+ renderMode?: RenderMode;
+ hasCompatibleActions?: (event: ExpressionRendererEvent) => Promise;
}
export interface ExpressionRendererEvent {
@@ -59,7 +60,11 @@ export class ExpressionRenderHandler {
constructor(
element: HTMLElement,
- { onRenderError, renderMode }: Partial = {}
+ {
+ onRenderError,
+ renderMode,
+ hasCompatibleActions = async () => false,
+ }: ExpressionRenderHandlerParams = {}
) {
this.element = element;
@@ -96,17 +101,18 @@ export class ExpressionRenderHandler {
getRenderMode: () => {
return renderMode || 'display';
},
+ hasCompatibleActions,
};
}
- render = async (data: any, uiState: any = {}) => {
- if (!data || typeof data !== 'object') {
+ render = async (value: any, uiState: any = {}) => {
+ if (!value || typeof value !== 'object') {
return this.handleRenderError(new Error('invalid data provided to the expression renderer'));
}
- if (data.type !== 'render' || !data.as) {
- if (data.type === 'error') {
- return this.handleRenderError(data.error);
+ if (value.type !== 'render' || !value.as) {
+ if (value.type === 'error') {
+ return this.handleRenderError(value.error);
} else {
return this.handleRenderError(
new Error('invalid data provided to the expression renderer')
@@ -114,15 +120,15 @@ export class ExpressionRenderHandler {
}
}
- if (!getRenderersRegistry().get(data.as)) {
- return this.handleRenderError(new Error(`invalid renderer id '${data.as}'`));
+ if (!getRenderersRegistry().get(value.as)) {
+ return this.handleRenderError(new Error(`invalid renderer id '${value.as}'`));
}
try {
// Rendering is asynchronous, completed by handlers.done()
await getRenderersRegistry()
- .get(data.as)!
- .render(this.element, data.value, {
+ .get(value.as)!
+ .render(this.element, value.value, {
...this.handlers,
uiState,
} as any);
@@ -152,7 +158,7 @@ export class ExpressionRenderHandler {
export function render(
element: HTMLElement,
data: any,
- options?: Partial
+ options?: ExpressionRenderHandlerParams
): ExpressionRenderHandler {
const handler = new ExpressionRenderHandler(element, options);
handler.render(data);
diff --git a/src/plugins/expressions/public/types/index.ts b/src/plugins/expressions/public/types/index.ts
index 5bae985699476..f37107abbb716 100644
--- a/src/plugins/expressions/public/types/index.ts
+++ b/src/plugins/expressions/public/types/index.ts
@@ -25,6 +25,7 @@ import {
SerializableState,
RenderMode,
} from '../../common';
+import { ExpressionRenderHandlerParams } from '../render';
/**
* @deprecated
@@ -56,6 +57,7 @@ export interface IExpressionLoaderParams {
onRenderError?: RenderErrorHandlerFnType;
searchSessionId?: string;
renderMode?: RenderMode;
+ hasCompatibleActions?: ExpressionRenderHandlerParams['hasCompatibleActions'];
}
export interface ExpressionRenderError extends Error {
diff --git a/src/plugins/expressions/server/server.api.md b/src/plugins/expressions/server/server.api.md
index 761ddba8f9270..7c1ab11f75027 100644
--- a/src/plugins/expressions/server/server.api.md
+++ b/src/plugins/expressions/server/server.api.md
@@ -736,6 +736,8 @@ export interface IInterpreterRenderHandlers {
// (undocumented)
getRenderMode: () => RenderMode;
// (undocumented)
+ hasCompatibleActions?: (event: any) => Promise;
+ // (undocumented)
onDestroy: (fn: () => void) => void;
// (undocumented)
reload: () => void;
diff --git a/src/plugins/ui_actions/public/index.ts b/src/plugins/ui_actions/public/index.ts
index b9f4a4a0426bf..d223c0abcccb7 100644
--- a/src/plugins/ui_actions/public/index.ts
+++ b/src/plugins/ui_actions/public/index.ts
@@ -50,6 +50,9 @@ export {
visualizeFieldTrigger,
VISUALIZE_GEO_FIELD_TRIGGER,
visualizeGeoFieldTrigger,
+ ROW_CLICK_TRIGGER,
+ rowClickTrigger,
+ RowClickContext,
} from './triggers';
export {
TriggerContextMapping,
diff --git a/src/plugins/ui_actions/public/plugin.ts b/src/plugins/ui_actions/public/plugin.ts
index 87a1df959ccbd..fdb75e9a426e9 100644
--- a/src/plugins/ui_actions/public/plugin.ts
+++ b/src/plugins/ui_actions/public/plugin.ts
@@ -23,6 +23,7 @@ import { UiActionsService } from './service';
import {
selectRangeTrigger,
valueClickTrigger,
+ rowClickTrigger,
applyFilterTrigger,
visualizeFieldTrigger,
visualizeGeoFieldTrigger,
@@ -48,6 +49,7 @@ export class UiActionsPlugin implements Plugin {
public setup(core: CoreSetup): UiActionsSetup {
this.service.registerTrigger(selectRangeTrigger);
this.service.registerTrigger(valueClickTrigger);
+ this.service.registerTrigger(rowClickTrigger);
this.service.registerTrigger(applyFilterTrigger);
this.service.registerTrigger(visualizeFieldTrigger);
this.service.registerTrigger(visualizeGeoFieldTrigger);
diff --git a/src/plugins/ui_actions/public/public.api.md b/src/plugins/ui_actions/public/public.api.md
index ca27e19b247c2..2384dfab13c8c 100644
--- a/src/plugins/ui_actions/public/public.api.md
+++ b/src/plugins/ui_actions/public/public.api.md
@@ -133,6 +133,32 @@ export class IncompatibleActionError extends Error {
// @public (undocumented)
export function plugin(initializerContext: PluginInitializerContext): UiActionsPlugin;
+// Warning: (ae-missing-release-tag) "ROW_CLICK_TRIGGER" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
+//
+// @public (undocumented)
+export const ROW_CLICK_TRIGGER = "ROW_CLICK_TRIGGER";
+
+// Warning: (ae-missing-release-tag) "RowClickContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
+//
+// @public (undocumented)
+export interface RowClickContext {
+ // (undocumented)
+ data: {
+ rowIndex: number;
+ table: Datatable;
+ columns?: string[];
+ };
+ // Warning: (ae-forgotten-export) The symbol "IEmbeddable" needs to be exported by the entry point index.d.ts
+ //
+ // (undocumented)
+ embeddable?: IEmbeddable;
+}
+
+// Warning: (ae-missing-release-tag) "rowClickTrigger" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
+//
+// @public (undocumented)
+export const rowClickTrigger: Trigger<'ROW_CLICK_TRIGGER'>;
+
// Warning: (ae-missing-release-tag) "SELECT_RANGE_TRIGGER" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
@@ -170,6 +196,8 @@ export interface TriggerContextMapping {
//
// (undocumented)
[APPLY_FILTER_TRIGGER]: ApplyGlobalFilterActionContext;
+ // (undocumented)
+ [ROW_CLICK_TRIGGER]: RowClickContext;
// Warning: (ae-forgotten-export) The symbol "RangeSelectContext" needs to be exported by the entry point index.d.ts
//
// (undocumented)
@@ -234,14 +262,14 @@ export class UiActionsService {
//
// (undocumented)
protected readonly actions: ActionRegistry;
- readonly addTriggerAction: (triggerId: T, action: UiActionsActionDefinition | Action) => void;
+ readonly addTriggerAction: (triggerId: T, action: UiActionsActionDefinition | Action) => void;
// (undocumented)
- readonly attachAction: (triggerId: T, actionId: string) => void;
+ readonly attachAction: (triggerId: T, actionId: string) => void;
readonly clear: () => void;
// (undocumented)
readonly detachAction: (triggerId: TriggerId, actionId: string) => void;
// @deprecated (undocumented)
- readonly executeTriggerActions: (triggerId: T, context: TriggerContext) => Promise;
+ readonly executeTriggerActions: (triggerId: T, context: TriggerContext) => Promise;
// Warning: (ae-forgotten-export) The symbol "UiActionsExecutionService" needs to be exported by the entry point index.d.ts
//
// (undocumented)
@@ -252,11 +280,11 @@ export class UiActionsService {
// Warning: (ae-forgotten-export) The symbol "TriggerContract" needs to be exported by the entry point index.d.ts
//
// (undocumented)
- readonly getTrigger: (triggerId: T) => TriggerContract;
+ readonly getTrigger: (triggerId: T) => TriggerContract;
// (undocumented)
- readonly getTriggerActions: (triggerId: T) => Action[];
+ readonly getTriggerActions: (triggerId: T) => Action[];
// (undocumented)
- readonly getTriggerCompatibleActions: (triggerId: T, context: TriggerContextMapping[T]) => Promise[]>;
+ readonly getTriggerCompatibleActions: (triggerId: T, context: TriggerContextMapping[T]) => Promise[]>;
// (undocumented)
readonly hasAction: (actionId: string) => boolean;
// Warning: (ae-forgotten-export) The symbol "ActionContext" needs to be exported by the entry point index.d.ts
@@ -341,6 +369,10 @@ export const visualizeFieldTrigger: Trigger<'VISUALIZE_FIELD_TRIGGER'>;
export const visualizeGeoFieldTrigger: Trigger<'VISUALIZE_GEO_FIELD_TRIGGER'>;
+// Warnings were encountered during analysis:
+//
+// src/plugins/ui_actions/public/triggers/row_click_trigger.ts:45:5 - (ae-forgotten-export) The symbol "Datatable" needs to be exported by the entry point index.d.ts
+
// (No @packageDocumentation comment for this package)
```
diff --git a/src/plugins/ui_actions/public/service/ui_actions_execution_service.ts b/src/plugins/ui_actions/public/service/ui_actions_execution_service.ts
index 4f0ab52501a95..59616dcf3f38d 100644
--- a/src/plugins/ui_actions/public/service/ui_actions_execution_service.ts
+++ b/src/plugins/ui_actions/public/service/ui_actions_execution_service.ts
@@ -29,6 +29,7 @@ interface ExecuteActionTask {
context: BaseContext;
trigger: Trigger;
defer: Defer;
+ alwaysShowPopup?: boolean;
}
export class UiActionsExecutionService {
@@ -37,21 +38,25 @@ export class UiActionsExecutionService {
constructor() {}
- async execute({
- action,
- context,
- trigger,
- }: {
- action: Action;
- context: BaseContext;
- trigger: Trigger;
- }): Promise {
+ async execute(
+ {
+ action,
+ context,
+ trigger,
+ }: {
+ action: Action;
+ context: BaseContext;
+ trigger: Trigger;
+ },
+ alwaysShowPopup?: boolean
+ ): Promise {
const shouldBatch = !(await action.shouldAutoExecute?.({ ...context, trigger })) ?? false;
const task: ExecuteActionTask = {
action,
context,
trigger,
defer: createDefer(),
+ alwaysShowPopup: !!alwaysShowPopup,
};
if (shouldBatch) {
@@ -84,11 +89,23 @@ export class UiActionsExecutionService {
setTimeout(() => {
if (this.pendingTasks.size === 0) {
const tasks = uniqBy(this.batchingQueue, (t) => t.action.id);
- if (tasks.length === 1) {
- this.executeSingleTask(tasks[0]);
- }
- if (tasks.length > 1) {
- this.executeMultipleActions(tasks);
+ if (tasks.length > 0) {
+ let alwaysShowPopup = false;
+ for (const task of tasks) {
+ if (task.alwaysShowPopup) {
+ alwaysShowPopup = true;
+ break;
+ }
+ }
+ if (alwaysShowPopup) {
+ this.showActionPopupMenu(tasks);
+ } else {
+ if (tasks.length === 1) {
+ this.executeSingleTask(tasks[0]);
+ } else if (tasks.length > 1) {
+ this.showActionPopupMenu(tasks);
+ }
+ }
}
this.batchingQueue.splice(0, this.batchingQueue.length);
@@ -108,7 +125,7 @@ export class UiActionsExecutionService {
}
}
- private async executeMultipleActions(tasks: ExecuteActionTask[]) {
+ private async showActionPopupMenu(tasks: ExecuteActionTask[]) {
const panels = await buildContextMenuForActions({
actions: tasks.map(({ action, context, trigger }) => ({
action,
diff --git a/src/plugins/ui_actions/public/tests/execute_trigger_actions.test.ts b/src/plugins/ui_actions/public/tests/execute_trigger_actions.test.ts
index af2510467ba87..51ba165ba730b 100644
--- a/src/plugins/ui_actions/public/tests/execute_trigger_actions.test.ts
+++ b/src/plugins/ui_actions/public/tests/execute_trigger_actions.test.ts
@@ -143,7 +143,32 @@ test('shows a context menu when more than one action is mapped to a trigger', as
const start = doStart();
const context = {};
- await start.executeTriggerActions('MY-TRIGGER' as TriggerId, context);
+ await start.getTrigger('MY-TRIGGER' as TriggerId)!.exec(context);
+
+ jest.runAllTimers();
+
+ await waitFor(() => {
+ expect(executeFn).toBeCalledTimes(0);
+ expect(openContextMenu).toHaveBeenCalledTimes(1);
+ });
+});
+
+test('shows a context menu when there is only one action mapped to a trigger and "alwaysShowPopup" is set', async () => {
+ const { setup, doStart } = uiActions;
+ const trigger: Trigger = {
+ id: 'MY-TRIGGER' as TriggerId,
+ title: 'My trigger',
+ };
+ const action1 = createTestAction('test1', () => true);
+
+ setup.registerTrigger(trigger);
+ setup.addTriggerAction(trigger.id, action1);
+
+ expect(openContextMenu).toHaveBeenCalledTimes(0);
+
+ const start = doStart();
+ const context = {};
+ await start.getTrigger('MY-TRIGGER' as TriggerId)!.exec(context, true);
jest.runAllTimers();
diff --git a/src/plugins/ui_actions/public/triggers/index.ts b/src/plugins/ui_actions/public/triggers/index.ts
index b7039d287c6e2..ecbf4d1f7b988 100644
--- a/src/plugins/ui_actions/public/triggers/index.ts
+++ b/src/plugins/ui_actions/public/triggers/index.ts
@@ -22,6 +22,7 @@ export * from './trigger_contract';
export * from './trigger_internal';
export * from './select_range_trigger';
export * from './value_click_trigger';
+export * from './row_click_trigger';
export * from './apply_filter_trigger';
export * from './visualize_field_trigger';
export * from './visualize_geo_field_trigger';
diff --git a/src/plugins/ui_actions/public/triggers/row_click_trigger.ts b/src/plugins/ui_actions/public/triggers/row_click_trigger.ts
new file mode 100644
index 0000000000000..87bca03f8c3ba
--- /dev/null
+++ b/src/plugins/ui_actions/public/triggers/row_click_trigger.ts
@@ -0,0 +1,53 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { i18n } from '@kbn/i18n';
+import { IEmbeddable } from '../../../embeddable/public';
+import { Trigger } from '.';
+import { Datatable } from '../../../expressions';
+
+export const ROW_CLICK_TRIGGER = 'ROW_CLICK_TRIGGER';
+
+export const rowClickTrigger: Trigger<'ROW_CLICK_TRIGGER'> = {
+ id: ROW_CLICK_TRIGGER,
+ title: i18n.translate('uiActions.triggers.rowClickTitle', {
+ defaultMessage: 'Table row click',
+ }),
+ description: i18n.translate('uiActions.triggers.rowClickkDescription', {
+ defaultMessage: 'A click on a table row',
+ }),
+};
+
+export interface RowClickContext {
+ embeddable?: IEmbeddable;
+ data: {
+ /**
+ * Row index, starting from 0, where user clicked.
+ */
+ rowIndex: number;
+
+ table: Datatable;
+
+ /**
+ * Sorted list column IDs that were visible to the user. Useful when only
+ * a subset of datatable columns should be used.
+ */
+ columns?: string[];
+ };
+}
diff --git a/src/plugins/ui_actions/public/triggers/trigger_contract.ts b/src/plugins/ui_actions/public/triggers/trigger_contract.ts
index ba1c5a693f937..04a75cb3a53d0 100644
--- a/src/plugins/ui_actions/public/triggers/trigger_contract.ts
+++ b/src/plugins/ui_actions/public/triggers/trigger_contract.ts
@@ -49,7 +49,7 @@ export class TriggerContract {
/**
* Use this method to execute action attached to this trigger.
*/
- public readonly exec = async (context: TriggerContextMapping[T]) => {
- await this.internal.execute(context);
+ public readonly exec = async (context: TriggerContextMapping[T], alwaysShowPopup?: boolean) => {
+ await this.internal.execute(context, alwaysShowPopup);
};
}
diff --git a/src/plugins/ui_actions/public/triggers/trigger_internal.ts b/src/plugins/ui_actions/public/triggers/trigger_internal.ts
index c766b5c798ecb..fd43a020504c0 100644
--- a/src/plugins/ui_actions/public/triggers/trigger_internal.ts
+++ b/src/plugins/ui_actions/public/triggers/trigger_internal.ts
@@ -31,17 +31,20 @@ export class TriggerInternal {
constructor(public readonly service: UiActionsService, public readonly trigger: Trigger) {}
- public async execute(context: TriggerContextMapping[T]) {
+ public async execute(context: TriggerContextMapping[T], alwaysShowPopup?: boolean) {
const triggerId = this.trigger.id;
const actions = await this.service.getTriggerCompatibleActions!(triggerId, context);
await Promise.all([
actions.map((action) =>
- this.service.executionService.execute({
- action,
- context,
- trigger: this.trigger,
- })
+ this.service.executionService.execute(
+ {
+ action,
+ context,
+ trigger: this.trigger,
+ },
+ alwaysShowPopup
+ )
),
]);
}
diff --git a/src/plugins/ui_actions/public/types.ts b/src/plugins/ui_actions/public/types.ts
index 0be3c19fc1c4d..0266a755be926 100644
--- a/src/plugins/ui_actions/public/types.ts
+++ b/src/plugins/ui_actions/public/types.ts
@@ -22,10 +22,12 @@ import { TriggerInternal } from './triggers/trigger_internal';
import {
SELECT_RANGE_TRIGGER,
VALUE_CLICK_TRIGGER,
+ ROW_CLICK_TRIGGER,
APPLY_FILTER_TRIGGER,
VISUALIZE_FIELD_TRIGGER,
VISUALIZE_GEO_FIELD_TRIGGER,
DEFAULT_TRIGGER,
+ RowClickContext,
} from './triggers';
import type { RangeSelectContext, ValueClickContext } from '../../embeddable/public';
import type { ApplyGlobalFilterActionContext } from '../../data/public';
@@ -49,6 +51,7 @@ export interface TriggerContextMapping {
[DEFAULT_TRIGGER]: TriggerContext;
[SELECT_RANGE_TRIGGER]: RangeSelectContext;
[VALUE_CLICK_TRIGGER]: ValueClickContext;
+ [ROW_CLICK_TRIGGER]: RowClickContext;
[APPLY_FILTER_TRIGGER]: ApplyGlobalFilterActionContext;
[VISUALIZE_FIELD_TRIGGER]: VisualizeFieldContext;
[VISUALIZE_GEO_FIELD_TRIGGER]: VisualizeFieldContext;
diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_editor.js b/src/plugins/vis_type_timeseries/public/application/components/vis_editor.js
index 520ad281576cd..89e7a50ab79b0 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/vis_editor.js
+++ b/src/plugins/vis_type_timeseries/public/application/components/vis_editor.js
@@ -74,20 +74,26 @@ export class VisEditor extends Component {
this.props.eventEmitter.emit('dirtyStateChange', {
isDirty: false,
});
+
+ const extractedIndexPatterns = extractIndexPatterns(this.state.model);
+ if (!isEqual(this.state.extractedIndexPatterns, extractedIndexPatterns)) {
+ this.abortableFetchFields(extractedIndexPatterns).then((visFields) => {
+ this.setState({
+ visFields,
+ extractedIndexPatterns,
+ });
+ });
+ }
}, VIS_STATE_DEBOUNCE_DELAY);
- debouncedFetchFields = debounce(
- (extractedIndexPatterns) => {
- if (this.abortControllerFetchFields) {
- this.abortControllerFetchFields.abort();
- }
- this.abortControllerFetchFields = new AbortController();
+ abortableFetchFields = (extractedIndexPatterns) => {
+ if (this.abortControllerFetchFields) {
+ this.abortControllerFetchFields.abort();
+ }
+ this.abortControllerFetchFields = new AbortController();
- return fetchFields(extractedIndexPatterns, this.abortControllerFetchFields.signal);
- },
- VIS_STATE_DEBOUNCE_DELAY,
- { leading: true }
- );
+ return fetchFields(extractedIndexPatterns, this.abortControllerFetchFields.signal);
+ };
handleChange = (partialModel) => {
if (isEmpty(partialModel)) {
@@ -105,16 +111,6 @@ export class VisEditor extends Component {
dirty = false;
}
- const extractedIndexPatterns = extractIndexPatterns(nextModel);
- if (!isEqual(this.state.extractedIndexPatterns, extractedIndexPatterns)) {
- this.debouncedFetchFields(extractedIndexPatterns).then((visFields) =>
- this.setState({
- visFields,
- extractedIndexPatterns,
- })
- );
- }
-
this.setState({
dirty,
model: nextModel,
diff --git a/src/plugins/visualizations/public/embeddable/events.ts b/src/plugins/visualizations/public/embeddable/events.ts
index 52cac59fbffaa..41e52c3ac1327 100644
--- a/src/plugins/visualizations/public/embeddable/events.ts
+++ b/src/plugins/visualizations/public/embeddable/events.ts
@@ -21,16 +21,19 @@ import {
APPLY_FILTER_TRIGGER,
SELECT_RANGE_TRIGGER,
VALUE_CLICK_TRIGGER,
-} from '../../../../plugins/ui_actions/public';
+ ROW_CLICK_TRIGGER,
+} from '../../../ui_actions/public';
export interface VisEventToTrigger {
['applyFilter']: typeof APPLY_FILTER_TRIGGER;
['brush']: typeof SELECT_RANGE_TRIGGER;
['filter']: typeof VALUE_CLICK_TRIGGER;
+ ['tableRowContextMenuClick']: typeof ROW_CLICK_TRIGGER;
}
export const VIS_EVENT_TO_TRIGGER: VisEventToTrigger = {
applyFilter: APPLY_FILTER_TRIGGER,
brush: SELECT_RANGE_TRIGGER,
filter: VALUE_CLICK_TRIGGER,
+ tableRowContextMenuClick: ROW_CLICK_TRIGGER,
};
diff --git a/x-pack/plugins/alerts/server/config.test.ts b/x-pack/plugins/alerts/server/config.test.ts
index bf3b30b5d2378..e4691ad6229a0 100644
--- a/x-pack/plugins/alerts/server/config.test.ts
+++ b/x-pack/plugins/alerts/server/config.test.ts
@@ -15,7 +15,7 @@ describe('config validation', () => {
},
"invalidateApiKeysTask": Object {
"interval": "5m",
- "removalDelay": "5m",
+ "removalDelay": "1h",
},
}
`);
diff --git a/x-pack/plugins/alerts/server/config.ts b/x-pack/plugins/alerts/server/config.ts
index 41340c7dfe5fc..e53b99852c354 100644
--- a/x-pack/plugins/alerts/server/config.ts
+++ b/x-pack/plugins/alerts/server/config.ts
@@ -13,7 +13,7 @@ export const configSchema = schema.object({
}),
invalidateApiKeysTask: schema.object({
interval: schema.string({ validate: validateDurationSchema, defaultValue: '5m' }),
- removalDelay: schema.string({ validate: validateDurationSchema, defaultValue: '5m' }),
+ removalDelay: schema.string({ validate: validateDurationSchema, defaultValue: '1h' }),
}),
});
diff --git a/x-pack/plugins/alerts/server/plugin.test.ts b/x-pack/plugins/alerts/server/plugin.test.ts
index fee7901c4ea55..48fd2e12336a8 100644
--- a/x-pack/plugins/alerts/server/plugin.test.ts
+++ b/x-pack/plugins/alerts/server/plugin.test.ts
@@ -24,7 +24,7 @@ describe('Alerting Plugin', () => {
},
invalidateApiKeysTask: {
interval: '5m',
- removalDelay: '5m',
+ removalDelay: '1h',
},
});
const plugin = new AlertingPlugin(context);
@@ -73,7 +73,7 @@ describe('Alerting Plugin', () => {
},
invalidateApiKeysTask: {
interval: '5m',
- removalDelay: '5m',
+ removalDelay: '1h',
},
});
const plugin = new AlertingPlugin(context);
@@ -124,7 +124,7 @@ describe('Alerting Plugin', () => {
},
invalidateApiKeysTask: {
interval: '5m',
- removalDelay: '5m',
+ removalDelay: '1h',
},
});
const plugin = new AlertingPlugin(context);
diff --git a/x-pack/plugins/apm/e2e/cypress/integration/snapshots.js b/x-pack/plugins/apm/e2e/cypress/integration/snapshots.js
index 0ecda7a113de7..152186a8a738a 100644
--- a/x-pack/plugins/apm/e2e/cypress/integration/snapshots.js
+++ b/x-pack/plugins/apm/e2e/cypress/integration/snapshots.js
@@ -1,3 +1,3 @@
module.exports = {
- "__version": "5.4.0"
+ "__version": "6.0.1"
}
diff --git a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/breakdown_filter.ts b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/breakdown_filter.ts
index 342f3e0aa5267..e558d1ef9c0bc 100644
--- a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/breakdown_filter.ts
+++ b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/breakdown_filter.ts
@@ -6,13 +6,13 @@
import { Given, When, Then } from 'cypress-cucumber-preprocessor/steps';
import { DEFAULT_TIMEOUT } from './csm_dashboard';
+import { waitForLoadingToFinish } from './utils';
/** The default time in ms to wait for a Cypress command to complete */
Given(`a user clicks the page load breakdown filter`, () => {
- // wait for all loading to finish
- cy.get('kbnLoadingIndicator').should('not.be.visible');
- cy.get('.euiStat__title-isLoading').should('not.be.visible');
+ waitForLoadingToFinish();
+ cy.get('.euiStat__title-isLoading').should('not.exist');
const breakDownBtn = cy.get(
'[data-test-subj=pldBreakdownFilter]',
DEFAULT_TIMEOUT
@@ -27,7 +27,7 @@ When(`the user selected the breakdown`, () => {
});
Then(`breakdown series should appear in chart`, () => {
- cy.get('.euiLoadingChart').should('not.be.visible');
+ cy.get('.euiLoadingChart').should('not.exist');
cy.get('[data-cy=pageLoadDist]').within(() => {
cy.get('div.echLegendItem__label[title=Chrome] ', DEFAULT_TIMEOUT)
diff --git a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/client_metrics_helper.ts b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/client_metrics_helper.ts
index 0b26c6de66f4b..d8d8c7c3a62e9 100644
--- a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/client_metrics_helper.ts
+++ b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/client_metrics_helper.ts
@@ -5,6 +5,7 @@
*/
import { DEFAULT_TIMEOUT } from './csm_dashboard';
+import { waitForLoadingToFinish } from './utils';
/**
* Verifies the behavior of the client metrics component
@@ -17,15 +18,14 @@ export function verifyClientMetrics(
) {
const clientMetricsSelector = '[data-cy=client-metrics] .euiStat__title';
- // wait for all loading to finish
- cy.get('kbnLoadingIndicator').should('not.be.visible');
+ waitForLoadingToFinish();
if (checkTitleStatus) {
cy.get('.euiStat__title', DEFAULT_TIMEOUT).should('be.visible');
- cy.get('.euiSelect-isLoading').should('not.be.visible');
+ cy.get('.euiSelect-isLoading').should('not.exist');
}
- cy.get('.euiStat__title-isLoading').should('not.be.visible');
+ cy.get('.euiStat__title-isLoading').should('not.exist');
cy.get(clientMetricsSelector).eq(0).should('have.text', metrics[0]);
diff --git a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/csm_dashboard.ts b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/csm_dashboard.ts
index 452d8b719b3cb..5207ea39c959f 100644
--- a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/csm_dashboard.ts
+++ b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/csm_dashboard.ts
@@ -7,6 +7,7 @@
import { Given, Then } from 'cypress-cucumber-preprocessor/steps';
import { loginAndWaitForPage } from '../../../integration/helpers';
import { verifyClientMetrics } from './client_metrics_helper';
+import { waitForLoadingToFinish } from './utils';
/** The default time in ms to wait for a Cypress command to complete */
export const DEFAULT_TIMEOUT = { timeout: 60 * 1000 };
@@ -36,9 +37,9 @@ Then(`should display percentile for page load chart`, () => {
cy.get('.euiLoadingChart', DEFAULT_TIMEOUT).should('be.visible');
- // wait for all loading to finish
- cy.get('kbnLoadingIndicator').should('not.be.visible');
- cy.get('.euiStat__title-isLoading').should('not.be.visible');
+ waitForLoadingToFinish();
+
+ cy.get('.euiStat__title-isLoading').should('not.exist');
cy.get(pMarkers).eq(0).should('have.text', '50th');
@@ -52,21 +53,19 @@ Then(`should display percentile for page load chart`, () => {
Then(`should display chart legend`, () => {
const chartLegend = 'div.echLegendItem__label';
- // wait for all loading to finish
- cy.get('kbnLoadingIndicator').should('not.be.visible');
- cy.get('.euiLoadingChart').should('not.be.visible');
+ waitForLoadingToFinish();
+ cy.get('.euiLoadingChart').should('not.exist');
cy.get(chartLegend, DEFAULT_TIMEOUT).eq(0).should('have.text', 'Overall');
});
Then(`should display tooltip on hover`, () => {
- cy.get('.euiLoadingChart').should('not.be.visible');
+ cy.get('.euiLoadingChart').should('not.exist');
const pMarkers = '[data-cy=percentile-markers] span.euiToolTipAnchor';
- // wait for all loading to finish
- cy.get('kbnLoadingIndicator').should('not.be.visible');
- cy.get('.euiLoadingChart').should('not.be.visible');
+ waitForLoadingToFinish();
+ cy.get('.euiLoadingChart').should('not.exist');
const marker = cy.get(pMarkers, DEFAULT_TIMEOUT).eq(0);
marker.invoke('show');
diff --git a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/csm_filters.ts b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/csm_filters.ts
index 88287286c66c5..9aeddad686385 100644
--- a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/csm_filters.ts
+++ b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/csm_filters.ts
@@ -7,11 +7,11 @@
import { When, Then } from 'cypress-cucumber-preprocessor/steps';
import { DEFAULT_TIMEOUT } from './csm_dashboard';
import { verifyClientMetrics } from './client_metrics_helper';
+import { waitForLoadingToFinish } from './utils';
When(/^the user filters by "([^"]*)"$/, (filterName) => {
- // wait for all loading to finish
- cy.get('kbnLoadingIndicator').should('not.be.visible');
- cy.get('.euiStat__title-isLoading').should('not.be.visible');
+ waitForLoadingToFinish();
+ cy.get('.euiStat__title-isLoading').should('not.exist');
cy.get(`#local-filter-${filterName}`).click();
cy.get(`#local-filter-popover-${filterName}`, DEFAULT_TIMEOUT).within(() => {
@@ -51,9 +51,8 @@ When(/^the user filters by "([^"]*)"$/, (filterName) => {
});
Then(/^it filters the client metrics "([^"]*)"$/, (filterName) => {
- // wait for all loading to finish
- cy.get('kbnLoadingIndicator').should('not.be.visible');
- cy.get('.euiStat__title-isLoading').should('not.be.visible');
+ waitForLoadingToFinish();
+ cy.get('.euiStat__title-isLoading').should('not.exist');
const data =
filterName === 'os'
diff --git a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/js_errors.ts b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/js_errors.ts
index 9e10e2fd59914..bc53de0bac6a7 100644
--- a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/js_errors.ts
+++ b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/js_errors.ts
@@ -9,8 +9,8 @@ import { DEFAULT_TIMEOUT } from './csm_dashboard';
import { getDataTestSubj } from './utils';
Then(`it displays list of relevant js errors`, () => {
- cy.get('.euiBasicTable-loading').should('not.be.visible');
- cy.get('.euiStat__title-isLoading').should('not.be.visible');
+ cy.get('.euiBasicTable-loading').should('not.exist');
+ cy.get('.euiStat__title-isLoading').should('not.exist');
getDataTestSubj('uxJsErrorsTotal').should('have.text', 'Total errors112');
diff --git a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/percentile_select.ts b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/percentile_select.ts
index 44802bbce6208..80b90422366d5 100644
--- a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/percentile_select.ts
+++ b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/percentile_select.ts
@@ -6,11 +6,10 @@
import { When, Then } from 'cypress-cucumber-preprocessor/steps';
import { verifyClientMetrics } from './client_metrics_helper';
-import { getDataTestSubj } from './utils';
+import { getDataTestSubj, waitForLoadingToFinish } from './utils';
When('the user changes the selected percentile', () => {
- // wait for all loading to finish
- cy.get('kbnLoadingIndicator').should('not.be.visible');
+ waitForLoadingToFinish();
getDataTestSubj('uxPercentileSelect').select('95');
});
diff --git a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/service_name_filter.ts b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/service_name_filter.ts
index 609d0d18f5bc8..5c0e8c6238238 100644
--- a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/service_name_filter.ts
+++ b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/service_name_filter.ts
@@ -7,10 +7,10 @@
import { When, Then } from 'cypress-cucumber-preprocessor/steps';
import { verifyClientMetrics } from './client_metrics_helper';
import { DEFAULT_TIMEOUT } from './csm_dashboard';
+import { waitForLoadingToFinish } from './utils';
When('the user changes the selected service name', () => {
- // wait for all loading to finish
- cy.get('kbnLoadingIndicator').should('not.be.visible');
+ waitForLoadingToFinish();
cy.get(`[data-cy=serviceNameFilter]`, DEFAULT_TIMEOUT).select('client');
});
diff --git a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/url_search_filter.ts b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/url_search_filter.ts
index 3dc98625baf85..cc9dc177d57a0 100644
--- a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/url_search_filter.ts
+++ b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/url_search_filter.ts
@@ -6,18 +6,18 @@
import { When, Then } from 'cypress-cucumber-preprocessor/steps';
import { DEFAULT_TIMEOUT } from './csm_dashboard';
+import { waitForLoadingToFinish } from './utils';
When(`a user clicks inside url search field`, () => {
- // wait for all loading to finish
- cy.get('kbnLoadingIndicator').should('not.be.visible');
- cy.get('.euiStat__title-isLoading').should('not.be.visible');
+ waitForLoadingToFinish();
+ cy.get('.euiStat__title-isLoading').should('not.exist');
cy.get('span[data-cy=csmUrlFilter]', DEFAULT_TIMEOUT).within(() => {
cy.get('input.euiFieldSearch').click();
});
});
Then(`it displays top pages in the suggestion popover`, () => {
- cy.get('kbnLoadingIndicator').should('not.be.visible');
+ waitForLoadingToFinish();
cy.get('div.euiPopover__panel-isOpen', DEFAULT_TIMEOUT).within(() => {
const listOfUrls = cy.get('li.euiSelectableListItem');
@@ -38,17 +38,17 @@ Then(`it displays top pages in the suggestion popover`, () => {
});
When(`a user enters a query in url search field`, () => {
- cy.get('kbnLoadingIndicator').should('not.be.visible');
+ waitForLoadingToFinish();
cy.get('[data-cy=csmUrlFilter]').within(() => {
cy.get('input.euiSelectableSearch').type('cus');
});
- cy.get('kbnLoadingIndicator').should('not.be.visible');
+ waitForLoadingToFinish();
});
Then(`it should filter results based on query`, () => {
- cy.get('kbnLoadingIndicator').should('not.be.visible');
+ waitForLoadingToFinish();
cy.get('div.euiPopover__panel-isOpen', DEFAULT_TIMEOUT).within(() => {
const listOfUrls = cy.get('li.euiSelectableListItem');
diff --git a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/utils.ts b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/utils.ts
index 87b3a1d70d073..0819a27ff16cb 100644
--- a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/utils.ts
+++ b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/utils.ts
@@ -6,9 +6,12 @@
import { DEFAULT_TIMEOUT } from './csm_dashboard';
+export function waitForLoadingToFinish() {
+ cy.get('[data-test-subj=globalLoadingIndicator-hidden]', DEFAULT_TIMEOUT);
+}
+
export function getDataTestSubj(dataTestSubj: string) {
- // wait for all loading to finish
- cy.get('kbnLoadingIndicator').should('not.be.visible');
+ waitForLoadingToFinish();
return cy.get(`[data-test-subj=${dataTestSubj}]`, DEFAULT_TIMEOUT);
}
diff --git a/x-pack/plugins/drilldowns/url_drilldown/public/lib/test/data.ts b/x-pack/plugins/drilldowns/url_drilldown/public/lib/test/data.ts
new file mode 100644
index 0000000000000..e0627c521bb79
--- /dev/null
+++ b/x-pack/plugins/drilldowns/url_drilldown/public/lib/test/data.ts
@@ -0,0 +1,173 @@
+/*
+ * 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 { DatatableColumnType } from '../../../../../../../src/plugins/expressions/common';
+import {
+ Embeddable,
+ EmbeddableInput,
+ EmbeddableOutput,
+} from '../../../../../../../src/plugins/embeddable/public';
+
+export const createPoint = ({
+ field,
+ value,
+}: {
+ field: string;
+ value: string | null | number | boolean;
+}) => ({
+ table: {
+ columns: [
+ {
+ name: field,
+ id: '1-1',
+ meta: {
+ type: 'date' as DatatableColumnType,
+ field,
+ source: 'esaggs',
+ sourceParams: {
+ type: 'histogram',
+ indexPatternId: 'logstash-*',
+ interval: 30,
+ otherBucket: true,
+ },
+ },
+ },
+ ],
+ rows: [
+ {
+ '1-1': '2048',
+ },
+ ],
+ },
+ column: 0,
+ row: 0,
+ value,
+});
+
+export const rowClickData = {
+ rowIndex: 1,
+ table: {
+ type: 'datatable',
+ rows: [
+ {
+ '6ced5344-2596-4545-b626-8b449924e2d4': 'IT',
+ '6890e417-c5f1-4565-a45c-92f55380e14c': '0',
+ '93b8ef16-2483-45b8-ad27-6cc1f790578b': 13,
+ 'b0c5dcc2-4012-4d7e-b983-0e089badc43c': 0,
+ 'e0719f1a-04fb-4036-a63c-c25deac3f011': 7,
+ },
+ {
+ '6ced5344-2596-4545-b626-8b449924e2d4': 'IT',
+ '6890e417-c5f1-4565-a45c-92f55380e14c': '2.25',
+ '93b8ef16-2483-45b8-ad27-6cc1f790578b': 3,
+ 'b0c5dcc2-4012-4d7e-b983-0e089badc43c': 0,
+ 'e0719f1a-04fb-4036-a63c-c25deac3f011': 2,
+ },
+ {
+ '6ced5344-2596-4545-b626-8b449924e2d4': 'IT',
+ '6890e417-c5f1-4565-a45c-92f55380e14c': '0.020939215995129826',
+ '93b8ef16-2483-45b8-ad27-6cc1f790578b': 2,
+ 'b0c5dcc2-4012-4d7e-b983-0e089badc43c': 12.490584373474121,
+ 'e0719f1a-04fb-4036-a63c-c25deac3f011': 1,
+ },
+ ],
+ columns: [
+ {
+ id: '6ced5344-2596-4545-b626-8b449924e2d4',
+ name: 'Top values of DestCountry',
+ meta: {
+ type: 'string',
+ field: 'DestCountry',
+ index: 'kibana_sample_data_flights',
+ params: {
+ id: 'terms',
+ params: {
+ id: 'string',
+ otherBucketLabel: 'Other',
+ missingBucketLabel: '(missing value)',
+ },
+ },
+ source: 'esaggs',
+ },
+ },
+ {
+ id: '6890e417-c5f1-4565-a45c-92f55380e14c',
+ name: 'Top values of FlightTimeHour',
+ meta: {
+ type: 'string',
+ field: 'FlightTimeHour',
+ index: 'kibana_sample_data_flights',
+ params: {
+ id: 'terms',
+ params: {
+ id: 'string',
+ otherBucketLabel: 'Other',
+ missingBucketLabel: '(missing value)',
+ },
+ },
+ source: 'esaggs',
+ },
+ },
+ {
+ id: '93b8ef16-2483-45b8-ad27-6cc1f790578b',
+ name: 'Count of records',
+ meta: {
+ type: 'number',
+ index: 'kibana_sample_data_flights',
+ params: {
+ id: 'number',
+ },
+ },
+ },
+ {
+ id: 'b0c5dcc2-4012-4d7e-b983-0e089badc43c',
+ name: 'Average of DistanceMiles',
+ meta: {
+ type: 'number',
+ field: 'DistanceMiles',
+ index: 'kibana_sample_data_flights',
+ params: {
+ id: 'number',
+ },
+ },
+ },
+ {
+ id: 'e0719f1a-04fb-4036-a63c-c25deac3f011',
+ name: 'Unique count of OriginAirportID',
+ meta: {
+ type: 'string',
+ field: 'OriginAirportID',
+ index: 'kibana_sample_data_flights',
+ params: {
+ id: 'number',
+ },
+ },
+ },
+ ],
+ },
+ columns: [
+ '6ced5344-2596-4545-b626-8b449924e2d4',
+ '6890e417-c5f1-4565-a45c-92f55380e14c',
+ '93b8ef16-2483-45b8-ad27-6cc1f790578b',
+ 'b0c5dcc2-4012-4d7e-b983-0e089badc43c',
+ 'e0719f1a-04fb-4036-a63c-c25deac3f011',
+ ],
+};
+
+interface TestInput extends EmbeddableInput {
+ savedObjectId?: string;
+}
+
+interface TestOutput extends EmbeddableOutput {
+ indexPatterns?: Array<{ id: string }>;
+}
+
+export class TestEmbeddable extends Embeddable {
+ type = 'test';
+
+ destroy() {}
+ reload() {}
+}
diff --git a/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.test.ts b/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.test.ts
index 79d380991f5fd..d9f63f233e1c2 100644
--- a/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.test.ts
+++ b/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.test.ts
@@ -7,6 +7,11 @@
import { UrlDrilldown, ActionContext, Config } from './url_drilldown';
import { IEmbeddable } from '../../../../../../src/plugins/embeddable/public/lib/embeddables';
import { DatatableColumnType } from '../../../../../../src/plugins/expressions/common';
+import { createPoint, rowClickData, TestEmbeddable } from './test/data';
+import {
+ VALUE_CLICK_TRIGGER,
+ ROW_CLICK_TRIGGER,
+} from '../../../../../../src/plugins/ui_actions/public';
const mockDataPoints = [
{
@@ -99,7 +104,8 @@ describe('UrlDrilldown', () => {
embeddable: mockEmbeddable,
};
- await expect(urlDrilldown.isCompatible(config, context)).resolves.toBe(true);
+ const result = urlDrilldown.isCompatible(config, context);
+ await expect(result).resolves.toBe(true);
});
test('not compatible if url is invalid', async () => {
@@ -168,4 +174,199 @@ describe('UrlDrilldown', () => {
expect(mockNavigateToUrl).not.toBeCalled();
});
});
+
+ describe('variables', () => {
+ const embeddable1 = new TestEmbeddable(
+ {
+ id: 'test',
+ title: 'The Title',
+ savedObjectId: 'SAVED_OBJECT_IDxx',
+ },
+ {
+ indexPatterns: [{ id: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' }],
+ }
+ );
+ const data: any = {
+ data: [
+ createPoint({ field: 'field0', value: 'value0' }),
+ createPoint({ field: 'field1', value: 'value1' }),
+ createPoint({ field: 'field2', value: 'value2' }),
+ ],
+ };
+
+ const embeddable2 = new TestEmbeddable(
+ {
+ id: 'the-id',
+ query: {
+ language: 'C++',
+ query: 'std::cout << 123;',
+ },
+ timeRange: {
+ from: 'FROM',
+ to: 'TO',
+ },
+ filters: [
+ {
+ meta: {
+ alias: 'asdf',
+ disabled: false,
+ negate: false,
+ },
+ },
+ ],
+ savedObjectId: 'SAVED_OBJECT_ID',
+ },
+ {
+ title: 'The Title',
+ indexPatterns: [
+ { id: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' },
+ { id: 'yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy' },
+ ],
+ }
+ );
+
+ describe('getRuntimeVariables()', () => {
+ test('builds runtime variables for VALUE_CLICK_TRIGGER trigger', () => {
+ const variables = urlDrilldown.getRuntimeVariables({
+ embeddable: embeddable1,
+ data,
+ });
+
+ expect(variables).toMatchObject({
+ kibanaUrl: 'http://localhost:5601/',
+ context: {
+ panel: {
+ id: 'test',
+ title: 'The Title',
+ savedObjectId: 'SAVED_OBJECT_IDxx',
+ indexPatternId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
+ },
+ },
+ event: {
+ key: 'field0',
+ value: 'value0',
+ negate: false,
+ points: [
+ {
+ value: 'value0',
+ key: 'field0',
+ },
+ {
+ value: 'value1',
+ key: 'field1',
+ },
+ {
+ value: 'value2',
+ key: 'field2',
+ },
+ ],
+ },
+ });
+ });
+
+ test('builds runtime variables for ROW_CLICK_TRIGGER trigger', () => {
+ const variables = urlDrilldown.getRuntimeVariables({
+ embeddable: embeddable2,
+ data: rowClickData as any,
+ });
+
+ expect(variables).toMatchObject({
+ kibanaUrl: 'http://localhost:5601/',
+ context: {
+ panel: {
+ id: 'the-id',
+ title: 'The Title',
+ savedObjectId: 'SAVED_OBJECT_ID',
+ query: {
+ language: 'C++',
+ query: 'std::cout << 123;',
+ },
+ timeRange: {
+ from: 'FROM',
+ to: 'TO',
+ },
+ filters: [
+ {
+ meta: {
+ alias: 'asdf',
+ disabled: false,
+ negate: false,
+ },
+ },
+ ],
+ },
+ },
+ event: {
+ rowIndex: 1,
+ values: ['IT', '2.25', 3, 0, 2],
+ keys: ['DestCountry', 'FlightTimeHour', '', 'DistanceMiles', 'OriginAirportID'],
+ columnNames: [
+ 'Top values of DestCountry',
+ 'Top values of FlightTimeHour',
+ 'Count of records',
+ 'Average of DistanceMiles',
+ 'Unique count of OriginAirportID',
+ ],
+ },
+ });
+ });
+ });
+
+ describe('getVariableList()', () => {
+ test('builds variable list for VALUE_CLICK_TRIGGER trigger', () => {
+ const list = urlDrilldown.getVariableList({
+ triggers: [VALUE_CLICK_TRIGGER],
+ embeddable: embeddable1,
+ });
+
+ const expectedList = [
+ 'event.key',
+ 'event.value',
+ 'event.negate',
+ 'event.points',
+
+ 'context.panel.id',
+ 'context.panel.title',
+ 'context.panel.indexPatternId',
+ 'context.panel.savedObjectId',
+
+ 'kibanaUrl',
+ ];
+
+ for (const expectedItem of expectedList) {
+ expect(list.includes(expectedItem)).toBe(true);
+ }
+ });
+
+ test('builds variable list for ROW_CLICK_TRIGGER trigger', () => {
+ const list = urlDrilldown.getVariableList({
+ triggers: [ROW_CLICK_TRIGGER],
+ embeddable: embeddable2,
+ });
+
+ const expectedList = [
+ 'event.columnNames',
+ 'event.keys',
+ 'event.rowIndex',
+ 'event.values',
+
+ 'context.panel.id',
+ 'context.panel.title',
+ 'context.panel.filters',
+ 'context.panel.query.language',
+ 'context.panel.query.query',
+ 'context.panel.indexPatternIds',
+ 'context.panel.savedObjectId',
+ 'context.panel.timeRange.from',
+ 'context.panel.timeRange.to',
+
+ 'kibanaUrl',
+ ];
+
+ for (const expectedItem of expectedList) {
+ expect(list.includes(expectedItem)).toBe(true);
+ }
+ });
+ });
+ });
});
diff --git a/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.tsx b/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.tsx
index 807dfeed21d1f..3a989c1b0b4cd 100644
--- a/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.tsx
+++ b/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.tsx
@@ -5,6 +5,7 @@
*/
import React from 'react';
+import { getFlattenedObject } from '@kbn/std';
import { reactToUiComponent } from '../../../../../../src/plugins/kibana_react/public';
import {
ChartActionContext,
@@ -13,6 +14,7 @@ import {
} from '../../../../../../src/plugins/embeddable/public';
import { CollectConfigProps as CollectConfigPropsBase } from '../../../../../../src/plugins/kibana_utils/public';
import {
+ ROW_CLICK_TRIGGER,
SELECT_RANGE_TRIGGER,
VALUE_CLICK_TRIGGER,
} from '../../../../../../src/plugins/ui_actions/public';
@@ -22,11 +24,10 @@ import {
UrlDrilldownConfig,
UrlDrilldownCollectConfig,
urlDrilldownValidateUrlTemplate,
- urlDrilldownBuildScope,
urlDrilldownCompileUrl,
UiActionsEnhancedBaseActionFactoryContext as BaseActionFactoryContext,
} from '../../../../ui_actions_enhanced/public';
-import { getContextScope, getEventScope, getMockEventScope } from './url_drilldown_scope';
+import { getPanelVariables, getEventScope, getEventVariableList } from './url_drilldown_scope';
import { txtUrlDrilldownDisplayName } from './i18n';
interface UrlDrilldownDeps {
@@ -39,9 +40,11 @@ interface UrlDrilldownDeps {
export type ActionContext = ChartActionContext;
export type Config = UrlDrilldownConfig;
export type UrlTrigger =
- | typeof CONTEXT_MENU_TRIGGER
| typeof VALUE_CLICK_TRIGGER
- | typeof SELECT_RANGE_TRIGGER;
+ | typeof SELECT_RANGE_TRIGGER
+ | typeof ROW_CLICK_TRIGGER
+ | typeof CONTEXT_MENU_TRIGGER;
+
export interface ActionFactoryContext extends BaseActionFactoryContext {
embeddable?: IEmbeddable;
}
@@ -65,7 +68,7 @@ export class UrlDrilldown implements Drilldown = ({
@@ -74,12 +77,12 @@ export class UrlDrilldown implements Drilldown {
// eslint-disable-next-line react-hooks/rules-of-hooks
- const scope = React.useMemo(() => this.buildEditorScope(context), [context]);
+ const variables = React.useMemo(() => this.getVariableList(context), [context]);
return (
@@ -93,19 +96,13 @@ export class UrlDrilldown implements Drilldown {
- const { isValid } = urlDrilldownValidateUrlTemplate(config.url, this.buildEditorScope(context));
- return isValid;
+ public readonly isConfigValid = (config: Config): config is Config => {
+ return !!config.url.template;
};
public readonly isCompatible = async (config: Config, context: ActionContext) => {
- const { isValid, error } = urlDrilldownValidateUrlTemplate(
- config.url,
- await this.buildRuntimeScope(context)
- );
+ const scope = this.getRuntimeVariables(context);
+ const { isValid, error } = urlDrilldownValidateUrlTemplate(config.url, scope);
if (!isValid) {
// eslint-disable-next-line no-console
@@ -117,11 +114,13 @@ export class UrlDrilldown implements Drilldown
- urlDrilldownCompileUrl(config.url.template, this.buildRuntimeScope(context));
+ public readonly getHref = async (config: Config, context: ActionContext) => {
+ const scope = this.getRuntimeVariables(context);
+ return urlDrilldownCompileUrl(config.url.template, scope);
+ };
public readonly execute = async (config: Config, context: ActionContext) => {
- const url = urlDrilldownCompileUrl(config.url.template, this.buildRuntimeScope(context));
+ const url = urlDrilldownCompileUrl(config.url.template, this.getRuntimeVariables(context));
if (config.openInNewTab) {
window.open(url, '_blank', 'noopener');
} else {
@@ -129,19 +128,23 @@ export class UrlDrilldown implements Drilldown {
- return urlDrilldownBuildScope({
- globalScope: this.deps.getGlobalScope(),
- contextScope: getContextScope(context),
- eventScope: getMockEventScope(context.triggers),
- });
+ public readonly getRuntimeVariables = (context: ActionContext) => {
+ return {
+ ...this.deps.getGlobalScope(),
+ context: {
+ panel: getPanelVariables(context),
+ },
+ event: getEventScope(context),
+ };
};
- private buildRuntimeScope = (context: ActionContext) => {
- return urlDrilldownBuildScope({
- globalScope: this.deps.getGlobalScope(),
- contextScope: getContextScope(context),
- eventScope: getEventScope(context),
- });
+ public readonly getVariableList = (context: ActionFactoryContext): string[] => {
+ const eventVariables = getEventVariableList(context);
+ const contextVariables = Object.keys(getFlattenedObject(getPanelVariables(context))).map(
+ (key) => 'context.panel.' + key
+ );
+ const globalVariables = Object.keys(getFlattenedObject(this.deps.getGlobalScope()));
+
+ return [...eventVariables.sort(), ...contextVariables.sort(), ...globalVariables.sort()];
};
}
diff --git a/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown_scope.test.ts b/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown_scope.test.ts
index a93e150deee8f..5917737d15eda 100644
--- a/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown_scope.test.ts
+++ b/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown_scope.test.ts
@@ -6,46 +6,15 @@
import {
getEventScope,
- getMockEventScope,
ValueClickTriggerEventScope,
+ getEventVariableList,
+ getPanelVariables,
} from './url_drilldown_scope';
-import { DatatableColumnType } from '../../../../../../src/plugins/expressions/common';
-
-const createPoint = ({
- field,
- value,
-}: {
- field: string;
- value: string | null | number | boolean;
-}) => ({
- table: {
- columns: [
- {
- name: field,
- id: '1-1',
- meta: {
- type: 'date' as DatatableColumnType,
- field,
- source: 'esaggs',
- sourceParams: {
- type: 'histogram',
- indexPatternId: 'logstash-*',
- interval: 30,
- otherBucket: true,
- },
- },
- },
- ],
- rows: [
- {
- '1-1': '2048',
- },
- ],
- },
- column: 0,
- row: 0,
- value,
-});
+import {
+ RowClickContext,
+ ROW_CLICK_TRIGGER,
+} from '../../../../../../src/plugins/ui_actions/public';
+import { createPoint, rowClickData, TestEmbeddable } from './test/data';
describe('VALUE_CLICK_TRIGGER', () => {
describe('supports `points[]`', () => {
@@ -80,33 +49,6 @@ describe('VALUE_CLICK_TRIGGER', () => {
]
`);
});
-
- test('getMockEventScope()', () => {
- const mockEventScope = getMockEventScope([
- 'VALUE_CLICK_TRIGGER',
- ]) as ValueClickTriggerEventScope;
- expect(mockEventScope.points.length).toBeGreaterThan(3);
- expect(mockEventScope.points).toMatchInlineSnapshot(`
- Array [
- Object {
- "key": "event.points.0.key",
- "value": "event.points.0.value",
- },
- Object {
- "key": "event.points.1.key",
- "value": "event.points.1.value",
- },
- Object {
- "key": "event.points.2.key",
- "value": "event.points.2.value",
- },
- Object {
- "key": "event.points.3.key",
- "value": "event.points.3.value",
- },
- ]
- `);
- });
});
describe('handles undefined, null or missing values', () => {
@@ -131,11 +73,221 @@ describe('VALUE_CLICK_TRIGGER', () => {
});
});
-describe('CONTEXT_MENU_TRIGGER', () => {
- test('getMockEventScope() results in empty scope', () => {
- const mockEventScope = getMockEventScope([
- 'CONTEXT_MENU_TRIGGER',
- ]) as ValueClickTriggerEventScope;
- expect(mockEventScope).toEqual({});
+describe('ROW_CLICK_TRIGGER', () => {
+ test('getEventVariableList() returns correct list of runtime variables', () => {
+ const vars = getEventVariableList({
+ triggers: [ROW_CLICK_TRIGGER],
+ });
+ expect(vars).toEqual(['event.rowIndex', 'event.values', 'event.keys', 'event.columnNames']);
+ });
+
+ test('getEventScope() returns correct variables for row click trigger', () => {
+ const context = ({
+ embeddable: {},
+ data: rowClickData as any,
+ } as unknown) as RowClickContext;
+ const res = getEventScope(context);
+
+ expect(res).toEqual({
+ rowIndex: 1,
+ values: ['IT', '2.25', 3, 0, 2],
+ keys: ['DestCountry', 'FlightTimeHour', '', 'DistanceMiles', 'OriginAirportID'],
+ columnNames: [
+ 'Top values of DestCountry',
+ 'Top values of FlightTimeHour',
+ 'Count of records',
+ 'Average of DistanceMiles',
+ 'Unique count of OriginAirportID',
+ ],
+ });
+ });
+});
+
+describe('getPanelVariables()', () => {
+ test('returns only ID for empty embeddable', () => {
+ const embeddable = new TestEmbeddable(
+ {
+ id: 'test',
+ },
+ {}
+ );
+ const vars = getPanelVariables({ embeddable });
+
+ expect(vars).toEqual({
+ id: 'test',
+ });
+ });
+
+ test('returns title as specified in input', () => {
+ const embeddable = new TestEmbeddable(
+ {
+ id: 'test',
+ title: 'title1',
+ },
+ {}
+ );
+ const vars = getPanelVariables({ embeddable });
+
+ expect(vars).toEqual({
+ id: 'test',
+ title: 'title1',
+ });
+ });
+
+ test('returns output title if input and output titles are specified', () => {
+ const embeddable = new TestEmbeddable(
+ {
+ id: 'test',
+ title: 'title1',
+ },
+ {
+ title: 'title2',
+ }
+ );
+ const vars = getPanelVariables({ embeddable });
+
+ expect(vars).toEqual({
+ id: 'test',
+ title: 'title2',
+ });
+ });
+
+ test('returns title from output if title in input is missing', () => {
+ const embeddable = new TestEmbeddable(
+ {
+ id: 'test',
+ },
+ {
+ title: 'title2',
+ }
+ );
+ const vars = getPanelVariables({ embeddable });
+
+ expect(vars).toEqual({
+ id: 'test',
+ title: 'title2',
+ });
+ });
+
+ test('returns saved object ID from output', () => {
+ const embeddable = new TestEmbeddable(
+ {
+ id: 'test',
+ savedObjectId: '5678',
+ },
+ {
+ savedObjectId: '1234',
+ }
+ );
+ const vars = getPanelVariables({ embeddable });
+
+ expect(vars).toEqual({
+ id: 'test',
+ savedObjectId: '1234',
+ });
+ });
+
+ test('returns saved object ID from input if it is not set on output', () => {
+ const embeddable = new TestEmbeddable(
+ {
+ id: 'test',
+ savedObjectId: '5678',
+ },
+ {}
+ );
+ const vars = getPanelVariables({ embeddable });
+
+ expect(vars).toEqual({
+ id: 'test',
+ savedObjectId: '5678',
+ });
+ });
+
+ test('returns query, timeRange and filters from input', () => {
+ const embeddable = new TestEmbeddable(
+ {
+ id: 'test',
+ query: {
+ language: 'C++',
+ query: 'std::cout << 123;',
+ },
+ timeRange: {
+ from: 'FROM',
+ to: 'TO',
+ },
+ filters: [
+ {
+ meta: {
+ alias: 'asdf',
+ disabled: false,
+ negate: false,
+ },
+ },
+ ],
+ },
+ {}
+ );
+ const vars = getPanelVariables({ embeddable });
+
+ expect(vars).toEqual({
+ id: 'test',
+ query: {
+ language: 'C++',
+ query: 'std::cout << 123;',
+ },
+ timeRange: {
+ from: 'FROM',
+ to: 'TO',
+ },
+ filters: [
+ {
+ meta: {
+ alias: 'asdf',
+ disabled: false,
+ negate: false,
+ },
+ },
+ ],
+ });
+ });
+
+ test('returns a single index pattern from output', () => {
+ const embeddable = new TestEmbeddable(
+ {
+ id: 'test',
+ },
+ {
+ indexPatterns: [{ id: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' }],
+ }
+ );
+ const vars = getPanelVariables({ embeddable });
+
+ expect(vars).toEqual({
+ id: 'test',
+ indexPatternId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
+ });
+ });
+
+ test('returns multiple index patterns from output', () => {
+ const embeddable = new TestEmbeddable(
+ {
+ id: 'test',
+ },
+ {
+ indexPatterns: [
+ { id: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' },
+ { id: 'yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy' },
+ ],
+ }
+ );
+ const vars = getPanelVariables({ embeddable });
+
+ expect(vars).toEqual({
+ id: 'test',
+ indexPatternIds: [
+ 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
+ 'yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy',
+ ],
+ });
});
});
diff --git a/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown_scope.ts b/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown_scope.ts
index 234af380689e9..3e5fc0a968d39 100644
--- a/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown_scope.ts
+++ b/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown_scope.ts
@@ -14,48 +14,54 @@ import {
IEmbeddable,
isRangeSelectTriggerContext,
isValueClickTriggerContext,
+ isRowClickTriggerContext,
isContextMenuTriggerContext,
RangeSelectContext,
ValueClickContext,
+ EmbeddableOutput,
} from '../../../../../../src/plugins/embeddable/public';
-import type { ActionContext, ActionFactoryContext, UrlTrigger } from './url_drilldown';
+import type { ActionContext, ActionFactoryContext } from './url_drilldown';
import {
SELECT_RANGE_TRIGGER,
+ RowClickContext,
VALUE_CLICK_TRIGGER,
+ ROW_CLICK_TRIGGER,
} from '../../../../../../src/plugins/ui_actions/public';
-type ContextScopeInput = ActionContext | ActionFactoryContext;
-
/**
* Part of context scope extracted from an embeddable
* Expose on the scope as: `{{context.panel.id}}`, `{{context.panel.filters.[0]}}`
*/
interface EmbeddableUrlDrilldownContextScope {
+ /**
+ * ID of the embeddable panel.
+ */
id: string;
+
+ /**
+ * Title of the embeddable panel.
+ */
title?: string;
- query?: Query;
- filters?: Filter[];
- timeRange?: TimeRange;
- savedObjectId?: string;
+
/**
- * In case panel supports only 1 index patterns
+ * In case panel supports only 1 index pattern.
*/
indexPatternId?: string;
+
/**
- * In case panel supports more then 1 index patterns
+ * In case panel supports more then 1 index pattern.
*/
indexPatternIds?: string[];
-}
-/**
- * Url drilldown context scope
- * `{{context.$}}`
- */
-interface UrlDrilldownContextScope {
- panel?: EmbeddableUrlDrilldownContextScope;
+ query?: Query;
+ filters?: Filter[];
+ timeRange?: TimeRange;
+ savedObjectId?: string;
}
-export function getContextScope(contextScopeInput: ContextScopeInput): UrlDrilldownContextScope {
+export function getPanelVariables(contextScopeInput: {
+ embeddable?: IEmbeddable;
+}): EmbeddableUrlDrilldownContextScope {
function hasEmbeddable(val: unknown): val is { embeddable: IEmbeddable } {
if (val && typeof val === 'object' && 'embeddable' in val) return true;
return false;
@@ -64,41 +70,52 @@ export function getContextScope(contextScopeInput: ContextScopeInput): UrlDrilld
throw new Error(
"UrlDrilldown [getContextScope] can't build scope because embeddable object is missing in context"
);
-
const embeddable = contextScopeInput.embeddable;
+
+ return getEmbeddableVariables(embeddable);
+}
+
+function hasSavedObjectId(obj: Record): obj is { savedObjectId: string } {
+ return 'savedObjectId' in obj && typeof obj.savedObjectId === 'string';
+}
+
+/**
+ * @todo Same functionality is implemented in x-pack/plugins/discover_enhanced/public/actions/explore_data/shared.ts,
+ * combine both implementations into a common approach.
+ */
+function getIndexPatternIds(output: EmbeddableOutput): string[] {
+ function hasIndexPatterns(
+ _output: Record
+ ): _output is { indexPatterns: Array<{ id?: string }> } {
+ return (
+ 'indexPatterns' in _output &&
+ Array.isArray(_output.indexPatterns) &&
+ _output.indexPatterns.length > 0
+ );
+ }
+ return hasIndexPatterns(output)
+ ? (output.indexPatterns.map((ip) => ip.id).filter(Boolean) as string[])
+ : [];
+}
+
+export function getEmbeddableVariables(
+ embeddable: IEmbeddable
+): EmbeddableUrlDrilldownContextScope {
const input = embeddable.getInput();
const output = embeddable.getOutput();
- function hasSavedObjectId(obj: Record): obj is { savedObjectId: string } {
- return 'savedObjectId' in obj && typeof obj.savedObjectId === 'string';
- }
- function getIndexPatternIds(): string[] {
- function hasIndexPatterns(
- _output: Record
- ): _output is { indexPatterns: Array<{ id?: string }> } {
- return (
- 'indexPatterns' in _output &&
- Array.isArray(_output.indexPatterns) &&
- _output.indexPatterns.length > 0
- );
- }
- return hasIndexPatterns(output)
- ? (output.indexPatterns.map((ip) => ip.id).filter(Boolean) as string[])
- : [];
- }
- const indexPatternsIds = getIndexPatternIds();
- return {
- panel: cleanEmptyKeys({
- id: input.id,
- title: output.title ?? input.title,
- savedObjectId:
- output.savedObjectId ?? (hasSavedObjectId(input) ? input.savedObjectId : undefined),
- query: input.query,
- timeRange: input.timeRange,
- filters: input.filters,
- indexPatternIds: indexPatternsIds.length > 1 ? indexPatternsIds : undefined,
- indexPatternId: indexPatternsIds.length === 1 ? indexPatternsIds[0] : undefined,
- }),
- };
+ const indexPatternsIds = getIndexPatternIds(output);
+
+ return deleteUndefinedKeys({
+ id: input.id,
+ title: output.title ?? input.title,
+ savedObjectId:
+ output.savedObjectId ?? (hasSavedObjectId(input) ? input.savedObjectId : undefined),
+ query: input.query,
+ timeRange: input.timeRange,
+ filters: input.filters,
+ indexPatternIds: indexPatternsIds.length > 1 ? indexPatternsIds : undefined,
+ indexPatternId: indexPatternsIds.length === 1 ? indexPatternsIds[0] : undefined,
+ });
}
/**
@@ -108,7 +125,9 @@ export function getContextScope(contextScopeInput: ContextScopeInput): UrlDrilld
export type UrlDrilldownEventScope =
| ValueClickTriggerEventScope
| RangeSelectTriggerEventScope
+ | RowClickTriggerEventScope
| ContextMenuTriggerEventScope;
+
export type EventScopeInput = ActionContext;
export interface ValueClickTriggerEventScope {
key?: string;
@@ -122,6 +141,12 @@ export interface RangeSelectTriggerEventScope {
to?: string | number;
}
+export interface RowClickTriggerEventScope {
+ rowIndex: number;
+ values: Primitive[];
+ keys: string[];
+ columnNames: string[];
+}
export type ContextMenuTriggerEventScope = object;
export function getEventScope(eventScopeInput: EventScopeInput): UrlDrilldownEventScope {
@@ -129,6 +154,8 @@ export function getEventScope(eventScopeInput: EventScopeInput): UrlDrilldownEve
return getEventScopeFromRangeSelectTriggerContext(eventScopeInput);
} else if (isValueClickTriggerContext(eventScopeInput)) {
return getEventScopeFromValueClickTriggerContext(eventScopeInput);
+ } else if (isRowClickTriggerContext(eventScopeInput)) {
+ return getEventScopeFromRowClickTriggerContext(eventScopeInput);
} else if (isContextMenuTriggerContext(eventScopeInput)) {
return {};
} else {
@@ -141,7 +168,7 @@ function getEventScopeFromRangeSelectTriggerContext(
): RangeSelectTriggerEventScope {
const { table, column: columnIndex, range } = eventScopeInput.data;
const column = table.columns[columnIndex];
- return cleanEmptyKeys({
+ return deleteUndefinedKeys({
key: toPrimitiveOrUndefined(column?.meta.field) as string,
from: toPrimitiveOrUndefined(range[0]) as string | number | undefined,
to: toPrimitiveOrUndefined(range[range.length - 1]) as string | number | undefined,
@@ -160,7 +187,7 @@ function getEventScopeFromValueClickTriggerContext(
};
});
- return cleanEmptyKeys({
+ return deleteUndefinedKeys({
key: points[0]?.key,
value: points[0]?.value,
negate,
@@ -168,37 +195,53 @@ function getEventScopeFromValueClickTriggerContext(
});
}
-/**
- * @remarks
- * Difference between `event` and `context` variables, is that real `context` variables are available during drilldown creation (e.g. embeddable panel)
- * `event` variables are mapped from trigger context. Since there is no trigger context during drilldown creation, we have to provide some _mock_ variables for validating and previewing the URL
- */
-export function getMockEventScope([trigger]: UrlTrigger[]): UrlDrilldownEventScope {
- if (trigger === SELECT_RANGE_TRIGGER) {
- return {
- key: 'event.key',
- from: new Date(Date.now() - 15 * 60 * 1000).toISOString(), // 15 minutes ago
- to: new Date().toISOString(),
- };
+function getEventScopeFromRowClickTriggerContext({
+ embeddable,
+ data,
+}: RowClickContext): RowClickTriggerEventScope {
+ const { rowIndex } = data;
+ const columns = data.columns || data.table.columns.map(({ id }) => id);
+ const values: Primitive[] = [];
+ const keys: string[] = [];
+ const columnNames: string[] = [];
+ const row = data.table.rows[rowIndex];
+
+ for (const columnId of columns) {
+ const column = data.table.columns.find(({ id }) => id === columnId);
+ if (!column) {
+ // This should never happe, but in case it does we log data necessary for debugging.
+ // eslint-disable-next-line no-console
+ console.error(data, embeddable ? `Embeddable [${embeddable.getTitle()}]` : null);
+ throw new Error('Could not find a datatable column.');
+ }
+ values.push(row[columnId]);
+ keys.push(column.meta.field || '');
+ columnNames.push(column.name || column.meta.field || '');
}
- if (trigger === VALUE_CLICK_TRIGGER) {
- // number of mock points to generate
- // should be larger or equal of any possible data points length emitted by VALUE_CLICK_TRIGGER
- const nPoints = 4;
- const points = new Array(nPoints).fill(0).map((_, index) => ({
- key: `event.points.${index}.key`,
- value: `event.points.${index}.value`,
- }));
- return {
- key: `event.key`,
- value: `event.value`,
- negate: false,
- points,
- };
+ const scope: RowClickTriggerEventScope = {
+ rowIndex,
+ values,
+ keys,
+ columnNames,
+ };
+
+ return scope;
+}
+
+export function getEventVariableList(context: ActionFactoryContext): string[] {
+ const [trigger] = context.triggers;
+
+ switch (trigger) {
+ case SELECT_RANGE_TRIGGER:
+ return ['event.key', 'event.from', 'event.to'];
+ case VALUE_CLICK_TRIGGER:
+ return ['event.key', 'event.value', 'event.negate', 'event.points'];
+ case ROW_CLICK_TRIGGER:
+ return ['event.rowIndex', 'event.values', 'event.keys', 'event.columnNames'];
}
- return {};
+ return [];
}
type Primitive = string | number | boolean | null;
@@ -210,7 +253,7 @@ function toPrimitiveOrUndefined(v: unknown): Primitive | undefined {
return String(v);
}
-function cleanEmptyKeys>(obj: T): T {
+function deleteUndefinedKeys>(obj: T): T {
Object.keys(obj).forEach((key) => {
if (obj[key] === undefined) {
delete obj[key];
diff --git a/x-pack/plugins/lens/public/datatable_visualization/__snapshots__/expression.test.tsx.snap b/x-pack/plugins/lens/public/datatable_visualization/__snapshots__/expression.test.tsx.snap
index 9c7bdc3397f9c..d340d002b242b 100644
--- a/x-pack/plugins/lens/public/datatable_visualization/__snapshots__/expression.test.tsx.snap
+++ b/x-pack/plugins/lens/public/datatable_visualization/__snapshots__/expression.test.tsx.snap
@@ -1,5 +1,60 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
+exports[`datatable_expression DatatableComponent it renders actions column when there are row actions 1`] = `
+
+
+
+`;
+
exports[`datatable_expression DatatableComponent it renders the title and value 1`] = `
{
).toMatchSnapshot();
});
+ test('it renders actions column when there are row actions', () => {
+ const { data, args } = sampleArgs();
+
+ expect(
+ shallow(
+ x as IFieldFormat}
+ onClickValue={onClickValue}
+ getType={jest.fn()}
+ onRowContextMenuClick={() => undefined}
+ rowHasRowClickTriggerActions={[true, true, true]}
+ />
+ )
+ ).toMatchSnapshot();
+ });
+
test('it invokes executeTriggerActions with correct context on click on top value', () => {
const { args, data } = sampleArgs();
diff --git a/x-pack/plugins/lens/public/datatable_visualization/expression.tsx b/x-pack/plugins/lens/public/datatable_visualization/expression.tsx
index 6502e07697816..f1eaab908717a 100644
--- a/x-pack/plugins/lens/public/datatable_visualization/expression.tsx
+++ b/x-pack/plugins/lens/public/datatable_visualization/expression.tsx
@@ -10,13 +10,22 @@ import React, { useMemo } from 'react';
import ReactDOM from 'react-dom';
import { i18n } from '@kbn/i18n';
import { I18nProvider } from '@kbn/i18n/react';
-import { EuiBasicTable, EuiFlexGroup, EuiButtonIcon, EuiFlexItem, EuiToolTip } from '@elastic/eui';
+import {
+ EuiBasicTable,
+ EuiFlexGroup,
+ EuiButtonIcon,
+ EuiFlexItem,
+ EuiToolTip,
+ EuiBasicTableColumn,
+ EuiTableActionsColumnType,
+} from '@elastic/eui';
import { IAggType } from 'src/plugins/data/public';
import {
FormatFactory,
ILensInterpreterRenderHandlers,
LensFilterEvent,
LensMultiTable,
+ LensTableRowContextMenuEvent,
} from '../types';
import {
ExpressionFunctionDefinition,
@@ -45,7 +54,14 @@ export interface DatatableProps {
type DatatableRenderProps = DatatableProps & {
formatFactory: FormatFactory;
onClickValue: (data: LensFilterEvent['data']) => void;
+ onRowContextMenuClick?: (data: LensTableRowContextMenuEvent['data']) => void;
getType: (name: string) => IAggType;
+
+ /**
+ * A boolean for each table row, which is true if the row active
+ * ROW_CLICK_TRIGGER actions attached to it, otherwise false.
+ */
+ rowHasRowClickTriggerActions?: boolean[];
};
export interface DatatableRender {
@@ -143,13 +159,47 @@ export const getDatatableRenderer = (dependencies: {
const onClickValue = (data: LensFilterEvent['data']) => {
handlers.event({ name: 'filter', data });
};
+ const onRowContextMenuClick = (data: LensTableRowContextMenuEvent['data']) => {
+ handlers.event({ name: 'tableRowContextMenuClick', data });
+ };
+ const { hasCompatibleActions } = handlers;
+
+ // An entry for each table row, whether it has any actions attached to
+ // ROW_CLICK_TRIGGER trigger.
+ let rowHasRowClickTriggerActions: boolean[] = [];
+ if (hasCompatibleActions) {
+ const table = Object.values(config.data.tables)[0];
+ if (!!table) {
+ rowHasRowClickTriggerActions = await Promise.all(
+ table.rows.map(async (row, rowIndex) => {
+ try {
+ const hasActions = await hasCompatibleActions({
+ name: 'tableRowContextMenuClick',
+ data: {
+ rowIndex,
+ table,
+ columns: config.args.columns.columnIds,
+ },
+ });
+
+ return hasActions;
+ } catch {
+ return false;
+ }
+ })
+ );
+ }
+ }
+
ReactDOM.render(
,
domNode,
@@ -169,7 +219,7 @@ export function DatatableComponent(props: DatatableRenderProps) {
formatters[column.id] = props.formatFactory(column.meta?.params);
});
- const { onClickValue } = props;
+ const { onClickValue, onRowContextMenuClick } = props;
const handleFilterClick = useMemo(
() => (field: string, value: unknown, colIndex: number, negate: boolean = false) => {
const col = firstTable.columns[colIndex];
@@ -214,6 +264,124 @@ export function DatatableComponent(props: DatatableRenderProps) {
return ;
}
+ const tableColumns: Array<
+ EuiBasicTableColumn<{ rowIndex: number; [key: string]: unknown }>
+ > = props.args.columns.columnIds
+ .map((field) => {
+ const col = firstTable.columns.find((c) => c.id === field);
+ const filterable = bucketColumns.includes(field);
+ const colIndex = firstTable.columns.findIndex((c) => c.id === field);
+ return {
+ field,
+ name: (col && col.name) || '',
+ render: (value: unknown) => {
+ const formattedValue = formatters[field]?.convert(value);
+ const fieldName = col?.meta?.field;
+
+ if (filterable) {
+ return (
+
+ {formattedValue}
+
+
+
+ handleFilterClick(field, value, colIndex)}
+ />
+
+
+
+ handleFilterClick(field, value, colIndex, true)}
+ />
+
+
+
+
+
+ );
+ }
+ return {formattedValue};
+ },
+ };
+ })
+ .filter(({ field }) => !!field);
+
+ if (!!props.rowHasRowClickTriggerActions && !!onRowContextMenuClick) {
+ const hasAtLeastOneRowClickAction = props.rowHasRowClickTriggerActions.find((x) => x);
+ if (hasAtLeastOneRowClickAction) {
+ const actions: EuiTableActionsColumnType<{ rowIndex: number; [key: string]: unknown }> = {
+ name: i18n.translate('xpack.lens.datatable.actionsColumnName', {
+ defaultMessage: 'Actions',
+ }),
+ actions: [
+ {
+ name: i18n.translate('xpack.lens.tableRowMore', {
+ defaultMessage: 'More',
+ }),
+ description: i18n.translate('xpack.lens.tableRowMoreDescription', {
+ defaultMessage: 'Table row context menu',
+ }),
+ type: 'icon',
+ icon: ({ rowIndex }: { rowIndex: number }) => {
+ if (
+ !!props.rowHasRowClickTriggerActions &&
+ !props.rowHasRowClickTriggerActions[rowIndex]
+ )
+ return 'empty';
+ return 'boxesVertical';
+ },
+ onClick: ({ rowIndex }) => {
+ onRowContextMenuClick({
+ rowIndex,
+ table: firstTable,
+ columns: props.args.columns.columnIds,
+ });
+ },
+ },
+ ],
+ };
+ tableColumns.push(actions);
+ }
+ }
+
return (
{
- const col = firstTable.columns.find((c) => c.id === field);
- const filterable = bucketColumns.includes(field);
- const colIndex = firstTable.columns.findIndex((c) => c.id === field);
- return {
- field,
- name: (col && col.name) || '',
- render: (value: unknown) => {
- const formattedValue = formatters[field]?.convert(value);
- const fieldName = col?.meta?.field;
-
- if (filterable) {
- return (
-
- {formattedValue}
-
-
-
- handleFilterClick(field, value, colIndex)}
- />
-
-
-
- handleFilterClick(field, value, colIndex, true)}
- />
-
-
-
-
-
- );
- }
- return {formattedValue};
- },
- };
- })
- .filter(({ field }) => !!field)}
- items={firstTable ? firstTable.rows : []}
+ columns={tableColumns}
+ items={firstTable ? firstTable.rows.map((row, rowIndex) => ({ ...row, rowIndex })) : []}
/>
);
diff --git a/x-pack/plugins/lens/public/datatable_visualization/index.ts b/x-pack/plugins/lens/public/datatable_visualization/index.ts
index 5d9be46db7fb5..9c7d7ae1f2d43 100644
--- a/x-pack/plugins/lens/public/datatable_visualization/index.ts
+++ b/x-pack/plugins/lens/public/datatable_visualization/index.ts
@@ -7,11 +7,9 @@
import { CoreSetup } from 'kibana/public';
import { ExpressionsSetup } from '../../../../../src/plugins/expressions/public';
import { EditorFrameSetup, FormatFactory } from '../types';
-import { UiActionsStart } from '../../../../../src/plugins/ui_actions/public';
import { DataPublicPluginStart } from '../../../../../src/plugins/data/public';
interface DatatableVisualizationPluginStartPlugins {
- uiActions: UiActionsStart;
data: DataPublicPluginStart;
}
export interface DatatableVisualizationPluginSetupPlugins {
@@ -34,6 +32,7 @@ export class DatatableVisualization {
getDatatableRenderer,
datatableVisualization,
} = await import('../async_services');
+
expressions.registerFunction(() => datatableColumns);
expressions.registerFunction(() => datatable);
expressions.registerRenderer(() =>
diff --git a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.test.tsx
index 54517e4ee8c84..175c573d3be3a 100644
--- a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.test.tsx
+++ b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.test.tsx
@@ -25,7 +25,7 @@ import { dataPluginMock } from '../../../../../../src/plugins/data/public/mocks'
import { VIS_EVENT_TO_TRIGGER } from '../../../../../../src/plugins/visualizations/public/embeddable';
import { coreMock, httpServiceMock } from '../../../../../../src/core/public/mocks';
import { IBasePath } from '../../../../../../src/core/public';
-import { AttributeService } from '../../../../../../src/plugins/embeddable/public';
+import { AttributeService, ViewMode } from '../../../../../../src/plugins/embeddable/public';
import { LensAttributeService } from '../../lens_attribute_service';
import { OnSaveProps } from '../../../../../../src/plugins/saved_objects/public/save_modal';
import { act } from 'react-dom/test-utils';
@@ -221,6 +221,74 @@ describe('embeddable', () => {
expect(expressionRenderer).toHaveBeenCalledTimes(2);
});
+ it('should re-render when dashboard view/edit mode changes', async () => {
+ const embeddable = new Embeddable(
+ {
+ timefilter: dataPluginMock.createSetupContract().query.timefilter.timefilter,
+ attributeService,
+ expressionRenderer,
+ basePath,
+ indexPatternService: {} as IndexPatternsContract,
+ editable: true,
+ getTrigger,
+ documentToExpression: () =>
+ Promise.resolve({
+ type: 'expression',
+ chain: [
+ { type: 'function', function: 'my', arguments: {} },
+ { type: 'function', function: 'expression', arguments: {} },
+ ],
+ }),
+ },
+ { id: '123' } as LensEmbeddableInput
+ );
+ await embeddable.initializeSavedVis({ id: '123' } as LensEmbeddableInput);
+ embeddable.render(mountpoint);
+
+ expect(expressionRenderer).toHaveBeenCalledTimes(1);
+
+ embeddable.updateInput({
+ viewMode: ViewMode.VIEW,
+ });
+
+ expect(expressionRenderer).toHaveBeenCalledTimes(2);
+ });
+
+ it('should re-render when dynamic actions input changes', async () => {
+ const embeddable = new Embeddable(
+ {
+ timefilter: dataPluginMock.createSetupContract().query.timefilter.timefilter,
+ attributeService,
+ expressionRenderer,
+ basePath,
+ indexPatternService: {} as IndexPatternsContract,
+ editable: true,
+ getTrigger,
+ documentToExpression: () =>
+ Promise.resolve({
+ type: 'expression',
+ chain: [
+ { type: 'function', function: 'my', arguments: {} },
+ { type: 'function', function: 'expression', arguments: {} },
+ ],
+ }),
+ },
+ { id: '123' } as LensEmbeddableInput
+ );
+ await embeddable.initializeSavedVis({ id: '123' } as LensEmbeddableInput);
+ embeddable.render(mountpoint);
+
+ expect(expressionRenderer).toHaveBeenCalledTimes(1);
+
+ embeddable.updateInput({
+ enhancements: {
+ dynamicActions: {},
+ },
+ });
+
+ expect(expressionRenderer).toHaveBeenCalledTimes(2);
+ });
+
it('should pass context to embeddable', async () => {
const timeRange: TimeRange = { from: 'now-15d', to: 'now' };
const query: Query = { language: 'kquery', query: '' };
@@ -396,6 +464,37 @@ describe('embeddable', () => {
);
});
+ it('should execute trigger on row click event from expression renderer', async () => {
+ const embeddable = new Embeddable(
+ {
+ timefilter: dataPluginMock.createSetupContract().query.timefilter.timefilter,
+ attributeService,
+ expressionRenderer,
+ basePath,
+ indexPatternService: {} as IndexPatternsContract,
+ editable: true,
+ getTrigger,
+ documentToExpression: () =>
+ Promise.resolve({
+ type: 'expression',
+ chain: [
+ { type: 'function', function: 'my', arguments: {} },
+ { type: 'function', function: 'expression', arguments: {} },
+ ],
+ }),
+ },
+ { id: '123' } as LensEmbeddableInput
+ );
+ await embeddable.initializeSavedVis({ id: '123' } as LensEmbeddableInput);
+ embeddable.render(mountpoint);
+
+ const onEvent = expressionRenderer.mock.calls[0][0].onEvent!;
+
+ onEvent({ name: 'tableRowContextMenuClick', data: {} });
+
+ expect(getTrigger).toHaveBeenCalledWith(VIS_EVENT_TO_TRIGGER.tableRowContextMenuClick);
+ });
+
it('should not re-render if only change is in disabled filter', async () => {
const timeRange: TimeRange = { from: 'now-15d', to: 'now' };
const query: Query = { language: 'kquery', query: '' };
diff --git a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx
index e7d3e1a4bfa5b..6c86ae5cff2c8 100644
--- a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx
+++ b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx
@@ -21,6 +21,8 @@ import { PaletteOutput } from 'src/plugins/charts/public';
import { Subscription } from 'rxjs';
import { toExpression, Ast } from '@kbn/interpreter/common';
import { RenderMode } from 'src/plugins/expressions';
+import { map, distinctUntilChanged, skip } from 'rxjs/operators';
+import isEqual from 'fast-deep-equal';
import {
ExpressionRendererEvent,
ReactExpressionRendererType,
@@ -38,7 +40,11 @@ import {
import { Document, injectFilterReferences } from '../../persistence';
import { ExpressionWrapper } from './expression_wrapper';
import { UiActionsStart } from '../../../../../../src/plugins/ui_actions/public';
-import { isLensBrushEvent, isLensFilterEvent } from '../../types';
+import {
+ isLensBrushEvent,
+ isLensFilterEvent,
+ isLensTableRowContextMenuClickEvent,
+} from '../../types';
import { IndexPatternsContract } from '../../../../../../src/plugins/data/public';
import { getEditPath, DOC_TYPE } from '../../../common';
@@ -71,6 +77,7 @@ export interface LensEmbeddableDeps {
timefilter: TimefilterContract;
basePath: IBasePath;
getTrigger?: UiActionsStart['getTrigger'] | undefined;
+ getTriggerCompatibleActions?: UiActionsStart['getTriggerCompatibleActions'];
}
export class Embeddable
@@ -117,6 +124,36 @@ export class Embeddable
this.autoRefreshFetchSubscription = deps.timefilter
.getAutoRefreshFetch$()
.subscribe(this.reload.bind(this));
+
+ const input$ = this.getInput$();
+
+ // Lens embeddable does not re-render when embeddable input changes in
+ // general, to improve performance. This line makes sure the Lens embeddable
+ // re-renders when anything in ".dynamicActions" (e.g. drilldowns) changes.
+ input$
+ .pipe(
+ map((input) => input.enhancements?.dynamicActions),
+ distinctUntilChanged((a, b) => isEqual(a, b)),
+ skip(1)
+ )
+ .subscribe((input) => {
+ this.reload();
+ });
+
+ // Lens embeddable does not re-render when embeddable input changes in
+ // general, to improve performance. This line makes sure the Lens embeddable
+ // re-renders when dashboard view mode switches between "view/edit". This is
+ // needed to see the changes to ".dynamicActions" (e.g. drilldowns) when
+ // dashboard's mode is toggled.
+ input$
+ .pipe(
+ map((input) => input.viewMode),
+ distinctUntilChanged(),
+ skip(1)
+ )
+ .subscribe((input) => {
+ this.reload();
+ });
}
public supportedTriggers() {
@@ -127,6 +164,7 @@ export class Embeddable
case 'lnsXY':
return [VIS_EVENT_TO_TRIGGER.filter, VIS_EVENT_TO_TRIGGER.brush];
case 'lnsDatatable':
+ return [VIS_EVENT_TO_TRIGGER.filter, VIS_EVENT_TO_TRIGGER.tableRowContextMenuClick];
case 'lnsPie':
return [VIS_EVENT_TO_TRIGGER.filter];
case 'lnsMetric':
@@ -217,11 +255,31 @@ export class Embeddable
handleEvent={this.handleEvent}
onData$={this.updateActiveData}
renderMode={input.renderMode}
+ hasCompatibleActions={this.hasCompatibleActions}
/>,
domNode
);
}
+ private readonly hasCompatibleActions = async (
+ event: ExpressionRendererEvent
+ ): Promise => {
+ if (isLensTableRowContextMenuClickEvent(event)) {
+ const { getTriggerCompatibleActions } = this.deps;
+ if (!getTriggerCompatibleActions) {
+ return false;
+ }
+ const actions = await getTriggerCompatibleActions(VIS_EVENT_TO_TRIGGER[event.name], {
+ data: event.data,
+ embeddable: this,
+ });
+
+ return actions.length > 0;
+ }
+
+ return false;
+ };
+
/**
* Combines the embeddable context with the saved object context, and replaces
* any references to index patterns
@@ -264,6 +322,16 @@ export class Embeddable
embeddable: this,
});
}
+
+ if (isLensTableRowContextMenuClickEvent(event)) {
+ this.deps.getTrigger(VIS_EVENT_TO_TRIGGER[event.name]).exec(
+ {
+ data: event.data,
+ embeddable: this,
+ },
+ true
+ );
+ }
};
async reload() {
diff --git a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable_factory.ts b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable_factory.ts
index 65e9c22d24eaf..175ec0dbcfd54 100644
--- a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable_factory.ts
+++ b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable_factory.ts
@@ -94,6 +94,7 @@ export class EmbeddableFactory implements EmbeddableFactoryDefinition {
editable: await this.isEditable(),
basePath: coreHttp.basePath,
getTrigger: uiActions?.getTrigger,
+ getTriggerCompatibleActions: uiActions?.getTriggerCompatibleActions,
documentToExpression,
},
input,
diff --git a/x-pack/plugins/lens/public/editor_frame_service/embeddable/expression_wrapper.tsx b/x-pack/plugins/lens/public/editor_frame_service/embeddable/expression_wrapper.tsx
index 4645420898314..2fc1cfee82fd3 100644
--- a/x-pack/plugins/lens/public/editor_frame_service/embeddable/expression_wrapper.tsx
+++ b/x-pack/plugins/lens/public/editor_frame_service/embeddable/expression_wrapper.tsx
@@ -11,6 +11,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiText, EuiIcon } from '@elastic/eui';
import {
ExpressionRendererEvent,
ReactExpressionRendererType,
+ ReactExpressionRendererProps,
} from 'src/plugins/expressions/public';
import { ExecutionContextSearch } from 'src/plugins/data/public';
import { RenderMode } from 'src/plugins/expressions';
@@ -26,6 +27,7 @@ export interface ExpressionWrapperProps {
handleEvent: (event: ExpressionRendererEvent) => void;
onData$: (data: unknown, inspectorAdapters?: LensInspectorAdapters | undefined) => void;
renderMode?: RenderMode;
+ hasCompatibleActions?: ReactExpressionRendererProps['hasCompatibleActions'];
}
export function ExpressionWrapper({
@@ -37,6 +39,7 @@ export function ExpressionWrapper({
searchSessionId,
onData$,
renderMode,
+ hasCompatibleActions,
}: ExpressionWrapperProps) {
return (
@@ -80,6 +83,7 @@ export function ExpressionWrapper({
)}
onEvent={handleEvent}
+ hasCompatibleActions={hasCompatibleActions}
/>
)}
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/shared_components/label_input.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/shared_components/label_input.tsx
index ddcb5633b376f..de7a826485831 100644
--- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/shared_components/label_input.tsx
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/shared_components/label_input.tsx
@@ -47,7 +47,7 @@ export const LabelInput = ({
inputRef.current = node;
}
}}
- onKeyDown={({ key }: React.KeyboardEvent) => {
+ onKeyUp={({ key }: React.KeyboardEvent) => {
if (keys.ENTER === key && onSubmit) {
onSubmit();
}
diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts
index b0da6cf2e8434..23d026bf2b443 100644
--- a/x-pack/plugins/lens/public/types.ts
+++ b/x-pack/plugins/lens/public/types.ts
@@ -8,6 +8,7 @@ import { IconType } from '@elastic/eui/src/components/icon/icon';
import { CoreSetup } from 'kibana/public';
import { PaletteOutput, PaletteRegistry } from 'src/plugins/charts/public';
import { SavedObjectReference } from 'kibana/public';
+import { ROW_CLICK_TRIGGER } from '../../../../src/plugins/ui_actions/public';
import {
ExpressionAstExpression,
ExpressionRendererEvent,
@@ -614,11 +615,17 @@ export interface LensFilterEvent {
name: 'filter';
data: TriggerContext['data'];
}
+
export interface LensBrushEvent {
name: 'brush';
data: TriggerContext['data'];
}
+export interface LensTableRowContextMenuEvent {
+ name: 'tableRowContextMenuClick';
+ data: TriggerContext['data'];
+}
+
export function isLensFilterEvent(event: ExpressionRendererEvent): event is LensFilterEvent {
return event.name === 'filter';
}
@@ -627,11 +634,17 @@ export function isLensBrushEvent(event: ExpressionRendererEvent): event is LensB
return event.name === 'brush';
}
+export function isLensTableRowContextMenuClickEvent(
+ event: ExpressionRendererEvent
+): event is LensBrushEvent {
+ return event.name === 'tableRowContextMenuClick';
+}
+
/**
* Expression renderer handlers specifically for lens renderers. This is a narrowed down
* version of the general render handlers, specifying supported event types. If this type is
* used, dispatched events will be handled correctly.
*/
export interface ILensInterpreterRenderHandlers extends IInterpreterRenderHandlers {
- event: (event: LensFilterEvent | LensBrushEvent) => void;
+ event: (event: LensFilterEvent | LensBrushEvent | LensTableRowContextMenuEvent) => void;
}
diff --git a/x-pack/plugins/security/server/audit/audit_events.ts b/x-pack/plugins/security/server/audit/audit_events.ts
index 0bb2f8ba1a246..59184562b67ff 100644
--- a/x-pack/plugins/security/server/audit/audit_events.ts
+++ b/x-pack/plugins/security/server/audit/audit_events.ts
@@ -8,8 +8,11 @@ import { KibanaRequest } from 'src/core/server';
import { AuthenticationResult } from '../authentication/authentication_result';
/**
- * Audit event schema using ECS format.
- * https://www.elastic.co/guide/en/ecs/1.6/index.html
+ * Audit event schema using ECS format: https://www.elastic.co/guide/en/ecs/1.6/index.html
+ *
+ * If you add additional fields to the schema ensure you update the Kibana Filebeat module:
+ * https://github.com/elastic/beats/tree/master/filebeat/module/kibana
+ *
* @public
*/
export interface AuditEvent {
@@ -37,20 +40,45 @@ export interface AuditEvent {
};
kibana?: {
/**
- * Current space id of the request.
+ * The ID of the space associated with this event.
*/
space_id?: string;
/**
- * Saved object that was created, changed, deleted or accessed as part of the action.
+ * The ID of the user session associated with this event. Each login attempt
+ * results in a unique session id.
+ */
+ session_id?: string;
+ /**
+ * Saved object that was created, changed, deleted or accessed as part of this event.
*/
saved_object?: {
type: string;
id: string;
};
/**
- * Any additional event specific fields.
+ * Name of authentication provider associated with a login event.
+ */
+ authentication_provider?: string;
+ /**
+ * Type of authentication provider associated with a login event.
+ */
+ authentication_type?: string;
+ /**
+ * Name of Elasticsearch realm that has authenticated the user.
+ */
+ authentication_realm?: string;
+ /**
+ * Name of Elasticsearch realm where the user details were retrieved from.
+ */
+ lookup_realm?: string;
+ /**
+ * Set of space IDs that a saved object was shared to.
+ */
+ add_to_spaces?: readonly string[];
+ /**
+ * Set of space IDs that a saved object was removed from.
*/
- [x: string]: any;
+ delete_from_spaces?: readonly string[];
};
error?: {
code?: string;
diff --git a/x-pack/plugins/security/server/audit/audit_service.test.ts b/x-pack/plugins/security/server/audit/audit_service.test.ts
index a9c7668871248..91c656ad69f18 100644
--- a/x-pack/plugins/security/server/audit/audit_service.test.ts
+++ b/x-pack/plugins/security/server/audit/audit_service.test.ts
@@ -27,6 +27,7 @@ const { logging } = coreMock.createSetup();
const http = httpServiceMock.createSetupContract();
const getCurrentUser = jest.fn().mockReturnValue({ username: 'jdoe', roles: ['admin'] });
const getSpaceId = jest.fn().mockReturnValue('default');
+const getSID = jest.fn().mockResolvedValue('SESSION_ID');
beforeEach(() => {
logger.info.mockClear();
@@ -45,6 +46,7 @@ describe('#setup', () => {
http,
getCurrentUser,
getSpaceId,
+ getSID,
})
).toMatchInlineSnapshot(`
Object {
@@ -70,6 +72,7 @@ describe('#setup', () => {
http,
getCurrentUser,
getSpaceId,
+ getSID,
});
expect(logging.configure).toHaveBeenCalledWith(expect.any(Observable));
});
@@ -82,6 +85,7 @@ describe('#setup', () => {
http,
getCurrentUser,
getSpaceId,
+ getSID,
});
expect(http.registerOnPostAuth).toHaveBeenCalledWith(expect.any(Function));
});
@@ -96,16 +100,17 @@ describe('#asScoped', () => {
http,
getCurrentUser,
getSpaceId,
+ getSID,
});
const request = httpServerMock.createKibanaRequest({
kibanaRequestState: { requestId: 'REQUEST_ID', requestUuid: 'REQUEST_UUID' },
});
- audit.asScoped(request).log({ message: 'MESSAGE', event: { action: 'ACTION' } });
+ await audit.asScoped(request).log({ message: 'MESSAGE', event: { action: 'ACTION' } });
expect(logger.info).toHaveBeenCalledWith('MESSAGE', {
ecs: { version: '1.6.0' },
event: { action: 'ACTION' },
- kibana: { space_id: 'default' },
+ kibana: { space_id: 'default', session_id: 'SESSION_ID' },
message: 'MESSAGE',
trace: { id: 'REQUEST_ID' },
user: { name: 'jdoe', roles: ['admin'] },
@@ -123,12 +128,13 @@ describe('#asScoped', () => {
http,
getCurrentUser,
getSpaceId,
+ getSID,
});
const request = httpServerMock.createKibanaRequest({
kibanaRequestState: { requestId: 'REQUEST_ID', requestUuid: 'REQUEST_UUID' },
});
- audit.asScoped(request).log({ message: 'MESSAGE', event: { action: 'ACTION' } });
+ await audit.asScoped(request).log({ message: 'MESSAGE', event: { action: 'ACTION' } });
expect(logger.info).not.toHaveBeenCalled();
});
@@ -143,12 +149,13 @@ describe('#asScoped', () => {
http,
getCurrentUser,
getSpaceId,
+ getSID,
});
const request = httpServerMock.createKibanaRequest({
kibanaRequestState: { requestId: 'REQUEST_ID', requestUuid: 'REQUEST_UUID' },
});
- audit.asScoped(request).log(undefined);
+ await audit.asScoped(request).log(undefined);
expect(logger.info).not.toHaveBeenCalled();
});
});
@@ -368,6 +375,7 @@ describe('#getLogger', () => {
http,
getCurrentUser,
getSpaceId,
+ getSID,
});
const auditLogger = auditService.getLogger(pluginId);
@@ -398,6 +406,7 @@ describe('#getLogger', () => {
http,
getCurrentUser,
getSpaceId,
+ getSID,
});
const auditLogger = auditService.getLogger(pluginId);
@@ -436,6 +445,7 @@ describe('#getLogger', () => {
http,
getCurrentUser,
getSpaceId,
+ getSID,
});
const auditLogger = auditService.getLogger(pluginId);
@@ -464,6 +474,7 @@ describe('#getLogger', () => {
http,
getCurrentUser,
getSpaceId,
+ getSID,
});
const auditLogger = auditService.getLogger(pluginId);
@@ -493,6 +504,7 @@ describe('#getLogger', () => {
http,
getCurrentUser,
getSpaceId,
+ getSID,
});
const auditLogger = auditService.getLogger(pluginId);
diff --git a/x-pack/plugins/security/server/audit/audit_service.ts b/x-pack/plugins/security/server/audit/audit_service.ts
index 8dbdc48c7dee9..4ad1f873581c9 100644
--- a/x-pack/plugins/security/server/audit/audit_service.ts
+++ b/x-pack/plugins/security/server/audit/audit_service.ts
@@ -36,9 +36,6 @@ interface AuditLogMeta extends AuditEvent {
ecs: {
version: string;
};
- session?: {
- id: string;
- };
trace: {
id: string;
};
@@ -57,6 +54,7 @@ interface AuditServiceSetupParams {
getCurrentUser(
request: KibanaRequest
): ReturnType | undefined;
+ getSID(request: KibanaRequest): Promise;
getSpaceId(
request: KibanaRequest
): ReturnType | undefined;
@@ -84,6 +82,7 @@ export class AuditService {
logging,
http,
getCurrentUser,
+ getSID,
getSpaceId,
}: AuditServiceSetupParams): AuditServiceSetup {
if (config.enabled && !config.appender) {
@@ -134,12 +133,13 @@ export class AuditService {
* });
* ```
*/
- const log: AuditLogger['log'] = (event) => {
+ const log: AuditLogger['log'] = async (event) => {
if (!event) {
return;
}
- const user = getCurrentUser(request);
const spaceId = getSpaceId(request);
+ const user = getCurrentUser(request);
+ const sessionId = await getSID(request);
const meta: AuditLogMeta = {
ecs: { version: ECS_VERSION },
...event,
@@ -151,11 +151,10 @@ export class AuditService {
event.user,
kibana: {
space_id: spaceId,
+ session_id: sessionId,
...event.kibana,
},
- trace: {
- id: request.id,
- },
+ trace: { id: request.id },
};
if (filterEvent(meta, config.ignore_filters)) {
this.ecsLogger.info(event.message!, meta);
diff --git a/x-pack/plugins/security/server/plugin.ts b/x-pack/plugins/security/server/plugin.ts
index 4016b78b6d998..070e187e869b1 100644
--- a/x-pack/plugins/security/server/plugin.ts
+++ b/x-pack/plugins/security/server/plugin.ts
@@ -188,24 +188,25 @@ export class Plugin {
registerSecurityUsageCollector({ usageCollection, config, license });
+ const { session } = this.sessionManagementService.setup({
+ config,
+ clusterClient,
+ http: core.http,
+ kibanaIndexName: legacyConfig.kibana.index,
+ taskManager,
+ });
+
const audit = this.auditService.setup({
license,
config: config.audit,
logging: core.logging,
http: core.http,
getSpaceId: (request) => spaces?.spacesService.getSpaceId(request),
+ getSID: (request) => session.getSID(request),
getCurrentUser: (request) => authenticationSetup.getCurrentUser(request),
});
const legacyAuditLogger = new SecurityAuditLogger(audit.getLogger());
- const { session } = this.sessionManagementService.setup({
- config,
- clusterClient,
- http: core.http,
- kibanaIndexName: legacyConfig.kibana.index,
- taskManager,
- });
-
const authenticationSetup = this.authenticationService.setup({
legacyAuditLogger,
audit,
diff --git a/x-pack/plugins/security/server/session_management/session.mock.ts b/x-pack/plugins/security/server/session_management/session.mock.ts
index 973341acbfce3..b740249180407 100644
--- a/x-pack/plugins/security/server/session_management/session.mock.ts
+++ b/x-pack/plugins/security/server/session_management/session.mock.ts
@@ -10,6 +10,7 @@ import { sessionIndexMock } from './session_index.mock';
export const sessionMock = {
create: (): jest.Mocked> => ({
+ getSID: jest.fn(),
get: jest.fn(),
create: jest.fn(),
update: jest.fn(),
diff --git a/x-pack/plugins/security/server/session_management/session.test.ts b/x-pack/plugins/security/server/session_management/session.test.ts
index 3010e70c31421..47e391ed57925 100644
--- a/x-pack/plugins/security/server/session_management/session.test.ts
+++ b/x-pack/plugins/security/server/session_management/session.test.ts
@@ -56,6 +56,20 @@ describe('Session', () => {
});
});
+ describe('#getSID', () => {
+ const mockRequest = httpServerMock.createKibanaRequest();
+
+ it('returns `undefined` if session cookie does not exist', async () => {
+ mockSessionCookie.get.mockResolvedValue(null);
+ await expect(session.getSID(mockRequest)).resolves.toBeUndefined();
+ });
+
+ it('returns session id', async () => {
+ mockSessionCookie.get.mockResolvedValue(sessionCookieMock.createValue());
+ await expect(session.getSID(mockRequest)).resolves.toEqual('some-long-sid');
+ });
+ });
+
describe('#get', () => {
const mockAAD = Buffer.from([2, ...Array(255).keys()]).toString('base64');
diff --git a/x-pack/plugins/security/server/session_management/session.ts b/x-pack/plugins/security/server/session_management/session.ts
index 4dc83a1abe4af..3c97c13c2d41d 100644
--- a/x-pack/plugins/security/server/session_management/session.ts
+++ b/x-pack/plugins/security/server/session_management/session.ts
@@ -99,6 +99,17 @@ export class Session {
this.crypto = nodeCrypto({ encryptionKey: this.options.config.encryptionKey });
}
+ /**
+ * Extracts session id for the specified request.
+ * @param request Request instance to get session value for.
+ */
+ async getSID(request: KibanaRequest) {
+ const sessionCookieValue = await this.options.sessionCookie.get(request);
+ if (sessionCookieValue) {
+ return sessionCookieValue.sid;
+ }
+ }
+
/**
* Extracts session value for the specified request. Under the hood it can clear session if it is
* invalid or created by the legacy versions of Kibana.
diff --git a/x-pack/plugins/ui_actions_enhanced/public/drilldowns/url_drilldown/components/url_drilldown_collect_config/test_samples/demo.tsx b/x-pack/plugins/ui_actions_enhanced/public/drilldowns/url_drilldown/components/url_drilldown_collect_config/test_samples/demo.tsx
index e6c9797623e9f..1b975da0b369d 100644
--- a/x-pack/plugins/ui_actions_enhanced/public/drilldowns/url_drilldown/components/url_drilldown_collect_config/test_samples/demo.tsx
+++ b/x-pack/plugins/ui_actions_enhanced/public/drilldowns/url_drilldown/components/url_drilldown_collect_config/test_samples/demo.tsx
@@ -5,7 +5,7 @@
*/
import React from 'react';
-import { UrlDrilldownConfig, UrlDrilldownScope } from '../../../types';
+import { UrlDrilldownConfig } from '../../../types';
import { UrlDrilldownCollectConfig } from '../url_drilldown_collect_config';
export const Demo = () => {
@@ -14,33 +14,13 @@ export const Demo = () => {
url: { template: '' },
});
- const fakeScope: UrlDrilldownScope = {
- kibanaUrl: 'http://localhost:5601/',
- context: {
- filters: [
- {
- query: { match: { extension: { query: 'jpg', type: 'phrase' } } },
- meta: { index: 'logstash-*', negate: false, disabled: false, alias: null },
- },
- {
- query: { match: { '@tags': { query: 'info', type: 'phrase' } } },
- meta: { index: 'logstash-*', negate: false, disabled: false, alias: null },
- },
- {
- query: { match: { _type: { query: 'nginx', type: 'phrase' } } },
- meta: { index: 'logstash-*', negate: false, disabled: false, alias: null },
- },
- ],
- },
- event: {
- key: 'fakeKey',
- value: 'fakeValue',
- },
- };
-
return (
<>
-
+
{JSON.stringify(config)}
>
);
diff --git a/x-pack/plugins/ui_actions_enhanced/public/drilldowns/url_drilldown/components/url_drilldown_collect_config/url_drilldown_collect_config.test.tsx b/x-pack/plugins/ui_actions_enhanced/public/drilldowns/url_drilldown/components/url_drilldown_collect_config/url_drilldown_collect_config.test.tsx
deleted file mode 100644
index a6fcd77d75040..0000000000000
--- a/x-pack/plugins/ui_actions_enhanced/public/drilldowns/url_drilldown/components/url_drilldown_collect_config/url_drilldown_collect_config.test.tsx
+++ /dev/null
@@ -1,47 +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 { Demo } from './test_samples/demo';
-import { fireEvent, render } from '@testing-library/react';
-import React from 'react';
-
-jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({
- htmlIdGenerator: () => () => `id-${Math.random()}`,
-}));
-
-test('configure valid URL template', () => {
- const screen = render();
-
- const urlTemplate = 'https://elastic.co/?{{event.key}}={{event.value}}';
- fireEvent.change(screen.getByLabelText(/Enter URL template/i), {
- target: { value: urlTemplate },
- });
-
- const preview = screen.getByLabelText(/URL preview/i) as HTMLTextAreaElement;
- expect(preview.value).toMatchInlineSnapshot(`"https://elastic.co/?fakeKey=fakeValue"`);
- expect(preview.disabled).toEqual(true);
- const previewLink = screen.getByText('Preview') as HTMLAnchorElement;
- expect(previewLink.href).toMatchInlineSnapshot(`"https://elastic.co/?fakeKey=fakeValue"`);
- expect(previewLink.target).toMatchInlineSnapshot(`"_blank"`);
-});
-
-test('configure invalid URL template', () => {
- const screen = render();
-
- const urlTemplate = 'https://elastic.co/?{{event.wrongKey}}={{event.wrongValue}}';
- fireEvent.change(screen.getByLabelText(/Enter URL template/i), {
- target: { value: urlTemplate },
- });
-
- const previewTextArea = screen.getByLabelText(/URL preview/i) as HTMLTextAreaElement;
- expect(previewTextArea.disabled).toEqual(true);
- expect(previewTextArea.value).toEqual(urlTemplate);
- expect(screen.getByText(/invalid format/i)).toBeInTheDocument(); // check that error is shown
-
- const previewLink = screen.getByText('Preview') as HTMLAnchorElement;
- expect(previewLink.href).toEqual(urlTemplate);
- expect(previewLink.target).toMatchInlineSnapshot(`"_blank"`);
-});
diff --git a/x-pack/plugins/ui_actions_enhanced/public/drilldowns/url_drilldown/components/url_drilldown_collect_config/url_drilldown_collect_config.tsx b/x-pack/plugins/ui_actions_enhanced/public/drilldowns/url_drilldown/components/url_drilldown_collect_config/url_drilldown_collect_config.tsx
index 3251e85841d86..eb8d01afbf420 100644
--- a/x-pack/plugins/ui_actions_enhanced/public/drilldowns/url_drilldown/components/url_drilldown_collect_config/url_drilldown_collect_config.tsx
+++ b/x-pack/plugins/ui_actions_enhanced/public/drilldowns/url_drilldown/components/url_drilldown_collect_config/url_drilldown_collect_config.tsx
@@ -18,52 +18,40 @@ import {
EuiTextArea,
EuiSelectableOption,
} from '@elastic/eui';
-import { UrlDrilldownConfig, UrlDrilldownScope } from '../../types';
-import { compile } from '../../url_template';
-import { validateUrlTemplate } from '../../url_validation';
-import { buildScopeSuggestions } from '../../url_drilldown_scope';
+import { UrlDrilldownConfig } from '../../types';
import './index.scss';
import {
txtAddVariableButtonTitle,
- txtUrlPreviewHelpText,
txtUrlTemplateSyntaxHelpLinkText,
txtUrlTemplateVariablesHelpLinkText,
txtUrlTemplateVariablesFilterPlaceholderText,
txtUrlTemplateLabel,
txtUrlTemplateOpenInNewTab,
txtUrlTemplatePlaceholder,
- txtUrlTemplatePreviewLabel,
- txtUrlTemplatePreviewLinkText,
} from './i18n';
export interface UrlDrilldownCollectConfig {
config: UrlDrilldownConfig;
+ variables: string[];
onConfig: (newConfig: UrlDrilldownConfig) => void;
- scope: UrlDrilldownScope;
syntaxHelpDocsLink?: string;
variablesHelpDocsLink?: string;
}
export const UrlDrilldownCollectConfig: React.FC = ({
config,
+ variables,
onConfig,
- scope,
syntaxHelpDocsLink,
variablesHelpDocsLink,
}) => {
const textAreaRef = useRef(null);
+ const [showUrlError, setShowUrlError] = React.useState(false);
const urlTemplate = config.url.template ?? '';
- const compiledUrl = React.useMemo(() => {
- try {
- return compile(urlTemplate, scope);
- } catch {
- return urlTemplate;
- }
- }, [urlTemplate, scope]);
- const scopeVariables = React.useMemo(() => buildScopeSuggestions(scope), [scope]);
function updateUrlTemplate(newUrlTemplate: string) {
if (config.url.template !== newUrlTemplate) {
+ setShowUrlError(true);
onConfig({
...config,
url: {
@@ -73,18 +61,31 @@ export const UrlDrilldownCollectConfig: React.FC = ({
});
}
}
- const { error, isValid } = React.useMemo(
- () => validateUrlTemplate({ template: urlTemplate }, scope),
- [urlTemplate, scope]
- );
const isEmpty = !urlTemplate;
- const isInvalid = !isValid && !isEmpty;
+ const isInvalid = showUrlError && isEmpty;
+ const variablesDropdown = (
+ {
+ if (textAreaRef.current) {
+ updateUrlTemplate(
+ urlTemplate.substr(0, textAreaRef.current!.selectionStart) +
+ `{{${variable}}}` +
+ urlTemplate.substr(textAreaRef.current!.selectionEnd)
+ );
+ } else {
+ updateUrlTemplate(urlTemplate + `{{${variable}}}`);
+ }
+ }}
+ />
+ );
+
return (
<>
= ({
)
}
- labelAppend={
- {
- if (textAreaRef.current) {
- updateUrlTemplate(
- urlTemplate.substr(0, textAreaRef.current!.selectionStart) +
- `{{${variable}}}` +
- urlTemplate.substr(textAreaRef.current!.selectionEnd)
- );
- } else {
- updateUrlTemplate(urlTemplate + `{{${variable}}}`);
- }
- }}
- />
- }
+ labelAppend={variablesDropdown}
>
= ({
value={urlTemplate}
placeholder={txtUrlTemplatePlaceholder}
onChange={(event) => updateUrlTemplate(event.target.value)}
+ onBlur={() => setShowUrlError(true)}
rows={3}
inputRef={textAreaRef}
/>
-
-
- {txtUrlTemplatePreviewLinkText}
-
-
- }
- helpText={txtUrlPreviewHelpText}
- >
-
-
{
- expect(
- buildScopeSuggestions(
- buildScope({
- globalScope: {
- kibanaUrl: 'http://localhost:5061/',
- },
- eventScope: {
- key: '__testKey__',
- value: '__testValue__',
- },
- contextScope: {
- filters: [
- {
- query: { match: { extension: { query: 'jpg', type: 'phrase' } } },
- meta: { index: 'logstash-*', negate: false, disabled: false, alias: null },
- },
- {
- query: { match: { '@tags': { query: 'info', type: 'phrase' } } },
- meta: { index: 'logstash-*', negate: false, disabled: false, alias: null },
- },
- {
- query: { match: { _type: { query: 'nginx', type: 'phrase' } } },
- meta: { index: 'logstash-*', negate: false, disabled: false, alias: null },
- },
- ],
- query: {
- query: '',
- language: 'kquery',
- },
- },
- })
- )
- ).toMatchInlineSnapshot(`
- Array [
- "event.key",
- "event.value",
- "context.filters",
- "context.query.language",
- "context.query.query",
- "kibanaUrl",
- ]
- `);
-});
diff --git a/x-pack/plugins/ui_actions_enhanced/public/drilldowns/url_drilldown/url_drilldown_scope.ts b/x-pack/plugins/ui_actions_enhanced/public/drilldowns/url_drilldown/url_drilldown_scope.ts
deleted file mode 100644
index 74940c4b07077..0000000000000
--- a/x-pack/plugins/ui_actions_enhanced/public/drilldowns/url_drilldown/url_drilldown_scope.ts
+++ /dev/null
@@ -1,39 +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 { partition } from 'lodash';
-import { getFlattenedObject } from '@kbn/std';
-import { UrlDrilldownGlobalScope, UrlDrilldownScope } from './types';
-
-export function buildScope<
- ContextScope extends object = object,
- EventScope extends object = object
->({
- globalScope,
- contextScope,
- eventScope,
-}: {
- globalScope: UrlDrilldownGlobalScope;
- contextScope?: ContextScope;
- eventScope?: EventScope;
-}): UrlDrilldownScope {
- return {
- ...globalScope,
- context: contextScope,
- event: eventScope,
- };
-}
-
-/**
- * Builds list of variables for suggestion from scope
- * keys sorted alphabetically, except {{event.$}} variables are pulled to the top
- * @param scope
- */
-export function buildScopeSuggestions(scope: UrlDrilldownGlobalScope): string[] {
- const allKeys = Object.keys(getFlattenedObject(scope)).sort();
- const [eventKeys, otherKeys] = partition(allKeys, (key) => key.startsWith('event'));
- return [...eventKeys, ...otherKeys];
-}
diff --git a/x-pack/plugins/uptime/public/state/api/utils.ts b/x-pack/plugins/uptime/public/state/api/utils.ts
index 965cbefd13114..54e129c0811c2 100644
--- a/x-pack/plugins/uptime/public/state/api/utils.ts
+++ b/x-pack/plugins/uptime/public/state/api/utils.ts
@@ -8,6 +8,7 @@ import { PathReporter } from 'io-ts/lib/PathReporter';
import { isRight } from 'fp-ts/lib/Either';
import { HttpFetchQuery, HttpSetup } from 'src/core/public';
import * as t from 'io-ts';
+import { startsWith } from 'lodash';
function isObject(value: unknown) {
const type = typeof value;
@@ -60,7 +61,14 @@ class ApiService {
}
public async get(apiUrl: string, params?: HttpFetchQuery, decodeType?: any, asResponse = false) {
- const response = await this._http!.fetch({ path: apiUrl, query: params, asResponse });
+ const debugEnabled =
+ sessionStorage.getItem('uptime_debug') === 'true' && startsWith(apiUrl, '/api/uptime');
+
+ const response = await this._http!.fetch({
+ path: apiUrl,
+ query: { ...params, ...(debugEnabled ? { _debug: true } : {}) },
+ asResponse,
+ });
if (decodeType) {
const decoded = decodeType.decode(response);
diff --git a/x-pack/plugins/uptime/server/lib/lib.ts b/x-pack/plugins/uptime/server/lib/lib.ts
index 39dd868462525..ee84cf4463ceb 100644
--- a/x-pack/plugins/uptime/server/lib/lib.ts
+++ b/x-pack/plugins/uptime/server/lib/lib.ts
@@ -3,7 +3,8 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
-import { ElasticsearchClient, SavedObjectsClientContract } from 'kibana/server';
+import { ElasticsearchClient, SavedObjectsClientContract, KibanaRequest } from 'kibana/server';
+import chalk from 'chalk';
import { UMBackendFrameworkAdapter } from './adapters';
import { UMLicenseCheck } from './domains';
import { UptimeRequests } from './requests';
@@ -19,53 +20,83 @@ export interface UMServerLibs extends UMDomainLibs {
framework: UMBackendFrameworkAdapter;
}
+interface CountResponse {
+ body: {
+ count: number;
+ _shards: {
+ total: number;
+ successful: number;
+ skipped: number;
+ failed: number;
+ };
+ };
+}
+
export type UptimeESClient = ReturnType;
export function createUptimeESClient({
esClient,
+ request,
savedObjectsClient,
}: {
esClient: ElasticsearchClient;
+ request?: KibanaRequest;
savedObjectsClient: SavedObjectsClientContract;
}) {
+ const { _debug = false } = (request?.query as { _debug: boolean }) ?? {};
+
return {
baseESClient: esClient,
async search(params: TParams): Promise<{ body: ESSearchResponse }> {
+ let res: any;
+ let esError: any;
const dynamicSettings = await savedObjectsAdapter.getUptimeDynamicSettings(
savedObjectsClient!
);
- let res: any;
+ const esParams = { index: dynamicSettings!.heartbeatIndices, ...params };
+ const startTime = process.hrtime();
+
try {
- res = await esClient.search({ index: dynamicSettings!.heartbeatIndices, ...params });
+ res = await esClient.search(esParams);
} catch (e) {
- throw e;
+ esError = e;
+ }
+ if (_debug && request) {
+ debugESCall({ startTime, request, esError, operationName: 'search', params: esParams });
}
+
+ if (esError) {
+ throw esError;
+ }
+
return res;
},
- async count(
- params: TParams
- ): Promise<{
- body: {
- count: number;
- _shards: {
- total: number;
- successful: number;
- skipped: number;
- failed: number;
- };
- };
- }> {
+ async count(params: TParams): Promise {
+ let res: any;
+ let esError: any;
+
const dynamicSettings = await savedObjectsAdapter.getUptimeDynamicSettings(
savedObjectsClient!
);
- let res: any;
+ const esParams = { index: dynamicSettings!.heartbeatIndices, ...params };
+ const startTime = process.hrtime();
+
try {
- res = await esClient.count({ index: dynamicSettings!.heartbeatIndices, ...params });
+ res = await esClient.count(esParams);
} catch (e) {
- throw e;
+ esError = e;
}
+
+ if (_debug && request) {
+ debugESCall({ startTime, request, esError, operationName: 'count', params: esParams });
+ }
+
+ if (esError) {
+ throw esError;
+ }
+
return res;
},
getSavedObjectsClient() {
@@ -73,3 +104,41 @@ export function createUptimeESClient({
},
};
}
+
+/* eslint-disable no-console */
+
+function formatObj(obj: Record) {
+ return JSON.stringify(obj);
+}
+
+export function debugESCall({
+ operationName,
+ params,
+ request,
+ esError,
+ startTime,
+}: {
+ operationName: string;
+ params: Record;
+ request: KibanaRequest;
+ esError: any;
+ startTime: [number, number];
+}) {
+ const highlightColor = esError ? 'bgRed' : 'inverse';
+ const diff = process.hrtime(startTime);
+ const duration = `${Math.round(diff[0] * 1000 + diff[1] / 1e6)}ms`;
+ const routeInfo = `${request.route.method.toUpperCase()} ${request.route.path}`;
+
+ console.log(chalk.bold[highlightColor](`=== Debug: ${routeInfo} (${duration}) ===`));
+
+ if (operationName === 'search') {
+ console.log(`GET ${params.index}/_${operationName}`);
+ console.log(formatObj(params.body));
+ } else {
+ console.log(chalk.bold('ES operation:'), operationName);
+
+ console.log(chalk.bold('ES query:'));
+ console.log(formatObj(params));
+ }
+ console.log(`\n`);
+}
diff --git a/x-pack/plugins/uptime/server/rest_api/certs/certs.ts b/x-pack/plugins/uptime/server/rest_api/certs/certs.ts
index d377095a2a370..7af5717d8416d 100644
--- a/x-pack/plugins/uptime/server/rest_api/certs/certs.ts
+++ b/x-pack/plugins/uptime/server/rest_api/certs/certs.ts
@@ -30,7 +30,7 @@ export const createGetCertsRoute: UMRestApiRouteFactory = (libs: UMServerLibs) =
direction: schema.maybe(schema.string()),
}),
},
- handler: async ({ uptimeEsClient }, _context, request, response): Promise => {
+ handler: async ({ uptimeEsClient, request }): Promise => {
const index = request.query?.index ?? 0;
const size = request.query?.size ?? DEFAULT_SIZE;
const from = request.query?.from ?? DEFAULT_FROM;
@@ -38,7 +38,8 @@ export const createGetCertsRoute: UMRestApiRouteFactory = (libs: UMServerLibs) =
const sortBy = request.query?.sortBy ?? DEFAULT_SORT;
const direction = request.query?.direction ?? DEFAULT_DIRECTION;
const { search } = request.query;
- const result = await libs.requests.getCerts({
+
+ return await libs.requests.getCerts({
uptimeEsClient,
index,
search,
@@ -48,11 +49,5 @@ export const createGetCertsRoute: UMRestApiRouteFactory = (libs: UMServerLibs) =
sortBy,
direction,
});
- return response.ok({
- body: {
- certs: result.certs,
- total: result.total,
- },
- });
},
});
diff --git a/x-pack/plugins/uptime/server/rest_api/create_route_with_auth.ts b/x-pack/plugins/uptime/server/rest_api/create_route_with_auth.ts
index 966dc20e27a7e..93593cc315d62 100644
--- a/x-pack/plugins/uptime/server/rest_api/create_route_with_auth.ts
+++ b/x-pack/plugins/uptime/server/rest_api/create_route_with_auth.ts
@@ -13,10 +13,22 @@ export const createRouteWithAuth = (
): UptimeRoute => {
const restRoute = routeCreator(libs);
const { handler, method, path, options, ...rest } = restRoute;
- const licenseCheckHandler: UMRouteHandler = async (customParams, context, request, response) => {
+ const licenseCheckHandler: UMRouteHandler = async ({
+ uptimeEsClient,
+ context,
+ request,
+ response,
+ savedObjectsClient,
+ }) => {
const { statusCode, message } = libs.license(context.licensing.license);
if (statusCode === 200) {
- return handler(customParams, context, request, response);
+ return handler({
+ uptimeEsClient,
+ context,
+ request,
+ response,
+ savedObjectsClient,
+ });
}
switch (statusCode) {
case 400:
@@ -29,6 +41,7 @@ export const createRouteWithAuth = (
return response.internalError();
}
};
+
return {
method,
path,
diff --git a/x-pack/plugins/uptime/server/rest_api/dynamic_settings.ts b/x-pack/plugins/uptime/server/rest_api/dynamic_settings.ts
index f7be9e10d1004..3dd77be6eaf8c 100644
--- a/x-pack/plugins/uptime/server/rest_api/dynamic_settings.ts
+++ b/x-pack/plugins/uptime/server/rest_api/dynamic_settings.ts
@@ -20,12 +20,8 @@ export const createGetDynamicSettingsRoute: UMRestApiRouteFactory = (libs: UMSer
method: 'GET',
path: '/api/uptime/dynamic_settings',
validate: false,
- handler: async ({ savedObjectsClient }, _context, _request, response): Promise => {
- const dynamicSettings = await savedObjectsAdapter.getUptimeDynamicSettings(savedObjectsClient);
-
- return response.ok({
- body: dynamicSettings,
- });
+ handler: async ({ savedObjectsClient }): Promise => {
+ return savedObjectsAdapter.getUptimeDynamicSettings(savedObjectsClient);
},
});
@@ -60,7 +56,7 @@ export const createPostDynamicSettingsRoute: UMRestApiRouteFactory = (libs: UMSe
}),
},
writeAccess: true,
- handler: async ({ savedObjectsClient }, _context, request, response): Promise => {
+ handler: async ({ savedObjectsClient, request, response }): Promise => {
const decoded = DynamicSettingsType.decode(request.body);
const certThresholdErrors = validateCertsValues(request.body as DynamicSettings);
diff --git a/x-pack/plugins/uptime/server/rest_api/index_state/get_index_pattern.ts b/x-pack/plugins/uptime/server/rest_api/index_state/get_index_pattern.ts
index 5c1be4cdd8143..671c6f0a71aa8 100644
--- a/x-pack/plugins/uptime/server/rest_api/index_state/get_index_pattern.ts
+++ b/x-pack/plugins/uptime/server/rest_api/index_state/get_index_pattern.ts
@@ -12,17 +12,9 @@ export const createGetIndexPatternRoute: UMRestApiRouteFactory = (libs: UMServer
method: 'GET',
path: API_URLS.INDEX_PATTERN,
validate: false,
- handler: async ({ uptimeEsClient }, _context, _request, response): Promise => {
- try {
- return response.ok({
- body: {
- ...(await libs.requests.getIndexPattern({
- uptimeEsClient,
- })),
- },
- });
- } catch (e) {
- return response.internalError({ body: { message: e.message } });
- }
+ handler: async ({ uptimeEsClient }): Promise => {
+ return await libs.requests.getIndexPattern({
+ uptimeEsClient,
+ });
},
});
diff --git a/x-pack/plugins/uptime/server/rest_api/index_state/get_index_status.ts b/x-pack/plugins/uptime/server/rest_api/index_state/get_index_status.ts
index e57643aed7e36..a347baf770d6a 100644
--- a/x-pack/plugins/uptime/server/rest_api/index_state/get_index_status.ts
+++ b/x-pack/plugins/uptime/server/rest_api/index_state/get_index_status.ts
@@ -4,6 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import { schema } from '@kbn/config-schema';
import { UMServerLibs } from '../../lib/lib';
import { UMRestApiRouteFactory } from '../types';
import { API_URLS } from '../../../common/constants';
@@ -11,18 +12,12 @@ import { API_URLS } from '../../../common/constants';
export const createGetIndexStatusRoute: UMRestApiRouteFactory = (libs: UMServerLibs) => ({
method: 'GET',
path: API_URLS.INDEX_STATUS,
- validate: false,
- handler: async ({ uptimeEsClient }, _context, _request, response): Promise => {
- try {
- return response.ok({
- body: {
- ...(await libs.requests.getIndexStatus({
- uptimeEsClient,
- })),
- },
- });
- } catch (e) {
- return response.internalError({ body: { message: e.message } });
- }
+ validate: {
+ query: schema.object({
+ _debug: schema.maybe(schema.boolean()),
+ }),
+ },
+ handler: async ({ uptimeEsClient }): Promise => {
+ return await libs.requests.getIndexStatus({ uptimeEsClient });
},
});
diff --git a/x-pack/plugins/uptime/server/rest_api/monitors/monitor_list.ts b/x-pack/plugins/uptime/server/rest_api/monitors/monitor_list.ts
index 50fe616ae9cb5..2c3c649bf68c6 100644
--- a/x-pack/plugins/uptime/server/rest_api/monitors/monitor_list.ts
+++ b/x-pack/plugins/uptime/server/rest_api/monitors/monitor_list.ts
@@ -19,52 +19,39 @@ export const createMonitorListRoute: UMRestApiRouteFactory = (libs) => ({
pagination: schema.maybe(schema.string()),
statusFilter: schema.maybe(schema.string()),
pageSize: schema.number(),
+ _debug: schema.maybe(schema.boolean()),
}),
},
options: {
tags: ['access:uptime-read'],
},
- handler: async ({ uptimeEsClient }, _context, request, response): Promise => {
- try {
- const {
- dateRangeStart,
- dateRangeEnd,
- filters,
- pagination,
- statusFilter,
- pageSize,
- } = request.query;
+ handler: async ({ uptimeEsClient, request }): Promise => {
+ const {
+ dateRangeStart,
+ dateRangeEnd,
+ filters,
+ pagination,
+ statusFilter,
+ pageSize,
+ } = request.query;
- const decodedPagination = pagination
- ? JSON.parse(decodeURIComponent(pagination))
- : CONTEXT_DEFAULTS.CURSOR_PAGINATION;
+ const decodedPagination = pagination
+ ? JSON.parse(decodeURIComponent(pagination))
+ : CONTEXT_DEFAULTS.CURSOR_PAGINATION;
- const {
- summaries,
- nextPagePagination,
- prevPagePagination,
- } = await libs.requests.getMonitorStates({
- uptimeEsClient,
- dateRangeStart,
- dateRangeEnd,
- pagination: decodedPagination,
- pageSize,
- filters,
- // this is added to make typescript happy,
- // this sort of reassignment used to be further downstream but I've moved it here
- // because this code is going to be decomissioned soon
- statusFilter: statusFilter || undefined,
- });
+ const result = await libs.requests.getMonitorStates({
+ uptimeEsClient,
+ dateRangeStart,
+ dateRangeEnd,
+ pagination: decodedPagination,
+ pageSize,
+ filters,
+ // this is added to make typescript happy,
+ // this sort of reassignment used to be further downstream but I've moved it here
+ // because this code is going to be decomissioned soon
+ statusFilter: statusFilter || undefined,
+ });
- return response.ok({
- body: {
- summaries,
- nextPagePagination,
- prevPagePagination,
- },
- });
- } catch (e) {
- return response.internalError({ body: { message: e.message } });
- }
+ return result;
},
});
diff --git a/x-pack/plugins/uptime/server/rest_api/monitors/monitor_locations.ts b/x-pack/plugins/uptime/server/rest_api/monitors/monitor_locations.ts
index e2dbf7114fc91..48a39d94ac69e 100644
--- a/x-pack/plugins/uptime/server/rest_api/monitors/monitor_locations.ts
+++ b/x-pack/plugins/uptime/server/rest_api/monitors/monitor_locations.ts
@@ -17,20 +17,17 @@ export const createGetMonitorLocationsRoute: UMRestApiRouteFactory = (libs: UMSe
monitorId: schema.string(),
dateStart: schema.string(),
dateEnd: schema.string(),
+ _debug: schema.maybe(schema.boolean()),
}),
},
- handler: async ({ uptimeEsClient }, _context, request, response): Promise => {
+ handler: async ({ uptimeEsClient, request }): Promise => {
const { monitorId, dateStart, dateEnd } = request.query;
- return response.ok({
- body: {
- ...(await libs.requests.getMonitorLocations({
- uptimeEsClient,
- monitorId,
- dateStart,
- dateEnd,
- })),
- },
+ return await libs.requests.getMonitorLocations({
+ uptimeEsClient,
+ monitorId,
+ dateStart,
+ dateEnd,
});
},
});
diff --git a/x-pack/plugins/uptime/server/rest_api/monitors/monitor_status.ts b/x-pack/plugins/uptime/server/rest_api/monitors/monitor_status.ts
index 3d47f0eab8640..94edc6e4d7efe 100644
--- a/x-pack/plugins/uptime/server/rest_api/monitors/monitor_status.ts
+++ b/x-pack/plugins/uptime/server/rest_api/monitors/monitor_status.ts
@@ -12,26 +12,22 @@ import { API_URLS } from '../../../common/constants';
export const createGetStatusBarRoute: UMRestApiRouteFactory = (libs: UMServerLibs) => ({
method: 'GET',
path: API_URLS.MONITOR_STATUS,
-
validate: {
query: schema.object({
monitorId: schema.string(),
dateStart: schema.string(),
dateEnd: schema.string(),
+ _debug: schema.maybe(schema.boolean()),
}),
},
- handler: async ({ uptimeEsClient }, _context, request, response): Promise => {
+ handler: async ({ uptimeEsClient, request }): Promise => {
const { monitorId, dateStart, dateEnd } = request.query;
- const result = await libs.requests.getLatestMonitor({
+
+ return await libs.requests.getLatestMonitor({
uptimeEsClient,
monitorId,
dateStart,
dateEnd,
});
- return response.ok({
- body: {
- ...result,
- },
- });
},
});
diff --git a/x-pack/plugins/uptime/server/rest_api/monitors/monitors_details.ts b/x-pack/plugins/uptime/server/rest_api/monitors/monitors_details.ts
index 0982fc1986604..efbdf69a883d6 100644
--- a/x-pack/plugins/uptime/server/rest_api/monitors/monitors_details.ts
+++ b/x-pack/plugins/uptime/server/rest_api/monitors/monitors_details.ts
@@ -17,23 +17,20 @@ export const createGetMonitorDetailsRoute: UMRestApiRouteFactory = (libs: UMServ
monitorId: schema.string(),
dateStart: schema.maybe(schema.string()),
dateEnd: schema.maybe(schema.string()),
+ _debug: schema.maybe(schema.boolean()),
}),
},
- handler: async ({ uptimeEsClient }, context, request, response): Promise => {
+ handler: async ({ uptimeEsClient, context, request }): Promise => {
const { monitorId, dateStart, dateEnd } = request.query;
const alertsClient = context.alerting?.getAlertsClient();
- return response.ok({
- body: {
- ...(await libs.requests.getMonitorDetails({
- uptimeEsClient,
- monitorId,
- dateStart,
- dateEnd,
- alertsClient,
- })),
- },
+ return await libs.requests.getMonitorDetails({
+ uptimeEsClient,
+ monitorId,
+ dateStart,
+ dateEnd,
+ alertsClient,
});
},
});
diff --git a/x-pack/plugins/uptime/server/rest_api/monitors/monitors_durations.ts b/x-pack/plugins/uptime/server/rest_api/monitors/monitors_durations.ts
index eec3fdf9e7257..09d0ce4555309 100644
--- a/x-pack/plugins/uptime/server/rest_api/monitors/monitors_durations.ts
+++ b/x-pack/plugins/uptime/server/rest_api/monitors/monitors_durations.ts
@@ -18,20 +18,17 @@ export const createGetMonitorDurationRoute: UMRestApiRouteFactory = (libs: UMSer
monitorId: schema.string(),
dateStart: schema.string(),
dateEnd: schema.string(),
+ _debug: schema.maybe(schema.boolean()),
}),
},
- handler: async ({ uptimeEsClient }, _context, request, response): Promise => {
+ handler: async ({ uptimeEsClient, request }): Promise => {
const { monitorId, dateStart, dateEnd } = request.query;
- return response.ok({
- body: {
- ...(await libs.requests.getMonitorDurationChart({
- uptimeEsClient,
- monitorId,
- dateStart,
- dateEnd,
- })),
- },
+ return await libs.requests.getMonitorDurationChart({
+ uptimeEsClient,
+ monitorId,
+ dateStart,
+ dateEnd,
});
},
});
diff --git a/x-pack/plugins/uptime/server/rest_api/overview_filters/get_overview_filters.ts b/x-pack/plugins/uptime/server/rest_api/overview_filters/get_overview_filters.ts
index 163fbd4f8dd6e..ac58a8002899b 100644
--- a/x-pack/plugins/uptime/server/rest_api/overview_filters/get_overview_filters.ts
+++ b/x-pack/plugins/uptime/server/rest_api/overview_filters/get_overview_filters.ts
@@ -26,9 +26,10 @@ export const createGetOverviewFilters: UMRestApiRouteFactory = (libs: UMServerLi
schemes: arrayOrStringType,
ports: arrayOrStringType,
tags: arrayOrStringType,
+ _debug: schema.maybe(schema.boolean()),
}),
},
- handler: async ({ uptimeEsClient }, _context, request, response) => {
+ handler: async ({ uptimeEsClient, request, response }): Promise => {
const { dateRangeStart, dateRangeEnd, locations, schemes, search, ports, tags } = request.query;
let parsedSearch: Record | undefined;
@@ -40,7 +41,7 @@ export const createGetOverviewFilters: UMRestApiRouteFactory = (libs: UMServerLi
}
}
- const filtersResponse = await libs.requests.getFilterBar({
+ return await libs.requests.getFilterBar({
uptimeEsClient,
dateRangeStart,
dateRangeEnd,
@@ -52,7 +53,5 @@ export const createGetOverviewFilters: UMRestApiRouteFactory = (libs: UMServerLi
tags,
}),
});
-
- return response.ok({ body: { ...filtersResponse } });
},
});
diff --git a/x-pack/plugins/uptime/server/rest_api/pings/get_ping_histogram.ts b/x-pack/plugins/uptime/server/rest_api/pings/get_ping_histogram.ts
index ba36b171793b7..4797e4aae94bf 100644
--- a/x-pack/plugins/uptime/server/rest_api/pings/get_ping_histogram.ts
+++ b/x-pack/plugins/uptime/server/rest_api/pings/get_ping_histogram.ts
@@ -19,12 +19,13 @@ export const createGetPingHistogramRoute: UMRestApiRouteFactory = (libs: UMServe
monitorId: schema.maybe(schema.string()),
filters: schema.maybe(schema.string()),
bucketSize: schema.maybe(schema.string()),
+ _debug: schema.maybe(schema.boolean()),
}),
},
- handler: async ({ uptimeEsClient }, _context, request, response): Promise => {
+ handler: async ({ uptimeEsClient, request }): Promise => {
const { dateStart, dateEnd, monitorId, filters, bucketSize } = request.query;
- const result = await libs.requests.getPingHistogram({
+ return await libs.requests.getPingHistogram({
uptimeEsClient,
from: dateStart,
to: dateEnd,
@@ -32,11 +33,5 @@ export const createGetPingHistogramRoute: UMRestApiRouteFactory = (libs: UMServe
filters,
bucketSize,
});
-
- return response.ok({
- body: {
- ...result,
- },
- });
},
});
diff --git a/x-pack/plugins/uptime/server/rest_api/pings/get_pings.ts b/x-pack/plugins/uptime/server/rest_api/pings/get_pings.ts
index 2a1a401ec3153..fa1ef565d0146 100644
--- a/x-pack/plugins/uptime/server/rest_api/pings/get_pings.ts
+++ b/x-pack/plugins/uptime/server/rest_api/pings/get_pings.ts
@@ -22,12 +22,13 @@ export const createGetPingsRoute: UMRestApiRouteFactory = (libs: UMServerLibs) =
size: schema.maybe(schema.number()),
sort: schema.maybe(schema.string()),
status: schema.maybe(schema.string()),
+ _debug: schema.maybe(schema.boolean()),
}),
},
- handler: async ({ uptimeEsClient }, _context, request, response): Promise => {
+ handler: async ({ uptimeEsClient, request, response }): Promise => {
const { from, to, index, monitorId, status, sort, size, locations } = request.query;
- const result = await libs.requests.getPings({
+ return await libs.requests.getPings({
uptimeEsClient,
dateRange: { from, to },
index,
@@ -37,11 +38,5 @@ export const createGetPingsRoute: UMRestApiRouteFactory = (libs: UMServerLibs) =
size,
locations: locations ? JSON.parse(locations) : [],
});
-
- return response.ok({
- body: {
- ...result,
- },
- });
},
});
diff --git a/x-pack/plugins/uptime/server/rest_api/pings/journey_screenshots.ts b/x-pack/plugins/uptime/server/rest_api/pings/journey_screenshots.ts
index 080fc8ab8f8ee..8343b24e601a1 100644
--- a/x-pack/plugins/uptime/server/rest_api/pings/journey_screenshots.ts
+++ b/x-pack/plugins/uptime/server/rest_api/pings/journey_screenshots.ts
@@ -15,10 +15,12 @@ export const createJourneyScreenshotRoute: UMRestApiRouteFactory = (libs: UMServ
params: schema.object({
checkGroup: schema.string(),
stepIndex: schema.number(),
+ _debug: schema.maybe(schema.boolean()),
}),
},
- handler: async ({ uptimeEsClient }, _context, request, response) => {
+ handler: async ({ uptimeEsClient, request, response }) => {
const { checkGroup, stepIndex } = request.params;
+
const result = await libs.requests.getJourneyScreenshot({
uptimeEsClient,
checkGroup,
diff --git a/x-pack/plugins/uptime/server/rest_api/pings/journeys.ts b/x-pack/plugins/uptime/server/rest_api/pings/journeys.ts
index 615da48e8f0b2..b2559ee8d7054 100644
--- a/x-pack/plugins/uptime/server/rest_api/pings/journeys.ts
+++ b/x-pack/plugins/uptime/server/rest_api/pings/journeys.ts
@@ -14,9 +14,10 @@ export const createJourneyRoute: UMRestApiRouteFactory = (libs: UMServerLibs) =>
validate: {
params: schema.object({
checkGroup: schema.string(),
+ _debug: schema.maybe(schema.boolean()),
}),
},
- handler: async ({ uptimeEsClient }, _context, request, response) => {
+ handler: async ({ uptimeEsClient, request }): Promise => {
const { checkGroup } = request.params;
const result = await libs.requests.getJourneySteps({
uptimeEsClient,
@@ -28,13 +29,11 @@ export const createJourneyRoute: UMRestApiRouteFactory = (libs: UMServerLibs) =>
checkGroup,
});
- return response.ok({
- body: {
- checkGroup,
- steps: result,
- details,
- },
- });
+ return {
+ checkGroup,
+ steps: result,
+ details,
+ };
},
});
@@ -46,18 +45,16 @@ export const createJourneyFailedStepsRoute: UMRestApiRouteFactory = (libs: UMSer
checkGroups: schema.arrayOf(schema.string()),
}),
},
- handler: async ({ uptimeEsClient }, _context, request, response) => {
+ handler: async ({ uptimeEsClient, request }): Promise => {
const { checkGroups } = request.query;
const result = await libs.requests.getJourneyFailedSteps({
uptimeEsClient,
checkGroups,
});
- return response.ok({
- body: {
- checkGroups,
- steps: result,
- },
- });
+ return {
+ checkGroups,
+ steps: result,
+ };
},
});
diff --git a/x-pack/plugins/uptime/server/rest_api/snapshot/get_snapshot_count.ts b/x-pack/plugins/uptime/server/rest_api/snapshot/get_snapshot_count.ts
index 224ef87fd90af..2d22259fbf786 100644
--- a/x-pack/plugins/uptime/server/rest_api/snapshot/get_snapshot_count.ts
+++ b/x-pack/plugins/uptime/server/rest_api/snapshot/get_snapshot_count.ts
@@ -17,20 +17,17 @@ export const createGetSnapshotCount: UMRestApiRouteFactory = (libs: UMServerLibs
dateRangeStart: schema.string(),
dateRangeEnd: schema.string(),
filters: schema.maybe(schema.string()),
+ _debug: schema.maybe(schema.boolean()),
}),
},
- handler: async ({ uptimeEsClient }, _context, request, response): Promise => {
+ handler: async ({ uptimeEsClient, request }): Promise => {
const { dateRangeStart, dateRangeEnd, filters } = request.query;
- const result = await libs.requests.getSnapshotCount({
+
+ return await libs.requests.getSnapshotCount({
uptimeEsClient,
dateRangeStart,
dateRangeEnd,
filters,
});
- return response.ok({
- body: {
- ...result,
- },
- });
},
});
diff --git a/x-pack/plugins/uptime/server/rest_api/telemetry/log_page_view.ts b/x-pack/plugins/uptime/server/rest_api/telemetry/log_page_view.ts
index 85f274c96cf9a..e69556837af44 100644
--- a/x-pack/plugins/uptime/server/rest_api/telemetry/log_page_view.ts
+++ b/x-pack/plugins/uptime/server/rest_api/telemetry/log_page_view.ts
@@ -21,14 +21,10 @@ export const createLogPageViewRoute: UMRestApiRouteFactory = () => ({
autoRefreshEnabled: schema.boolean(),
autorefreshInterval: schema.number(),
refreshTelemetryHistory: schema.maybe(schema.boolean()),
+ _debug: schema.maybe(schema.boolean()),
}),
},
- handler: async (
- { savedObjectsClient, uptimeEsClient },
- _context,
- request,
- response
- ): Promise => {
+ handler: async ({ savedObjectsClient, uptimeEsClient, request }): Promise => {
const pageView = request.body as PageViewParams;
if (pageView.refreshTelemetryHistory) {
KibanaTelemetryAdapter.clearLocalTelemetry();
@@ -37,10 +33,6 @@ export const createLogPageViewRoute: UMRestApiRouteFactory = () => ({
uptimeEsClient,
savedObjectsClient
);
- const pageViewResult = KibanaTelemetryAdapter.countPageView(pageView as PageViewParams);
-
- return response.ok({
- body: pageViewResult,
- });
+ return KibanaTelemetryAdapter.countPageView(pageView as PageViewParams);
},
});
diff --git a/x-pack/plugins/uptime/server/rest_api/types.ts b/x-pack/plugins/uptime/server/rest_api/types.ts
index df1762a3b318d..4e627cebb3459 100644
--- a/x-pack/plugins/uptime/server/rest_api/types.ts
+++ b/x-pack/plugins/uptime/server/rest_api/types.ts
@@ -14,7 +14,6 @@ import {
KibanaRequest,
KibanaResponseFactory,
IKibanaResponse,
- IScopedClusterClient,
} from 'kibana/server';
import { UMServerLibs, UptimeESClient } from '../lib/lib';
@@ -59,20 +58,18 @@ export type UMRestApiRouteFactory = (libs: UMServerLibs) => UptimeRoute;
export type UMKibanaRouteWrapper = (uptimeRoute: UptimeRoute) => UMKibanaRoute;
/**
- * This type can store custom parameters used by the internal Uptime route handlers.
+ * This is the contract we specify internally for route handling.
*/
-export interface UMRouteParams {
+export type UMRouteHandler = ({
+ uptimeEsClient,
+ context,
+ request,
+ response,
+ savedObjectsClient,
+}: {
uptimeEsClient: UptimeESClient;
- esClient: IScopedClusterClient;
+ context: RequestHandlerContext;
+ request: KibanaRequest, Record, Record>;
+ response: KibanaResponseFactory;
savedObjectsClient: SavedObjectsClientContract;
-}
-
-/**
- * This is the contract we specify internally for route handling.
- */
-export type UMRouteHandler = (
- params: UMRouteParams,
- context: RequestHandlerContext,
- request: KibanaRequest, Record, Record>,
- response: KibanaResponseFactory
-) => IKibanaResponse | Promise>;
+}) => IKibanaResponse | Promise>;
diff --git a/x-pack/plugins/uptime/server/rest_api/uptime_route_wrapper.ts b/x-pack/plugins/uptime/server/rest_api/uptime_route_wrapper.ts
index a1cf3c05e2de3..c8769dc4ea3df 100644
--- a/x-pack/plugins/uptime/server/rest_api/uptime_route_wrapper.ts
+++ b/x-pack/plugins/uptime/server/rest_api/uptime_route_wrapper.ts
@@ -7,6 +7,9 @@
import { UMKibanaRouteWrapper } from './types';
import { createUptimeESClient } from '../lib/lib';
+// eslint-disable-next-line @kbn/eslint/no-restricted-paths
+import { KibanaResponse } from '../../../../../src/core/server/http/router';
+
export const uptimeRouteWrapper: UMKibanaRouteWrapper = (uptimeRoute) => ({
...uptimeRoute,
options: {
@@ -17,15 +20,39 @@ export const uptimeRouteWrapper: UMKibanaRouteWrapper = (uptimeRoute) => ({
const { client: savedObjectsClient } = context.core.savedObjects;
const uptimeEsClient = createUptimeESClient({
+ request,
savedObjectsClient,
esClient: esClient.asCurrentUser,
});
- return uptimeRoute.handler(
- { uptimeEsClient, esClient, savedObjectsClient },
- context,
- request,
- response
- );
+ try {
+ const res = await uptimeRoute.handler({
+ uptimeEsClient,
+ savedObjectsClient,
+ context,
+ request,
+ response,
+ });
+
+ if (res instanceof KibanaResponse) {
+ return res;
+ }
+
+ return response.ok({
+ body: {
+ ...res,
+ },
+ });
+ } catch (e) {
+ // please don't remove this, this will be really helpful during debugging
+ /* eslint-disable-next-line no-console */
+ console.error(e);
+
+ return response.internalError({
+ body: {
+ message: e.message,
+ },
+ });
+ }
},
});
diff --git a/x-pack/test/functional/apps/lens/smokescreen.ts b/x-pack/test/functional/apps/lens/smokescreen.ts
index 462b385f27e5d..b91399a4a6756 100644
--- a/x-pack/test/functional/apps/lens/smokescreen.ts
+++ b/x-pack/test/functional/apps/lens/smokescreen.ts
@@ -13,8 +13,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const listingTable = getService('listingTable');
const testSubjects = getService('testSubjects');
- // FLAKY: https://github.com/elastic/kibana/issues/77969
- describe.skip('lens smokescreen tests', () => {
+ describe('lens smokescreen tests', () => {
it('should allow creation of lens xy chart', async () => {
await PageObjects.visualize.navigateToNewVisualization();
await PageObjects.visualize.clickVisType('lens');
diff --git a/x-pack/test/functional_cors/config.ts b/x-pack/test/functional_cors/config.ts
index da03fee476f13..b792aa2d183b6 100644
--- a/x-pack/test/functional_cors/config.ts
+++ b/x-pack/test/functional_cors/config.ts
@@ -55,8 +55,8 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
`--plugin-path=${corsTestPlugin}`,
`--test.cors.port=${pluginPort}`,
'--server.cors.enabled=true',
- '--server.cors.credentials=true',
- `--server.cors.origin=["${originUrl}"]`,
+ '--server.cors.allowCredentials=true',
+ `--server.cors.allowOrigin=["${originUrl}"]`,
],
},
};
diff --git a/x-pack/test/functional_cors/tests/cors.ts b/x-pack/test/functional_cors/tests/cors.ts
index ff5da26b4e275..774ffe1719f07 100644
--- a/x-pack/test/functional_cors/tests/cors.ts
+++ b/x-pack/test/functional_cors/tests/cors.ts
@@ -15,9 +15,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
describe('CORS', () => {
it('Communicates to Kibana with configured CORS', async () => {
const args: string[] = config.get('kbnTestServer.serverArgs');
- const originSetting = args.find((str) => str.includes('server.cors.origin'));
+ const originSetting = args.find((str) => str.includes('server.cors.allowOrigin'));
if (!originSetting) {
- throw new Error('Cannot find "server.cors.origin" argument');
+ throw new Error('Cannot find "server.cors.allowOrigin" argument');
}
const [, value] = originSetting.split('=');
const url = JSON.parse(value);