mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
* [APM] Replace ui/kfetch with core.http Closes #46548. * Remove kfetch mocks in tests * Expose HttpFetchError from src/core/public/index * Make HttpFetchError public * Simplify tests for ServiceOverview
This commit is contained in:
parent
1a3c8bf814
commit
cf7f2aa8c5
65 changed files with 882 additions and 701 deletions
|
@ -0,0 +1,23 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [HttpFetchError](./kibana-plugin-public.httpfetcherror.md) > [(constructor)](./kibana-plugin-public.httpfetcherror._constructor_.md)
|
||||
|
||||
## HttpFetchError.(constructor)
|
||||
|
||||
Constructs a new instance of the `HttpFetchError` class
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
constructor(message: string, request: Request, response?: Response | undefined, body?: any);
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| message | <code>string</code> | |
|
||||
| request | <code>Request</code> | |
|
||||
| response | <code>Response | undefined</code> | |
|
||||
| body | <code>any</code> | |
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [HttpFetchError](./kibana-plugin-public.httpfetcherror.md) > [body](./kibana-plugin-public.httpfetcherror.body.md)
|
||||
|
||||
## HttpFetchError.body property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
readonly body?: any;
|
||||
```
|
|
@ -0,0 +1,27 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [HttpFetchError](./kibana-plugin-public.httpfetcherror.md)
|
||||
|
||||
## HttpFetchError class
|
||||
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export declare class HttpFetchError extends Error
|
||||
```
|
||||
|
||||
## Constructors
|
||||
|
||||
| Constructor | Modifiers | Description |
|
||||
| --- | --- | --- |
|
||||
| [(constructor)(message, request, response, body)](./kibana-plugin-public.httpfetcherror._constructor_.md) | | Constructs a new instance of the <code>HttpFetchError</code> class |
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Modifiers | Type | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| [body](./kibana-plugin-public.httpfetcherror.body.md) | | <code>any</code> | |
|
||||
| [request](./kibana-plugin-public.httpfetcherror.request.md) | | <code>Request</code> | |
|
||||
| [response](./kibana-plugin-public.httpfetcherror.response.md) | | <code>Response | undefined</code> | |
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [HttpFetchError](./kibana-plugin-public.httpfetcherror.md) > [request](./kibana-plugin-public.httpfetcherror.request.md)
|
||||
|
||||
## HttpFetchError.request property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
readonly request: Request;
|
||||
```
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [HttpFetchError](./kibana-plugin-public.httpfetcherror.md) > [response](./kibana-plugin-public.httpfetcherror.response.md)
|
||||
|
||||
## HttpFetchError.response property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
readonly response?: Response | undefined;
|
||||
```
|
|
@ -14,6 +14,7 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
|
|||
|
||||
| Class | Description |
|
||||
| --- | --- |
|
||||
| [HttpFetchError](./kibana-plugin-public.httpfetcherror.md) | |
|
||||
| [HttpInterceptController](./kibana-plugin-public.httpinterceptcontroller.md) | |
|
||||
| [SavedObjectsClient](./kibana-plugin-public.savedobjectsclient.md) | Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing plugin state. The client-side SavedObjectsClient is a thin convenience library around the SavedObjects HTTP API for interacting with Saved Objects. |
|
||||
| [SimpleSavedObject](./kibana-plugin-public.simplesavedobject.md) | This class is a very simple wrapper for SavedObjects loaded from the server with the [SavedObjectsClient](./kibana-plugin-public.savedobjectsclient.md)<!-- -->.<!-- -->It provides basic functionality for creating/saving/deleting saved objects, but doesn't include any type-specific implementations. |
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
/** @public */
|
||||
export class HttpFetchError extends Error {
|
||||
constructor(
|
||||
message: string,
|
||||
|
|
|
@ -110,6 +110,7 @@ export {
|
|||
HttpFetchQuery,
|
||||
HttpErrorResponse,
|
||||
HttpErrorRequest,
|
||||
HttpFetchError,
|
||||
HttpInterceptor,
|
||||
HttpResponse,
|
||||
HttpHandler,
|
||||
|
|
|
@ -425,8 +425,6 @@ export interface HttpErrorRequest {
|
|||
export interface HttpErrorResponse {
|
||||
// (undocumented)
|
||||
body?: HttpBody;
|
||||
// Warning: (ae-forgotten-export) The symbol "HttpFetchError" needs to be exported by the entry point index.d.ts
|
||||
//
|
||||
// (undocumented)
|
||||
error: Error | HttpFetchError;
|
||||
// (undocumented)
|
||||
|
@ -435,6 +433,17 @@ export interface HttpErrorResponse {
|
|||
response?: Response;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export class HttpFetchError extends Error {
|
||||
constructor(message: string, request: Request, response?: Response | undefined, body?: any);
|
||||
// (undocumented)
|
||||
readonly body?: any;
|
||||
// (undocumented)
|
||||
readonly request: Request;
|
||||
// (undocumented)
|
||||
readonly response?: Response | undefined;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface HttpFetchOptions extends HttpRequestInit {
|
||||
// (undocumented)
|
||||
|
|
|
@ -10,8 +10,6 @@ import React from 'react';
|
|||
import { mockMoment } from '../../../../utils/testHelpers';
|
||||
import { DetailView } from './index';
|
||||
|
||||
jest.mock('ui/kfetch');
|
||||
|
||||
describe('DetailView', () => {
|
||||
beforeEach(() => {
|
||||
// Avoid timezone issues
|
||||
|
|
|
@ -27,7 +27,6 @@ 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)};
|
||||
|
@ -63,43 +62,49 @@ export function ErrorGroupDetails() {
|
|||
const { urlParams, uiFilters } = useUrlParams();
|
||||
const { serviceName, start, end, errorGroupId } = urlParams;
|
||||
|
||||
const { data: errorGroupData } = useFetcher(() => {
|
||||
if (serviceName && start && end && errorGroupId) {
|
||||
return callApmApi({
|
||||
pathname: '/api/apm/services/{serviceName}/errors/{groupId}',
|
||||
params: {
|
||||
path: {
|
||||
serviceName,
|
||||
groupId: errorGroupId
|
||||
},
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
uiFilters: JSON.stringify(uiFilters)
|
||||
const { data: errorGroupData } = useFetcher(
|
||||
callApmApi => {
|
||||
if (serviceName && start && end && errorGroupId) {
|
||||
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]);
|
||||
});
|
||||
}
|
||||
},
|
||||
[serviceName, start, end, errorGroupId, uiFilters]
|
||||
);
|
||||
|
||||
const { data: errorDistributionData } = useFetcher(() => {
|
||||
if (serviceName && start && end && errorGroupId) {
|
||||
return callApmApi({
|
||||
pathname: '/api/apm/services/{serviceName}/errors/distribution',
|
||||
params: {
|
||||
path: {
|
||||
serviceName
|
||||
},
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
groupId: errorGroupId,
|
||||
uiFilters: JSON.stringify(uiFilters)
|
||||
const { data: errorDistributionData } = useFetcher(
|
||||
callApmApi => {
|
||||
if (serviceName && start && end && errorGroupId) {
|
||||
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]);
|
||||
});
|
||||
}
|
||||
},
|
||||
[serviceName, start, end, errorGroupId, uiFilters]
|
||||
);
|
||||
|
||||
useTrackPageview({ app: 'apm', path: 'error_group_details' });
|
||||
useTrackPageview({ app: 'apm', path: 'error_group_details', delay: 15000 });
|
||||
|
|
|
@ -20,50 +20,55 @@ 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();
|
||||
|
||||
const { serviceName, start, end, sortField, sortDirection } = urlParams;
|
||||
|
||||
const { data: errorDistributionData } = useFetcher(() => {
|
||||
if (serviceName && start && end) {
|
||||
return callApmApi({
|
||||
pathname: '/api/apm/services/{serviceName}/errors/distribution',
|
||||
params: {
|
||||
path: {
|
||||
serviceName
|
||||
},
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
uiFilters: JSON.stringify(uiFilters)
|
||||
const { data: errorDistributionData } = useFetcher(
|
||||
callApmApi => {
|
||||
if (serviceName && start && end) {
|
||||
return callApmApi({
|
||||
pathname: '/api/apm/services/{serviceName}/errors/distribution',
|
||||
params: {
|
||||
path: {
|
||||
serviceName
|
||||
},
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
uiFilters: JSON.stringify(uiFilters)
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [serviceName, start, end, uiFilters]);
|
||||
});
|
||||
}
|
||||
},
|
||||
[serviceName, start, end, uiFilters]
|
||||
);
|
||||
|
||||
const { data: errorGroupListData } = useFetcher(() => {
|
||||
if (serviceName && start && end) {
|
||||
return callApmApi({
|
||||
pathname: '/api/apm/services/{serviceName}/errors',
|
||||
params: {
|
||||
path: {
|
||||
serviceName
|
||||
},
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
sortField,
|
||||
sortDirection,
|
||||
uiFilters: JSON.stringify(uiFilters)
|
||||
const { data: errorGroupListData } = useFetcher(
|
||||
callApmApi => {
|
||||
if (serviceName && start && end) {
|
||||
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]);
|
||||
});
|
||||
}
|
||||
},
|
||||
[serviceName, start, end, sortField, sortDirection, uiFilters]
|
||||
);
|
||||
|
||||
useTrackPageview({
|
||||
app: 'apm',
|
||||
|
|
|
@ -8,7 +8,6 @@ import { shallow } from 'enzyme';
|
|||
import React from 'react';
|
||||
import { Home } from '../Home';
|
||||
|
||||
jest.mock('ui/kfetch');
|
||||
jest.mock('ui/index_patterns');
|
||||
jest.mock('ui/new_platform');
|
||||
|
||||
|
|
|
@ -10,7 +10,6 @@ import { MemoryRouter } from 'react-router-dom';
|
|||
import { UpdateBreadcrumbs } from '../UpdateBreadcrumbs';
|
||||
import * as kibanaCore from '../../../../../../observability/public/context/kibana_core';
|
||||
|
||||
jest.mock('ui/kfetch');
|
||||
jest.mock('ui/index_patterns');
|
||||
jest.mock('ui/new_platform');
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import { startMLJob } from '../../../../../services/rest/ml';
|
|||
import { IUrlParams } from '../../../../../context/UrlParamsContext/types';
|
||||
import { MLJobLink } from '../../../../shared/Links/MachineLearningLinks/MLJobLink';
|
||||
import { MachineLearningFlyoutView } from './view';
|
||||
import { KibanaCoreContext } from '../../../../../../../observability/public/context/kibana_core';
|
||||
import { KibanaCoreContext } from '../../../../../../../observability/public';
|
||||
|
||||
interface Props {
|
||||
isOpen: boolean;
|
||||
|
@ -36,11 +36,12 @@ export class MachineLearningFlyout extends Component<Props, State> {
|
|||
}) => {
|
||||
this.setState({ isCreatingJob: true });
|
||||
try {
|
||||
const { http } = this.context;
|
||||
const { serviceName } = this.props.urlParams;
|
||||
if (!serviceName) {
|
||||
throw new Error('Service name is required to create this ML job');
|
||||
}
|
||||
const res = await startMLJob({ serviceName, transactionType });
|
||||
const res = await startMLJob({ http, serviceName, transactionType });
|
||||
const didSucceed = res.datafeeds[0].success && res.jobs[0].success;
|
||||
if (!didSucceed) {
|
||||
throw new Error('Creating ML job failed');
|
||||
|
|
|
@ -22,6 +22,7 @@ import { i18n } from '@kbn/i18n';
|
|||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { useKibanaCore } from '../../../../../../../observability/public';
|
||||
import { FETCH_STATUS, useFetcher } from '../../../../../hooks/useFetcher';
|
||||
import { getHasMLJob } from '../../../../../services/rest/ml';
|
||||
import { MLJobLink } from '../../../../shared/Links/MachineLearningLinks/MLJobLink';
|
||||
|
@ -49,14 +50,18 @@ export function MachineLearningFlyoutView({
|
|||
const [selectedTransactionType, setSelectedTransactionType] = useState<
|
||||
string | undefined
|
||||
>(undefined);
|
||||
|
||||
const { http } = useKibanaCore();
|
||||
|
||||
const { data: hasMLJob = false, status } = useFetcher(() => {
|
||||
if (serviceName && selectedTransactionType) {
|
||||
return getHasMLJob({
|
||||
serviceName,
|
||||
transactionType: selectedTransactionType
|
||||
transactionType: selectedTransactionType,
|
||||
http
|
||||
});
|
||||
}
|
||||
}, [serviceName, selectedTransactionType]);
|
||||
}, [serviceName, selectedTransactionType, http]);
|
||||
|
||||
// update selectedTransactionType when list of transaction types has loaded
|
||||
useEffect(() => {
|
||||
|
|
|
@ -13,8 +13,6 @@ import * as rest from '../../../../../services/rest/watcher';
|
|||
import { createErrorGroupWatch } from '../createErrorGroupWatch';
|
||||
import { esResponse } from './esResponse';
|
||||
|
||||
jest.mock('ui/kfetch');
|
||||
|
||||
// disable html escaping since this is also disabled in watcher\s mustache implementation
|
||||
mustache.escape = value => value;
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@ import React from 'react';
|
|||
import theme from '@elastic/eui/dist/eui_theme_light.json';
|
||||
import { useUrlParams } from '../../../hooks/useUrlParams';
|
||||
import { useFetcher } from '../../../hooks/useFetcher';
|
||||
import { callApmApi } from '../../../services/rest/callApmApi';
|
||||
import { Cytoscape } from './Cytoscape';
|
||||
import { Controls } from './Controls';
|
||||
|
||||
|
@ -41,14 +40,17 @@ export function ServiceMap({ serviceName }: ServiceMapProps) {
|
|||
urlParams: { start, end }
|
||||
} = useUrlParams();
|
||||
|
||||
const { data } = useFetcher(async () => {
|
||||
if (start && end) {
|
||||
return callApmApi({
|
||||
pathname: '/api/apm/service-map',
|
||||
params: { query: { start, end } }
|
||||
});
|
||||
}
|
||||
}, [start, end]);
|
||||
const { data } = useFetcher(
|
||||
callApmApi => {
|
||||
if (start && end) {
|
||||
return callApmApi({
|
||||
pathname: '/api/apm/service-map',
|
||||
params: { query: { start, end } }
|
||||
});
|
||||
}
|
||||
},
|
||||
[start, end]
|
||||
);
|
||||
|
||||
const elements = Array.isArray(data) ? data : [];
|
||||
|
||||
|
|
|
@ -25,7 +25,6 @@ import { useServiceMetricCharts } from '../../../hooks/useServiceMetricCharts';
|
|||
import { ChartsSyncContextProvider } from '../../../context/ChartsSyncContext';
|
||||
import { MetricsChart } from '../../shared/charts/MetricsChart';
|
||||
import { useFetcher, FETCH_STATUS } from '../../../hooks/useFetcher';
|
||||
import { callApmApi } from '../../../services/rest/callApmApi';
|
||||
import { truncate, px, unit } from '../../../style/variables';
|
||||
|
||||
const INITIAL_DATA = {
|
||||
|
@ -46,20 +45,20 @@ export function ServiceNodeMetrics() {
|
|||
const { data } = useServiceMetricCharts(urlParams, agentName);
|
||||
const { start, end } = urlParams;
|
||||
|
||||
const {
|
||||
data: { host, containerId } = INITIAL_DATA,
|
||||
status
|
||||
} = useFetcher(() => {
|
||||
if (serviceName && serviceNodeName) {
|
||||
return callApmApi({
|
||||
pathname:
|
||||
'/api/apm/services/{serviceName}/node/{serviceNodeName}/metadata',
|
||||
params: {
|
||||
path: { serviceName, serviceNodeName }
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [serviceName, serviceNodeName]);
|
||||
const { data: { host, containerId } = INITIAL_DATA, status } = useFetcher(
|
||||
callApmApi => {
|
||||
if (serviceName && serviceNodeName) {
|
||||
return callApmApi({
|
||||
pathname:
|
||||
'/api/apm/services/{serviceName}/node/{serviceNodeName}/metadata',
|
||||
params: {
|
||||
path: { serviceName, serviceNodeName }
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
[serviceName, serviceNodeName]
|
||||
);
|
||||
|
||||
const isLoading = status === FETCH_STATUS.LOADING;
|
||||
|
||||
|
|
|
@ -12,7 +12,6 @@ import { LocalUIFilters } from '../../shared/LocalUIFilters';
|
|||
import { useUrlParams } from '../../../hooks/useUrlParams';
|
||||
import { ManagedTable, ITableColumn } from '../../shared/ManagedTable';
|
||||
import { useFetcher } from '../../../hooks/useFetcher';
|
||||
import { callApmApi } from '../../../services/rest/callApmApi';
|
||||
import {
|
||||
asDynamicBytes,
|
||||
asInteger,
|
||||
|
@ -46,24 +45,27 @@ const ServiceNodeOverview = () => {
|
|||
[serviceName]
|
||||
);
|
||||
|
||||
const { data: items } = useFetcher(() => {
|
||||
if (!serviceName || !start || !end) {
|
||||
return;
|
||||
}
|
||||
return callApmApi({
|
||||
pathname: '/api/apm/services/{serviceName}/serviceNodes',
|
||||
params: {
|
||||
path: {
|
||||
serviceName
|
||||
},
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
uiFilters: JSON.stringify(uiFilters)
|
||||
}
|
||||
const { data: items = [] } = useFetcher(
|
||||
callApmApi => {
|
||||
if (!serviceName || !start || !end) {
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
}, [serviceName, start, end, uiFilters]);
|
||||
return callApmApi({
|
||||
pathname: '/api/apm/services/{serviceName}/serviceNodes',
|
||||
params: {
|
||||
path: {
|
||||
serviceName
|
||||
},
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
uiFilters: JSON.stringify(uiFilters)
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
[serviceName, start, end, uiFilters]
|
||||
);
|
||||
|
||||
if (!serviceName) {
|
||||
return null;
|
||||
|
@ -134,7 +136,7 @@ const ServiceNodeOverview = () => {
|
|||
noItemsMessage={i18n.translate('xpack.apm.jvmsTable.noJvmsLabel', {
|
||||
defaultMessage: 'No JVMs were found'
|
||||
})}
|
||||
items={items || []}
|
||||
items={items}
|
||||
columns={columns}
|
||||
initialPageSize={INITIAL_PAGE_SIZE}
|
||||
initialSortField={INITIAL_SORT_FIELD}
|
||||
|
|
|
@ -7,15 +7,13 @@
|
|||
import React from 'react';
|
||||
import { render, wait, waitForElement } from 'react-testing-library';
|
||||
import 'react-testing-library/cleanup-after-each';
|
||||
import * as callApmApi from '../../../../services/rest/callApmApi';
|
||||
import { ServiceOverview } from '..';
|
||||
import * as urlParamsHooks from '../../../../hooks/useUrlParams';
|
||||
import * as kibanaCore from '../../../../../../observability/public/context/kibana_core';
|
||||
import { LegacyCoreStart } from 'src/core/public';
|
||||
import * as useLocalUIFilters from '../../../../hooks/useLocalUIFilters';
|
||||
import { FETCH_STATUS } from '../../../../hooks/useFetcher';
|
||||
|
||||
jest.mock('ui/kfetch');
|
||||
import { SessionStorageMock } from '../../../../services/__test__/SessionStorageMock';
|
||||
|
||||
function renderServiceOverview() {
|
||||
return render(<ServiceOverview />);
|
||||
|
@ -25,13 +23,24 @@ const coreMock = ({
|
|||
http: {
|
||||
basePath: {
|
||||
prepend: (path: string) => `/basepath${path}`
|
||||
}
|
||||
},
|
||||
get: jest.fn()
|
||||
},
|
||||
notifications: { toasts: { addWarning: () => {} } }
|
||||
} as unknown) as LegacyCoreStart;
|
||||
notifications: {
|
||||
toasts: {
|
||||
addWarning: () => {}
|
||||
}
|
||||
}
|
||||
} as unknown) as LegacyCoreStart & {
|
||||
http: { get: jest.Mock<any, any> };
|
||||
};
|
||||
|
||||
describe('Service Overview -> View', () => {
|
||||
beforeEach(() => {
|
||||
// @ts-ignore
|
||||
global.sessionStorage = new SessionStorageMock();
|
||||
|
||||
spyOn(kibanaCore, 'useKibanaCore').and.returnValue(coreMock);
|
||||
// mock urlParams
|
||||
spyOn(urlParamsHooks, 'useUrlParams').and.returnValue({
|
||||
urlParams: {
|
||||
|
@ -39,7 +48,6 @@ describe('Service Overview -> View', () => {
|
|||
end: 'myEnd'
|
||||
}
|
||||
});
|
||||
spyOn(kibanaCore, 'useKibanaCore').and.returnValue(coreMock);
|
||||
|
||||
jest.spyOn(useLocalUIFilters, 'useLocalUIFilters').mockReturnValue({
|
||||
filters: [],
|
||||
|
@ -65,53 +73,49 @@ describe('Service Overview -> View', () => {
|
|||
|
||||
it('should render services, when list is not empty', async () => {
|
||||
// mock rest requests
|
||||
const dataFetchingSpy = jest
|
||||
.spyOn(callApmApi, 'callApmApi')
|
||||
.mockResolvedValue({
|
||||
hasLegacyData: false,
|
||||
hasHistoricalData: true,
|
||||
items: [
|
||||
{
|
||||
serviceName: 'My Python Service',
|
||||
agentName: 'python',
|
||||
transactionsPerMinute: 100,
|
||||
errorsPerMinute: 200,
|
||||
avgResponseTime: 300,
|
||||
environments: ['test', 'dev']
|
||||
},
|
||||
{
|
||||
serviceName: 'My Go Service',
|
||||
agentName: 'go',
|
||||
transactionsPerMinute: 400,
|
||||
errorsPerMinute: 500,
|
||||
avgResponseTime: 600,
|
||||
environments: []
|
||||
}
|
||||
]
|
||||
});
|
||||
coreMock.http.get.mockResolvedValueOnce({
|
||||
hasLegacyData: false,
|
||||
hasHistoricalData: true,
|
||||
items: [
|
||||
{
|
||||
serviceName: 'My Python Service',
|
||||
agentName: 'python',
|
||||
transactionsPerMinute: 100,
|
||||
errorsPerMinute: 200,
|
||||
avgResponseTime: 300,
|
||||
environments: ['test', 'dev']
|
||||
},
|
||||
{
|
||||
serviceName: 'My Go Service',
|
||||
agentName: 'go',
|
||||
transactionsPerMinute: 400,
|
||||
errorsPerMinute: 500,
|
||||
avgResponseTime: 600,
|
||||
environments: []
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const { container, getByText } = renderServiceOverview();
|
||||
|
||||
// wait for requests to be made
|
||||
await wait(() => expect(dataFetchingSpy).toHaveBeenCalledTimes(1));
|
||||
await wait(() => expect(coreMock.http.get).toHaveBeenCalledTimes(1));
|
||||
await waitForElement(() => getByText('My Python Service'));
|
||||
|
||||
expect(container.querySelectorAll('.euiTableRow')).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should render getting started message, when list is empty and no historical data is found', async () => {
|
||||
const dataFetchingSpy = jest
|
||||
.spyOn(callApmApi, 'callApmApi')
|
||||
.mockResolvedValue({
|
||||
hasLegacyData: false,
|
||||
hasHistoricalData: false,
|
||||
items: []
|
||||
});
|
||||
coreMock.http.get.mockResolvedValueOnce({
|
||||
hasLegacyData: false,
|
||||
hasHistoricalData: false,
|
||||
items: []
|
||||
});
|
||||
|
||||
const { container, getByText } = renderServiceOverview();
|
||||
|
||||
// wait for requests to be made
|
||||
await wait(() => expect(dataFetchingSpy).toHaveBeenCalledTimes(1));
|
||||
await wait(() => expect(coreMock.http.get).toHaveBeenCalledTimes(1));
|
||||
|
||||
// wait for elements to be rendered
|
||||
await waitForElement(() =>
|
||||
|
@ -124,18 +128,16 @@ describe('Service Overview -> View', () => {
|
|||
});
|
||||
|
||||
it('should render empty message, when list is empty and historical data is found', async () => {
|
||||
const dataFetchingSpy = jest
|
||||
.spyOn(callApmApi, 'callApmApi')
|
||||
.mockResolvedValue({
|
||||
hasLegacyData: false,
|
||||
hasHistoricalData: true,
|
||||
items: []
|
||||
});
|
||||
coreMock.http.get.mockResolvedValueOnce({
|
||||
hasLegacyData: false,
|
||||
hasHistoricalData: true,
|
||||
items: []
|
||||
});
|
||||
|
||||
const { container, getByText } = renderServiceOverview();
|
||||
|
||||
// wait for requests to be made
|
||||
await wait(() => expect(dataFetchingSpy).toHaveBeenCalledTimes(1));
|
||||
await wait(() => expect(coreMock.http.get).toHaveBeenCalledTimes(1));
|
||||
await waitForElement(() => getByText('No services found'));
|
||||
|
||||
expect(container.querySelectorAll('.euiTableRow')).toMatchSnapshot();
|
||||
|
@ -149,18 +151,16 @@ describe('Service Overview -> View', () => {
|
|||
'addWarning'
|
||||
);
|
||||
|
||||
const dataFetchingSpy = jest
|
||||
.spyOn(callApmApi, 'callApmApi')
|
||||
.mockResolvedValue({
|
||||
hasLegacyData: true,
|
||||
hasHistoricalData: true,
|
||||
items: []
|
||||
});
|
||||
coreMock.http.get.mockResolvedValueOnce({
|
||||
hasLegacyData: true,
|
||||
hasHistoricalData: true,
|
||||
items: []
|
||||
});
|
||||
|
||||
renderServiceOverview();
|
||||
|
||||
// wait for requests to be made
|
||||
await wait(() => expect(dataFetchingSpy).toHaveBeenCalledTimes(1));
|
||||
await wait(() => expect(coreMock.http.get).toHaveBeenCalledTimes(1));
|
||||
|
||||
expect(addWarning).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
|
@ -177,18 +177,16 @@ describe('Service Overview -> View', () => {
|
|||
coreMock.notifications.toasts,
|
||||
'addWarning'
|
||||
);
|
||||
const dataFetchingSpy = jest
|
||||
.spyOn(callApmApi, 'callApmApi')
|
||||
.mockResolvedValue({
|
||||
hasLegacyData: false,
|
||||
hasHistoricalData: true,
|
||||
items: []
|
||||
});
|
||||
coreMock.http.get.mockResolvedValueOnce({
|
||||
hasLegacyData: false,
|
||||
hasHistoricalData: true,
|
||||
items: []
|
||||
});
|
||||
|
||||
renderServiceOverview();
|
||||
|
||||
// wait for requests to be made
|
||||
await wait(() => expect(dataFetchingSpy).toHaveBeenCalledTimes(1));
|
||||
await wait(() => expect(coreMock.http.get).toHaveBeenCalledTimes(1));
|
||||
|
||||
expect(addWarning).not.toHaveBeenCalled();
|
||||
});
|
||||
|
|
|
@ -17,7 +17,6 @@ import { useTrackPageview } from '../../../../../infra/public';
|
|||
import { useKibanaCore } from '../../../../../observability/public';
|
||||
import { PROJECTION } from '../../../../common/projections/typings';
|
||||
import { LocalUIFilters } from '../../shared/LocalUIFilters';
|
||||
import { callApmApi } from '../../../services/rest/callApmApi';
|
||||
|
||||
const initalData = {
|
||||
items: [],
|
||||
|
@ -33,16 +32,19 @@ export function ServiceOverview() {
|
|||
urlParams: { start, end },
|
||||
uiFilters
|
||||
} = useUrlParams();
|
||||
const { data = initalData, status } = useFetcher(() => {
|
||||
if (start && end) {
|
||||
return callApmApi({
|
||||
pathname: '/api/apm/services',
|
||||
params: {
|
||||
query: { start, end, uiFilters: JSON.stringify(uiFilters) }
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [start, end, uiFilters]);
|
||||
const { data = initalData, status } = useFetcher(
|
||||
callApmApi => {
|
||||
if (start && end) {
|
||||
return callApmApi({
|
||||
pathname: '/api/apm/services',
|
||||
params: {
|
||||
query: { start, end, uiFilters: JSON.stringify(uiFilters) }
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
[start, end, uiFilters]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (data.hasLegacyData && !hasDisplayedToast) {
|
||||
|
|
|
@ -8,10 +8,11 @@ import React, { useState } from 'react';
|
|||
import { EuiButtonEmpty } from '@elastic/eui';
|
||||
import { NotificationsStart } from 'kibana/public';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useCallApmApi } from '../../../../../hooks/useCallApmApi';
|
||||
import { Config } from '../index';
|
||||
import { callApmApi } from '../../../../../services/rest/callApmApi';
|
||||
import { getOptionLabel } from '../../../../../../common/agent_configuration_constants';
|
||||
import { useKibanaCore } from '../../../../../../../observability/public';
|
||||
import { APMClient } from '../../../../../services/rest/createCallApmApi';
|
||||
|
||||
interface Props {
|
||||
onDeleted: () => void;
|
||||
|
@ -24,6 +25,8 @@ export function DeleteButton({ onDeleted, selectedConfig }: Props) {
|
|||
notifications: { toasts }
|
||||
} = useKibanaCore();
|
||||
|
||||
const callApmApi = useCallApmApi();
|
||||
|
||||
return (
|
||||
<EuiButtonEmpty
|
||||
color="danger"
|
||||
|
@ -31,7 +34,7 @@ export function DeleteButton({ onDeleted, selectedConfig }: Props) {
|
|||
iconSide="right"
|
||||
onClick={async () => {
|
||||
setIsDeleting(true);
|
||||
await deleteConfig(selectedConfig, toasts);
|
||||
await deleteConfig(callApmApi, selectedConfig, toasts);
|
||||
setIsDeleting(false);
|
||||
onDeleted();
|
||||
}}
|
||||
|
@ -45,6 +48,7 @@ export function DeleteButton({ onDeleted, selectedConfig }: Props) {
|
|||
}
|
||||
|
||||
async function deleteConfig(
|
||||
callApmApi: APMClient,
|
||||
selectedConfig: Config,
|
||||
toasts: NotificationsStart['toasts']
|
||||
) {
|
||||
|
|
|
@ -9,7 +9,6 @@ import React from 'react';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { SelectWithPlaceholder } from '../../../../shared/SelectWithPlaceholder';
|
||||
import { useFetcher } from '../../../../../hooks/useFetcher';
|
||||
import { callApmApi } from '../../../../../services/rest/callApmApi';
|
||||
import {
|
||||
getOptionLabel,
|
||||
omitAllOption
|
||||
|
@ -36,7 +35,7 @@ export function ServiceSection({
|
|||
setEnvironment
|
||||
}: Props) {
|
||||
const { data: serviceNames = [], status: serviceNamesStatus } = useFetcher(
|
||||
() => {
|
||||
callApmApi => {
|
||||
if (!isReadOnly) {
|
||||
return callApmApi({
|
||||
pathname: '/api/apm/settings/agent-configuration/services',
|
||||
|
@ -48,7 +47,7 @@ export function ServiceSection({
|
|||
{ preservePreviousData: false }
|
||||
);
|
||||
const { data: environments = [], status: environmentStatus } = useFetcher(
|
||||
() => {
|
||||
callApmApi => {
|
||||
if (!isReadOnly && serviceName) {
|
||||
return callApmApi({
|
||||
pathname: '/api/apm/settings/agent-configuration/environments',
|
||||
|
|
|
@ -23,8 +23,8 @@ import { idx } from '@kbn/elastic-idx';
|
|||
import React, { useState } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { isRight } from 'fp-ts/lib/Either';
|
||||
import { useCallApmApi } from '../../../../../hooks/useCallApmApi';
|
||||
import { transactionSampleRateRt } from '../../../../../../common/runtime_types/transaction_sample_rate_rt';
|
||||
import { callApmApi } from '../../../../../services/rest/callApmApi';
|
||||
import { Config } from '../index';
|
||||
import { SettingsSection } from './SettingsSection';
|
||||
import { ServiceSection } from './ServiceSection';
|
||||
|
@ -60,6 +60,8 @@ export function AddEditFlyout({
|
|||
} = useKibanaCore();
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
|
||||
const callApmApiFromHook = useCallApmApi();
|
||||
|
||||
// config conditions (service)
|
||||
const [serviceName, setServiceName] = useState<string>(
|
||||
selectedConfig ? selectedConfig.service.name || ALL_OPTION_VALUE : ''
|
||||
|
@ -69,9 +71,9 @@ export function AddEditFlyout({
|
|||
);
|
||||
|
||||
const { data: { agentName } = { agentName: undefined } } = useFetcher(
|
||||
() => {
|
||||
callApmApi => {
|
||||
if (serviceName === ALL_OPTION_VALUE) {
|
||||
return { agentName: undefined };
|
||||
return Promise.resolve({ agentName: undefined });
|
||||
}
|
||||
|
||||
if (serviceName) {
|
||||
|
@ -127,6 +129,7 @@ export function AddEditFlyout({
|
|||
setIsSaving(true);
|
||||
|
||||
await saveConfig({
|
||||
callApmApi: callApmApiFromHook,
|
||||
serviceName,
|
||||
environment,
|
||||
sampleRate,
|
||||
|
|
|
@ -6,13 +6,13 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { NotificationsStart } from 'kibana/public';
|
||||
import { APMClient } from '../../../../../services/rest/createCallApmApi';
|
||||
import { trackEvent } from '../../../../../../../infra/public/hooks/use_track_metric';
|
||||
import { isRumAgentName } from '../../../../../../common/agent_name';
|
||||
import {
|
||||
getOptionLabel,
|
||||
omitAllOption
|
||||
} from '../../../../../../common/agent_configuration_constants';
|
||||
import { callApmApi } from '../../../../../services/rest/callApmApi';
|
||||
|
||||
interface Settings {
|
||||
transaction_sample_rate: number;
|
||||
|
@ -21,6 +21,7 @@ interface Settings {
|
|||
}
|
||||
|
||||
export async function saveConfig({
|
||||
callApmApi,
|
||||
serviceName,
|
||||
environment,
|
||||
sampleRate,
|
||||
|
@ -30,6 +31,7 @@ export async function saveConfig({
|
|||
agentName,
|
||||
toasts
|
||||
}: {
|
||||
callApmApi: APMClient;
|
||||
serviceName: string;
|
||||
environment: string;
|
||||
sampleRate: string;
|
||||
|
|
|
@ -18,7 +18,6 @@ import {
|
|||
import { isEmpty } from 'lodash';
|
||||
import { useFetcher } from '../../../../hooks/useFetcher';
|
||||
import { AgentConfigurationListAPIResponse } from '../../../../../server/lib/settings/agent_configuration/list_configurations';
|
||||
import { callApmApi } from '../../../../services/rest/callApmApi';
|
||||
import { HomeLink } from '../../../shared/Links/apm/HomeLink';
|
||||
import { AgentConfigurationList } from './AgentConfigurationList';
|
||||
import { useTrackPageview } from '../../../../../../infra/public';
|
||||
|
@ -28,7 +27,8 @@ export type Config = AgentConfigurationListAPIResponse[0];
|
|||
|
||||
export function AgentConfigurations() {
|
||||
const { data = [], status, refetch } = useFetcher(
|
||||
() => callApmApi({ pathname: `/api/apm/settings/agent-configuration` }),
|
||||
callApmApi =>
|
||||
callApmApi({ pathname: `/api/apm/settings/agent-configuration` }),
|
||||
[],
|
||||
{ preservePreviousData: false }
|
||||
);
|
||||
|
|
|
@ -12,25 +12,27 @@ import { useUrlParams } from '../../../hooks/useUrlParams';
|
|||
import { useTrackPageview } from '../../../../../infra/public';
|
||||
import { LocalUIFilters } from '../../shared/LocalUIFilters';
|
||||
import { PROJECTION } from '../../../../common/projections/typings';
|
||||
import { callApmApi } from '../../../services/rest/callApmApi';
|
||||
|
||||
export function TraceOverview() {
|
||||
const { urlParams, uiFilters } = useUrlParams();
|
||||
const { start, end } = urlParams;
|
||||
const { status, data = [] } = useFetcher(() => {
|
||||
if (start && end) {
|
||||
return callApmApi({
|
||||
pathname: '/api/apm/traces',
|
||||
params: {
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
uiFilters: JSON.stringify(uiFilters)
|
||||
const { status, data = [] } = useFetcher(
|
||||
callApmApi => {
|
||||
if (start && end) {
|
||||
return callApmApi({
|
||||
pathname: '/api/apm/traces',
|
||||
params: {
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
uiFilters: JSON.stringify(uiFilters)
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [start, end, uiFilters]);
|
||||
});
|
||||
}
|
||||
},
|
||||
[start, end, uiFilters]
|
||||
);
|
||||
|
||||
useTrackPageview({ app: 'apm', path: 'traces_overview' });
|
||||
useTrackPageview({ app: 'apm', path: 'traces_overview', delay: 15000 });
|
||||
|
|
|
@ -28,8 +28,6 @@ import { LegacyCoreStart } from 'kibana/public';
|
|||
jest.spyOn(history, 'push');
|
||||
jest.spyOn(history, 'replace');
|
||||
|
||||
jest.mock('ui/kfetch');
|
||||
|
||||
const coreMock = ({
|
||||
notifications: { toasts: { addWarning: () => {} } }
|
||||
} as unknown) as LegacyCoreStart;
|
||||
|
|
|
@ -15,6 +15,7 @@ import {
|
|||
import { Location } from 'history';
|
||||
import { first } from 'lodash';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useKibanaCore } from '../../../../../observability/public';
|
||||
import { useTransactionList } from '../../../hooks/useTransactionList';
|
||||
import { useTransactionCharts } from '../../../hooks/useTransactionCharts';
|
||||
import { IUrlParams } from '../../../context/UrlParamsContext/types';
|
||||
|
@ -85,11 +86,13 @@ export function TransactionOverview() {
|
|||
status: transactionListStatus
|
||||
} = useTransactionList(urlParams);
|
||||
|
||||
const { http } = useKibanaCore();
|
||||
|
||||
const { data: hasMLJob = false } = useFetcher(() => {
|
||||
if (serviceName && transactionType) {
|
||||
return getHasMLJob({ serviceName, transactionType });
|
||||
return getHasMLJob({ serviceName, transactionType, http });
|
||||
}
|
||||
}, [serviceName, transactionType]);
|
||||
}, [http, serviceName, transactionType]);
|
||||
|
||||
const localFiltersConfig: React.ComponentProps<
|
||||
typeof LocalUIFilters
|
||||
|
|
|
@ -16,7 +16,6 @@ import {
|
|||
ENVIRONMENT_ALL,
|
||||
ENVIRONMENT_NOT_DEFINED
|
||||
} from '../../../../common/environment_filter_values';
|
||||
import { callApmApi } from '../../../services/rest/callApmApi';
|
||||
|
||||
function updateEnvironmentUrl(
|
||||
location: ReturnType<typeof useLocation>,
|
||||
|
@ -79,20 +78,23 @@ export const EnvironmentFilter: React.FC = () => {
|
|||
const { start, end, serviceName } = urlParams;
|
||||
|
||||
const { environment } = uiFilters;
|
||||
const { data: environments = [], status = 'loading' } = useFetcher(() => {
|
||||
if (start && end) {
|
||||
return callApmApi({
|
||||
pathname: '/api/apm/ui_filters/environments',
|
||||
params: {
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
serviceName
|
||||
const { data: environments = [], status = 'loading' } = useFetcher(
|
||||
callApmApi => {
|
||||
if (start && end) {
|
||||
return callApmApi({
|
||||
pathname: '/api/apm/ui_filters/environments',
|
||||
params: {
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
serviceName
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [start, end, serviceName]);
|
||||
});
|
||||
}
|
||||
},
|
||||
[start, end, serviceName]
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiSelect
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { uniqueId, startsWith } from 'lodash';
|
||||
import { EuiCallOut } from '@elastic/eui';
|
||||
import styled from 'styled-components';
|
||||
|
@ -25,8 +25,10 @@ import { history } from '../../../utils/history';
|
|||
import { useMatchedRoutes } from '../../../hooks/useMatchedRoutes';
|
||||
import { RouteName } from '../../app/Main/route_config/route_names';
|
||||
import { useKibanaCore } from '../../../../../observability/public';
|
||||
import { getAPMIndexPattern } from '../../../services/rest/savedObjects';
|
||||
import { ISavedObject } from '../../../services/rest/savedObjects';
|
||||
import { AutocompleteSuggestion } from '../../../../../../../../src/plugins/data/public';
|
||||
import { FETCH_STATUS } from '../../../hooks/useFetcher';
|
||||
import { useAPMIndexPattern } from '../../../hooks/useAPMIndexPattern';
|
||||
|
||||
const Container = styled.div`
|
||||
margin-bottom: 10px;
|
||||
|
@ -36,9 +38,7 @@ const getAutocompleteProvider = (language: string) =>
|
|||
npStart.plugins.data.autocomplete.getProvider(language);
|
||||
|
||||
interface State {
|
||||
indexPattern: StaticIndexPattern | null;
|
||||
suggestions: AutocompleteSuggestion[];
|
||||
isLoadingIndexPattern: boolean;
|
||||
isLoadingSuggestions: boolean;
|
||||
}
|
||||
|
||||
|
@ -50,13 +50,9 @@ function convertKueryToEsQuery(
|
|||
return toElasticsearchQuery(ast, indexPattern);
|
||||
}
|
||||
|
||||
async function getAPMIndexPatternForKuery(): Promise<
|
||||
StaticIndexPattern | undefined
|
||||
> {
|
||||
const apmIndexPattern = await getAPMIndexPattern();
|
||||
if (!apmIndexPattern) {
|
||||
return;
|
||||
}
|
||||
function getAPMIndexPatternForKuery(
|
||||
apmIndexPattern: ISavedObject
|
||||
): StaticIndexPattern | undefined {
|
||||
return getFromSavedObject(apmIndexPattern);
|
||||
}
|
||||
|
||||
|
@ -89,9 +85,7 @@ function getSuggestions(
|
|||
export function KueryBar() {
|
||||
const core = useKibanaCore();
|
||||
const [state, setState] = useState<State>({
|
||||
indexPattern: null,
|
||||
suggestions: [],
|
||||
isLoadingIndexPattern: true,
|
||||
isLoadingSuggestions: false
|
||||
});
|
||||
const { urlParams } = useUrlParams();
|
||||
|
@ -101,8 +95,19 @@ export function KueryBar() {
|
|||
const apmIndexPatternTitle = core.injectedMetadata.getInjectedVar(
|
||||
'apmIndexPatternTitle'
|
||||
);
|
||||
const indexPatternMissing =
|
||||
!state.isLoadingIndexPattern && !state.indexPattern;
|
||||
|
||||
const {
|
||||
apmIndexPattern,
|
||||
status: apmIndexPatternStatus
|
||||
} = useAPMIndexPattern();
|
||||
|
||||
const indexPattern =
|
||||
apmIndexPatternStatus === FETCH_STATUS.SUCCESS
|
||||
? getAPMIndexPatternForKuery(apmIndexPattern)
|
||||
: null;
|
||||
|
||||
const indexPatternMissing = status === FETCH_STATUS.SUCCESS && !indexPattern;
|
||||
|
||||
let currentRequestCheck;
|
||||
|
||||
const exampleMap: { [key: string]: string } = {
|
||||
|
@ -116,36 +121,8 @@ export function KueryBar() {
|
|||
matchedRoutes.map(({ name }) => exampleMap[name]).find(Boolean) ||
|
||||
'transaction.duration.us > 300000 AND http.response.status_code >= 400';
|
||||
|
||||
useEffect(() => {
|
||||
let didCancel = false;
|
||||
|
||||
async function loadIndexPattern() {
|
||||
setState(value => ({ ...value, isLoadingIndexPattern: true }));
|
||||
const indexPattern = await getAPMIndexPatternForKuery();
|
||||
if (didCancel) {
|
||||
return;
|
||||
}
|
||||
if (!indexPattern) {
|
||||
setState(value => ({ ...value, isLoadingIndexPattern: false }));
|
||||
} else {
|
||||
setState(value => ({
|
||||
...value,
|
||||
indexPattern,
|
||||
isLoadingIndexPattern: false
|
||||
}));
|
||||
}
|
||||
}
|
||||
loadIndexPattern();
|
||||
|
||||
return () => {
|
||||
didCancel = true;
|
||||
};
|
||||
}, []);
|
||||
|
||||
async function onChange(inputValue: string, selectionStart: number) {
|
||||
const { indexPattern } = state;
|
||||
|
||||
if (indexPattern === null) {
|
||||
if (indexPattern == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -177,9 +154,7 @@ export function KueryBar() {
|
|||
}
|
||||
|
||||
function onSubmit(inputValue: string) {
|
||||
const { indexPattern } = state;
|
||||
|
||||
if (indexPattern === null) {
|
||||
if (indexPattern == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ interface Props {
|
|||
|
||||
export function DiscoverLink({ query = {}, ...rest }: Props) {
|
||||
const core = useKibanaCore();
|
||||
const apmIndexPattern = useAPMIndexPattern();
|
||||
const { apmIndexPattern } = useAPMIndexPattern();
|
||||
const location = useLocation();
|
||||
|
||||
if (!apmIndexPattern.id) {
|
||||
|
|
|
@ -10,8 +10,6 @@ import React from 'react';
|
|||
import { APMError } from '../../../../../../typings/es_schemas/ui/APMError';
|
||||
import { DiscoverErrorLink } from '../DiscoverErrorLink';
|
||||
|
||||
jest.mock('ui/kfetch');
|
||||
|
||||
describe('DiscoverErrorLink without kuery', () => {
|
||||
let wrapper: ShallowWrapper;
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -10,8 +10,6 @@ import React from 'react';
|
|||
import { APMError } from '../../../../../../typings/es_schemas/ui/APMError';
|
||||
import { DiscoverErrorLink } from '../DiscoverErrorLink';
|
||||
|
||||
jest.mock('ui/kfetch');
|
||||
|
||||
describe('DiscoverErrorLink without kuery', () => {
|
||||
let wrapper: ShallowWrapper;
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -9,115 +9,120 @@ import React from 'react';
|
|||
import { APMError } from '../../../../../../typings/es_schemas/ui/APMError';
|
||||
import { Span } from '../../../../../../typings/es_schemas/ui/Span';
|
||||
import { Transaction } from '../../../../../../typings/es_schemas/ui/Transaction';
|
||||
import * as savedObjects from '../../../../../services/rest/savedObjects';
|
||||
import { getRenderedHref } from '../../../../../utils/testHelpers';
|
||||
import { DiscoverErrorLink } from '../DiscoverErrorLink';
|
||||
import { DiscoverSpanLink } from '../DiscoverSpanLink';
|
||||
import { DiscoverTransactionLink } from '../DiscoverTransactionLink';
|
||||
import * as kibanaCore from '../../../../../../../observability/public/context/kibana_core';
|
||||
import * as useAPMIndexPattern from '../../../../../hooks/useAPMIndexPattern';
|
||||
import { LegacyCoreStart } from 'src/core/public';
|
||||
import { FETCH_STATUS } from '../../../../../hooks/useFetcher';
|
||||
|
||||
jest.mock('ui/kfetch');
|
||||
jest.spyOn(useAPMIndexPattern, 'useAPMIndexPattern').mockReturnValue({
|
||||
status: FETCH_STATUS.SUCCESS,
|
||||
apmIndexPattern: { id: 'apm-index-pattern-id' }
|
||||
} as any);
|
||||
|
||||
jest
|
||||
.spyOn(savedObjects, 'getAPMIndexPattern')
|
||||
.mockResolvedValue({ id: 'apm-index-pattern-id' } as any);
|
||||
describe('DiscoverLinks', () => {
|
||||
beforeAll(() => {
|
||||
jest.spyOn(console, 'error').mockImplementation(() => null);
|
||||
|
||||
beforeAll(() => {
|
||||
jest.spyOn(console, 'error').mockImplementation(() => null);
|
||||
|
||||
const coreMock = ({
|
||||
http: {
|
||||
basePath: {
|
||||
prepend: (path: string) => `/basepath${path}`
|
||||
const coreMock = ({
|
||||
http: {
|
||||
notifications: {
|
||||
toasts: {}
|
||||
},
|
||||
basePath: {
|
||||
prepend: (path: string) => `/basepath${path}`
|
||||
}
|
||||
}
|
||||
}
|
||||
} as unknown) as LegacyCoreStart;
|
||||
} as unknown) as LegacyCoreStart;
|
||||
|
||||
jest.spyOn(kibanaCore, 'useKibanaCore').mockReturnValue(coreMock);
|
||||
});
|
||||
spyOn(kibanaCore, 'useKibanaCore').and.returnValue(coreMock);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
afterAll(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
test('DiscoverTransactionLink should produce the correct URL', async () => {
|
||||
const transaction = {
|
||||
transaction: {
|
||||
id: '8b60bd32ecc6e150'
|
||||
},
|
||||
trace: {
|
||||
id: '8b60bd32ecc6e1506735a8b6cfcf175c'
|
||||
}
|
||||
} as Transaction;
|
||||
it('produces the correct URL for a transaction', async () => {
|
||||
const transaction = {
|
||||
transaction: {
|
||||
id: '8b60bd32ecc6e150'
|
||||
},
|
||||
trace: {
|
||||
id: '8b60bd32ecc6e1506735a8b6cfcf175c'
|
||||
}
|
||||
} as Transaction;
|
||||
|
||||
const href = await getRenderedHref(
|
||||
() => <DiscoverTransactionLink transaction={transaction} />,
|
||||
{
|
||||
const href = await getRenderedHref(
|
||||
() => <DiscoverTransactionLink transaction={transaction} />,
|
||||
{
|
||||
search: '?rangeFrom=now/w&rangeTo=now'
|
||||
} as Location
|
||||
);
|
||||
|
||||
expect(href).toEqual(
|
||||
`/basepath/app/kibana#/discover?_g=(refreshInterval:(pause:true,value:'0'),time:(from:now%2Fw,to:now))&_a=(index:apm-index-pattern-id,interval:auto,query:(language:lucene,query:'processor.event:"transaction" AND transaction.id:"8b60bd32ecc6e150" AND trace.id:"8b60bd32ecc6e1506735a8b6cfcf175c"'))`
|
||||
);
|
||||
});
|
||||
|
||||
it('produces the correct URL for a span', async () => {
|
||||
const span = {
|
||||
span: {
|
||||
id: 'test-span-id'
|
||||
}
|
||||
} as Span;
|
||||
|
||||
const href = await getRenderedHref(() => <DiscoverSpanLink span={span} />, {
|
||||
search: '?rangeFrom=now/w&rangeTo=now'
|
||||
} as Location
|
||||
);
|
||||
} as Location);
|
||||
|
||||
expect(href).toEqual(
|
||||
`/basepath/app/kibana#/discover?_g=(refreshInterval:(pause:true,value:'0'),time:(from:now%2Fw,to:now))&_a=(index:apm-index-pattern-id,interval:auto,query:(language:lucene,query:'processor.event:"transaction" AND transaction.id:"8b60bd32ecc6e150" AND trace.id:"8b60bd32ecc6e1506735a8b6cfcf175c"'))`
|
||||
);
|
||||
});
|
||||
|
||||
test('DiscoverSpanLink should produce the correct URL', async () => {
|
||||
const span = {
|
||||
span: {
|
||||
id: 'test-span-id'
|
||||
}
|
||||
} as Span;
|
||||
|
||||
const href = await getRenderedHref(() => <DiscoverSpanLink span={span} />, {
|
||||
search: '?rangeFrom=now/w&rangeTo=now'
|
||||
} as Location);
|
||||
|
||||
expect(href).toEqual(
|
||||
`/basepath/app/kibana#/discover?_g=(refreshInterval:(pause:true,value:'0'),time:(from:now%2Fw,to:now))&_a=(index:apm-index-pattern-id,interval:auto,query:(language:lucene,query:'span.id:"test-span-id"'))`
|
||||
);
|
||||
});
|
||||
|
||||
test('DiscoverErrorLink should produce the correct URL', async () => {
|
||||
const error = {
|
||||
service: {
|
||||
name: 'service-name'
|
||||
},
|
||||
error: {
|
||||
grouping_key: 'grouping-key'
|
||||
}
|
||||
} as APMError;
|
||||
const href = await getRenderedHref(
|
||||
() => <DiscoverErrorLink error={error} />,
|
||||
{
|
||||
search: '?rangeFrom=now/w&rangeTo=now'
|
||||
} as Location
|
||||
);
|
||||
|
||||
expect(href).toEqual(
|
||||
`/basepath/app/kibana#/discover?_g=(refreshInterval:(pause:true,value:'0'),time:(from:now%2Fw,to:now))&_a=(index:apm-index-pattern-id,interval:auto,query:(language:lucene,query:'service.name:"service-name" AND error.grouping_key:"grouping-key"'),sort:('@timestamp':desc))`
|
||||
);
|
||||
});
|
||||
|
||||
test('DiscoverErrorLink should include optional kuery string in URL', async () => {
|
||||
const error = {
|
||||
service: {
|
||||
name: 'service-name'
|
||||
},
|
||||
error: {
|
||||
grouping_key: 'grouping-key'
|
||||
}
|
||||
} as APMError;
|
||||
|
||||
const href = await getRenderedHref(
|
||||
() => <DiscoverErrorLink error={error} kuery="some:kuery-string" />,
|
||||
{
|
||||
search: '?rangeFrom=now/w&rangeTo=now'
|
||||
} as Location
|
||||
);
|
||||
|
||||
expect(href).toEqual(
|
||||
`/basepath/app/kibana#/discover?_g=(refreshInterval:(pause:true,value:'0'),time:(from:now%2Fw,to:now))&_a=(index:apm-index-pattern-id,interval:auto,query:(language:lucene,query:'service.name:"service-name" AND error.grouping_key:"grouping-key" AND some:kuery-string'),sort:('@timestamp':desc))`
|
||||
);
|
||||
expect(href).toEqual(
|
||||
`/basepath/app/kibana#/discover?_g=(refreshInterval:(pause:true,value:'0'),time:(from:now%2Fw,to:now))&_a=(index:apm-index-pattern-id,interval:auto,query:(language:lucene,query:'span.id:"test-span-id"'))`
|
||||
);
|
||||
});
|
||||
|
||||
test('DiscoverErrorLink should produce the correct URL', async () => {
|
||||
const error = {
|
||||
service: {
|
||||
name: 'service-name'
|
||||
},
|
||||
error: {
|
||||
grouping_key: 'grouping-key'
|
||||
}
|
||||
} as APMError;
|
||||
const href = await getRenderedHref(
|
||||
() => <DiscoverErrorLink error={error} />,
|
||||
{
|
||||
search: '?rangeFrom=now/w&rangeTo=now'
|
||||
} as Location
|
||||
);
|
||||
|
||||
expect(href).toEqual(
|
||||
`/basepath/app/kibana#/discover?_g=(refreshInterval:(pause:true,value:'0'),time:(from:now%2Fw,to:now))&_a=(index:apm-index-pattern-id,interval:auto,query:(language:lucene,query:'service.name:"service-name" AND error.grouping_key:"grouping-key"'),sort:('@timestamp':desc))`
|
||||
);
|
||||
});
|
||||
|
||||
test('DiscoverErrorLink should include optional kuery string in URL', async () => {
|
||||
const error = {
|
||||
service: {
|
||||
name: 'service-name'
|
||||
},
|
||||
error: {
|
||||
grouping_key: 'grouping-key'
|
||||
}
|
||||
} as APMError;
|
||||
|
||||
const href = await getRenderedHref(
|
||||
() => <DiscoverErrorLink error={error} kuery="some:kuery-string" />,
|
||||
{
|
||||
search: '?rangeFrom=now/w&rangeTo=now'
|
||||
} as Location
|
||||
);
|
||||
|
||||
expect(href).toEqual(
|
||||
`/basepath/app/kibana#/discover?_g=(refreshInterval:(pause:true,value:'0'),time:(from:now%2Fw,to:now))&_a=(index:apm-index-pattern-id,interval:auto,query:(language:lucene,query:'service.name:"service-name" AND error.grouping_key:"grouping-key" AND some:kuery-string'),sort:('@timestamp':desc))`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -14,8 +14,6 @@ import {
|
|||
} from '../DiscoverTransactionLink';
|
||||
import mockTransaction from './mockTransaction.json';
|
||||
|
||||
jest.mock('ui/kfetch');
|
||||
|
||||
describe('DiscoverTransactionLink component', () => {
|
||||
it('should render with data', () => {
|
||||
const transaction: Transaction = mockTransaction;
|
||||
|
|
|
@ -10,8 +10,6 @@ import { Transaction } from '../../../../../../typings/es_schemas/ui/Transaction
|
|||
import configureStore from '../../../../../store/config/configureStore';
|
||||
import { getDiscoverQuery } from '../DiscoverTransactionLink';
|
||||
|
||||
jest.mock('ui/kfetch');
|
||||
|
||||
function getMockTransaction() {
|
||||
return {
|
||||
transaction: {
|
||||
|
|
|
@ -12,8 +12,6 @@ import * as savedObjects from '../../../../services/rest/savedObjects';
|
|||
import * as kibanaCore from '../../../../../../observability/public/context/kibana_core';
|
||||
import { LegacyCoreStart } from 'src/core/public';
|
||||
|
||||
jest.mock('ui/kfetch');
|
||||
|
||||
const coreMock = ({
|
||||
http: {
|
||||
basePath: {
|
||||
|
|
|
@ -14,8 +14,7 @@ import * as apmIndexPatternHooks from '../../../../hooks/useAPMIndexPattern';
|
|||
import * as kibanaCore from '../../../../../../observability/public/context/kibana_core';
|
||||
import { ISavedObject } from '../../../../services/rest/savedObjects';
|
||||
import { LegacyCoreStart } from 'src/core/public';
|
||||
|
||||
jest.mock('ui/kfetch');
|
||||
import { FETCH_STATUS } from '../../../../hooks/useFetcher';
|
||||
|
||||
const renderTransaction = async (transaction: Record<string, any>) => {
|
||||
const rendered = render(
|
||||
|
@ -37,9 +36,10 @@ describe('TransactionActionMenu component', () => {
|
|||
}
|
||||
} as unknown) as LegacyCoreStart;
|
||||
|
||||
jest
|
||||
.spyOn(apmIndexPatternHooks, 'useAPMIndexPattern')
|
||||
.mockReturnValue({ id: 'foo' } as ISavedObject);
|
||||
jest.spyOn(apmIndexPatternHooks, 'useAPMIndexPattern').mockReturnValue({
|
||||
apmIndexPattern: { id: 'foo' } as ISavedObject,
|
||||
status: FETCH_STATUS.SUCCESS
|
||||
});
|
||||
jest.spyOn(kibanaCore, 'useKibanaCore').mockReturnValue(coreMock);
|
||||
});
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ import React from 'react';
|
|||
import { FETCH_STATUS, useFetcher } from '../../hooks/useFetcher';
|
||||
import { loadLicense, LicenseApiResponse } from '../../services/rest/xpack';
|
||||
import { InvalidLicenseNotification } from './InvalidLicenseNotification';
|
||||
import { useKibanaCore } from '../../../../observability/public';
|
||||
|
||||
const initialLicense: LicenseApiResponse = {
|
||||
features: {},
|
||||
|
@ -17,7 +18,11 @@ const initialLicense: LicenseApiResponse = {
|
|||
export const LicenseContext = React.createContext(initialLicense);
|
||||
|
||||
export const LicenseProvider: React.FC = ({ children }) => {
|
||||
const { data = initialLicense, status } = useFetcher(() => loadLicense(), []);
|
||||
const { http } = useKibanaCore();
|
||||
const { data = initialLicense, status } = useFetcher(
|
||||
() => loadLicense(http),
|
||||
[http]
|
||||
);
|
||||
const hasValidLicense = data.license.is_active;
|
||||
|
||||
// if license is invalid show an error message
|
||||
|
|
|
@ -4,25 +4,17 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import {
|
||||
getAPMIndexPattern,
|
||||
ISavedObject
|
||||
} from '../services/rest/savedObjects';
|
||||
import { useFetcher } from './useFetcher';
|
||||
|
||||
export function useAPMIndexPattern() {
|
||||
const [pattern, setPattern] = useState({} as ISavedObject);
|
||||
const { data: apmIndexPattern = {} as ISavedObject, status } = useFetcher(
|
||||
getAPMIndexPattern,
|
||||
[]
|
||||
);
|
||||
|
||||
async function fetchPattern() {
|
||||
const indexPattern = await getAPMIndexPattern();
|
||||
if (indexPattern) {
|
||||
setPattern(indexPattern);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
fetchPattern();
|
||||
}, []);
|
||||
|
||||
return pattern;
|
||||
return { apmIndexPattern, status };
|
||||
}
|
||||
|
|
|
@ -5,24 +5,26 @@
|
|||
*/
|
||||
|
||||
import { useFetcher } from './useFetcher';
|
||||
import { callApmApi } from '../services/rest/callApmApi';
|
||||
import { useUrlParams } from './useUrlParams';
|
||||
|
||||
export function useAgentName() {
|
||||
const { urlParams } = useUrlParams();
|
||||
const { start, end, serviceName } = urlParams;
|
||||
|
||||
const { data: agentName, error, status } = useFetcher(() => {
|
||||
if (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]);
|
||||
const { data: agentName, error, status } = useFetcher(
|
||||
callApmApi => {
|
||||
if (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]
|
||||
);
|
||||
|
||||
return {
|
||||
agentName,
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
|
||||
import { useFetcher } from './useFetcher';
|
||||
import { useUrlParams } from './useUrlParams';
|
||||
import { callApmApi } from '../services/rest/callApmApi';
|
||||
|
||||
export function useAvgDurationByCountry() {
|
||||
const {
|
||||
|
@ -14,22 +13,25 @@ export function useAvgDurationByCountry() {
|
|||
uiFilters
|
||||
} = useUrlParams();
|
||||
|
||||
const { data = [], error, status } = useFetcher(() => {
|
||||
if (serviceName && start && end) {
|
||||
return callApmApi({
|
||||
pathname:
|
||||
'/api/apm/services/{serviceName}/transaction_groups/avg_duration_by_country',
|
||||
params: {
|
||||
path: { serviceName },
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
uiFilters: JSON.stringify(uiFilters)
|
||||
const { data = [], error, status } = useFetcher(
|
||||
callApmApi => {
|
||||
if (serviceName && start && end) {
|
||||
return callApmApi({
|
||||
pathname:
|
||||
'/api/apm/services/{serviceName}/transaction_groups/avg_duration_by_country',
|
||||
params: {
|
||||
path: { serviceName },
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
uiFilters: JSON.stringify(uiFilters)
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [serviceName, start, end, uiFilters]);
|
||||
});
|
||||
}
|
||||
},
|
||||
[serviceName, start, end, uiFilters]
|
||||
);
|
||||
|
||||
return {
|
||||
data,
|
||||
|
|
17
x-pack/legacy/plugins/apm/public/hooks/useCallApi.ts
Normal file
17
x-pack/legacy/plugins/apm/public/hooks/useCallApi.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* 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 { useMemo } from 'react';
|
||||
import { useKibanaCore } from '../../../observability/public';
|
||||
import { callApi, FetchOptions } from '../services/rest/callApi';
|
||||
|
||||
export function useCallApi() {
|
||||
const { http } = useKibanaCore();
|
||||
|
||||
return useMemo(() => {
|
||||
return <T = void>(options: FetchOptions) => callApi<T>(http, options);
|
||||
}, [http]);
|
||||
}
|
17
x-pack/legacy/plugins/apm/public/hooks/useCallApmApi.ts
Normal file
17
x-pack/legacy/plugins/apm/public/hooks/useCallApmApi.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* 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 { useMemo } from 'react';
|
||||
import { useKibanaCore } from '../../../observability/public';
|
||||
import { createCallApmApi } from '../services/rest/createCallApmApi';
|
||||
|
||||
export function useCallApmApi() {
|
||||
const { http } = useKibanaCore();
|
||||
|
||||
return useMemo(() => {
|
||||
return createCallApmApi(http);
|
||||
}, [http]);
|
||||
}
|
|
@ -7,10 +7,12 @@
|
|||
import React, { useContext, useEffect, useState, useMemo } from 'react';
|
||||
import { idx } from '@kbn/elastic-idx';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { HttpFetchError } from 'src/core/public';
|
||||
import { LoadingIndicatorContext } from '../context/LoadingIndicatorContext';
|
||||
import { useComponentId } from './useComponentId';
|
||||
import { KFetchError } from '../../../../../../src/legacy/ui/public/kfetch/kfetch_error';
|
||||
import { useKibanaCore } from '../../../observability/public';
|
||||
import { APMClient } from '../services/rest/createCallApmApi';
|
||||
import { useCallApmApi } from './useCallApmApi';
|
||||
|
||||
export enum FETCH_STATUS {
|
||||
LOADING = 'loading',
|
||||
|
@ -20,43 +22,35 @@ export enum FETCH_STATUS {
|
|||
}
|
||||
|
||||
interface Result<Data> {
|
||||
data: Data;
|
||||
data?: Data;
|
||||
status: FETCH_STATUS;
|
||||
error?: Error;
|
||||
}
|
||||
|
||||
export function useFetcher<TState>(
|
||||
fn: () => Promise<TState> | TState | undefined,
|
||||
fnDeps: any[],
|
||||
options?: {
|
||||
preservePreviousData?: boolean;
|
||||
}
|
||||
): Result<TState> & { refetch: () => void };
|
||||
// fetcher functions can return undefined OR a promise. Previously we had a more simple type
|
||||
// but it led to issues when using object destructuring with default values
|
||||
type InferResponseType<TReturn> = Exclude<TReturn, undefined> extends Promise<
|
||||
infer TResponseType
|
||||
>
|
||||
? TResponseType
|
||||
: unknown;
|
||||
|
||||
// To avoid infinite rescursion when infering the type of `TState` `initialState` must be given if `prevResult` is consumed
|
||||
export function useFetcher<TState>(
|
||||
fn: (prevResult: Result<TState>) => Promise<TState> | TState | undefined,
|
||||
export function useFetcher<TReturn>(
|
||||
fn: (callApmApi: APMClient) => TReturn,
|
||||
fnDeps: any[],
|
||||
options: {
|
||||
preservePreviousData?: boolean;
|
||||
initialState: TState;
|
||||
}
|
||||
): Result<TState> & { refetch: () => void };
|
||||
|
||||
export function useFetcher(
|
||||
fn: Function,
|
||||
fnDeps: any[],
|
||||
options: {
|
||||
preservePreviousData?: boolean;
|
||||
initialState?: unknown;
|
||||
} = {}
|
||||
) {
|
||||
): Result<InferResponseType<TReturn>> & { refetch: () => void } {
|
||||
const { notifications } = useKibanaCore();
|
||||
const { preservePreviousData = true } = options;
|
||||
const id = useComponentId();
|
||||
|
||||
const callApmApi = useCallApmApi();
|
||||
|
||||
const { dispatchStatus } = useContext(LoadingIndicatorContext);
|
||||
const [result, setResult] = useState<Result<unknown>>({
|
||||
data: options.initialState,
|
||||
const [result, setResult] = useState<Result<InferResponseType<TReturn>>>({
|
||||
data: undefined,
|
||||
status: FETCH_STATUS.PENDING
|
||||
});
|
||||
const [counter, setCounter] = useState(0);
|
||||
|
@ -65,7 +59,7 @@ export function useFetcher(
|
|||
let didCancel = false;
|
||||
|
||||
async function doFetch() {
|
||||
const promise = fn(result);
|
||||
const promise = fn(callApmApi);
|
||||
// if `fn` doesn't return a promise it is a signal that data fetching was not initiated.
|
||||
// This can happen if the data fetching is conditional (based on certain inputs).
|
||||
// In these cases it is not desirable to invoke the global loading spinner, or change the status to success
|
||||
|
@ -89,10 +83,10 @@ export function useFetcher(
|
|||
data,
|
||||
status: FETCH_STATUS.SUCCESS,
|
||||
error: undefined
|
||||
});
|
||||
} as Result<InferResponseType<TReturn>>);
|
||||
}
|
||||
} catch (e) {
|
||||
const err = e as KFetchError;
|
||||
const err = e as HttpFetchError;
|
||||
if (!didCancel) {
|
||||
notifications.toasts.addWarning({
|
||||
title: i18n.translate('xpack.apm.fetcher.error.title', {
|
||||
|
@ -105,14 +99,14 @@ export function useFetcher(
|
|||
defaultMessage: `Error`
|
||||
})}
|
||||
</h5>
|
||||
{idx(err.res, r => r.statusText)} ({idx(err.res, r => r.status)}
|
||||
)
|
||||
{idx(err.response, r => r.statusText)} (
|
||||
{idx(err.response, r => r.status)})
|
||||
<h5>
|
||||
{i18n.translate('xpack.apm.fetcher.error.url', {
|
||||
defaultMessage: `URL`
|
||||
})}
|
||||
</h5>
|
||||
{idx(err.res, r => r.url)}
|
||||
{idx(err.response, r => r.url)}
|
||||
</div>
|
||||
)
|
||||
});
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
|
||||
import { omit } from 'lodash';
|
||||
import { useFetcher } from './useFetcher';
|
||||
import { callApi } from '../services/rest/callApi';
|
||||
import { LocalUIFiltersAPIResponse } from '../../server/lib/ui_filters/local_ui_filters';
|
||||
import { useUrlParams } from './useUrlParams';
|
||||
import {
|
||||
|
@ -18,6 +17,7 @@ import { toQuery, fromQuery } from '../components/shared/Links/url_helpers';
|
|||
import { removeUndefinedProps } from '../context/UrlParamsContext/helpers';
|
||||
import { PROJECTION } from '../../common/projections/typings';
|
||||
import { pickKeys } from '../utils/pickKeys';
|
||||
import { useCallApi } from './useCallApi';
|
||||
|
||||
const getInitialData = (
|
||||
filterNames: LocalUIFilterName[]
|
||||
|
@ -38,6 +38,7 @@ export function useLocalUIFilters({
|
|||
params?: Record<string, string | number | boolean | undefined>;
|
||||
}) {
|
||||
const { uiFilters, urlParams } = useUrlParams();
|
||||
const callApi = useCallApi();
|
||||
|
||||
const values = pickKeys(uiFilters, ...filterNames);
|
||||
|
||||
|
@ -75,7 +76,15 @@ export function useLocalUIFilters({
|
|||
...params
|
||||
}
|
||||
});
|
||||
}, [uiFilters, urlParams, params, filterNames, projection]);
|
||||
}, [
|
||||
callApi,
|
||||
projection,
|
||||
uiFilters,
|
||||
urlParams.start,
|
||||
urlParams.end,
|
||||
filterNames,
|
||||
params
|
||||
]);
|
||||
|
||||
const filters = data.map(filter => ({
|
||||
...filter,
|
||||
|
|
|
@ -8,7 +8,6 @@ import { MetricsChartsByAgentAPIResponse } from '../../server/lib/metrics/get_me
|
|||
import { IUrlParams } from '../context/UrlParamsContext/types';
|
||||
import { useUiFilters } from '../context/UrlParamsContext';
|
||||
import { useFetcher } from './useFetcher';
|
||||
import { callApmApi } from '../services/rest/callApmApi';
|
||||
|
||||
const INITIAL_DATA: MetricsChartsByAgentAPIResponse = {
|
||||
charts: []
|
||||
|
@ -20,22 +19,25 @@ export function useServiceMetricCharts(
|
|||
) {
|
||||
const { serviceName, start, end } = urlParams;
|
||||
const uiFilters = useUiFilters(urlParams);
|
||||
const { data = INITIAL_DATA, error, status } = useFetcher(() => {
|
||||
if (serviceName && start && end && agentName) {
|
||||
return callApmApi({
|
||||
pathname: '/api/apm/services/{serviceName}/metrics/charts',
|
||||
params: {
|
||||
path: { serviceName },
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
agentName,
|
||||
uiFilters: JSON.stringify(uiFilters)
|
||||
const { data = INITIAL_DATA, error, status } = useFetcher(
|
||||
callApmApi => {
|
||||
if (serviceName && start && end && agentName) {
|
||||
return callApmApi({
|
||||
pathname: '/api/apm/services/{serviceName}/metrics/charts',
|
||||
params: {
|
||||
path: { serviceName },
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
agentName,
|
||||
uiFilters: JSON.stringify(uiFilters)
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [serviceName, start, end, agentName, uiFilters]);
|
||||
});
|
||||
}
|
||||
},
|
||||
[serviceName, start, end, agentName, uiFilters]
|
||||
);
|
||||
|
||||
return {
|
||||
data,
|
||||
|
|
|
@ -6,23 +6,25 @@
|
|||
|
||||
import { IUrlParams } from '../context/UrlParamsContext/types';
|
||||
import { useFetcher } from './useFetcher';
|
||||
import { callApmApi } from '../services/rest/callApmApi';
|
||||
|
||||
const INITIAL_DATA = { transactionTypes: [] };
|
||||
|
||||
export function useServiceTransactionTypes(urlParams: IUrlParams) {
|
||||
const { serviceName, start, end } = urlParams;
|
||||
const { data = INITIAL_DATA } = useFetcher(() => {
|
||||
if (serviceName && start && end) {
|
||||
return callApmApi({
|
||||
pathname: '/api/apm/services/{serviceName}/transaction_types',
|
||||
params: {
|
||||
path: { serviceName },
|
||||
query: { start, end }
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [serviceName, start, end]);
|
||||
const { data = INITIAL_DATA } = useFetcher(
|
||||
callApmApi => {
|
||||
if (serviceName && start && end) {
|
||||
return callApmApi({
|
||||
pathname: '/api/apm/services/{serviceName}/transaction_types',
|
||||
params: {
|
||||
path: { serviceName },
|
||||
query: { start, end }
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
[serviceName, start, end]
|
||||
);
|
||||
|
||||
return data.transactionTypes;
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
|
||||
import { useFetcher } from './useFetcher';
|
||||
import { useUrlParams } from './useUrlParams';
|
||||
import { callApmApi } from '../services/rest/callApmApi';
|
||||
|
||||
export function useTransactionBreakdown() {
|
||||
const {
|
||||
|
@ -14,28 +13,27 @@ export function useTransactionBreakdown() {
|
|||
uiFilters
|
||||
} = useUrlParams();
|
||||
|
||||
const {
|
||||
data = { kpis: [], timeseries: [] },
|
||||
error,
|
||||
status
|
||||
} = useFetcher(() => {
|
||||
if (serviceName && start && end && transactionType) {
|
||||
return callApmApi({
|
||||
pathname:
|
||||
'/api/apm/services/{serviceName}/transaction_groups/breakdown',
|
||||
params: {
|
||||
path: { serviceName },
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
transactionName,
|
||||
transactionType,
|
||||
uiFilters: JSON.stringify(uiFilters)
|
||||
const { data = { kpis: [], timeseries: [] }, error, status } = useFetcher(
|
||||
callApmApi => {
|
||||
if (serviceName && start && end && transactionType) {
|
||||
return callApmApi({
|
||||
pathname:
|
||||
'/api/apm/services/{serviceName}/transaction_groups/breakdown',
|
||||
params: {
|
||||
path: { serviceName },
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
transactionName,
|
||||
transactionType,
|
||||
uiFilters: JSON.stringify(uiFilters)
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [serviceName, start, end, transactionType, transactionName, uiFilters]);
|
||||
});
|
||||
}
|
||||
},
|
||||
[serviceName, start, end, transactionType, transactionName, uiFilters]
|
||||
);
|
||||
|
||||
return {
|
||||
data,
|
||||
|
|
|
@ -8,7 +8,6 @@ import { useMemo } from 'react';
|
|||
import { getTransactionCharts } from '../selectors/chartSelectors';
|
||||
import { useFetcher } from './useFetcher';
|
||||
import { useUrlParams } from './useUrlParams';
|
||||
import { callApmApi } from '../services/rest/callApmApi';
|
||||
|
||||
export function useTransactionCharts() {
|
||||
const {
|
||||
|
@ -16,23 +15,26 @@ export function useTransactionCharts() {
|
|||
uiFilters
|
||||
} = useUrlParams();
|
||||
|
||||
const { data, error, status } = useFetcher(() => {
|
||||
if (serviceName && start && end) {
|
||||
return callApmApi({
|
||||
pathname: '/api/apm/services/{serviceName}/transaction_groups/charts',
|
||||
params: {
|
||||
path: { serviceName },
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
transactionType,
|
||||
transactionName,
|
||||
uiFilters: JSON.stringify(uiFilters)
|
||||
const { data, error, status } = useFetcher(
|
||||
callApmApi => {
|
||||
if (serviceName && start && end) {
|
||||
return callApmApi({
|
||||
pathname: '/api/apm/services/{serviceName}/transaction_groups/charts',
|
||||
params: {
|
||||
path: { serviceName },
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
transactionType,
|
||||
transactionName,
|
||||
uiFilters: JSON.stringify(uiFilters)
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [serviceName, start, end, transactionName, transactionType, uiFilters]);
|
||||
});
|
||||
}
|
||||
},
|
||||
[serviceName, start, end, transactionName, transactionType, uiFilters]
|
||||
);
|
||||
|
||||
const memoizedData = useMemo(
|
||||
() => getTransactionCharts({ transactionType }, data),
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
import { IUrlParams } from '../context/UrlParamsContext/types';
|
||||
import { useFetcher } from './useFetcher';
|
||||
import { useUiFilters } from '../context/UrlParamsContext';
|
||||
import { callApmApi } from '../services/rest/callApmApi';
|
||||
import { TransactionDistributionAPIResponse } from '../../server/lib/transactions/distribution';
|
||||
|
||||
const INITIAL_DATA = {
|
||||
|
@ -28,30 +27,42 @@ export function useTransactionDistribution(urlParams: IUrlParams) {
|
|||
} = urlParams;
|
||||
const uiFilters = useUiFilters(urlParams);
|
||||
|
||||
const { data = INITIAL_DATA, status, error } = useFetcher(() => {
|
||||
if (serviceName && start && end && transactionType && transactionName) {
|
||||
return callApmApi({
|
||||
pathname:
|
||||
'/api/apm/services/{serviceName}/transaction_groups/distribution',
|
||||
params: {
|
||||
path: {
|
||||
serviceName
|
||||
},
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
transactionType,
|
||||
transactionName,
|
||||
transactionId,
|
||||
traceId,
|
||||
uiFilters: JSON.stringify(uiFilters)
|
||||
const { data = INITIAL_DATA, status, error } = useFetcher(
|
||||
callApmApi => {
|
||||
if (serviceName && start && end && transactionType && transactionName) {
|
||||
return callApmApi({
|
||||
pathname:
|
||||
'/api/apm/services/{serviceName}/transaction_groups/distribution',
|
||||
params: {
|
||||
path: {
|
||||
serviceName
|
||||
},
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
transactionType,
|
||||
transactionName,
|
||||
transactionId,
|
||||
traceId,
|
||||
uiFilters: JSON.stringify(uiFilters)
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
// the histogram should not be refetched if the transactionId or traceId changes
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [serviceName, start, end, transactionType, transactionName, uiFilters]);
|
||||
});
|
||||
}
|
||||
// the histogram should not be refetched if the transactionId or traceId changes
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
},
|
||||
[
|
||||
serviceName,
|
||||
start,
|
||||
end,
|
||||
transactionType,
|
||||
transactionName,
|
||||
transactionId,
|
||||
traceId,
|
||||
uiFilters
|
||||
]
|
||||
);
|
||||
|
||||
return { data, status, error };
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@ import { IUrlParams } from '../context/UrlParamsContext/types';
|
|||
import { useUiFilters } from '../context/UrlParamsContext';
|
||||
import { useFetcher } from './useFetcher';
|
||||
import { TransactionGroupListAPIResponse } from '../../server/lib/transaction_groups';
|
||||
import { callApmApi } from '../services/rest/callApmApi';
|
||||
|
||||
const getRelativeImpact = (
|
||||
impact: number,
|
||||
|
@ -43,22 +42,25 @@ function getWithRelativeImpact(items: TransactionGroupListAPIResponse) {
|
|||
export function useTransactionList(urlParams: IUrlParams) {
|
||||
const { serviceName, transactionType, start, end } = urlParams;
|
||||
const uiFilters = useUiFilters(urlParams);
|
||||
const { data = [], error, status } = useFetcher(() => {
|
||||
if (serviceName && start && end && transactionType) {
|
||||
return callApmApi({
|
||||
pathname: '/api/apm/services/{serviceName}/transaction_groups',
|
||||
params: {
|
||||
path: { serviceName },
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
transactionType,
|
||||
uiFilters: JSON.stringify(uiFilters)
|
||||
const { data = [], error, status } = useFetcher(
|
||||
callApmApi => {
|
||||
if (serviceName && start && end && transactionType) {
|
||||
return callApmApi({
|
||||
pathname: '/api/apm/services/{serviceName}/transaction_groups',
|
||||
params: {
|
||||
path: { serviceName },
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
transactionType,
|
||||
uiFilters: JSON.stringify(uiFilters)
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [serviceName, start, end, transactionType, uiFilters]);
|
||||
});
|
||||
}
|
||||
},
|
||||
[serviceName, start, end, transactionType, uiFilters]
|
||||
);
|
||||
|
||||
const memoizedData = useMemo(() => getWithRelativeImpact(data), [data]);
|
||||
return {
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
import { useMemo } from 'react';
|
||||
import { IUrlParams } from '../context/UrlParamsContext/types';
|
||||
import { useFetcher } from './useFetcher';
|
||||
import { callApmApi } from '../services/rest/callApmApi';
|
||||
import { getWaterfall } from '../components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers';
|
||||
|
||||
const INITIAL_DATA = {
|
||||
|
@ -18,20 +17,23 @@ const INITIAL_DATA = {
|
|||
|
||||
export function useWaterfall(urlParams: IUrlParams) {
|
||||
const { traceId, start, end, transactionId } = urlParams;
|
||||
const { data = INITIAL_DATA, status, error } = useFetcher(() => {
|
||||
if (traceId && start && end) {
|
||||
return callApmApi({
|
||||
pathname: '/api/apm/traces/{traceId}',
|
||||
params: {
|
||||
path: { traceId },
|
||||
query: {
|
||||
start,
|
||||
end
|
||||
const { data = INITIAL_DATA, status, error } = useFetcher(
|
||||
callApmApi => {
|
||||
if (traceId && start && end) {
|
||||
return callApmApi({
|
||||
pathname: '/api/apm/traces/{traceId}',
|
||||
params: {
|
||||
path: { traceId },
|
||||
query: {
|
||||
start,
|
||||
end
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [traceId, start, end]);
|
||||
});
|
||||
}
|
||||
},
|
||||
[traceId, start, end]
|
||||
);
|
||||
|
||||
const waterfall = useMemo(() => getWaterfall(data, transactionId), [
|
||||
data,
|
||||
|
|
|
@ -4,26 +4,31 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import * as kfetchModule from 'ui/kfetch';
|
||||
import { mockNow } from '../../utils/testHelpers';
|
||||
import { clearCache, callApi } from '../rest/callApi';
|
||||
import { SessionStorageMock } from './SessionStorageMock';
|
||||
import { HttpServiceBase } from 'kibana/public';
|
||||
|
||||
jest.mock('ui/kfetch');
|
||||
type HttpMock = HttpServiceBase & {
|
||||
get: jest.SpyInstance<HttpServiceBase['get']>;
|
||||
};
|
||||
|
||||
describe('callApi', () => {
|
||||
let kfetchSpy: jest.SpyInstance;
|
||||
let http: HttpMock;
|
||||
|
||||
beforeEach(() => {
|
||||
kfetchSpy = jest.spyOn(kfetchModule, 'kfetch').mockResolvedValue({
|
||||
my_key: 'hello world'
|
||||
});
|
||||
http = ({
|
||||
get: jest.fn().mockReturnValue({
|
||||
my_key: 'hello_world'
|
||||
})
|
||||
} as unknown) as HttpMock;
|
||||
|
||||
// @ts-ignore
|
||||
global.sessionStorage = new SessionStorageMock();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
kfetchSpy.mockClear();
|
||||
http.get.mockClear();
|
||||
clearCache();
|
||||
});
|
||||
|
||||
|
@ -33,32 +38,17 @@ describe('callApi', () => {
|
|||
});
|
||||
|
||||
it('should add debug param for APM endpoints', async () => {
|
||||
await callApi({ pathname: `/api/apm/status/server` });
|
||||
await callApi(http, { pathname: `/api/apm/status/server` });
|
||||
|
||||
expect(kfetchSpy).toHaveBeenCalledWith(
|
||||
{ pathname: '/api/apm/status/server', query: { _debug: true } },
|
||||
undefined
|
||||
);
|
||||
expect(http.get).toHaveBeenCalledWith('/api/apm/status/server', {
|
||||
query: { _debug: true }
|
||||
});
|
||||
});
|
||||
|
||||
it('should not add debug param for non-APM endpoints', async () => {
|
||||
await callApi({ pathname: `/api/kibana` });
|
||||
await callApi(http, { pathname: `/api/kibana` });
|
||||
|
||||
expect(kfetchSpy).toHaveBeenCalledWith(
|
||||
{ pathname: '/api/kibana' },
|
||||
undefined
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('prependBasePath', () => {
|
||||
it('should be passed on to kFetch', async () => {
|
||||
await callApi({ pathname: `/api/kibana` }, { prependBasePath: false });
|
||||
|
||||
expect(kfetchSpy).toHaveBeenCalledWith(
|
||||
{ pathname: '/api/kibana' },
|
||||
{ prependBasePath: false }
|
||||
);
|
||||
expect(http.get).toHaveBeenCalledWith('/api/kibana', {});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -74,98 +64,98 @@ describe('callApi', () => {
|
|||
|
||||
describe('when the call does not contain start/end params', () => {
|
||||
it('should not return cached response for identical calls', async () => {
|
||||
await callApi({ pathname: `/api/kibana`, query: { foo: 'bar' } });
|
||||
await callApi({ pathname: `/api/kibana`, query: { foo: 'bar' } });
|
||||
await callApi({ pathname: `/api/kibana`, query: { foo: 'bar' } });
|
||||
await callApi(http, { pathname: `/api/kibana`, query: { foo: 'bar' } });
|
||||
await callApi(http, { pathname: `/api/kibana`, query: { foo: 'bar' } });
|
||||
await callApi(http, { pathname: `/api/kibana`, query: { foo: 'bar' } });
|
||||
|
||||
expect(kfetchSpy).toHaveBeenCalledTimes(3);
|
||||
expect(http.get).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the call contains start/end params', () => {
|
||||
it('should return cached response for identical calls', async () => {
|
||||
await callApi({
|
||||
await callApi(http, {
|
||||
pathname: `/api/kibana`,
|
||||
query: { start: '2010', end: '2011' }
|
||||
});
|
||||
await callApi({
|
||||
await callApi(http, {
|
||||
pathname: `/api/kibana`,
|
||||
query: { start: '2010', end: '2011' }
|
||||
});
|
||||
await callApi({
|
||||
await callApi(http, {
|
||||
pathname: `/api/kibana`,
|
||||
query: { start: '2010', end: '2011' }
|
||||
});
|
||||
|
||||
expect(kfetchSpy).toHaveBeenCalledTimes(1);
|
||||
expect(http.get).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should not return cached response for subsequent calls if arguments change', async () => {
|
||||
await callApi({
|
||||
await callApi(http, {
|
||||
pathname: `/api/kibana`,
|
||||
query: { start: '2010', end: '2011', foo: 'bar1' }
|
||||
});
|
||||
await callApi({
|
||||
await callApi(http, {
|
||||
pathname: `/api/kibana`,
|
||||
query: { start: '2010', end: '2011', foo: 'bar2' }
|
||||
});
|
||||
await callApi({
|
||||
await callApi(http, {
|
||||
pathname: `/api/kibana`,
|
||||
query: { start: '2010', end: '2011', foo: 'bar3' }
|
||||
});
|
||||
|
||||
expect(kfetchSpy).toHaveBeenCalledTimes(3);
|
||||
expect(http.get).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
it('should not return cached response if `end` is a future timestamp', async () => {
|
||||
await callApi({
|
||||
await callApi(http, {
|
||||
pathname: `/api/kibana`,
|
||||
query: { end: '2030' }
|
||||
});
|
||||
await callApi({
|
||||
await callApi(http, {
|
||||
pathname: `/api/kibana`,
|
||||
query: { end: '2030' }
|
||||
});
|
||||
await callApi({
|
||||
await callApi(http, {
|
||||
pathname: `/api/kibana`,
|
||||
query: { end: '2030' }
|
||||
});
|
||||
|
||||
expect(kfetchSpy).toHaveBeenCalledTimes(3);
|
||||
expect(http.get).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
it('should return cached response if calls contain `end` param in the past', async () => {
|
||||
await callApi({
|
||||
await callApi(http, {
|
||||
pathname: `/api/kibana`,
|
||||
query: { start: '2009', end: '2010' }
|
||||
});
|
||||
await callApi({
|
||||
await callApi(http, {
|
||||
pathname: `/api/kibana`,
|
||||
query: { start: '2009', end: '2010' }
|
||||
});
|
||||
await callApi({
|
||||
await callApi(http, {
|
||||
pathname: `/api/kibana`,
|
||||
query: { start: '2009', end: '2010' }
|
||||
});
|
||||
|
||||
expect(kfetchSpy).toHaveBeenCalledTimes(1);
|
||||
expect(http.get).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should return cached response even if order of properties change', async () => {
|
||||
await callApi({
|
||||
await callApi(http, {
|
||||
pathname: `/api/kibana`,
|
||||
query: { end: '2010', start: '2009' }
|
||||
});
|
||||
await callApi({
|
||||
await callApi(http, {
|
||||
pathname: `/api/kibana`,
|
||||
query: { start: '2009', end: '2010' }
|
||||
});
|
||||
await callApi({
|
||||
await callApi(http, {
|
||||
query: { start: '2009', end: '2010' },
|
||||
pathname: `/api/kibana`
|
||||
});
|
||||
|
||||
expect(kfetchSpy).toHaveBeenCalledTimes(1);
|
||||
expect(http.get).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,15 +5,19 @@
|
|||
*/
|
||||
|
||||
import * as callApiExports from '../rest/callApi';
|
||||
import { callApmApi } from '../rest/callApmApi';
|
||||
|
||||
jest.mock('ui/kfetch');
|
||||
import { createCallApmApi, APMClient } from '../rest/createCallApmApi';
|
||||
import { HttpServiceBase } from 'kibana/public';
|
||||
|
||||
const callApi = jest
|
||||
.spyOn(callApiExports, 'callApi')
|
||||
.mockImplementation(() => Promise.resolve(null));
|
||||
|
||||
describe('callApmApi', () => {
|
||||
let callApmApi: APMClient;
|
||||
beforeEach(() => {
|
||||
callApmApi = createCallApmApi({} as HttpServiceBase);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
callApi.mockClear();
|
||||
});
|
||||
|
@ -30,6 +34,7 @@ describe('callApmApi', () => {
|
|||
} as never);
|
||||
|
||||
expect(callApi).toHaveBeenCalledWith(
|
||||
{},
|
||||
expect.objectContaining({
|
||||
pathname: '/api/apm/foo/to/bar'
|
||||
})
|
||||
|
@ -48,6 +53,7 @@ describe('callApmApi', () => {
|
|||
} as never);
|
||||
|
||||
expect(callApi).toHaveBeenCalledWith(
|
||||
{},
|
||||
expect.objectContaining({
|
||||
pathname: '/api/apm',
|
||||
query: {
|
||||
|
@ -71,6 +77,7 @@ describe('callApmApi', () => {
|
|||
} as never);
|
||||
|
||||
expect(callApi).toHaveBeenCalledWith(
|
||||
{},
|
||||
expect.objectContaining({
|
||||
pathname: '/api/apm',
|
||||
method: 'POST',
|
||||
|
|
|
@ -4,14 +4,18 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { FetchOptions } from 'apollo-link-http';
|
||||
import { isString, startsWith } from 'lodash';
|
||||
import LRU from 'lru-cache';
|
||||
import hash from 'object-hash';
|
||||
import { kfetch, KFetchOptions } from 'ui/kfetch';
|
||||
import { KFetchKibanaOptions } from 'ui/kfetch/kfetch';
|
||||
import { HttpServiceBase, HttpFetchOptions } from 'kibana/public';
|
||||
|
||||
function fetchOptionsWithDebug(fetchOptions: KFetchOptions) {
|
||||
export type FetchOptions = HttpFetchOptions & {
|
||||
pathname: string;
|
||||
forceCache?: boolean;
|
||||
method?: string;
|
||||
};
|
||||
|
||||
function fetchOptionsWithDebug(fetchOptions: FetchOptions) {
|
||||
const debugEnabled =
|
||||
sessionStorage.getItem('apm_debug') === 'true' &&
|
||||
startsWith(fetchOptions.pathname, '/api/apm');
|
||||
|
@ -35,9 +39,11 @@ export function clearCache() {
|
|||
cache.reset();
|
||||
}
|
||||
|
||||
export type CallApi = typeof callApi;
|
||||
|
||||
export async function callApi<T = void>(
|
||||
fetchOptions: KFetchOptions & { forceCache?: boolean },
|
||||
options?: KFetchKibanaOptions
|
||||
http: HttpServiceBase,
|
||||
fetchOptions: FetchOptions
|
||||
): Promise<T> {
|
||||
const cacheKey = getCacheKey(fetchOptions);
|
||||
const cacheResponse = cache.get(cacheKey);
|
||||
|
@ -45,8 +51,18 @@ export async function callApi<T = void>(
|
|||
return cacheResponse;
|
||||
}
|
||||
|
||||
const combinedFetchOptions = fetchOptionsWithDebug(fetchOptions);
|
||||
const res = await kfetch(combinedFetchOptions, options);
|
||||
const { pathname, method = 'get', ...options } = fetchOptionsWithDebug(
|
||||
fetchOptions
|
||||
);
|
||||
|
||||
const lowercaseMethod = method.toLowerCase() as
|
||||
| 'get'
|
||||
| 'post'
|
||||
| 'put'
|
||||
| 'delete'
|
||||
| 'patch';
|
||||
|
||||
const res = await http[lowercaseMethod](pathname, options);
|
||||
|
||||
if (isCachable(fetchOptions)) {
|
||||
cache.set(cacheKey, res);
|
||||
|
@ -57,7 +73,7 @@ export async function callApi<T = void>(
|
|||
|
||||
// only cache items that has a time range with `start` and `end` params,
|
||||
// and where `end` is not a timestamp in the future
|
||||
function isCachable(fetchOptions: KFetchOptions & { forceCache?: boolean }) {
|
||||
function isCachable(fetchOptions: FetchOptions) {
|
||||
if (fetchOptions.forceCache) {
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -1,27 +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 { callApi } from './callApi';
|
||||
import { APMAPI } from '../../../server/routes/create_apm_api';
|
||||
import { Client } from '../../../server/routes/typings';
|
||||
|
||||
export const callApmApi: Client<APMAPI['_S']> = (options => {
|
||||
const { pathname, params = {}, ...opts } = options;
|
||||
|
||||
const path = (params.path || {}) as Record<string, any>;
|
||||
const body = params.body ? { body: JSON.stringify(params.body) } : undefined;
|
||||
const query = params.query ? { query: params.query } : undefined;
|
||||
|
||||
const formattedPathname = Object.keys(path).reduce((acc, paramName) => {
|
||||
return acc.replace(`{${paramName}}`, path[paramName]);
|
||||
}, pathname);
|
||||
|
||||
return callApi({
|
||||
...opts,
|
||||
pathname: formattedPathname,
|
||||
...body,
|
||||
...query
|
||||
}) as any;
|
||||
}) as Client<APMAPI['_S']>;
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* 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 { HttpServiceBase } from 'kibana/public';
|
||||
import { callApi, FetchOptions } from './callApi';
|
||||
import { APMAPI } from '../../../server/routes/create_apm_api';
|
||||
import { Client } from '../../../server/routes/typings';
|
||||
|
||||
export type APMClient = Client<APMAPI['_S']>;
|
||||
export type APMClientOptions = Omit<FetchOptions, 'query' | 'body'> & {
|
||||
params?: {
|
||||
body?: any;
|
||||
query?: any;
|
||||
path?: any;
|
||||
};
|
||||
};
|
||||
|
||||
export const createCallApmApi = (http: HttpServiceBase) =>
|
||||
((options: APMClientOptions) => {
|
||||
const { pathname, params = {}, ...opts } = options;
|
||||
|
||||
const path = (params.path || {}) as Record<string, any>;
|
||||
const body = params.body
|
||||
? { body: JSON.stringify(params.body) }
|
||||
: undefined;
|
||||
const query = params.query ? { query: params.query } : undefined;
|
||||
|
||||
const formattedPathname = Object.keys(path).reduce((acc, paramName) => {
|
||||
return acc.replace(`{${paramName}}`, path[paramName]);
|
||||
}, pathname);
|
||||
|
||||
return callApi(http, {
|
||||
...opts,
|
||||
pathname: formattedPathname,
|
||||
...body,
|
||||
...query
|
||||
});
|
||||
}) as APMClient;
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
import { npStart } from 'ui/new_platform';
|
||||
import { ESFilter } from 'elasticsearch';
|
||||
import { HttpServiceBase } from 'kibana/public';
|
||||
import {
|
||||
PROCESSOR_EVENT,
|
||||
SERVICE_NAME,
|
||||
|
@ -35,10 +36,12 @@ const { core } = npStart;
|
|||
|
||||
export async function startMLJob({
|
||||
serviceName,
|
||||
transactionType
|
||||
transactionType,
|
||||
http
|
||||
}: {
|
||||
serviceName: string;
|
||||
transactionType: string;
|
||||
http: HttpServiceBase;
|
||||
}) {
|
||||
const indexPatternName = core.injectedMetadata.getInjectedVar(
|
||||
'apmTransactionIndices'
|
||||
|
@ -50,7 +53,7 @@ export async function startMLJob({
|
|||
{ term: { [TRANSACTION_TYPE]: transactionType } }
|
||||
];
|
||||
groups.push(transactionType.toLowerCase());
|
||||
return callApi<StartedMLJobApiResponse>({
|
||||
return callApi<StartedMLJobApiResponse>(http, {
|
||||
method: 'POST',
|
||||
pathname: `/api/ml/modules/setup/apm_transaction`,
|
||||
body: JSON.stringify({
|
||||
|
@ -77,13 +80,15 @@ export interface MLJobApiResponse {
|
|||
|
||||
export async function getHasMLJob({
|
||||
serviceName,
|
||||
transactionType
|
||||
transactionType,
|
||||
http
|
||||
}: {
|
||||
serviceName: string;
|
||||
transactionType: string;
|
||||
http: HttpServiceBase;
|
||||
}) {
|
||||
try {
|
||||
await callApi<MLJobApiResponse>({
|
||||
await callApi<MLJobApiResponse>(http, {
|
||||
method: 'GET',
|
||||
pathname: `/api/ml/anomaly_detectors/${getMlJobId(
|
||||
serviceName,
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import { memoize } from 'lodash';
|
||||
import { callApmApi } from './callApmApi';
|
||||
import { APMClient } from './createCallApmApi';
|
||||
|
||||
export interface ISavedObject {
|
||||
attributes: {
|
||||
|
@ -16,7 +16,7 @@ export interface ISavedObject {
|
|||
type: string;
|
||||
}
|
||||
|
||||
export const getAPMIndexPattern = memoize(async () => {
|
||||
export const getAPMIndexPattern = memoize(async (callApmApi: APMClient) => {
|
||||
try {
|
||||
return await callApmApi({
|
||||
pathname: '/api/apm/index_pattern'
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { HttpServiceBase } from 'kibana/public';
|
||||
import { StringMap } from '../../../typings/common';
|
||||
import { callApi } from './callApi';
|
||||
|
||||
|
@ -38,8 +39,8 @@ export interface LicenseApiResponse {
|
|||
};
|
||||
}
|
||||
|
||||
export async function loadLicense() {
|
||||
return callApi<LicenseApiResponse>({
|
||||
export async function loadLicense(http: HttpServiceBase) {
|
||||
return callApi<LicenseApiResponse>(http, {
|
||||
pathname: `/api/xpack/v1/info`
|
||||
});
|
||||
}
|
||||
|
|
|
@ -97,7 +97,8 @@ export const agentConfigurationAgentNameRoute = createRoute(() => ({
|
|||
handler: async (req, { query }) => {
|
||||
const setup = await setupRequest(req);
|
||||
const { serviceName } = query;
|
||||
return await getAgentNameByService({ serviceName, setup });
|
||||
const agentName = await getAgentNameByService({ serviceName, setup });
|
||||
return agentName;
|
||||
}
|
||||
}));
|
||||
|
||||
|
|
|
@ -7,8 +7,8 @@
|
|||
import t from 'io-ts';
|
||||
import { Request, ResponseToolkit } from 'hapi';
|
||||
import { InternalCoreSetup } from 'src/core/server';
|
||||
import { KFetchOptions } from 'ui/kfetch';
|
||||
import { PickByValue, Optional } from 'utility-types';
|
||||
import { FetchOptions } from '../../public/services/rest/callApi';
|
||||
|
||||
export interface Params {
|
||||
query?: t.HasProps;
|
||||
|
@ -105,7 +105,7 @@ type GetParams<TParams extends Params> = Exclude<
|
|||
|
||||
export type Client<TRouteState> = <
|
||||
TPath extends keyof TRouteState & string,
|
||||
TMethod extends keyof TRouteState[TPath],
|
||||
TMethod extends keyof TRouteState[TPath] & string,
|
||||
TRouteDescription extends TRouteState[TPath][TMethod],
|
||||
TParams extends TRouteDescription extends { params: Params }
|
||||
? TRouteDescription['params']
|
||||
|
@ -114,7 +114,7 @@ export type Client<TRouteState> = <
|
|||
? TRouteDescription['ret']
|
||||
: undefined
|
||||
>(
|
||||
options: Omit<KFetchOptions, 'query' | 'body' | 'pathname' | 'method'> & {
|
||||
options: Omit<FetchOptions, 'query' | 'body' | 'pathname' | 'method'> & {
|
||||
forceCache?: boolean;
|
||||
pathname: TPath;
|
||||
} & (TMethod extends 'GET' ? { method?: TMethod } : { method: TMethod }) &
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue