mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Canvas] Add Lens embeddables (#57499)
* Added lens embeddables to embed flyout Fixed import embedded panel styles (#58654) Merging to WIP draft branch * Added i18n strings for savedLens * Added tests for lens embeddables * Updated tests * Updated tests * Added style overrides for lens table * DDisables triggers on lens emebeddable * Updated test * Sets embeddable view mode according to app state * Fix embeddable component * Removed embeddable view mode logic * Removed unused import
This commit is contained in:
parent
fcf439625b
commit
3bd3364a55
15 changed files with 282 additions and 7 deletions
|
@ -7,9 +7,16 @@
|
|||
// @ts-ignore
|
||||
import { MAP_SAVED_OBJECT_TYPE } from '../../../maps/common/constants';
|
||||
import { VISUALIZE_EMBEDDABLE_TYPE } from '../../../../../../src/legacy/core_plugins/visualizations/public';
|
||||
import { LENS_EMBEDDABLE_TYPE } from '../../../../../plugins/lens/common/constants';
|
||||
import { SEARCH_EMBEDDABLE_TYPE } from '../../../../../../src/legacy/core_plugins/kibana/public/discover/np_ready/embeddable/constants';
|
||||
|
||||
export const EmbeddableTypes: { map: string; search: string; visualization: string } = {
|
||||
export const EmbeddableTypes: {
|
||||
lens: string;
|
||||
map: string;
|
||||
search: string;
|
||||
visualization: string;
|
||||
} = {
|
||||
lens: LENS_EMBEDDABLE_TYPE,
|
||||
map: MAP_SAVED_OBJECT_TYPE,
|
||||
search: SEARCH_EMBEDDABLE_TYPE,
|
||||
visualization: VISUALIZE_EMBEDDABLE_TYPE,
|
||||
|
|
|
@ -48,6 +48,7 @@ import { rounddate } from './rounddate';
|
|||
import { rowCount } from './rowCount';
|
||||
import { repeatImage } from './repeatImage';
|
||||
import { revealImage } from './revealImage';
|
||||
import { savedLens } from './saved_lens';
|
||||
import { savedMap } from './saved_map';
|
||||
import { savedSearch } from './saved_search';
|
||||
import { savedVisualization } from './saved_visualization';
|
||||
|
@ -109,6 +110,7 @@ export const functions = [
|
|||
revealImage,
|
||||
rounddate,
|
||||
rowCount,
|
||||
savedLens,
|
||||
savedMap,
|
||||
savedSearch,
|
||||
savedVisualization,
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
jest.mock('ui/new_platform');
|
||||
import { savedLens } from './saved_lens';
|
||||
import { getQueryFilters } from '../../../public/lib/build_embeddable_filters';
|
||||
|
||||
const filterContext = {
|
||||
and: [
|
||||
{ and: [], value: 'filter-value', column: 'filter-column', type: 'exactly' },
|
||||
{
|
||||
and: [],
|
||||
column: 'time-column',
|
||||
type: 'time',
|
||||
from: '2019-06-04T04:00:00.000Z',
|
||||
to: '2019-06-05T04:00:00.000Z',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
describe('savedLens', () => {
|
||||
const fn = savedLens().fn;
|
||||
const args = {
|
||||
id: 'some-id',
|
||||
title: null,
|
||||
timerange: null,
|
||||
};
|
||||
|
||||
it('accepts null context', () => {
|
||||
const expression = fn(null, args, {} as any);
|
||||
|
||||
expect(expression.input.filters).toEqual([]);
|
||||
});
|
||||
|
||||
it('accepts filter context', () => {
|
||||
const expression = fn(filterContext, args, {} as any);
|
||||
const embeddableFilters = getQueryFilters(filterContext.and);
|
||||
|
||||
expect(expression.input.filters).toEqual(embeddableFilters);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* 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 { ExpressionFunctionDefinition } from 'src/plugins/expressions/common';
|
||||
import { TimeRange } from 'src/plugins/data/public';
|
||||
import { EmbeddableInput } from 'src/legacy/core_plugins/embeddable_api/public/np_ready/public';
|
||||
import { getQueryFilters } from '../../../public/lib/build_embeddable_filters';
|
||||
import { Filter, TimeRange as TimeRangeArg } from '../../../types';
|
||||
import {
|
||||
EmbeddableTypes,
|
||||
EmbeddableExpressionType,
|
||||
EmbeddableExpression,
|
||||
} from '../../expression_types';
|
||||
import { getFunctionHelp } from '../../../i18n';
|
||||
import { Filter as DataFilter } from '../../../../../../../src/plugins/data/public';
|
||||
|
||||
interface Arguments {
|
||||
id: string;
|
||||
title: string | null;
|
||||
timerange: TimeRangeArg | null;
|
||||
}
|
||||
|
||||
export type SavedLensInput = EmbeddableInput & {
|
||||
id: string;
|
||||
timeRange?: TimeRange;
|
||||
filters: DataFilter[];
|
||||
};
|
||||
|
||||
const defaultTimeRange = {
|
||||
from: 'now-15m',
|
||||
to: 'now',
|
||||
};
|
||||
|
||||
type Return = EmbeddableExpression<SavedLensInput>;
|
||||
|
||||
export function savedLens(): ExpressionFunctionDefinition<
|
||||
'savedLens',
|
||||
Filter | null,
|
||||
Arguments,
|
||||
Return
|
||||
> {
|
||||
const { help, args: argHelp } = getFunctionHelp().savedLens;
|
||||
return {
|
||||
name: 'savedLens',
|
||||
help,
|
||||
args: {
|
||||
id: {
|
||||
types: ['string'],
|
||||
required: false,
|
||||
help: argHelp.id,
|
||||
},
|
||||
timerange: {
|
||||
types: ['timerange'],
|
||||
help: argHelp.timerange,
|
||||
required: false,
|
||||
},
|
||||
title: {
|
||||
types: ['string'],
|
||||
help: argHelp.title,
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
type: EmbeddableExpressionType,
|
||||
fn: (context, args) => {
|
||||
const filters = context ? context.and : [];
|
||||
|
||||
return {
|
||||
type: EmbeddableExpressionType,
|
||||
input: {
|
||||
id: args.id,
|
||||
filters: getQueryFilters(filters),
|
||||
timeRange: args.timerange || defaultTimeRange,
|
||||
title: args.title ? args.title : undefined,
|
||||
disableTriggers: true,
|
||||
},
|
||||
embeddableType: EmbeddableTypes.lens,
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
.canvasEmbeddable {
|
||||
.embPanel {
|
||||
border: none;
|
||||
background: none;
|
||||
|
||||
.embPanel__title {
|
||||
margin-bottom: $euiSizeXS;
|
||||
}
|
||||
|
||||
.embPanel__optionsMenuButton {
|
||||
border-radius: $euiBorderRadius;
|
||||
}
|
||||
|
||||
.canvas-isFullscreen & {
|
||||
.embPanel__optionsMenuButton {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&:focus .embPanel__optionsMenuButton,
|
||||
&:hover .embPanel__optionsMenuButton {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.euiTable {
|
||||
background: none;
|
||||
}
|
||||
|
||||
.lnsExpressionRenderer {
|
||||
@include euiScrollBar;
|
||||
}
|
||||
}
|
|
@ -18,11 +18,12 @@ import { start } from '../../../../../../../src/legacy/core_plugins/embeddable_a
|
|||
import { EmbeddableExpression } from '../../expression_types/embeddable';
|
||||
import { RendererStrings } from '../../../i18n';
|
||||
import { getSavedObjectFinder } from '../../../../../../../src/plugins/saved_objects/public';
|
||||
|
||||
const { embeddable: strings } = RendererStrings;
|
||||
import { embeddableInputToExpression } from './embeddable_input_to_expression';
|
||||
import { EmbeddableInput } from '../../expression_types';
|
||||
import { RendererHandlers } from '../../../types';
|
||||
import { CANVAS_EMBEDDABLE_CLASSNAME } from '../../../common/lib';
|
||||
|
||||
const { embeddable: strings } = RendererStrings;
|
||||
|
||||
const embeddablesRegistry: {
|
||||
[key: string]: IEmbeddable;
|
||||
|
@ -31,7 +32,7 @@ const embeddablesRegistry: {
|
|||
const renderEmbeddable = (embeddableObject: IEmbeddable, domNode: HTMLElement) => {
|
||||
return (
|
||||
<div
|
||||
className="embeddable"
|
||||
className={CANVAS_EMBEDDABLE_CLASSNAME}
|
||||
style={{ width: domNode.offsetWidth, height: domNode.offsetHeight, cursor: 'auto' }}
|
||||
>
|
||||
<I18nContext>
|
||||
|
|
|
@ -7,12 +7,17 @@
|
|||
jest.mock('ui/new_platform');
|
||||
import { embeddableInputToExpression } from './embeddable_input_to_expression';
|
||||
import { SavedMapInput } from '../../functions/common/saved_map';
|
||||
import { SavedLensInput } from '../../functions/common/saved_lens';
|
||||
import { EmbeddableTypes } from '../../expression_types';
|
||||
import { fromExpression, Ast } from '@kbn/interpreter/common';
|
||||
|
||||
const baseSavedMapInput = {
|
||||
const baseEmbeddableInput = {
|
||||
id: 'embeddableId',
|
||||
filters: [],
|
||||
};
|
||||
|
||||
const baseSavedMapInput = {
|
||||
...baseEmbeddableInput,
|
||||
isLayerTOCOpen: false,
|
||||
refreshConfig: {
|
||||
isPaused: true,
|
||||
|
@ -73,4 +78,45 @@ describe('input to expression', () => {
|
|||
expect(timerangeExpression.chain[0].arguments.to[0]).toEqual(input.timeRange?.to);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Lens Embeddable', () => {
|
||||
it('converts to a savedLens expression', () => {
|
||||
const input: SavedLensInput = {
|
||||
...baseEmbeddableInput,
|
||||
};
|
||||
|
||||
const expression = embeddableInputToExpression(input, EmbeddableTypes.lens);
|
||||
const ast = fromExpression(expression);
|
||||
|
||||
expect(ast.type).toBe('expression');
|
||||
expect(ast.chain[0].function).toBe('savedLens');
|
||||
|
||||
expect(ast.chain[0].arguments.id).toStrictEqual([input.id]);
|
||||
|
||||
expect(ast.chain[0].arguments).not.toHaveProperty('title');
|
||||
expect(ast.chain[0].arguments).not.toHaveProperty('timerange');
|
||||
});
|
||||
|
||||
it('includes optional input values', () => {
|
||||
const input: SavedLensInput = {
|
||||
...baseEmbeddableInput,
|
||||
title: 'title',
|
||||
timeRange: {
|
||||
from: 'now-1h',
|
||||
to: 'now',
|
||||
},
|
||||
};
|
||||
|
||||
const expression = embeddableInputToExpression(input, EmbeddableTypes.map);
|
||||
const ast = fromExpression(expression);
|
||||
|
||||
expect(ast.chain[0].arguments).toHaveProperty('title', [input.title]);
|
||||
expect(ast.chain[0].arguments).toHaveProperty('timerange');
|
||||
|
||||
const timerangeExpression = ast.chain[0].arguments.timerange[0] as Ast;
|
||||
expect(timerangeExpression.chain[0].function).toBe('timerange');
|
||||
expect(timerangeExpression.chain[0].arguments.from[0]).toEqual(input.timeRange?.from);
|
||||
expect(timerangeExpression.chain[0].arguments.to[0]).toEqual(input.timeRange?.to);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
import { EmbeddableTypes, EmbeddableInput } from '../../expression_types';
|
||||
import { SavedMapInput } from '../../functions/common/saved_map';
|
||||
import { SavedLensInput } from '../../functions/common/saved_lens';
|
||||
|
||||
/*
|
||||
Take the input from an embeddable and the type of embeddable and convert it into an expression
|
||||
|
@ -46,5 +47,23 @@ export function embeddableInputToExpression(
|
|||
}
|
||||
}
|
||||
|
||||
if (embeddableType === EmbeddableTypes.lens) {
|
||||
const lensInput = input as SavedLensInput;
|
||||
|
||||
expressionParts.push('savedLens');
|
||||
|
||||
expressionParts.push(`id="${input.id}"`);
|
||||
|
||||
if (input.title) {
|
||||
expressionParts.push(`title="${input.title}"`);
|
||||
}
|
||||
|
||||
if (lensInput.timeRange) {
|
||||
expressionParts.push(
|
||||
`timerange={timerange from="${lensInput.timeRange.from}" to="${lensInput.timeRange.to}"}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return expressionParts.join(' ');
|
||||
}
|
||||
|
|
|
@ -39,3 +39,4 @@ export const API_ROUTE_SHAREABLE_BASE = '/public/canvas';
|
|||
export const API_ROUTE_SHAREABLE_ZIP = '/public/canvas/zip';
|
||||
export const API_ROUTE_SHAREABLE_RUNTIME = '/public/canvas/runtime';
|
||||
export const API_ROUTE_SHAREABLE_RUNTIME_DOWNLOAD = `/public/canvas/${SHAREABLE_RUNTIME_NAME}.js`;
|
||||
export const CANVAS_EMBEDDABLE_CLASSNAME = `canvasEmbeddable`;
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { savedLens } from '../../../canvas_plugin_src/functions/common/saved_lens';
|
||||
import { FunctionHelp } from '../function_help';
|
||||
import { FunctionFactory } from '../../../types';
|
||||
|
||||
export const help: FunctionHelp<FunctionFactory<typeof savedLens>> = {
|
||||
help: i18n.translate('xpack.canvas.functions.savedLensHelpText', {
|
||||
defaultMessage: `Returns an embeddable for a saved lens object`,
|
||||
}),
|
||||
args: {
|
||||
id: i18n.translate('xpack.canvas.functions.savedLens.args.idHelpText', {
|
||||
defaultMessage: `The ID of the Saved Lens Object`,
|
||||
}),
|
||||
timerange: i18n.translate('xpack.canvas.functions.savedLens.args.timerangeHelpText', {
|
||||
defaultMessage: `The timerange of data that should be included`,
|
||||
}),
|
||||
title: i18n.translate('xpack.canvas.functions.savedLens.args.titleHelpText', {
|
||||
defaultMessage: `The title for the lens emebeddable`,
|
||||
}),
|
||||
},
|
||||
};
|
|
@ -62,6 +62,7 @@ import { help as replace } from './dict/replace';
|
|||
import { help as revealImage } from './dict/reveal_image';
|
||||
import { help as rounddate } from './dict/rounddate';
|
||||
import { help as rowCount } from './dict/row_count';
|
||||
import { help as savedLens } from './dict/saved_lens';
|
||||
import { help as savedMap } from './dict/saved_map';
|
||||
import { help as savedSearch } from './dict/saved_search';
|
||||
import { help as savedVisualization } from './dict/saved_visualization';
|
||||
|
@ -216,6 +217,7 @@ export const getFunctionHelp = (): FunctionHelpDict => ({
|
|||
revealImage,
|
||||
rounddate,
|
||||
rowCount,
|
||||
savedLens,
|
||||
savedMap,
|
||||
savedSearch,
|
||||
savedVisualization,
|
||||
|
|
|
@ -21,6 +21,9 @@ const allowedEmbeddables = {
|
|||
[EmbeddableTypes.map]: (id: string) => {
|
||||
return `savedMap id="${id}" | render`;
|
||||
},
|
||||
[EmbeddableTypes.lens]: (id: string) => {
|
||||
return `savedLens id="${id}" | render`;
|
||||
},
|
||||
// FIX: Only currently allow Map embeddables
|
||||
/* [EmbeddableTypes.visualization]: (id: string) => {
|
||||
return `filters | savedVisualization id="${id}" | render`;
|
||||
|
|
|
@ -19,6 +19,7 @@ import {
|
|||
} from '../../../state/actions/elements';
|
||||
import { selectToplevelNodes } from '../../../state/actions/transient';
|
||||
import { crawlTree, globalStateUpdater, shapesForNodes } from '../integration_utils';
|
||||
import { CANVAS_EMBEDDABLE_CLASSNAME } from '../../../../common/lib';
|
||||
import { InteractiveWorkpadPage as InteractiveComponent } from './interactive_workpad_page';
|
||||
import { eventHandlers } from './event_handlers';
|
||||
|
||||
|
@ -79,9 +80,14 @@ const isEmbeddableBody = element => {
|
|||
const hasClosest = typeof element.closest === 'function';
|
||||
|
||||
if (hasClosest) {
|
||||
return element.closest('.embeddable') && !element.closest('.embPanel__header');
|
||||
return (
|
||||
element.closest(`.${CANVAS_EMBEDDABLE_CLASSNAME}`) && !element.closest('.embPanel__header')
|
||||
);
|
||||
} else {
|
||||
return closest.call(element, '.embeddable') && !closest.call(element, '.embPanel__header');
|
||||
return (
|
||||
closest.call(element, `.${CANVAS_EMBEDDABLE_CLASSNAME}`) &&
|
||||
!closest.call(element, '.embPanel__header')
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -61,6 +61,7 @@
|
|||
|
||||
@import '../../canvas_plugin_src/renderers/advanced_filter/component/advanced_filter.scss';
|
||||
@import '../../canvas_plugin_src/renderers/dropdown_filter/component/dropdown_filter.scss';
|
||||
@import '../../canvas_plugin_src/renderers/embeddable/embeddable.scss';
|
||||
@import '../../canvas_plugin_src/renderers/plot/plot.scss';
|
||||
@import '../../canvas_plugin_src/renderers/reveal_image/reveal_image.scss';
|
||||
@import '../../canvas_plugin_src/renderers/time_filter/components/datetime_calendar/datetime_calendar.scss';
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
export const PLUGIN_ID = 'lens';
|
||||
export const LENS_EMBEDDABLE_TYPE = 'lens';
|
||||
export const NOT_INTERNATIONALIZED_PRODUCT_NAME = 'Lens Visualizations';
|
||||
export const BASE_APP_URL = '/app/kibana';
|
||||
export const BASE_API_URL = '/api/lens';
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue