mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
* [TSVB] Fix reappearing of hidden series on refresh and styles loading * Add functional test * Update condition and move loading component to another file Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
94f639c24a
commit
0a90384195
5 changed files with 139 additions and 99 deletions
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiLoadingChart } from '@elastic/eui';
|
||||
|
||||
export const TimeseriesLoading = () => (
|
||||
<div className="visChart__spinner">
|
||||
<EuiLoadingChart mono size="l" />
|
||||
</div>
|
||||
);
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import './timeseries_visualization.scss';
|
||||
|
||||
import React, { Suspense, useCallback, useEffect } from 'react';
|
||||
import React, { Suspense, useCallback, useEffect, useState } from 'react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiLoadingChart } from '@elastic/eui';
|
||||
import { XYChartSeriesIdentifier, GeometryValue } from '@elastic/charts';
|
||||
import { IUiSettingsClient } from 'src/core/public';
|
||||
|
@ -16,8 +16,9 @@ import { IInterpreterRenderHandlers } from 'src/plugins/expressions';
|
|||
import { PersistedState } from 'src/plugins/visualizations/public';
|
||||
import { PaletteRegistry } from 'src/plugins/charts/public';
|
||||
|
||||
import { TimeseriesLoading } from './timeseries_loading';
|
||||
import { TimeseriesVisTypes } from './vis_types';
|
||||
import type { PanelData, TimeseriesVisData } from '../../../common/types';
|
||||
import type { FetchedIndexPattern, PanelData, TimeseriesVisData } from '../../../common/types';
|
||||
import { isVisTableData } from '../../../common/vis_data_utils';
|
||||
import { TimeseriesVisParams } from '../../types';
|
||||
import { convertSeriesToDataTable } from './lib/convert_series_to_datatable';
|
||||
|
@ -27,32 +28,41 @@ import { LastValueModeIndicator } from './last_value_mode_indicator';
|
|||
import { getInterval } from './lib/get_interval';
|
||||
import { AUTO_INTERVAL } from '../../../common/constants';
|
||||
import { TIME_RANGE_DATA_MODES, PANEL_TYPES } from '../../../common/enums';
|
||||
import type { IndexPattern } from '../../../../../data/common';
|
||||
import '../index.scss';
|
||||
import { fetchIndexPattern } from '../../../common/index_patterns_utils';
|
||||
import { getCharts, getDataStart } from '../../services';
|
||||
|
||||
interface TimeseriesVisualizationProps {
|
||||
className?: string;
|
||||
getConfig: IUiSettingsClient['get'];
|
||||
handlers: IInterpreterRenderHandlers;
|
||||
model: TimeseriesVisParams;
|
||||
visData: TimeseriesVisData;
|
||||
uiState: PersistedState;
|
||||
syncColors: boolean;
|
||||
palettesService: PaletteRegistry;
|
||||
indexPattern?: IndexPattern | null;
|
||||
}
|
||||
|
||||
function TimeseriesVisualization({
|
||||
className = 'tvbVis',
|
||||
visData,
|
||||
model,
|
||||
handlers,
|
||||
uiState,
|
||||
getConfig,
|
||||
syncColors,
|
||||
palettesService,
|
||||
indexPattern,
|
||||
}: TimeseriesVisualizationProps) {
|
||||
const [indexPattern, setIndexPattern] = useState<FetchedIndexPattern['indexPattern']>(null);
|
||||
const [palettesService, setPalettesService] = useState<PaletteRegistry | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
getCharts()
|
||||
.palettes.getPalettes()
|
||||
.then((paletteRegistry) => setPalettesService(paletteRegistry));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
fetchIndexPattern(model.index_pattern, getDataStart().indexPatterns).then(
|
||||
(fetchedIndexPattern) => setIndexPattern(fetchedIndexPattern.indexPattern)
|
||||
);
|
||||
}, [model.index_pattern]);
|
||||
|
||||
const onBrush = useCallback(
|
||||
async (gte: string, lte: string, series: PanelData[]) => {
|
||||
let event;
|
||||
|
@ -136,10 +146,6 @@ function TimeseriesVisualization({
|
|||
[uiState]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
handlers.done();
|
||||
});
|
||||
|
||||
const VisComponent = TimeseriesVisTypes[model.type];
|
||||
|
||||
const isLastValueMode =
|
||||
|
@ -150,46 +156,46 @@ function TimeseriesVisualization({
|
|||
const [firstSeries] =
|
||||
(isVisTableData(visData) ? visData.series : visData[model.id]?.series) ?? [];
|
||||
|
||||
if (VisComponent) {
|
||||
return (
|
||||
<EuiFlexGroup direction="column" gutterSize="none" responsive={false}>
|
||||
{shouldDisplayLastValueIndicator && (
|
||||
<EuiFlexItem className="tvbLastValueIndicator" grow={false}>
|
||||
<LastValueModeIndicator
|
||||
seriesData={firstSeries?.data}
|
||||
ignoreDaylightTime={model.ignore_daylight_time}
|
||||
panelInterval={getInterval(visData, model)}
|
||||
modelInterval={model.interval ?? AUTO_INTERVAL}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
<EuiFlexItem>
|
||||
<Suspense
|
||||
fallback={
|
||||
<div className="visChart__spinner">
|
||||
<EuiLoadingChart mono size="l" />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<VisComponent
|
||||
getConfig={getConfig}
|
||||
model={model}
|
||||
visData={visData}
|
||||
uiState={uiState}
|
||||
onBrush={onBrush}
|
||||
onFilterClick={handleFilterClick}
|
||||
onUiState={handleUiState}
|
||||
syncColors={syncColors}
|
||||
palettesService={palettesService}
|
||||
fieldFormatMap={indexPattern?.fieldFormatMap}
|
||||
/>
|
||||
</Suspense>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
if (!VisComponent || palettesService === null || indexPattern === null) {
|
||||
return <TimeseriesLoading />;
|
||||
}
|
||||
|
||||
return <div className={className} />;
|
||||
return (
|
||||
<EuiFlexGroup direction="column" gutterSize="none" responsive={false}>
|
||||
{shouldDisplayLastValueIndicator && (
|
||||
<EuiFlexItem className="tvbLastValueIndicator" grow={false}>
|
||||
<LastValueModeIndicator
|
||||
seriesData={firstSeries?.data}
|
||||
ignoreDaylightTime={model.ignore_daylight_time}
|
||||
panelInterval={getInterval(visData, model)}
|
||||
modelInterval={model.interval ?? AUTO_INTERVAL}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
<EuiFlexItem>
|
||||
<Suspense
|
||||
fallback={
|
||||
<div className="visChart__spinner">
|
||||
<EuiLoadingChart mono size="l" />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<VisComponent
|
||||
getConfig={getConfig}
|
||||
model={model}
|
||||
visData={visData}
|
||||
uiState={uiState}
|
||||
onBrush={onBrush}
|
||||
onFilterClick={handleFilterClick}
|
||||
onUiState={handleUiState}
|
||||
syncColors={syncColors}
|
||||
palettesService={palettesService}
|
||||
fieldFormatMap={indexPattern?.fieldFormatMap}
|
||||
/>
|
||||
</Suspense>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
||||
// default export required for React.Lazy
|
||||
|
|
|
@ -13,13 +13,10 @@ import { render, unmountComponentAtNode } from 'react-dom';
|
|||
import { I18nProvider } from '@kbn/i18n/react';
|
||||
import { IUiSettingsClient } from 'kibana/public';
|
||||
|
||||
import { EuiLoadingChart } from '@elastic/eui';
|
||||
import { fetchIndexPattern } from '../common/index_patterns_utils';
|
||||
import { VisualizationContainer, PersistedState } from '../../../visualizations/public';
|
||||
|
||||
import type { TimeseriesVisData } from '../common/types';
|
||||
import { isVisTableData } from '../common/vis_data_utils';
|
||||
import { getCharts, getDataStart } from './services';
|
||||
|
||||
import type { TimeseriesVisParams } from './types';
|
||||
import type { ExpressionRenderDefinition } from '../../../expressions/common';
|
||||
|
@ -44,57 +41,40 @@ export const getTimeseriesVisRenderer: (deps: {
|
|||
name: 'timeseries_vis',
|
||||
reuseDomNode: true,
|
||||
render: async (domNode, config, handlers) => {
|
||||
// Build optimization. Move app styles from main bundle
|
||||
// @ts-expect-error TS error, cannot find type declaration for scss
|
||||
import('./application/index.scss');
|
||||
|
||||
handlers.onDestroy(() => {
|
||||
unmountComponentAtNode(domNode);
|
||||
});
|
||||
const { visParams: model, visData, syncColors } = config;
|
||||
const { palettes } = getCharts();
|
||||
const { indexPatterns } = getDataStart();
|
||||
|
||||
const showNoResult = !checkIfDataExists(visData, model);
|
||||
|
||||
let servicesLoaded;
|
||||
|
||||
Promise.all([
|
||||
palettes.getPalettes(),
|
||||
fetchIndexPattern(model.index_pattern, indexPatterns),
|
||||
]).then(([palettesService, { indexPattern }]) => {
|
||||
servicesLoaded = true;
|
||||
|
||||
unmountComponentAtNode(domNode);
|
||||
|
||||
render(
|
||||
<I18nProvider>
|
||||
<VisualizationContainer
|
||||
data-test-subj="timeseriesVis"
|
||||
render(
|
||||
<I18nProvider>
|
||||
<VisualizationContainer
|
||||
data-test-subj="timeseriesVis"
|
||||
handlers={handlers}
|
||||
showNoResult={showNoResult}
|
||||
error={get(visData, [model.id, 'error'])}
|
||||
>
|
||||
<TimeseriesVisualization
|
||||
// it is mandatory to bind uiSettings because of "this" usage inside "get" method
|
||||
getConfig={uiSettings.get.bind(uiSettings)}
|
||||
handlers={handlers}
|
||||
showNoResult={showNoResult}
|
||||
error={get(visData, [model.id, 'error'])}
|
||||
>
|
||||
<TimeseriesVisualization
|
||||
// it is mandatory to bind uiSettings because of "this" usage inside "get" method
|
||||
getConfig={uiSettings.get.bind(uiSettings)}
|
||||
handlers={handlers}
|
||||
indexPattern={indexPattern}
|
||||
model={model}
|
||||
visData={visData as TimeseriesVisData}
|
||||
syncColors={syncColors}
|
||||
uiState={handlers.uiState! as PersistedState}
|
||||
palettesService={palettesService}
|
||||
/>
|
||||
</VisualizationContainer>
|
||||
</I18nProvider>,
|
||||
domNode
|
||||
);
|
||||
});
|
||||
|
||||
if (!servicesLoaded) {
|
||||
render(
|
||||
<div className="visChart__spinner">
|
||||
<EuiLoadingChart mono size="l" />
|
||||
</div>,
|
||||
domNode
|
||||
);
|
||||
}
|
||||
model={model}
|
||||
visData={visData as TimeseriesVisData}
|
||||
syncColors={syncColors}
|
||||
uiState={handlers.uiState! as PersistedState}
|
||||
/>
|
||||
</VisualizationContainer>
|
||||
</I18nProvider>,
|
||||
domNode,
|
||||
() => {
|
||||
handlers.done();
|
||||
}
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -361,6 +361,40 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
expect(chartData).to.eql(expectedChartData);
|
||||
});
|
||||
|
||||
describe('Hiding series', () => {
|
||||
it('should hide series by legend item click', async () => {
|
||||
await visualBuilder.clickDataTab('timeSeries');
|
||||
await visualBuilder.setMetricsGroupByTerms('@tags.raw');
|
||||
|
||||
let areasCount = (await visualBuilder.getChartItems())?.length;
|
||||
expect(areasCount).to.be(6);
|
||||
|
||||
await visualBuilder.clickSeriesLegendItem('success');
|
||||
await visualBuilder.clickSeriesLegendItem('info');
|
||||
await visualBuilder.clickSeriesLegendItem('error');
|
||||
|
||||
areasCount = (await visualBuilder.getChartItems())?.length;
|
||||
expect(areasCount).to.be(3);
|
||||
});
|
||||
|
||||
it('should keep series hidden after refresh', async () => {
|
||||
await visualBuilder.clickDataTab('timeSeries');
|
||||
await visualBuilder.setMetricsGroupByTerms('extension.raw');
|
||||
|
||||
let legendNames = await visualBuilder.getLegendNames();
|
||||
expect(legendNames).to.eql(['jpg', 'css', 'png', 'gif', 'php']);
|
||||
|
||||
await visualBuilder.clickSeriesLegendItem('png');
|
||||
await visualBuilder.clickSeriesLegendItem('php');
|
||||
legendNames = await visualBuilder.getLegendNames();
|
||||
expect(legendNames).to.eql(['jpg', 'css', 'gif']);
|
||||
|
||||
await visualize.clickRefresh(true);
|
||||
legendNames = await visualBuilder.getLegendNames();
|
||||
expect(legendNames).to.eql(['jpg', 'css', 'gif']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Query filter', () => {
|
||||
it('should display correct chart data for applied series filter', async () => {
|
||||
const expectedChartData = [
|
||||
|
|
|
@ -878,6 +878,10 @@ export class VisualBuilderPageObject extends FtrService {
|
|||
await optionInput.type(query);
|
||||
}
|
||||
|
||||
public async clickSeriesLegendItem(name: string) {
|
||||
await this.find.clickByCssSelector(`[data-ech-series-name="${name}"] .echLegendItem__label`);
|
||||
}
|
||||
|
||||
public async toggleNewChartsLibraryWithDebug(enabled: boolean) {
|
||||
await this.elasticChart.setNewChartUiDebugFlag(enabled);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue