Skip to content

Commit

Permalink
change formatter based on available legends
Browse files Browse the repository at this point in the history
  • Loading branch information
cauemarcondes committed Aug 27, 2020
1 parent 294879e commit ee533a2
Show file tree
Hide file tree
Showing 9 changed files with 366 additions and 139 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,7 @@ export class InnerCustomPlot extends PureComponent {
});

if (typeof this.props.onToggleLegend === 'function') {
//Filters out disabled series
const availableSeries = this.props.series.filter(
(serie, index) => !nextSeriesEnabledState[index]
);
this.props.onToggleLegend(availableSeries);
this.props.onToggleLegend(nextSeriesEnabledState);
}

return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,17 @@
* you may not use this file except in compliance with the Elastic License.
*/

import React from 'react';
import { i18n } from '@kbn/i18n';
import { EuiTitle } from '@elastic/eui';
import { NOT_AVAILABLE_LABEL } from '../../../../../common/i18n';
import { isValidCoordinateValue } from '../../../../utils/isValidCoordinateValue';
import { TransactionLineChart } from './TransactionLineChart';
import { getMaxY } from '.';
import {
getDurationFormatter,
TimeFormatter,
} from '../../../../utils/formatters';
import { i18n } from '@kbn/i18n';
import React from 'react';
import { useAvgDurationByBrowser } from '../../../../hooks/useAvgDurationByBrowser';
import { Coordinate } from '../../../../../typings/timeseries';

function getResponseTimeTickFormatter(formatter: TimeFormatter) {
return (t: number) => formatter(t).formatted;
}

function getResponseTimeTooltipFormatter(formatter: TimeFormatter) {
return (p: Coordinate) => {
return isValidCoordinateValue(p.y)
? formatter(p.y).formatted
: NOT_AVAILABLE_LABEL;
};
}
import { getDurationFormatter } from '../../../../utils/formatters';
import {
getResponseTimeTickFormatter,
getResponseTimeTooltipFormatter,
getMaxY,
} from './helper';
import { TransactionLineChart } from './TransactionLineChart';

