Skip to content

Commit

Permalink
[TSVB] Custom renderer (#83554)
Browse files Browse the repository at this point in the history
* Implement custom renderer

* Remove legacy code

* Use custom expression

* Convert to typescript

* Remove savedObjectId extra param

* Other updates

* Fix types

* Cleanup

* Fix functional tests

* Bind uiSettings

* Update snapshot

* Update types

* Remove extra params

* Move common types

* Return back validation error message

* Use panel types enum

* Fix types

* Lazy load visualizations
  • Loading branch information
Daniil authored Nov 23, 2020
1 parent 378d89b commit bb023c5
Show file tree
Hide file tree
Showing 37 changed files with 446 additions and 402 deletions.
4 changes: 4 additions & 0 deletions src/plugins/vis_type_timeseries/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,7 @@

export const MAX_BUCKETS_SETTING = 'metrics:max_buckets';
export const INDEXES_SEPARATOR = ',';

export const ROUTES = {
VIS_DATA: '/api/metrics/vis/data',
};
16 changes: 8 additions & 8 deletions src/plugins/vis_type_timeseries/common/panel_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@
* under the License.
*/

export const PANEL_TYPES = {
TABLE: 'table',
GAUGE: 'gauge',
MARKDOWN: 'markdown',
TOP_N: 'top_n',
TIMESERIES: 'timeseries',
METRIC: 'metric',
};
export enum PANEL_TYPES {
TABLE = 'table',
GAUGE = 'gauge',
MARKDOWN = 'markdown',
TOP_N = 'top_n',
TIMESERIES = 'timeseries',
METRIC = 'metric',
}
29 changes: 29 additions & 0 deletions src/plugins/vis_type_timeseries/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,37 @@

import { TypeOf } from '@kbn/config-schema';
import { metricsItems, panel, seriesItems, visPayloadSchema } from './vis_schema';
import { PANEL_TYPES } from './panel_types';
import { TimeseriesUIRestrictions } from './ui_restrictions';

export type SeriesItemsSchema = TypeOf<typeof seriesItems>;
export type MetricsItemsSchema = TypeOf<typeof metricsItems>;
export type PanelSchema = TypeOf<typeof panel>;
export type VisPayload = TypeOf<typeof visPayloadSchema>;

interface PanelData {
id: string;
label: string;
data: Array<[number, number]>;
}

// series data is not fully typed yet
interface SeriesData {
[key: string]: {
annotations: {
[key: string]: unknown[];
};
id: string;
series: PanelData[];
error?: unknown;
};
}

export type TimeseriesVisData = SeriesData & {
type: PANEL_TYPES;
uiRestrictions: TimeseriesUIRestrictions;
/**
* series array is responsible only for "table" vis type
*/
series?: unknown[];
};
2 changes: 1 addition & 1 deletion src/plugins/vis_type_timeseries/common/ui_restrictions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export const DEFAULT_UI_RESTRICTION: UIRestrictions = {
* @constant
* @public
*/
export const limitOfSeries = {
export const limitOfSeries: Partial<Record<PANEL_TYPES, number>> = {
[PANEL_TYPES.GAUGE]: 1,
[PANEL_TYPES.METRIC]: 2,
};
10 changes: 8 additions & 2 deletions src/plugins/vis_type_timeseries/common/vis_schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,14 @@ export const panel = schema.object({
),
time_field: stringOptionalNullable,
time_range_mode: stringOptionalNullable,
type: stringRequired,
type: schema.oneOf([
schema.literal('table'),
schema.literal('gauge'),
schema.literal('markdown'),
schema.literal('top_n'),
schema.literal('timeseries'),
schema.literal('metric'),
]),
});

export const visPayloadSchema = schema.object({
Expand All @@ -267,7 +274,6 @@ export const visPayloadSchema = schema.object({
})
),
}),
savedObjectId: schema.maybe(schema.string()),
timerange: schema.object({
timezone: stringRequired,
min: stringRequired,
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/*
* 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 React, { useCallback, useEffect } from 'react';

import { IUiSettingsClient } from 'src/core/public';
import { IInterpreterRenderHandlers } from 'src/plugins/expressions';
import { PersistedState } from 'src/plugins/visualizations/public';

// @ts-expect-error
import { ErrorComponent } from './error';
import { TimeseriesVisTypes } from './vis_types';
import { TimeseriesVisParams } from '../../metrics_fn';
import { TimeseriesVisData } from '../../../common/types';

interface TimeseriesVisualizationProps {
className?: string;
getConfig: IUiSettingsClient['get'];
handlers: IInterpreterRenderHandlers;
model: TimeseriesVisParams;
visData: TimeseriesVisData;
uiState: PersistedState;
}

function TimeseriesVisualization({
className = 'tvbVis',
visData,
model,
handlers,
uiState,
getConfig,
}: TimeseriesVisualizationProps) {
const onBrush = useCallback(
(gte: string, lte: string) => {
handlers.event({
name: 'applyFilter',
data: {
timeFieldName: '*',
filters: [
{
range: {
'*': {
gte,
lte,
},
},
},
],
},
});
},
[handlers]
);

const handleUiState = useCallback(
(field: string, value: { column: string; order: string }) => {
uiState.set(field, value);
// reload visualization because data might need to be re-fetched
uiState.emit('reload');
},
[uiState]
);

useEffect(() => {
handlers.done();
});

// Show the error panel
const error = visData[model.id]?.error;
if (error) {
return (
<div className={className}>
<ErrorComponent error={error} />
</div>
);
}

const VisComponent = TimeseriesVisTypes[model.type];

if (VisComponent) {
return (
<VisComponent
dateFormat={getConfig('dateFormat')}
getConfig={getConfig}
model={model}
visData={visData}
uiState={uiState}
onBrush={onBrush}
onUiState={handleUiState}
/>
);
}

return <div className={className} />;
}

// default export required for React.Lazy
// eslint-disable-next-line import/no-default-export
export { TimeseriesVisualization as default };
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,8 @@ import * as Rx from 'rxjs';
import { share } from 'rxjs/operators';
import { isEqual, isEmpty, debounce } from 'lodash';
import { VisEditorVisualization } from './vis_editor_visualization';
import { Visualization } from './visualization';
import { VisPicker } from './vis_picker';
import { PanelConfig } from './panel_config';
import { createBrushHandler } from '../lib/create_brush_handler';
import { fetchFields } from '../lib/fetch_fields';
import { extractIndexPatterns } from '../../../common/extract_index_patterns';
import { getSavedObjectsClient, getUISettings, getDataStart, getCoreStart } from '../../services';
Expand All @@ -49,7 +47,6 @@ export class VisEditor extends Component {
visFields: props.visFields,
extractedIndexPatterns: [''],
};
this.onBrush = createBrushHandler((data) => props.vis.API.events.applyFilter(data));
this.visDataSubject = new Rx.BehaviorSubject(this.props.visData);
this.visData$ = this.visDataSubject.asObservable().pipe(share());

Expand All @@ -71,12 +68,6 @@ export class VisEditor extends Component {
return this.props.config.get(...args);
};

handleUiState = (field, value) => {
this.props.vis.uiState.set(field, value);
// reload visualization because data might need to be re-fetched
this.props.vis.uiState.emit('reload');
};

updateVisState = debounce(() => {
this.props.vis.params = this.state.model;
this.props.embeddableHandler.reload();
Expand All @@ -101,16 +92,14 @@ export class VisEditor extends Component {
dirty = false;
}

if (this.props.isEditorMode) {
const extractedIndexPatterns = extractIndexPatterns(nextModel);
if (!isEqual(this.state.extractedIndexPatterns, extractedIndexPatterns)) {
fetchFields(extractedIndexPatterns).then((visFields) =>
this.setState({
visFields,
extractedIndexPatterns,
})
);
}
const extractedIndexPatterns = extractIndexPatterns(nextModel);
if (!isEqual(this.state.extractedIndexPatterns, extractedIndexPatterns)) {
fetchFields(extractedIndexPatterns).then((visFields) =>
this.setState({
visFields,
extractedIndexPatterns,
})
);
}

this.setState({
Expand Down Expand Up @@ -141,23 +130,6 @@ export class VisEditor extends Component {
};

render() {
if (!this.props.isEditorMode) {
if (!this.props.visParams || !this.props.visData) {
return null;
}
return (
<Visualization
dateFormat={this.props.config.get('dateFormat')}
onBrush={this.onBrush}
onUiState={this.handleUiState}
uiState={this.uiState}
model={this.props.visParams}
visData={this.props.visData}
getConfig={this.getConfig}
/>
);
}

const { model } = this.state;

if (model) {
Expand Down Expand Up @@ -211,23 +183,12 @@ export class VisEditor extends Component {
}

componentDidMount() {
this.props.renderComplete();

if (this.props.isEditorMode && this.props.eventEmitter) {
this.props.eventEmitter.on('updateEditor', this.updateModel);
}
}

componentDidUpdate() {
this.props.renderComplete();
this.props.eventEmitter.on('updateEditor', this.updateModel);
}

componentWillUnmount() {
this.updateVisState.cancel();

if (this.props.isEditorMode && this.props.eventEmitter) {
this.props.eventEmitter.off('updateEditor', this.updateModel);
}
this.props.eventEmitter.off('updateEditor', this.updateModel);
}
}

Expand All @@ -241,7 +202,6 @@ VisEditor.propTypes = {
visFields: PropTypes.object,
renderComplete: PropTypes.func,
config: PropTypes.object,
isEditorMode: PropTypes.bool,
savedObj: PropTypes.object,
timeRange: PropTypes.object,
appState: PropTypes.object,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,8 @@ GaugeVisualization.propTypes = {
getConfig: PropTypes.func,
};

export const gauge = visWithSplits(GaugeVisualization);
const gauge = visWithSplits(GaugeVisualization);

// default export required for React.Lazy
// eslint-disable-next-line import/no-default-export
export { gauge as default };
Loading

0 comments on commit bb023c5

Please sign in to comment.