Transaction type handling and breakdown chart (#83587)
This commit is contained in:
parent
7d8ca10fbc
commit
3714658760
82
x-pack/plugins/apm/common/agent_name.test.ts
Normal file
82
x-pack/plugins/apm/common/agent_name.test.ts
Normal file
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import {
|
||||
getFirstTransactionType,
|
||||
isJavaAgentName,
|
||||
isRumAgentName,
|
||||
} from './agent_name';
|
||||
|
||||
describe('agent name helpers', () => {
|
||||
describe('getFirstTransactionType', () => {
|
||||
describe('with no transaction types', () => {
|
||||
expect(getFirstTransactionType([])).toBeUndefined();
|
||||
});
|
||||
|
||||
describe('with a non-rum agent', () => {
|
||||
it('returns "request"', () => {
|
||||
expect(getFirstTransactionType(['worker', 'request'], 'java')).toEqual(
|
||||
'request'
|
||||
);
|
||||
});
|
||||
|
||||
describe('with no request types', () => {
|
||||
it('returns the first type', () => {
|
||||
expect(
|
||||
getFirstTransactionType(['worker', 'shirker'], 'java')
|
||||
).toEqual('worker');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('with a rum agent', () => {
|
||||
it('returns "page-load"', () => {
|
||||
expect(
|
||||
getFirstTransactionType(['http-request', 'page-load'], 'js-base')
|
||||
).toEqual('page-load');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('isJavaAgentName', () => {
|
||||
describe('when the agent name is java', () => {
|
||||
it('returns true', () => {
|
||||
expect(isJavaAgentName('java')).toEqual(true);
|
||||
});
|
||||
});
|
||||
describe('when the agent name is not java', () => {
|
||||
it('returns true', () => {
|
||||
expect(isJavaAgentName('not java')).toEqual(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('isRumAgentName', () => {
|
||||
describe('when the agent name is js-base', () => {
|
||||
it('returns true', () => {
|
||||
expect(isRumAgentName('js-base')).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the agent name is rum-js', () => {
|
||||
it('returns true', () => {
|
||||
expect(isRumAgentName('rum-js')).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the agent name is opentelemetry/webjs', () => {
|
||||
it('returns true', () => {
|
||||
expect(isRumAgentName('opentelemetry/webjs')).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the agent name something else', () => {
|
||||
it('returns true', () => {
|
||||
expect(isRumAgentName('java')).toEqual(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -5,6 +5,10 @@
|
|||
*/
|
||||
|
||||
import { AgentName } from '../typings/es_schemas/ui/fields/agent';
|
||||
import {
|
||||
TRANSACTION_PAGE_LOAD,
|
||||
TRANSACTION_REQUEST,
|
||||
} from './transaction_types';
|
||||
|
||||
/*
|
||||
* Agent names can be any string. This list only defines the official agents
|
||||
|
@ -46,10 +50,24 @@ export const RUM_AGENT_NAMES: AgentName[] = [
|
|||
'opentelemetry/webjs',
|
||||
];
|
||||
|
||||
export function isRumAgentName(
|
||||
function getDefaultTransactionTypeForAgentName(agentName?: string) {
|
||||
return isRumAgentName(agentName)
|
||||
? TRANSACTION_PAGE_LOAD
|
||||
: TRANSACTION_REQUEST;
|
||||
}
|
||||
|
||||
export function getFirstTransactionType(
|
||||
transactionTypes: string[],
|
||||
agentName?: string
|
||||
): agentName is 'js-base' | 'rum-js' | 'opentelemetry/webjs' {
|
||||
return RUM_AGENT_NAMES.includes(agentName! as AgentName);
|
||||
) {
|
||||
const defaultTransactionType = getDefaultTransactionTypeForAgentName(
|
||||
agentName
|
||||
);
|
||||
|
||||
return (
|
||||
transactionTypes.find((type) => type === defaultTransactionType) ??
|
||||
transactionTypes[0]
|
||||
);
|
||||
}
|
||||
|
||||
export function isJavaAgentName(
|
||||
|
@ -57,3 +75,9 @@ export function isJavaAgentName(
|
|||
): agentName is 'java' {
|
||||
return agentName === 'java';
|
||||
}
|
||||
|
||||
export function isRumAgentName(
|
||||
agentName?: string
|
||||
): agentName is 'js-base' | 'rum-js' | 'opentelemetry/webjs' {
|
||||
return RUM_AGENT_NAMES.includes(agentName! as AgentName);
|
||||
}
|
||||
|
|
|
@ -14,14 +14,9 @@ const { createJestConfig } = require('../../dev-tools/jest/create_jest_config');
|
|||
const { resolve } = require('path');
|
||||
|
||||
const rootDir = resolve(__dirname, '.');
|
||||
const xPackKibanaDirectory = resolve(__dirname, '../..');
|
||||
const kibanaDirectory = resolve(__dirname, '../../..');
|
||||
|
||||
const jestConfig = createJestConfig({
|
||||
kibanaDirectory,
|
||||
rootDir,
|
||||
xPackKibanaDirectory,
|
||||
});
|
||||
const jestConfig = createJestConfig({ kibanaDirectory, rootDir });
|
||||
|
||||
module.exports = {
|
||||
...jestConfig,
|
||||
|
|
|
@ -23,7 +23,7 @@ import { ServiceMap } from '../ServiceMap';
|
|||
import { ServiceMetrics } from '../service_metrics';
|
||||
import { ServiceNodeOverview } from '../ServiceNodeOverview';
|
||||
import { ServiceOverview } from '../service_overview';
|
||||
import { TransactionOverview } from '../TransactionOverview';
|
||||
import { TransactionOverview } from '../transaction_overview';
|
||||
|
||||
interface Tab {
|
||||
key: string;
|
||||
|
|
|
@ -16,6 +16,7 @@ import React from 'react';
|
|||
import { useTrackPageview } from '../../../../../observability/public';
|
||||
import { isRumAgentName } from '../../../../common/agent_name';
|
||||
import { ChartsSyncContextProvider } from '../../../context/charts_sync_context';
|
||||
import { TransactionBreakdownChart } from '../../shared/charts/transaction_breakdown_chart';
|
||||
import { TransactionErrorRateChart } from '../../shared/charts/transaction_error_rate_chart';
|
||||
import { ServiceMapLink } from '../../shared/Links/apm/ServiceMapLink';
|
||||
import { SearchBar } from '../../shared/search_bar';
|
||||
|
@ -103,22 +104,7 @@ export function ServiceOverview({
|
|||
<EuiFlexItem>
|
||||
<EuiFlexGroup gutterSize="s">
|
||||
<EuiFlexItem grow={4}>
|
||||
<EuiPanel>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<EuiTitle size="xs">
|
||||
<h2>
|
||||
{i18n.translate(
|
||||
'xpack.apm.serviceOverview.averageDurationBySpanTypeChartTitle',
|
||||
{
|
||||
defaultMessage: 'Average duration by span type',
|
||||
}
|
||||
)}
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
<TransactionBreakdownChart showAnnotations={false} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={6}>
|
||||
<EuiPanel>
|
||||
|
|
|
@ -17,6 +17,8 @@ import { MockUrlParamsContextProvider } from '../../../context/UrlParamsContext/
|
|||
import * as useDynamicIndexPatternHooks from '../../../hooks/useDynamicIndexPattern';
|
||||
import * as useFetcherHooks from '../../../hooks/useFetcher';
|
||||
import { FETCH_STATUS } from '../../../hooks/useFetcher';
|
||||
import * as useAnnotationsHooks from '../../../hooks/use_annotations';
|
||||
import * as useTransactionBreakdownHooks from '../../../hooks/use_transaction_breakdown';
|
||||
import { renderWithTheme } from '../../../utils/testHelpers';
|
||||
import { ServiceOverview } from './';
|
||||
|
||||
|
@ -53,6 +55,9 @@ function Wrapper({ children }: { children?: ReactNode }) {
|
|||
|
||||
describe('ServiceOverview', () => {
|
||||
it('renders', () => {
|
||||
jest
|
||||
.spyOn(useAnnotationsHooks, 'useAnnotations')
|
||||
.mockReturnValue({ annotations: [] });
|
||||
jest
|
||||
.spyOn(useDynamicIndexPatternHooks, 'useDynamicIndexPattern')
|
||||
.mockReturnValue({
|
||||
|
@ -71,6 +76,13 @@ describe('ServiceOverview', () => {
|
|||
refetch: () => {},
|
||||
status: FETCH_STATUS.SUCCESS,
|
||||
});
|
||||
jest
|
||||
.spyOn(useTransactionBreakdownHooks, 'useTransactionBreakdown')
|
||||
.mockReturnValue({
|
||||
data: { timeseries: [] },
|
||||
error: undefined,
|
||||
status: FETCH_STATUS.SUCCESS,
|
||||
});
|
||||
|
||||
expect(() =>
|
||||
renderWithTheme(<ServiceOverview serviceName="test service name" />, {
|
||||
|
|
|
@ -18,7 +18,6 @@ import {
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { Location } from 'history';
|
||||
import { first } from 'lodash';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { useTrackPageview } from '../../../../../observability/public';
|
||||
|
@ -29,6 +28,7 @@ import { useServiceTransactionTypes } from '../../../hooks/useServiceTransaction
|
|||
import { useTransactionCharts } from '../../../hooks/useTransactionCharts';
|
||||
import { useTransactionList } from '../../../hooks/useTransactionList';
|
||||
import { useUrlParams } from '../../../hooks/useUrlParams';
|
||||
import { useTransactionType } from '../../../hooks/use_transaction_type';
|
||||
import { TransactionCharts } from '../../shared/charts/transaction_charts';
|
||||
import { ElasticDocsLink } from '../../shared/Links/ElasticDocsLink';
|
||||
import { fromQuery, toQuery } from '../../shared/Links/url_helpers';
|
||||
|
@ -41,23 +41,22 @@ import { useRedirect } from './useRedirect';
|
|||
import { UserExperienceCallout } from './user_experience_callout';
|
||||
|
||||
function getRedirectLocation({
|
||||
urlParams,
|
||||
location,
|
||||
serviceTransactionTypes,
|
||||
transactionType,
|
||||
urlParams,
|
||||
}: {
|
||||
location: Location;
|
||||
transactionType?: string;
|
||||
urlParams: IUrlParams;
|
||||
serviceTransactionTypes: string[];
|
||||
}): Location | undefined {
|
||||
const { transactionType } = urlParams;
|
||||
const firstTransactionType = first(serviceTransactionTypes);
|
||||
const transactionTypeFromUrlParams = urlParams.transactionType;
|
||||
|
||||
if (!transactionType && firstTransactionType) {
|
||||
if (!transactionTypeFromUrlParams && transactionType) {
|
||||
return {
|
||||
...location,
|
||||
search: fromQuery({
|
||||
...toQuery(location.search),
|
||||
transactionType: firstTransactionType,
|
||||
transactionType,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
@ -70,19 +69,11 @@ interface TransactionOverviewProps {
|
|||
export function TransactionOverview({ serviceName }: TransactionOverviewProps) {
|
||||
const location = useLocation();
|
||||
const { urlParams } = useUrlParams();
|
||||
const { transactionType } = urlParams;
|
||||
|
||||
// TODO: fetching of transaction types should perhaps be lifted since it is needed in several places. Context?
|
||||
const transactionType = useTransactionType();
|
||||
const serviceTransactionTypes = useServiceTransactionTypes(urlParams);
|
||||
|
||||
// redirect to first transaction type
|
||||
useRedirect(
|
||||
getRedirectLocation({
|
||||
urlParams,
|
||||
location,
|
||||
serviceTransactionTypes,
|
||||
})
|
||||
);
|
||||
useRedirect(getRedirectLocation({ location, transactionType, urlParams }));
|
||||
|
||||
const {
|
||||
data: transactionCharts,
|
|
@ -46,17 +46,7 @@ export function SparkPlot(props: Props) {
|
|||
|
||||
return (
|
||||
<Chart size={{ height: px(24), width }}>
|
||||
<Settings
|
||||
theme={{
|
||||
...chartTheme,
|
||||
background: {
|
||||
...chartTheme.background,
|
||||
color: 'transparent',
|
||||
},
|
||||
}}
|
||||
showLegend={false}
|
||||
tooltip="none"
|
||||
/>
|
||||
<Settings theme={chartTheme} showLegend={false} tooltip="none" />
|
||||
<AreaSeries
|
||||
id="area"
|
||||
xScaleType={ScaleType.Time}
|
||||
|
|
|
@ -16,11 +16,11 @@ import {
|
|||
Position,
|
||||
ScaleType,
|
||||
Settings,
|
||||
SettingsSpec,
|
||||
} from '@elastic/charts';
|
||||
import moment from 'moment';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { useChartTheme } from '../../../../../observability/public';
|
||||
import { TimeSeries } from '../../../../typings/timeseries';
|
||||
import { FETCH_STATUS } from '../../../hooks/useFetcher';
|
||||
import { useUrlParams } from '../../../hooks/useUrlParams';
|
||||
|
@ -59,6 +59,7 @@ export function TimeseriesChart({
|
|||
}: Props) {
|
||||
const history = useHistory();
|
||||
const chartRef = React.createRef<Chart>();
|
||||
const chartTheme = useChartTheme();
|
||||
const { event, setEvent } = useChartsSync();
|
||||
const { urlParams } = useUrlParams();
|
||||
const { start, end } = urlParams;
|
||||
|
@ -74,13 +75,6 @@ export function TimeseriesChart({
|
|||
|
||||
const xFormatter = niceTimeFormatter([min, max]);
|
||||
|
||||
const chartTheme: SettingsSpec['theme'] = {
|
||||
lineSeriesStyle: {
|
||||
point: { visible: false },
|
||||
line: { strokeWidth: 2 },
|
||||
},
|
||||
};
|
||||
|
||||
const isEmpty = timeseries
|
||||
.map((serie) => serie.data)
|
||||
.flat()
|
||||
|
|
|
@ -6,10 +6,16 @@
|
|||
import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiTitle } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
import { useTransactionBreakdown } from '../../../hooks/useTransactionBreakdown';
|
||||
import { TransactionBreakdownGraph } from './TransactionBreakdownGraph';
|
||||
import { useTransactionBreakdown } from '../../../../hooks/use_transaction_breakdown';
|
||||
import { TransactionBreakdownChartContents } from './transaction_breakdown_chart_contents';
|
||||
|
||||
function TransactionBreakdown() {
|
||||
export function TransactionBreakdownChart({
|
||||
height,
|
||||
showAnnotations = true,
|
||||
}: {
|
||||
height?: number;
|
||||
showAnnotations?: boolean;
|
||||
}) {
|
||||
const { data, status } = useTransactionBreakdown();
|
||||
const { timeseries } = data;
|
||||
|
||||
|
@ -20,20 +26,20 @@ function TransactionBreakdown() {
|
|||
<EuiTitle size="xs">
|
||||
<h3>
|
||||
{i18n.translate('xpack.apm.transactionBreakdown.chartTitle', {
|
||||
defaultMessage: 'Time spent by span type',
|
||||
defaultMessage: 'Average duration by span type',
|
||||
})}
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<TransactionBreakdownGraph
|
||||
timeseries={timeseries}
|
||||
<TransactionBreakdownChartContents
|
||||
fetchStatus={status}
|
||||
height={height}
|
||||
showAnnotations={showAnnotations}
|
||||
timeseries={timeseries}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
);
|
||||
}
|
||||
|
||||
export { TransactionBreakdown };
|
|
@ -18,6 +18,7 @@ import {
|
|||
import moment from 'moment';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { useChartTheme } from '../../../../../../observability/public';
|
||||
import { asPercent } from '../../../../../common/utils/formatters';
|
||||
import { TimeSeries } from '../../../../../typings/timeseries';
|
||||
import { FETCH_STATUS } from '../../../../hooks/useFetcher';
|
||||
|
@ -28,16 +29,22 @@ import { Annotations } from '../../charts/annotations';
|
|||
import { ChartContainer } from '../../charts/chart_container';
|
||||
import { onBrushEnd } from '../../charts/helper/helper';
|
||||
|
||||
const XY_HEIGHT = unit * 16;
|
||||
|
||||
interface Props {
|
||||
fetchStatus: FETCH_STATUS;
|
||||
height?: number;
|
||||
showAnnotations: boolean;
|
||||
timeseries?: TimeSeries[];
|
||||
}
|
||||
|
||||
export function TransactionBreakdownGraph({ fetchStatus, timeseries }: Props) {
|
||||
export function TransactionBreakdownChartContents({
|
||||
fetchStatus,
|
||||
height = unit * 16,
|
||||
showAnnotations,
|
||||
timeseries,
|
||||
}: Props) {
|
||||
const history = useHistory();
|
||||
const chartRef = React.createRef<Chart>();
|
||||
const chartTheme = useChartTheme();
|
||||
const { event, setEvent } = useChartsSync2();
|
||||
const { urlParams } = useUrlParams();
|
||||
const { start, end } = urlParams;
|
||||
|
@ -54,17 +61,14 @@ export function TransactionBreakdownGraph({ fetchStatus, timeseries }: Props) {
|
|||
const xFormatter = niceTimeFormatter([min, max]);
|
||||
|
||||
return (
|
||||
<ChartContainer
|
||||
height={XY_HEIGHT}
|
||||
hasData={!!timeseries}
|
||||
status={fetchStatus}
|
||||
>
|
||||
<ChartContainer height={height} hasData={!!timeseries} status={fetchStatus}>
|
||||
<Chart ref={chartRef} id="timeSpentBySpan">
|
||||
<Settings
|
||||
onBrushEnd={({ x }) => onBrushEnd({ x, history })}
|
||||
showLegend
|
||||
showLegendExtra
|
||||
legendPosition={Position.Bottom}
|
||||
theme={chartTheme}
|
||||
xDomain={{ min, max }}
|
||||
flatLegend
|
||||
onPointerUpdate={(currEvent: any) => {
|
||||
|
@ -87,7 +91,7 @@ export function TransactionBreakdownGraph({ fetchStatus, timeseries }: Props) {
|
|||
tickFormat={(y: number) => asPercent(y ?? 0, 1)}
|
||||
/>
|
||||
|
||||
<Annotations />
|
||||
{showAnnotations && <Annotations />}
|
||||
|
||||
{timeseries?.length ? (
|
||||
timeseries.map((serie) => {
|
|
@ -28,7 +28,7 @@ import { IUrlParams } from '../../../../context/UrlParamsContext/types';
|
|||
import { FETCH_STATUS } from '../../../../hooks/useFetcher';
|
||||
import { ITransactionChartData } from '../../../../selectors/chart_selectors';
|
||||
import { isValidCoordinateValue } from '../../../../utils/isValidCoordinateValue';
|
||||
import { TransactionBreakdown } from '../../TransactionBreakdown';
|
||||
import { TransactionBreakdownChart } from '../transaction_breakdown_chart';
|
||||
import { TimeseriesChart } from '../timeseries_chart';
|
||||
import { TransactionErrorRateChart } from '../transaction_error_rate_chart/';
|
||||
import { getResponseTimeTickFormatter } from './helper';
|
||||
|
@ -117,7 +117,7 @@ export function TransactionCharts({
|
|||
<TransactionErrorRateChart />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<TransactionBreakdown />
|
||||
<TransactionBreakdownChart />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGrid>
|
||||
</ChartsSyncContextProvider>
|
||||
|
|
|
@ -7,13 +7,13 @@
|
|||
import { useParams } from 'react-router-dom';
|
||||
import { useFetcher } from './useFetcher';
|
||||
import { useUrlParams } from './useUrlParams';
|
||||
import { useTransactionType } from './use_transaction_type';
|
||||
|
||||
export function useTransactionBreakdown() {
|
||||
const { serviceName } = useParams<{ serviceName?: string }>();
|
||||
const {
|
||||
urlParams: { start, end, transactionName, transactionType },
|
||||
uiFilters,
|
||||
} = useUrlParams();
|
||||
const { urlParams, uiFilters } = useUrlParams();
|
||||
const { start, end, transactionName } = urlParams;
|
||||
const transactionType = useTransactionType();
|
||||
|
||||
const { data = { timeseries: undefined }, error, status } = useFetcher(
|
||||
(callApmApi) => {
|
28
x-pack/plugins/apm/public/hooks/use_transaction_type.ts
Normal file
28
x-pack/plugins/apm/public/hooks/use_transaction_type.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { getFirstTransactionType } from '../../common/agent_name';
|
||||
import { useAgentName } from './useAgentName';
|
||||
import { useServiceTransactionTypes } from './useServiceTransactionTypes';
|
||||
import { useUrlParams } from './useUrlParams';
|
||||
|
||||
/**
|
||||
* Get either the transaction type from the URL parameters, "request"
|
||||
* (for non-RUM agents), "page-load" (for RUM agents) if this service uses them,
|
||||
* or the first available transaction type.
|
||||
*/
|
||||
export function useTransactionType() {
|
||||
const { agentName } = useAgentName();
|
||||
const { urlParams } = useUrlParams();
|
||||
const transactionTypeFromUrlParams = urlParams.transactionType;
|
||||
const transactionTypes = useServiceTransactionTypes(urlParams);
|
||||
const firstTransactionType = getFirstTransactionType(
|
||||
transactionTypes,
|
||||
agentName
|
||||
);
|
||||
|
||||
return transactionTypeFromUrlParams ?? firstTransactionType;
|
||||
}
|
|
@ -8,5 +8,19 @@ import { useTheme } from './use_theme';
|
|||
|
||||
export function useChartTheme() {
|
||||
const theme = useTheme();
|
||||
return theme.darkMode ? EUI_CHARTS_THEME_DARK.theme : EUI_CHARTS_THEME_LIGHT.theme;
|
||||
const baseChartTheme = theme.darkMode
|
||||
? EUI_CHARTS_THEME_DARK.theme
|
||||
: EUI_CHARTS_THEME_LIGHT.theme;
|
||||
|
||||
return {
|
||||
...baseChartTheme,
|
||||
background: {
|
||||
...baseChartTheme.background,
|
||||
color: 'transparent',
|
||||
},
|
||||
lineSeriesStyle: {
|
||||
...baseChartTheme.lineSeriesStyle,
|
||||
point: { visible: false },
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue