[Visualizations] Changes in custom visualizations support (#88317) (#89266)

* Convert to typescript

* Move related files directly into plugin

* Implement toExpressionAst

* Remove build_pipeline dedicated fn

* Async import converter

* Create a custom renderer

* Move function directly into plugin

* Update tests

* Move files directly into related plugins

* Remove ExprVis instance usage in maps visualizations

* Use uiState updates

* Fix minor issues

* Update expression builder

* Update styles

* Create wrapper component

* Update rendering

* Create region map expression renderer

* Fix tests and types

* Fix initial render

* Remove resize subscription

* Fix custom visualization expression

* Update region map expression in tests

* Update files structure

* Remove ReactVisController

* Remove base visualization renderer

* Remove extra vis properties

* Use requiresSearch flag for agg based vis

* Update types

* Remove visualization expression function

* Create toExpressionAst function

* Update custom visualization example

* Update interpreter functional tests

* Enhance VisTypeDefinition interface

* Enhance visualization types

* Update license

Co-authored-by: Alexey Antonov <alexwizp@gmail.com>
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>

Co-authored-by: Alexey Antonov <alexwizp@gmail.com>
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Daniil 2021-01-26 13:02:07 +03:00 committed by GitHub
parent 2916bd9d8c
commit 3f81a0b16a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
140 changed files with 751 additions and 1889 deletions

View file

@ -29,8 +29,6 @@ beforeEach(() => {
name: 'test',
title: 'test',
visualization: null,
requestHandler: 'test',
responseHandler: 'test',
stage: 'beta',
requiresSearch: false,
hidden: false,

View file

@ -19,7 +19,7 @@ import {
EuiSelect,
} from '@elastic/eui';
import { VisOptionsProps } from 'src/plugins/vis_default_editor/public';
import { VisEditorOptionsProps } from 'src/plugins/visualizations/public';
import { IIndexPattern } from 'src/plugins/data/public';
import { ControlEditor } from './control_editor';
import {
@ -40,7 +40,7 @@ interface ControlsTabUiState {
type: CONTROL_TYPES;
}
export type ControlsTabProps = VisOptionsProps<InputControlVisParams> & {
export type ControlsTabProps = VisEditorOptionsProps<InputControlVisParams> & {
deps: InputControlVisDependencies;
};

View file

@ -7,7 +7,7 @@
*/
import React, { lazy } from 'react';
import { VisOptionsProps } from 'src/plugins/vis_default_editor/public';
import { VisEditorOptionsProps } from 'src/plugins/visualizations/public';
import { InputControlVisDependencies } from '../../plugin';
import { InputControlVisParams } from '../../types';
@ -15,9 +15,9 @@ const ControlsTab = lazy(() => import('./controls_tab'));
const OptionsTab = lazy(() => import('./options_tab'));
export const getControlsTab = (deps: InputControlVisDependencies) => (
props: VisOptionsProps<InputControlVisParams>
props: VisEditorOptionsProps<InputControlVisParams>
) => <ControlsTab {...props} deps={deps} />;
export const OptionsTabLazy = (props: VisOptionsProps<InputControlVisParams>) => (
export const OptionsTabLazy = (props: VisEditorOptionsProps<InputControlVisParams>) => (
<OptionsTab {...props} />
);

View file

@ -12,10 +12,10 @@ import { EuiForm, EuiFormRow, EuiSwitch } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiSwitchEvent } from '@elastic/eui';
import { VisOptionsProps } from 'src/plugins/vis_default_editor/public';
import { VisEditorOptionsProps } from 'src/plugins/visualizations/public';
import { InputControlVisParams } from '../../types';
export type OptionsTabProps = VisOptionsProps<InputControlVisParams>;
export type OptionsTabProps = VisEditorOptionsProps<InputControlVisParams>;
class OptionsTab extends PureComponent<OptionsTabProps> {
handleUpdateFiltersChange = (event: EuiSwitchEvent) => {

View file

@ -7,7 +7,7 @@
*/
import { i18n } from '@kbn/i18n';
import { VisGroups, BaseVisTypeOptions } from '../../visualizations/public';
import { VisGroups, VisTypeDefinition } from '../../visualizations/public';
import { getControlsTab, OptionsTabLazy } from './components/editor';
import { InputControlVisDependencies } from './plugin';
import { toExpressionAst } from './to_ast';
@ -15,7 +15,7 @@ import { InputControlVisParams } from './types';
export function createInputControlVisTypeDefinition(
deps: InputControlVisDependencies
): BaseVisTypeOptions<InputControlVisParams> {
): VisTypeDefinition<InputControlVisParams> {
const ControlsTab = getControlsTab(deps);
return {
@ -56,7 +56,6 @@ export function createInputControlVisTypeDefinition(
],
},
inspectorAdapters: {},
requestHandler: 'none',
toExpressionAst,
};
}

View file

@ -8,11 +8,11 @@
import React, { lazy } from 'react';
import { IServiceSettings } from 'src/plugins/maps_legacy/public';
import { VisOptionsProps } from 'src/plugins/vis_default_editor/public';
import { VisEditorOptionsProps } from 'src/plugins/visualizations/public';
import { RegionMapVisParams } from '../region_map_types';
const RegionMapOptions = lazy(() => import('./region_map_options'));
export const createRegionMapOptions = (getServiceSettings: () => Promise<IServiceSettings>) => (
props: VisOptionsProps<RegionMapVisParams>
props: VisEditorOptionsProps<RegionMapVisParams>
) => <RegionMapOptions {...props} getServiceSettings={getServiceSettings} />;

View file

@ -10,7 +10,7 @@ import React, { useCallback, useMemo } from 'react';
import { EuiIcon, EuiLink, EuiPanel, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { VisOptionsProps } from 'src/plugins/vis_default_editor/public';
import { VisEditorOptionsProps } from 'src/plugins/visualizations/public';
import { FileLayerField, VectorLayer, IServiceSettings } from '../../../maps_legacy/public';
import { SelectOption, SwitchOption, NumberInputOption } from '../../../vis_default_editor/public';
import { WmsOptions } from '../../../maps_legacy/public';
@ -28,7 +28,7 @@ const mapFieldForOption = ({ description, name }: FileLayerField) => ({
export type RegionMapOptionsProps = {
getServiceSettings: () => Promise<IServiceSettings>;
} & VisOptionsProps<RegionMapVisParams>;
} & VisEditorOptionsProps<RegionMapVisParams>;
function RegionMapOptions(props: RegionMapOptionsProps) {
const { getServiceSettings, stateParams, vis, setValue } = props;

View file

@ -8,7 +8,7 @@
import { i18n } from '@kbn/i18n';
import { BaseVisTypeOptions } from '../../visualizations/public';
import { VisTypeDefinition } from '../../visualizations/public';
import { truncatedColorSchemas } from '../../charts/public';
import { ORIGIN } from '../../maps_legacy/public';
@ -23,7 +23,7 @@ export function createRegionMapTypeDefinition({
uiSettings,
regionmapsConfig,
getServiceSettings,
}: RegionMapVisualizationDependencies): BaseVisTypeOptions<RegionMapVisParams> {
}: RegionMapVisualizationDependencies): VisTypeDefinition<RegionMapVisParams> {
return {
name: 'region_map',
getInfoMessage: getDeprecationMessage,
@ -139,5 +139,6 @@ provided base maps, or add your own. Darker colors represent higher values.',
return vis;
},
requiresSearch: true,
};
}

View file

@ -11,11 +11,11 @@ import {
IndexPatternLoadExpressionFunctionDefinition,
} from '../../data/public';
import { buildExpression, buildExpressionFunction } from '../../expressions/public';
import { getVisSchemas, Vis, BuildPipelineParams } from '../../visualizations/public';
import { getVisSchemas, VisToExpressionAst } from '../../visualizations/public';
import { RegionMapExpressionFunctionDefinition } from './region_map_fn';
import { RegionMapVisConfig, RegionMapVisParams } from './region_map_types';
export const toExpressionAst = (vis: Vis<RegionMapVisParams>, params: BuildPipelineParams) => {
export const toExpressionAst: VisToExpressionAst<RegionMapVisParams> = (vis, params) => {
const esaggs = buildExpressionFunction<EsaggsExpressionFunctionDefinition>('esaggs', {
index: buildExpression([
buildExpressionFunction<IndexPatternLoadExpressionFunctionDefinition>('indexPatternLoad', {

View file

@ -10,8 +10,8 @@ import React, { useEffect } from 'react';
import { EuiPanel, EuiSpacer } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { VisEditorOptionsProps } from 'src/plugins/visualizations/public';
import {
VisOptionsProps,
BasicOptions,
SelectOption,
SwitchOption,
@ -21,7 +21,7 @@ import { WmsOptions } from '../../../maps_legacy/public';
import { TileMapVisParams } from '../types';
import { MapTypes } from '../utils/map_types';
export type TileMapOptionsProps = VisOptionsProps<TileMapVisParams>;
export type TileMapOptionsProps = VisEditorOptionsProps<TileMapVisParams>;
function TileMapOptions(props: TileMapOptionsProps) {
const { stateParams, setValue, vis } = props;

View file

@ -7,7 +7,7 @@
*/
import { i18n } from '@kbn/i18n';
import { BaseVisTypeOptions } from 'src/plugins/visualizations/public';
import { VisTypeDefinition } from 'src/plugins/visualizations/public';
import { truncatedColorSchemas } from '../../charts/public';
// @ts-expect-error
@ -21,7 +21,7 @@ import { MapTypes } from './utils/map_types';
export function createTileMapTypeDefinition(
dependencies: TileMapVisualizationDependencies
): BaseVisTypeOptions<TileMapVisParams> {
): VisTypeDefinition<TileMapVisParams> {
const { uiSettings, getServiceSettings } = dependencies;
return {
@ -147,5 +147,6 @@ export function createTileMapTypeDefinition(
}
return vis;
},
requiresSearch: true,
};
}

View file

@ -11,11 +11,11 @@ import {
IndexPatternLoadExpressionFunctionDefinition,
} from '../../data/public';
import { buildExpression, buildExpressionFunction } from '../../expressions/public';
import { getVisSchemas, Vis, BuildPipelineParams } from '../../visualizations/public';
import { getVisSchemas, VisToExpressionAst } from '../../visualizations/public';
import { TileMapExpressionFunctionDefinition } from './tile_map_fn';
import { TileMapVisConfig, TileMapVisParams } from './types';
export const toExpressionAst = (vis: Vis<TileMapVisParams>, params: BuildPipelineParams) => {
export const toExpressionAst: VisToExpressionAst<TileMapVisParams> = (vis, params) => {
const esaggs = buildExpressionFunction<EsaggsExpressionFunctionDefinition>('esaggs', {
index: buildExpression([
buildExpressionFunction<IndexPatternLoadExpressionFunctionDefinition>('indexPatternLoad', {

View file

@ -50,7 +50,6 @@ describe('DefaultEditorGroup helpers', () => {
min: 0,
max: 3,
aggFilter: [],
editor: false,
params: [],
defaults: null,
mustBeFirst: true,
@ -62,7 +61,6 @@ describe('DefaultEditorGroup helpers', () => {
min: 2,
max: 3,
aggFilter: [],
editor: false,
params: [],
defaults: null,
},

View file

@ -10,7 +10,7 @@ import React from 'react';
import { i18n } from '@kbn/i18n';
import { VisOptionsProps } from '../../vis_options_props';
import { VisEditorOptionsProps } from '../../../../visualizations/public';
import { SwitchOption } from './switch';
import { SelectOption } from './select';
@ -23,7 +23,7 @@ function BasicOptions<VisParams extends BasicOptionsParams>({
stateParams,
setValue,
vis,
}: VisOptionsProps<VisParams>) {
}: VisEditorOptionsProps<VisParams>) {
return (
<>
<SelectOption

View file

@ -12,7 +12,7 @@ import { EuiLink, EuiText } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { ColorSchemaParams, ColorSchema } from 'src/plugins/charts/public';
import { VisOptionsProps } from '../../vis_options_props';
import { VisEditorOptionsProps } from 'src/plugins/visualizations/public';
import { SelectOption } from './select';
import { SwitchOption } from './switch';
@ -24,7 +24,7 @@ export type SetColorSchemaOptionsValue = <T extends keyof ColorSchemaParams>(
interface ColorSchemaOptionsProps extends ColorSchemaParams {
disabled?: boolean;
colorSchemas: ColorSchema[];
uiState: VisOptionsProps['uiState'];
uiState: VisEditorOptionsProps['uiState'];
setValue: SetColorSchemaOptionsValue;
showHelpText?: boolean;
}

View file

@ -9,13 +9,12 @@
import { useCallback, useState } from 'react';
import { i18n } from '@kbn/i18n';
import { Vis } from '../../../../visualizations/public';
import { Vis, VisEditorOptionsProps } from '../../../../visualizations/public';
import { VisOptionsProps } from '../../vis_options_props';
import { DefaultEditorDataTab, DefaultEditorDataTabProps } from './data_tab';
export interface OptionTab {
editor: React.ComponentType<VisOptionsProps | DefaultEditorDataTabProps>;
editor: React.ComponentType<VisEditorOptionsProps | DefaultEditorDataTabProps>;
name: string;
title: string;
isSelected?: boolean;

View file

@ -16,7 +16,6 @@ export { PalettePicker } from './components/controls/palette_picker';
export * from './components/options';
export { RangesParamEditor, RangeValues } from './components/controls/ranges';
export * from './editor_size';
export * from './vis_options_props';
export * from './utils';
export const plugin = (context: PluginInitializerContext) => {

View file

@ -1,22 +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
* and the Server Side Public License, v 1; you may not use this file except in
* compliance with, at your election, the Elastic License or the Server Side
* Public License, v 1.
*/
import { Vis, PersistedState } from 'src/plugins/visualizations/public';
import { IAggConfigs } from 'src/plugins/data/public';
export interface VisOptionsProps<VisParamType = unknown> {
aggs: IAggConfigs;
hasHistogramAgg: boolean;
isTabSelected: boolean;
stateParams: VisParamType;
vis: Vis;
uiState: PersistedState;
setValue<T extends keyof VisParamType>(paramName: T, value: VisParamType[T]): void;
setValidity(isValid: boolean): void;
setTouched(isTouched: boolean): void;
}

View file

@ -9,7 +9,7 @@
import React from 'react';
import { shallow } from 'enzyme';
import { VisOptionsProps } from 'src/plugins/vis_default_editor/public';
import { VisEditorOptionsProps } from 'src/plugins/visualizations/public';
import { MarkdownVisParams } from './types';
import { MarkdownOptions } from './markdown_options';
@ -21,7 +21,7 @@ describe('MarkdownOptions', () => {
openLinksInNewTab: false,
},
setValue: jest.fn(),
} as unknown) as VisOptionsProps<MarkdownVisParams>;
} as unknown) as VisEditorOptionsProps<MarkdownVisParams>;
it('should match snapshot', () => {
const comp = shallow(<MarkdownOptions {...props} />);

View file

@ -18,10 +18,10 @@ import {
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { VisOptionsProps } from 'src/plugins/vis_default_editor/public';
import { VisEditorOptionsProps } from 'src/plugins/visualizations/public';
import { MarkdownVisParams } from './types';
function MarkdownOptions({ stateParams, setValue }: VisOptionsProps<MarkdownVisParams>) {
function MarkdownOptions({ stateParams, setValue }: VisEditorOptionsProps<MarkdownVisParams>) {
const onMarkdownUpdate = useCallback(
({ target: { value } }: React.ChangeEvent<HTMLTextAreaElement>) => setValue('markdown', value),
[setValue]

View file

@ -11,10 +11,11 @@ import { i18n } from '@kbn/i18n';
import { MarkdownOptions } from './markdown_options';
import { SettingsOptions } from './settings_options_lazy';
import { DefaultEditorSize } from '../../vis_default_editor/public';
import { VisGroups } from '../../visualizations/public';
import { VisGroups, VisTypeDefinition } from '../../visualizations/public';
import { toExpressionAst } from './to_ast';
import { MarkdownVisParams } from './types';
export const markdownVisDefinition = {
export const markdownVisDefinition: VisTypeDefinition<MarkdownVisParams> = {
name: 'markdown',
title: 'Markdown',
isAccessible: true,
@ -58,7 +59,5 @@ export const markdownVisDefinition = {
showTimePicker: false,
showFilterBar: false,
},
requestHandler: 'none',
responseHandler: 'none',
inspectorAdapters: {},
};

View file

@ -10,10 +10,11 @@ import React from 'react';
import { EuiPanel } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { VisOptionsProps, SwitchOption, RangeOption } from '../../vis_default_editor/public';
import { VisEditorOptionsProps } from 'src/plugins/visualizations/public';
import { SwitchOption, RangeOption } from '../../vis_default_editor/public';
import { MarkdownVisParams } from './types';
function SettingsOptions({ stateParams, setValue }: VisOptionsProps<MarkdownVisParams>) {
function SettingsOptions({ stateParams, setValue }: VisEditorOptionsProps<MarkdownVisParams>) {
return (
<EuiPanel paddingSize="s">
<RangeOption

View file

@ -9,8 +9,9 @@
import { VisToExpressionAst } from '../../visualizations/public';
import { buildExpression, buildExpressionFunction } from '../../expressions/public';
import { MarkdownVisExpressionFunctionDefinition } from './markdown_fn';
import { MarkdownVisParams } from './types';
export const toExpressionAst: VisToExpressionAst = (vis) => {
export const toExpressionAst: VisToExpressionAst<MarkdownVisParams> = (vis) => {
const { markdown, fontSize, openLinksInNewTab } = vis.params;
const markdownVis = buildExpressionFunction<MarkdownVisExpressionFunctionDefinition>(

View file

@ -5,9 +5,6 @@ Object {
"as": "metric_vis",
"type": "render",
"value": Object {
"params": Object {
"listenOnChange": true,
},
"visConfig": Object {
"dimensions": Object {
"metrics": undefined,

View file

@ -37,6 +37,9 @@ Object {
"percentageMode": Array [
true,
],
"showLabels": Array [
false,
],
},
"function": "metricVis",
"type": "function",
@ -79,7 +82,11 @@ Object {
"type": "function",
},
Object {
"arguments": Object {},
"arguments": Object {
"showLabels": Array [
false,
],
},
"function": "metricVis",
"type": "function",
},

View file

@ -18,10 +18,10 @@ import {
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { VisEditorOptionsProps } from 'src/plugins/visualizations/public';
import {
ColorRanges,
SetColorRangeValue,
VisOptionsProps,
SwitchOption,
SetColorSchemaOptionsValue,
ColorSchemaOptions,
@ -37,7 +37,7 @@ function MetricVisOptions({
setTouched,
vis,
uiState,
}: VisOptionsProps<VisParams>) {
}: VisEditorOptionsProps<VisParams>) {
const setMetricValue: <T extends keyof MetricVisParam>(
paramName: T,
value: MetricVisParam[T]

View file

@ -39,7 +39,6 @@ export interface MetricVisRenderValue {
visType: typeof visType;
visData: Input;
visConfig: Pick<VisParams, 'metric' | 'dimensions'>;
params: any;
}
export type MetricVisExpressionFunctionDefinition = ExpressionFunctionDefinition<
@ -194,9 +193,6 @@ export const createMetricVisFn = (): MetricVisExpressionFunctionDefinition => ({
},
dimensions,
},
params: {
listenOnChange: true,
},
},
};
},

View file

@ -9,11 +9,12 @@
import { i18n } from '@kbn/i18n';
import { MetricVisOptions } from './components/metric_vis_options';
import { ColorSchemas, colorSchemas, ColorMode } from '../../charts/public';
import { BaseVisTypeOptions } from '../../visualizations/public';
import { VisTypeDefinition } from '../../visualizations/public';
import { AggGroupNames } from '../../data/public';
import { toExpressionAst } from './to_ast';
import { VisParams } from './types';
export const createMetricVisTypeDefinition = (): BaseVisTypeOptions => ({
export const createMetricVisTypeDefinition = (): VisTypeDefinition<VisParams> => ({
name: 'metric',
title: i18n.translate('visTypeMetric.metricTitle', { defaultMessage: 'Metric' }),
icon: 'visMetric',
@ -110,4 +111,5 @@ export const createMetricVisTypeDefinition = (): BaseVisTypeOptions => ({
},
],
},
requiresSearch: true,
});

View file

@ -6,14 +6,17 @@
* Public License, v 1.
*/
import { TimefilterContract } from 'src/plugins/data/public';
import { Vis } from 'src/plugins/visualizations/public';
import { toExpressionAst } from './to_ast';
import { Vis } from '../../visualizations/public';
import { VisParams } from './types';
describe('metric vis toExpressionAst function', () => {
let vis: Vis;
let vis: Vis<VisParams>;
beforeEach(() => {
vis = {
vis = ({
isHierarchical: () => false,
type: {},
params: {
@ -26,18 +29,22 @@ describe('metric vis toExpressionAst function', () => {
aggs: [],
} as any,
},
} as any;
} as unknown) as Vis<VisParams>;
});
it('without params', () => {
vis.params = { metric: {} };
const actual = toExpressionAst(vis, {});
vis.params = { metric: {} } as VisParams;
const actual = toExpressionAst(vis, {
timefilter: {} as TimefilterContract,
});
expect(actual).toMatchSnapshot();
});
it('with percentage mode should have percentage format', () => {
vis.params = { metric: { percentageMode: true } };
const actual = toExpressionAst(vis, {});
vis.params = { metric: { percentageMode: true } } as VisParams;
const actual = toExpressionAst(vis, {
timefilter: {} as TimefilterContract,
});
expect(actual).toMatchSnapshot();
});
});

View file

@ -7,13 +7,14 @@
*/
import { get } from 'lodash';
import { getVisSchemas, SchemaConfig, Vis } from '../../visualizations/public';
import { getVisSchemas, SchemaConfig, VisToExpressionAst } from '../../visualizations/public';
import { buildExpression, buildExpressionFunction } from '../../expressions/public';
import { MetricVisExpressionFunctionDefinition } from './metric_vis_fn';
import {
EsaggsExpressionFunctionDefinition,
IndexPatternLoadExpressionFunctionDefinition,
} from '../../data/public';
import { VisParams } from './types';
const prepareDimension = (params: SchemaConfig) => {
const visdimension = buildExpressionFunction('visdimension', { accessor: params.accessor });
@ -26,7 +27,7 @@ const prepareDimension = (params: SchemaConfig) => {
return buildExpression([visdimension]);
};
export const toExpressionAst = (vis: Vis, params: any) => {
export const toExpressionAst: VisToExpressionAst<VisParams> = (vis, params) => {
const esaggs = buildExpressionFunction<EsaggsExpressionFunctionDefinition>('esaggs', {
index: buildExpression([
buildExpressionFunction<IndexPatternLoadExpressionFunctionDefinition>('indexPatternLoad', {
@ -34,7 +35,7 @@ export const toExpressionAst = (vis: Vis, params: any) => {
}),
]),
metricsAtAllLevels: vis.isHierarchical(),
partialRows: vis.params.showPartialRows || false,
partialRows: false,
aggs: vis.data.aggs!.aggs.map((agg) => buildExpression(agg.toExpressionAst())),
});
@ -65,7 +66,7 @@ export const toExpressionAst = (vis: Vis, params: any) => {
colorMode: metricColorMode,
useRanges,
invertColors,
showLabels: labels && labels.show,
showLabels: labels?.show ?? false,
});
if (style) {

View file

@ -12,13 +12,9 @@ import { EuiIconTip, EuiPanel } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { VisEditorOptionsProps } from 'src/plugins/visualizations/public';
import { search } from '../../../data/public';
import {
SwitchOption,
SelectOption,
NumberInputOption,
VisOptionsProps,
} from '../../../vis_default_editor/public';
import { SwitchOption, SelectOption, NumberInputOption } from '../../../vis_default_editor/public';
import { TableVisParams } from '../../common';
import { totalAggregations } from './utils';
@ -29,7 +25,7 @@ function TableOptions({
stateParams,
setValidity,
setValue,
}: VisOptionsProps<TableVisParams>) {
}: VisEditorOptionsProps<TableVisParams>) {
const percentageColumns = useMemo(
() => [
{

View file

@ -8,12 +8,13 @@
import React, { lazy, Suspense } from 'react';
import { EuiLoadingSpinner } from '@elastic/eui';
import { VisOptionsProps } from 'src/plugins/vis_default_editor/public';
import { VisEditorOptionsProps } from 'src/plugins/visualizations/public';
import { TableVisParams } from '../../common';
const TableOptionsComponent = lazy(() => import('./table_vis_options'));
export const TableOptions = (props: VisOptionsProps<TableVisParams>) => (
export const TableOptions = (props: VisEditorOptionsProps<TableVisParams>) => (
<Suspense fallback={<EuiLoadingSpinner />}>
<TableOptionsComponent {...props} />
</Suspense>

View file

@ -8,14 +8,14 @@
import { i18n } from '@kbn/i18n';
import { AggGroupNames } from '../../../data/public';
import { BaseVisTypeOptions } from '../../../visualizations/public';
import { VisTypeDefinition } from '../../../visualizations/public';
import { TableOptions } from '../components/table_vis_options_lazy';
import { VIS_EVENT_TO_TRIGGER } from '../../../visualizations/public';
import { TableVisParams, VIS_TYPE_TABLE } from '../../common';
import { toExpressionAst } from '../to_ast';
export const tableVisLegacyTypeDefinition: BaseVisTypeOptions<TableVisParams> = {
export const tableVisLegacyTypeDefinition: VisTypeDefinition<TableVisParams> = {
name: VIS_TYPE_TABLE,
title: i18n.translate('visTypeTable.tableVisTitle', {
defaultMessage: 'Data table',
@ -81,4 +81,5 @@ export const tableVisLegacyTypeDefinition: BaseVisTypeOptions<TableVisParams> =
},
toExpressionAst,
hierarchicalData: (vis) => vis.params.showPartialRows || vis.params.showMetricsAtAllLevels,
requiresSearch: true,
};

View file

@ -7,15 +7,14 @@
*/
import { i18n } from '@kbn/i18n';
import { AggGroupNames } from '../../data/public';
import { BaseVisTypeOptions } from '../../visualizations/public';
import { AggGroupNames } from '../../data/public';
import { VIS_EVENT_TO_TRIGGER, VisTypeDefinition } from '../../visualizations/public';
import { TableVisParams, VIS_TYPE_TABLE } from '../common';
import { TableOptions } from './components/table_vis_options_lazy';
import { VIS_EVENT_TO_TRIGGER } from '../../../plugins/visualizations/public';
import { toExpressionAst } from './to_ast';
export const tableVisTypeDefinition: BaseVisTypeOptions<TableVisParams> = {
export const tableVisTypeDefinition: VisTypeDefinition<TableVisParams> = {
name: VIS_TYPE_TABLE,
title: i18n.translate('visTypeTable.tableVisTitle', {
defaultMessage: 'Data table',
@ -78,4 +77,5 @@ export const tableVisTypeDefinition: BaseVisTypeOptions<TableVisParams> = {
},
toExpressionAst,
hierarchicalData: (vis) => vis.params.showPartialRows || vis.params.showMetricsAtAllLevels,
requiresSearch: true,
};

View file

@ -11,7 +11,7 @@ import {
IndexPatternLoadExpressionFunctionDefinition,
} from '../../data/public';
import { buildExpression, buildExpressionFunction } from '../../expressions/public';
import { getVisSchemas, Vis, BuildPipelineParams } from '../../visualizations/public';
import { getVisSchemas, VisToExpressionAst } from '../../visualizations/public';
import { TableVisParams } from '../common';
import { TableExpressionFunctionDefinition } from './table_vis_fn';
import { TableVisConfig } from './types';
@ -41,7 +41,7 @@ const buildTableVisConfig = (
return visConfig;
};
export const toExpressionAst = (vis: Vis<TableVisParams>, params: BuildPipelineParams) => {
export const toExpressionAst: VisToExpressionAst<TableVisParams> = (vis, params) => {
const esaggs = buildExpressionFunction<EsaggsExpressionFunctionDefinition>('esaggs', {
index: buildExpression([
buildExpressionFunction<IndexPatternLoadExpressionFunctionDefinition>('indexPatternLoad', {

View file

@ -9,11 +9,12 @@
import React from 'react';
import { EuiPanel } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { VisOptionsProps, SelectOption, SwitchOption } from '../../../vis_default_editor/public';
import { VisEditorOptionsProps } from 'src/plugins/visualizations/public';
import { SelectOption, SwitchOption } from '../../../vis_default_editor/public';
import { ValidatedDualRange } from '../../../kibana_react/public';
import { TagCloudVisParams } from '../types';
function TagCloudOptions({ stateParams, setValue, vis }: VisOptionsProps<TagCloudVisParams>) {
function TagCloudOptions({ stateParams, setValue, vis }: VisEditorOptionsProps<TagCloudVisParams>) {
const handleFontSizeChange = ([minFontSize, maxFontSize]: [string | number, string | number]) => {
setValue('minFontSize', Number(minFontSize));
setValue('maxFontSize', Number(maxFontSize));

View file

@ -7,6 +7,7 @@
*/
import { i18n } from '@kbn/i18n';
import { AggGroupNames } from '../../data/public';
import { VIS_EVENT_TO_TRIGGER } from '../../visualizations/public';
import { TagCloudOptions } from './components/tag_cloud_options';
@ -78,7 +79,7 @@ export const tagCloudVisTypeDefinition = {
optionsTemplate: TagCloudOptions,
schemas: [
{
group: 'metrics',
group: AggGroupNames.Metrics,
name: 'metric',
title: i18n.translate('visTypeTagCloud.vis.schemas.metricTitle', {
defaultMessage: 'Tag size',
@ -96,7 +97,7 @@ export const tagCloudVisTypeDefinition = {
defaults: [{ schema: 'metric', type: 'count' }],
},
{
group: 'buckets',
group: AggGroupNames.Buckets,
name: 'segment',
title: i18n.translate('visTypeTagCloud.vis.schemas.segmentTitle', {
defaultMessage: 'Tags',
@ -107,4 +108,5 @@ export const tagCloudVisTypeDefinition = {
},
],
},
requiresSearch: true,
};

View file

@ -11,7 +11,7 @@ import {
IndexPatternLoadExpressionFunctionDefinition,
} from '../../data/public';
import { buildExpression, buildExpressionFunction } from '../../expressions/public';
import { getVisSchemas, SchemaConfig, Vis, BuildPipelineParams } from '../../visualizations/public';
import { getVisSchemas, SchemaConfig, VisToExpressionAst } from '../../visualizations/public';
import { TagcloudExpressionFunctionDefinition } from './tag_cloud_fn';
import { TagCloudVisParams } from './types';
@ -26,7 +26,7 @@ const prepareDimension = (params: SchemaConfig) => {
return buildExpression([visdimension]);
};
export const toExpressionAst = (vis: Vis<TagCloudVisParams>, params: BuildPipelineParams) => {
export const toExpressionAst: VisToExpressionAst<TagCloudVisParams> = (vis, params) => {
const esaggs = buildExpressionFunction<EsaggsExpressionFunctionDefinition>('esaggs', {
index: buildExpression([
buildExpressionFunction<IndexPatternLoadExpressionFunctionDefinition>('indexPatternLoad', {

View file

@ -9,7 +9,7 @@
import React, { useCallback } from 'react';
import { EuiPanel } from '@elastic/eui';
import { VisOptionsProps } from 'src/plugins/vis_default_editor/public';
import { VisEditorOptionsProps } from 'src/plugins/visualizations/public';
import { KibanaContextProvider } from '../../kibana_react/public';
import { TimelionVisParams } from './timelion_vis_fn';
@ -18,7 +18,7 @@ import { TimelionVisDependencies } from './plugin';
import './timelion_options.scss';
export type TimelionOptionsProps = VisOptionsProps<TimelionVisParams>;
export type TimelionOptionsProps = VisEditorOptionsProps<TimelionVisParams>;
function TimelionOptions({
services,

View file

@ -10,7 +10,6 @@ import React, { lazy } from 'react';
import { i18n } from '@kbn/i18n';
import { DefaultEditorSize } from '../../vis_default_editor/public';
import { getTimelionRequestHandler } from './helpers/timelion_request_handler';
import { TimelionOptionsProps } from './timelion_options';
import { TimelionVisDependencies } from './plugin';
import { toExpressionAst } from './to_ast';
@ -22,8 +21,6 @@ const TimelionOptions = lazy(() => import('./timelion_options'));
export const TIMELION_VIS_NAME = 'timelion';
export function getTimelionVisDefinition(dependencies: TimelionVisDependencies) {
const timelionRequestHandler = getTimelionRequestHandler(dependencies);
// return the visType object, which kibana will use to display and configure new
// Vis object of this type.
return {
@ -45,9 +42,7 @@ export function getTimelionVisDefinition(dependencies: TimelionVisDependencies)
),
defaultSize: DefaultEditorSize.MEDIUM,
},
requestHandler: timelionRequestHandler,
toExpressionAst,
responseHandler: 'none',
inspectorAdapters: {},
getSupportedTriggers: () => {
return [VIS_EVENT_TO_TRIGGER.applyFilter];
@ -57,5 +52,6 @@ export function getTimelionVisDefinition(dependencies: TimelionVisDependencies)
showQueryBar: false,
showFilterBar: false,
},
requiresSearch: true,
};
}

View file

@ -13,7 +13,7 @@ import hjson from 'hjson';
import 'brace/mode/hjson';
import { i18n } from '@kbn/i18n';
import { VisOptionsProps } from 'src/plugins/vis_default_editor/public';
import { VisEditorOptionsProps } from 'src/plugins/visualizations/public';
import { getNotifications } from '../services';
import { VisParams } from '../vega_fn';
import { VegaHelpMenu } from './vega_help_menu';
@ -55,7 +55,7 @@ function format(
}
}
function VegaVisEditor({ stateParams, setValue }: VisOptionsProps<VisParams>) {
function VegaVisEditor({ stateParams, setValue }: VisEditorOptionsProps<VisParams>) {
const onChange = useCallback(
(value: string) => {
setValue('spec', value);

View file

@ -8,11 +8,11 @@
import React, { lazy } from 'react';
import { VisOptionsProps } from 'src/plugins/vis_default_editor/public';
import { VisEditorOptionsProps } from 'src/plugins/visualizations/public';
import { VisParams } from '../vega_fn';
const VegaVisEditor = lazy(() => import('./vega_vis_editor'));
export const VegaVisEditorComponent = (props: VisOptionsProps<VisParams>) => (
export const VegaVisEditorComponent = (props: VisEditorOptionsProps<VisParams>) => (
<VegaVisEditor {...props} />
);

View file

@ -84,7 +84,7 @@ export class VegaPlugin implements Plugin<Promise<void>, void> {
expressions.registerFunction(() => createVegaFn(visualizationDependencies));
expressions.registerRenderer(getVegaVisRenderer(visualizationDependencies));
visualizations.createBaseVisualization(createVegaTypeDefinition(visualizationDependencies));
visualizations.createBaseVisualization(createVegaTypeDefinition());
}
public start(core: CoreStart, { data }: VegaPluginStartDependencies) {

View file

@ -8,16 +8,13 @@
import { i18n } from '@kbn/i18n';
import { parse } from 'hjson';
import type { BaseVisTypeOptions } from 'src/plugins/visualizations/public';
import { DefaultEditorSize } from '../../vis_default_editor/public';
import type { VegaVisualizationDependencies } from './plugin';
import { VIS_EVENT_TO_TRIGGER, VisGroups, VisTypeDefinition } from '../../visualizations/public';
import { createVegaRequestHandler } from './vega_request_handler';
import { getDefaultSpec } from './default_spec';
import { extractIndexPatternsFromSpec } from './lib/extract_index_pattern';
import { createInspectorAdapters } from './vega_inspector';
import { VIS_EVENT_TO_TRIGGER, VisGroups } from '../../visualizations/public';
import { toExpressionAst } from './to_ast';
import { getInfoMessage } from './components/experimental_map_vis_info';
import { VegaVisEditorComponent } from './components/vega_vis_editor_lazy';
@ -25,11 +22,7 @@ import { VegaVisEditorComponent } from './components/vega_vis_editor_lazy';
import type { VegaSpec } from './data_model/types';
import type { VisParams } from './vega_fn';
export const createVegaTypeDefinition = (
dependencies: VegaVisualizationDependencies
): BaseVisTypeOptions<VisParams> => {
const requestHandler = createVegaRequestHandler(dependencies);
export const createVegaTypeDefinition = (): VisTypeDefinition<VisParams> => {
return {
name: 'vega',
title: 'Vega',
@ -52,7 +45,6 @@ export const createVegaTypeDefinition = (
enableAutoApply: true,
defaultSize: DefaultEditorSize.MEDIUM,
},
requestHandler,
toExpressionAst,
options: {
showIndexSelection: false,
@ -73,5 +65,9 @@ export const createVegaTypeDefinition = (
return [];
},
inspectorAdapters: createInspectorAdapters,
/**
* This is necessary for showing actions bar in top of vega editor
*/
requiresSearch: true,
};
};

View file

@ -7,13 +7,12 @@
*/
import { xyVisTypes } from '../../vis_type_xy/public';
import { BaseVisTypeOptions } from '../../visualizations/public';
import { VisTypeDefinition } from '../../visualizations/public';
import { toExpressionAst } from './to_ast';
import { BasicVislibParams } from './types';
export const areaVisTypeDefinition: BaseVisTypeOptions<BasicVislibParams> = {
...(xyVisTypes.area() as BaseVisTypeOptions<BasicVislibParams>),
export const areaVisTypeDefinition = {
...xyVisTypes.area(),
toExpressionAst,
visualization: undefined,
};
} as VisTypeDefinition<BasicVislibParams>;

View file

@ -9,20 +9,20 @@
import React, { useCallback } from 'react';
import { EuiSpacer } from '@elastic/eui';
import { VisOptionsProps } from 'src/plugins/vis_default_editor/public';
import { VisEditorOptionsProps } from 'src/plugins/visualizations/public';
import { GaugeVisParams } from '../../../gauge';
import { RangesPanel } from './ranges_panel';
import { StylePanel } from './style_panel';
import { LabelsPanel } from './labels_panel';
export type GaugeOptionsInternalProps = VisOptionsProps<GaugeVisParams> & {
export type GaugeOptionsInternalProps = VisEditorOptionsProps<GaugeVisParams> & {
setGaugeValue: <T extends keyof GaugeVisParams['gauge']>(
paramName: T,
value: GaugeVisParams['gauge'][T]
) => void;
};
function GaugeOptions(props: VisOptionsProps<GaugeVisParams>) {
function GaugeOptions(props: VisEditorOptionsProps<GaugeVisParams>) {
const { stateParams, setValue } = props;
const setGaugeValue: GaugeOptionsInternalProps['setGaugeValue'] = useCallback(

View file

@ -12,9 +12,9 @@ import { EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { VisEditorOptionsProps } from 'src/plugins/visualizations/public';
import { ValueAxis } from '../../../../../vis_type_xy/public';
import {
VisOptionsProps,
BasicOptions,
SelectOption,
SwitchOption,
@ -28,7 +28,7 @@ import {
import { HeatmapVisParams } from '../../../heatmap';
import { LabelsPanel } from './labels_panel';
function HeatmapOptions(props: VisOptionsProps<HeatmapVisParams>) {
function HeatmapOptions(props: VisEditorOptionsProps<HeatmapVisParams>) {
const { stateParams, vis, uiState, setValue, setValidity, setTouched } = props;
const [valueAxis] = stateParams.valueAxes;
const isColorsNumberInvalid = stateParams.colorsNumber < 2 || stateParams.colorsNumber > 10;

View file

@ -12,7 +12,8 @@ import { EuiColorPicker, EuiFormRow, EuiPanel, EuiSpacer, EuiTitle } from '@elas
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { VisOptionsProps, SwitchOption } from '../../../../../vis_default_editor/public';
import { VisEditorOptionsProps } from 'src/plugins/visualizations/public';
import { SwitchOption } from '../../../../../vis_default_editor/public';
import { ValueAxis } from '../../../../../vis_type_xy/public';
import { HeatmapVisParams } from '../../../heatmap';
@ -21,7 +22,7 @@ const VERTICAL_ROTATION = 270;
interface LabelsPanelProps {
valueAxis: ValueAxis;
setValue: VisOptionsProps<HeatmapVisParams>['setValue'];
setValue: VisEditorOptionsProps<HeatmapVisParams>['setValue'];
}
function LabelsPanel({ valueAxis, setValue }: LabelsPanelProps) {

View file

@ -8,8 +8,7 @@
import React, { lazy } from 'react';
import { VisOptionsProps } from '../../../../vis_default_editor/public';
import { VisEditorOptionsProps } from 'src/plugins/visualizations/public';
import { GaugeVisParams } from '../../gauge';
import { PieVisParams } from '../../pie';
import { HeatmapVisParams } from '../../heatmap';
@ -18,12 +17,14 @@ const GaugeOptionsLazy = lazy(() => import('./gauge'));
const PieOptionsLazy = lazy(() => import('./pie'));
const HeatmapOptionsLazy = lazy(() => import('./heatmap'));
export const GaugeOptions = (props: VisOptionsProps<GaugeVisParams>) => (
export const GaugeOptions = (props: VisEditorOptionsProps<GaugeVisParams>) => (
<GaugeOptionsLazy {...props} />
);
export const PieOptions = (props: VisOptionsProps<PieVisParams>) => <PieOptionsLazy {...props} />;
export const PieOptions = (props: VisEditorOptionsProps<PieVisParams>) => (
<PieOptionsLazy {...props} />
);
export const HeatmapOptions = (props: VisOptionsProps<HeatmapVisParams>) => (
export const HeatmapOptions = (props: VisEditorOptionsProps<HeatmapVisParams>) => (
<HeatmapOptionsLazy {...props} />
);

View file

@ -12,12 +12,13 @@ import { EuiPanel, EuiTitle, EuiSpacer } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { BasicOptions, SwitchOption, VisOptionsProps } from '../../../../vis_default_editor/public';
import { VisEditorOptionsProps } from 'src/plugins/visualizations/public';
import { BasicOptions, SwitchOption } from '../../../../vis_default_editor/public';
import { TruncateLabelsOption } from '../../../../vis_type_xy/public';
import { PieVisParams } from '../../pie';
function PieOptions(props: VisOptionsProps<PieVisParams>) {
function PieOptions(props: VisEditorOptionsProps<PieVisParams>) {
const { stateParams, setValue } = props;
const setLabels = <T extends keyof PieVisParams['labels']>(
paramName: T,

View file

@ -11,9 +11,9 @@ import { i18n } from '@kbn/i18n';
import { ColorMode, ColorSchemas, ColorSchemaParams, Labels, Style } from '../../charts/public';
import { RangeValues } from '../../vis_default_editor/public';
import { AggGroupNames } from '../../data/public';
import { BaseVisTypeOptions, VIS_EVENT_TO_TRIGGER } from '../../visualizations/public';
import { VisTypeDefinition, VIS_EVENT_TO_TRIGGER } from '../../visualizations/public';
import { Alignment, GaugeType, BasicVislibParams, VislibChartType } from './types';
import { Alignment, GaugeType, VislibChartType } from './types';
import { getGaugeCollections } from './editor';
import { toExpressionAst } from './to_ast';
import { GaugeOptions } from './editor/components';
@ -46,7 +46,7 @@ export interface GaugeVisParams {
gauge: Gauge;
}
export const gaugeVisTypeDefinition: BaseVisTypeOptions<BasicVislibParams> = {
export const gaugeVisTypeDefinition: VisTypeDefinition<GaugeVisParams> = {
name: 'gauge',
title: i18n.translate('visTypeVislib.gauge.gaugeTitle', { defaultMessage: 'Gauge' }),
icon: 'visGauge',
@ -135,5 +135,5 @@ export const gaugeVisTypeDefinition: BaseVisTypeOptions<BasicVislibParams> = {
},
],
},
useCustomNoDataScreen: true,
requiresSearch: true,
};

View file

@ -10,13 +10,14 @@ import { i18n } from '@kbn/i18n';
import { AggGroupNames } from '../../data/public';
import { ColorMode, ColorSchemas } from '../../charts/public';
import { BaseVisTypeOptions } from '../../visualizations/public';
import { VisTypeDefinition } from '../../visualizations/public';
import { getGaugeCollections, GaugeOptions } from './editor';
import { toExpressionAst } from './to_ast';
import { GaugeType, BasicVislibParams } from './types';
import { GaugeType } from './types';
import { GaugeVisParams } from './gauge';
export const goalVisTypeDefinition: BaseVisTypeOptions<BasicVislibParams> = {
export const goalVisTypeDefinition: VisTypeDefinition<GaugeVisParams> = {
name: 'goal',
title: i18n.translate('visTypeVislib.goal.goalTitle', { defaultMessage: 'Goal' }),
icon: 'visGoal',
@ -98,5 +99,5 @@ export const goalVisTypeDefinition: BaseVisTypeOptions<BasicVislibParams> = {
},
],
},
useCustomNoDataScreen: true,
requiresSearch: true,
};

View file

@ -12,12 +12,12 @@ import { Position } from '@elastic/charts';
import { RangeValues } from '../../vis_default_editor/public';
import { AggGroupNames } from '../../data/public';
import { ColorSchemas, ColorSchemaParams } from '../../charts/public';
import { VIS_EVENT_TO_TRIGGER, BaseVisTypeOptions } from '../../visualizations/public';
import { VIS_EVENT_TO_TRIGGER, VisTypeDefinition } from '../../visualizations/public';
import { ValueAxis, ScaleType, AxisType } from '../../vis_type_xy/public';
import { HeatmapOptions, getHeatmapCollections } from './editor';
import { TimeMarker } from './vislib/visualizations/time_marker';
import { CommonVislibParams, BasicVislibParams, VislibChartType } from './types';
import { CommonVislibParams, VislibChartType } from './types';
import { toExpressionAst } from './to_ast';
export interface HeatmapVisParams extends CommonVislibParams, ColorSchemaParams {
@ -32,7 +32,7 @@ export interface HeatmapVisParams extends CommonVislibParams, ColorSchemaParams
times: TimeMarker[];
}
export const heatmapVisTypeDefinition: BaseVisTypeOptions<BasicVislibParams> = {
export const heatmapVisTypeDefinition: VisTypeDefinition<HeatmapVisParams> = {
name: 'heatmap',
title: i18n.translate('visTypeVislib.heatmap.heatmapTitle', { defaultMessage: 'Heat map' }),
icon: 'heatmap',
@ -127,4 +127,5 @@ export const heatmapVisTypeDefinition: BaseVisTypeOptions<BasicVislibParams> = {
},
],
},
requiresSearch: true,
};

View file

@ -7,13 +7,12 @@
*/
import { xyVisTypes } from '../../vis_type_xy/public';
import { BaseVisTypeOptions } from '../../visualizations/public';
import { VisTypeDefinition } from '../../visualizations/public';
import { toExpressionAst } from './to_ast';
import { BasicVislibParams } from './types';
export const histogramVisTypeDefinition: BaseVisTypeOptions<BasicVislibParams> = {
...(xyVisTypes.histogram() as BaseVisTypeOptions<BasicVislibParams>),
export const histogramVisTypeDefinition = {
...xyVisTypes.histogram(),
toExpressionAst,
visualization: undefined,
};
} as VisTypeDefinition<BasicVislibParams>;

View file

@ -7,13 +7,12 @@
*/
import { xyVisTypes } from '../../vis_type_xy/public';
import { BaseVisTypeOptions } from '../../visualizations/public';
import { VisTypeDefinition } from '../../visualizations/public';
import { toExpressionAst } from './to_ast';
import { BasicVislibParams } from './types';
export const horizontalBarVisTypeDefinition: BaseVisTypeOptions<BasicVislibParams> = {
...(xyVisTypes.horizontalBar() as BaseVisTypeOptions<BasicVislibParams>),
export const horizontalBarVisTypeDefinition = {
...xyVisTypes.horizontalBar(),
toExpressionAst,
visualization: undefined,
};
} as VisTypeDefinition<BasicVislibParams>;

View file

@ -7,13 +7,12 @@
*/
import { xyVisTypes } from '../../vis_type_xy/public';
import { BaseVisTypeOptions } from '../../visualizations/public';
import { VisTypeDefinition } from '../../visualizations/public';
import { toExpressionAst } from './to_ast';
import { BasicVislibParams } from './types';
export const lineVisTypeDefinition: BaseVisTypeOptions<BasicVislibParams> = {
...(xyVisTypes.line() as BaseVisTypeOptions<BasicVislibParams>),
export const lineVisTypeDefinition = {
...xyVisTypes.line(),
toExpressionAst,
visualization: undefined,
};
} as VisTypeDefinition<BasicVislibParams>;

View file

@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n';
import { Position } from '@elastic/charts';
import { AggGroupNames } from '../../data/public';
import { BaseVisTypeOptions, VIS_EVENT_TO_TRIGGER } from '../../visualizations/public';
import { VisTypeDefinition, VIS_EVENT_TO_TRIGGER } from '../../visualizations/public';
import { getPositions } from '../../vis_type_xy/public';
import { CommonVislibParams } from './types';
@ -28,7 +28,7 @@ export interface PieVisParams extends CommonVislibParams {
};
}
export const pieVisTypeDefinition: BaseVisTypeOptions<PieVisParams> = {
export const pieVisTypeDefinition: VisTypeDefinition<PieVisParams> = {
name: 'pie',
title: i18n.translate('visTypeVislib.pie.pieTitle', { defaultMessage: 'Pie' }),
icon: 'visPie',
@ -93,5 +93,5 @@ export const pieVisTypeDefinition: BaseVisTypeOptions<PieVisParams> = {
],
},
hierarchicalData: true,
responseHandler: 'vislib_slices',
requiresSearch: true,
};

View file

@ -8,7 +8,12 @@
import moment from 'moment';
import { VisToExpressionAst, getVisSchemas } from '../../visualizations/public';
import {
Vis,
VisToExpressionAstParams,
getVisSchemas,
VisParams,
} from '../../visualizations/public';
import { buildExpression, buildExpressionFunction } from '../../expressions/public';
import type { Dimensions, DateHistogramParams, HistogramParams } from '../../vis_type_xy/public';
import { BUCKET_TYPES } from '../../data/public';
@ -17,7 +22,10 @@ import { vislibVisName, VisTypeVislibExpressionFunctionDefinition } from './vis_
import { BasicVislibParams, VislibChartType } from './types';
import { getEsaggsFn } from './to_ast_esaggs';
export const toExpressionAst: VisToExpressionAst<BasicVislibParams> = async (vis, params) => {
export const toExpressionAst = async <TVisParams extends VisParams>(
vis: Vis<TVisParams>,
params: VisToExpressionAstParams
) => {
const schemas = getVisSchemas(vis, params);
const dimensions: Dimensions = {
x: schemas.segment ? schemas.segment[0] : null,
@ -58,9 +66,11 @@ export const toExpressionAst: VisToExpressionAst<BasicVislibParams> = async (vis
(dimensions.y || []).forEach((yDimension) => {
const yAgg = responseAggs.filter(({ enabled }) => enabled)[yDimension.accessor];
const seriesParam = (visConfig.seriesParams || []).find((param) => param.data.id === yAgg.id);
const seriesParam = ((visConfig.seriesParams as BasicVislibParams['seriesParams']) || []).find(
(param) => param.data.id === yAgg.id
);
if (seriesParam) {
const usedValueAxis = (visConfig.valueAxes || []).find(
const usedValueAxis = ((visConfig.valueAxes as BasicVislibParams['valueAxes']) || []).find(
(valueAxis) => valueAxis.id === seriesParam.valueAxis
);
if (usedValueAxis?.scale.mode === 'percentage') {
@ -72,13 +82,11 @@ export const toExpressionAst: VisToExpressionAst<BasicVislibParams> = async (vis
}
});
visConfig.dimensions = dimensions;
const visTypeVislib = buildExpressionFunction<VisTypeVislibExpressionFunctionDefinition>(
vislibVisName,
{
type: vis.type.name as Exclude<VislibChartType, 'pie'>,
visConfig: JSON.stringify(visConfig),
visConfig: JSON.stringify({ ...visConfig, dimensions }),
}
);

View file

@ -13,16 +13,13 @@ import {
IndexPatternLoadExpressionFunctionDefinition,
} from '../../data/public';
import { PieVisParams } from './pie';
import { BasicVislibParams } from './types';
/**
* Get esaggs expressions function
* TODO: replace this with vis.data.aggs!.toExpressionAst();
* https://github.com/elastic/kibana/issues/61768
* @param vis
*/
export function getEsaggsFn(vis: Vis<PieVisParams> | Vis<BasicVislibParams>) {
export function getEsaggsFn<T>(vis: Vis<T>) {
return buildExpressionFunction<EsaggsExpressionFunctionDefinition>('esaggs', {
index: buildExpression([
buildExpressionFunction<IndexPatternLoadExpressionFunctionDefinition>('indexPatternLoad', {

View file

@ -6,6 +6,7 @@
* Public License, v 1.
*/
import { VisTypeDefinition } from 'src/plugins/visualizations/public';
import { histogramVisTypeDefinition } from './histogram';
import { lineVisTypeDefinition } from './line';
import { areaVisTypeDefinition } from './area';
@ -16,7 +17,7 @@ import { goalVisTypeDefinition } from './goal';
export { pieVisTypeDefinition } from './pie';
export const visLibVisTypeDefinitions = [
export const visLibVisTypeDefinitions: Array<VisTypeDefinition<any>> = [
histogramVisTypeDefinition,
lineVisTypeDefinition,
areaVisTypeDefinition,
@ -26,7 +27,7 @@ export const visLibVisTypeDefinitions = [
goalVisTypeDefinition,
];
export const convertedTypeDefinitions = [
export const convertedTypeDefinitions: Array<VisTypeDefinition<any>> = [
heatmapVisTypeDefinition,
gaugeVisTypeDefinition,
goalVisTypeDefinition,

View file

@ -143,39 +143,38 @@ export const getRotateOptions = () => [
},
];
export const getFittingFunctions = () =>
[
{
value: Fit.None,
text: i18n.translate('visTypeXy.fittingFunctionsTitle.none', {
defaultMessage: 'Hide (Do not fill gaps)',
}),
},
{
value: Fit.Zero,
text: i18n.translate('visTypeXy.fittingFunctionsTitle.zero', {
defaultMessage: 'Zero (Fill gaps with zeros)',
}),
},
{
value: Fit.Linear,
text: i18n.translate('visTypeXy.fittingFunctionsTitle.linear', {
defaultMessage: 'Linear (Fill gaps with a line)',
}),
},
{
value: Fit.Carry,
text: i18n.translate('visTypeXy.fittingFunctionsTitle.carry', {
defaultMessage: 'Last (Fill gaps with the last value)',
}),
},
{
value: Fit.Lookahead,
text: i18n.translate('visTypeXy.fittingFunctionsTitle.lookahead', {
defaultMessage: 'Next (Fill gaps with the next value)',
}),
},
] as const;
export const getFittingFunctions = () => [
{
value: Fit.None,
text: i18n.translate('visTypeXy.fittingFunctionsTitle.none', {
defaultMessage: 'Hide (Do not fill gaps)',
}),
},
{
value: Fit.Zero,
text: i18n.translate('visTypeXy.fittingFunctionsTitle.zero', {
defaultMessage: 'Zero (Fill gaps with zeros)',
}),
},
{
value: Fit.Linear,
text: i18n.translate('visTypeXy.fittingFunctionsTitle.linear', {
defaultMessage: 'Linear (Fill gaps with a line)',
}),
},
{
value: Fit.Carry,
text: i18n.translate('visTypeXy.fittingFunctionsTitle.carry', {
defaultMessage: 'Last (Fill gaps with the last value)',
}),
},
{
value: Fit.Lookahead,
text: i18n.translate('visTypeXy.fittingFunctionsTitle.lookahead', {
defaultMessage: 'Next (Fill gaps with the next value)',
}),
},
];
export const getConfigCollections = () => ({
legendPositions: getPositions(),

View file

@ -9,7 +9,7 @@
import React from 'react';
import { i18n } from '@kbn/i18n';
import { VisOptionsProps } from '../../../vis_default_editor/public';
import { VisEditorOptionsProps } from '../../../visualizations/public';
import { VisParams } from '../types';
import { MetricsAxisOptions, PointSeriesOptions } from './components/options';
@ -22,7 +22,7 @@ export function getOptionTabs(showElasticChartsOptions = false) {
title: i18n.translate('visTypeXy.area.tabs.metricsAxesTitle', {
defaultMessage: 'Metrics & axes',
}),
editor: (props: VisOptionsProps<VisParams>) => (
editor: (props: VisEditorOptionsProps<VisParams>) => (
<ValidationWrapper {...props} component={MetricsAxisOptions} />
),
},
@ -31,7 +31,7 @@ export function getOptionTabs(showElasticChartsOptions = false) {
title: i18n.translate('visTypeXy.area.tabs.panelSettingsTitle', {
defaultMessage: 'Panel settings',
}),
editor: (props: VisOptionsProps<VisParams>) => (
editor: (props: VisEditorOptionsProps<VisParams>) => (
<ValidationWrapper
{...props}
extraProps={{

View file

@ -8,14 +8,14 @@
import React, { useEffect, useState, useCallback } from 'react';
import { VisOptionsProps } from '../../../../../vis_default_editor/public';
import { VisEditorOptionsProps } from '../../../../../visualizations/public';
export interface ValidationVisOptionsProps<T, E = unknown> extends VisOptionsProps<T> {
export interface ValidationVisOptionsProps<T, E = unknown> extends VisEditorOptionsProps<T> {
setMultipleValidity(paramName: string, isValid: boolean): void;
extraProps?: E;
}
interface ValidationWrapperProps<T, E> extends VisOptionsProps<T> {
interface ValidationWrapperProps<T, E> extends VisEditorOptionsProps<T> {
component: React.ComponentType<ValidationVisOptionsProps<T, E>>;
extraProps?: E;
}

View file

@ -13,11 +13,8 @@ import { FormattedMessage } from '@kbn/i18n/react';
import { EuiPanel, EuiTitle, EuiSpacer } from '@elastic/eui';
import { Position } from '@elastic/charts';
import {
SelectOption,
SwitchOption,
VisOptionsProps,
} from '../../../../../../vis_default_editor/public';
import { VisEditorOptionsProps } from 'src/plugins/visualizations/public';
import { SelectOption, SwitchOption } from '../../../../../../vis_default_editor/public';
import { LabelOptions, SetAxisLabel } from './label_options';
import { CategoryAxis } from '../../../../types';
@ -26,7 +23,7 @@ export interface CategoryAxisPanelProps {
axis: CategoryAxis;
onPositionChanged: (position: Position) => void;
setCategoryAxis: (value: CategoryAxis) => void;
vis: VisOptionsProps['vis'];
vis: VisEditorOptionsProps['vis'];
}
function CategoryAxisPanel({

View file

@ -106,10 +106,7 @@ export const samplePieVis = {
},
},
hidden: false,
requestHandler: 'courier',
responseHandler: 'vislib_slices',
hierarchicalData: true,
useCustomNoDataScreen: false,
},
title: '[Flights] Airline Carrier',
description: '',
@ -126,7 +123,6 @@ export const samplePieVis = {
truncate: 100,
},
},
sessionState: {},
data: {
searchSource: {
id: 'data_source1',
@ -1622,10 +1618,7 @@ export const sampleAreaVis = {
},
},
hidden: false,
requestHandler: 'courier',
responseHandler: 'none',
hierarchicalData: false,
useCustomNoDataScreen: false,
},
title: '[eCommerce] Sales by Category',
description: '',
@ -1762,7 +1755,6 @@ export const sampleAreaVis = {
],
},
},
sessionState: {},
data: {
searchSource: {
id: 'data_source1',

View file

@ -6,16 +6,11 @@
* Public License, v 1.
*/
import { BaseVisTypeOptions } from '../../../visualizations/public';
import { VisTypeDefinition } from '../../../visualizations/public';
import { ChartType } from '../../common';
import { VisParams } from './param';
export type VisTypeNames = ChartType | 'horizontal_bar';
export type XyVisTypeDefinition = BaseVisTypeOptions<VisParams> & {
name: VisTypeNames;
visConfig: {
defaults: Omit<VisParams, 'dimensions'>;
};
};
export type XyVisTypeDefinition = VisTypeDefinition<VisParams>;

View file

@ -181,4 +181,5 @@ export const getAreaVisTypeDefinition = (
},
],
},
requiresSearch: true,
});

View file

@ -184,4 +184,5 @@ export const getHistogramVisTypeDefinition = (
},
],
},
requiresSearch: true,
});

View file

@ -183,4 +183,5 @@ export const getHorizontalBarVisTypeDefinition = (
},
],
},
requiresSearch: true,
});

View file

@ -175,4 +175,5 @@ export const getLineVisTypeDefinition = (
},
],
},
requiresSearch: true,
});

View file

@ -1,24 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`VisualizationRequestError should render according to snapshot 1`] = `
<div
class="visError"
>
<div
class="euiText euiText--extraSmall"
>
<div
class="euiTextColor euiTextColor--subdued"
>
<span
color="danger"
data-euiicon-type="alert"
/>
<div
class="euiSpacer euiSpacer--s"
/>
Request error
</div>
</div>
</div>
`;

View file

@ -15,48 +15,6 @@
position: relative;
padding: $euiSizeS;
flex: 1 1 100%;
/**
* 1. Expand to fill the container but accept being squeezed smaller by the spy, even so small
* that it disappears entirely.
*/
.visChart__container {
@include euiScrollBar;
min-height: 0;
flex: 1 1 0; /* 1 */
display: flex;
flex-direction: row;
overflow: auto;
transition: opacity .01s;
// IE11 Hack
//
// Normally we would just set flex: 1 1 0%, which works as expected in IE11.
// Unfortunately, a recent bug in Firefox causes this rule to be ignored, so we
// have to use an IE-specific hack instead.
@include internetExplorerOnly() {
flex: 1 0;
}
&:focus {
box-shadow: none;
}
}
// SASSTODO: Can't find exact usage
.loading {
opacity: .5;
}
// SASSTODO: Can't find exact usage
.spinner {
position: absolute;
top: 40%;
left: 0;
right: 0;
z-index: 20;
opacity: .5;
}
}
.visChart {

View file

@ -6,6 +6,4 @@
* Public License, v 1.
*/
export { Visualization } from './visualization';
export { VisualizationContainer } from './visualization_container';
export { VisualizationNoResults } from './visualization_noresults';

View file

@ -1,92 +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
* and the Server Side Public License, v 1; you may not use this file except in
* compliance with, at your election, the Elastic License or the Server Side
* Public License, v 1.
*/
jest.useFakeTimers();
import React from 'react';
import { render, mount } from 'enzyme';
import { Visualization } from './visualization';
let renderPromise;
class VisualizationStub {
constructor(el, vis) {
this.el = el;
this.vis = vis;
}
render() {
renderPromise = new Promise((resolve) => {
this.el.innerText = this.vis.params.markdown;
resolve();
});
return renderPromise;
}
}
describe('<Visualization/>', () => {
const visData = {
hits: 1,
};
const uiState = {
on: () => {},
off: () => {},
set: () => {},
};
let vis;
beforeEach(() => {
vis = {
setUiState: function (uiState) {
this.uiState = uiState;
},
getUiState: function () {
return this.uiState;
},
params: {},
type: {
title: 'new vis',
requiresSearch: true,
useCustomNoDataScreen: false,
visualization: VisualizationStub,
},
};
});
it('should display no result message when length of data is 0', () => {
const data = { rows: [] };
const wrapper = render(
<Visualization vis={vis} visData={data} listenOnChange={true} uiState={uiState} />
);
expect(wrapper.text()).toBe('No results found');
});
it('should render chart when data is present', () => {
const wrapper = render(
<Visualization vis={vis} visData={visData} uiState={uiState} listenOnChange={true} />
);
expect(wrapper.text()).not.toBe('No results found');
});
it('should call onInit when rendering no data', () => {
const spy = jest.fn();
const noData = { hits: 0 };
mount(
<Visualization
vis={vis}
visData={noData}
uiState={uiState}
listenOnChange={false}
onInit={spy}
/>
);
expect(spy).toHaveBeenCalled();
});
});

View file

@ -1,73 +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
* and the Server Side Public License, v 1; you may not use this file except in
* compliance with, at your election, the Elastic License or the Server Side
* Public License, v 1.
*/
import { get } from 'lodash';
import React from 'react';
import { PersistedState } from '../../../../plugins/visualizations/public';
import { memoizeLast } from '../legacy/memoize';
import { VisualizationChart } from './visualization_chart';
import { VisualizationNoResults } from './visualization_noresults';
import { ExprVis } from '../expressions/vis';
function shouldShowNoResultsMessage(vis: ExprVis, visData: any): boolean {
const requiresSearch = get(vis, 'type.requiresSearch');
const rows: object[] | undefined = get(visData, 'rows');
const isZeroHits = get(visData, 'hits') === 0 || (rows && !rows.length);
const shouldShowMessage = !get(vis, 'type.useCustomNoDataScreen');
return Boolean(requiresSearch && isZeroHits && shouldShowMessage);
}
interface VisualizationProps {
listenOnChange: boolean;
onInit?: () => void;
uiState: PersistedState;
vis: ExprVis;
visData: any;
visParams: any;
}
export class Visualization extends React.Component<VisualizationProps> {
private showNoResultsMessage = memoizeLast(shouldShowNoResultsMessage);
constructor(props: VisualizationProps) {
super(props);
props.vis.setUiState(props.uiState);
}
public render() {
const { vis, visData, visParams, onInit, uiState, listenOnChange } = this.props;
const noResults = this.showNoResultsMessage(vis, visData);
return (
<div className="visualization">
{noResults ? (
<VisualizationNoResults onInit={onInit} />
) : (
<VisualizationChart
vis={vis}
visData={visData}
visParams={visParams}
onInit={onInit}
uiState={uiState}
listenOnChange={listenOnChange}
/>
)}
</div>
);
}
public shouldComponentUpdate(nextProps: VisualizationProps): boolean {
if (nextProps.uiState !== this.props.uiState) {
throw new Error('Changing uiState on <Visualization/> is not supported!');
}
return true;
}
}

View file

@ -1,56 +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
* and the Server Side Public License, v 1; you may not use this file except in
* compliance with, at your election, the Elastic License or the Server Side
* Public License, v 1.
*/
jest.useFakeTimers();
import React from 'react';
import { render, mount } from 'enzyme';
import { VisualizationChart } from './visualization_chart';
let renderPromise;
class VisualizationStub {
constructor(el, vis) {
this.el = el;
this.vis = vis;
}
render() {
renderPromise = new Promise((resolve) => {
this.el.textContent = this.vis.params.markdown;
resolve();
});
return renderPromise;
}
}
describe('<VisualizationChart/>', () => {
const vis = {
type: {
title: 'Test Visualization',
visualization: VisualizationStub,
},
params: {
markdown:
'This is a test of the [markdown](http://daringfireball.net/projects/markdown) vis.',
},
};
it('should render initial html', () => {
const wrapper = render(<VisualizationChart vis={vis} listenOnChange={true} />);
expect(wrapper.text()).toBe('');
});
it('should render visualization', async () => {
const wrapper = mount(<VisualizationChart vis={vis} />);
jest.runAllTimers();
await renderPromise;
expect(wrapper.find('.visChart').text()).toMatch(/markdown/);
});
});

View file

@ -1,134 +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
* and the Server Side Public License, v 1; you may not use this file except in
* compliance with, at your election, the Elastic License or the Server Side
* Public License, v 1.
*/
import React from 'react';
import * as Rx from 'rxjs';
import { debounceTime, filter, share, switchMap } from 'rxjs/operators';
import { PersistedState } from '../../../../plugins/visualizations/public';
import { VisualizationController } from '../types';
import { ResizeChecker } from '../../../../plugins/kibana_utils/public';
import { ExprVis } from '../expressions/vis';
interface VisualizationChartProps {
onInit?: () => void;
uiState: PersistedState;
vis: ExprVis;
visData: any;
visParams: any;
listenOnChange: boolean;
}
class VisualizationChart extends React.Component<VisualizationChartProps> {
private resizeChecker?: ResizeChecker;
private visualization?: VisualizationController;
private chartDiv = React.createRef<HTMLDivElement>();
private containerDiv = React.createRef<HTMLDivElement>();
private renderSubject: Rx.Subject<{
vis: ExprVis;
visParams: any;
visData: any;
}>;
private renderSubscription: Rx.Subscription;
constructor(props: VisualizationChartProps) {
super(props);
this.renderSubject = new Rx.Subject();
const render$ = this.renderSubject.asObservable().pipe(share());
const success$ = render$.pipe(
filter(({ vis, visData }) => vis && (!vis.type.requiresSearch || visData)),
debounceTime(100),
switchMap(async ({ vis, visData, visParams }) => {
if (!this.visualization) {
// This should never happen, since we only should trigger another rendering
// after this component has mounted and thus the visualization implementation
// has been initialized
throw new Error('Visualization implementation was not initialized on first render.');
}
return this.visualization.render(visData, visParams);
})
);
this.renderSubscription = success$.subscribe(() => {
if (this.props.onInit) {
this.props.onInit();
}
});
}
public render() {
return (
<div className="visChart__container kbn-resetFocusState" tabIndex={0} ref={this.containerDiv}>
<div className="visChart" ref={this.chartDiv} />
</div>
);
}
public componentDidMount() {
if (!this.chartDiv.current || !this.containerDiv.current) {
throw new Error('chartDiv and currentDiv reference should always be present.');
}
const { vis } = this.props;
const Visualization = vis.type.visualization;
if (!Visualization) {
throw new Error(
'Tried to use VisualizationChart component with a vis without visualization property.'
);
}
this.visualization = new Visualization(this.chartDiv.current, vis);
// We know that containerDiv.current will never be null, since we will always
// have rendered and the div is always rendered into the tree (i.e. not
// inside any condition).
this.resizeChecker = new ResizeChecker(this.containerDiv.current);
this.resizeChecker.on('resize', () => this.startRenderVisualization());
if (this.props.listenOnChange) {
this.props.uiState.on('change', this.onUiStateChanged);
}
this.startRenderVisualization();
}
public componentDidUpdate() {
this.startRenderVisualization();
}
public componentWillUnmount() {
if (this.renderSubscription) {
this.renderSubscription.unsubscribe();
}
if (this.resizeChecker) {
this.resizeChecker.destroy();
}
if (this.visualization) {
this.visualization.destroy();
}
}
private onUiStateChanged = () => {
this.startRenderVisualization();
};
private startRenderVisualization(): void {
if (this.containerDiv.current && this.chartDiv.current) {
this.renderSubject.next({
vis: this.props.vis,
visData: this.props.visData,
visParams: this.props.visParams,
});
}
}
}
export { VisualizationChart };

View file

@ -1,28 +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
* and the Server Side Public License, v 1; you may not use this file except in
* compliance with, at your election, the Elastic License or the Server Side
* Public License, v 1.
*/
import React from 'react';
import { render } from 'enzyme';
import { VisualizationRequestError } from './visualization_requesterror';
describe('VisualizationRequestError', () => {
it('should render according to snapshot', () => {
const wrapper = render(<VisualizationRequestError error="Request error" />);
expect(wrapper).toMatchSnapshot();
});
it('should set html when error is an object', () => {
const wrapper = render(<VisualizationRequestError error={{ message: 'Request error' }} />);
expect(wrapper.text()).toBe('Request error');
});
it('should set html when error is a string', () => {
const wrapper = render(<VisualizationRequestError error="Request error" />);
expect(wrapper.text()).toBe('Request error');
});
});

View file

@ -1,51 +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
* and the Server Side Public License, v 1; you may not use this file except in
* compliance with, at your election, the Elastic License or the Server Side
* Public License, v 1.
*/
import { EuiIcon, EuiSpacer, EuiText } from '@elastic/eui';
import React from 'react';
import { SearchError } from '../../../../plugins/data/public';
interface VisualizationRequestErrorProps {
onInit?: () => void;
error: SearchError | string;
}
export class VisualizationRequestError extends React.Component<VisualizationRequestErrorProps> {
private containerDiv = React.createRef<HTMLDivElement>();
public render() {
const { error } = this.props;
const errorMessage = typeof error === 'string' ? error : error.message;
return (
<div className="visError" ref={this.containerDiv}>
<EuiText size="xs" color="subdued">
<EuiIcon type="alert" size="m" color="danger" />
<EuiSpacer size="s" />
{errorMessage}
</EuiText>
</div>
);
}
public componentDidMount() {
this.afterRender();
}
public componentDidUpdate() {
this.afterRender();
}
private afterRender() {
if (this.props.onInit) {
this.props.onInit();
}
}
}

View file

@ -8,10 +8,6 @@
@include euiScrollBar; /* 2 */
}
.visualization .visChart__container {
overflow: visible; /* 1 */
}
.visLegend__toggle {
border-bottom-right-radius: 0;
border-top-left-radius: 0;

View file

@ -1,25 +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
* and the Server Side Public License, v 1; you may not use this file except in
* compliance with, at your election, the Elastic License or the Server Side
* Public License, v 1.
*/
import { VisSavedObject } from '../types';
import type { IndexPattern } from '../../../../plugins/data/public';
import { getIndexPatterns } from '../services';
export async function getIndexPattern(
savedVis: VisSavedObject
): Promise<IndexPattern | undefined | null> {
if (savedVis.visState.type !== 'metrics') {
return savedVis.searchSource!.getField('index');
}
const indexPatternsClient = getIndexPatterns();
return savedVis.visState.params.index_pattern
? (await indexPatternsClient.find(`"${savedVis.visState.params.index_pattern}"`))[0]
: await indexPatternsClient.getDefault();
}

View file

@ -0,0 +1,45 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* and the Server Side Public License, v 1; you may not use this file except in
* compliance with, at your election, the Elastic License or the Server Side
* Public License, v 1.
*/
import { ExpressionFunctionKibana, ExpressionFunctionKibanaContext } from '../../../data/public';
import { buildExpression, buildExpressionFunction } from '../../../expressions/public';
import { VisToExpressionAst } from '../types';
/**
* Creates an ast expression for a visualization based on kibana context (query, filters, timerange)
* including a saved search if the visualization is based on it.
* The expression also includes particular visualization expression ast if presented.
*
* @internal
*/
export const toExpressionAst: VisToExpressionAst = async (vis, params) => {
const { savedSearchId, searchSource } = vis.data;
const query = searchSource?.getField('query');
const filters = searchSource?.getField('filter');
const kibana = buildExpressionFunction<ExpressionFunctionKibana>('kibana', {});
const kibanaContext = buildExpressionFunction<ExpressionFunctionKibanaContext>('kibana_context', {
q: query && JSON.stringify(query),
filters: filters && JSON.stringify(filters),
savedSearchId,
});
const ast = buildExpression([kibana, kibanaContext]);
const expression = ast.toAst();
if (!vis.type.toExpressionAst) {
throw new Error('Visualization type definition should have toExpressionAst function defined');
}
const visExpressionAst = await vis.type.toExpressionAst(vis, params);
// expand the expression chain with a particular visualization expression chain, if it exists
expression.chain.push(...visExpressionAst.chain);
return expression;
};

View file

@ -32,8 +32,8 @@ import {
IExpressionLoaderParams,
ExpressionsStart,
ExpressionRenderError,
ExpressionAstExpression,
} from '../../../../plugins/expressions/public';
import { buildPipeline } from '../legacy/build_pipeline';
import { Vis, SerializedVis } from '../vis';
import { getExpressions, getUiActions } from '../services';
import { VIS_EVENT_TO_TRIGGER } from './events';
@ -41,6 +41,7 @@ import { VisualizeEmbeddableFactoryDeps } from './visualize_embeddable_factory';
import { SavedObjectAttributes } from '../../../../core/types';
import { SavedVisualizationsLoader } from '../saved_visualizations';
import { VisSavedObject } from '../types';
import { toExpressionAst } from './to_ast';
const getKeys = <T extends {}>(o: T): Array<keyof T> => Object.keys(o) as Array<keyof T>;
@ -94,7 +95,7 @@ export class VisualizeEmbeddable
private syncColors?: boolean;
private visCustomizations?: Pick<VisualizeInput, 'vis' | 'table'>;
private subscriptions: Subscription[] = [];
private expression: string = '';
private expression?: ExpressionAstExpression;
private vis: Vis;
private domNode: any;
public readonly type = VISUALIZE_EMBEDDABLE_TYPE;
@ -382,7 +383,7 @@ export class VisualizeEmbeddable
}
this.abortController = new AbortController();
const abortController = this.abortController;
this.expression = await buildPipeline(this.vis, {
this.expression = await toExpressionAst(this.vis, {
timefilter: this.timefilter,
timeRange: this.timeRange,
abortSignal: this.abortController!.signal,

View file

@ -74,12 +74,12 @@ export class VisualizeEmbeddableFactory
type: 'visualization',
getIconForSavedObject: (savedObject) => {
return (
getTypes().get(JSON.parse(savedObject.attributes.visState).type).icon || 'visualizeApp'
getTypes().get(JSON.parse(savedObject.attributes.visState).type)?.icon || 'visualizeApp'
);
},
getTooltipForSavedObject: (savedObject) => {
return `${savedObject.attributes.title} (${
getTypes().get(JSON.parse(savedObject.attributes.visState).type).title
getTypes().get(JSON.parse(savedObject.attributes.visState).type)?.title
})`;
},
showSavedObject: (savedObject) => {

View file

@ -1,157 +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
* and the Server Side Public License, v 1; you may not use this file except in
* compliance with, at your election, the Elastic License or the Server Side
* Public License, v 1.
*/
/**
* @name Vis
*
* @description This class consists of aggs, params, listeners, title, and type.
* - Aggs: Instances of AggConfig.
* - Params: The settings in the Options tab.
*
* Not to be confused with vislib/vis.js.
*/
import { EventEmitter } from 'events';
import _ from 'lodash';
import { VisParams, PersistedState } from '../../../../plugins/visualizations/public';
import { getTypes } from '../services';
import { VisType } from '../vis_types';
export interface ExprVisState {
title?: string;
type: VisType<unknown> | string;
params?: VisParams;
}
export interface ExprVisAPIEvents {
filter: (data: any) => void;
brush: (data: any) => void;
applyFilter: (data: any) => void;
}
export interface ExprVisAPI {
events: ExprVisAPIEvents;
}
export class ExprVis extends EventEmitter {
public title: string = '';
public type: VisType<unknown>;
public params: VisParams = {};
public sessionState: Record<string, any> = {};
public API: ExprVisAPI;
public eventsSubject: any;
private uiState: PersistedState;
constructor(visState: ExprVisState = { type: 'histogram' }) {
super();
this.type = this.getType(visState.type);
this.uiState = new PersistedState();
this.setState(visState);
this.API = {
events: {
filter: (data: any) => {
if (!this.eventsSubject) return;
this.eventsSubject.next({
name: 'filterBucket',
data: data.data
? {
data: data.data,
negate: data.negate,
}
: { data: [data] },
});
},
brush: (data: any) => {
if (!this.eventsSubject) return;
this.eventsSubject.next({ name: 'brush', data });
},
applyFilter: (data: any) => {
if (!this.eventsSubject) return;
this.eventsSubject.next({ name: 'applyFilter', data });
},
},
};
}
private getType(type: string | VisType<unknown>) {
if (_.isString(type)) {
const newType = getTypes().get(type);
if (!newType) {
throw new Error(`Invalid type "${type}"`);
}
return newType;
} else {
return type;
}
}
setState(state: ExprVisState) {
this.title = state.title || '';
if (state.type) {
this.type = this.getType(state.type);
}
this.params = _.defaultsDeep(
{},
_.cloneDeep(state.params || {}),
_.cloneDeep(this.type.visConfig.defaults || {})
);
}
getState() {
return {
title: this.title,
type: this.type.name,
params: _.cloneDeep(this.params),
};
}
updateState() {
this.emit('update');
}
forceReload() {
this.emit('reload');
}
isHierarchical() {
if (_.isFunction(this.type.hierarchicalData)) {
return !!this.type.hierarchicalData(this);
} else {
return !!this.type.hierarchicalData;
}
}
hasUiState() {
return !!this.uiState;
}
getUiState() {
return this.uiState;
}
setUiState(state: PersistedState) {
this.uiState = state;
}
/**
* Currently this is only used to extract map-specific information
* (e.g. mapZoom, mapCenter).
*/
uiStateVal(key: string, val: any) {
if (this.hasUiState()) {
if (_.isUndefined(val)) {
return this.uiState.get(key);
}
return this.uiState.set(key, val);
}
return val;
}
}

View file

@ -1,144 +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
* and the Server Side Public License, v 1; you may not use this file except in
* compliance with, at your election, the Elastic License or the Server Side
* Public License, v 1.
*/
import { get } from 'lodash';
import { i18n } from '@kbn/i18n';
import { VisResponseValue, PersistedState } from '../../../../plugins/visualizations/public';
import { ExpressionFunctionDefinition, Render } from '../../../../plugins/expressions/public';
import { getTypes, getIndexPatterns, getFilterManager, getSearch } from '../services';
interface Arguments {
index?: string | null;
metricsAtAllLevels?: boolean;
partialRows?: boolean;
type?: string;
schemas?: string;
visConfig?: string;
uiState?: string;
aggConfigs?: string;
}
export type ExpressionFunctionVisualization = ExpressionFunctionDefinition<
'visualization',
any,
Arguments,
Promise<Render<VisResponseValue>>
>;
export const visualization = (): ExpressionFunctionVisualization => ({
name: 'visualization',
type: 'render',
help: i18n.translate('visualizations.functions.visualization.help', {
defaultMessage: 'A simple visualization',
}),
args: {
// TODO: Below `help` keys should be internationalized once this function
// TODO: is moved to visualizations plugin.
index: {
types: ['string', 'null'],
default: null,
help: 'Index',
},
metricsAtAllLevels: {
types: ['boolean'],
default: false,
help: 'Metrics levels',
},
partialRows: {
types: ['boolean'],
default: false,
help: 'Partial rows',
},
type: {
types: ['string'],
default: '',
help: 'Type',
},
schemas: {
types: ['string'],
default: '"{}"',
help: 'Schemas',
},
visConfig: {
types: ['string'],
default: '"{}"',
help: 'Visualization configuration',
},
uiState: {
types: ['string'],
default: '"{}"',
help: 'User interface state',
},
aggConfigs: {
types: ['string'],
default: '"{}"',
help: 'Aggregation configurations',
},
},
async fn(input, args, { inspectorAdapters }) {
const visConfigParams = args.visConfig ? JSON.parse(args.visConfig) : {};
const schemas = args.schemas ? JSON.parse(args.schemas) : {};
const visType = getTypes().get(args.type || 'histogram') as any;
const indexPattern = args.index ? await getIndexPatterns().get(args.index) : null;
const uiStateParams = args.uiState ? JSON.parse(args.uiState) : {};
const uiState = new PersistedState(uiStateParams);
const aggConfigsState = args.aggConfigs ? JSON.parse(args.aggConfigs) : [];
const aggs = indexPattern
? getSearch().aggs.createAggConfigs(indexPattern, aggConfigsState)
: undefined;
if (typeof visType.requestHandler === 'function') {
input = await visType.requestHandler({
partialRows: args.partialRows,
metricsAtAllLevels: args.metricsAtAllLevels,
index: indexPattern,
visParams: visConfigParams,
timeRange: get(input, 'timeRange', null),
query: get(input, 'query', null),
filters: get(input, 'filters', null),
uiState,
inspectorAdapters,
queryFilter: getFilterManager(),
aggs,
});
}
if (typeof visType.responseHandler === 'function') {
if (input.columns) {
// assign schemas to aggConfigs
input.columns.forEach((column: any) => {
if (column.aggConfig) {
column.aggConfig.aggConfigs.schemas = visType.schemas.all;
}
});
Object.keys(schemas).forEach((key) => {
schemas[key].forEach((i: any) => {
if (input.columns[i] && input.columns[i].aggConfig) {
input.columns[i].aggConfig.schema = key;
}
});
});
}
input = await visType.responseHandler(input, visConfigParams.dimensions);
}
return {
type: 'render',
as: 'visualization',
value: {
visData: input,
visType: args.type || '',
visConfig: visConfigParams,
},
};
},
});

View file

@ -1,51 +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
* and the Server Side Public License, v 1; you may not use this file except in
* compliance with, at your election, the Elastic License or the Server Side
* Public License, v 1.
*/
import React from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
// @ts-ignore
import { ExprVis } from './vis';
import { Visualization } from '../components';
import { VisParams } from '../types';
export const visualization = () => ({
name: 'visualization',
displayName: 'visualization',
reuseDomNode: true,
render: async (domNode: HTMLElement, config: any, handlers: any) => {
const { visData, visConfig, params } = config;
const visType = config.visType || visConfig.type;
const vis = new ExprVis({
title: config.title,
type: visType as string,
params: visConfig as VisParams,
});
vis.eventsSubject = { next: handlers.event };
const uiState = handlers.uiState || vis.getUiState();
handlers.onDestroy(() => {
unmountComponentAtNode(domNode);
});
const listenOnChange = params ? params.listenOnChange : false;
render(
<Visualization
vis={vis}
visData={visData}
visParams={vis.params}
uiState={uiState}
listenOnChange={listenOnChange}
onInit={handlers.done}
/>,
domNode
);
},
});

View file

@ -10,7 +10,6 @@ import { PublicContract } from '@kbn/utility-types';
import { PluginInitializerContext } from 'src/core/public';
import { VisualizationsPlugin, VisualizationsSetup, VisualizationsStart } from './plugin';
import { VisualizeEmbeddableFactory, VisualizeEmbeddable } from './embeddable';
import { ExprVis as ExprVisClass } from './expressions/vis';
export function plugin(initializerContext: PluginInitializerContext) {
return new VisualizationsPlugin(initializerContext);
@ -20,39 +19,27 @@ export function plugin(initializerContext: PluginInitializerContext) {
export { Vis } from './vis';
export { TypesService } from './vis_types/types_service';
export { VISUALIZE_EMBEDDABLE_TYPE, VIS_EVENT_TO_TRIGGER } from './embeddable';
export { VisualizationContainer, VisualizationNoResults } from './components';
export { getSchemas as getVisSchemas } from './legacy/build_pipeline';
export { VisualizationContainer } from './components';
export { getVisSchemas } from './vis_schemas';
/** @public types */
export { VisualizationsSetup, VisualizationsStart };
export { VisGroups } from './vis_types';
export type {
VisTypeAlias,
VisType,
BaseVisTypeOptions,
ReactVisTypeOptions,
Schema,
ISchemas,
} from './vis_types';
export type { VisTypeAlias, VisTypeDefinition, Schema, ISchemas } from './vis_types';
export { SerializedVis, SerializedVisData, VisData } from './vis';
export type VisualizeEmbeddableFactoryContract = PublicContract<VisualizeEmbeddableFactory>;
export type VisualizeEmbeddableContract = PublicContract<VisualizeEmbeddable>;
export { VisualizeInput } from './embeddable';
export type ExprVis = ExprVisClass;
export { SchemaConfig, BuildPipelineParams } from './legacy/build_pipeline';
// @ts-ignore
export { SchemaConfig } from './vis_schemas';
export { updateOldState } from './legacy/vis_update_state';
export { PersistedState } from './persisted_state';
export {
VisualizationControllerConstructor,
VisualizationController,
ISavedVis,
VisSavedObject,
VisResponseValue,
VisToExpressionAst,
VisParams,
VisToExpressionAstParams,
VisEditorOptionsProps,
} from './types';
export { ExprVisAPIEvents } from './expressions/vis';
export { VisualizationListItem, VisualizationStage } from './vis_types/vis_type_alias_registry';
export { VISUALIZE_ENABLE_LABS_SETTING } from '../common/constants';
export { SavedVisState } from '../common';
export { SavedVisState, VisParams } from '../common';

View file

@ -1,3 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`visualize loader pipeline helpers: build pipeline buildPipeline calls toExpression on vis_type if it exists 1`] = `"kibana | kibana_context | test"`;

View file

@ -1,86 +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
* and the Server Side Public License, v 1; you may not use this file except in
* compliance with, at your election, the Elastic License or the Server Side
* Public License, v 1.
*/
import { prepareJson, prepareString, buildPipeline } from './build_pipeline';
import { Vis } from '..';
import { dataPluginMock } from '../../../../plugins/data/public/mocks';
import { parseExpression } from '../../../expressions/common';
describe('visualize loader pipeline helpers: build pipeline', () => {
describe('prepareJson', () => {
it('returns a correctly formatted key/value string', () => {
const expected = `foo='{}' `; // trailing space is expected
const actual = prepareJson('foo', {});
expect(actual).toBe(expected);
});
it('stringifies provided data', () => {
const expected = `foo='{\"well\":\"hello\",\"there\":{\"friend\":true}}' `;
const actual = prepareJson('foo', { well: 'hello', there: { friend: true } });
expect(actual).toBe(expected);
});
it('escapes single quotes', () => {
const expected = `foo='{\"well\":\"hello \\'hi\\'\",\"there\":{\"friend\":true}}' `;
const actual = prepareJson('foo', { well: `hello 'hi'`, there: { friend: true } });
expect(actual).toBe(expected);
});
it('returns empty string if data is undefined', () => {
const actual = prepareJson('foo', undefined);
expect(actual).toBe('');
});
});
describe('prepareString', () => {
it('returns a correctly formatted key/value string', () => {
const expected = `foo='bar' `; // trailing space is expected
const actual = prepareString('foo', 'bar');
expect(actual).toBe(expected);
});
it('escapes single quotes', () => {
const expected = `foo='\\'bar\\'' `;
const actual = prepareString('foo', `'bar'`);
expect(actual).toBe(expected);
});
it('returns empty string if data is undefined', () => {
const actual = prepareString('foo', undefined);
expect(actual).toBe('');
});
});
describe('buildPipeline', () => {
const dataStart = dataPluginMock.createStartContract();
it('calls toExpression on vis_type if it exists', async () => {
const vis = ({
getState: () => {},
isHierarchical: () => false,
data: {
aggs: {
getResponseAggs: () => [],
},
searchSource: {
getField: jest.fn(),
getParent: jest.fn(),
},
},
// @ts-ignore
type: {
toExpressionAst: () => parseExpression('test'),
},
} as unknown) as Vis;
const expression = await buildPipeline(vis, {
timefilter: dataStart.query.timefilter.timefilter,
});
expect(expression).toMatchSnapshot();
});
});
});

View file

@ -1,253 +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
* and the Server Side Public License, v 1; you may not use this file except in
* compliance with, at your election, the Elastic License or the Server Side
* Public License, v 1.
*/
import {
buildExpression,
formatExpression,
SerializedFieldFormat,
} from '../../../../plugins/expressions/public';
import { IAggConfig, search, TimefilterContract } from '../../../../plugins/data/public';
import { Vis } from '../types';
const { isDateHistogramBucketAggConfig } = search.aggs;
interface SchemaConfigParams {
precision?: number;
useGeocentroid?: boolean;
}
export interface SchemaConfig {
accessor: number;
label: string;
format: SerializedFieldFormat;
params: SchemaConfigParams;
aggType: string;
}
export interface Schemas {
metric: SchemaConfig[];
bucket?: SchemaConfig[];
geo_centroid?: any[];
group?: any[];
params?: any[];
radius?: any[];
segment?: any[];
split_column?: SchemaConfig[];
split_row?: SchemaConfig[];
width?: any[];
// catch all for schema name
[key: string]: any[] | undefined;
}
export interface BuildPipelineParams {
timefilter: TimefilterContract;
timeRange?: any;
abortSignal?: AbortSignal;
}
export const getSchemas = <TVisParams>(
vis: Vis<TVisParams>,
{ timeRange, timefilter }: BuildPipelineParams
): Schemas => {
const createSchemaConfig = (accessor: number, agg: IAggConfig): SchemaConfig => {
if (isDateHistogramBucketAggConfig(agg)) {
agg.params.timeRange = timeRange;
const bounds =
agg.params.timeRange && agg.fieldIsTimeField()
? timefilter.calculateBounds(agg.params.timeRange)
: undefined;
agg.buckets.setBounds(bounds);
agg.buckets.setInterval(agg.params.interval);
}
const hasSubAgg = [
'derivative',
'moving_avg',
'serial_diff',
'cumulative_sum',
'sum_bucket',
'avg_bucket',
'min_bucket',
'max_bucket',
].includes(agg.type.name);
const formatAgg = hasSubAgg
? agg.params.customMetric || agg.aggConfigs.getRequestAggById(agg.params.metricAgg)
: agg;
const params: SchemaConfigParams = {};
if (agg.type.name === 'geohash_grid') {
params.precision = agg.params.precision;
params.useGeocentroid = agg.params.useGeocentroid;
}
const label = agg.makeLabel && agg.makeLabel();
return {
accessor,
format: formatAgg.toSerializedFieldFormat(),
params,
label,
aggType: agg.type.name,
};
};
let cnt = 0;
const schemas: Schemas = {
metric: [],
};
if (!vis.data.aggs) {
return schemas;
}
const responseAggs = vis.data.aggs.getResponseAggs().filter((agg: IAggConfig) => agg.enabled);
const isHierarchical = vis.isHierarchical();
const metrics = responseAggs.filter((agg: IAggConfig) => agg.type.type === 'metrics');
responseAggs.forEach((agg: IAggConfig) => {
let skipMetrics = false;
let schemaName = agg.schema;
if (!schemaName) {
if (agg.type.name === 'geo_centroid') {
schemaName = 'geo_centroid';
} else {
cnt++;
return;
}
}
if (schemaName === 'split') {
// TODO: We should check if there's a better way then casting to `any` here
schemaName = `split_${(vis.params as any).row ? 'row' : 'column'}`;
skipMetrics = responseAggs.length - metrics.length > 1;
}
if (!schemas[schemaName]) {
schemas[schemaName] = [];
}
if (!isHierarchical || agg.type.type !== 'metrics') {
schemas[schemaName]!.push(createSchemaConfig(cnt++, agg));
}
if (isHierarchical && (agg.type.type !== 'metrics' || metrics.length === responseAggs.length)) {
metrics.forEach((metric: any) => {
const schemaConfig = createSchemaConfig(cnt++, metric);
if (!skipMetrics) {
schemas.metric.push(schemaConfig);
}
});
}
});
return schemas;
};
export const prepareJson = (variable: string, data?: object): string => {
if (data === undefined) {
return '';
}
return `${variable}='${JSON.stringify(data).replace(/\\/g, `\\\\`).replace(/'/g, `\\'`)}' `;
};
export const escapeString = (data: string): string => {
return data.replace(/\\/g, `\\\\`).replace(/'/g, `\\'`);
};
export const prepareString = (variable: string, data?: string): string => {
if (data === undefined) {
return '';
}
return `${variable}='${escapeString(data)}' `;
};
export const prepareValue = (variable: string, data: any, raw: boolean = false) => {
if (data === undefined) {
return '';
}
if (raw) {
return `${variable}=${data} `;
}
switch (typeof data) {
case 'string':
return prepareString(variable, data);
case 'object':
return prepareJson(variable, data);
default:
return `${variable}=${data} `;
}
};
export const prepareDimension = (variable: string, data: any) => {
if (data === undefined) {
return '';
}
let expr = `${variable}={visdimension ${data.accessor} `;
if (data.format) {
expr += prepareValue('format', data.format.id);
expr += prepareJson('formatParams', data.format.params);
}
expr += '} ';
return expr;
};
export const buildPipeline = async (vis: Vis, params: BuildPipelineParams) => {
const { indexPattern, searchSource } = vis.data;
const query = searchSource!.getField('query');
const filters = searchSource!.getField('filter');
const { uiState, title } = vis;
// context
let pipeline = `kibana | kibana_context `;
if (query) {
pipeline += prepareJson('query', query);
}
if (filters) {
pipeline += prepareJson('filters', filters);
}
if (vis.data.savedSearchId) {
pipeline += prepareString('savedSearchId', vis.data.savedSearchId);
}
pipeline += '| ';
if (vis.type.toExpressionAst) {
const visAst = await vis.type.toExpressionAst(vis, params);
pipeline += formatExpression(visAst);
} else {
// request handler
if (vis.type.requestHandler === 'courier') {
pipeline += `esaggs
index={indexPatternLoad ${prepareString('id', indexPattern!.id)}}
metricsAtAllLevels=${vis.isHierarchical()}
partialRows=${vis.params.showPartialRows || false} `;
if (vis.data.aggs) {
vis.data.aggs.aggs.forEach((agg) => {
const ast = agg.toExpressionAst();
if (ast) {
pipeline += `aggs={${buildExpression(ast).toString()}} `;
}
});
}
pipeline += `| `;
} else {
const schemas = getSchemas(vis, params);
const visConfig = { ...vis.params };
visConfig.dimensions = schemas;
visConfig.title = title;
pipeline += `visualization type='${vis.type.name}'
${prepareJson('visConfig', visConfig)}
${prepareJson('uiState', uiState)}
metricsAtAllLevels=${vis.isHierarchical()}
partialRows=${vis.params.showPartialRows || false} `;
if (indexPattern) {
pipeline += `${prepareString('index', indexPattern.id)} `;
if (vis.data.aggs) {
pipeline += `${prepareJson('aggConfigs', vis.data.aggs!.aggs)}`;
}
}
}
}
return pipeline;
};

View file

@ -22,7 +22,6 @@ import { savedObjectsPluginMock } from '../../../plugins/saved_objects/public/mo
const createSetupContract = (): VisualizationsSetup => ({
createBaseVisualization: jest.fn(),
createReactVisualization: jest.fn(),
registerAlias: jest.fn(),
hideTypes: jest.fn(),
});

View file

@ -20,15 +20,12 @@ import { TypesService, TypesSetup, TypesStart } from './vis_types';
import {
setUISettings,
setTypes,
setI18n,
setApplication,
setCapabilities,
setHttp,
setIndexPatterns,
setSearch,
setSavedObjects,
setUsageCollector,
setFilterManager,
setExpressions,
setUiActions,
setSavedVisualizationsLoader,
@ -47,8 +44,6 @@ import {
} from './embeddable';
import { ExpressionsSetup, ExpressionsStart } from '../../expressions/public';
import { EmbeddableSetup, EmbeddableStart } from '../../embeddable/public';
import { visualization as visualizationFunction } from './expressions/visualization_function';
import { visualization as visualizationRenderer } from './expressions/visualization_renderer';
import { range as rangeExpressionFunction } from './expression_functions/range';
import { visDimension as visDimensionExpressionFunction } from './expression_functions/vis_dimension';
import { DataPublicPluginSetup, DataPublicPluginStart } from '../../../plugins/data/public';
@ -138,8 +133,6 @@ export class VisualizationsPlugin
setUISettings(core.uiSettings);
setUsageCollector(usageCollection);
expressions.registerFunction(visualizationFunction);
expressions.registerRenderer(visualizationRenderer);
expressions.registerFunction(rangeExpressionFunction);
expressions.registerFunction(visDimensionExpressionFunction);
const embeddableFactory = new VisualizeEmbeddableFactory({ start });
@ -155,7 +148,6 @@ export class VisualizationsPlugin
{ data, expressions, uiActions, embeddable, dashboard, savedObjects }: VisualizationsStartDeps
): VisualizationsStart {
const types = this.types.start();
setI18n(core.i18n);
setTypes(types);
setEmbeddable(embeddable);
setApplication(core.application);
@ -163,9 +155,7 @@ export class VisualizationsPlugin
setHttp(core.http);
setSavedObjects(core.savedObjects);
setDocLinks(core.docLinks);
setIndexPatterns(data.indexPatterns);
setSearch(data.search);
setFilterManager(data.query.filterManager);
setExpressions(expressions);
setUiActions(uiActions);
setTimeFilter(data.query.timefilter.timefilter);

View file

@ -11,7 +11,6 @@ import {
Capabilities,
ChromeStart,
HttpStart,
I18nStart,
IUiSettingsClient,
OverlayStart,
SavedObjectsStart,
@ -19,12 +18,7 @@ import {
} from '../../../core/public';
import { TypesStart } from './vis_types';
import { createGetterSetter } from '../../../plugins/kibana_utils/public';
import {
DataPublicPluginStart,
FilterManager,
IndexPatternsContract,
TimefilterContract,
} from '../../../plugins/data/public';
import { DataPublicPluginStart, TimefilterContract } from '../../../plugins/data/public';
import { UsageCollectionSetup } from '../../../plugins/usage_collection/public';
import { ExpressionsStart } from '../../../plugins/expressions/public';
import { UiActionsStart } from '../../../plugins/ui_actions/public';
@ -48,20 +42,10 @@ export const [getSavedObjects, setSavedObjects] = createGetterSetter<SavedObject
export const [getTypes, setTypes] = createGetterSetter<TypesStart>('Types');
export const [getI18n, setI18n] = createGetterSetter<I18nStart>('I18n');
export const [getDocLinks, setDocLinks] = createGetterSetter<DocLinksStart>('DocLinks');
export const [getFilterManager, setFilterManager] = createGetterSetter<FilterManager>(
'FilterManager'
);
export const [getTimeFilter, setTimeFilter] = createGetterSetter<TimefilterContract>('TimeFilter');
export const [getIndexPatterns, setIndexPatterns] = createGetterSetter<IndexPatternsContract>(
'IndexPatterns'
);
export const [getSearch, setSearch] = createGetterSetter<DataPublicPluginStart['search']>('Search');
export const [getUsageCollector, setUsageCollector] = createGetterSetter<UsageCollectionSetup>(

View file

@ -7,26 +7,27 @@
*/
import { SavedObject } from '../../../plugins/saved_objects/public';
import { SearchSourceFields, TimefilterContract } from '../../../plugins/data/public';
import {
AggConfigOptions,
IAggConfigs,
SearchSourceFields,
TimefilterContract,
} from '../../../plugins/data/public';
import { ExpressionAstExpression } from '../../expressions/public';
import { SerializedVis, Vis } from './vis';
import { ExprVis } from './expressions/vis';
import { SavedVisState, VisParams } from '../common/types';
import { PersistedState } from './persisted_state';
import { VisParams } from '../common';
export { Vis, SerializedVis, VisParams };
export interface VisualizationController {
render(visData: any, visParams: any): Promise<void>;
destroy(): void;
isLoaded?(): Promise<void> | void;
export interface SavedVisState {
title: string;
type: string;
params: VisParams;
aggs: AggConfigOptions[];
}
export type VisualizationControllerConstructor = new (
el: HTMLElement,
vis: ExprVis
) => VisualizationController;
export interface ISavedVis {
id?: string;
title: string;
@ -40,13 +41,6 @@ export interface ISavedVis {
export interface VisSavedObject extends SavedObject, ISavedVis {}
export interface VisResponseValue {
visType: string;
visData: object;
visConfig: object;
params?: object;
}
export interface VisToExpressionAstParams {
timefilter: TimefilterContract;
timeRange?: any;
@ -57,3 +51,15 @@ export type VisToExpressionAst<TVisParams = VisParams> = (
vis: Vis<TVisParams>,
params: VisToExpressionAstParams
) => Promise<ExpressionAstExpression> | ExpressionAstExpression;
export interface VisEditorOptionsProps<VisParamType = unknown> {
aggs: IAggConfigs;
hasHistogramAgg: boolean;
isTabSelected: boolean;
stateParams: VisParamType;
vis: Vis;
uiState: PersistedState;
setValue<T extends keyof VisParamType>(paramName: T, value: VisParamType[T]): void;
setValidity(isValid: boolean): void;
setTouched(isTouched: boolean): void;
}

View file

@ -22,7 +22,6 @@ import { i18n } from '@kbn/i18n';
import { PersistedState } from './persisted_state';
import { getTypes, getAggs, getSearch, getSavedSearchLoader } from './services';
import { VisType } from './vis_types';
import {
IAggConfigs,
IndexPattern,
@ -30,6 +29,7 @@ import {
AggConfigOptions,
SearchSourceFields,
} from '../../../plugins/data/public';
import { BaseVisType } from './vis_types';
import { VisParams } from '../common/types';
export interface SerializedVisData {
@ -71,14 +71,11 @@ const getSearchSource = async (inputSearchSource: ISearchSource, savedSearchId?:
type PartialVisState = Assign<SerializedVis, { data: Partial<SerializedVisData> }>;
export class Vis<TVisParams = VisParams> {
public readonly type: VisType<TVisParams>;
public readonly type: BaseVisType<TVisParams>;
public readonly id?: string;
public title: string = '';
public description: string = '';
public params: TVisParams;
// Session state is for storing information that is transitory, and will not be saved with the visualization.
// For instance, map bounds, which depends on the view port, browser window size, etc.
public sessionState: Record<string, any> = {};
public data: VisData = {};
public readonly uiState: PersistedState;

View file

@ -0,0 +1,136 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* and the Server Side Public License, v 1; you may not use this file except in
* compliance with, at your election, the Elastic License or the Server Side
* Public License, v 1.
*/
import { SerializedFieldFormat } from '../../expressions/public';
import { IAggConfig, search } from '../../data/public';
import { Vis, VisToExpressionAstParams } from './types';
const { isDateHistogramBucketAggConfig } = search.aggs;
interface SchemaConfigParams {
precision?: number;
useGeocentroid?: boolean;
}
export interface SchemaConfig {
accessor: number;
label: string;
format: SerializedFieldFormat;
params: SchemaConfigParams;
aggType: string;
}
export interface Schemas {
metric: SchemaConfig[];
bucket?: SchemaConfig[];
geo_centroid?: any[];
group?: any[];
params?: any[];
radius?: any[];
segment?: any[];
split_column?: SchemaConfig[];
split_row?: SchemaConfig[];
width?: any[];
// catch all for schema name
[key: string]: any[] | undefined;
}
export const getVisSchemas = <TVisParams>(
vis: Vis<TVisParams>,
{ timeRange, timefilter }: VisToExpressionAstParams
): Schemas => {
const createSchemaConfig = (accessor: number, agg: IAggConfig): SchemaConfig => {
if (isDateHistogramBucketAggConfig(agg)) {
agg.params.timeRange = timeRange;
const bounds =
agg.params.timeRange && agg.fieldIsTimeField()
? timefilter.calculateBounds(agg.params.timeRange)
: undefined;
agg.buckets.setBounds(bounds);
agg.buckets.setInterval(agg.params.interval);
}
const hasSubAgg = [
'derivative',
'moving_avg',
'serial_diff',
'cumulative_sum',
'sum_bucket',
'avg_bucket',
'min_bucket',
'max_bucket',
].includes(agg.type.name);
const formatAgg = hasSubAgg
? agg.params.customMetric || agg.aggConfigs.getRequestAggById(agg.params.metricAgg)
: agg;
const params: SchemaConfigParams = {};
if (agg.type.name === 'geohash_grid') {
params.precision = agg.params.precision;
params.useGeocentroid = agg.params.useGeocentroid;
}
const label = agg.makeLabel && agg.makeLabel();
return {
accessor,
format: formatAgg.toSerializedFieldFormat(),
params,
label,
aggType: agg.type.name,
};
};
let cnt = 0;
const schemas: Schemas = {
metric: [],
};
if (!vis.data.aggs) {
return schemas;
}
const responseAggs = vis.data.aggs.getResponseAggs().filter((agg: IAggConfig) => agg.enabled);
const isHierarchical = vis.isHierarchical();
const metrics = responseAggs.filter((agg: IAggConfig) => agg.type.type === 'metrics');
responseAggs.forEach((agg: IAggConfig) => {
let skipMetrics = false;
let schemaName = agg.schema;
if (!schemaName) {
if (agg.type.name === 'geo_centroid') {
schemaName = 'geo_centroid';
} else {
cnt++;
return;
}
}
if (schemaName === 'split') {
// TODO: We should check if there's a better way then casting to `any` here
schemaName = `split_${(vis.params as any).row ? 'row' : 'column'}`;
skipMetrics = responseAggs.length - metrics.length > 1;
}
if (!schemas[schemaName]) {
schemas[schemaName] = [];
}
if (!isHierarchical || agg.type.type !== 'metrics') {
schemas[schemaName]!.push(createSchemaConfig(cnt++, agg));
}
if (isHierarchical && (agg.type.type !== 'metrics' || metrics.length === responseAggs.length)) {
metrics.forEach((metric: any) => {
const schemaConfig = createSchemaConfig(cnt++, metric);
if (!skipMetrics) {
schemas.metric.push(schemaConfig);
}
});
}
});
return schemas;
};

View file

@ -16,7 +16,16 @@ describe('BaseVisType', () => {
name: 'test',
title: 'test',
description: 'test',
visualization: {} as any,
visConfig: {
defaults: {},
},
toExpressionAst: () => ({
type: 'expression',
chain: [],
}),
editorConfig: {
editor: 'custom',
},
});
}).toThrow();
});

View file

@ -9,54 +9,9 @@
import { defaultsDeep } from 'lodash';
import { VisParams } from '../types';
import { VisType, VisTypeOptions, VisGroups } from './types';
import { VisTypeDefinition, VisTypeOptions, VisGroups } from './types';
import { Schemas } from './schemas';
interface CommonBaseVisTypeOptions<TVisParams>
extends Pick<
VisType<TVisParams>,
| 'description'
| 'getInfoMessage'
| 'getSupportedTriggers'
| 'hierarchicalData'
| 'icon'
| 'image'
| 'inspectorAdapters'
| 'name'
| 'requestHandler'
| 'responseHandler'
| 'setup'
| 'title'
>,
Pick<
Partial<VisType<TVisParams>>,
| 'editorConfig'
| 'hidden'
| 'stage'
| 'getUsedIndexPattern'
| 'useCustomNoDataScreen'
| 'visConfig'
| 'group'
| 'titleInWizard'
| 'note'
> {
options?: Partial<VisType<TVisParams>['options']>;
}
interface ExpressionBaseVisTypeOptions<TVisParams> extends CommonBaseVisTypeOptions<TVisParams> {
toExpressionAst: VisType<TVisParams>['toExpressionAst'];
visualization?: undefined;
}
interface VisualizationBaseVisTypeOptions<TVisParams> extends CommonBaseVisTypeOptions<TVisParams> {
toExpressionAst?: undefined;
visualization: VisType<TVisParams>['visualization'];
}
export type BaseVisTypeOptions<TVisParams = VisParams> =
| ExpressionBaseVisTypeOptions<TVisParams>
| VisualizationBaseVisTypeOptions<TVisParams>;
const defaultOptions: VisTypeOptions = {
showTimePicker: true,
showQueryBar: true,
@ -65,7 +20,7 @@ const defaultOptions: VisTypeOptions = {
hierarchicalData: false, // we should get rid of this i guess ?
};
export class BaseVisType<TVisParams = VisParams> implements VisType<TVisParams> {
export class BaseVisType<TVisParams = VisParams> {
public readonly name;
public readonly title;
public readonly description;
@ -76,23 +31,20 @@ export class BaseVisType<TVisParams = VisParams> implements VisType<TVisParams>
public readonly stage;
public readonly group;
public readonly titleInWizard;
public readonly options;
public readonly visualization;
public readonly options: VisTypeOptions;
public readonly visConfig;
public readonly editorConfig;
public hidden;
public readonly requestHandler;
public readonly responseHandler;
public readonly requiresSearch;
public readonly hierarchicalData;
public readonly setup;
public readonly getUsedIndexPattern;
public readonly useCustomNoDataScreen;
public readonly inspectorAdapters;
public readonly toExpressionAst;
public readonly getInfoMessage;
public readonly schemas;
constructor(opts: BaseVisTypeOptions<TVisParams>) {
constructor(opts: VisTypeDefinition<TVisParams>) {
if (!opts.icon && !opts.image) {
throw new Error('vis_type must define its icon or image');
}
@ -104,7 +56,6 @@ export class BaseVisType<TVisParams = VisParams> implements VisType<TVisParams>
this.title = opts.title;
this.icon = opts.icon;
this.image = opts.image;
this.visualization = opts.visualization;
this.visConfig = defaultsDeep({}, opts.visConfig, { defaults: {} });
this.editorConfig = defaultsDeep({}, opts.editorConfig, { collections: {} });
this.options = defaultsDeep({}, opts.options, defaultOptions);
@ -112,20 +63,14 @@ export class BaseVisType<TVisParams = VisParams> implements VisType<TVisParams>
this.group = opts.group ?? VisGroups.AGGBASED;
this.titleInWizard = opts.titleInWizard ?? '';
this.hidden = opts.hidden ?? false;
this.requestHandler = opts.requestHandler ?? 'courier';
this.responseHandler = opts.responseHandler ?? 'none';
this.requiresSearch = opts.requiresSearch ?? false;
this.setup = opts.setup;
this.hierarchicalData = opts.hierarchicalData ?? false;
this.getUsedIndexPattern = opts.getUsedIndexPattern;
this.useCustomNoDataScreen = opts.useCustomNoDataScreen ?? false;
this.inspectorAdapters = opts.inspectorAdapters;
this.toExpressionAst = opts.toExpressionAst;
this.getInfoMessage = opts.getInfoMessage;
this.schemas = new Schemas(this.editorConfig?.schemas ?? []);
}
public get requiresSearch(): boolean {
return this.requestHandler !== 'none';
}
}

View file

@ -9,6 +9,5 @@
export * from './types_service';
export { Schemas } from './schemas';
export { VisGroups } from './types';
export type { VisType, ISchemas, Schema } from './types';
export type { BaseVisTypeOptions } from './base_vis_type';
export type { ReactVisTypeOptions } from './react_vis_type';
export { BaseVisType } from './base_vis_type';
export type { VisTypeDefinition, ISchemas, Schema } from './types';

Some files were not shown because too many files have changed in this diff Show more