[Observability] Change appLink passing the date range (#71259)

* changing apm appLink

* changing apm appLink

* removing title from api

* adding absolute and relative times

* addressing pr comments

* addressing pr comments

* addressing pr comments

* fixing TS issues

* addressing pr comments

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Cauê Marcondes 2020-07-14 10:05:48 +01:00 committed by GitHub
parent 831e427682
commit 3374b2d3b0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 255 additions and 221 deletions

View file

@ -39,9 +39,9 @@ import { toggleAppLinkInNav } from './toggleAppLinkInNav';
import { setReadonlyBadge } from './updateBadge';
import { createStaticIndexPattern } from './services/rest/index_pattern';
import {
fetchLandingPageData,
fetchOverviewPageData,
hasData,
} from './services/rest/observability_dashboard';
} from './services/rest/apm_overview_fetchers';
export type ApmPluginSetup = void;
export type ApmPluginStart = void;
@ -81,9 +81,7 @@ export class ApmPlugin implements Plugin<ApmPluginSetup, ApmPluginStart> {
if (plugins.observability) {
plugins.observability.dashboard.register({
appName: 'apm',
fetchData: async (params) => {
return fetchLandingPageData(params);
},
fetchData: fetchOverviewPageData,
hasData,
});
}

View file

@ -4,11 +4,23 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { fetchLandingPageData, hasData } from './observability_dashboard';
import moment from 'moment';
import { fetchOverviewPageData, hasData } from './apm_overview_fetchers';
import * as createCallApmApi from './createCallApmApi';
describe('Observability dashboard data', () => {
const callApmApiMock = jest.spyOn(createCallApmApi, 'callApmApi');
const params = {
absoluteTime: {
start: moment('2020-07-02T13:25:11.629Z').valueOf(),
end: moment('2020-07-09T14:25:11.629Z').valueOf(),
},
relativeTime: {
start: 'now-15m',
end: 'now',
},
bucketSize: '600s',
};
afterEach(() => {
callApmApiMock.mockClear();
});
@ -25,7 +37,7 @@ describe('Observability dashboard data', () => {
});
});
describe('fetchLandingPageData', () => {
describe('fetchOverviewPageData', () => {
it('returns APM data with series and stats', async () => {
callApmApiMock.mockImplementation(() =>
Promise.resolve({
@ -37,14 +49,9 @@ describe('Observability dashboard data', () => {
],
})
);
const response = await fetchLandingPageData({
startTime: '1',
endTime: '2',
bucketSize: '3',
});
const response = await fetchOverviewPageData(params);
expect(response).toEqual({
title: 'APM',
appLink: '/app/apm',
appLink: '/app/apm#/services?rangeFrom=now-15m&rangeTo=now',
stats: {
services: {
type: 'number',
@ -73,14 +80,9 @@ describe('Observability dashboard data', () => {
transactionCoordinates: [],
})
);
const response = await fetchLandingPageData({
startTime: '1',
endTime: '2',
bucketSize: '3',
});
const response = await fetchOverviewPageData(params);
expect(response).toEqual({
title: 'APM',
appLink: '/app/apm',
appLink: '/app/apm#/services?rangeFrom=now-15m&rangeTo=now',
stats: {
services: {
type: 'number',
@ -105,14 +107,9 @@ describe('Observability dashboard data', () => {
transactionCoordinates: [{ x: 1 }, { x: 2 }, { x: 3 }],
})
);
const response = await fetchLandingPageData({
startTime: '1',
endTime: '2',
bucketSize: '3',
});
const response = await fetchOverviewPageData(params);
expect(response).toEqual({
title: 'APM',
appLink: '/app/apm',
appLink: '/app/apm#/services?rangeFrom=now-15m&rangeTo=now',
stats: {
services: {
type: 'number',

View file

@ -4,7 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { i18n } from '@kbn/i18n';
import { mean } from 'lodash';
import {
ApmFetchDataResponse,
@ -12,23 +11,26 @@ import {
} from '../../../../observability/public';
import { callApmApi } from './createCallApmApi';
export const fetchLandingPageData = async ({
startTime,
endTime,
export const fetchOverviewPageData = async ({
absoluteTime,
relativeTime,
bucketSize,
}: FetchDataParams): Promise<ApmFetchDataResponse> => {
const data = await callApmApi({
pathname: '/api/apm/observability_dashboard',
params: { query: { start: startTime, end: endTime, bucketSize } },
pathname: '/api/apm/observability_overview',
params: {
query: {
start: new Date(absoluteTime.start).toISOString(),
end: new Date(absoluteTime.end).toISOString(),
bucketSize,
},
},
});
const { serviceCount, transactionCoordinates } = data;
return {
title: i18n.translate('xpack.apm.observabilityDashboard.title', {
defaultMessage: 'APM',
}),
appLink: '/app/apm',
appLink: `/app/apm#/services?rangeFrom=${relativeTime.start}&rangeTo=${relativeTime.end}`,
stats: {
services: {
type: 'number',
@ -54,6 +56,6 @@ export const fetchLandingPageData = async ({
export async function hasData() {
return await callApmApi({
pathname: '/api/apm/observability_dashboard/has_data',
pathname: '/api/apm/observability_overview/has_data',
});
}

View file

@ -79,9 +79,9 @@ import {
rumServicesRoute,
} from './rum_client';
import {
observabilityDashboardHasDataRoute,
observabilityDashboardDataRoute,
} from './observability_dashboard';
observabilityOverviewHasDataRoute,
observabilityOverviewRoute,
} from './observability_overview';
import {
anomalyDetectionJobsRoute,
createAnomalyDetectionJobsRoute,
@ -176,8 +176,8 @@ const createApmApi = () => {
.add(rumServicesRoute)
// Observability dashboard
.add(observabilityDashboardHasDataRoute)
.add(observabilityDashboardDataRoute)
.add(observabilityOverviewHasDataRoute)
.add(observabilityOverviewRoute)
// Anomaly detection
.add(anomalyDetectionJobsRoute)

View file

@ -5,22 +5,22 @@
*/
import * as t from 'io-ts';
import { setupRequest } from '../lib/helpers/setup_request';
import { hasData } from '../lib/observability_dashboard/has_data';
import { getServiceCount } from '../lib/observability_overview/get_service_count';
import { getTransactionCoordinates } from '../lib/observability_overview/get_transaction_coordinates';
import { hasData } from '../lib/observability_overview/has_data';
import { createRoute } from './create_route';
import { rangeRt } from './default_api_types';
import { getServiceCount } from '../lib/observability_dashboard/get_service_count';
import { getTransactionCoordinates } from '../lib/observability_dashboard/get_transaction_coordinates';
export const observabilityDashboardHasDataRoute = createRoute(() => ({
path: '/api/apm/observability_dashboard/has_data',
export const observabilityOverviewHasDataRoute = createRoute(() => ({
path: '/api/apm/observability_overview/has_data',
handler: async ({ context, request }) => {
const setup = await setupRequest(context, request);
return await hasData({ setup });
},
}));
export const observabilityDashboardDataRoute = createRoute(() => ({
path: '/api/apm/observability_dashboard',
export const observabilityOverviewRoute = createRoute(() => ({
path: '/api/apm/observability_overview',
params: {
query: t.intersection([rangeRt, t.type({ bucketSize: t.string })]),
},

View file

@ -2,7 +2,7 @@
exports[`Metrics UI Observability Homepage Functions createMetricsFetchData() should just work 1`] = `
Object {
"appLink": "/app/metrics",
"appLink": "/app/metrics/inventory?waffleTime=(currentTime:1593696311629,isAutoReloading:!f)",
"series": Object {
"inboundTraffic": Object {
"coordinates": Array [
@ -203,6 +203,5 @@ Object {
"value": 3,
},
},
"title": "Metrics",
}
`;

View file

@ -53,12 +53,18 @@ describe('Metrics UI Observability Homepage Functions', () => {
const { core, mockedGetStartServices } = setup();
core.http.post.mockResolvedValue(FAKE_SNAPSHOT_RESPONSE);
const fetchData = createMetricsFetchData(mockedGetStartServices);
const endTime = moment();
const endTime = moment('2020-07-02T13:25:11.629Z');
const startTime = endTime.clone().subtract(1, 'h');
const bucketSize = '300s';
const response = await fetchData({
startTime: startTime.toISOString(),
endTime: endTime.toISOString(),
absoluteTime: {
start: startTime.valueOf(),
end: endTime.valueOf(),
},
relativeTime: {
start: 'now-15m',
end: 'now',
},
bucketSize,
});
expect(core.http.post).toHaveBeenCalledTimes(1);

View file

@ -4,15 +4,13 @@
* you may not use this file except in compliance with the Elastic License.
*/
import moment from 'moment';
import { sum, isFinite, isNumber } from 'lodash';
import { i18n } from '@kbn/i18n';
import { MetricsFetchDataResponse, FetchDataParams } from '../../observability/public';
import { isFinite, isNumber, sum } from 'lodash';
import { FetchDataParams, MetricsFetchDataResponse } from '../../observability/public';
import {
SnapshotRequest,
SnapshotMetricInput,
SnapshotNode,
SnapshotNodeResponse,
SnapshotRequest,
} from '../common/http_api/snapshot_api';
import { SnapshotMetricType } from '../common/inventory_models/types';
import { InfraClientCoreSetup } from './types';
@ -77,13 +75,12 @@ export const combineNodeTimeseriesBy = (
export const createMetricsFetchData = (
getStartServices: InfraClientCoreSetup['getStartServices']
) => async ({
startTime,
endTime,
bucketSize,
}: FetchDataParams): Promise<MetricsFetchDataResponse> => {
) => async ({ absoluteTime, bucketSize }: FetchDataParams): Promise<MetricsFetchDataResponse> => {
const [coreServices] = await getStartServices();
const { http } = coreServices;
const { start, end } = absoluteTime;
const snapshotRequest: SnapshotRequest = {
sourceId: 'default',
metrics: ['cpu', 'memory', 'rx', 'tx'].map((type) => ({ type })) as SnapshotMetricInput[],
@ -91,8 +88,8 @@ export const createMetricsFetchData = (
nodeType: 'host',
includeTimeseries: true,
timerange: {
from: moment(startTime).valueOf(),
to: moment(endTime).valueOf(),
from: start,
to: end,
interval: bucketSize,
forceInterval: true,
ignoreLookback: true,
@ -102,12 +99,8 @@ export const createMetricsFetchData = (
const results = await http.post<SnapshotNodeResponse>('/api/metrics/snapshot', {
body: JSON.stringify(snapshotRequest),
});
return {
title: i18n.translate('xpack.infra.observabilityHomepage.metrics.title', {
defaultMessage: 'Metrics',
}),
appLink: '/app/metrics',
appLink: `/app/metrics/inventory?waffleTime=(currentTime:${end},isAutoReloading:!f)`,
stats: {
hosts: {
type: 'number',

View file

@ -5,18 +5,17 @@
*/
import { encode } from 'rison-node';
import { i18n } from '@kbn/i18n';
import { SearchResponse } from 'src/plugins/data/public';
import { DEFAULT_SOURCE_ID } from '../../common/constants';
import { InfraClientCoreSetup, InfraClientStartDeps } from '../types';
import {
FetchData,
LogsFetchDataResponse,
HasData,
FetchDataParams,
HasData,
LogsFetchDataResponse,
} from '../../../observability/public';
import { DEFAULT_SOURCE_ID } from '../../common/constants';
import { callFetchLogSourceConfigurationAPI } from '../containers/logs/log_source/api/fetch_log_source_configuration';
import { callFetchLogSourceStatusAPI } from '../containers/logs/log_source/api/fetch_log_source_status';
import { InfraClientCoreSetup, InfraClientStartDeps } from '../types';
interface StatsAggregation {
buckets: Array<{ key: string; doc_count: number }>;
@ -69,15 +68,11 @@ export function getLogsOverviewDataFetcher(
data
);
const timeSpanInMinutes =
(Date.parse(params.endTime).valueOf() - Date.parse(params.startTime).valueOf()) / (1000 * 60);
const timeSpanInMinutes = (params.absoluteTime.end - params.absoluteTime.start) / (1000 * 60);
return {
title: i18n.translate('xpack.infra.logs.logOverview.logOverviewTitle', {
defaultMessage: 'Logs',
}),
appLink: `/app/logs/stream?logPosition=(end:${encode(params.endTime)},start:${encode(
params.startTime
appLink: `/app/logs/stream?logPosition=(end:${encode(params.relativeTime.end)},start:${encode(
params.relativeTime.start
)})`,
stats: normalizeStats(stats, timeSpanInMinutes),
series: normalizeSeries(series),
@ -122,8 +117,8 @@ function buildLogOverviewQuery(logParams: LogParams, params: FetchDataParams) {
return {
range: {
[logParams.timestampField]: {
gt: params.startTime,
lte: params.endTime,
gt: new Date(params.absoluteTime.start).toISOString(),
lte: new Date(params.absoluteTime.end).toISOString(),
format: 'strict_date_optional_time',
},
},

View file

@ -44,12 +44,16 @@ export const AlertsSection = ({ alerts }: Props) => {
return (
<SectionContainer
title="Alerts"
appLink={'/app/management/insightsAndAlerting/triggersActions/alerts'}
hasError={false}
appLinkName={i18n.translate('xpack.observability.overview.alert.appLink', {
defaultMessage: 'Manage alerts',
title={i18n.translate('xpack.observability.overview.alerts.title', {
defaultMessage: 'Alerts',
})}
appLink={{
href: '/app/management/insightsAndAlerting/triggersActions/alerts',
label: i18n.translate('xpack.observability.overview.alert.appLink', {
defaultMessage: 'Manage alerts',
}),
}}
hasError={false}
>
<EuiFlexGroup direction="column" gutterSize="none">
<EuiFlexItem grow={false}>

View file

@ -8,6 +8,7 @@ import * as fetcherHook from '../../../../hooks/use_fetcher';
import { render } from '../../../../utils/test_helper';
import { APMSection } from './';
import { response } from './mock_data/apm.mock';
import moment from 'moment';
describe('APMSection', () => {
it('renders with transaction series and stats', () => {
@ -18,8 +19,11 @@ describe('APMSection', () => {
});
const { getByText, queryAllByTestId } = render(
<APMSection
startTime="2020-06-29T11:38:23.747Z"
endTime="2020-06-29T12:08:23.748Z"
absoluteTime={{
start: moment('2020-06-29T11:38:23.747Z').valueOf(),
end: moment('2020-06-29T12:08:23.748Z').valueOf(),
}}
relativeTime={{ start: 'now-15m', end: 'now' }}
bucketSize="60s"
/>
);
@ -38,8 +42,11 @@ describe('APMSection', () => {
});
const { getByText, queryAllByText, getByTestId } = render(
<APMSection
startTime="2020-06-29T11:38:23.747Z"
endTime="2020-06-29T12:08:23.748Z"
absoluteTime={{
start: moment('2020-06-29T11:38:23.747Z').valueOf(),
end: moment('2020-06-29T12:08:23.748Z').valueOf(),
}}
relativeTime={{ start: 'now-15m', end: 'now' }}
bucketSize="60s"
/>
);

View file

@ -21,8 +21,8 @@ import { StyledStat } from '../../styled_stat';
import { onBrushEnd } from '../helper';
interface Props {
startTime?: string;
endTime?: string;
absoluteTime: { start?: number; end?: number };
relativeTime: { start: string; end: string };
bucketSize?: string;
}
@ -30,20 +30,25 @@ function formatTpm(value?: number) {
return numeral(value).format('0.00a');
}
export const APMSection = ({ startTime, endTime, bucketSize }: Props) => {
export const APMSection = ({ absoluteTime, relativeTime, bucketSize }: Props) => {
const theme = useContext(ThemeContext);
const history = useHistory();
const { start, end } = absoluteTime;
const { data, status } = useFetcher(() => {
if (startTime && endTime && bucketSize) {
return getDataHandler('apm')?.fetchData({ startTime, endTime, bucketSize });
if (start && end && bucketSize) {
return getDataHandler('apm')?.fetchData({
absoluteTime: { start, end },
relativeTime,
bucketSize,
});
}
}, [startTime, endTime, bucketSize]);
}, [start, end, bucketSize]);
const { title = 'APM', appLink, stats, series } = data || {};
const { appLink, stats, series } = data || {};
const min = moment.utc(startTime).valueOf();
const max = moment.utc(endTime).valueOf();
const min = moment.utc(absoluteTime.start).valueOf();
const max = moment.utc(absoluteTime.end).valueOf();
const formatter = niceTimeFormatter([min, max]);
@ -53,8 +58,15 @@ export const APMSection = ({ startTime, endTime, bucketSize }: Props) => {
return (
<SectionContainer
title={title || 'APM'}
appLink={appLink}
title={i18n.translate('xpack.observability.overview.apm.title', {
defaultMessage: 'APM',
})}
appLink={{
href: appLink,
label: i18n.translate('xpack.observability.overview.apm.appLink', {
defaultMessage: 'View in app',
}),
}}
hasError={status === FETCH_STATUS.FAILURE}
>
<EuiFlexGroup>

View file

@ -7,8 +7,6 @@
import { ApmFetchDataResponse } from '../../../../../typings';
export const response: ApmFetchDataResponse = {
title: 'APM',
appLink: '/app/apm',
stats: {
services: { value: 11, type: 'number' },

View file

@ -20,13 +20,13 @@ describe('SectionContainer', () => {
});
it('renders section with app link', () => {
const component = render(
<SectionContainer title="Foo" appLink="/foo/bar" hasError={false}>
<SectionContainer title="Foo" appLink={{ label: 'foo', href: '/foo/bar' }} hasError={false}>
<div>I am a very nice component</div>
</SectionContainer>
);
expect(component.getByText('I am a very nice component')).toBeInTheDocument();
expect(component.getByText('Foo')).toBeInTheDocument();
expect(component.getByText('View in app')).toBeInTheDocument();
expect(component.getByText('foo')).toBeInTheDocument();
});
it('renders section with error', () => {
const component = render(

View file

@ -4,21 +4,23 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { EuiAccordion, EuiLink, EuiPanel, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
import { ErrorPanel } from './error_panel';
import { usePluginContext } from '../../../hooks/use_plugin_context';
interface AppLink {
label: string;
href?: string;
}
interface Props {
title: string;
hasError: boolean;
children: React.ReactNode;
minHeight?: number;
appLink?: string;
appLinkName?: string;
appLink?: AppLink;
}
export const SectionContainer = ({ title, appLink, children, hasError, appLinkName }: Props) => {
export const SectionContainer = ({ title, appLink, children, hasError }: Props) => {
const { core } = usePluginContext();
return (
<EuiAccordion
@ -31,15 +33,9 @@ export const SectionContainer = ({ title, appLink, children, hasError, appLinkNa
</EuiTitle>
}
extraAction={
appLink && (
<EuiLink href={core.http.basePath.prepend(appLink)}>
<EuiText size="s">
{appLinkName
? appLinkName
: i18n.translate('xpack.observability.chart.viewInAppLabel', {
defaultMessage: 'View in app',
})}
</EuiText>
appLink?.href && (
<EuiLink href={core.http.basePath.prepend(appLink.href)}>
<EuiText size="s">{appLink.label}</EuiText>
</EuiLink>
)
}

View file

@ -25,8 +25,8 @@ import { StyledStat } from '../../styled_stat';
import { onBrushEnd } from '../helper';
interface Props {
startTime?: string;
endTime?: string;
absoluteTime: { start?: number; end?: number };
relativeTime: { start: string; end: string };
bucketSize?: string;
}
@ -45,21 +45,26 @@ function getColorPerItem(series?: LogsFetchDataResponse['series']) {
return colorsPerItem;
}
export const LogsSection = ({ startTime, endTime, bucketSize }: Props) => {
export const LogsSection = ({ absoluteTime, relativeTime, bucketSize }: Props) => {
const history = useHistory();
const { start, end } = absoluteTime;
const { data, status } = useFetcher(() => {
if (startTime && endTime && bucketSize) {
return getDataHandler('infra_logs')?.fetchData({ startTime, endTime, bucketSize });
if (start && end && bucketSize) {
return getDataHandler('infra_logs')?.fetchData({
absoluteTime: { start, end },
relativeTime,
bucketSize,
});
}
}, [startTime, endTime, bucketSize]);
}, [start, end, bucketSize]);
const min = moment.utc(startTime).valueOf();
const max = moment.utc(endTime).valueOf();
const min = moment.utc(absoluteTime.start).valueOf();
const max = moment.utc(absoluteTime.end).valueOf();
const formatter = niceTimeFormatter([min, max]);
const { title, appLink, stats, series } = data || {};
const { appLink, stats, series } = data || {};
const colorsPerItem = getColorPerItem(series);
@ -67,8 +72,15 @@ export const LogsSection = ({ startTime, endTime, bucketSize }: Props) => {
return (
<SectionContainer
title={title || 'Logs'}
appLink={appLink}
title={i18n.translate('xpack.observability.overview.logs.title', {
defaultMessage: 'Logs',
})}
appLink={{
href: appLink,
label: i18n.translate('xpack.observability.overview.logs.appLink', {
defaultMessage: 'View in app',
}),
}}
hasError={status === FETCH_STATUS.FAILURE}
>
<EuiTitle size="xs">

View file

@ -18,8 +18,8 @@ import { ChartContainer } from '../../chart_container';
import { StyledStat } from '../../styled_stat';
interface Props {
startTime?: string;
endTime?: string;
absoluteTime: { start?: number; end?: number };
relativeTime: { start: string; end: string };
bucketSize?: string;
}
@ -46,17 +46,23 @@ const StyledProgress = styled.div<{ color?: string }>`
}
`;
export const MetricsSection = ({ startTime, endTime, bucketSize }: Props) => {
export const MetricsSection = ({ absoluteTime, relativeTime, bucketSize }: Props) => {
const theme = useContext(ThemeContext);
const { start, end } = absoluteTime;
const { data, status } = useFetcher(() => {
if (startTime && endTime && bucketSize) {
return getDataHandler('infra_metrics')?.fetchData({ startTime, endTime, bucketSize });
if (start && end && bucketSize) {
return getDataHandler('infra_metrics')?.fetchData({
absoluteTime: { start, end },
relativeTime,
bucketSize,
});
}
}, [startTime, endTime, bucketSize]);
}, [start, end, bucketSize]);
const isLoading = status === FETCH_STATUS.LOADING;
const { title = 'Metrics', appLink, stats, series } = data || {};
const { appLink, stats, series } = data || {};
const cpuColor = theme.eui.euiColorVis7;
const memoryColor = theme.eui.euiColorVis0;
@ -65,9 +71,15 @@ export const MetricsSection = ({ startTime, endTime, bucketSize }: Props) => {
return (
<SectionContainer
minHeight={135}
title={title}
appLink={appLink}
title={i18n.translate('xpack.observability.overview.metrics.title', {
defaultMessage: 'Metrics',
})}
appLink={{
href: appLink,
label: i18n.translate('xpack.observability.overview.metrics.appLink', {
defaultMessage: 'View in app',
}),
}}
hasError={status === FETCH_STATUS.FAILURE}
>
<EuiFlexGroup>

View file

@ -30,37 +30,49 @@ import { StyledStat } from '../../styled_stat';
import { onBrushEnd } from '../helper';
interface Props {
startTime?: string;
endTime?: string;
absoluteTime: { start?: number; end?: number };
relativeTime: { start: string; end: string };
bucketSize?: string;
}
export const UptimeSection = ({ startTime, endTime, bucketSize }: Props) => {
export const UptimeSection = ({ absoluteTime, relativeTime, bucketSize }: Props) => {
const theme = useContext(ThemeContext);
const history = useHistory();
const { start, end } = absoluteTime;
const { data, status } = useFetcher(() => {
if (startTime && endTime && bucketSize) {
return getDataHandler('uptime')?.fetchData({ startTime, endTime, bucketSize });
if (start && end && bucketSize) {
return getDataHandler('uptime')?.fetchData({
absoluteTime: { start, end },
relativeTime,
bucketSize,
});
}
}, [startTime, endTime, bucketSize]);
}, [start, end, bucketSize]);
const min = moment.utc(absoluteTime.start).valueOf();
const max = moment.utc(absoluteTime.end).valueOf();
const min = moment.utc(startTime).valueOf();
const max = moment.utc(endTime).valueOf();
const formatter = niceTimeFormatter([min, max]);
const isLoading = status === FETCH_STATUS.LOADING;
const { title = 'Uptime', appLink, stats, series } = data || {};
const { appLink, stats, series } = data || {};
const downColor = theme.eui.euiColorVis2;
const upColor = theme.eui.euiColorLightShade;
return (
<SectionContainer
minHeight={273}
title={title}
appLink={appLink}
title={i18n.translate('xpack.observability.overview.uptime.title', {
defaultMessage: 'Uptime',
})}
appLink={{
href: appLink,
label: i18n.translate('xpack.observability.overview.uptime.appLink', {
defaultMessage: 'View in app',
}),
}}
hasError={status === FETCH_STATUS.FAILURE}
>
<EuiFlexGroup>

View file

@ -4,10 +4,17 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { registerDataHandler, getDataHandler } from './data_handler';
import moment from 'moment';
const params = {
startTime: '0',
endTime: '1',
absoluteTime: {
start: moment('2020-07-02T13:25:11.629Z').valueOf(),
end: moment('2020-07-09T13:25:11.629Z').valueOf(),
},
relativeTime: {
start: 'now-15m',
end: 'now',
},
bucketSize: '10s',
};

View file

@ -4,7 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { EuiFlexGrid, EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, EuiSpacer } from '@elastic/eui';
import moment from 'moment';
import React, { useContext } from 'react';
import { ThemeContext } from 'styled-components';
import { EmptySection } from '../../components/app/empty_section';
@ -23,7 +22,7 @@ import { UI_SETTINGS, useKibanaUISettings } from '../../hooks/use_kibana_ui_sett
import { usePluginContext } from '../../hooks/use_plugin_context';
import { RouteParams } from '../../routes';
import { getObservabilityAlerts } from '../../services/get_observability_alerts';
import { getParsedDate } from '../../utils/date';
import { getAbsoluteTime } from '../../utils/date';
import { getBucketSize } from '../../utils/get_bucket_size';
import { getEmptySections } from './empty_section';
import { LoadingObservability } from './loading_observability';
@ -33,13 +32,9 @@ interface Props {
routeParams: RouteParams<'/overview'>;
}
function calculatetBucketSize({ startTime, endTime }: { startTime?: string; endTime?: string }) {
if (startTime && endTime) {
return getBucketSize({
start: moment.utc(startTime).valueOf(),
end: moment.utc(endTime).valueOf(),
minInterval: '60s',
});
function calculatetBucketSize({ start, end }: { start?: number; end?: number }) {
if (start && end) {
return getBucketSize({ start, end, minInterval: '60s' });
}
}
@ -62,16 +57,22 @@ export const OverviewPage = ({ routeParams }: Props) => {
return <LoadingObservability />;
}
const {
rangeFrom = timePickerTime.from,
rangeTo = timePickerTime.to,
refreshInterval = 10000,
refreshPaused = true,
} = routeParams.query;
const { refreshInterval = 10000, refreshPaused = true } = routeParams.query;
const startTime = getParsedDate(rangeFrom);
const endTime = getParsedDate(rangeTo, { roundUp: true });
const bucketSize = calculatetBucketSize({ startTime, endTime });
const relativeTime = {
start: routeParams.query.rangeFrom ?? timePickerTime.from,
end: routeParams.query.rangeTo ?? timePickerTime.to,
};
const absoluteTime = {
start: getAbsoluteTime(relativeTime.start),
end: getAbsoluteTime(relativeTime.end, { roundUp: true }),
};
const bucketSize = calculatetBucketSize({
start: absoluteTime.start,
end: absoluteTime.end,
});
const appEmptySections = getEmptySections({ core }).filter(({ id }) => {
if (id === 'alert') {
@ -93,8 +94,8 @@ export const OverviewPage = ({ routeParams }: Props) => {
<EuiFlexGroup justifyContent="flexEnd">
<EuiFlexItem grow={false}>
<DatePicker
rangeFrom={rangeFrom}
rangeTo={rangeTo}
rangeFrom={relativeTime.start}
rangeTo={relativeTime.end}
refreshInterval={refreshInterval}
refreshPaused={refreshPaused}
/>
@ -116,8 +117,8 @@ export const OverviewPage = ({ routeParams }: Props) => {
{hasData.infra_logs && (
<EuiFlexItem grow={false}>
<LogsSection
startTime={startTime}
endTime={endTime}
absoluteTime={absoluteTime}
relativeTime={relativeTime}
bucketSize={bucketSize?.intervalString}
/>
</EuiFlexItem>
@ -125,8 +126,8 @@ export const OverviewPage = ({ routeParams }: Props) => {
{hasData.infra_metrics && (
<EuiFlexItem grow={false}>
<MetricsSection
startTime={startTime}
endTime={endTime}
absoluteTime={absoluteTime}
relativeTime={relativeTime}
bucketSize={bucketSize?.intervalString}
/>
</EuiFlexItem>
@ -134,8 +135,8 @@ export const OverviewPage = ({ routeParams }: Props) => {
{hasData.apm && (
<EuiFlexItem grow={false}>
<APMSection
startTime={startTime}
endTime={endTime}
absoluteTime={absoluteTime}
relativeTime={relativeTime}
bucketSize={bucketSize?.intervalString}
/>
</EuiFlexItem>
@ -143,8 +144,8 @@ export const OverviewPage = ({ routeParams }: Props) => {
{hasData.uptime && (
<EuiFlexItem grow={false}>
<UptimeSection
startTime={startTime}
endTime={endTime}
absoluteTime={absoluteTime}
relativeTime={relativeTime}
bucketSize={bucketSize?.intervalString}
/>
</EuiFlexItem>

View file

@ -10,7 +10,6 @@ export const fetchApmData: FetchData<ApmFetchDataResponse> = () => {
};
const response: ApmFetchDataResponse = {
title: 'APM',
appLink: '/app/apm',
stats: {
services: {
@ -607,7 +606,6 @@ const response: ApmFetchDataResponse = {
};
export const emptyResponse: ApmFetchDataResponse = {
title: 'APM',
appLink: '/app/apm',
stats: {
services: {

View file

@ -11,7 +11,6 @@ export const fetchLogsData: FetchData<LogsFetchDataResponse> = () => {
};
const response: LogsFetchDataResponse = {
title: 'Logs',
appLink:
"/app/logs/stream?logPosition=(end:'2020-06-30T21:30:00.000Z',start:'2020-06-27T22:00:00.000Z')",
stats: {
@ -2319,7 +2318,6 @@ const response: LogsFetchDataResponse = {
};
export const emptyResponse: LogsFetchDataResponse = {
title: 'Logs',
appLink: '/app/logs',
stats: {},
series: {},

View file

@ -11,7 +11,6 @@ export const fetchMetricsData: FetchData<MetricsFetchDataResponse> = () => {
};
const response: MetricsFetchDataResponse = {
title: 'Metrics',
appLink: '/app/apm',
stats: {
hosts: { value: 11, type: 'number' },
@ -113,7 +112,6 @@ const response: MetricsFetchDataResponse = {
};
export const emptyResponse: MetricsFetchDataResponse = {
title: 'Metrics',
appLink: '/app/apm',
stats: {
hosts: { value: 0, type: 'number' },

View file

@ -10,7 +10,6 @@ export const fetchUptimeData: FetchData<UptimeFetchDataResponse> = () => {
};
const response: UptimeFetchDataResponse = {
title: 'Uptime',
appLink: '/app/uptime#/',
stats: {
monitors: {
@ -1191,7 +1190,6 @@ const response: UptimeFetchDataResponse = {
};
export const emptyResponse: UptimeFetchDataResponse = {
title: 'Uptime',
appLink: '/app/uptime#/',
stats: {
monitors: {

View file

@ -21,11 +21,8 @@ export interface Series {
}
export interface FetchDataParams {
// The start timestamp in milliseconds of the queried time interval
startTime: string;
// The end timestamp in milliseconds of the queried time interval
endTime: string;
// The aggregation bucket size in milliseconds if applicable to the data source
absoluteTime: { start: number; end: number };
relativeTime: { start: string; end: string };
bucketSize: string;
}
@ -41,7 +38,6 @@ export interface DataHandler<T extends ObservabilityApp = ObservabilityApp> {
}
export interface FetchDataResponse {
title: string;
appLink: string;
}

View file

@ -5,11 +5,9 @@
*/
import datemath from '@elastic/datemath';
export function getParsedDate(range?: string, opts = {}) {
if (range) {
const parsed = datemath.parse(range, opts);
if (parsed) {
return parsed.toISOString();
}
export function getAbsoluteTime(range: string, opts = {}) {
const parsed = datemath.parse(range, opts);
if (parsed) {
return parsed.valueOf();
}
}

View file

@ -5,27 +5,24 @@
*/
import { fetchPingHistogram, fetchSnapshotCount } from '../state/api';
import { UptimeFetchDataResponse } from '../../../observability/public';
import { UptimeFetchDataResponse, FetchDataParams } from '../../../observability/public';
export async function fetchUptimeOverviewData({
startTime,
endTime,
absoluteTime,
relativeTime,
bucketSize,
}: {
startTime: string;
endTime: string;
bucketSize: string;
}) {
}: FetchDataParams) {
const start = new Date(absoluteTime.start).toISOString();
const end = new Date(absoluteTime.end).toISOString();
const snapshot = await fetchSnapshotCount({
dateRangeStart: startTime,
dateRangeEnd: endTime,
dateRangeStart: start,
dateRangeEnd: end,
});
const pings = await fetchPingHistogram({ dateStart: startTime, dateEnd: endTime, bucketSize });
const pings = await fetchPingHistogram({ dateStart: start, dateEnd: end, bucketSize });
const response: UptimeFetchDataResponse = {
title: 'Uptime',
appLink: '/app/uptime#/',
appLink: `/app/uptime#/?dateRangeStart=${relativeTime.start}&dateRangeEnd=${relativeTime.end}`,
stats: {
monitors: {
type: 'number',