export function BrowserLineChart() {
const { data } = useAvgDurationByBrowser();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ interface Props {
height?: number;
stacked?: boolean;
onHover?: () => void;
onToggleLegend?: (visibleSeries: TimeSeries[]) => void;
onToggleLegend?: (disabledSeriesState: boolean[]) => void;
}

function TransactionLineChart(props: Props) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* 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 {
getResponseTimeTickFormatter,
getResponseTimeTooltipFormatter,
getMaxY,
} from './helper';
import {
getDurationFormatter,
toMicroseconds,
} from '../../../../utils/formatters';
import { TimeSeries } from '../../../../../typings/timeseries';

describe('transaction chart helper', () => {
describe('getResponseTimeTickFormatter', () => {
it('formattes time tick in minutes', () => {
const formatter = getDurationFormatter(toMicroseconds(11, 'minutes'));
const timeTickFormatter = getResponseTimeTickFormatter(formatter);
expect(timeTickFormatter(toMicroseconds(60, 'seconds'))).toEqual(
'1.0 min'
);
});
it('formattes time tick in seconds', () => {
const formatter = getDurationFormatter(toMicroseconds(11, 'seconds'));
const timeTickFormatter = getResponseTimeTickFormatter(formatter);
expect(timeTickFormatter(toMicroseconds(6, 'seconds'))).toEqual('6.0 s');
});
});
describe('getResponseTimeTooltipFormatter', () => {
const formatter = getDurationFormatter(toMicroseconds(11, 'minutes'));
const tooltipFormatter = getResponseTimeTooltipFormatter(formatter);
it("doesn't format invalid y coordinate", () => {
expect(tooltipFormatter({ x: 1, y: undefined })).toEqual('N/A');
expect(tooltipFormatter({ x: 1, y: null })).toEqual('N/A');
});
it('formattes tooltip in minutes', () => {
expect(
tooltipFormatter({ x: 1, y: toMicroseconds(60, 'seconds') })
).toEqual('1.0 min');
});
});
describe('getMaxY', () => {
it('returns zero when empty time series', () => {
expect(getMaxY([])).toEqual(0);
});
it('returns zero for invalid y coordinate', () => {
const timeSeries = ([
{ data: [{ x: 1 }, { x: 2 }, { x: 3, y: -1 }] },
] as unknown) as TimeSeries[];
expect(getMaxY(timeSeries)).toEqual(0);
});
it('returns the max y coordinate', () => {
const timeSeries = ([
{
data: [
{ x: 1, y: 10 },
{ x: 2, y: 5 },
{ x: 3, y: 1 },
],
},
] as unknown) as TimeSeries[];
expect(getMaxY(timeSeries)).toEqual(10);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* 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 { flatten } from 'lodash';
import { NOT_AVAILABLE_LABEL } from '../../../../../common/i18n';
import { isValidCoordinateValue } from '../../../../utils/isValidCoordinateValue';
import { TimeSeries, Coordinate } from '../../../../../typings/timeseries';
import { TimeFormatter } from '../../../../utils/formatters';

export function getResponseTimeTickFormatter(formatter: TimeFormatter) {
return (t: number) => {
return formatter(t).formatted;
};
}

export function getResponseTimeTooltipFormatter(formatter: TimeFormatter) {
return (coordinate: Coordinate) => {
return isValidCoordinateValue(coordinate.y)
? formatter(coordinate.y).formatted
: NOT_AVAILABLE_LABEL;
};
}

export function getMaxY(timeSeries: TimeSeries[]) {
const coordinates = flatten(
timeSeries.map((serie: TimeSeries) => serie.data as Coordinate[])
);

const numbers: number[] = coordinates.map((c: Coordinate) => (c.y ? c.y : 0));

return Math.max(...numbers, 0);
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,66 +8,41 @@ import {
EuiFlexGrid,
EuiFlexGroup,
EuiFlexItem,
EuiIconTip,
EuiPanel,
EuiSpacer,
EuiText,
EuiTitle,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { Location } from 'history';
import { flatten, isEmpty } from 'lodash';
import React from 'react';
import styled from 'styled-components';
import { NOT_AVAILABLE_LABEL } from '../../../../../common/i18n';
import {
TRANSACTION_PAGE_LOAD,
TRANSACTION_REQUEST,
TRANSACTION_ROUTE_CHANGE,
} from '../../../../../common/transaction_types';
import { Coordinate, TimeSeries } from '../../../../../typings/timeseries';
import { Coordinate } from '../../../../../typings/timeseries';
import { LicenseContext } from '../../../../context/LicenseContext';
import { IUrlParams } from '../../../../context/UrlParamsContext/types';
import { ITransactionChartData } from '../../../../selectors/chartSelectors';
import {
asDecimal,
getDurationFormatter,
tpmUnit,
} from '../../../../utils/formatters';
import { asDecimal, tpmUnit } from '../../../../utils/formatters';
import { isValidCoordinateValue } from '../../../../utils/isValidCoordinateValue';
import { MLJobLink } from '../../Links/MachineLearningLinks/MLJobLink';
import { BrowserLineChart } from './BrowserLineChart';
import { DurationByCountryMap } from './DurationByCountryMap';
import {
getResponseTimeTickFormatter,
getResponseTimeTooltipFormatter,
} from './helper';
import { MLHeader } from './ml_header';
import { TransactionLineChart } from './TransactionLineChart';
import { useFormatter } from './use_formatter';

interface TransactionChartProps {
charts: ITransactionChartData;
location: Location;
urlParams: IUrlParams;
}

const ShiftedIconWrapper = styled.span`
padding-right: 5px;
position: relative;
top: -1px;
display: inline-block;
`;

const ShiftedEuiText = styled(EuiText)`
position: relative;
top: 5px;
`;

export function getMaxY(responseTimeSeries: TimeSeries[]) {
const coordinates = flatten(
responseTimeSeries.map((serie: TimeSeries) => serie.data as Coordinate[])
);

const numbers: number[] = coordinates.map((c: Coordinate) => (c.y ? c.y : 0));

return Math.max(...numbers, 0);
}

export function TransactionCharts({
charts,
location,
Expand All @@ -84,82 +59,16 @@ export function TransactionCharts({
: NOT_AVAILABLE_LABEL;
};

function renderMLHeader(hasValidMlLicense: boolean | undefined) {
const { mlJobId } = charts;

if (!hasValidMlLicense || !mlJobId) {
return null;
}

const { serviceName, kuery, transactionType } = urlParams;
if (!serviceName) {
return null;
}

const hasKuery = !isEmpty(kuery);
const icon = hasKuery ? (
<EuiIconTip
aria-label="Warning"
type="alert"
color="warning"
content="The Machine learning results are hidden when the search bar is used for filtering"
/>
) : (
<EuiIconTip
content={i18n.translate(
'xpack.apm.metrics.transactionChart.machineLearningTooltip',
{
defaultMessage:
'The stream around the average duration shows the expected bounds. An annotation is shown for anomaly scores ≥ 75.',
}
)}
/>
);

return (
<EuiFlexItem grow={false}>
<ShiftedEuiText size="xs">
<ShiftedIconWrapper>{icon}</ShiftedIconWrapper>
<span>
{i18n.translate(
'xpack.apm.metrics.transactionChart.machineLearningLabel',
{
defaultMessage: 'Machine learning:',
}
)}{' '}
</span>
<MLJobLink
jobId={mlJobId}
serviceName={serviceName}
transactionType={transactionType}
>
View Job
</MLJobLink>
</ShiftedEuiText>
</EuiFlexItem>
);
}
const { responseTimeSeries, tpmSeries } = charts;
const { transactionType } = urlParams;
const maxY = getMaxY(responseTimeSeries);
let formatter = getDurationFormatter(maxY);

function onToggleLegend(visibleSeries: TimeSeries[]) {
if (!isEmpty(visibleSeries)) {
// recalculate the formatter based on the max Y from the visible series
const maxVisibleY = getMaxY(visibleSeries);
formatter = getDurationFormatter(maxVisibleY);
}
}
const { responseTimeSeries, tpmSeries } = charts;

function getResponseTimeTickFormatter(t: number) {
return formatter(t).formatted;
}
const { formatter, setDisabledSeriesState } = useFormatter(
responseTimeSeries
);

function getResponseTimeTooltipFormatter(coordinate: Coordinate) {
return isValidCoordinateValue(coordinate.y)
? formatter(coordinate.y).formatted
: NOT_AVAILABLE_LABEL;
function onToggleLegend(disabledSeriesStates: boolean[]) {
setDisabledSeriesState(disabledSeriesStates);
}

return (
Expand All @@ -175,15 +84,18 @@ export function TransactionCharts({
</EuiTitle>
</EuiFlexItem>
<LicenseContext.Consumer>
{(license) =>
renderMLHeader(license?.getFeature('ml').isAvailable)
}
{(license) => (
<MLHeader
hasValidMlLicense={license?.getFeature('ml').isAvailable}
mlJobId={charts.mlJobId}
/>
)}
</LicenseContext.Consumer>
</EuiFlexGroup>
<TransactionLineChart
series={responseTimeSeries}
tickFormatY={getResponseTimeTickFormatter}
formatTooltipValue={getResponseTimeTooltipFormatter}
tickFormatY={getResponseTimeTickFormatter(formatter)}
formatTooltipValue={getResponseTimeTooltipFormatter(formatter)}
onToggleLegend={onToggleLegend}
/>
</React.Fragment>
Expand Down
Loading

0 comments on commit ee533a2

Please sign in to comment.