[TSVB] Fix reappearing of hidden series on refresh and styles loading (#117311) (#117985)

* [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:
Diana Derevyankina 2021-11-09 15:10:50 +03:00 committed by GitHub
parent 94f639c24a
commit 0a90384195
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 139 additions and 99 deletions

View file

@ -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>
);

View file

@ -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

View file

@ -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();
}
);
},
});

View file

@ -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 = [

View file

@ -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);
}