Skip to content

Commit

Permalink
[APM] Backends inventory & overview page routes (#106223)
Browse files Browse the repository at this point in the history
  • Loading branch information
dgieselaar committed Jul 21, 2021
1 parent 2e0fdda commit 3039db2
Show file tree
Hide file tree
Showing 23 changed files with 684 additions and 18 deletions.
4 changes: 2 additions & 2 deletions src/core/types/elasticsearch/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ type Source = estypes.SearchSourceFilter | boolean | estypes.Fields;

type ValueTypeOfField<T> = T extends Record<string, string | number>
? ValuesType<T>
: T extends string[] | number[]
? ValueTypeOfField<ValuesType<T>>
: T extends Array<infer U>
? ValueTypeOfField<U>
: T extends { field: estypes.Field }
? T['field']
: T extends string | number
Expand Down
24 changes: 24 additions & 0 deletions x-pack/plugins/apm/common/utils/get_offset_in_ms.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import moment from 'moment';
import { parseInterval } from '../../../../../src/plugins/data/common';

export function getOffsetInMs(start: number, offset?: string) {
if (!offset) {
return 0;
}

const interval = parseInterval(offset);

if (!interval) {
throw new Error(`Could not parse offset: ${offset}`);
}

const calculatedOffset = start - moment(start).subtract(interval).valueOf();

return calculatedOffset;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React, { useMemo } from 'react';
import { i18n } from '@kbn/i18n';
import { getDurationFormatter } from '../../../../common/utils/formatters';
import { useApmBackendContext } from '../../../context/apm_backend/use_apm_backend_context';
import { useUrlParams } from '../../../context/url_params_context/use_url_params';
import { useComparison } from '../../../hooks/use_comparison';
import { useFetcher } from '../../../hooks/use_fetcher';
import { useTimeRange } from '../../../hooks/use_time_range';
import { Coordinate, TimeSeries } from '../../../../typings/timeseries';
import { TimeseriesChart } from '../../shared/charts/timeseries_chart';
import { useTheme } from '../../../hooks/use_theme';
import {
getMaxY,
getResponseTimeTickFormatter,
} from '../../shared/charts/transaction_charts/helper';

export function BackendLatencyChart({ height }: { height: number }) {
const { backendName } = useApmBackendContext();

const theme = useTheme();

const { start, end } = useTimeRange();

const {
urlParams: { kuery, environment },
} = useUrlParams();

const { offset, comparisonChartTheme } = useComparison();

const { data, status } = useFetcher(
(callApmApi) => {
if (!start || !end) {
return;
}

return callApmApi({
endpoint: 'GET /api/apm/backends/{backendName}/charts/latency',
params: {
path: {
backendName,
},
query: {
start,
end,
offset,
kuery,
environment,
},
},
});
},
[backendName, start, end, offset, kuery, environment]
);

const timeseries = useMemo(() => {
const specs: Array<TimeSeries<Coordinate>> = [];

if (data?.currentTimeseries) {
specs.push({
data: data.currentTimeseries,
type: 'linemark',
color: theme.eui.euiColorVis0,
title: i18n.translate('xpack.apm.backendLatencyChart.chartTitle', {
defaultMessage: 'Latency',
}),
});
}

if (data?.comparisonTimeseries) {
specs.push({
data: data.comparisonTimeseries,
type: 'area',
color: theme.eui.euiColorMediumShade,
title: i18n.translate(
'xpack.apm.backendLatencyChart.previousPeriodLabel',
{ defaultMessage: 'Previous period' }
),
});
}

return specs;
}, [data, theme.eui.euiColorVis0, theme.eui.euiColorMediumShade]);

const maxY = getMaxY(timeseries);
const latencyFormatter = getDurationFormatter(maxY);

return (
<TimeseriesChart
height={height}
fetchStatus={status}
id="latencyChart"
customTheme={comparisonChartTheme}
timeseries={timeseries}
yLabelFormat={getResponseTimeTickFormatter(latencyFormatter)}
/>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { EuiFlexItem } from '@elastic/eui';
import { EuiPanel } from '@elastic/eui';
import { EuiFlexGroup } from '@elastic/eui';
import React from 'react';
import { ApmBackendContextProvider } from '../../../context/apm_backend/apm_backend_context';
import { useBreadcrumb } from '../../../context/breadcrumbs/use_breadcrumb';
import { ChartPointerEventContextProvider } from '../../../context/chart_pointer_event/chart_pointer_event_context';
import { useApmParams } from '../../../hooks/use_apm_params';
import { useApmRouter } from '../../../hooks/use_apm_router';
import { ApmMainTemplate } from '../../routing/templates/apm_main_template';
import { SearchBar } from '../../shared/search_bar';
import { BackendLatencyChart } from './backend_latency_chart';
import { BackendInventoryTitle } from '../../routing/home';

export function BackendDetailOverview() {
const {
path: { backendName },
query,
} = useApmParams('/backends/:backendName/overview');

const apmRouter = useApmRouter();

useBreadcrumb([
{
title: BackendInventoryTitle,
href: apmRouter.link('/backends'),
},
{
title: backendName,
href: apmRouter.link('/backends/:backendName/overview', {
path: { backendName },
query,
}),
},
]);

return (
<ApmMainTemplate pageTitle={backendName}>
<ApmBackendContextProvider>
<SearchBar showTimeComparison />
<ChartPointerEventContextProvider>
<EuiFlexGroup direction="column" gutterSize="s">
<EuiFlexItem>
<EuiPanel hasBorder={true}>
<BackendLatencyChart height={200} />
</EuiPanel>
</EuiFlexItem>
</EuiFlexGroup>
</ChartPointerEventContextProvider>
</ApmBackendContextProvider>
</ApmMainTemplate>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import React from 'react';
import { SearchBar } from '../../shared/search_bar';

export function BackendInventory() {
return <SearchBar showTimeComparison />;
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ import {
import { i18n } from '@kbn/i18n';
import { keyBy } from 'lodash';
import React from 'react';
import { EuiLink } from '@elastic/eui';
import { useApmParams } from '../../../../hooks/use_apm_params';
import { useApmRouter } from '../../../../hooks/use_apm_router';
import { getNextEnvironmentUrlParam } from '../../../../../common/environment_filter_values';
import {
asMillisecondDuration,
Expand Down Expand Up @@ -97,13 +100,19 @@ export function ServiceOverviewDependenciesTable({ serviceName }: Props) {
urlParams: { start, end, environment, comparisonEnabled, comparisonType },
} = useUrlParams();

const {
query: { rangeFrom, rangeTo, kuery },
} = useApmParams('/services/:serviceName/overview');

const { comparisonStart, comparisonEnd } = getTimeRangeComparison({
start,
end,
comparisonEnabled,
comparisonType,
});

const apmRouter = useApmRouter();

const columns: Array<EuiBasicTableColumn<ServiceDependencyPeriods>> = [
{
field: 'name',
Expand Down Expand Up @@ -138,7 +147,14 @@ export function ServiceOverviewDependenciesTable({ serviceName }: Props) {
{item.name}
</ServiceOverviewLink>
) : (
item.name
<EuiLink
href={apmRouter.link('/backends/:backendName/overview', {
path: { backendName: item.name },
query: { rangeFrom, rangeTo, kuery, environment },
})}
>
{item.name}
</EuiLink>
)}
</EuiFlexItem>
</EuiFlexGroup>
Expand Down
31 changes: 31 additions & 0 deletions x-pack/plugins/apm/public/components/routing/home/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { Outlet } from '@kbn/typed-react-router-config';
import * as t from 'io-ts';
import React from 'react';
import { Redirect } from 'react-router-dom';
import { BackendDetailOverview } from '../../app/backend_detail_overview';
import { BackendInventory } from '../../app/backend_inventory';
import { Breadcrumb } from '../../app/breadcrumb';
import { ServiceInventory } from '../../app/service_inventory';
import { ServiceMap } from '../../app/service_map';
Expand Down Expand Up @@ -41,13 +43,22 @@ export const ServiceInventoryTitle = i18n.translate(
}
);

export const BackendInventoryTitle = i18n.translate(
'xpack.apm.views.backendInventory.title',
{
defaultMessage: 'Backends',
}
);

export const home = {
path: '/',
element: <Outlet />,
params: t.partial({
query: t.partial({
rangeFrom: t.string,
rangeTo: t.string,
environment: t.string,
kuery: t.string,
}),
}),
children: [
Expand All @@ -70,6 +81,26 @@ export const home = {
}),
element: <ServiceMap />,
}),
{
path: '/backends',
element: <Outlet />,
children: [
{
path: '/:backendName/overview',
element: <BackendDetailOverview />,
params: t.type({
path: t.type({
backendName: t.string,
}),
}),
},
page({
path: '/',
title: BackendInventoryTitle,
element: <BackendInventory />,
}),
],
},
{
path: '/',
element: <Redirect to="/services" />,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ describe('getTimeRangeComparison', () => {
});
expect(result.comparisonStart).toEqual('2021-01-27T14:45:00.000Z');
expect(result.comparisonEnd).toEqual('2021-01-27T15:00:00.000Z');
expect(result.offset).toEqual('1d');
});
});
describe('when a week before is selected', () => {
Expand All @@ -71,6 +72,7 @@ describe('getTimeRangeComparison', () => {
});
expect(result.comparisonStart).toEqual('2021-01-21T14:45:00.000Z');
expect(result.comparisonEnd).toEqual('2021-01-21T15:00:00.000Z');
expect(result.offset).toEqual('1w');
});
});
describe('when previous period is selected', () => {
Expand All @@ -86,6 +88,7 @@ describe('getTimeRangeComparison', () => {
expect(result).toEqual({
comparisonStart: '2021-02-09T14:24:02.174Z',
comparisonEnd: '2021-02-09T14:40:01.087Z',
offset: '958913ms',
});
});
});
Expand All @@ -104,6 +107,7 @@ describe('getTimeRangeComparison', () => {
});
expect(result.comparisonStart).toEqual('2021-01-19T15:00:00.000Z');
expect(result.comparisonEnd).toEqual('2021-01-21T15:00:00.000Z');
expect(result.offset).toEqual('1w');
});
});
});
Expand All @@ -120,6 +124,7 @@ describe('getTimeRangeComparison', () => {
});
expect(result.comparisonStart).toEqual('2021-01-02T15:00:00.000Z');
expect(result.comparisonEnd).toEqual('2021-01-10T15:00:00.000Z');
expect(result.offset).toEqual('691200000ms');
});

