[APM] Convert most of remaining js to ts (#32115)

* [APM] Convert most of remaining js to ts

* Fixes for distribution component

* Make `page` and `sort` optional

* Add Server definition from hapi
This commit is contained in:
Søren Louv-Jansen 2019-02-28 17:54:57 +01:00 committed by GitHub
parent 95a3284637
commit aa71146252
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
36 changed files with 291 additions and 232 deletions

View file

@ -4,18 +4,20 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { i18n } from '@kbn/i18n';
import { Server } from 'hapi';
import { resolve } from 'path';
import { initTransactionGroupsApi } from './server/routes/transaction_groups';
import { initServicesApi } from './server/routes/services';
import mappings from './mappings.json';
import { makeApmUsageCollector } from './server/lib/apm_telemetry';
import { initErrorsApi } from './server/routes/errors';
import { initMetricsApi } from './server/routes/metrics';
import { initServicesApi } from './server/routes/services';
import { initStatusApi } from './server/routes/status_check';
import { initTracesApi } from './server/routes/traces';
import { initMetricsApi } from './server/routes/metrics';
import mappings from './mappings';
import { makeApmUsageCollector } from './server/lib/apm_telemetry';
import { i18n } from '@kbn/i18n';
import { initTransactionGroupsApi } from './server/routes/transaction_groups';
export function apm(kibana) {
// TODO: get proper types
export function apm(kibana: any) {
return new kibana.Plugin({
require: ['kibana', 'elasticsearch', 'xpack_main', 'apm_oss'],
id: 'apm',
@ -35,7 +37,9 @@ export function apm(kibana) {
},
styleSheetPaths: resolve(__dirname, 'public/index.scss'),
home: ['plugins/apm/register_feature'],
injectDefaultVars(server) {
// TODO: get proper types
injectDefaultVars(server: Server) {
const config = server.config();
return {
apmUiEnabled: config.get('xpack.apm.ui.enabled'),
@ -51,7 +55,8 @@ export function apm(kibana) {
mappings
},
config(Joi) {
// TODO: get proper types
config(Joi: any) {
return Joi.object({
// display menu item
ui: Joi.object({
@ -68,7 +73,8 @@ export function apm(kibana) {
}).default();
},
init(server) {
// TODO: get proper types
init(server: any) {
initTransactionGroupsApi(server);
initTracesApi(server);
initServicesApi(server);

View file

@ -7,10 +7,32 @@
import { EuiTitle } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
// @ts-ignore
import Histogram from '../../../shared/charts/Histogram';
import { EmptyMessage } from '../../../shared/EmptyMessage';
export function getFormattedBuckets(buckets, bucketSize) {
interface IBucket {
key: number;
count: number;
}
// TODO: cleanup duplication of this in distribution/get_distribution.ts (ErrorDistributionAPIResponse) and transactions/distribution/index.ts (ITransactionDistributionAPIResponse)
interface IDistribution {
totalHits: number;
buckets: IBucket[];
bucketSize: number;
}
interface FormattedBucket {
x0: number;
x: number;
y: number;
}
export function getFormattedBuckets(
buckets: IBucket[],
bucketSize: number
): FormattedBucket[] | null {
if (!buckets) {
return null;
}
@ -24,12 +46,12 @@ export function getFormattedBuckets(buckets, bucketSize) {
});
}
function Distribution({
distribution,
title = i18n.translate('xpack.apm.errorGroupDetails.occurrencesChartLabel', {
defaultMessage: 'Occurrences'
})
}) {
interface Props {
distribution: IDistribution;
title: React.ReactNode;
}
export function ErrorDistribution({ distribution, title }: Props) {
const buckets = getFormattedBuckets(
distribution.buckets,
distribution.bucketSize
@ -53,17 +75,17 @@ function Distribution({
<span>{title}</span>
</EuiTitle>
<Histogram
verticalLineHover={bucket => bucket.x}
verticalLineHover={(bucket: FormattedBucket) => bucket.x}
xType="time"
buckets={buckets}
bucketSize={distribution.bucketSize}
formatYShort={value =>
formatYShort={(value: number) =>
i18n.translate('xpack.apm.errorGroupDetails.occurrencesShortLabel', {
defaultMessage: '{occCount} occ.',
values: { occCount: value }
})
}
formatYLong={value =>
formatYLong={(value: number) =>
i18n.translate('xpack.apm.errorGroupDetails.occurrencesLongLabel', {
defaultMessage: '{occCount} occurrences',
values: { occCount: value }
@ -73,5 +95,3 @@ function Distribution({
</div>
);
}
export default Distribution;

View file

@ -5,10 +5,11 @@
*/
import { connect } from 'react-redux';
import { ErrorGroupDetails } from './view';
import { IReduxState } from '../../../store/rootReducer';
import { getUrlParams } from '../../../store/urlParams';
import { ErrorGroupDetailsView } from './view';
function mapStateToProps(state = {}) {
function mapStateToProps(state = {} as IReduxState) {
return {
urlParams: getUrlParams(state),
location: state.location
@ -17,7 +18,7 @@ function mapStateToProps(state = {}) {
const mapDispatchToProps = {};
export default connect(
export const ErrorGroupDetails = connect(
mapStateToProps,
mapDispatchToProps
)(ErrorGroupDetails);
)(ErrorGroupDetailsView);

View file

@ -25,8 +25,7 @@ import {
// @ts-ignore
import { KueryBar } from '../../shared/KueryBar';
import { DetailView } from './DetailView';
// @ts-ignore
import Distribution from './Distribution';
import { ErrorDistribution } from './Distribution';
const Titles = styled.div`
margin-bottom: ${px(units.plus)};
@ -67,7 +66,7 @@ interface Props {
location: Location;
}
export function ErrorGroupDetails({ urlParams, location }: Props) {
export function ErrorGroupDetailsView({ urlParams, location }: Props) {
return (
<ErrorGroupDetailsRequest
urlParams={urlParams}
@ -152,7 +151,17 @@ export function ErrorGroupDetails({ urlParams, location }: Props) {
)}
<ErrorDistributionRequest
urlParams={urlParams}
render={({ data }) => <Distribution distribution={data} />}
render={({ data }) => (
<ErrorDistribution
distribution={data}
title={i18n.translate(
'xpack.apm.errorGroupDetails.occurrencesChartLabel',
{
defaultMessage: 'Occurrences'
}
)}
/>
)}
/>
{showDetails && (
<DetailView

View file

@ -5,15 +5,14 @@
*/
import { mount } from 'enzyme';
import { Location } from 'history';
import React from 'react';
import { MemoryRouter } from 'react-router-dom';
import {
mockMoment,
mountWithRouterAndStore,
toJson
// @ts-ignore
} from '../../../../../utils/testHelpers';
// @ts-ignore
import { ErrorGroupList } from '../index';
import props from './props.json';
@ -26,7 +25,11 @@ describe('ErrorGroupOverview -> List', () => {
const storeState = {};
const wrapper = mount(
<MemoryRouter>
<ErrorGroupList items={[]} urlParams={props.urlParams} location={{}} />
<ErrorGroupList
items={[]}
urlParams={props.urlParams}
location={{} as Location}
/>
</MemoryRouter>,
storeState
);

View file

@ -4,25 +4,35 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { EuiBasicTable, EuiBadge, EuiToolTip } from '@elastic/eui';
import { EuiBadge, EuiBasicTable, EuiToolTip } from '@elastic/eui';
import numeral from '@elastic/numeral';
import { i18n } from '@kbn/i18n';
import { Location } from 'history';
import moment from 'moment';
import { toQuery, fromQuery, history } from '../../../shared/Links/url_helpers';
import { KibanaLink } from '../../../shared/Links/KibanaLink';
import React, { Component } from 'react';
import styled from 'styled-components';
import { IUrlParams } from 'x-pack/plugins/apm/public/store/urlParams';
import { ErrorGroupListAPIResponse } from 'x-pack/plugins/apm/server/lib/errors/get_error_groups';
import { NOT_AVAILABLE_LABEL } from '../../../../../common/i18n';
import {
unit,
px,
fontFamilyCode,
fontSizes,
truncate
px,
truncate,
unit
} from '../../../../style/variables';
import { NOT_AVAILABLE_LABEL } from '../../../../../common/i18n';
import { i18n } from '@kbn/i18n';
import { KibanaLink } from '../../../shared/Links/KibanaLink';
import { fromQuery, history, toQuery } from '../../../shared/Links/url_helpers';
function paginateItems({ items, pageIndex, pageSize }) {
function paginateItems({
items,
pageIndex,
pageSize
}: {
items: any[];
pageIndex: number;
pageSize: number;
}) {
return items.slice(pageIndex * pageSize, (pageIndex + 1) * pageSize);
}
@ -44,15 +54,33 @@ const Culprit = styled.div`
font-family: ${fontFamilyCode};
`;
export class ErrorGroupList extends Component {
state = {
interface Props {
location: Location;
urlParams: IUrlParams;
items: ErrorGroupListAPIResponse;
}
interface ITableChange {
page: { index?: number; size?: number };
sort: {
field?: string;
direction?: string;
};
}
interface State {
page: { index?: number; size?: number };
}
export class ErrorGroupList extends Component<Props, State> {
public state = {
page: {
index: 0,
size: 25
}
};
onTableChange = ({ page = {}, sort = {} }) => {
public onTableChange = ({ page = {}, sort = {} }: ITableChange) => {
this.setState({ page });
const { location } = this.props;
@ -67,7 +95,7 @@ export class ErrorGroupList extends Component {
});
};
render() {
public render() {
const { items } = this.props;
const { serviceName, sortDirection, sortField } = this.props.urlParams;
@ -85,7 +113,7 @@ export class ErrorGroupList extends Component {
field: 'groupId',
sortable: false,
width: px(unit * 6),
render: groupId => {
render: (groupId: string) => {
return (
<GroupIdLink hash={`/${serviceName}/errors/${groupId}`}>
{groupId.slice(0, 5) || NOT_AVAILABLE_LABEL}
@ -103,7 +131,7 @@ export class ErrorGroupList extends Component {
field: 'message',
sortable: false,
width: '50%',
render: (message, item) => {
render: (message: string, item: ErrorGroupListAPIResponse[0]) => {
return (
<MessageAndCulpritCell>
<EuiToolTip
@ -130,7 +158,7 @@ export class ErrorGroupList extends Component {
field: 'handled',
sortable: false,
align: 'right',
render: isUnhandled =>
render: (isUnhandled: boolean) =>
isUnhandled === false && (
<EuiBadge color="warning">
{i18n.translate('xpack.apm.errorsTable.unhandledLabel', {
@ -146,7 +174,7 @@ export class ErrorGroupList extends Component {
field: 'occurrenceCount',
sortable: true,
dataType: 'number',
render: value =>
render: (value?: number) =>
value ? numeral(value).format('0.[0]a') : NOT_AVAILABLE_LABEL
},
{
@ -159,7 +187,8 @@ export class ErrorGroupList extends Component {
}
),
align: 'right',
render: value => (value ? moment(value).fromNow() : NOT_AVAILABLE_LABEL)
render: (value?: number) =>
value ? moment(value).fromNow() : NOT_AVAILABLE_LABEL
}
];
@ -186,7 +215,3 @@ export class ErrorGroupList extends Component {
);
}
}
ErrorGroupList.propTypes = {
location: PropTypes.object.isRequired
};

View file

@ -4,16 +4,14 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiTitle } from '@elastic/eui';
import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { Location } from 'history';
import React from 'react';
// @ts-ignore
import Distribution from 'x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution';
import { ErrorDistribution } from 'x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution';
import { ErrorDistributionRequest } from 'x-pack/plugins/apm/public/store/reactReduxRequest/errorDistribution';
import { IUrlParams } from 'x-pack/plugins/apm/public/store/urlParams';
import { ErrorGroupOverviewRequest } from '../../../store/reactReduxRequest/errorGroupList';
// @ts-ignore
import { ErrorGroupList } from './List';
interface ErrorGroupOverviewProps {
@ -32,20 +30,14 @@ const ErrorGroupOverview: React.SFC<ErrorGroupOverviewProps> = ({
<ErrorDistributionRequest
urlParams={urlParams}
render={({ data }) => (
<Distribution
<ErrorDistribution
distribution={data}
title={
<EuiTitle size="xs">
<span>
{i18n.translate(
'xpack.apm.serviceDetails.metrics.errorOccurrencesChartTitle',
{
defaultMessage: 'Error occurrences'
}
)}
</span>
</EuiTitle>
}
title={i18n.translate(
'xpack.apm.serviceDetails.metrics.errorOccurrencesChartTitle',
{
defaultMessage: 'Error occurrences'
}
)}
/>
)}
/>

View file

@ -4,22 +4,23 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { get, some } from 'lodash';
import { connect } from 'react-redux';
import view from './view';
import { some, get } from 'lodash';
import { IReduxState } from 'x-pack/plugins/apm/public/store/rootReducer';
import { STATUS } from '../../../../constants/index';
import { GlobalProgressView } from './view';
function getIsLoading(state) {
function getIsLoading(state: IReduxState) {
return some(
state.reactReduxRequest,
subState => get(subState, 'status') === STATUS.LOADING
);
}
function mapStateToProps(state = {}) {
function mapStateToProps(state = {} as IReduxState) {
return {
isLoading: getIsLoading(state)
};
}
export default connect(mapStateToProps)(view);
export const GlobalProgress = connect(mapStateToProps)(GlobalProgressView);

View file

@ -4,10 +4,14 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { EuiDelayHide, EuiPortal, EuiProgress } from '@elastic/eui';
import React from 'react';
import { EuiPortal, EuiProgress, EuiDelayHide } from '@elastic/eui';
export default ({ isLoading }) => {
interface Props {
isLoading: boolean;
}
export function GlobalProgressView({ isLoading }: Props) {
return (
<EuiDelayHide
hide={!isLoading}
@ -19,4 +23,4 @@ export default ({ isLoading }) => {
)}
/>
);
};
}

View file

@ -4,18 +4,21 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { Location } from 'history';
import { Component } from 'react';
class ScrollToTopOnPathChange extends Component {
componentDidUpdate(prevProps) {
interface Props {
location: Location;
}
export class ScrollToTopOnPathChange extends Component<Props> {
public componentDidUpdate(prevProps: Props) {
if (this.props.location.pathname !== prevProps.location.pathname) {
window.scrollTo(0, 0);
}
}
render() {
public render() {
return null;
}
}
export default ScrollToTopOnPathChange;

View file

@ -12,8 +12,7 @@ import { px, topNavHeight, unit, units } from '../../../style/variables';
import ConnectRouterToRedux from '../../shared/ConnectRouterToRedux';
import { LicenseCheck } from './LicenseCheck';
import { routes } from './routeConfig';
// @ts-ignore
import ScrollToTopOnPathChange from './ScrollToTopOnPathChange';
import { ScrollToTopOnPathChange } from './ScrollToTopOnPathChange';
import { UpdateBreadcrumbs } from './UpdateBreadcrumbs';
const MainContainer = styled.div`

View file

@ -8,8 +8,7 @@ import { i18n } from '@kbn/i18n';
import React from 'react';
import { Redirect, RouteComponentProps } from 'react-router-dom';
import { legacyDecodeURIComponent } from 'x-pack/plugins/apm/public/components/shared/Links/url_helpers';
// @ts-ignore
import ErrorGroupDetails from '../ErrorGroupDetails';
import { ErrorGroupDetails } from '../ErrorGroupDetails';
import { ServiceDetails } from '../ServiceDetails';
import { TransactionDetails } from '../TransactionDetails';
import { Home } from './Home';

View file

@ -8,14 +8,12 @@ import {
EuiFlexGrid,
EuiFlexGroup,
EuiFlexItem,
EuiSpacer,
EuiTitle
EuiSpacer
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { Location } from 'history';
import React from 'react';
// @ts-ignore
import Distribution from 'x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution';
import { ErrorDistribution } from 'x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution';
import { SyncChartGroup } from 'x-pack/plugins/apm/public/components/shared/charts/SyncChartGroup';
import { TransactionCharts } from 'x-pack/plugins/apm/public/components/shared/charts/TransactionCharts';
import { ErrorDistributionRequest } from 'x-pack/plugins/apm/public/store/reactReduxRequest/errorDistribution';
@ -51,20 +49,14 @@ export function ServiceMetrics({ urlParams, location }: ServiceMetricsProps) {
<ErrorDistributionRequest
urlParams={urlParams}
render={({ data }) => (
<Distribution
<ErrorDistribution
distribution={data}
title={
<EuiTitle size="xs">
<span>
{i18n.translate(
'xpack.apm.serviceDetails.metrics.errorOccurrencesChartTitle',
{
defaultMessage: 'Error occurrences'
}
)}
</span>
</EuiTitle>
}
title={i18n.translate(
'xpack.apm.serviceDetails.metrics.errorOccurrencesChartTitle',
{
defaultMessage: 'Error occurrences'
}
)}
/>
)}
/>

View file

@ -8,7 +8,6 @@ import { i18n } from '@kbn/i18n';
import React from 'react';
import { RRRRenderResponse } from 'react-redux-request';
import { TraceListAPIResponse } from 'x-pack/plugins/apm/server/lib/traces/get_top_traces';
// @ts-ignore
import { TraceListRequest } from '../../../store/reactReduxRequest/traceList';
import { EmptyMessage } from '../../shared/EmptyMessage';
import { TraceList } from './TraceList';

View file

@ -56,7 +56,7 @@ interface Props {
urlParams: IUrlParams;
}
export class Distribution extends Component<Props> {
export class TransactionDistribution extends Component<Props> {
public formatYShort = (t: number) => {
return i18n.translate(
'xpack.apm.transactionDetails.transactionsDurationDistributionChart.unitShortLabel',

View file

@ -9,7 +9,6 @@ import { i18n } from '@kbn/i18n';
import React, { Fragment, useEffect, useRef, useState } from 'react';
import styled from 'styled-components';
import { px, units } from '../../../../../../../style/variables';
// @ts-ignore
import { Ellipsis } from '../../../../../../shared/Icons';
const ToggleButtonContainer = styled.div`
@ -59,10 +58,7 @@ export const TruncateHeightSection: React.SFC<Props> = ({
setIsOpen(!isOpen);
}}
>
<Ellipsis
horizontal={!isOpen}
style={{ marginRight: units.half }}
/>{' '}
<Ellipsis horizontal={!isOpen} />{' '}
{isOpen
? i18n.translate('xpack.apm.toggleHeight.showMoreButtonLabel', {
defaultMessage: 'Show more lines'

View file

@ -16,7 +16,7 @@ import { TransactionCharts } from '../../shared/charts/TransactionCharts';
import { EmptyMessage } from '../../shared/EmptyMessage';
// @ts-ignore
import { KueryBar } from '../../shared/KueryBar';
import { Distribution } from './Distribution';
import { TransactionDistribution } from './Distribution';
import { Transaction } from './Transaction';
interface Props {
@ -54,7 +54,7 @@ export function TransactionDetailsView({ urlParams, location }: Props) {
<TransactionDistributionRequest
urlParams={urlParams}
render={({ data }) => (
<Distribution
<TransactionDistribution
distribution={data}
urlParams={urlParams}
location={location}

View file

@ -1,33 +0,0 @@
/*
* 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 React from 'react';
export function Icon({ name, className, ...props }) {
return <i className={`fa ${name} ${className}`} {...props} />;
}
export function Ellipsis({ horizontal, style, ...props }) {
return (
<Icon
style={{
transition: 'transform 0.1s',
transform: `rotate(${horizontal ? 90 : 0}deg)`,
...style
}}
name="fa-ellipsis-v"
{...props}
/>
);
}
export function Check({ ...props }) {
return <Icon name="fa-check" {...props} />;
}
export function Close({ ...props }) {
return <Icon name="fa-times" {...props} />;
}

View file

@ -0,0 +1,22 @@
/*
* 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 { EuiIcon } from '@elastic/eui';
import React from 'react';
import { units } from '../../style/variables';
export function Ellipsis({ horizontal }: { horizontal: boolean }) {
return (
<EuiIcon
style={{
transition: 'transform 0.1s',
transform: `rotate(${horizontal ? 90 : 0}deg)`,
marginRight: units.half
}}
type="boxesVertical"
/>
);
}

View file

@ -9,8 +9,6 @@ import { i18n } from '@kbn/i18n';
import React, { Fragment } from 'react';
import styled from 'styled-components';
import { IStackframe } from 'x-pack/plugins/apm/typings/es_schemas/fields/Stackframe';
import { units } from '../../../style/variables';
// @ts-ignore
import { Ellipsis } from '../../shared/Icons';
import { Stackframe } from './Stackframe';
@ -48,10 +46,7 @@ export class LibraryStackFrames extends React.Component<Props, State> {
<div>
<LibraryFrameToggle>
<EuiLink onClick={this.onClick}>
<Ellipsis
horizontal={isVisible}
style={{ marginRight: units.half }}
/>{' '}
<Ellipsis horizontal={isVisible} />{' '}
{i18n.translate(
'xpack.apm.stacktraceTab.libraryFramesToogleButtonLabel',
{

View file

@ -16,7 +16,6 @@ import {
unit,
units
} from '../../../style/variables';
// @ts-ignore
import { Ellipsis } from '../Icons';
import { PropertiesTable } from '../PropertiesTable';
@ -59,10 +58,7 @@ export class Variables extends React.Component<Props> {
return (
<VariablesContainer>
<VariablesToggle onClick={this.onClick}>
<Ellipsis
horizontal={this.state.isVisible}
style={{ marginRight: units.half }}
/>{' '}
<Ellipsis horizontal={this.state.isVisible} />{' '}
{i18n.translate(
'xpack.apm.stacktraceTab.localVariablesToogleButtonLabel',
{ defaultMessage: 'Local variables' }

View file

@ -10,8 +10,6 @@ import { isEmpty, last } from 'lodash';
import React, { Fragment } from 'react';
import { IStackframe } from '../../../../typings/es_schemas/fields/Stackframe';
import { EmptyMessage } from '../../shared/EmptyMessage';
// @ts-ignore
import { Ellipsis } from '../../shared/Icons';
import { LibraryStackFrames } from './LibraryStackFrames';
import { Stackframe } from './Stackframe';

View file

@ -4,26 +4,28 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { uiModules } from 'ui/modules'; // eslint-disable-line no-unused-vars
import chrome from 'ui/chrome';
import React, { Fragment } from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { Router } from 'react-router-dom';
import ReactDOM from 'react-dom';
import 'ui/autoload/styles';
import 'ui/autoload/all';
import 'uiExports/autocompleteProviders';
import 'react-vis/dist/style.css';
import 'ui/autoload/all';
import 'ui/autoload/styles';
import chrome from 'ui/chrome';
import { I18nContext } from 'ui/i18n';
// @ts-ignore
import { uiModules } from 'ui/modules';
import 'uiExports/autocompleteProviders';
import { GlobalHelpExtension } from './components/app/GlobalHelpExtension';
import { Main } from './components/app/Main';
import { GlobalProgress } from './components/app/Main/GlobalProgress';
import { history } from './components/shared/Links/url_helpers';
// @ts-ignore
import configureStore from './store/config/configureStore';
import './style/global_overrides.css';
import template from './templates/index.html';
import { Main } from './components/app/Main';
// @ts-ignore
import { initTimepicker } from './utils/timepicker';
import configureStore from './store/config/configureStore';
import GlobalProgress from './components/app/Main/GlobalProgress';
import { GlobalHelpExtension } from './components/app/GlobalHelpExtension';
import { history } from './components/shared/Links/url_helpers';
import { I18nContext } from 'ui/i18n';
// render APM feedback link in global help menu
chrome.helpExtension.set(domElement => {
@ -33,6 +35,7 @@ chrome.helpExtension.set(domElement => {
};
});
// @ts-ignore
chrome.setRootTemplate(template);
const store = configureStore();

View file

@ -4,9 +4,15 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { Location } from 'history';
import { AnyAction } from 'redux';
export const LOCATION_UPDATE = 'LOCATION_UPDATE';
function location(state = { pathname: '', search: '', hash: '' }, action) {
export function locationReducer(
state = { pathname: '', search: '', hash: '' },
action: AnyAction
) {
switch (action.type) {
case LOCATION_UPDATE:
return action.location;
@ -15,11 +21,9 @@ function location(state = { pathname: '', search: '', hash: '' }, action) {
}
}
export function updateLocation(nextLocation) {
export function updateLocation(nextLocation: Location) {
return {
type: LOCATION_UPDATE,
location: nextLocation
};
}
export default location;

View file

@ -10,7 +10,6 @@ import { ErrorDistributionAPIResponse } from 'x-pack/plugins/apm/server/lib/erro
import { loadErrorDistribution } from '../../services/rest/apm/error_groups';
import { IReduxState } from '../rootReducer';
import { IUrlParams } from '../urlParams';
// @ts-ignore
import { createInitialDataSelector } from './helpers';
const ID = 'errorDistribution';

View file

@ -10,7 +10,6 @@ import { ErrorGroupAPIResponse } from 'x-pack/plugins/apm/server/lib/errors/get_
import { loadErrorGroupDetails } from '../../services/rest/apm/error_groups';
import { IReduxState } from '../rootReducer';
import { IUrlParams } from '../urlParams';
// @ts-ignore
import { createInitialDataSelector } from './helpers';
const ID = 'errorGroupDetails';

View file

@ -5,16 +5,19 @@
*/
import React from 'react';
import { Request } from 'react-redux-request';
import { Request, RRRRender } from 'react-redux-request';
import { createSelector } from 'reselect';
import { TraceListAPIResponse } from 'x-pack/plugins/apm/server/lib/traces/get_top_traces';
import { loadTraceList } from '../../services/rest/apm/traces';
import { IReduxState } from '../rootReducer';
import { IUrlParams } from '../urlParams';
import { createInitialDataSelector } from './helpers';
const ID = 'traceList';
const INITIAL_DATA = [];
const INITIAL_DATA: TraceListAPIResponse = [];
const withInitialData = createInitialDataSelector(INITIAL_DATA);
const selectRRR = (state = {}) => state.reactReduxRequest;
const selectRRR = (state = {} as IReduxState) => state.reactReduxRequest;
export const selectTraceList = createSelector(
[selectRRR],
@ -23,7 +26,12 @@ export const selectTraceList = createSelector(
}
);
export function TraceListRequest({ urlParams = {}, render }) {
interface Props {
urlParams: IUrlParams;
render: RRRRender<TraceListAPIResponse>;
}
export function TraceListRequest({ urlParams, render }: Props) {
const { start, end, kuery } = urlParams;
if (!start || !end) {

View file

@ -13,8 +13,6 @@ import {
} from '../../components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers';
import { loadTrace } from '../../services/rest/apm/traces';
import { IUrlParams } from '../urlParams';
// @ts-ignore
import { createInitialDataSelector } from './helpers';
export const ID = 'waterfall';

View file

@ -8,8 +8,7 @@ import { Location } from 'history';
import { reducer } from 'react-redux-request';
import { combineReducers } from 'redux';
import { StringMap } from '../../typings/common';
// @ts-ignore
import location from './location';
import { locationReducer } from './location';
import { IUrlParams, urlParamsReducer } from './urlParams';
export interface IReduxState {
@ -19,7 +18,7 @@ export interface IReduxState {
}
export const rootReducer = combineReducers({
location,
location: locationReducer,
urlParams: urlParamsReducer,
reactReduxRequest: reducer
});

View file

@ -4,14 +4,13 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { Location } from 'history';
import { compact, pick } from 'lodash';
import { AnyAction } from 'redux';
import { createSelector } from 'reselect';
import {
legacyDecodeURIComponent,
toQuery
} from '../components/shared/Links/url_helpers';
// @ts-ignore
import { LOCATION_UPDATE } from './location';
import { getDefaultTransactionType } from './reactReduxRequest/serviceDetails';
import { getDefaultDistributionSample } from './reactReduxRequest/transactionDistribution';
@ -20,6 +19,16 @@ import { IReduxState } from './rootReducer';
// ACTION TYPES
export const TIMEPICKER_UPDATE = 'TIMEPICKER_UPDATE';
interface LocationAction {
type: typeof LOCATION_UPDATE;
location: Location;
}
interface TimepickerAction {
type: typeof TIMEPICKER_UPDATE;
time: { min: number; max: number };
}
type Action = LocationAction | TimepickerAction;
// "urlParams" contains path and query parameters from the url, that can be easily consumed from
// any (container) component with access to the store
@ -28,7 +37,7 @@ export const TIMEPICKER_UPDATE = 'TIMEPICKER_UPDATE';
// serviceName: opbeans-backend (path param)
// transactionType: Brewing%20Bot (path param)
// transactionId: 1321 (query param)
export function urlParamsReducer(state = {}, action: AnyAction) {
export function urlParamsReducer(state = {}, action: Action) {
switch (action.type) {
case LOCATION_UPDATE: {
const {

View file

@ -4,7 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
// @ts-ignore
import { metadata } from 'ui/metadata';
const STACK_VERSION = metadata.branch;

View file

@ -4,7 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
// @ts-ignore
import { metadata } from 'ui/metadata';
const STACK_VERSION = metadata.branch;

View file

@ -6,15 +6,17 @@
/* global jest */
import { mount } from 'enzyme';
import moment from 'moment';
import { createMockStore } from 'redux-test-utils';
import createHistory from 'history/createHashHistory';
import PropTypes from 'prop-types';
import { mount, ReactWrapper } from 'enzyme';
import enzymeToJson from 'enzyme-to-json';
import createHistory from 'history/createHashHistory';
import 'jest-styled-components';
import moment from 'moment';
import { Moment } from 'moment-timezone';
import PropTypes from 'prop-types';
// @ts-ignore
import { createMockStore } from 'redux-test-utils';
export function toJson(wrapper) {
export function toJson(wrapper: ReactWrapper) {
return enzymeToJson(wrapper, {
noKey: true,
mode: 'deep'
@ -27,7 +29,7 @@ const defaultRoute = {
};
export function mountWithRouterAndStore(
Component,
Component: React.ReactElement,
storeState = {},
route = defaultRoute
) {
@ -51,7 +53,7 @@ export function mountWithRouterAndStore(
return mount(Component, options);
}
export function mountWithStore(Component, storeState = {}) {
export function mountWithStore(Component: React.ReactElement, storeState = {}) {
const store = createMockStore(storeState);
const options = {
@ -68,12 +70,16 @@ export function mountWithStore(Component, storeState = {}) {
export function mockMoment() {
// avoid timezone issues
jest.spyOn(moment.prototype, 'format').mockImplementation(function() {
return `1st of January (mocking ${this.unix()})`;
});
jest
.spyOn(moment.prototype, 'format')
.mockImplementation(function(this: Moment) {
return `1st of January (mocking ${this.unix()})`;
});
// convert relative time to absolute time to avoid timing issues
jest.spyOn(moment.prototype, 'fromNow').mockImplementation(function() {
return `1337 minutes ago (mocking ${this.unix()})`;
});
jest
.spyOn(moment.prototype, 'fromNow')
.mockImplementation(function(this: Moment) {
return `1337 minutes ago (mocking ${this.unix()})`;
});
}

View file

@ -4,13 +4,15 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { SearchParams } from 'elasticsearch';
import { OBSERVER_LISTENING } from '../../../common/elasticsearch_fieldnames';
import { Setup } from '../helpers/setup_request';
// Note: this logic is duplicated in tutorials/apm/envs/on_prem
export async function getServerStatus({ setup }) {
export async function getServerStatus({ setup }: { setup: Setup }) {
const { client, config } = setup;
const params = {
const params: SearchParams = {
index: config.get('apm_oss.onboardingIndices'),
body: {
size: 0,

View file

@ -4,26 +4,28 @@
* you may not use this file except in compliance with the Elastic License.
*/
import Joi from 'joi';
import Boom from 'boom';
import { Server } from 'hapi';
import Joi from 'joi';
import { Legacy } from 'kibana';
import { getDistribution } from '../lib/errors/distribution/get_distribution';
import { getErrorGroups } from '../lib/errors/get_error_groups';
import { getErrorGroup } from '../lib/errors/get_error_group';
import { setupRequest } from '../lib/helpers/setup_request';
import { getErrorGroups } from '../lib/errors/get_error_groups';
import { withDefaultValidators } from '../lib/helpers/input_validation';
import { setupRequest } from '../lib/helpers/setup_request';
const ROOT = '/api/apm/services/{serviceName}/errors';
const defaultErrorHandler = err => {
const defaultErrorHandler = (err: Error) => {
// tslint:disable-next-line
console.error(err.stack);
throw Boom.boomify(err, { statusCode: 400 });
};
export function initErrorsApi(server) {
export function initErrorsApi(server: Server) {
server.route({
method: 'GET',
path: ROOT,
config: {
options: {
validate: {
query: withDefaultValidators({
sortField: Joi.string(),
@ -34,7 +36,10 @@ export function initErrorsApi(server) {
handler: req => {
const setup = setupRequest(req);
const { serviceName } = req.params;
const { sortField, sortDirection } = req.query;
const { sortField, sortDirection } = req.query as {
sortField: string;
sortDirection: 'desc' | 'asc';
};
return getErrorGroups({
serviceName,
@ -48,7 +53,7 @@ export function initErrorsApi(server) {
server.route({
method: 'GET',
path: `${ROOT}/{groupId}`,
config: {
options: {
validate: {
query: withDefaultValidators()
}
@ -62,19 +67,19 @@ export function initErrorsApi(server) {
}
});
const distributionHandler = req => {
function distributionHandler(req: Legacy.Request) {
const setup = setupRequest(req);
const { serviceName, groupId } = req.params;
return getDistribution({ serviceName, groupId, setup }).catch(
defaultErrorHandler
);
};
}
server.route({
method: 'GET',
path: `${ROOT}/{groupId}/distribution`,
config: {
options: {
validate: {
query: withDefaultValidators()
}
@ -85,7 +90,7 @@ export function initErrorsApi(server) {
server.route({
method: 'GET',
path: `${ROOT}/distribution`,
config: {
options: {
validate: {
query: withDefaultValidators()
}

View file

@ -4,23 +4,25 @@
* you may not use this file except in compliance with the Elastic License.
*/
import Joi from 'joi';
import Boom from 'boom';
import { getServerStatus } from '../lib/status_check/server_check';
import { getAgentStatus } from '../lib/status_check/agent_check';
import { Server } from 'hapi';
import Joi from 'joi';
import { setupRequest } from '../lib/helpers/setup_request';
import { getAgentStatus } from '../lib/status_check/agent_check';
import { getServerStatus } from '../lib/status_check/server_check';
const ROOT = '/api/apm/status';
const defaultErrorHandler = err => {
const defaultErrorHandler = (err: Error) => {
// tslint:disable-next-line
console.error(err.stack);
throw Boom.boomify(err, { statusCode: 400 });
};
export function initStatusApi(server) {
export function initStatusApi(server: Server) {
server.route({
method: 'GET',
path: `${ROOT}/server`,
config: {
options: {
validate: {
query: Joi.object().keys({
_debug: Joi.bool()
@ -36,7 +38,7 @@ export function initStatusApi(server) {
server.route({
method: 'GET',
path: `${ROOT}/agent`,
config: {
options: {
validate: {
query: Joi.object().keys({
_debug: Joi.bool()