Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* 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 { dateAsStringRt } from './index';

describe('dateAsStringRt', () => {
it('validates whether a string is a valid date', () => {
expect(dateAsStringRt.decode(1566299881499).isLeft()).toBe(true);

expect(dateAsStringRt.decode('2019-08-20T11:18:31.407Z').isRight()).toBe(
true
);
});

it('returns the string it was given', () => {
const either = dateAsStringRt.decode('2019-08-20T11:18:31.407Z');

expect(either.value).toBe('2019-08-20T11:18:31.407Z');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* 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 * as t from 'io-ts';
import { either } from 'fp-ts/lib/Either';

// Checks whether a string is a valid ISO timestamp,
// but doesn't convert it into a Date object when decoding

export const dateAsStringRt = new t.Type<string, string, unknown>(
'DateAsString',
t.string.is,
(input, context) =>
either.chain(t.string.validate(input, context), str => {
const date = new Date(str);
return isNaN(date.getTime()) ? t.failure(input, context) : t.success(str);
}),
t.identity
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* 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 * as t from 'io-ts';
import { jsonRt } from './index';

describe('jsonRt', () => {
it('validates json', () => {
expect(jsonRt.decode('{}').isRight()).toBe(true);
expect(jsonRt.decode('[]').isRight()).toBe(true);
expect(jsonRt.decode('true').isRight()).toBe(true);
expect(jsonRt.decode({}).isLeft()).toBe(true);
expect(jsonRt.decode('foo').isLeft()).toBe(true);
});

it('returns parsed json when decoding', () => {
expect(jsonRt.decode('{}').value).toEqual({});
expect(jsonRt.decode('[]').value).toEqual([]);
expect(jsonRt.decode('true').value).toEqual(true);
});

it('is pipable', () => {
const piped = jsonRt.pipe(t.type({ foo: t.string }));

const validInput = { foo: 'bar' };
const invalidInput = { foo: null };

const valid = piped.decode(JSON.stringify(validInput));
const invalid = piped.decode(JSON.stringify(invalidInput));

expect(valid.isRight()).toBe(true);
expect(valid.value).toEqual(validInput);

expect(invalid.isLeft()).toBe(true);
});

it('returns strings when encoding', () => {
expect(jsonRt.encode({})).toBe('{}');
});
});
21 changes: 21 additions & 0 deletions x-pack/legacy/plugins/apm/common/runtime_types/json_rt/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* 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 * as t from 'io-ts';
import { either } from 'fp-ts/lib/Either';

export const jsonRt = new t.Type<any, string, unknown>(
'JSON',
t.any.is,
(input, context) =>
either.chain(t.string.validate(input, context), str => {
try {
return t.success(JSON.parse(str));
} catch (e) {
return t.failure(input, context);
}
}),
a => JSON.stringify(a)
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* 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 { transactionSampleRateRt } from './index';

describe('transactionSampleRateRt', () => {
it('accepts both strings and numbers as values', () => {
expect(transactionSampleRateRt.decode('0.5').isRight()).toBe(true);
expect(transactionSampleRateRt.decode(0.5).isRight()).toBe(true);
});

it('checks if the number falls within 0, 1', () => {
expect(transactionSampleRateRt.decode(0).isRight()).toBe(true);

expect(transactionSampleRateRt.decode(0.5).isRight()).toBe(true);

expect(transactionSampleRateRt.decode(-0.1).isRight()).toBe(false);
expect(transactionSampleRateRt.decode(1.1).isRight()).toBe(false);

expect(transactionSampleRateRt.decode(NaN).isRight()).toBe(false);
});

it('checks whether the number of decimals is 3', () => {
expect(transactionSampleRateRt.decode(1).isRight()).toBe(true);
expect(transactionSampleRateRt.decode(0.99).isRight()).toBe(true);
expect(transactionSampleRateRt.decode(0.999).isRight()).toBe(true);
expect(transactionSampleRateRt.decode(0.998).isRight()).toBe(true);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* 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 * as t from 'io-ts';

export const transactionSampleRateRt = new t.Type<number, number, unknown>(
'TransactionSampleRate',
t.number.is,
(input, context) => {
const value = Number(input);
return value >= 0 && value <= 1 && Number(value.toFixed(3)) === value
? t.success(value)
: t.failure(input, context);
},
t.identity
);
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,14 @@ import styled from 'styled-components';
import { idx } from '@kbn/elastic-idx';
import { NOT_AVAILABLE_LABEL } from '../../../../common/i18n';
import { useFetcher } from '../../../hooks/useFetcher';
import {
loadErrorDistribution,
loadErrorGroupDetails
} from '../../../services/rest/apm/error_groups';
import { fontFamilyCode, fontSizes, px, units } from '../../../style/variables';
import { ApmHeader } from '../../shared/ApmHeader';
import { DetailView } from './DetailView';
import { ErrorDistribution } from './Distribution';
import { useLocation } from '../../../hooks/useLocation';
import { useUrlParams } from '../../../hooks/useUrlParams';
import { useTrackPageview } from '../../../../../infra/public';
import { callApmApi } from '../../../services/rest/callApmApi';

const Titles = styled.div`
margin-bottom: ${px(units.plus)};
Expand Down Expand Up @@ -68,24 +65,38 @@ export function ErrorGroupDetails() {

const { data: errorGroupData } = useFetcher(() => {
if (serviceName && start && end && errorGroupId) {
return loadErrorGroupDetails({
serviceName,
start,
end,
errorGroupId,
uiFilters
return callApmApi({
pathname: '/api/apm/services/{serviceName}/errors/{groupId}',
params: {
path: {
serviceName,
groupId: errorGroupId
},
query: {
start,
end,
uiFilters: JSON.stringify(uiFilters)
}
}
});
}
}, [serviceName, start, end, errorGroupId, uiFilters]);

const { data: errorDistributionData } = useFetcher(() => {
if (serviceName && start && end && errorGroupId) {
return loadErrorDistribution({
serviceName,
start,
end,
errorGroupId,
uiFilters
return callApmApi({
pathname: '/api/apm/services/{serviceName}/errors/distribution',
params: {
path: {
serviceName
},
query: {
start,
end,
groupId: errorGroupId,
uiFilters: JSON.stringify(uiFilters)
}
}
});
}
}, [serviceName, start, end, errorGroupId, uiFilters]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,13 @@ import {
import { i18n } from '@kbn/i18n';
import React, { useMemo } from 'react';
import { useFetcher } from '../../../hooks/useFetcher';
import {
loadErrorDistribution,
loadErrorGroupList
} from '../../../services/rest/apm/error_groups';
import { ErrorDistribution } from '../ErrorGroupDetails/Distribution';
import { ErrorGroupList } from './List';
import { useUrlParams } from '../../../hooks/useUrlParams';
import { useTrackPageview } from '../../../../../infra/public';
import { PROJECTION } from '../../../../common/projections/typings';
import { LocalUIFilters } from '../../shared/LocalUIFilters';
import { callApmApi } from '../../../services/rest/callApmApi';

const ErrorGroupOverview: React.SFC = () => {
const { urlParams, uiFilters } = useUrlParams();
Expand All @@ -32,24 +29,38 @@ const ErrorGroupOverview: React.SFC = () => {

const { data: errorDistributionData } = useFetcher(() => {
if (serviceName && start && end) {
return loadErrorDistribution({
serviceName,
start,
end,
uiFilters
return callApmApi({
pathname: '/api/apm/services/{serviceName}/errors/distribution',
params: {
path: {
serviceName
},
query: {
start,
end,
uiFilters: JSON.stringify(uiFilters)
}
}
});
}
}, [serviceName, start, end, uiFilters]);

const { data: errorGroupListData } = useFetcher(() => {
if (serviceName && start && end) {
return loadErrorGroupList({
serviceName,
start,
end,
sortField,
sortDirection,
uiFilters
return callApmApi({
pathname: '/api/apm/services/{serviceName}/errors',
params: {
path: {
serviceName
},
query: {
start,
end,
sortField,
sortDirection,
uiFilters: JSON.stringify(uiFilters)
}
}
});
}
}, [serviceName, start, end, sortField, sortDirection, uiFilters]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import { ErrorGroupOverview } from '../ErrorGroupOverview';
import { TransactionOverview } from '../TransactionOverview';
import { ServiceMetrics } from '../ServiceMetrics';
import { useFetcher } from '../../../hooks/useFetcher';
import { loadServiceAgentName } from '../../../services/rest/apm/services';
import { isRumAgentName } from '../../../../common/agent_name';
import { callApmApi } from '../../../services/rest/callApmApi';

interface Props {
urlParams: IUrlParams;
Expand All @@ -23,7 +23,13 @@ export function ServiceDetailTabs({ urlParams }: Props) {
const { serviceName, start, end } = urlParams;
const { data: agentName } = useFetcher(() => {
if (serviceName && start && end) {
return loadServiceAgentName({ serviceName, start, end });
return callApmApi({
pathname: '/api/apm/services/{serviceName}/agent_name',
params: {
path: { serviceName },
query: { start, end }
}
}).then(res => res.agentName);
}
}, [serviceName, start, end]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import React from 'react';
import { render, wait, waitForElement } from 'react-testing-library';
import 'react-testing-library/cleanup-after-each';
import { toastNotifications } from 'ui/notify';
import * as apmRestServices from '../../../../services/rest/apm/services';
import * as callApmApi from '../../../../services/rest/callApmApi';
import { ServiceOverview } from '..';
import * as urlParamsHooks from '../../../../hooks/useUrlParams';
import * as coreHooks from '../../../../hooks/useCore';
Expand Down Expand Up @@ -66,7 +66,7 @@ describe('Service Overview -> View', () => {
it('should render services, when list is not empty', async () => {
// mock rest requests
const dataFetchingSpy = jest
.spyOn(apmRestServices, 'loadServiceList')
.spyOn(callApmApi, 'callApmApi')
.mockResolvedValue({
hasLegacyData: false,
hasHistoricalData: true,
Expand Down Expand Up @@ -101,7 +101,7 @@ describe('Service Overview -> View', () => {

it('should render getting started message, when list is empty and no historical data is found', async () => {
const dataFetchingSpy = jest
.spyOn(apmRestServices, 'loadServiceList')
.spyOn(callApmApi, 'callApmApi')
.mockResolvedValue({
hasLegacyData: false,
hasHistoricalData: false,
Expand All @@ -125,7 +125,7 @@ describe('Service Overview -> View', () => {

it('should render empty message, when list is empty and historical data is found', async () => {
const dataFetchingSpy = jest
.spyOn(apmRestServices, 'loadServiceList')
.spyOn(callApmApi, 'callApmApi')
.mockResolvedValue({
hasLegacyData: false,
hasHistoricalData: true,
Expand All @@ -145,7 +145,7 @@ describe('Service Overview -> View', () => {
// create spies
const toastSpy = jest.spyOn(toastNotifications, 'addWarning');
const dataFetchingSpy = jest
.spyOn(apmRestServices, 'loadServiceList')
.spyOn(callApmApi, 'callApmApi')
.mockResolvedValue({
hasLegacyData: true,
hasHistoricalData: true,
Expand All @@ -168,7 +168,7 @@ describe('Service Overview -> View', () => {
// create spies
const toastSpy = jest.spyOn(toastNotifications, 'addWarning');
const dataFetchingSpy = jest
.spyOn(apmRestServices, 'loadServiceList')
.spyOn(callApmApi, 'callApmApi')
.mockResolvedValue({
hasLegacyData: false,
hasHistoricalData: true,
Expand Down
Loading