mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[TSVB] Custom renderer (#83554)
* 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
This commit is contained in:
parent
378d89b5cd
commit
bb023c5c1c
37 changed files with 446 additions and 402 deletions
|
@ -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',
|
||||
};
|
||||
|
|
|
@ -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',
|
||||
}
|
||||
|
|
|
@ -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[];
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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({
|
||||
|
@ -267,7 +274,6 @@ export const visPayloadSchema = schema.object({
|
|||
})
|
||||
),
|
||||
}),
|
||||
savedObjectId: schema.maybe(schema.string()),
|
||||
timerange: schema.object({
|
||||
timezone: stringRequired,
|
||||
min: stringRequired,
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
/*
|
||||
* 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 from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiIcon, EuiSpacer, EuiText } from '@elastic/eui';
|
||||
|
||||
export function NoDataComponent() {
|
||||
return (
|
||||
<div className="visError" data-test-subj="noTSVBDataMessage">
|
||||
<EuiText size="xs" color="subdued">
|
||||
<EuiIcon type="visualizeApp" size="m" color="subdued" aria-hidden="true" />
|
||||
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="visTypeTimeseries.noDataDescription"
|
||||
defaultMessage="No data to display for the selected metrics"
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -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 };
|
|
@ -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';
|
||||
|
@ -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());
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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({
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* 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, { lazy } from 'react';
|
||||
|
||||
import { IUiSettingsClient } from 'src/core/public';
|
||||
import { PersistedState } from 'src/plugins/visualizations/public';
|
||||
|
||||
import { TimeseriesVisParams } from '../../../metrics_fn';
|
||||
import { TimeseriesVisData } from '../../../../common/types';
|
||||
|
||||
/**
|
||||
* Lazy load each visualization type, since the only one is presented on the screen at the same time.
|
||||
* Disable typescript errors since the components are not typed yet.
|
||||
*/
|
||||
|
||||
// @ts-expect-error
|
||||
const timeseries = lazy(() => import('./timeseries/vis'));
|
||||
// @ts-expect-error
|
||||
const metric = lazy(() => import('./metric/vis'));
|
||||
// @ts-expect-error
|
||||
const topN = lazy(() => import('./top_n/vis'));
|
||||
// @ts-expect-error
|
||||
const table = lazy(() => import('./table/vis'));
|
||||
// @ts-expect-error
|
||||
const gauge = lazy(() => import('./gauge/vis'));
|
||||
// @ts-expect-error
|
||||
const markdown = lazy(() => import('./markdown/vis'));
|
||||
|
||||
export const TimeseriesVisTypes: Record<string, React.ComponentType<TimeseriesVisProps>> = {
|
||||
timeseries,
|
||||
metric,
|
||||
top_n: topN,
|
||||
table,
|
||||
gauge,
|
||||
markdown,
|
||||
};
|
||||
|
||||
export interface TimeseriesVisProps {
|
||||
model: TimeseriesVisParams;
|
||||
onBrush: (gte: string, lte: string) => void;
|
||||
onUiState: (
|
||||
field: string,
|
||||
value: {
|
||||
column: string;
|
||||
order: string;
|
||||
}
|
||||
) => void;
|
||||
uiState: PersistedState;
|
||||
visData: TimeseriesVisData;
|
||||
dateFormat: string;
|
||||
getConfig: IUiSettingsClient['get'];
|
||||
}
|
|
@ -30,7 +30,7 @@ import { isBackgroundInverted } from '../../../lib/set_is_reversed';
|
|||
|
||||
const getMarkdownId = (id) => `markdown-${id}`;
|
||||
|
||||
export function MarkdownVisualization(props) {
|
||||
function MarkdownVisualization(props) {
|
||||
const { backgroundColor, model, visData, dateFormat } = props;
|
||||
const series = get(visData, `${model.id}.series`, []);
|
||||
const variables = convertSeriesToVars(series, model, dateFormat, props.getConfig);
|
||||
|
@ -106,3 +106,7 @@ MarkdownVisualization.propTypes = {
|
|||
dateFormat: PropTypes.string,
|
||||
getConfig: PropTypes.func,
|
||||
};
|
||||
|
||||
// default export required for React.Lazy
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export { MarkdownVisualization as default };
|
||||
|
|
|
@ -95,4 +95,8 @@ MetricVisualization.propTypes = {
|
|||
getConfig: PropTypes.func,
|
||||
};
|
||||
|
||||
export const metric = visWithSplits(MetricVisualization);
|
||||
const metric = visWithSplits(MetricVisualization);
|
||||
|
||||
// default export required for React.Lazy
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export { metric as default };
|
||||
|
|
|
@ -46,7 +46,7 @@ function getColor(rules, colorKey, value) {
|
|||
return color;
|
||||
}
|
||||
|
||||
export class TableVis extends Component {
|
||||
class TableVis extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
|
@ -260,3 +260,7 @@ TableVis.propTypes = {
|
|||
pageNumber: PropTypes.number,
|
||||
getConfig: PropTypes.func,
|
||||
};
|
||||
|
||||
// default export required for React.Lazy
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export { TableVis as default };
|
||||
|
|
|
@ -34,7 +34,7 @@ import { createXaxisFormatter } from '../../lib/create_xaxis_formatter';
|
|||
import { STACKED_OPTIONS } from '../../../visualizations/constants';
|
||||
import { getCoreStart } from '../../../../services';
|
||||
|
||||
export class TimeseriesVisualization extends Component {
|
||||
class TimeseriesVisualization extends Component {
|
||||
static propTypes = {
|
||||
model: PropTypes.object,
|
||||
onBrush: PropTypes.func,
|
||||
|
@ -44,7 +44,8 @@ export class TimeseriesVisualization extends Component {
|
|||
};
|
||||
|
||||
xAxisFormatter = (interval) => (val) => {
|
||||
const { scaledDataFormat, dateFormat } = this.props.visData;
|
||||
const scaledDataFormat = this.props.getConfig('dateFormat:scaled');
|
||||
const { dateFormat } = this.props;
|
||||
|
||||
if (!scaledDataFormat || !dateFormat) {
|
||||
return val;
|
||||
|
@ -245,3 +246,7 @@ export class TimeseriesVisualization extends Component {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
// default export required for React.Lazy
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export { TimeseriesVisualization as default };
|
||||
|
|
|
@ -48,7 +48,7 @@ function sortSeries(visData, model) {
|
|||
}, []);
|
||||
}
|
||||
|
||||
export function TopNVisualization(props) {
|
||||
function TopNVisualization(props) {
|
||||
const { backgroundColor, model, visData } = props;
|
||||
|
||||
const series = sortSeries(visData, model).map((item) => {
|
||||
|
@ -111,3 +111,7 @@ TopNVisualization.propTypes = {
|
|||
visData: PropTypes.object,
|
||||
getConfig: PropTypes.func,
|
||||
};
|
||||
|
||||
// default export required for React.Lazy
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export { TopNVisualization as default };
|
||||
|
|
|
@ -1,96 +0,0 @@
|
|||
/*
|
||||
* 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 PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { TimeseriesVisualization } from './vis_types/timeseries/vis';
|
||||
import { metric } from './vis_types/metric/vis';
|
||||
import { TopNVisualization as topN } from './vis_types/top_n/vis';
|
||||
import { TableVis as table } from './vis_types/table/vis';
|
||||
import { gauge } from './vis_types/gauge/vis';
|
||||
import { MarkdownVisualization as markdown } from './vis_types/markdown/vis';
|
||||
import { ErrorComponent } from './error';
|
||||
import { NoDataComponent } from './no_data';
|
||||
|
||||
const types = {
|
||||
timeseries: TimeseriesVisualization,
|
||||
metric,
|
||||
top_n: topN,
|
||||
table,
|
||||
gauge,
|
||||
markdown,
|
||||
};
|
||||
|
||||
export function Visualization(props) {
|
||||
const { visData, model } = props;
|
||||
// Show the error panel
|
||||
const error = _.get(visData, `${model.id}.error`);
|
||||
if (error) {
|
||||
return (
|
||||
<div className={props.className}>
|
||||
<ErrorComponent error={error} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const path = visData.type === 'table' ? 'series' : `${model.id}.series`;
|
||||
const noData = _.get(visData, path, []).length === 0;
|
||||
if (noData) {
|
||||
return (
|
||||
<div className={props.className}>
|
||||
<NoDataComponent />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const component = types[model.type];
|
||||
if (component) {
|
||||
return React.createElement(component, {
|
||||
dateFormat: props.dateFormat,
|
||||
backgroundColor: props.backgroundColor,
|
||||
model: props.model,
|
||||
onBrush: props.onBrush,
|
||||
onChange: props.onChange,
|
||||
onUiState: props.onUiState,
|
||||
uiState: props.uiState,
|
||||
visData: visData.type === model.type ? visData : {},
|
||||
getConfig: props.getConfig,
|
||||
});
|
||||
}
|
||||
return <div className={props.className} />;
|
||||
}
|
||||
|
||||
Visualization.propTypes = {
|
||||
backgroundColor: PropTypes.string,
|
||||
className: PropTypes.string,
|
||||
model: PropTypes.object,
|
||||
onBrush: PropTypes.func,
|
||||
onChange: PropTypes.func,
|
||||
onUiState: PropTypes.func,
|
||||
uiState: PropTypes.object,
|
||||
visData: PropTypes.object,
|
||||
dateFormat: PropTypes.string,
|
||||
getConfig: PropTypes.func,
|
||||
};
|
||||
|
||||
Visualization.defaultProps = {
|
||||
className: 'tvbVis',
|
||||
};
|
|
@ -70,7 +70,6 @@ export class EditorController {
|
|||
visParams={this.state.vis.params}
|
||||
timeRange={params.timeRange}
|
||||
renderComplete={() => {}}
|
||||
isEditorMode={true}
|
||||
appState={params.appState}
|
||||
embeddableHandler={this.embeddableHandler}
|
||||
eventEmitter={this.eventEmitter}
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
/*
|
||||
* 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 { createBrushHandler } from './create_brush_handler';
|
||||
import { ExprVisAPIEvents } from '../../../../visualizations/public';
|
||||
|
||||
describe('brushHandler', () => {
|
||||
let onBrush: ReturnType<typeof createBrushHandler>;
|
||||
let applyFilter: ExprVisAPIEvents['applyFilter'];
|
||||
|
||||
beforeEach(() => {
|
||||
applyFilter = jest.fn();
|
||||
|
||||
onBrush = createBrushHandler(applyFilter);
|
||||
});
|
||||
|
||||
test('returns brushHandler() should updates timefilter through vis.API.events.applyFilter', () => {
|
||||
const gte = '2017-01-01T00:00:00Z';
|
||||
const lte = '2017-01-01T00:10:00Z';
|
||||
|
||||
onBrush(gte, lte);
|
||||
|
||||
expect(applyFilter).toHaveBeenCalledWith({
|
||||
timeFieldName: '*',
|
||||
filters: [
|
||||
{
|
||||
range: { '*': { gte: '2017-01-01T00:00:00Z', lte: '2017-01-01T00:10:00Z' } },
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
|
@ -17,38 +17,36 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { get } from 'lodash';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { KibanaContext } from '../../data/public';
|
||||
import { ExpressionFunctionDefinition, Render } from '../../expressions/public';
|
||||
|
||||
// @ts-ignore
|
||||
import { PanelSchema, TimeseriesVisData } from '../common/types';
|
||||
import { metricsRequestHandler } from './request_handler';
|
||||
|
||||
type Input = KibanaContext | null;
|
||||
type Output = Promise<Render<RenderValue>>;
|
||||
type Output = Promise<Render<TimeseriesRenderValue>>;
|
||||
|
||||
interface Arguments {
|
||||
params: string;
|
||||
uiState: string;
|
||||
savedObjectId: string | null;
|
||||
}
|
||||
|
||||
type VisParams = Required<Arguments>;
|
||||
export type TimeseriesVisParams = PanelSchema;
|
||||
|
||||
interface RenderValue {
|
||||
visType: 'metrics';
|
||||
visData: Input;
|
||||
visConfig: VisParams;
|
||||
uiState: any;
|
||||
export interface TimeseriesRenderValue {
|
||||
visData: TimeseriesVisData | {};
|
||||
visParams: TimeseriesVisParams;
|
||||
}
|
||||
|
||||
export const createMetricsFn = (): ExpressionFunctionDefinition<
|
||||
export type TimeseriesExpressionFunctionDefinition = ExpressionFunctionDefinition<
|
||||
'tsvb',
|
||||
Input,
|
||||
Arguments,
|
||||
Output
|
||||
> => ({
|
||||
>;
|
||||
|
||||
export const createMetricsFn = (): TimeseriesExpressionFunctionDefinition => ({
|
||||
name: 'tsvb',
|
||||
type: 'render',
|
||||
inputTypes: ['kibana_context', 'null'],
|
||||
|
@ -66,37 +64,22 @@ export const createMetricsFn = (): ExpressionFunctionDefinition<
|
|||
default: '"{}"',
|
||||
help: '',
|
||||
},
|
||||
savedObjectId: {
|
||||
types: ['null', 'string'],
|
||||
default: null,
|
||||
help: '',
|
||||
},
|
||||
},
|
||||
async fn(input, args) {
|
||||
const params = JSON.parse(args.params);
|
||||
const uiStateParams = JSON.parse(args.uiState);
|
||||
const savedObjectId = args.savedObjectId;
|
||||
const { PersistedState } = await import('../../visualizations/public');
|
||||
const uiState = new PersistedState(uiStateParams);
|
||||
const visParams: TimeseriesVisParams = JSON.parse(args.params);
|
||||
const uiState = JSON.parse(args.uiState);
|
||||
|
||||
const response = await metricsRequestHandler({
|
||||
timeRange: get(input, 'timeRange', null),
|
||||
query: get(input, 'query', null),
|
||||
filters: get(input, 'filters', null),
|
||||
visParams: params,
|
||||
input,
|
||||
visParams,
|
||||
uiState,
|
||||
savedObjectId,
|
||||
});
|
||||
|
||||
response.visType = 'metrics';
|
||||
|
||||
return {
|
||||
type: 'render',
|
||||
as: 'visualization',
|
||||
as: 'timeseries_vis',
|
||||
value: {
|
||||
uiState,
|
||||
visType: 'metrics',
|
||||
visConfig: params,
|
||||
visParams,
|
||||
visData: response,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -19,12 +19,9 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
// @ts-ignore
|
||||
import { metricsRequestHandler } from './request_handler';
|
||||
import { EditorController } from './application';
|
||||
// @ts-ignore
|
||||
import { PANEL_TYPES } from '../common/panel_types';
|
||||
import { VisEditor } from './application/components/vis_editor_lazy';
|
||||
import { toExpressionAst } from './to_ast';
|
||||
import { VIS_EVENT_TO_TRIGGER, VisGroups, VisParams } from '../../visualizations/public';
|
||||
import { getDataStart } from './services';
|
||||
import { INDEXES_SEPARATOR } from '../common/constants';
|
||||
|
@ -73,7 +70,6 @@ export const metricsVisDefinition = {
|
|||
show_grid: 1,
|
||||
tooltip_mode: 'show_all',
|
||||
},
|
||||
component: VisEditor,
|
||||
},
|
||||
editor: EditorController,
|
||||
options: {
|
||||
|
@ -81,7 +77,7 @@ export const metricsVisDefinition = {
|
|||
showFilterBar: false,
|
||||
showIndexSelection: false,
|
||||
},
|
||||
requestHandler: metricsRequestHandler,
|
||||
toExpressionAst,
|
||||
getSupportedTriggers: () => {
|
||||
return [VIS_EVENT_TO_TRIGGER.applyFilter];
|
||||
},
|
||||
|
@ -102,5 +98,4 @@ export const metricsVisDefinition = {
|
|||
|
||||
return [];
|
||||
},
|
||||
responseHandler: 'none',
|
||||
};
|
||||
|
|
|
@ -36,6 +36,7 @@ import {
|
|||
} from './services';
|
||||
import { DataPublicPluginStart } from '../../data/public';
|
||||
import { ChartsPluginSetup } from '../../charts/public';
|
||||
import { getTimeseriesVisRenderer } from './timeseries_vis_renderer';
|
||||
|
||||
/** @internal */
|
||||
export interface MetricsPluginSetupDependencies {
|
||||
|
@ -62,9 +63,14 @@ export class MetricsPlugin implements Plugin<Promise<void>, void> {
|
|||
{ expressions, visualizations, charts }: MetricsPluginSetupDependencies
|
||||
) {
|
||||
expressions.registerFunction(createMetricsFn);
|
||||
expressions.registerRenderer(
|
||||
getTimeseriesVisRenderer({
|
||||
uiSettings: core.uiSettings,
|
||||
})
|
||||
);
|
||||
setUISettings(core.uiSettings);
|
||||
setChartsSetup(charts);
|
||||
visualizations.createReactVisualization(metricsVisDefinition);
|
||||
visualizations.createBaseVisualization(metricsVisDefinition);
|
||||
}
|
||||
|
||||
public start(core: CoreStart, { data }: MetricsPluginStartDependencies) {
|
||||
|
|
|
@ -17,57 +17,52 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { KibanaContext } from '../../data/public';
|
||||
|
||||
import { getTimezone, validateInterval } from './application';
|
||||
import { getUISettings, getDataStart, getCoreStart } from './services';
|
||||
import { MAX_BUCKETS_SETTING } from '../common/constants';
|
||||
import { MAX_BUCKETS_SETTING, ROUTES } from '../common/constants';
|
||||
import { TimeseriesVisParams } from './metrics_fn';
|
||||
import { TimeseriesVisData } from '../common/types';
|
||||
|
||||
interface MetricsRequestHandlerParams {
|
||||
input: KibanaContext | null;
|
||||
uiState: Record<string, any>;
|
||||
visParams: TimeseriesVisParams;
|
||||
}
|
||||
|
||||
export const metricsRequestHandler = async ({
|
||||
input,
|
||||
uiState,
|
||||
timeRange,
|
||||
filters,
|
||||
query,
|
||||
visParams,
|
||||
savedObjectId,
|
||||
}) => {
|
||||
}: MetricsRequestHandlerParams): Promise<TimeseriesVisData | {}> => {
|
||||
const config = getUISettings();
|
||||
const timezone = getTimezone(config);
|
||||
const uiStateObj = uiState.get(visParams.type, {});
|
||||
const uiStateObj = uiState[visParams.type] ?? {};
|
||||
const dataSearch = getDataStart();
|
||||
const parsedTimeRange = dataSearch.query.timefilter.timefilter.calculateBounds(timeRange);
|
||||
const scaledDataFormat = config.get('dateFormat:scaled');
|
||||
const dateFormat = config.get('dateFormat');
|
||||
const parsedTimeRange = dataSearch.query.timefilter.timefilter.calculateBounds(input?.timeRange!);
|
||||
|
||||
if (visParams && visParams.id && !visParams.isModelInvalid) {
|
||||
try {
|
||||
const maxBuckets = config.get(MAX_BUCKETS_SETTING);
|
||||
const maxBuckets = config.get(MAX_BUCKETS_SETTING);
|
||||
|
||||
validateInterval(parsedTimeRange, visParams, maxBuckets);
|
||||
validateInterval(parsedTimeRange, visParams, maxBuckets);
|
||||
|
||||
const resp = await getCoreStart().http.post('/api/metrics/vis/data', {
|
||||
body: JSON.stringify({
|
||||
timerange: {
|
||||
timezone,
|
||||
...parsedTimeRange,
|
||||
},
|
||||
query,
|
||||
filters,
|
||||
panels: [visParams],
|
||||
state: uiStateObj,
|
||||
savedObjectId: savedObjectId || 'unsaved',
|
||||
sessionId: dataSearch.search.session.getSessionId(),
|
||||
}),
|
||||
});
|
||||
const resp = await getCoreStart().http.post(ROUTES.VIS_DATA, {
|
||||
body: JSON.stringify({
|
||||
timerange: {
|
||||
timezone,
|
||||
...parsedTimeRange,
|
||||
},
|
||||
query: input?.query,
|
||||
filters: input?.filters,
|
||||
panels: [visParams],
|
||||
state: uiStateObj,
|
||||
sessionId: dataSearch.search.session.getSessionId(),
|
||||
}),
|
||||
});
|
||||
|
||||
return {
|
||||
dateFormat,
|
||||
scaledDataFormat,
|
||||
timezone,
|
||||
...resp,
|
||||
};
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
return resp;
|
||||
}
|
||||
|
||||
return Promise.resolve({});
|
||||
return {};
|
||||
};
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* 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, { lazy } from 'react';
|
||||
import { render, unmountComponentAtNode } from 'react-dom';
|
||||
|
||||
import { IUiSettingsClient } from 'kibana/public';
|
||||
import { VisualizationContainer } from '../../visualizations/public';
|
||||
import { ExpressionRenderDefinition } from '../../expressions/common/expression_renderers';
|
||||
import { TimeseriesRenderValue, TimeseriesVisParams } from './metrics_fn';
|
||||
import { TimeseriesVisData } from '../common/types';
|
||||
|
||||
const TimeseriesVisualization = lazy(
|
||||
() => import('./application/components/timeseries_visualization')
|
||||
);
|
||||
|
||||
const checkIfDataExists = (visData: TimeseriesVisData | {}, model: TimeseriesVisParams) => {
|
||||
if ('type' in visData) {
|
||||
const data = visData.type === 'table' ? visData.series : visData?.[model.id]?.series;
|
||||
return Boolean(data?.length);
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
export const getTimeseriesVisRenderer: (deps: {
|
||||
uiSettings: IUiSettingsClient;
|
||||
}) => ExpressionRenderDefinition<TimeseriesRenderValue> = ({ uiSettings }) => ({
|
||||
name: 'timeseries_vis',
|
||||
reuseDomNode: true,
|
||||
render: async (domNode, config, handlers) => {
|
||||
handlers.onDestroy(() => {
|
||||
unmountComponentAtNode(domNode);
|
||||
});
|
||||
|
||||
const showNoResult = !checkIfDataExists(config.visData, config.visParams);
|
||||
|
||||
render(
|
||||
<VisualizationContainer
|
||||
data-test-subj="timeseriesVis"
|
||||
handlers={handlers}
|
||||
showNoResult={showNoResult}
|
||||
>
|
||||
<TimeseriesVisualization
|
||||
// it is mandatory to bind uiSettings because of "this" usage inside "get" method
|
||||
getConfig={uiSettings.get.bind(uiSettings)}
|
||||
handlers={handlers}
|
||||
model={config.visParams}
|
||||
visData={config.visData as TimeseriesVisData}
|
||||
uiState={handlers.uiState!}
|
||||
/>
|
||||
</VisualizationContainer>,
|
||||
domNode
|
||||
);
|
||||
},
|
||||
});
|
|
@ -17,23 +17,17 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { ExprVisAPIEvents } from '../../../../visualizations/public';
|
||||
import { buildExpression, buildExpressionFunction } from '../../expressions/public';
|
||||
import { Vis } from '../../visualizations/public';
|
||||
import { TimeseriesExpressionFunctionDefinition, TimeseriesVisParams } from './metrics_fn';
|
||||
|
||||
export const createBrushHandler = (applyFilter: ExprVisAPIEvents['applyFilter']) => (
|
||||
gte: string,
|
||||
lte: string
|
||||
) => {
|
||||
return applyFilter({
|
||||
timeFieldName: '*',
|
||||
filters: [
|
||||
{
|
||||
range: {
|
||||
'*': {
|
||||
gte,
|
||||
lte,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
export const toExpressionAst = (vis: Vis<TimeseriesVisParams>) => {
|
||||
const timeseries = buildExpressionFunction<TimeseriesExpressionFunctionDefinition>('tsvb', {
|
||||
params: JSON.stringify(vis.params),
|
||||
uiState: JSON.stringify(vis.uiState),
|
||||
});
|
||||
|
||||
const ast = buildExpression([timeseries]);
|
||||
|
||||
return ast.toAst();
|
||||
};
|
|
@ -20,46 +20,37 @@
|
|||
import { FakeRequest, RequestHandlerContext } from 'kibana/server';
|
||||
import _ from 'lodash';
|
||||
import { first, map } from 'rxjs/operators';
|
||||
|
||||
import { Filter, Query } from 'src/plugins/data/common';
|
||||
import { getPanelData } from './vis_data/get_panel_data';
|
||||
import { Framework } from '../plugin';
|
||||
import { ReqFacade } from './search_strategies/strategies/abstract_search_strategy';
|
||||
|
||||
interface GetVisDataResponse {
|
||||
[key: string]: GetVisDataPanel;
|
||||
}
|
||||
|
||||
interface GetVisDataPanel {
|
||||
id: string;
|
||||
series: GetVisDataSeries[];
|
||||
}
|
||||
|
||||
interface GetVisDataSeries {
|
||||
id: string;
|
||||
label: string;
|
||||
data: GetVisDataDataPoint[];
|
||||
}
|
||||
|
||||
type GetVisDataDataPoint = [number, number];
|
||||
import { TimeseriesVisData } from '../../common/types';
|
||||
|
||||
export interface GetVisDataOptions {
|
||||
timerange?: any;
|
||||
panels?: any;
|
||||
filters?: any;
|
||||
state?: any;
|
||||
query?: any;
|
||||
timerange: {
|
||||
min: number | string;
|
||||
max: number | string;
|
||||
timezone?: string;
|
||||
};
|
||||
panels: unknown[];
|
||||
filters?: Filter[];
|
||||
state?: Record<string, unknown>;
|
||||
query?: Query | Query[];
|
||||
sessionId?: string;
|
||||
}
|
||||
|
||||
export type GetVisData = (
|
||||
requestContext: RequestHandlerContext,
|
||||
options: GetVisDataOptions,
|
||||
framework: Framework
|
||||
) => Promise<GetVisDataResponse>;
|
||||
) => Promise<TimeseriesVisData>;
|
||||
|
||||
export function getVisData(
|
||||
requestContext: RequestHandlerContext,
|
||||
request: FakeRequest & { body: GetVisDataOptions },
|
||||
framework: Framework
|
||||
): Promise<GetVisDataResponse> {
|
||||
): Promise<TimeseriesVisData> {
|
||||
// NOTE / TODO: This facade has been put in place to make migrating to the New Platform easier. It
|
||||
// removes the need to refactor many layers of dependencies on "req", and instead just augments the top
|
||||
// level object passed from here. The layers should be refactored fully at some point, but for now
|
||||
|
@ -81,10 +72,10 @@ export function getVisData(
|
|||
.toPromise();
|
||||
},
|
||||
};
|
||||
const promises = (reqFacade.payload as GetVisDataOptions).panels.map(getPanelData(reqFacade));
|
||||
const promises = reqFacade.payload.panels.map(getPanelData(reqFacade));
|
||||
return Promise.all(promises).then((res) => {
|
||||
return res.reduce((acc, data) => {
|
||||
return _.assign(acc as any, data);
|
||||
}, {});
|
||||
}) as Promise<GetVisDataResponse>;
|
||||
}) as Promise<TimeseriesVisData>;
|
||||
}
|
||||
|
|
|
@ -29,7 +29,8 @@ export const getActiveSeries = (panel: PanelSchema) => {
|
|||
}
|
||||
|
||||
// Toogle visibility functionality for 'gauge', 'markdown' is not accessible
|
||||
const shouldNotApplyFilter = [PANEL_TYPES.GAUGE, PANEL_TYPES.MARKDOWN].includes(panel.type);
|
||||
const shouldNotApplyFilter =
|
||||
PANEL_TYPES.GAUGE === panel.type || PANEL_TYPES.MARKDOWN === panel.type;
|
||||
|
||||
return visibleSeries.filter((series) => !series.hidden || shouldNotApplyFilter);
|
||||
};
|
||||
|
|
|
@ -21,6 +21,7 @@ import { IRouter, KibanaRequest } from 'kibana/server';
|
|||
import { schema } from '@kbn/config-schema';
|
||||
import { getVisData, GetVisDataOptions } from '../lib/get_vis_data';
|
||||
import { visPayloadSchema } from '../../common/vis_schema';
|
||||
import { ROUTES } from '../../common/constants';
|
||||
import { ValidationTelemetryServiceSetup } from '../index';
|
||||
import { Framework } from '../plugin';
|
||||
|
||||
|
@ -33,7 +34,7 @@ export const visDataRoutes = (
|
|||
) => {
|
||||
router.post(
|
||||
{
|
||||
path: '/api/metrics/vis/data',
|
||||
path: ROUTES.VIS_DATA,
|
||||
validate: {
|
||||
body: escapeHatch,
|
||||
},
|
||||
|
@ -43,11 +44,9 @@ export const visDataRoutes = (
|
|||
visPayloadSchema.validate(request.body);
|
||||
} catch (error) {
|
||||
logFailedValidation();
|
||||
const savedObjectId =
|
||||
(typeof request.body === 'object' && (request.body as any).savedObjectId) ||
|
||||
'unavailable';
|
||||
|
||||
framework.logger.warn(
|
||||
`Request validation error: ${error.message} (saved object id: ${savedObjectId}). This most likely means your TSVB visualization contains outdated configuration. You can report this problem under https://github.com/elastic/kibana/issues/new?template=Bug_report.md`
|
||||
`Request validation error: ${error.message}. This most likely means your TSVB visualization contains outdated configuration. You can report this problem under https://github.com/elastic/kibana/issues/new?template=Bug_report.md`
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
exports[`VisualizationNoResults should render according to snapshot 1`] = `
|
||||
<div
|
||||
class="visError"
|
||||
data-test-subj="visNoResult"
|
||||
>
|
||||
<div
|
||||
class="item top"
|
||||
|
|
|
@ -24,6 +24,7 @@ import { VisualizationNoResults } from './visualization_noresults';
|
|||
import { IInterpreterRenderHandlers } from '../../../expressions/common';
|
||||
|
||||
interface VisualizationContainerProps {
|
||||
'data-test-subj'?: string;
|
||||
className?: string;
|
||||
children: ReactNode;
|
||||
handlers: IInterpreterRenderHandlers;
|
||||
|
@ -31,6 +32,7 @@ interface VisualizationContainerProps {
|
|||
}
|
||||
|
||||
export const VisualizationContainer = ({
|
||||
'data-test-subj': dataTestSubj = '',
|
||||
className,
|
||||
children,
|
||||
handlers,
|
||||
|
@ -45,7 +47,7 @@ export const VisualizationContainer = ({
|
|||
);
|
||||
|
||||
return (
|
||||
<div className={classes}>
|
||||
<div data-test-subj={dataTestSubj} className={classes}>
|
||||
<Suspense fallback={fallBack}>
|
||||
{showNoResult ? <VisualizationNoResults onInit={() => handlers.done()} /> : children}
|
||||
</Suspense>
|
||||
|
|
|
@ -30,7 +30,7 @@ export class VisualizationNoResults extends React.Component<VisualizationNoResul
|
|||
|
||||
public render() {
|
||||
return (
|
||||
<div className="visError" ref={this.containerDiv}>
|
||||
<div data-test-subj="visNoResult" className="visError" ref={this.containerDiv}>
|
||||
<div className="item top" />
|
||||
<div className="item">
|
||||
<EuiText size="xs" color="subdued">
|
||||
|
|
|
@ -4,8 +4,6 @@ exports[`visualize loader pipeline helpers: build pipeline buildPipeline calls t
|
|||
|
||||
exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles input_control_vis function 1`] = `"input_control_vis visConfig='{\\"some\\":\\"nested\\",\\"data\\":{\\"here\\":true}}' "`;
|
||||
|
||||
exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles metrics/tsvb function 1`] = `"tsvb params='{\\"foo\\":\\"bar\\"}' uiState='{}' "`;
|
||||
|
||||
exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles region_map function with buckets 1`] = `"regionmap visConfig='{\\"metric\\":{\\"accessor\\":0,\\"label\\":\\"\\",\\"format\\":{},\\"params\\":{},\\"aggType\\":\\"\\"},\\"bucket\\":1}' "`;
|
||||
|
||||
exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles region_map function without buckets 1`] = `"regionmap visConfig='{\\"metric\\":{\\"accessor\\":0,\\"label\\":\\"\\",\\"format\\":{},\\"params\\":{},\\"aggType\\":\\"\\"}}' "`;
|
||||
|
|
|
@ -101,12 +101,6 @@ describe('visualize loader pipeline helpers: build pipeline', () => {
|
|||
expect(actual).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('handles metrics/tsvb function', () => {
|
||||
const params = { foo: 'bar' };
|
||||
const actual = buildPipelineVisFunction.metrics(params, schemasDef, uiState);
|
||||
expect(actual).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe('handles region_map function', () => {
|
||||
it('without buckets', () => {
|
||||
const params = { metric: {} };
|
||||
|
|
|
@ -222,13 +222,6 @@ export const buildPipelineVisFunction: BuildPipelineVisFunction = {
|
|||
input_control_vis: (params) => {
|
||||
return `input_control_vis ${prepareJson('visConfig', params)}`;
|
||||
},
|
||||
metrics: ({ title, ...params }, schemas, uiState = {}) => {
|
||||
const paramsJson = prepareJson('params', params);
|
||||
const uiStateJson = prepareJson('uiState', uiState);
|
||||
|
||||
const paramsArray = [paramsJson, uiStateJson].filter((param) => Boolean(param));
|
||||
return `tsvb ${paramsArray.join(' ')}`;
|
||||
},
|
||||
region_map: (params, schemas) => {
|
||||
const visConfig = {
|
||||
...params,
|
||||
|
|
|
@ -92,7 +92,7 @@ export default function ({ getService, getPageObjects }) {
|
|||
});
|
||||
|
||||
it('tsvb time series shows no data message', async () => {
|
||||
expect(await testSubjects.exists('noTSVBDataMessage')).to.be(true);
|
||||
expect(await testSubjects.exists('timeseriesVis > visNoResult')).to.be(true);
|
||||
});
|
||||
|
||||
it('metric value shows no data', async () => {
|
||||
|
|
|
@ -549,7 +549,7 @@ export function VisualBuilderPageProvider({ getService, getPageObjects }: FtrPro
|
|||
|
||||
public async checkPreviewIsDisabled(): Promise<void> {
|
||||
log.debug(`Check no data message is present`);
|
||||
await testSubjects.existOrFail('noTSVBDataMessage', { timeout: 5000 });
|
||||
await testSubjects.existOrFail('timeseriesVis > visNoResult', { timeout: 5000 });
|
||||
}
|
||||
|
||||
public async cloneSeries(nth: number = 0): Promise<void> {
|
||||
|
|
|
@ -4028,7 +4028,6 @@
|
|||
"visTypeTimeseries.movingAverage.windowSizeHint": "ウィンドウは、必ず、期間のサイズの 2 倍以上でなければなりません",
|
||||
"visTypeTimeseries.movingAverage.windowSizeLabel": "ウィンドウサイズ",
|
||||
"visTypeTimeseries.noButtonLabel": "いいえ",
|
||||
"visTypeTimeseries.noDataDescription": "選択されたメトリックに表示するデータがありません",
|
||||
"visTypeTimeseries.percentile.aggregationLabel": "集約",
|
||||
"visTypeTimeseries.percentile.fieldLabel": "フィールド",
|
||||
"visTypeTimeseries.percentile.fillToLabel": "次の基準に合わせる:",
|
||||
|
|
|
@ -4029,7 +4029,6 @@
|
|||
"visTypeTimeseries.movingAverage.windowSizeHint": "窗口必须始终至少是期间大小的两倍",
|
||||
"visTypeTimeseries.movingAverage.windowSizeLabel": "窗口大小",
|
||||
"visTypeTimeseries.noButtonLabel": "否",
|
||||
"visTypeTimeseries.noDataDescription": "所选指标没有可显示的数据",
|
||||
"visTypeTimeseries.percentile.aggregationLabel": "聚合",
|
||||
"visTypeTimeseries.percentile.fieldLabel": "字段",
|
||||
"visTypeTimeseries.percentile.fillToLabel": "填充到:",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue