Improve visualization typings (#79128)
* Improve visualization typings * Fix vis selection dialog * Fix broken getInfoMessage type Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
2ba729f904
commit
b8f4ea1d06
|
@ -76,7 +76,7 @@ function TileMapOptions(props: TileMapOptionsProps) {
|
|||
<BasicOptions {...props} />
|
||||
|
||||
<SwitchOption
|
||||
disabled={!vis.type.visConfig.canDesaturate}
|
||||
disabled={!vis.type.visConfig?.canDesaturate}
|
||||
label={i18n.translate('tileMap.visParams.desaturateTilesLabel', {
|
||||
defaultMessage: 'Desaturate tiles',
|
||||
})}
|
||||
|
|
|
@ -55,7 +55,6 @@ export function createTileMapTypeDefinition(dependencies) {
|
|||
wms: uiSettings.get('visualization:tileMap:WMSdefaults'),
|
||||
},
|
||||
},
|
||||
requiresPartialRows: true,
|
||||
visualization: CoordinateMapsVisualization,
|
||||
responseHandler: convertToGeoJson,
|
||||
editorConfig: {
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
*/
|
||||
|
||||
import React, { useMemo, useState, useCallback, KeyboardEventHandler, useEffect } from 'react';
|
||||
import { get, isEqual } from 'lodash';
|
||||
import { isEqual } from 'lodash';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { keys, EuiButtonIcon, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { EventEmitter } from 'events';
|
||||
|
@ -71,7 +71,7 @@ function DefaultEditorSideBar({
|
|||
]);
|
||||
const metricSchemas = (vis.type.schemas.metrics || []).map((s: Schema) => s.name);
|
||||
const metricAggs = useMemo(
|
||||
() => responseAggs.filter((agg) => metricSchemas.includes(get(agg, 'schema'))),
|
||||
() => responseAggs.filter((agg) => agg.schema && metricSchemas.includes(agg.schema)),
|
||||
[responseAggs, metricSchemas]
|
||||
);
|
||||
const hasHistogramAgg = useMemo(() => responseAggs.some((agg) => agg.type.name === 'histogram'), [
|
||||
|
|
|
@ -39,7 +39,7 @@ export const toExpressionAst = (vis: Vis, params: any) => {
|
|||
const esaggs = buildExpressionFunction<EsaggsExpressionFunctionDefinition>('esaggs', {
|
||||
index: vis.data.indexPattern!.id!,
|
||||
metricsAtAllLevels: vis.isHierarchical(),
|
||||
partialRows: vis.type.requiresPartialRows || vis.params.showPartialRows || false,
|
||||
partialRows: vis.params.showPartialRows || false,
|
||||
aggConfigs: JSON.stringify(vis.data.aggs!.aggs),
|
||||
includeFormatHints: false,
|
||||
});
|
||||
|
|
|
@ -249,13 +249,13 @@ describe('Table Vis - Controller', () => {
|
|||
const vis = getRangeVis({ showPartialRows: true });
|
||||
initController(vis);
|
||||
|
||||
expect(vis.type.hierarchicalData(vis)).toEqual(true);
|
||||
expect((vis.type.hierarchicalData as Function)(vis)).toEqual(true);
|
||||
});
|
||||
|
||||
test('passes partialRows:false to tabify based on the vis params', () => {
|
||||
const vis = getRangeVis({ showPartialRows: false });
|
||||
initController(vis);
|
||||
|
||||
expect(vis.type.hierarchicalData(vis)).toEqual(false);
|
||||
expect((vis.type.hierarchicalData as Function)(vis)).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -20,7 +20,7 @@ import { CoreSetup, PluginInitializerContext } from 'kibana/public';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { AggGroupNames } from '../../data/public';
|
||||
import { Schemas } from '../../vis_default_editor/public';
|
||||
import { BaseVisTypeOptions, Vis } from '../../visualizations/public';
|
||||
import { BaseVisTypeOptions } from '../../visualizations/public';
|
||||
import { tableVisResponseHandler } from './table_vis_response_handler';
|
||||
// @ts-ignore
|
||||
import tableVisTemplate from './table_vis.html';
|
||||
|
@ -99,7 +99,7 @@ export function getTableVisTypeDefinition(
|
|||
]),
|
||||
},
|
||||
responseHandler: tableVisResponseHandler,
|
||||
hierarchicalData: (vis: Vis) => {
|
||||
hierarchicalData: (vis) => {
|
||||
return Boolean(vis.params.showPartialRows || vis.params.showMetricsAtAllLevels);
|
||||
},
|
||||
};
|
||||
|
|
|
@ -103,7 +103,9 @@ export function getTableVisualizationControllerClass(
|
|||
this.$scope = this.$rootScope.$new();
|
||||
this.$scope.uiState = this.vis.getUiState();
|
||||
updateScope();
|
||||
this.el.find('div').append(this.$compile(this.vis.type!.visConfig.template)(this.$scope));
|
||||
this.el
|
||||
.find('div')
|
||||
.append(this.$compile(this.vis.type.visConfig?.template ?? '')(this.$scope));
|
||||
this.$scope.$apply();
|
||||
} else {
|
||||
updateScope();
|
||||
|
|
|
@ -38,7 +38,7 @@ export const toExpressionAst = (vis: Vis<TagCloudVisParams>, params: BuildPipeli
|
|||
const esaggs = buildExpressionFunction<EsaggsExpressionFunctionDefinition>('esaggs', {
|
||||
index: vis.data.indexPattern!.id!,
|
||||
metricsAtAllLevels: vis.isHierarchical(),
|
||||
partialRows: vis.type.requiresPartialRows || false,
|
||||
partialRows: false,
|
||||
aggConfigs: JSON.stringify(vis.data.aggs!.aggs),
|
||||
includeFormatHints: false,
|
||||
});
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { BaseVisTypeOptions } from 'src/plugins/visualizations/public';
|
||||
import { DefaultEditorSize } from '../../vis_default_editor/public';
|
||||
import { VegaVisualizationDependencies } from './plugin';
|
||||
import { VegaVisEditor } from './components';
|
||||
|
@ -31,7 +32,9 @@ import { VIS_EVENT_TO_TRIGGER } from '../../visualizations/public';
|
|||
|
||||
import { getInfoMessage } from './components/experimental_map_vis_info';
|
||||
|
||||
export const createVegaTypeDefinition = (dependencies: VegaVisualizationDependencies) => {
|
||||
export const createVegaTypeDefinition = (
|
||||
dependencies: VegaVisualizationDependencies
|
||||
): BaseVisTypeOptions => {
|
||||
const requestHandler = createVegaRequestHandler(dependencies);
|
||||
const visualization = createVegaVisualization(dependencies);
|
||||
|
||||
|
|
|
@ -90,6 +90,12 @@ class VisualizationChart extends React.Component<VisualizationChartProps> {
|
|||
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
|
||||
|
|
|
@ -36,7 +36,7 @@ import { VisType } from '../vis_types';
|
|||
|
||||
export interface ExprVisState {
|
||||
title?: string;
|
||||
type: VisType | string;
|
||||
type: VisType<unknown> | string;
|
||||
params?: VisParams;
|
||||
}
|
||||
|
||||
|
@ -52,7 +52,7 @@ export interface ExprVisAPI {
|
|||
|
||||
export class ExprVis extends EventEmitter {
|
||||
public title: string = '';
|
||||
public type: VisType;
|
||||
public type: VisType<unknown>;
|
||||
public params: VisParams = {};
|
||||
public sessionState: Record<string, any> = {};
|
||||
public API: ExprVisAPI;
|
||||
|
@ -92,7 +92,7 @@ export class ExprVis extends EventEmitter {
|
|||
};
|
||||
}
|
||||
|
||||
private getType(type: string | VisType) {
|
||||
private getType(type: string | VisType<unknown>) {
|
||||
if (_.isString(type)) {
|
||||
const newType = getTypes().get(type);
|
||||
if (!newType) {
|
||||
|
|
|
@ -86,7 +86,10 @@ const vislibCharts: string[] = [
|
|||
'line',
|
||||
];
|
||||
|
||||
export const getSchemas = (vis: Vis, { timeRange, timefilter }: BuildPipelineParams): Schemas => {
|
||||
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;
|
||||
|
@ -155,7 +158,8 @@ export const getSchemas = (vis: Vis, { timeRange, timefilter }: BuildPipelinePar
|
|||
}
|
||||
}
|
||||
if (schemaName === 'split') {
|
||||
schemaName = `split_${vis.params.row ? 'row' : 'column'}`;
|
||||
// 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]) {
|
||||
|
@ -410,7 +414,7 @@ export const buildPipeline = async (vis: Vis, params: BuildPipelineParams) => {
|
|||
pipeline += `esaggs
|
||||
${prepareString('index', indexPattern!.id)}
|
||||
metricsAtAllLevels=${vis.isHierarchical()}
|
||||
partialRows=${vis.type.requiresPartialRows || vis.params.showPartialRows || false}
|
||||
partialRows=${vis.params.showPartialRows || false}
|
||||
${prepareJson('aggConfigs', vis.data.aggs!.aggs)} | `;
|
||||
}
|
||||
|
||||
|
@ -433,7 +437,7 @@ export const buildPipeline = async (vis: Vis, params: BuildPipelineParams) => {
|
|||
pipeline += `visualization type='${vis.type.name}'
|
||||
${prepareJson('visConfig', visConfig)}
|
||||
metricsAtAllLevels=${vis.isHierarchical()}
|
||||
partialRows=${vis.type.requiresPartialRows || vis.params.showPartialRows || false} `;
|
||||
partialRows=${vis.params.showPartialRows || false} `;
|
||||
if (indexPattern) {
|
||||
pipeline += `${prepareString('index', indexPattern.id)} `;
|
||||
if (vis.data.aggs) {
|
||||
|
|
|
@ -121,7 +121,7 @@ describe('Vis Class', function () {
|
|||
});
|
||||
|
||||
it('should return true for hierarchical vis (like pie)', function () {
|
||||
vis.type.hierarchicalData = true;
|
||||
(vis.type as any).hierarchicalData = true;
|
||||
expect(vis.isHierarchical()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -84,7 +84,7 @@ const getSearchSource = async (inputSearchSource: ISearchSource, savedSearchId?:
|
|||
type PartialVisState = Assign<SerializedVis, { data: Partial<SerializedVisData> }>;
|
||||
|
||||
export class Vis<TVisParams = VisParams> {
|
||||
public readonly type: VisType;
|
||||
public readonly type: VisType<TVisParams>;
|
||||
public readonly id?: string;
|
||||
public title: string = '';
|
||||
public description: string = '';
|
||||
|
@ -97,14 +97,14 @@ export class Vis<TVisParams = VisParams> {
|
|||
public readonly uiState: PersistedState;
|
||||
|
||||
constructor(visType: string, visState: SerializedVis = {} as any) {
|
||||
this.type = this.getType(visType);
|
||||
this.type = this.getType<TVisParams>(visType);
|
||||
this.params = this.getParams(visState.params);
|
||||
this.uiState = new PersistedState(visState.uiState);
|
||||
this.id = visState.id;
|
||||
}
|
||||
|
||||
private getType(visType: string) {
|
||||
const type = getTypes().get(visType);
|
||||
private getType<TVisParams>(visType: string) {
|
||||
const type = getTypes().get<TVisParams>(visType);
|
||||
if (!type) {
|
||||
const errorMessage = i18n.translate('visualizations.visualizationTypeInvalidMessage', {
|
||||
defaultMessage: 'Invalid visualization type "{visType}"',
|
||||
|
@ -118,7 +118,7 @@ export class Vis<TVisParams = VisParams> {
|
|||
}
|
||||
|
||||
private getParams(params: VisParams) {
|
||||
return defaults({}, cloneDeep(params || {}), cloneDeep(this.type.visConfig.defaults || {}));
|
||||
return defaults({}, cloneDeep(params ?? {}), cloneDeep(this.type.visConfig?.defaults ?? {}));
|
||||
}
|
||||
|
||||
async setState(state: PartialVisState) {
|
||||
|
@ -202,10 +202,6 @@ export class Vis<TVisParams = VisParams> {
|
|||
};
|
||||
}
|
||||
|
||||
toExpressionAst() {
|
||||
return this.type.toExpressionAst(this.params);
|
||||
}
|
||||
|
||||
// deprecated
|
||||
isHierarchical() {
|
||||
if (isFunction(this.type.hierarchicalData)) {
|
||||
|
|
|
@ -17,118 +17,113 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import { ReactElement } from 'react';
|
||||
import { VisParams, VisToExpressionAst, VisualizationControllerConstructor } from '../types';
|
||||
import { TriggerContextMapping } from '../../../ui_actions/public';
|
||||
import { Adapters } from '../../../inspector/public';
|
||||
import { Vis } from '../vis';
|
||||
import { defaultsDeep } from 'lodash';
|
||||
import { ISchemas } from 'src/plugins/vis_default_editor/public';
|
||||
import { VisParams } from '../types';
|
||||
import { VisType, VisTypeOptions } from './types';
|
||||
|
||||
interface CommonBaseVisTypeOptions {
|
||||
name: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
getSupportedTriggers?: () => Array<keyof TriggerContextMapping>;
|
||||
icon?: string;
|
||||
image?: string;
|
||||
stage?: 'experimental' | 'beta' | 'production';
|
||||
options?: Record<string, any>;
|
||||
visConfig?: Record<string, any>;
|
||||
editor?: any;
|
||||
editorConfig?: Record<string, any>;
|
||||
hidden?: boolean;
|
||||
requestHandler?: string | unknown;
|
||||
responseHandler?: string | unknown;
|
||||
hierarchicalData?: boolean | unknown;
|
||||
setup?: unknown;
|
||||
useCustomNoDataScreen?: boolean;
|
||||
inspectorAdapters?: Adapters | (() => Adapters);
|
||||
isDeprecated?: boolean;
|
||||
getInfoMessage?: (vis: Vis) => ReactElement<{}> | null;
|
||||
interface CommonBaseVisTypeOptions<TVisParams>
|
||||
extends Pick<
|
||||
VisType<TVisParams>,
|
||||
| 'description'
|
||||
| 'editor'
|
||||
| 'getInfoMessage'
|
||||
| 'getSupportedTriggers'
|
||||
| 'hierarchicalData'
|
||||
| 'icon'
|
||||
| 'image'
|
||||
| 'inspectorAdapters'
|
||||
| 'name'
|
||||
| 'requestHandler'
|
||||
| 'responseHandler'
|
||||
| 'setup'
|
||||
| 'title'
|
||||
>,
|
||||
Pick<
|
||||
Partial<VisType<TVisParams>>,
|
||||
'editorConfig' | 'hidden' | 'stage' | 'useCustomNoDataScreen' | 'visConfig'
|
||||
> {
|
||||
options?: Partial<VisType<TVisParams>['options']>;
|
||||
}
|
||||
|
||||
interface ExpressionBaseVisTypeOptions<TVisParams> extends CommonBaseVisTypeOptions {
|
||||
toExpressionAst: VisToExpressionAst<TVisParams>;
|
||||
interface ExpressionBaseVisTypeOptions<TVisParams> extends CommonBaseVisTypeOptions<TVisParams> {
|
||||
toExpressionAst: VisType<TVisParams>['toExpressionAst'];
|
||||
visualization?: undefined;
|
||||
}
|
||||
|
||||
interface VisualizationBaseVisTypeOptions extends CommonBaseVisTypeOptions {
|
||||
interface VisualizationBaseVisTypeOptions<TVisParams> extends CommonBaseVisTypeOptions<TVisParams> {
|
||||
toExpressionAst?: undefined;
|
||||
visualization: VisualizationControllerConstructor | undefined;
|
||||
visualization: VisType<TVisParams>['visualization'];
|
||||
}
|
||||
|
||||
export type BaseVisTypeOptions<TVisParams = VisParams> =
|
||||
| ExpressionBaseVisTypeOptions<TVisParams>
|
||||
| VisualizationBaseVisTypeOptions;
|
||||
| VisualizationBaseVisTypeOptions<TVisParams>;
|
||||
|
||||
export class BaseVisType<TVisParams = VisParams> {
|
||||
name: string;
|
||||
title: string;
|
||||
description: string;
|
||||
getSupportedTriggers?: () => Array<keyof TriggerContextMapping>;
|
||||
icon?: string;
|
||||
image?: string;
|
||||
stage: 'experimental' | 'beta' | 'production';
|
||||
isExperimental: boolean;
|
||||
options: Record<string, any>;
|
||||
visualization: VisualizationControllerConstructor | undefined;
|
||||
visConfig: Record<string, any>;
|
||||
editor: any;
|
||||
editorConfig: Record<string, any>;
|
||||
hidden: boolean;
|
||||
requiresSearch: boolean;
|
||||
requestHandler: string | unknown;
|
||||
responseHandler: string | unknown;
|
||||
hierarchicalData: boolean | unknown;
|
||||
setup?: unknown;
|
||||
useCustomNoDataScreen: boolean;
|
||||
inspectorAdapters?: Adapters | (() => Adapters);
|
||||
toExpressionAst?: VisToExpressionAst<TVisParams>;
|
||||
getInfoMessage?: (vis: Vis) => ReactElement<{}> | null;
|
||||
const defaultOptions: VisTypeOptions = {
|
||||
showTimePicker: true,
|
||||
showQueryBar: true,
|
||||
showFilterBar: true,
|
||||
showIndexSelection: true,
|
||||
hierarchicalData: false, // we should get rid of this i guess ?
|
||||
};
|
||||
|
||||
export class BaseVisType<TVisParams = VisParams> implements VisType<TVisParams> {
|
||||
public readonly name;
|
||||
public readonly title;
|
||||
public readonly description;
|
||||
public readonly getSupportedTriggers;
|
||||
public readonly icon;
|
||||
public readonly image;
|
||||
public readonly stage;
|
||||
public readonly options;
|
||||
public readonly visualization;
|
||||
public readonly visConfig;
|
||||
public readonly editor;
|
||||
public readonly editorConfig;
|
||||
public hidden;
|
||||
public readonly requestHandler;
|
||||
public readonly responseHandler;
|
||||
public readonly hierarchicalData;
|
||||
public readonly setup;
|
||||
public readonly useCustomNoDataScreen;
|
||||
public readonly inspectorAdapters;
|
||||
public readonly toExpressionAst;
|
||||
public readonly getInfoMessage;
|
||||
|
||||
constructor(opts: BaseVisTypeOptions<TVisParams>) {
|
||||
if (!opts.icon && !opts.image) {
|
||||
throw new Error('vis_type must define its icon or image');
|
||||
}
|
||||
|
||||
const defaultOptions = {
|
||||
// controls the visualize editor
|
||||
showTimePicker: true,
|
||||
showQueryBar: true,
|
||||
showFilterBar: true,
|
||||
showIndexSelection: true,
|
||||
hierarchicalData: false, // we should get rid of this i guess ?
|
||||
};
|
||||
|
||||
this.name = opts.name;
|
||||
this.description = opts.description || '';
|
||||
this.description = opts.description ?? '';
|
||||
this.getSupportedTriggers = opts.getSupportedTriggers;
|
||||
this.title = opts.title;
|
||||
this.icon = opts.icon;
|
||||
this.image = opts.image;
|
||||
this.visualization = opts.visualization;
|
||||
this.visConfig = _.defaultsDeep({}, opts.visConfig, { defaults: {} });
|
||||
this.visConfig = defaultsDeep({}, opts.visConfig, { defaults: {} });
|
||||
this.editor = opts.editor;
|
||||
this.editorConfig = _.defaultsDeep({}, opts.editorConfig, { collections: {} });
|
||||
this.options = _.defaultsDeep({}, opts.options, defaultOptions);
|
||||
this.stage = opts.stage || 'production';
|
||||
this.isExperimental = opts.stage === 'experimental';
|
||||
this.hidden = opts.hidden || false;
|
||||
this.requestHandler = opts.requestHandler || 'courier';
|
||||
this.responseHandler = opts.responseHandler || 'none';
|
||||
this.editorConfig = defaultsDeep({}, opts.editorConfig, { collections: {} });
|
||||
this.options = defaultsDeep({}, opts.options, defaultOptions);
|
||||
this.stage = opts.stage ?? 'production';
|
||||
this.hidden = opts.hidden ?? false;
|
||||
this.requestHandler = opts.requestHandler ?? 'courier';
|
||||
this.responseHandler = opts.responseHandler ?? 'none';
|
||||
this.setup = opts.setup;
|
||||
this.requiresSearch = this.requestHandler !== 'none';
|
||||
this.hierarchicalData = opts.hierarchicalData || false;
|
||||
this.useCustomNoDataScreen = opts.useCustomNoDataScreen || false;
|
||||
this.hierarchicalData = opts.hierarchicalData ?? false;
|
||||
this.useCustomNoDataScreen = opts.useCustomNoDataScreen ?? false;
|
||||
this.inspectorAdapters = opts.inspectorAdapters;
|
||||
this.toExpressionAst = opts.toExpressionAst;
|
||||
this.getInfoMessage = opts.getInfoMessage;
|
||||
}
|
||||
|
||||
public get schemas() {
|
||||
if (this.editorConfig && this.editorConfig.schemas) {
|
||||
return this.editorConfig.schemas;
|
||||
}
|
||||
return [];
|
||||
public get schemas(): ISchemas {
|
||||
return this.editorConfig?.schemas ?? [];
|
||||
}
|
||||
|
||||
public get requiresSearch(): boolean {
|
||||
return this.requestHandler !== 'none';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,5 +18,6 @@
|
|||
*/
|
||||
|
||||
export * from './types_service';
|
||||
export { VisType } from './types';
|
||||
export type { BaseVisTypeOptions } from './base_vis_type';
|
||||
export type { ReactVisTypeOptions } from './react_vis_type';
|
||||
|
|
|
@ -19,15 +19,21 @@
|
|||
|
||||
import { BaseVisType, BaseVisTypeOptions } from './base_vis_type';
|
||||
import { ReactVisController } from './react_vis_controller';
|
||||
import { VisType } from './types';
|
||||
|
||||
export type ReactVisTypeOptions = Omit<BaseVisTypeOptions, 'visualization' | 'toExpressionAst'>;
|
||||
export type ReactVisTypeOptions<TVisParams> = Omit<
|
||||
BaseVisTypeOptions<TVisParams>,
|
||||
'visualization' | 'toExpressionAst'
|
||||
>;
|
||||
|
||||
/**
|
||||
* This class should only be used for visualizations not using the `toExpressionAst` with a custom renderer.
|
||||
* If you implement a custom renderer you should just mount a react component inside this.
|
||||
*/
|
||||
export class ReactVisType extends BaseVisType {
|
||||
constructor(opts: ReactVisTypeOptions) {
|
||||
export class ReactVisType<TVisParams>
|
||||
extends BaseVisType<TVisParams>
|
||||
implements VisType<TVisParams> {
|
||||
constructor(opts: ReactVisTypeOptions<TVisParams>) {
|
||||
super({
|
||||
...opts,
|
||||
visualization: ReactVisController,
|
||||
|
|
80
src/plugins/visualizations/public/vis_types/types.ts
Normal file
80
src/plugins/visualizations/public/vis_types/types.ts
Normal file
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { IconType } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { Adapters } from 'src/plugins/inspector';
|
||||
import { ISchemas } from 'src/plugins/vis_default_editor/public';
|
||||
import { TriggerContextMapping } from '../../../ui_actions/public';
|
||||
import { Vis, VisToExpressionAst, VisualizationControllerConstructor } from '../types';
|
||||
|
||||
export interface VisTypeOptions {
|
||||
showTimePicker: boolean;
|
||||
showQueryBar: boolean;
|
||||
showFilterBar: boolean;
|
||||
showIndexSelection: boolean;
|
||||
hierarchicalData: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* A visualization type representing one specific type of "classical"
|
||||
* visualizations (i.e. not Lens visualizations).
|
||||
*/
|
||||
export interface VisType<TVisParams = unknown> {
|
||||
readonly name: string;
|
||||
readonly title: string;
|
||||
readonly description?: string;
|
||||
readonly getSupportedTriggers?: () => Array<keyof TriggerContextMapping>;
|
||||
readonly isAccessible?: boolean;
|
||||
readonly requestHandler?: string | unknown;
|
||||
readonly responseHandler?: string | unknown;
|
||||
readonly icon?: IconType;
|
||||
readonly image?: string;
|
||||
readonly stage: 'experimental' | 'beta' | 'production';
|
||||
readonly requiresSearch: boolean;
|
||||
readonly useCustomNoDataScreen: boolean;
|
||||
readonly hierarchicalData?: boolean | ((vis: { params: TVisParams }) => boolean);
|
||||
readonly inspectorAdapters?: Adapters | (() => Adapters);
|
||||
/**
|
||||
* When specified this visualization is deprecated. This function
|
||||
* should return a ReactElement that will render a deprecation warning.
|
||||
* It will be shown in the editor when editing/creating visualizations
|
||||
* of this type.
|
||||
*/
|
||||
readonly getInfoMessage?: (vis: Vis) => React.ReactNode;
|
||||
|
||||
readonly toExpressionAst?: VisToExpressionAst<TVisParams>;
|
||||
readonly visualization?: VisualizationControllerConstructor;
|
||||
|
||||
readonly setup?: (vis: Vis<TVisParams>) => Promise<Vis<TVisParams>>;
|
||||
hidden: boolean;
|
||||
|
||||
readonly schemas: ISchemas;
|
||||
|
||||
readonly options: VisTypeOptions;
|
||||
|
||||
// TODO: The following types still need to be refined properly.
|
||||
|
||||
/**
|
||||
* The editor that should be used to edit visualizations of this type.
|
||||
*/
|
||||
readonly editor?: any;
|
||||
readonly editorConfig: Record<string, any>;
|
||||
readonly visConfig: Record<string, any>;
|
||||
}
|
|
@ -17,33 +17,10 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { IconType } from '@elastic/eui';
|
||||
import { visTypeAliasRegistry, VisTypeAlias } from './vis_type_alias_registry';
|
||||
import { BaseVisType, BaseVisTypeOptions } from './base_vis_type';
|
||||
import { ReactVisType, ReactVisTypeOptions } from './react_vis_type';
|
||||
import { TriggerContextMapping } from '../../../ui_actions/public';
|
||||
|
||||
export interface VisType {
|
||||
name: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
getSupportedTriggers?: () => Array<keyof TriggerContextMapping>;
|
||||
visualization: any;
|
||||
isAccessible?: boolean;
|
||||
requestHandler: string | unknown;
|
||||
responseHandler: string | unknown;
|
||||
icon?: IconType;
|
||||
image?: string;
|
||||
stage: 'experimental' | 'beta' | 'production';
|
||||
requiresSearch: boolean;
|
||||
hidden: boolean;
|
||||
|
||||
// Since we haven't typed everything here yet, we basically "any" the rest
|
||||
// of that interface. This should be removed as soon as this type definition
|
||||
// has been completed. But that way we at least have typing for a couple of
|
||||
// properties on that type.
|
||||
[key: string]: any;
|
||||
}
|
||||
import { VisType } from './types';
|
||||
|
||||
/**
|
||||
* Vis Types Service
|
||||
|
@ -51,21 +28,21 @@ export interface VisType {
|
|||
* @internal
|
||||
*/
|
||||
export class TypesService {
|
||||
private types: Record<string, VisType> = {};
|
||||
private types: Record<string, VisType<any>> = {};
|
||||
private unregisteredHiddenTypes: string[] = [];
|
||||
|
||||
public setup() {
|
||||
const registerVisualization = (registerFn: () => VisType) => {
|
||||
const visDefinition = registerFn();
|
||||
if (this.unregisteredHiddenTypes.includes(visDefinition.name)) {
|
||||
visDefinition.hidden = true;
|
||||
}
|
||||
private registerVisualization<TVisParam>(visDefinition: VisType<TVisParam>) {
|
||||
if (this.unregisteredHiddenTypes.includes(visDefinition.name)) {
|
||||
visDefinition.hidden = true;
|
||||
}
|
||||
|
||||
if (this.types[visDefinition.name]) {
|
||||
throw new Error('type already exists!');
|
||||
}
|
||||
this.types[visDefinition.name] = visDefinition;
|
||||
};
|
||||
if (this.types[visDefinition.name]) {
|
||||
throw new Error('type already exists!');
|
||||
}
|
||||
this.types[visDefinition.name] = visDefinition;
|
||||
}
|
||||
|
||||
public setup() {
|
||||
return {
|
||||
/**
|
||||
* registers a visualization type
|
||||
|
@ -73,15 +50,15 @@ export class TypesService {
|
|||
*/
|
||||
createBaseVisualization: <TVisParams>(config: BaseVisTypeOptions<TVisParams>): void => {
|
||||
const vis = new BaseVisType(config);
|
||||
registerVisualization(() => vis);
|
||||
this.registerVisualization(vis);
|
||||
},
|
||||
/**
|
||||
* registers a visualization which uses react for rendering
|
||||
* @param config - visualization type definition
|
||||
*/
|
||||
createReactVisualization: (config: ReactVisTypeOptions): void => {
|
||||
createReactVisualization: <TVisParams>(config: ReactVisTypeOptions<TVisParams>): void => {
|
||||
const vis = new ReactVisType(config);
|
||||
registerVisualization(() => vis);
|
||||
this.registerVisualization(vis);
|
||||
},
|
||||
/**
|
||||
* registers a visualization alias
|
||||
|
@ -93,7 +70,7 @@ export class TypesService {
|
|||
* allows to hide specific visualization types from create visualization dialog
|
||||
* @param {string[]} typeNames - list of type ids to hide
|
||||
*/
|
||||
hideTypes: (typeNames: string[]) => {
|
||||
hideTypes: (typeNames: string[]): void => {
|
||||
typeNames.forEach((name: string) => {
|
||||
if (this.types[name]) {
|
||||
this.types[name].hidden = true;
|
||||
|
@ -111,13 +88,13 @@ export class TypesService {
|
|||
* returns specific visualization or undefined if not found
|
||||
* @param {string} visualization - id of visualization to return
|
||||
*/
|
||||
get: (visualization: string) => {
|
||||
get: <TVisParams>(visualization: string): VisType<TVisParams> => {
|
||||
return this.types[visualization];
|
||||
},
|
||||
/**
|
||||
* returns all registered visualization types
|
||||
*/
|
||||
all: () => {
|
||||
all: (): VisType[] => {
|
||||
return [...Object.values(this.types)];
|
||||
},
|
||||
/**
|
||||
|
|
|
@ -238,12 +238,43 @@ exports[`NewVisModal filter for visualization types should render as expected 1`
|
|||
aria-live="polite"
|
||||
class="euiScreenReaderOnly"
|
||||
>
|
||||
2 types found
|
||||
3 types found
|
||||
</span>
|
||||
<ul
|
||||
class="euiKeyPadMenu visNewVisDialog__types"
|
||||
data-test-subj="visNewDialogTypes"
|
||||
>
|
||||
<li>
|
||||
<button
|
||||
aria-describedby="visTypeDescription-visAliasWithPromotion"
|
||||
class="euiKeyPadMenuItem visNewVisDialog__type"
|
||||
data-test-subj="visType-visAliasWithPromotion"
|
||||
data-vis-stage="alias"
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
class="euiKeyPadMenuItem__inner"
|
||||
>
|
||||
<div
|
||||
class="euiKeyPadMenuItem__icon"
|
||||
>
|
||||
<div
|
||||
color="secondary"
|
||||
data-euiicon-type="empty"
|
||||
/>
|
||||
</div>
|
||||
<p
|
||||
class="euiKeyPadMenuItem__label"
|
||||
>
|
||||
<span
|
||||
data-test-subj="visTypeTitle"
|
||||
>
|
||||
Vis alias with promotion
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button
|
||||
aria-describedby="visTypeDescription-visWithAliasUrl"
|
||||
|
@ -359,6 +390,29 @@ exports[`NewVisModal filter for visualization types should render as expected 1`
|
|||
<p>
|
||||
Start creating your visualization by selecting a type for that visualization.
|
||||
</p>
|
||||
<p>
|
||||
<strong>
|
||||
promotion description
|
||||
</strong>
|
||||
</p>
|
||||
<button
|
||||
class="euiButton euiButton--primary euiButton--small euiButton--fill"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="euiButtonContent euiButtonContent--iconRight euiButton__content"
|
||||
>
|
||||
<div
|
||||
class="euiButtonContent__icon"
|
||||
data-euiicon-type="popout"
|
||||
/>
|
||||
<span
|
||||
class="euiButton__text"
|
||||
>
|
||||
another app
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -605,11 +659,11 @@ exports[`NewVisModal filter for visualization types should render as expected 1`
|
|||
id="visualizations.newVisWizard.resultsFound"
|
||||
values={
|
||||
Object {
|
||||
"resultCount": 2,
|
||||
"resultCount": 3,
|
||||
}
|
||||
}
|
||||
>
|
||||
2 types found
|
||||
3 types found
|
||||
</FormattedMessage>
|
||||
</span>
|
||||
</EuiScreenReaderOnly>
|
||||
|
@ -621,6 +675,75 @@ exports[`NewVisModal filter for visualization types should render as expected 1`
|
|||
className="euiKeyPadMenu visNewVisDialog__types"
|
||||
data-test-subj="visNewDialogTypes"
|
||||
>
|
||||
<li
|
||||
key=".$visAliasWithPromotion"
|
||||
>
|
||||
<EuiKeyPadMenuItem
|
||||
aria-describedby="visTypeDescription-visAliasWithPromotion"
|
||||
className="visNewVisDialog__type"
|
||||
data-test-subj="visType-visAliasWithPromotion"
|
||||
data-vis-stage="alias"
|
||||
disabled={false}
|
||||
key="visAliasWithPromotion"
|
||||
label={
|
||||
<span
|
||||
data-test-subj="visTypeTitle"
|
||||
>
|
||||
Vis alias with promotion
|
||||
</span>
|
||||
}
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseEnter={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
>
|
||||
<button
|
||||
aria-describedby="visTypeDescription-visAliasWithPromotion"
|
||||
className="euiKeyPadMenuItem visNewVisDialog__type"
|
||||
data-test-subj="visType-visAliasWithPromotion"
|
||||
data-vis-stage="alias"
|
||||
disabled={false}
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseEnter={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
className="euiKeyPadMenuItem__inner"
|
||||
>
|
||||
<div
|
||||
className="euiKeyPadMenuItem__icon"
|
||||
>
|
||||
<VisTypeIcon>
|
||||
<EuiIcon
|
||||
color="secondary"
|
||||
size="l"
|
||||
type="empty"
|
||||
>
|
||||
<div
|
||||
color="secondary"
|
||||
data-euiicon-type="empty"
|
||||
size="l"
|
||||
/>
|
||||
</EuiIcon>
|
||||
</VisTypeIcon>
|
||||
</div>
|
||||
<p
|
||||
className="euiKeyPadMenuItem__label"
|
||||
>
|
||||
<span
|
||||
data-test-subj="visTypeTitle"
|
||||
>
|
||||
Vis alias with promotion
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
</EuiKeyPadMenuItem>
|
||||
</li>
|
||||
<li
|
||||
key=".$visWithAliasUrl"
|
||||
>
|
||||
|
@ -867,7 +990,21 @@ exports[`NewVisModal filter for visualization types should render as expected 1`
|
|||
</EuiSpacer>
|
||||
<NewVisHelp
|
||||
onPromotionClicked={[Function]}
|
||||
promotedTypes={Array []}
|
||||
promotedTypes={
|
||||
Array [
|
||||
Object {
|
||||
"aliasApp": "anotherApp",
|
||||
"aliasPath": "#/anotherUrl",
|
||||
"name": "visAliasWithPromotion",
|
||||
"promotion": Object {
|
||||
"buttonText": "another app",
|
||||
"description": "promotion description",
|
||||
},
|
||||
"stage": "production",
|
||||
"title": "Vis alias with promotion",
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<EuiText>
|
||||
<div
|
||||
|
@ -882,6 +1019,66 @@ exports[`NewVisModal filter for visualization types should render as expected 1`
|
|||
Start creating your visualization by selecting a type for that visualization.
|
||||
</FormattedMessage>
|
||||
</p>
|
||||
<p>
|
||||
<strong>
|
||||
promotion description
|
||||
</strong>
|
||||
</p>
|
||||
<EuiButton
|
||||
fill={true}
|
||||
iconSide="right"
|
||||
iconType="popout"
|
||||
onClick={[Function]}
|
||||
size="s"
|
||||
>
|
||||
<EuiButtonDisplay
|
||||
element="button"
|
||||
fill={true}
|
||||
iconSide="right"
|
||||
iconType="popout"
|
||||
onClick={[Function]}
|
||||
size="s"
|
||||
type="button"
|
||||
>
|
||||
<button
|
||||
className="euiButton euiButton--primary euiButton--small euiButton--fill"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<EuiButtonContent
|
||||
className="euiButton__content"
|
||||
iconSide="right"
|
||||
iconType="popout"
|
||||
textProps={
|
||||
Object {
|
||||
"className": "euiButton__text",
|
||||
}
|
||||
}
|
||||
>
|
||||
<span
|
||||
className="euiButtonContent euiButtonContent--iconRight euiButton__content"
|
||||
>
|
||||
<EuiIcon
|
||||
className="euiButtonContent__icon"
|
||||
size="m"
|
||||
type="popout"
|
||||
>
|
||||
<div
|
||||
className="euiButtonContent__icon"
|
||||
data-euiicon-type="popout"
|
||||
size="m"
|
||||
/>
|
||||
</EuiIcon>
|
||||
<span
|
||||
className="euiButton__text"
|
||||
>
|
||||
another app
|
||||
</span>
|
||||
</span>
|
||||
</EuiButtonContent>
|
||||
</button>
|
||||
</EuiButtonDisplay>
|
||||
</EuiButton>
|
||||
</div>
|
||||
</EuiText>
|
||||
</NewVisHelp>
|
||||
|
@ -1129,6 +1326,37 @@ exports[`NewVisModal should render as expected 1`] = `
|
|||
class="euiKeyPadMenu visNewVisDialog__types"
|
||||
data-test-subj="visNewDialogTypes"
|
||||
>
|
||||
<li>
|
||||
<button
|
||||
aria-describedby="visTypeDescription-visAliasWithPromotion"
|
||||
class="euiKeyPadMenuItem visNewVisDialog__type"
|
||||
data-test-subj="visType-visAliasWithPromotion"
|
||||
data-vis-stage="alias"
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
class="euiKeyPadMenuItem__inner"
|
||||
>
|
||||
<div
|
||||
class="euiKeyPadMenuItem__icon"
|
||||
>
|
||||
<div
|
||||
color="secondary"
|
||||
data-euiicon-type="empty"
|
||||
/>
|
||||
</div>
|
||||
<p
|
||||
class="euiKeyPadMenuItem__label"
|
||||
>
|
||||
<span
|
||||
data-test-subj="visTypeTitle"
|
||||
>
|
||||
Vis alias with promotion
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button
|
||||
aria-describedby="visTypeDescription-vis"
|
||||
|
@ -1243,6 +1471,29 @@ exports[`NewVisModal should render as expected 1`] = `
|
|||
<p>
|
||||
Start creating your visualization by selecting a type for that visualization.
|
||||
</p>
|
||||
<p>
|
||||
<strong>
|
||||
promotion description
|
||||
</strong>
|
||||
</p>
|
||||
<button
|
||||
class="euiButton euiButton--primary euiButton--small euiButton--fill"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="euiButtonContent euiButtonContent--iconRight euiButton__content"
|
||||
>
|
||||
<div
|
||||
class="euiButtonContent__icon"
|
||||
data-euiicon-type="popout"
|
||||
/>
|
||||
<span
|
||||
class="euiButton__text"
|
||||
>
|
||||
another app
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1454,6 +1705,75 @@ exports[`NewVisModal should render as expected 1`] = `
|
|||
className="euiKeyPadMenu visNewVisDialog__types"
|
||||
data-test-subj="visNewDialogTypes"
|
||||
>
|
||||
<li
|
||||
key=".$visAliasWithPromotion"
|
||||
>
|
||||
<EuiKeyPadMenuItem
|
||||
aria-describedby="visTypeDescription-visAliasWithPromotion"
|
||||
className="visNewVisDialog__type"
|
||||
data-test-subj="visType-visAliasWithPromotion"
|
||||
data-vis-stage="alias"
|
||||
disabled={false}
|
||||
key="visAliasWithPromotion"
|
||||
label={
|
||||
<span
|
||||
data-test-subj="visTypeTitle"
|
||||
>
|
||||
Vis alias with promotion
|
||||
</span>
|
||||
}
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseEnter={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
>
|
||||
<button
|
||||
aria-describedby="visTypeDescription-visAliasWithPromotion"
|
||||
className="euiKeyPadMenuItem visNewVisDialog__type"
|
||||
data-test-subj="visType-visAliasWithPromotion"
|
||||
data-vis-stage="alias"
|
||||
disabled={false}
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseEnter={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
className="euiKeyPadMenuItem__inner"
|
||||
>
|
||||
<div
|
||||
className="euiKeyPadMenuItem__icon"
|
||||
>
|
||||
<VisTypeIcon>
|
||||
<EuiIcon
|
||||
color="secondary"
|
||||
size="l"
|
||||
type="empty"
|
||||
>
|
||||
<div
|
||||
color="secondary"
|
||||
data-euiicon-type="empty"
|
||||
size="l"
|
||||
/>
|
||||
</EuiIcon>
|
||||
</VisTypeIcon>
|
||||
</div>
|
||||
<p
|
||||
className="euiKeyPadMenuItem__label"
|
||||
>
|
||||
<span
|
||||
data-test-subj="visTypeTitle"
|
||||
>
|
||||
Vis alias with promotion
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
</EuiKeyPadMenuItem>
|
||||
</li>
|
||||
<li
|
||||
key=".$vis"
|
||||
>
|
||||
|
@ -1700,7 +2020,21 @@ exports[`NewVisModal should render as expected 1`] = `
|
|||
</EuiSpacer>
|
||||
<NewVisHelp
|
||||
onPromotionClicked={[Function]}
|
||||
promotedTypes={Array []}
|
||||
promotedTypes={
|
||||
Array [
|
||||
Object {
|
||||
"aliasApp": "anotherApp",
|
||||
"aliasPath": "#/anotherUrl",
|
||||
"name": "visAliasWithPromotion",
|
||||
"promotion": Object {
|
||||
"buttonText": "another app",
|
||||
"description": "promotion description",
|
||||
},
|
||||
"stage": "production",
|
||||
"title": "Vis alias with promotion",
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<EuiText>
|
||||
<div
|
||||
|
@ -1715,6 +2049,66 @@ exports[`NewVisModal should render as expected 1`] = `
|
|||
Start creating your visualization by selecting a type for that visualization.
|
||||
</FormattedMessage>
|
||||
</p>
|
||||
<p>
|
||||
<strong>
|
||||
promotion description
|
||||
</strong>
|
||||
</p>
|
||||
<EuiButton
|
||||
fill={true}
|
||||
iconSide="right"
|
||||
iconType="popout"
|
||||
onClick={[Function]}
|
||||
size="s"
|
||||
>
|
||||
<EuiButtonDisplay
|
||||
element="button"
|
||||
fill={true}
|
||||
iconSide="right"
|
||||
iconType="popout"
|
||||
onClick={[Function]}
|
||||
size="s"
|
||||
type="button"
|
||||
>
|
||||
<button
|
||||
className="euiButton euiButton--primary euiButton--small euiButton--fill"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<EuiButtonContent
|
||||
className="euiButton__content"
|
||||
iconSide="right"
|
||||
iconType="popout"
|
||||
textProps={
|
||||
Object {
|
||||
"className": "euiButton__text",
|
||||
}
|
||||
}
|
||||
>
|
||||
<span
|
||||
className="euiButtonContent euiButtonContent--iconRight euiButton__content"
|
||||
>
|
||||
<EuiIcon
|
||||
className="euiButtonContent__icon"
|
||||
size="m"
|
||||
type="popout"
|
||||
>
|
||||
<div
|
||||
className="euiButtonContent__icon"
|
||||
data-euiicon-type="popout"
|
||||
size="m"
|
||||
/>
|
||||
</EuiIcon>
|
||||
<span
|
||||
className="euiButton__text"
|
||||
>
|
||||
another app
|
||||
</span>
|
||||
</span>
|
||||
</EuiButtonContent>
|
||||
</button>
|
||||
</EuiButtonDisplay>
|
||||
</EuiButton>
|
||||
</div>
|
||||
</EuiText>
|
||||
</NewVisHelp>
|
||||
|
|
|
@ -51,13 +51,24 @@ describe('NewVisModal', () => {
|
|||
aliasApp: 'otherApp',
|
||||
aliasPath: '#/aliasUrl',
|
||||
},
|
||||
{
|
||||
name: 'visAliasWithPromotion',
|
||||
title: 'Vis alias with promotion',
|
||||
stage: 'production',
|
||||
aliasApp: 'anotherApp',
|
||||
aliasPath: '#/anotherUrl',
|
||||
promotion: {
|
||||
description: 'promotion description',
|
||||
buttonText: 'another app',
|
||||
},
|
||||
},
|
||||
];
|
||||
const visTypes: TypesStart = {
|
||||
get: (id: string) => {
|
||||
return _visTypes.find((vis) => vis.name === id) as VisType;
|
||||
get<T>(id: string): VisType<T> {
|
||||
return (_visTypes.find((vis) => vis.name === id) as unknown) as VisType<T>;
|
||||
},
|
||||
all: () => {
|
||||
return _visTypes as VisType[];
|
||||
return (_visTypes as unknown) as VisType[];
|
||||
},
|
||||
getAliases: () => [],
|
||||
};
|
||||
|
@ -107,6 +118,30 @@ describe('NewVisModal', () => {
|
|||
expect(wrapper.find('[data-test-subj="visType-vis"]').exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('should sort promoted visualizations first', () => {
|
||||
const wrapper = mountWithIntl(
|
||||
<NewVisModal
|
||||
isOpen={true}
|
||||
onClose={() => null}
|
||||
visTypesRegistry={visTypes}
|
||||
addBasePath={addBasePath}
|
||||
uiSettings={uiSettings}
|
||||
application={{} as ApplicationStart}
|
||||
savedObjects={{} as SavedObjectsStart}
|
||||
/>
|
||||
);
|
||||
expect(
|
||||
wrapper
|
||||
.find('button[data-test-subj^="visType-"]')
|
||||
.map((button) => button.prop('data-test-subj'))
|
||||
).toEqual([
|
||||
'visType-visAliasWithPromotion',
|
||||
'visType-vis',
|
||||
'visType-visWithAliasUrl',
|
||||
'visType-visWithSearch',
|
||||
]);
|
||||
});
|
||||
|
||||
describe('open editor', () => {
|
||||
it('should open the editor for visualizations without search', () => {
|
||||
const wrapper = mountWithIntl(
|
||||
|
|
|
@ -31,7 +31,6 @@ describe('NewVisHelp', () => {
|
|||
aliasApp: 'myApp',
|
||||
aliasPath: '/my/fancy/new/thing',
|
||||
description: 'Some desc',
|
||||
highlighted: false,
|
||||
icon: 'whatever',
|
||||
name: 'whatever',
|
||||
promotion: {
|
||||
|
|
|
@ -20,11 +20,10 @@
|
|||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import React, { Fragment } from 'react';
|
||||
import { EuiText, EuiButton } from '@elastic/eui';
|
||||
import { VisTypeAliasListEntry } from './type_selection';
|
||||
import { VisTypeAlias } from '../../vis_types';
|
||||
|
||||
interface Props {
|
||||
promotedTypes: VisTypeAliasListEntry[];
|
||||
promotedTypes: VisTypeAlias[];
|
||||
onPromotionClicked: (visType: VisTypeAlias) => void;
|
||||
}
|
||||
|
||||
|
|
|
@ -42,11 +42,8 @@ import { VisHelpText } from './vis_help_text';
|
|||
import { VisTypeIcon } from './vis_type_icon';
|
||||
import { VisType, TypesStart } from '../../vis_types';
|
||||
|
||||
export interface VisTypeListEntry extends VisType {
|
||||
highlighted: boolean;
|
||||
}
|
||||
|
||||
export interface VisTypeAliasListEntry extends VisTypeAlias {
|
||||
interface VisTypeListEntry {
|
||||
type: VisType | VisTypeAlias;
|
||||
highlighted: boolean;
|
||||
}
|
||||
|
||||
|
@ -69,6 +66,10 @@ interface TypeSelectionState {
|
|||
query: string;
|
||||
}
|
||||
|
||||
function isVisTypeAlias(type: VisType | VisTypeAlias): type is VisTypeAlias {
|
||||
return 'aliasPath' in type;
|
||||
}
|
||||
|
||||
class TypeSelection extends React.Component<TypeSelectionProps, TypeSelectionState> {
|
||||
public state: TypeSelectionState = {
|
||||
highlightedType: null,
|
||||
|
@ -155,7 +156,9 @@ class TypeSelection extends React.Component<TypeSelectionProps, TypeSelectionSta
|
|||
</EuiTitle>
|
||||
<EuiSpacer size="m" />
|
||||
<NewVisHelp
|
||||
promotedTypes={(visTypes as VisTypeAliasListEntry[]).filter((t) => t.promotion)}
|
||||
promotedTypes={visTypes
|
||||
.map((t) => t.type)
|
||||
.filter((t): t is VisTypeAlias => isVisTypeAlias(t) && Boolean(t.promotion))}
|
||||
onPromotionClicked={this.props.onVisTypeSelected}
|
||||
/>
|
||||
</React.Fragment>
|
||||
|
@ -167,10 +170,7 @@ class TypeSelection extends React.Component<TypeSelectionProps, TypeSelectionSta
|
|||
);
|
||||
}
|
||||
|
||||
private filteredVisTypes(
|
||||
visTypes: TypesStart,
|
||||
query: string
|
||||
): Array<VisTypeListEntry | VisTypeAliasListEntry> {
|
||||
private filteredVisTypes(visTypes: TypesStart, query: string): VisTypeListEntry[] {
|
||||
const types = visTypes.all().filter((type) => {
|
||||
// Filter out all lab visualizations if lab mode is not enabled
|
||||
if (!this.props.showExperimental && type.stage === 'experimental') {
|
||||
|
@ -187,9 +187,9 @@ class TypeSelection extends React.Component<TypeSelectionProps, TypeSelectionSta
|
|||
|
||||
const allTypes = [...types, ...visTypes.getAliases()];
|
||||
|
||||
let entries: Array<VisTypeListEntry | VisTypeAliasListEntry>;
|
||||
let entries: VisTypeListEntry[];
|
||||
if (!query) {
|
||||
entries = allTypes.map((type) => ({ ...type, highlighted: false }));
|
||||
entries = allTypes.map((type) => ({ type, highlighted: false }));
|
||||
} else {
|
||||
const q = query.toLowerCase();
|
||||
entries = allTypes.map((type) => {
|
||||
|
@ -197,17 +197,21 @@ class TypeSelection extends React.Component<TypeSelectionProps, TypeSelectionSta
|
|||
type.name.toLowerCase().includes(q) ||
|
||||
type.title.toLowerCase().includes(q) ||
|
||||
(typeof type.description === 'string' && type.description.toLowerCase().includes(q));
|
||||
return { ...type, highlighted: matchesQuery };
|
||||
return { type, highlighted: matchesQuery };
|
||||
});
|
||||
}
|
||||
|
||||
return orderBy(entries, ['highlighted', 'promotion', 'title'], ['desc', 'asc', 'asc']);
|
||||
return orderBy(
|
||||
entries,
|
||||
['highlighted', 'type.promotion', 'type.title'],
|
||||
['desc', 'asc', 'asc']
|
||||
);
|
||||
}
|
||||
|
||||
private renderVisType = (visType: VisTypeListEntry | VisTypeAliasListEntry) => {
|
||||
private renderVisType = (visType: VisTypeListEntry) => {
|
||||
let stage = {};
|
||||
let highlightMsg;
|
||||
if (!('aliasPath' in visType) && visType.stage === 'experimental') {
|
||||
if (!isVisTypeAlias(visType.type) && visType.type.stage === 'experimental') {
|
||||
stage = {
|
||||
betaBadgeLabel: i18n.translate('visualizations.newVisWizard.experimentalTitle', {
|
||||
defaultMessage: 'Experimental',
|
||||
|
@ -221,7 +225,7 @@ class TypeSelection extends React.Component<TypeSelectionProps, TypeSelectionSta
|
|||
defaultMessage:
|
||||
'This visualization is experimental. The design and implementation are less mature than stable visualizations and might be subject to change.',
|
||||
});
|
||||
} else if ('aliasPath' in visType && visType.stage === 'beta') {
|
||||
} else if (isVisTypeAlias(visType.type) && visType.type.stage === 'beta') {
|
||||
const aliasDescription = i18n.translate('visualizations.newVisWizard.betaDescription', {
|
||||
defaultMessage:
|
||||
'This visualization is in beta and is subject to change. The design and code is less mature than official GA features and is being provided as-is with no warranties. Beta features are not subject to the support SLA of official GA features',
|
||||
|
@ -236,34 +240,34 @@ class TypeSelection extends React.Component<TypeSelectionProps, TypeSelectionSta
|
|||
}
|
||||
|
||||
const isDisabled = this.state.query !== '' && !visType.highlighted;
|
||||
const onClick = () => this.props.onVisTypeSelected(visType);
|
||||
const onClick = () => this.props.onVisTypeSelected(visType.type);
|
||||
|
||||
const highlightedType: HighlightedType = {
|
||||
title: visType.title,
|
||||
name: visType.name,
|
||||
description: visType.description,
|
||||
title: visType.type.title,
|
||||
name: visType.type.name,
|
||||
description: visType.type.description,
|
||||
highlightMsg,
|
||||
};
|
||||
|
||||
return (
|
||||
<EuiKeyPadMenuItem
|
||||
key={visType.name}
|
||||
label={<span data-test-subj="visTypeTitle">{visType.title}</span>}
|
||||
key={visType.type.name}
|
||||
label={<span data-test-subj="visTypeTitle">{visType.type.title}</span>}
|
||||
onClick={onClick}
|
||||
onFocus={() => this.setHighlightType(highlightedType)}
|
||||
onMouseEnter={() => this.setHighlightType(highlightedType)}
|
||||
onMouseLeave={() => this.setHighlightType(null)}
|
||||
onBlur={() => this.setHighlightType(null)}
|
||||
className="visNewVisDialog__type"
|
||||
data-test-subj={`visType-${visType.name}`}
|
||||
data-vis-stage={!('aliasPath' in visType) ? visType.stage : 'alias'}
|
||||
data-test-subj={`visType-${visType.type.name}`}
|
||||
data-vis-stage={!isVisTypeAlias(visType.type) ? visType.type.stage : 'alias'}
|
||||
disabled={isDisabled}
|
||||
aria-describedby={`visTypeDescription-${visType.name}`}
|
||||
aria-describedby={`visTypeDescription-${visType.type.name}`}
|
||||
{...stage}
|
||||
>
|
||||
<VisTypeIcon
|
||||
icon={visType.icon}
|
||||
image={!('aliasPath' in visType) ? visType.image : undefined}
|
||||
icon={visType.type.icon}
|
||||
image={'image' in visType.type ? visType.type.image : undefined}
|
||||
/>
|
||||
</EuiKeyPadMenuItem>
|
||||
);
|
||||
|
|
|
@ -78,7 +78,7 @@ export const VisualizeEditorCommon = ({
|
|||
embeddableId={embeddableId}
|
||||
/>
|
||||
)}
|
||||
{visInstance?.vis?.type?.isExperimental && <ExperimentalVisInfo />}
|
||||
{visInstance?.vis?.type?.stage === 'experimental' && <ExperimentalVisInfo />}
|
||||
{visInstance?.vis?.type?.getInfoMessage?.(visInstance.vis)}
|
||||
{visInstance && (
|
||||
<EuiScreenReaderOnly>
|
||||
|
|
Loading…
Reference in a new issue