From 6656f992d19292752c4392f596d9a7962b4b68ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?= Date: Wed, 17 Oct 2018 20:20:30 +0200 Subject: [PATCH] [APM] use react-redux-request (#24117) * [APM] use react-redux-request * Stricter type checking --- package.json | 8 +- x-pack/package.json | 1 - .../waterfall_helpers/waterfall_helpers.ts | 13 +- .../Transaction/WaterfallContainer/index.tsx | 160 +++++++----------- .../reactReduxRequest/errorDistribution.js | 2 +- .../transactionDistribution.tsx | 1 - .../store/reactReduxRequest/waterfallV1.tsx | 55 ++++++ .../store/reactReduxRequest/waterfallV2.tsx | 47 +++++ .../plugins/apm/public/store/rootReducer.ts | 1 - .../lib/helpers/transaction_group_query.ts | 8 +- .../apm/server/lib/traces/get_trace.ts | 8 +- .../spans/{get_spans.js => get_spans.ts} | 11 +- .../plugins/apm/server/routes/transactions.js | 2 +- .../apm/typings/react-redux-request.d.ts | 35 ++++ x-pack/plugins/apm/typings/waterfall.ts | 13 ++ 15 files changed, 234 insertions(+), 131 deletions(-) create mode 100644 x-pack/plugins/apm/public/store/reactReduxRequest/waterfallV1.tsx create mode 100644 x-pack/plugins/apm/public/store/reactReduxRequest/waterfallV2.tsx rename x-pack/plugins/apm/server/lib/transactions/spans/{get_spans.js => get_spans.ts} (77%) create mode 100644 x-pack/plugins/apm/typings/react-redux-request.d.ts create mode 100644 x-pack/plugins/apm/typings/waterfall.ts diff --git a/package.json b/package.json index 3a18c6d5d9327..a2037bc368a9c 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,8 @@ "url": "https://github.com/elastic/kibana.git" }, "resolutions": { - "**/@types/node": "8.10.21" + "**/@types/node": "8.10.21", + "@types/react": "16.3.14" }, "dependencies": { "@elastic/eui": "4.4.1", @@ -210,9 +211,6 @@ "x-pack": "link:x-pack", "yauzl": "2.7.0" }, - "resolutions": { - "@types/react": "16.3.14" - }, "devDependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0", @@ -249,6 +247,7 @@ "@types/listr": "^0.13.0", "@types/lodash": "^3.10.1", "@types/minimatch": "^2.0.29", + "@types/moment-timezone": "^0.5.8", "@types/node": "^8.10.20", "@types/prop-types": "^15.5.3", "@types/puppeteer": "^1.6.2", @@ -262,6 +261,7 @@ "@types/semver": "^5.5.0", "@types/sinon": "^5.0.1", "@types/strip-ansi": "^3.0.0", + "@types/styled-components": "^3.0.1", "@types/supertest": "^2.0.5", "@types/type-detect": "^4.0.1", "@types/uuid": "^3.4.4", diff --git a/x-pack/package.json b/x-pack/package.json index 27e391db83360..f3e3b0ecf8e32 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -129,7 +129,6 @@ "@samverschueren/stream-to-observable": "^0.3.0", "@scant/router": "^0.1.0", "@slack/client": "^4.2.2", - "@types/moment-timezone": "^0.5.8", "angular-resource": "1.4.9", "angular-sanitize": "1.4.9", "angular-ui-ace": "0.2.3", diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.ts b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.ts index 583eb929d5750..aedd418abbe12 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.ts +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.ts @@ -113,6 +113,10 @@ export function getWaterfall( entryTransaction: Transaction ): IWaterfall { const items = hits + .filter(hit => { + const docType = hit.processor.event; + return ['span', 'transaction'].includes(docType); + }) .map(addVersion) .map(hit => { const docType = hit.processor.event; @@ -122,10 +126,9 @@ export function getWaterfall( case 'transaction': return getTransactionItem(hit as Transaction); default: - return null; + throw new Error(`Unknown type ${docType}`); } - }) - .filter(removeEmpty); + }); const entryTransactionItem = getTransactionItem(addVersion(entryTransaction)); const root = getWaterfallRoot(items, entryTransactionItem); @@ -137,10 +140,6 @@ export function getWaterfall( }; } -function removeEmpty(value: T | null): value is T { - return value !== null; -} - function addVersion(hit: T): T { hit.version = hit.hasOwnProperty('trace') ? 'v2' : 'v1'; return hit; diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/index.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/index.tsx index 03cdacad1cc56..2c0754781cae1 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/index.tsx @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { get } from 'lodash'; -import React, { PureComponent } from 'react'; +import React from 'react'; // @ts-ignore import { SERVICE_NAME, @@ -14,8 +13,11 @@ import { } from '../../../../../../common/constants'; import { Transaction } from '../../../../../../typings/Transaction'; +import { RRRRender } from 'react-redux-request'; +import { WaterfallV1Request } from 'x-pack/plugins/apm/public/store/reactReduxRequest/waterfallV1'; +import { WaterfallV2Request } from 'x-pack/plugins/apm/public/store/reactReduxRequest/waterfallV2'; import { IUrlParams } from 'x-pack/plugins/apm/public/store/urlParams'; -import { Span } from '../../../../../../typings/Span'; +import { WaterfallResponse } from 'x-pack/plugins/apm/typings/waterfall'; // @ts-ignore import { loadSpans, loadTrace } from '../../../../../services/rest/apm'; import { getServiceColors } from './getServiceColors'; @@ -29,107 +31,65 @@ interface Props { location: any; } -interface State { - services: string[]; - hits: Array; +interface WaterfallRequestProps { + urlParams: IUrlParams; + transaction: Transaction; + render: RRRRender; } -export class WaterfallContainer extends PureComponent { - public state: Readonly = { - services: [], - hits: [] - }; - - public maybeFetchWaterfall = async (prevProps?: Props) => { - const hasTrace = this.props.transaction.hasOwnProperty('trace'); - if (hasTrace) { - this.maybeFetchTrace(prevProps); - } else { - this.maybeFetchSpans(prevProps); - } - }; - - public maybeFetchSpans = async (prevProps = {} as Partial) => { - const { start, end } = this.props.urlParams; - const { start: prevStart, end: prevEnd } = - prevProps.urlParams || ({} as IUrlParams); - - const serviceName: string = get(this.props.transaction, SERVICE_NAME); - const prevServiceName: string = get(prevProps.transaction, SERVICE_NAME); - - const transactionId: string = get(this.props.transaction, TRANSACTION_ID); - const prevTransactionId: string = get( - prevProps.transaction, - TRANSACTION_ID +function WaterfallRequest({ + urlParams, + transaction, + render +}: WaterfallRequestProps) { + const hasTrace = transaction.hasOwnProperty('trace'); + if (hasTrace) { + return ( + ); - - if ( - serviceName !== prevServiceName || - transactionId !== prevTransactionId || - start !== prevStart || - end !== prevEnd - ) { - const spans = await loadSpans({ - serviceName, - transactionId, - start, - end - }); - - this.setState({ - hits: [this.props.transaction, ...spans], - services: [serviceName] - }); - } - }; - - public maybeFetchTrace = async (prevProps = {} as Partial) => { - const { start, end } = this.props.urlParams; - const { start: prevStart, end: prevEnd } = - prevProps.urlParams || ({} as IUrlParams); - - const traceId = get(this.props, `transaction.${TRACE_ID}`); - const prevTraceId: string = get(prevProps, `transaction.${TRACE_ID}`); - - if ( - traceId && - start && - end && - (traceId !== prevTraceId || start !== prevStart || end !== prevEnd) - ) { - const { hits, services } = await loadTrace({ traceId, start, end }); - this.setState({ hits, services }); - } - }; - - public componentDidMount() { - this.maybeFetchWaterfall(); - } - - public async componentDidUpdate(prevProps: Props) { - this.maybeFetchWaterfall(prevProps); - } - - public render() { - const { location, urlParams, transaction } = this.props; - const { hits, services } = this.state; - const waterfall = getWaterfall(hits, services, transaction); - - if (!waterfall) { - return null; - } - const serviceColors = getServiceColors(waterfall.services); - + } else { return ( -
- - -
+ ); } } + +export function WaterfallContainer({ + location, + urlParams, + transaction +}: Props) { + return ( + { + const waterfall = getWaterfall(data.hits, data.services, transaction); + if (!waterfall) { + return null; + } + const serviceColors = getServiceColors(waterfall.services); + + return ( +
+ + +
+ ); + }} + /> + ); +} diff --git a/x-pack/plugins/apm/public/store/reactReduxRequest/errorDistribution.js b/x-pack/plugins/apm/public/store/reactReduxRequest/errorDistribution.js index b01bee5f37d78..a800b31f9b3b5 100644 --- a/x-pack/plugins/apm/public/store/reactReduxRequest/errorDistribution.js +++ b/x-pack/plugins/apm/public/store/reactReduxRequest/errorDistribution.js @@ -20,7 +20,7 @@ export function getErrorDistribution(state) { export function ErrorDistributionRequest({ urlParams, render }) { const { serviceName, start, end, errorGroupId, kuery } = urlParams; - if (!(serviceName, start, end, errorGroupId)) { + if (!(serviceName && start && end && errorGroupId)) { return null; } diff --git a/x-pack/plugins/apm/public/store/reactReduxRequest/transactionDistribution.tsx b/x-pack/plugins/apm/public/store/reactReduxRequest/transactionDistribution.tsx index 7e38c3dd6b2a6..7ccd3b0f20148 100644 --- a/x-pack/plugins/apm/public/store/reactReduxRequest/transactionDistribution.tsx +++ b/x-pack/plugins/apm/public/store/reactReduxRequest/transactionDistribution.tsx @@ -5,7 +5,6 @@ */ import React from 'react'; -// @ts-ignore import { Request } from 'react-redux-request'; import { IDistributionResponse } from '../../../server/lib/transactions/distribution/get_distribution'; // @ts-ignore diff --git a/x-pack/plugins/apm/public/store/reactReduxRequest/waterfallV1.tsx b/x-pack/plugins/apm/public/store/reactReduxRequest/waterfallV1.tsx new file mode 100644 index 0000000000000..24176183a954a --- /dev/null +++ b/x-pack/plugins/apm/public/store/reactReduxRequest/waterfallV1.tsx @@ -0,0 +1,55 @@ +/* + * 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 { get } from 'lodash'; +import React from 'react'; +import { Request, RRRRender } from 'react-redux-request'; +import { + SERVICE_NAME, + TRANSACTION_ID +} from 'x-pack/plugins/apm/common/constants'; +import { Span } from 'x-pack/plugins/apm/typings/Span'; +import { Transaction } from 'x-pack/plugins/apm/typings/Transaction'; +import { WaterfallResponse } from 'x-pack/plugins/apm/typings/waterfall'; +// @ts-ignore +import { loadSpans, loadTrace } from '../../services/rest/apm'; +import { IUrlParams } from '../urlParams'; +// @ts-ignore +import { createInitialDataSelector } from './helpers'; + +const ID = 'waterfallV1'; + +interface Props { + urlParams: IUrlParams; + transaction: Transaction; + render: RRRRender; +} + +export function WaterfallV1Request({ urlParams, transaction, render }: Props) { + const { start, end } = urlParams; + const transactionId: string = get(transaction, TRANSACTION_ID); + const serviceName: string = get(transaction, SERVICE_NAME); + + if (!(serviceName && transactionId && start && end)) { + return null; + } + + return ( + + id={ID} + fn={loadSpans} + args={[{ serviceName, start, end, transactionId }]} + render={({ status, data, args }) => { + const res = { + hits: [transaction, ...data], + services: [serviceName] + }; + + return render({ status, data: res, args }); + }} + /> + ); +} diff --git a/x-pack/plugins/apm/public/store/reactReduxRequest/waterfallV2.tsx b/x-pack/plugins/apm/public/store/reactReduxRequest/waterfallV2.tsx new file mode 100644 index 0000000000000..555aa97144278 --- /dev/null +++ b/x-pack/plugins/apm/public/store/reactReduxRequest/waterfallV2.tsx @@ -0,0 +1,47 @@ +/* + * 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 { get } from 'lodash'; +import React from 'react'; +import { Request, RRRRender } from 'react-redux-request'; +import { TRACE_ID } from 'x-pack/plugins/apm/common/constants'; +import { Transaction } from 'x-pack/plugins/apm/typings/Transaction'; +import { WaterfallResponse } from 'x-pack/plugins/apm/typings/waterfall'; + +// @ts-ignore +import { loadSpans, loadTrace } from '../../services/rest/apm'; +import { IUrlParams } from '../urlParams'; +// @ts-ignore +import { createInitialDataSelector } from './helpers'; + +const ID = 'waterfallV2'; + +interface Props { + urlParams: IUrlParams; + transaction: Transaction; + render: RRRRender; +} + +const defaultData = { hits: [], services: [] }; +export function WaterfallV2Request({ urlParams, transaction, render }: Props) { + const { start, end } = urlParams; + const traceId: string = get(transaction, TRACE_ID); + + if (!(traceId && start && end)) { + return null; + } + + return ( + + id={ID} + fn={loadTrace} + args={[{ traceId, start, end }]} + render={({ args, data = defaultData, status }) => + render({ args, data, status }) + } + /> + ); +} diff --git a/x-pack/plugins/apm/public/store/rootReducer.ts b/x-pack/plugins/apm/public/store/rootReducer.ts index 13089da4476a3..3efab71b998b9 100644 --- a/x-pack/plugins/apm/public/store/rootReducer.ts +++ b/x-pack/plugins/apm/public/store/rootReducer.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -// @ts-ignore import { reducer } from 'react-redux-request'; import { combineReducers } from 'redux'; import { StringMap } from '../../typings/common'; diff --git a/x-pack/plugins/apm/server/lib/helpers/transaction_group_query.ts b/x-pack/plugins/apm/server/lib/helpers/transaction_group_query.ts index b6bffcc1fe772..315c5e0b21be4 100644 --- a/x-pack/plugins/apm/server/lib/helpers/transaction_group_query.ts +++ b/x-pack/plugins/apm/server/lib/helpers/transaction_group_query.ts @@ -31,11 +31,9 @@ interface ITransactionGroupBucket { }; sample: { hits: { - hits: [ - { - _source: ITransactionGroupSample; - } - ]; + hits: Array<{ + _source: ITransactionGroupSample; + }>; }; }; } diff --git a/x-pack/plugins/apm/server/lib/traces/get_trace.ts b/x-pack/plugins/apm/server/lib/traces/get_trace.ts index 5f496469632f4..eb43e5a76185f 100644 --- a/x-pack/plugins/apm/server/lib/traces/get_trace.ts +++ b/x-pack/plugins/apm/server/lib/traces/get_trace.ts @@ -5,21 +5,17 @@ */ import { SearchParams, SearchResponse } from 'elasticsearch'; +import { WaterfallResponse } from 'x-pack/plugins/apm/typings/waterfall'; import { SERVICE_NAME, TRACE_ID } from '../../../common/constants'; import { TermsAggsBucket } from '../../../typings/elasticsearch'; import { Span } from '../../../typings/Span'; import { Transaction } from '../../../typings/Transaction'; import { Setup } from '../helpers/setup_request'; -export interface TraceResponse { - services: string[]; - hits: Array; -} - export async function getTrace( traceId: string, setup: Setup -): Promise { +): Promise { const { start, end, client, config } = setup; const params: SearchParams = { diff --git a/x-pack/plugins/apm/server/lib/transactions/spans/get_spans.js b/x-pack/plugins/apm/server/lib/transactions/spans/get_spans.ts similarity index 77% rename from x-pack/plugins/apm/server/lib/transactions/spans/get_spans.js rename to x-pack/plugins/apm/server/lib/transactions/spans/get_spans.ts index a9a2b49994fb5..dc587dfd2f49b 100644 --- a/x-pack/plugins/apm/server/lib/transactions/spans/get_spans.js +++ b/x-pack/plugins/apm/server/lib/transactions/spans/get_spans.ts @@ -4,14 +4,17 @@ * you may not use this file except in compliance with the Elastic License. */ +import { SearchResponse } from 'elasticsearch'; +import { Span } from 'x-pack/plugins/apm/typings/Span'; import { - TRANSACTION_ID, + PROCESSOR_EVENT, SPAN_START, SPAN_TYPE, - PROCESSOR_EVENT + TRANSACTION_ID } from '../../../../common/constants'; +import { Setup } from '../../helpers/setup_request'; -export async function getSpans({ transactionId, setup }) { +export async function getSpans(transactionId: string, setup: Setup) { const { start, end, client, config } = setup; const params = { @@ -47,6 +50,6 @@ export async function getSpans({ transactionId, setup }) { } }; - const resp = await client('search', params); + const resp: SearchResponse = await client('search', params); return resp.hits.hits.map(hit => hit._source); } diff --git a/x-pack/plugins/apm/server/routes/transactions.js b/x-pack/plugins/apm/server/routes/transactions.js index 7c9bb0895b019..d6bf0da9385c2 100644 --- a/x-pack/plugins/apm/server/routes/transactions.js +++ b/x-pack/plugins/apm/server/routes/transactions.js @@ -83,7 +83,7 @@ export function initTransactionsApi(server) { handler: (req, reply) => { const { transactionId } = req.params; const { setup } = req.pre; - return getSpans({ transactionId, setup }) + return getSpans(transactionId, setup) .then(reply) .catch(defaultErrorHandler(reply)); } diff --git a/x-pack/plugins/apm/typings/react-redux-request.d.ts b/x-pack/plugins/apm/typings/react-redux-request.d.ts new file mode 100644 index 0000000000000..abee82e2f6754 --- /dev/null +++ b/x-pack/plugins/apm/typings/react-redux-request.d.ts @@ -0,0 +1,35 @@ +/* + * 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. + */ + +// Everything in here should be moved to http://github.com/sqren/react-redux-request + +declare module 'react-redux-request' { + import React from 'react'; + + export interface RRRRenderArgs { + status: 'SUCCESS' | 'LOADING' | 'FAILURE'; + data: T; + args: P; + } + + export type RRRRender = ( + args: RRRRenderArgs + ) => JSX.Element | null; + + export interface RequestProps { + id: string; + fn: (args: any) => Promise; + selector?: (state: any) => any; + args?: any[]; + render?: RRRRender; + } + + export function reducer(state: any): any; + + export class Request extends React.Component< + RequestProps + > {} +} diff --git a/x-pack/plugins/apm/typings/waterfall.ts b/x-pack/plugins/apm/typings/waterfall.ts new file mode 100644 index 0000000000000..ad191907698de --- /dev/null +++ b/x-pack/plugins/apm/typings/waterfall.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Span } from './Span'; +import { Transaction } from './Transaction'; + +export interface WaterfallResponse { + services: string[]; + hits: Array; +}