it('uses the date difference to calculate the time range - 30 days', () => {
Expand All @@ -133,6 +138,7 @@ describe('getTimeRangeComparison', () => {
});
expect(result.comparisonStart).toEqual('2020-12-02T15:00:00.000Z');
expect(result.comparisonEnd).toEqual('2021-01-01T15:00:00.000Z');
expect(result.offset).toEqual('2592000000ms');
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,17 @@ export function getTimeRangeComparison({
const endEpoch = endMoment.valueOf();

let diff: number;
let offset: string;

switch (comparisonType) {
case TimeRangeComparisonType.DayBefore:
diff = oneDayInMilliseconds;
offset = '1d';
break;

case TimeRangeComparisonType.WeekBefore:
diff = oneWeekInMilliseconds;
offset = '1w';
break;

case TimeRangeComparisonType.PeriodBefore:
Expand All @@ -77,6 +80,7 @@ export function getTimeRangeComparison({
unitOfTime: 'milliseconds',
precise: true,
});
offset = `${diff}ms`;
break;

default:
Expand All @@ -86,5 +90,6 @@ export function getTimeRangeComparison({
return {
comparisonStart: new Date(startEpoch - diff).toISOString(),
comparisonEnd: new Date(endEpoch - diff).toISOString(),
offset,
};
}

0 comments on commit 3039db2

Please sign in to comment.