mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
* fixing charts * addressing pr comments
This commit is contained in:
parent
cfbb5e97d0
commit
8a40ae98b5
9 changed files with 194 additions and 137 deletions
|
@ -90,7 +90,12 @@ export function ErrorDistribution({ distribution, title }: Props) {
|
|||
showOverlappingTicks
|
||||
tickFormat={xFormatter}
|
||||
/>
|
||||
<Axis id="y-axis" position={Position.Left} ticks={2} showGridLines />
|
||||
<Axis
|
||||
id="y-axis"
|
||||
position={Position.Left}
|
||||
ticks={2}
|
||||
gridLine={{ visible: true }}
|
||||
/>
|
||||
<HistogramBarSeries
|
||||
minBarHeight={2}
|
||||
id="errorOccurrences"
|
||||
|
|
|
@ -224,7 +224,7 @@ export function TransactionDistribution({
|
|||
id="y-axis"
|
||||
position={Position.Left}
|
||||
ticks={3}
|
||||
showGridLines
|
||||
gridLine={{ visible: true }}
|
||||
tickFormat={(value: number) => formatYShort(value)}
|
||||
/>
|
||||
<HistogramBarSeries
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
/*
|
||||
* 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 {
|
||||
AnnotationDomainTypes,
|
||||
LineAnnotation,
|
||||
Position,
|
||||
} from '@elastic/charts';
|
||||
import { EuiIcon } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
import { asAbsoluteDateTime } from '../../../../../common/utils/formatters';
|
||||
import { useTheme } from '../../../../hooks/useTheme';
|
||||
import { useAnnotations } from '../../../../hooks/use_annotations';
|
||||
|
||||
export function Annotations() {
|
||||
const { annotations } = useAnnotations();
|
||||
const theme = useTheme();
|
||||
|
||||
if (!annotations.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const color = theme.eui.euiColorSecondary;
|
||||
|
||||
return (
|
||||
<LineAnnotation
|
||||
id="annotations"
|
||||
domainType={AnnotationDomainTypes.XDomain}
|
||||
dataValues={annotations.map((annotation) => ({
|
||||
dataValue: annotation['@timestamp'],
|
||||
header: asAbsoluteDateTime(annotation['@timestamp']),
|
||||
details: `${i18n.translate('xpack.apm.chart.annotation.version', {
|
||||
defaultMessage: 'Version',
|
||||
})} ${annotation.text}`,
|
||||
}))}
|
||||
style={{ line: { strokeWidth: 1, stroke: color, opacity: 1 } }}
|
||||
marker={<EuiIcon type="dot" color={color} />}
|
||||
markerPosition={Position.Top}
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -5,28 +5,35 @@
|
|||
*/
|
||||
|
||||
import {
|
||||
AnnotationDomainTypes,
|
||||
AreaSeries,
|
||||
Axis,
|
||||
Chart,
|
||||
CurveType,
|
||||
LegendItemListener,
|
||||
LineAnnotation,
|
||||
LineSeries,
|
||||
niceTimeFormatter,
|
||||
Placement,
|
||||
Position,
|
||||
ScaleType,
|
||||
Settings,
|
||||
YDomainRange,
|
||||
} from '@elastic/charts';
|
||||
import { EuiIcon } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import moment from 'moment';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { useChartTheme } from '../../../../../observability/public';
|
||||
import { asAbsoluteDateTime } from '../../../../common/utils/formatters';
|
||||
import { TimeSeries } from '../../../../typings/timeseries';
|
||||
import { FETCH_STATUS } from '../../../hooks/useFetcher';
|
||||
import { useTheme } from '../../../hooks/useTheme';
|
||||
import { useUrlParams } from '../../../hooks/useUrlParams';
|
||||
import { useAnnotations } from '../../../hooks/use_annotations';
|
||||
import { useChartPointerEvent } from '../../../hooks/use_chart_pointer_event';
|
||||
import { unit } from '../../../style/variables';
|
||||
import { Annotations } from './annotations';
|
||||
import { ChartContainer } from './chart_container';
|
||||
import { onBrushEnd } from './helper/helper';
|
||||
|
||||
|
@ -45,6 +52,7 @@ interface Props {
|
|||
*/
|
||||
yTickFormat?: (y: number) => string;
|
||||
showAnnotations?: boolean;
|
||||
yDomain?: YDomainRange;
|
||||
}
|
||||
|
||||
export function TimeseriesChart({
|
||||
|
@ -56,12 +64,16 @@ export function TimeseriesChart({
|
|||
yLabelFormat,
|
||||
yTickFormat,
|
||||
showAnnotations = true,
|
||||
yDomain,
|
||||
}: Props) {
|
||||
const history = useHistory();
|
||||
const chartRef = React.createRef<Chart>();
|
||||
const { annotations } = useAnnotations();
|
||||
const chartTheme = useChartTheme();
|
||||
const { pointerEvent, setPointerEvent } = useChartPointerEvent();
|
||||
const { urlParams } = useUrlParams();
|
||||
const theme = useTheme();
|
||||
|
||||
const { start, end } = urlParams;
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -83,6 +95,8 @@ export function TimeseriesChart({
|
|||
y === null || y === undefined
|
||||
);
|
||||
|
||||
const annotationColor = theme.eui.euiColorSecondary;
|
||||
|
||||
return (
|
||||
<ChartContainer hasData={!isEmpty} height={height} status={fetchStatus}>
|
||||
<Chart ref={chartRef} id={id}>
|
||||
|
@ -108,17 +122,35 @@ export function TimeseriesChart({
|
|||
position={Position.Bottom}
|
||||
showOverlappingTicks
|
||||
tickFormat={xFormatter}
|
||||
gridLine={{ visible: false }}
|
||||
/>
|
||||
<Axis
|
||||
domain={yDomain}
|
||||
id="y-axis"
|
||||
ticks={3}
|
||||
position={Position.Left}
|
||||
tickFormat={yTickFormat ? yTickFormat : yLabelFormat}
|
||||
labelFormat={yLabelFormat}
|
||||
showGridLines
|
||||
/>
|
||||
|
||||
{showAnnotations && <Annotations />}
|
||||
{showAnnotations && (
|
||||
<LineAnnotation
|
||||
id="annotations"
|
||||
domainType={AnnotationDomainTypes.XDomain}
|
||||
dataValues={annotations.map((annotation) => ({
|
||||
dataValue: annotation['@timestamp'],
|
||||
header: asAbsoluteDateTime(annotation['@timestamp']),
|
||||
details: `${i18n.translate('xpack.apm.chart.annotation.version', {
|
||||
defaultMessage: 'Version',
|
||||
})} ${annotation.text}`,
|
||||
}))}
|
||||
style={{
|
||||
line: { strokeWidth: 1, stroke: annotationColor, opacity: 1 },
|
||||
}}
|
||||
marker={<EuiIcon type="dot" color={annotationColor} />}
|
||||
markerPosition={Position.Top}
|
||||
/>
|
||||
)}
|
||||
|
||||
{timeseries.map((serie) => {
|
||||
const Series = serie.type === 'area' ? AreaSeries : LineSeries;
|
||||
|
|
|
@ -5,27 +5,35 @@
|
|||
*/
|
||||
|
||||
import {
|
||||
AnnotationDomainTypes,
|
||||
AreaSeries,
|
||||
Axis,
|
||||
Chart,
|
||||
CurveType,
|
||||
LineAnnotation,
|
||||
niceTimeFormatter,
|
||||
Placement,
|
||||
Position,
|
||||
ScaleType,
|
||||
Settings,
|
||||
} from '@elastic/charts';
|
||||
import { EuiIcon } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
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 {
|
||||
asAbsoluteDateTime,
|
||||
asPercent,
|
||||
} from '../../../../../common/utils/formatters';
|
||||
import { TimeSeries } from '../../../../../typings/timeseries';
|
||||
import { FETCH_STATUS } from '../../../../hooks/useFetcher';
|
||||
import { useTheme } from '../../../../hooks/useTheme';
|
||||
import { useUrlParams } from '../../../../hooks/useUrlParams';
|
||||
import { useAnnotations } from '../../../../hooks/use_annotations';
|
||||
import { useChartPointerEvent } from '../../../../hooks/use_chart_pointer_event';
|
||||
import { unit } from '../../../../style/variables';
|
||||
import { Annotations } from '../../charts/annotations';
|
||||
import { ChartContainer } from '../../charts/chart_container';
|
||||
import { onBrushEnd } from '../../charts/helper/helper';
|
||||
|
||||
|
@ -44,9 +52,11 @@ export function TransactionBreakdownChartContents({
|
|||
}: Props) {
|
||||
const history = useHistory();
|
||||
const chartRef = React.createRef<Chart>();
|
||||
const { annotations } = useAnnotations();
|
||||
const chartTheme = useChartTheme();
|
||||
const { pointerEvent, setPointerEvent } = useChartPointerEvent();
|
||||
const { urlParams } = useUrlParams();
|
||||
const theme = useTheme();
|
||||
const { start, end } = urlParams;
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -64,6 +74,8 @@ export function TransactionBreakdownChartContents({
|
|||
|
||||
const xFormatter = niceTimeFormatter([min, max]);
|
||||
|
||||
const annotationColor = theme.eui.euiColorSecondary;
|
||||
|
||||
return (
|
||||
<ChartContainer height={height} hasData={!!timeseries} status={fetchStatus}>
|
||||
<Chart ref={chartRef} id="timeSpentBySpan">
|
||||
|
@ -85,6 +97,7 @@ export function TransactionBreakdownChartContents({
|
|||
position={Position.Bottom}
|
||||
showOverlappingTicks
|
||||
tickFormat={xFormatter}
|
||||
gridLine={{ visible: false }}
|
||||
/>
|
||||
<Axis
|
||||
id="y-axis"
|
||||
|
@ -93,7 +106,24 @@ export function TransactionBreakdownChartContents({
|
|||
tickFormat={(y: number) => asPercent(y ?? 0, 1)}
|
||||
/>
|
||||
|
||||
{showAnnotations && <Annotations />}
|
||||
{showAnnotations && (
|
||||
<LineAnnotation
|
||||
id="annotations"
|
||||
domainType={AnnotationDomainTypes.XDomain}
|
||||
dataValues={annotations.map((annotation) => ({
|
||||
dataValue: annotation['@timestamp'],
|
||||
header: asAbsoluteDateTime(annotation['@timestamp']),
|
||||
details: `${i18n.translate('xpack.apm.chart.annotation.version', {
|
||||
defaultMessage: 'Version',
|
||||
})} ${annotation.text}`,
|
||||
}))}
|
||||
style={{
|
||||
line: { strokeWidth: 1, stroke: annotationColor, opacity: 1 },
|
||||
}}
|
||||
marker={<EuiIcon type="dot" color={annotationColor} />}
|
||||
markerPosition={Position.Top}
|
||||
/>
|
||||
)}
|
||||
|
||||
{timeseries?.length ? (
|
||||
timeseries.map((serie) => {
|
||||
|
|
|
@ -20,13 +20,14 @@ import {
|
|||
TRANSACTION_ROUTE_CHANGE,
|
||||
} from '../../../../../common/transaction_types';
|
||||
import { asTransactionRate } from '../../../../../common/utils/formatters';
|
||||
import { AnnotationsContextProvider } from '../../../../context/annotations_context';
|
||||
import { ChartPointerEventContextProvider } from '../../../../context/chart_pointer_event_context';
|
||||
import { LicenseContext } from '../../../../context/LicenseContext';
|
||||
import { IUrlParams } from '../../../../context/UrlParamsContext/types';
|
||||
import { FETCH_STATUS } from '../../../../hooks/useFetcher';
|
||||
import { ITransactionChartData } from '../../../../selectors/chart_selectors';
|
||||
import { TransactionBreakdownChart } from '../transaction_breakdown_chart';
|
||||
import { TimeseriesChart } from '../timeseries_chart';
|
||||
import { TransactionBreakdownChart } from '../transaction_breakdown_chart';
|
||||
import { TransactionErrorRateChart } from '../transaction_error_rate_chart/';
|
||||
import { getResponseTimeTickFormatter } from './helper';
|
||||
import { MLHeader } from './ml_header';
|
||||
|
@ -51,65 +52,69 @@ export function TransactionCharts({
|
|||
|
||||
return (
|
||||
<>
|
||||
<ChartPointerEventContextProvider>
|
||||
<EuiFlexGrid columns={2} gutterSize="s">
|
||||
<EuiFlexItem data-cy={`transaction-duration-charts`}>
|
||||
<EuiPanel>
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem>
|
||||
<EuiTitle size="xs">
|
||||
<span>{responseTimeLabel(transactionType)}</span>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<LicenseContext.Consumer>
|
||||
{(license) => (
|
||||
<MLHeader
|
||||
hasValidMlLicense={license?.getFeature('ml').isAvailable}
|
||||
mlJobId={charts.mlJobId}
|
||||
/>
|
||||
)}
|
||||
</LicenseContext.Consumer>
|
||||
</EuiFlexGroup>
|
||||
<TimeseriesChart
|
||||
fetchStatus={fetchStatus}
|
||||
id="transactionDuration"
|
||||
timeseries={responseTimeSeries || []}
|
||||
yLabelFormat={getResponseTimeTickFormatter(formatter)}
|
||||
onToggleLegend={(serie) => {
|
||||
if (serie) {
|
||||
toggleSerie(serie);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
<AnnotationsContextProvider>
|
||||
<ChartPointerEventContextProvider>
|
||||
<EuiFlexGrid columns={2} gutterSize="s">
|
||||
<EuiFlexItem data-cy={`transaction-duration-charts`}>
|
||||
<EuiPanel>
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem>
|
||||
<EuiTitle size="xs">
|
||||
<span>{responseTimeLabel(transactionType)}</span>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<LicenseContext.Consumer>
|
||||
{(license) => (
|
||||
<MLHeader
|
||||
hasValidMlLicense={
|
||||
license?.getFeature('ml').isAvailable
|
||||
}
|
||||
mlJobId={charts.mlJobId}
|
||||
/>
|
||||
)}
|
||||
</LicenseContext.Consumer>
|
||||
</EuiFlexGroup>
|
||||
<TimeseriesChart
|
||||
fetchStatus={fetchStatus}
|
||||
id="transactionDuration"
|
||||
timeseries={responseTimeSeries || []}
|
||||
yLabelFormat={getResponseTimeTickFormatter(formatter)}
|
||||
onToggleLegend={(serie) => {
|
||||
if (serie) {
|
||||
toggleSerie(serie);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem style={{ flexShrink: 1 }}>
|
||||
<EuiPanel>
|
||||
<EuiTitle size="xs">
|
||||
<span>{tpmLabel(transactionType)}</span>
|
||||
</EuiTitle>
|
||||
<TimeseriesChart
|
||||
fetchStatus={fetchStatus}
|
||||
id="requestPerMinutes"
|
||||
timeseries={tpmSeries || []}
|
||||
yLabelFormat={asTransactionRate}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGrid>
|
||||
<EuiFlexItem style={{ flexShrink: 1 }}>
|
||||
<EuiPanel>
|
||||
<EuiTitle size="xs">
|
||||
<span>{tpmLabel(transactionType)}</span>
|
||||
</EuiTitle>
|
||||
<TimeseriesChart
|
||||
fetchStatus={fetchStatus}
|
||||
id="requestPerMinutes"
|
||||
timeseries={tpmSeries || []}
|
||||
yLabelFormat={asTransactionRate}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGrid>
|
||||
|
||||
<EuiSpacer size="s" />
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
<EuiFlexGrid columns={2} gutterSize="s">
|
||||
<EuiFlexItem>
|
||||
<TransactionErrorRateChart />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<TransactionBreakdownChart />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGrid>
|
||||
</ChartPointerEventContextProvider>
|
||||
<EuiFlexGrid columns={2} gutterSize="s">
|
||||
<EuiFlexItem>
|
||||
<TransactionErrorRateChart />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<TransactionBreakdownChart />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGrid>
|
||||
</ChartPointerEventContextProvider>
|
||||
</AnnotationsContextProvider>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -91,6 +91,7 @@ export function TransactionErrorRateChart({
|
|||
]}
|
||||
yLabelFormat={yLabelFormat}
|
||||
yTickFormat={yTickFormat}
|
||||
yDomain={{ min: 0, max: 1 }}
|
||||
/>
|
||||
</EuiPanel>
|
||||
);
|
||||
|
|
49
x-pack/plugins/apm/public/context/annotations_context.tsx
Normal file
49
x-pack/plugins/apm/public/context/annotations_context.tsx
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* 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 React, { createContext } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { Annotation } from '../../common/annotations';
|
||||
import { useFetcher } from '../hooks/useFetcher';
|
||||
import { useUrlParams } from '../hooks/useUrlParams';
|
||||
import { callApmApi } from '../services/rest/createCallApmApi';
|
||||
|
||||
export const AnnotationsContext = createContext({ annotations: [] } as {
|
||||
annotations: Annotation[];
|
||||
});
|
||||
|
||||
const INITIAL_STATE = { annotations: [] };
|
||||
|
||||
export function AnnotationsContextProvider({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const { serviceName } = useParams<{ serviceName?: string }>();
|
||||
const { urlParams, uiFilters } = useUrlParams();
|
||||
const { start, end } = urlParams;
|
||||
const { environment } = uiFilters;
|
||||
|
||||
const { data = INITIAL_STATE } = useFetcher(() => {
|
||||
if (start && end && serviceName) {
|
||||
return callApmApi({
|
||||
endpoint: 'GET /api/apm/services/{serviceName}/annotation/search',
|
||||
params: {
|
||||
path: {
|
||||
serviceName,
|
||||
},
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
environment,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}, [start, end, environment, serviceName]);
|
||||
|
||||
return <AnnotationsContext.Provider value={data} children={children} />;
|
||||
}
|
|
@ -3,36 +3,16 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { callApmApi } from '../services/rest/createCallApmApi';
|
||||
import { useFetcher } from './useFetcher';
|
||||
import { useUrlParams } from './useUrlParams';
|
||||
|
||||
const INITIAL_STATE = { annotations: [] };
|
||||
import { useContext } from 'react';
|
||||
import { AnnotationsContext } from '../context/annotations_context';
|
||||
|
||||
export function useAnnotations() {
|
||||
const { serviceName } = useParams<{ serviceName?: string }>();
|
||||
const { urlParams, uiFilters } = useUrlParams();
|
||||
const { start, end } = urlParams;
|
||||
const { environment } = uiFilters;
|
||||
const context = useContext(AnnotationsContext);
|
||||
|
||||
const { data = INITIAL_STATE } = useFetcher(() => {
|
||||
if (start && end && serviceName) {
|
||||
return callApmApi({
|
||||
endpoint: 'GET /api/apm/services/{serviceName}/annotation/search',
|
||||
params: {
|
||||
path: {
|
||||
serviceName,
|
||||
},
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
environment,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}, [start, end, environment, serviceName]);
|
||||
if (!context) {
|
||||
throw new Error('Missing Annotations context provider');
|
||||
}
|
||||
|
||||
return data;
|
||||
return context;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue