[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:
parent
831e427682
commit
3374b2d3b0
|
@ -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,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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',
|
|
@ -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',
|
||||
});
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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 })]),
|
||||
},
|
|
@ -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",
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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',
|
||||
},
|
||||
},
|
||||
|
|
|
@ -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}>
|
||||
|
|
|
@ -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"
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -7,8 +7,6 @@
|
|||
import { ApmFetchDataResponse } from '../../../../../typings';
|
||||
|
||||
export const response: ApmFetchDataResponse = {
|
||||
title: 'APM',
|
||||
|
||||
appLink: '/app/apm',
|
||||
stats: {
|
||||
services: { value: 11, type: 'number' },
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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',
|
||||
};
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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: {},
|
||||
|
|
|
@ -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' },
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -5,11 +5,9 @@
|
|||
*/
|
||||
import datemath from '@elastic/datemath';
|
||||
|
||||
export function getParsedDate(range?: string, opts = {}) {
|
||||
if (range) {
|
||||
export function getAbsoluteTime(range: string, opts = {}) {
|
||||
const parsed = datemath.parse(range, opts);
|
||||
if (parsed) {
|
||||
return parsed.toISOString();
|
||||
}
|
||||
return parsed.valueOf();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
|
|
Loading…
Reference in a new issue