[APM] Add Obs side nav and refactor APM templates (#101044)
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
be9fcad655
commit
caa4bd111d
|
@ -37,6 +37,18 @@ export function getEnvironmentLabel(environment: string) {
|
|||
return environmentLabels[environment] || environment;
|
||||
}
|
||||
|
||||
export function omitEsFieldValue({
|
||||
esFieldValue,
|
||||
value,
|
||||
text,
|
||||
}: {
|
||||
esFieldValue?: string;
|
||||
value: string;
|
||||
text: string;
|
||||
}) {
|
||||
return { value, text };
|
||||
}
|
||||
|
||||
export function parseEnvironmentUrlParam(environment: string) {
|
||||
if (environment === ENVIRONMENT_ALL_VALUE) {
|
||||
return ENVIRONMENT_ALL;
|
||||
|
|
|
@ -51,16 +51,16 @@ Then(`should display percentile for page load chart`, () => {
|
|||
cy.get(pMarkers).eq(3).should('have.text', '95th');
|
||||
});
|
||||
|
||||
Then(`should display chart legend`, () => {
|
||||
const chartLegend = 'button.echLegendItem__label';
|
||||
// Then(`should display chart legend`, () => {
|
||||
// const chartLegend = 'button.echLegendItem__label';
|
||||
|
||||
waitForLoadingToFinish();
|
||||
cy.get('.euiLoadingChart').should('not.exist');
|
||||
// waitForLoadingToFinish();
|
||||
// cy.get('.euiLoadingChart').should('not.exist');
|
||||
|
||||
cy.get('[data-cy=pageLoadDist]').within(() => {
|
||||
cy.get(chartLegend, DEFAULT_TIMEOUT).eq(0).should('have.text', 'Overall');
|
||||
});
|
||||
});
|
||||
// cy.get('[data-cy=pageLoadDist]').within(() => {
|
||||
// cy.get(chartLegend, DEFAULT_TIMEOUT).eq(0).should('have.text', 'Overall');
|
||||
// });
|
||||
// });
|
||||
|
||||
Then(`should display tooltip on hover`, () => {
|
||||
cy.get('.euiLoadingChart').should('not.exist');
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { act } from '@testing-library/react';
|
||||
import { createMemoryHistory } from 'history';
|
||||
import { Observable } from 'rxjs';
|
||||
|
@ -15,6 +16,7 @@ import { renderApp } from './';
|
|||
import { disableConsoleWarning } from '../utils/testHelpers';
|
||||
import { dataPluginMock } from 'src/plugins/data/public/mocks';
|
||||
import { embeddablePluginMock } from 'src/plugins/embeddable/public/mocks';
|
||||
import { ApmPluginStartDeps } from '../plugin';
|
||||
|
||||
jest.mock('../services/rest/index_pattern', () => ({
|
||||
createStaticIndexPattern: () => Promise.resolve(undefined),
|
||||
|
@ -44,6 +46,7 @@ describe('renderApp', () => {
|
|||
config,
|
||||
observabilityRuleTypeRegistry,
|
||||
} = mockApmPluginContextValue;
|
||||
|
||||
const plugins = {
|
||||
licensing: { license$: new Observable() },
|
||||
triggersActionsUi: { actionTypeRegistry: {}, alertTypeRegistry: {} },
|
||||
|
@ -56,7 +59,7 @@ describe('renderApp', () => {
|
|||
},
|
||||
},
|
||||
};
|
||||
const params = {
|
||||
const appMountParameters = {
|
||||
element: document.createElement('div'),
|
||||
history: createMemoryHistory(),
|
||||
setHeaderActionMenu: () => {},
|
||||
|
@ -64,7 +67,16 @@ describe('renderApp', () => {
|
|||
|
||||
const data = dataPluginMock.createStartContract();
|
||||
const embeddable = embeddablePluginMock.createStartContract();
|
||||
const startDeps = {
|
||||
|
||||
const pluginsStart = ({
|
||||
observability: {
|
||||
navigation: {
|
||||
registerSections: () => jest.fn(),
|
||||
PageTemplate: ({ children }: { children: React.ReactNode }) => (
|
||||
<div>hello worlds {children}</div>
|
||||
),
|
||||
},
|
||||
},
|
||||
triggersActionsUi: {
|
||||
actionTypeRegistry: {},
|
||||
alertTypeRegistry: {},
|
||||
|
@ -73,7 +85,8 @@ describe('renderApp', () => {
|
|||
},
|
||||
data,
|
||||
embeddable,
|
||||
};
|
||||
} as unknown) as ApmPluginStartDeps;
|
||||
|
||||
jest.spyOn(window, 'scrollTo').mockReturnValueOnce(undefined);
|
||||
createCallApmApi((core as unknown) as CoreStart);
|
||||
|
||||
|
@ -93,8 +106,8 @@ describe('renderApp', () => {
|
|||
unmount = renderApp({
|
||||
coreStart: core as any,
|
||||
pluginsSetup: plugins as any,
|
||||
appMountParameters: params as any,
|
||||
pluginsStart: startDeps as any,
|
||||
appMountParameters: appMountParameters as any,
|
||||
pluginsStart,
|
||||
config,
|
||||
observabilityRuleTypeRegistry,
|
||||
});
|
||||
|
|
|
@ -20,7 +20,6 @@ import {
|
|||
useUiSetting$,
|
||||
} from '../../../../../src/plugins/kibana_react/public';
|
||||
import { APMRouteDefinition } from '../application/routes';
|
||||
import { renderAsRedirectTo } from '../components/app/Main/route_config';
|
||||
import { ScrollToTopOnPathChange } from '../components/app/Main/ScrollToTopOnPathChange';
|
||||
import { RumHome, UX_LABEL } from '../components/app/RumDashboard/RumHome';
|
||||
import { ApmPluginContext } from '../context/apm_plugin/apm_plugin_context';
|
||||
|
@ -32,6 +31,7 @@ import { createCallApmApi } from '../services/rest/createCallApmApi';
|
|||
import { px, units } from '../style/variables';
|
||||
import { createStaticIndexPattern } from '../services/rest/index_pattern';
|
||||
import { UXActionMenu } from '../components/app/RumDashboard/ActionMenu';
|
||||
import { redirectTo } from '../components/routing/redirect_to';
|
||||
|
||||
const CsmMainContainer = euiStyled.div`
|
||||
padding: ${px(units.plus)};
|
||||
|
@ -42,7 +42,7 @@ export const rumRoutes: APMRouteDefinition[] = [
|
|||
{
|
||||
exact: true,
|
||||
path: '/',
|
||||
render: renderAsRedirectTo('/ux'),
|
||||
render: redirectTo('/ux'),
|
||||
breadcrumb: UX_LABEL,
|
||||
},
|
||||
];
|
||||
|
|
|
@ -5,99 +5,18 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { ApmRoute } from '@elastic/apm-rum-react';
|
||||
import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json';
|
||||
import euiLightVars from '@elastic/eui/dist/eui_theme_light.json';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { Route, Router, Switch } from 'react-router-dom';
|
||||
import 'react-vis/dist/style.css';
|
||||
import { DefaultTheme, ThemeProvider } from 'styled-components';
|
||||
import type { ObservabilityRuleTypeRegistry } from '../../../observability/public';
|
||||
import { euiStyled } from '../../../../../src/plugins/kibana_react/common';
|
||||
import { ConfigSchema } from '../';
|
||||
import { AppMountParameters, CoreStart } from '../../../../../src/core/public';
|
||||
import {
|
||||
KibanaContextProvider,
|
||||
RedirectAppLinks,
|
||||
useUiSetting$,
|
||||
} from '../../../../../src/plugins/kibana_react/public';
|
||||
import { routes } from '../components/app/Main/route_config';
|
||||
import { ScrollToTopOnPathChange } from '../components/app/Main/ScrollToTopOnPathChange';
|
||||
import {
|
||||
ApmPluginContext,
|
||||
ApmPluginContextValue,
|
||||
} from '../context/apm_plugin/apm_plugin_context';
|
||||
import { LicenseProvider } from '../context/license/license_context';
|
||||
import { UrlParamsProvider } from '../context/url_params_context/url_params_context';
|
||||
import { useBreadcrumbs } from '../hooks/use_breadcrumbs';
|
||||
import { ApmPluginSetupDeps, ApmPluginStartDeps } from '../plugin';
|
||||
import { createCallApmApi } from '../services/rest/createCallApmApi';
|
||||
import { createStaticIndexPattern } from '../services/rest/index_pattern';
|
||||
import { setHelpExtension } from '../setHelpExtension';
|
||||
import { setReadonlyBadge } from '../updateBadge';
|
||||
import { AnomalyDetectionJobsContextProvider } from '../context/anomaly_detection_jobs/anomaly_detection_jobs_context';
|
||||
|
||||
const MainContainer = euiStyled.div`
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
function App() {
|
||||
const [darkMode] = useUiSetting$<boolean>('theme:darkMode');
|
||||
|
||||
useBreadcrumbs(routes);
|
||||
|
||||
return (
|
||||
<ThemeProvider
|
||||
theme={(outerTheme?: DefaultTheme) => ({
|
||||
...outerTheme,
|
||||
eui: darkMode ? euiDarkVars : euiLightVars,
|
||||
darkMode,
|
||||
})}
|
||||
>
|
||||
<MainContainer data-test-subj="apmMainContainer" role="main">
|
||||
<Route component={ScrollToTopOnPathChange} />
|
||||
<Switch>
|
||||
{routes.map((route, i) => (
|
||||
<ApmRoute key={i} {...route} />
|
||||
))}
|
||||
</Switch>
|
||||
</MainContainer>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export function ApmAppRoot({
|
||||
apmPluginContextValue,
|
||||
startDeps,
|
||||
}: {
|
||||
apmPluginContextValue: ApmPluginContextValue;
|
||||
startDeps: ApmPluginStartDeps;
|
||||
}) {
|
||||
const { appMountParameters, core } = apmPluginContextValue;
|
||||
const { history } = appMountParameters;
|
||||
const i18nCore = core.i18n;
|
||||
|
||||
return (
|
||||
<RedirectAppLinks application={core.application}>
|
||||
<ApmPluginContext.Provider value={apmPluginContextValue}>
|
||||
<KibanaContextProvider services={{ ...core, ...startDeps }}>
|
||||
<i18nCore.Context>
|
||||
<Router history={history}>
|
||||
<UrlParamsProvider>
|
||||
<LicenseProvider>
|
||||
<AnomalyDetectionJobsContextProvider>
|
||||
<App />
|
||||
</AnomalyDetectionJobsContextProvider>
|
||||
</LicenseProvider>
|
||||
</UrlParamsProvider>
|
||||
</Router>
|
||||
</i18nCore.Context>
|
||||
</KibanaContextProvider>
|
||||
</ApmPluginContext.Provider>
|
||||
</RedirectAppLinks>
|
||||
);
|
||||
}
|
||||
import { ApmAppRoot } from '../components/routing/app_root';
|
||||
|
||||
/**
|
||||
* This module is rendered asynchronously in the Kibana platform.
|
||||
|
@ -141,7 +60,7 @@ export const renderApp = ({
|
|||
ReactDOM.render(
|
||||
<ApmAppRoot
|
||||
apmPluginContextValue={apmPluginContextValue}
|
||||
startDeps={pluginsStart}
|
||||
pluginsStart={pluginsStart}
|
||||
/>,
|
||||
element
|
||||
);
|
||||
|
|
|
@ -10,24 +10,17 @@ import { useParams } from 'react-router-dom';
|
|||
import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
|
||||
import { AlertType } from '../../../../common/alert_types';
|
||||
import { getInitialAlertValues } from '../get_initial_alert_values';
|
||||
import { TriggersAndActionsUIPublicPluginStart } from '../../../../../triggers_actions_ui/public';
|
||||
import { ApmPluginStartDeps } from '../../../plugin';
|
||||
interface Props {
|
||||
addFlyoutVisible: boolean;
|
||||
setAddFlyoutVisibility: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
alertType: AlertType | null;
|
||||
}
|
||||
|
||||
interface KibanaDeps {
|
||||
triggersActionsUi: TriggersAndActionsUIPublicPluginStart;
|
||||
}
|
||||
|
||||
export function AlertingFlyout(props: Props) {
|
||||
const { addFlyoutVisible, setAddFlyoutVisibility, alertType } = props;
|
||||
const { serviceName } = useParams<{ serviceName?: string }>();
|
||||
const {
|
||||
services: { triggersActionsUi },
|
||||
} = useKibana<KibanaDeps>();
|
||||
|
||||
const { services } = useKibana<ApmPluginStartDeps>();
|
||||
const initialValues = getInitialAlertValues(alertType, serviceName);
|
||||
|
||||
const onCloseAddFlyout = useCallback(() => setAddFlyoutVisibility(false), [
|
||||
|
@ -37,7 +30,7 @@ export function AlertingFlyout(props: Props) {
|
|||
const addAlertFlyout = useMemo(
|
||||
() =>
|
||||
alertType &&
|
||||
triggersActionsUi.getAddAlertFlyout({
|
||||
services.triggersActionsUi.getAddAlertFlyout({
|
||||
consumer: 'apm',
|
||||
onClose: onCloseAddFlyout,
|
||||
alertTypeId: alertType,
|
||||
|
@ -45,7 +38,7 @@ export function AlertingFlyout(props: Props) {
|
|||
initialValues,
|
||||
}),
|
||||
/* eslint-disable-next-line react-hooks/exhaustive-deps */
|
||||
[alertType, onCloseAddFlyout, triggersActionsUi]
|
||||
[alertType, onCloseAddFlyout, services.triggersActionsUi]
|
||||
);
|
||||
return <>{addFlyoutVisible && addAlertFlyout}</>;
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
*/
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
import { Location } from 'history';
|
||||
import React from 'react';
|
||||
import { mockMoment } from '../../../../utils/testHelpers';
|
||||
import { DetailView } from './index';
|
||||
|
@ -19,11 +18,7 @@ describe('DetailView', () => {
|
|||
|
||||
it('should render empty state', () => {
|
||||
const wrapper = shallow(
|
||||
<DetailView
|
||||
errorGroup={{} as any}
|
||||
urlParams={{}}
|
||||
location={{} as Location}
|
||||
/>
|
||||
<DetailView errorGroup={{} as any} urlParams={{}} />
|
||||
);
|
||||
expect(wrapper.isEmptyRender()).toBe(true);
|
||||
});
|
||||
|
@ -46,11 +41,7 @@ describe('DetailView', () => {
|
|||
};
|
||||
|
||||
const wrapper = shallow(
|
||||
<DetailView
|
||||
errorGroup={errorGroup}
|
||||
urlParams={{}}
|
||||
location={{} as Location}
|
||||
/>
|
||||
<DetailView errorGroup={errorGroup} urlParams={{}} />
|
||||
).find('DiscoverErrorLink');
|
||||
|
||||
expect(wrapper.exists()).toBe(true);
|
||||
|
@ -69,11 +60,7 @@ describe('DetailView', () => {
|
|||
transaction: undefined,
|
||||
};
|
||||
const wrapper = shallow(
|
||||
<DetailView
|
||||
errorGroup={errorGroup}
|
||||
urlParams={{}}
|
||||
location={{} as Location}
|
||||
/>
|
||||
<DetailView errorGroup={errorGroup} urlParams={{}} />
|
||||
).find('Summary');
|
||||
|
||||
expect(wrapper.exists()).toBe(true);
|
||||
|
@ -93,11 +80,7 @@ describe('DetailView', () => {
|
|||
} as any,
|
||||
};
|
||||
const wrapper = shallow(
|
||||
<DetailView
|
||||
errorGroup={errorGroup}
|
||||
urlParams={{}}
|
||||
location={{} as Location}
|
||||
/>
|
||||
<DetailView errorGroup={errorGroup} urlParams={{}} />
|
||||
).find('EuiTabs');
|
||||
|
||||
expect(wrapper.exists()).toBe(true);
|
||||
|
@ -117,11 +100,7 @@ describe('DetailView', () => {
|
|||
} as any,
|
||||
};
|
||||
const wrapper = shallow(
|
||||
<DetailView
|
||||
errorGroup={errorGroup}
|
||||
urlParams={{}}
|
||||
location={{} as Location}
|
||||
/>
|
||||
<DetailView errorGroup={errorGroup} urlParams={{}} />
|
||||
).find('TabContent');
|
||||
|
||||
expect(wrapper.exists()).toBe(true);
|
||||
|
@ -145,13 +124,7 @@ describe('DetailView', () => {
|
|||
} as any,
|
||||
};
|
||||
expect(() =>
|
||||
shallow(
|
||||
<DetailView
|
||||
errorGroup={errorGroup}
|
||||
urlParams={{}}
|
||||
location={{} as Location}
|
||||
/>
|
||||
)
|
||||
shallow(<DetailView errorGroup={errorGroup} urlParams={{}} />)
|
||||
).not.toThrowError();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -16,7 +16,6 @@ import {
|
|||
EuiToolTip,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { Location } from 'history';
|
||||
import { first } from 'lodash';
|
||||
import React from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
@ -58,7 +57,6 @@ const TransactionLinkName = euiStyled.div`
|
|||
interface Props {
|
||||
errorGroup: APIReturnType<'GET /api/apm/services/{serviceName}/errors/{groupId}'>;
|
||||
urlParams: IUrlParams;
|
||||
location: Location;
|
||||
}
|
||||
|
||||
// TODO: Move query-string-based tabs into a re-usable component?
|
||||
|
@ -70,7 +68,7 @@ function getCurrentTab(
|
|||
return selectedTab ? selectedTab : first(tabs) || {};
|
||||
}
|
||||
|
||||
export function DetailView({ errorGroup, urlParams, location }: Props) {
|
||||
export function DetailView({ errorGroup, urlParams }: Props) {
|
||||
const history = useHistory();
|
||||
const { transaction, error, occurrencesCount } = errorGroup;
|
||||
|
||||
|
|
|
@ -9,24 +9,19 @@ import {
|
|||
EuiBadge,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiPage,
|
||||
EuiPageBody,
|
||||
EuiPanel,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { Fragment } from 'react';
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
import React from 'react';
|
||||
import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common';
|
||||
import { useTrackPageview } from '../../../../../observability/public';
|
||||
import { NOT_AVAILABLE_LABEL } from '../../../../common/i18n';
|
||||
import { useFetcher } from '../../../hooks/use_fetcher';
|
||||
import { useUrlParams } from '../../../context/url_params_context/use_url_params';
|
||||
import { fontFamilyCode, fontSizes, px, units } from '../../../style/variables';
|
||||
import { ApmHeader } from '../../shared/ApmHeader';
|
||||
import { SearchBar } from '../../shared/search_bar';
|
||||
import { DetailView } from './DetailView';
|
||||
import { ErrorDistribution } from './Distribution';
|
||||
import { useErrorGroupDistributionFetcher } from '../../../hooks/use_error_group_distribution_fetcher';
|
||||
|
@ -68,44 +63,42 @@ function ErrorGroupHeader({
|
|||
isUnhandled?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<ApmHeader>
|
||||
<EuiFlexGroup alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiTitle>
|
||||
<h1>
|
||||
{i18n.translate('xpack.apm.errorGroupDetails.errorGroupTitle', {
|
||||
defaultMessage: 'Error group {errorGroupId}',
|
||||
values: {
|
||||
errorGroupId: getShortGroupId(groupId),
|
||||
},
|
||||
})}
|
||||
</h1>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
{isUnhandled && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiBadge color="warning">
|
||||
{i18n.translate('xpack.apm.errorGroupDetails.unhandledLabel', {
|
||||
defaultMessage: 'Unhandled',
|
||||
})}
|
||||
</EuiBadge>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</ApmHeader>
|
||||
<SearchBar />
|
||||
</>
|
||||
<EuiFlexGroup alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiTitle>
|
||||
<h2>
|
||||
{i18n.translate('xpack.apm.errorGroupDetails.errorGroupTitle', {
|
||||
defaultMessage: 'Error group {errorGroupId}',
|
||||
values: {
|
||||
errorGroupId: getShortGroupId(groupId),
|
||||
},
|
||||
})}
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
|
||||
{isUnhandled && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiBadge color="warning">
|
||||
{i18n.translate('xpack.apm.errorGroupDetails.unhandledLabel', {
|
||||
defaultMessage: 'Unhandled',
|
||||
})}
|
||||
</EuiBadge>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
||||
type ErrorGroupDetailsProps = RouteComponentProps<{
|
||||
interface ErrorGroupDetailsProps {
|
||||
groupId: string;
|
||||
serviceName: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export function ErrorGroupDetails({ location, match }: ErrorGroupDetailsProps) {
|
||||
const { serviceName, groupId } = match.params;
|
||||
export function ErrorGroupDetails({
|
||||
serviceName,
|
||||
groupId,
|
||||
}: ErrorGroupDetailsProps) {
|
||||
const { urlParams } = useUrlParams();
|
||||
const { environment, kuery, start, end } = urlParams;
|
||||
const { data: errorGroupData } = useFetcher(
|
||||
|
@ -154,66 +147,56 @@ export function ErrorGroupDetails({ location, match }: ErrorGroupDetailsProps) {
|
|||
return (
|
||||
<>
|
||||
<ErrorGroupHeader groupId={groupId} isUnhandled={isUnhandled} />
|
||||
<EuiPage>
|
||||
<EuiPageBody>
|
||||
<EuiPanel>
|
||||
{showDetails && (
|
||||
<Titles>
|
||||
<EuiText>
|
||||
{logMessage && (
|
||||
<Fragment>
|
||||
<Label>
|
||||
{i18n.translate(
|
||||
'xpack.apm.errorGroupDetails.logMessageLabel',
|
||||
{
|
||||
defaultMessage: 'Log message',
|
||||
}
|
||||
)}
|
||||
</Label>
|
||||
<Message>{logMessage}</Message>
|
||||
</Fragment>
|
||||
)}
|
||||
|
||||
<EuiPanel>
|
||||
{showDetails && (
|
||||
<Titles>
|
||||
<EuiText>
|
||||
{logMessage && (
|
||||
<>
|
||||
<Label>
|
||||
{i18n.translate(
|
||||
'xpack.apm.errorGroupDetails.exceptionMessageLabel',
|
||||
'xpack.apm.errorGroupDetails.logMessageLabel',
|
||||
{
|
||||
defaultMessage: 'Exception message',
|
||||
defaultMessage: 'Log message',
|
||||
}
|
||||
)}
|
||||
</Label>
|
||||
<Message>{excMessage || NOT_AVAILABLE_LABEL}</Message>
|
||||
<Label>
|
||||
{i18n.translate(
|
||||
'xpack.apm.errorGroupDetails.culpritLabel',
|
||||
{
|
||||
defaultMessage: 'Culprit',
|
||||
}
|
||||
)}
|
||||
</Label>
|
||||
<Culprit>{culprit || NOT_AVAILABLE_LABEL}</Culprit>
|
||||
</EuiText>
|
||||
</Titles>
|
||||
)}
|
||||
<ErrorDistribution
|
||||
distribution={errorDistributionData}
|
||||
title={i18n.translate(
|
||||
'xpack.apm.errorGroupDetails.occurrencesChartLabel',
|
||||
{
|
||||
defaultMessage: 'Occurrences',
|
||||
}
|
||||
<Message>{logMessage}</Message>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</EuiPanel>
|
||||
<EuiSpacer size="s" />
|
||||
{showDetails && (
|
||||
<DetailView
|
||||
errorGroup={errorGroupData}
|
||||
urlParams={urlParams}
|
||||
location={location}
|
||||
/>
|
||||
<Label>
|
||||
{i18n.translate(
|
||||
'xpack.apm.errorGroupDetails.exceptionMessageLabel',
|
||||
{
|
||||
defaultMessage: 'Exception message',
|
||||
}
|
||||
)}
|
||||
</Label>
|
||||
<Message>{excMessage || NOT_AVAILABLE_LABEL}</Message>
|
||||
<Label>
|
||||
{i18n.translate('xpack.apm.errorGroupDetails.culpritLabel', {
|
||||
defaultMessage: 'Culprit',
|
||||
})}
|
||||
</Label>
|
||||
<Culprit>{culprit || NOT_AVAILABLE_LABEL}</Culprit>
|
||||
</EuiText>
|
||||
</Titles>
|
||||
)}
|
||||
<ErrorDistribution
|
||||
distribution={errorDistributionData}
|
||||
title={i18n.translate(
|
||||
'xpack.apm.errorGroupDetails.occurrencesChartLabel',
|
||||
{
|
||||
defaultMessage: 'Occurrences',
|
||||
}
|
||||
)}
|
||||
</EuiPageBody>
|
||||
</EuiPage>
|
||||
/>
|
||||
</EuiPanel>
|
||||
<EuiSpacer size="s" />
|
||||
{showDetails && (
|
||||
<DetailView errorGroup={errorGroupData} urlParams={urlParams} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
import React from 'react';
|
||||
import { Home } from '../Home';
|
||||
import { MockApmPluginContextWrapper } from '../../../context/apm_plugin/mock_apm_plugin_context';
|
||||
|
||||
describe('Home component', () => {
|
||||
it('should render services', () => {
|
||||
expect(
|
||||
shallow(
|
||||
<MockApmPluginContextWrapper>
|
||||
<Home tab="services" />
|
||||
</MockApmPluginContextWrapper>
|
||||
)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should render traces', () => {
|
||||
expect(
|
||||
shallow(
|
||||
<MockApmPluginContextWrapper>
|
||||
<Home tab="traces" />
|
||||
</MockApmPluginContextWrapper>
|
||||
)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -1,189 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Home component should render services 1`] = `
|
||||
<ContextProvider
|
||||
value={
|
||||
Object {
|
||||
"appMountParameters": Object {
|
||||
"setHeaderActionMenu": [Function],
|
||||
},
|
||||
"config": Object {
|
||||
"profilingEnabled": false,
|
||||
"serviceMapEnabled": true,
|
||||
"ui": Object {
|
||||
"enabled": false,
|
||||
},
|
||||
},
|
||||
"core": Object {
|
||||
"application": Object {
|
||||
"capabilities": Object {
|
||||
"apm": Object {},
|
||||
"ml": Object {},
|
||||
},
|
||||
"currentAppId$": Observable {
|
||||
"_isScalar": false,
|
||||
},
|
||||
"navigateToUrl": [Function],
|
||||
},
|
||||
"chrome": Object {
|
||||
"docTitle": Object {
|
||||
"change": [Function],
|
||||
},
|
||||
"setBadge": [Function],
|
||||
"setBreadcrumbs": [Function],
|
||||
"setHelpExtension": [Function],
|
||||
},
|
||||
"docLinks": Object {
|
||||
"DOC_LINK_VERSION": "0",
|
||||
"ELASTIC_WEBSITE_URL": "https://www.elastic.co/",
|
||||
},
|
||||
"http": Object {
|
||||
"basePath": Object {
|
||||
"get": [Function],
|
||||
"prepend": [Function],
|
||||
},
|
||||
},
|
||||
"i18n": Object {
|
||||
"Context": [Function],
|
||||
},
|
||||
"notifications": Object {
|
||||
"toasts": Object {
|
||||
"addDanger": [Function],
|
||||
"addWarning": [Function],
|
||||
},
|
||||
},
|
||||
"uiSettings": Object {
|
||||
"get": [Function],
|
||||
"get$": [Function],
|
||||
},
|
||||
},
|
||||
"observabilityRuleTypeRegistry": Object {
|
||||
"registerFormatter": [Function],
|
||||
},
|
||||
"plugins": Object {
|
||||
"data": Object {
|
||||
"query": Object {
|
||||
"timefilter": Object {
|
||||
"timefilter": Object {
|
||||
"getTime": [Function],
|
||||
"setTime": [Function],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"ml": Object {
|
||||
"urlGenerator": MlUrlGenerator {
|
||||
"createUrl": [Function],
|
||||
"id": "ML_APP_URL_GENERATOR",
|
||||
"params": Object {
|
||||
"appBasePath": "/app/ml",
|
||||
"useHash": false,
|
||||
},
|
||||
},
|
||||
},
|
||||
"observability": Object {
|
||||
"isAlertingExperienceEnabled": [Function],
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
<Home
|
||||
tab="services"
|
||||
/>
|
||||
</ContextProvider>
|
||||
`;
|
||||
|
||||
exports[`Home component should render traces 1`] = `
|
||||
<ContextProvider
|
||||
value={
|
||||
Object {
|
||||
"appMountParameters": Object {
|
||||
"setHeaderActionMenu": [Function],
|
||||
},
|
||||
"config": Object {
|
||||
"profilingEnabled": false,
|
||||
"serviceMapEnabled": true,
|
||||
"ui": Object {
|
||||
"enabled": false,
|
||||
},
|
||||
},
|
||||
"core": Object {
|
||||
"application": Object {
|
||||
"capabilities": Object {
|
||||
"apm": Object {},
|
||||
"ml": Object {},
|
||||
},
|
||||
"currentAppId$": Observable {
|
||||
"_isScalar": false,
|
||||
},
|
||||
"navigateToUrl": [Function],
|
||||
},
|
||||
"chrome": Object {
|
||||
"docTitle": Object {
|
||||
"change": [Function],
|
||||
},
|
||||
"setBadge": [Function],
|
||||
"setBreadcrumbs": [Function],
|
||||
"setHelpExtension": [Function],
|
||||
},
|
||||
"docLinks": Object {
|
||||
"DOC_LINK_VERSION": "0",
|
||||
"ELASTIC_WEBSITE_URL": "https://www.elastic.co/",
|
||||
},
|
||||
"http": Object {
|
||||
"basePath": Object {
|
||||
"get": [Function],
|
||||
"prepend": [Function],
|
||||
},
|
||||
},
|
||||
"i18n": Object {
|
||||
"Context": [Function],
|
||||
},
|
||||
"notifications": Object {
|
||||
"toasts": Object {
|
||||
"addDanger": [Function],
|
||||
"addWarning": [Function],
|
||||
},
|
||||
},
|
||||
"uiSettings": Object {
|
||||
"get": [Function],
|
||||
"get$": [Function],
|
||||
},
|
||||
},
|
||||
"observabilityRuleTypeRegistry": Object {
|
||||
"registerFormatter": [Function],
|
||||
},
|
||||
"plugins": Object {
|
||||
"data": Object {
|
||||
"query": Object {
|
||||
"timefilter": Object {
|
||||
"timefilter": Object {
|
||||
"getTime": [Function],
|
||||
"setTime": [Function],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"ml": Object {
|
||||
"urlGenerator": MlUrlGenerator {
|
||||
"createUrl": [Function],
|
||||
"id": "ML_APP_URL_GENERATOR",
|
||||
"params": Object {
|
||||
"appBasePath": "/app/ml",
|
||||
"useHash": false,
|
||||
},
|
||||
},
|
||||
},
|
||||
"observability": Object {
|
||||
"isAlertingExperienceEnabled": [Function],
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
<Home
|
||||
tab="traces"
|
||||
/>
|
||||
</ContextProvider>
|
||||
`;
|
|
@ -1,80 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiTab, EuiTitle } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { ComponentType } from 'react';
|
||||
import { $ElementType } from 'utility-types';
|
||||
import { ApmHeader } from '../../shared/ApmHeader';
|
||||
import { useServiceMapHref } from '../../shared/Links/apm/ServiceMapLink';
|
||||
import { useServiceInventoryHref } from '../../shared/Links/apm/service_inventory_link';
|
||||
import { useTraceOverviewHref } from '../../shared/Links/apm/TraceOverviewLink';
|
||||
import { MainTabs } from '../../shared/main_tabs';
|
||||
import { ServiceMap } from '../ServiceMap';
|
||||
import { ServiceInventory } from '../service_inventory';
|
||||
import { TraceOverview } from '../trace_overview';
|
||||
|
||||
interface Tab {
|
||||
key: string;
|
||||
href: string;
|
||||
text: string;
|
||||
Component: ComponentType;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
tab: 'traces' | 'services' | 'service-map';
|
||||
}
|
||||
|
||||
export function Home({ tab }: Props) {
|
||||
const homeTabs: Tab[] = [
|
||||
{
|
||||
key: 'services',
|
||||
href: useServiceInventoryHref(),
|
||||
text: i18n.translate('xpack.apm.home.servicesTabLabel', {
|
||||
defaultMessage: 'Services',
|
||||
}),
|
||||
Component: ServiceInventory,
|
||||
},
|
||||
{
|
||||
key: 'traces',
|
||||
href: useTraceOverviewHref(),
|
||||
text: i18n.translate('xpack.apm.home.tracesTabLabel', {
|
||||
defaultMessage: 'Traces',
|
||||
}),
|
||||
Component: TraceOverview,
|
||||
},
|
||||
{
|
||||
key: 'service-map',
|
||||
href: useServiceMapHref(),
|
||||
text: i18n.translate('xpack.apm.home.serviceMapTabLabel', {
|
||||
defaultMessage: 'Service Map',
|
||||
}),
|
||||
Component: ServiceMap,
|
||||
},
|
||||
];
|
||||
const selectedTab = homeTabs.find(
|
||||
(homeTab) => homeTab.key === tab
|
||||
) as $ElementType<typeof homeTabs, number>;
|
||||
|
||||
return (
|
||||
<>
|
||||
<ApmHeader>
|
||||
<EuiTitle>
|
||||
<h1>APM</h1>
|
||||
</EuiTitle>
|
||||
</ApmHeader>
|
||||
<MainTabs>
|
||||
{homeTabs.map(({ href, key, text }) => (
|
||||
<EuiTab href={href} isSelected={key === selectedTab.key} key={key}>
|
||||
{text}
|
||||
</EuiTab>
|
||||
))}
|
||||
</MainTabs>
|
||||
<selectedTab.Component />
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -1,369 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
import { Redirect, RouteComponentProps } from 'react-router-dom';
|
||||
import { ApmServiceContextProvider } from '../../../../context/apm_service/apm_service_context';
|
||||
import { getServiceNodeName } from '../../../../../common/service_nodes';
|
||||
import { APMRouteDefinition } from '../../../../application/routes';
|
||||
import { toQuery } from '../../../shared/Links/url_helpers';
|
||||
import { ErrorGroupDetails } from '../../ErrorGroupDetails';
|
||||
import { Home } from '../../Home';
|
||||
import { ServiceDetails } from '../../service_details';
|
||||
import { ServiceNodeMetrics } from '../../service_node_metrics';
|
||||
import { Settings } from '../../Settings';
|
||||
import { AgentConfigurations } from '../../Settings/AgentConfigurations';
|
||||
import { AnomalyDetection } from '../../Settings/anomaly_detection';
|
||||
import { ApmIndices } from '../../Settings/ApmIndices';
|
||||
import { CustomizeUI } from '../../Settings/CustomizeUI';
|
||||
import { TraceLink } from '../../TraceLink';
|
||||
import { TransactionDetails } from '../../transaction_details';
|
||||
import {
|
||||
CreateAgentConfigurationRouteHandler,
|
||||
EditAgentConfigurationRouteHandler,
|
||||
} from './route_handlers/agent_configuration';
|
||||
import { enableServiceOverview } from '../../../../../common/ui_settings_keys';
|
||||
import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context';
|
||||
|
||||
/**
|
||||
* Given a path, redirect to that location, preserving the search and maintaining
|
||||
* backward-compatibilty with legacy (pre-7.9) hash-based URLs.
|
||||
*/
|
||||
export function renderAsRedirectTo(to: string) {
|
||||
return ({ location }: RouteComponentProps<{}>) => {
|
||||
let resolvedUrl: URL | undefined;
|
||||
|
||||
// Redirect root URLs with a hash to support backward compatibility with URLs
|
||||
// from before we switched to the non-hash platform history.
|
||||
if (location.pathname === '' && location.hash.length > 0) {
|
||||
// We just want the search and pathname so the host doesn't matter
|
||||
resolvedUrl = new URL(location.hash.slice(1), 'http://localhost');
|
||||
to = resolvedUrl.pathname;
|
||||
}
|
||||
|
||||
return (
|
||||
<Redirect
|
||||
to={{
|
||||
...location,
|
||||
hash: '',
|
||||
pathname: to,
|
||||
search: resolvedUrl ? resolvedUrl.search : location.search,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
// These component function definitions are used below with the `component`
|
||||
// property of the route definitions.
|
||||
//
|
||||
// If you provide an inline function to the component prop, you would create a
|
||||
// new component every render. This results in the existing component unmounting
|
||||
// and the new component mounting instead of just updating the existing component.
|
||||
function HomeServices() {
|
||||
return <Home tab="services" />;
|
||||
}
|
||||
|
||||
function HomeServiceMap() {
|
||||
return <Home tab="service-map" />;
|
||||
}
|
||||
|
||||
function HomeTraces() {
|
||||
return <Home tab="traces" />;
|
||||
}
|
||||
|
||||
function ServiceDetailsErrors(
|
||||
props: RouteComponentProps<{ serviceName: string }>
|
||||
) {
|
||||
return <ServiceDetails {...props} tab="errors" />;
|
||||
}
|
||||
|
||||
function ServiceDetailsMetrics(
|
||||
props: RouteComponentProps<{ serviceName: string }>
|
||||
) {
|
||||
return <ServiceDetails {...props} tab="metrics" />;
|
||||
}
|
||||
|
||||
function ServiceDetailsNodes(
|
||||
props: RouteComponentProps<{ serviceName: string }>
|
||||
) {
|
||||
return <ServiceDetails {...props} tab="nodes" />;
|
||||
}
|
||||
|
||||
function ServiceDetailsOverview(
|
||||
props: RouteComponentProps<{ serviceName: string }>
|
||||
) {
|
||||
return <ServiceDetails {...props} tab="overview" />;
|
||||
}
|
||||
|
||||
function ServiceDetailsServiceMap(
|
||||
props: RouteComponentProps<{ serviceName: string }>
|
||||
) {
|
||||
return <ServiceDetails {...props} tab="service-map" />;
|
||||
}
|
||||
|
||||
function ServiceDetailsTransactions(
|
||||
props: RouteComponentProps<{ serviceName: string }>
|
||||
) {
|
||||
return <ServiceDetails {...props} tab="transactions" />;
|
||||
}
|
||||
|
||||
function ServiceDetailsProfiling(
|
||||
props: RouteComponentProps<{ serviceName: string }>
|
||||
) {
|
||||
return <ServiceDetails {...props} tab="profiling" />;
|
||||
}
|
||||
|
||||
function SettingsAgentConfiguration(props: RouteComponentProps<{}>) {
|
||||
return (
|
||||
<Settings {...props}>
|
||||
<AgentConfigurations />
|
||||
</Settings>
|
||||
);
|
||||
}
|
||||
|
||||
function SettingsAnomalyDetection(props: RouteComponentProps<{}>) {
|
||||
return (
|
||||
<Settings {...props}>
|
||||
<AnomalyDetection />
|
||||
</Settings>
|
||||
);
|
||||
}
|
||||
|
||||
function SettingsApmIndices(props: RouteComponentProps<{}>) {
|
||||
return (
|
||||
<Settings {...props}>
|
||||
<ApmIndices />
|
||||
</Settings>
|
||||
);
|
||||
}
|
||||
|
||||
function SettingsCustomizeUI(props: RouteComponentProps<{}>) {
|
||||
return (
|
||||
<Settings {...props}>
|
||||
<CustomizeUI />
|
||||
</Settings>
|
||||
);
|
||||
}
|
||||
|
||||
function DefaultServicePageRouteHandler(
|
||||
props: RouteComponentProps<{ serviceName: string }>
|
||||
) {
|
||||
const { uiSettings } = useApmPluginContext().core;
|
||||
const { serviceName } = props.match.params;
|
||||
if (uiSettings.get(enableServiceOverview)) {
|
||||
return renderAsRedirectTo(`/services/${serviceName}/overview`)(props);
|
||||
}
|
||||
return renderAsRedirectTo(`/services/${serviceName}/transactions`)(props);
|
||||
}
|
||||
|
||||
/**
|
||||
* The array of route definitions to be used when the application
|
||||
* creates the routes.
|
||||
*/
|
||||
export const routes: APMRouteDefinition[] = [
|
||||
{
|
||||
exact: true,
|
||||
path: '/',
|
||||
render: renderAsRedirectTo('/services'),
|
||||
breadcrumb: 'APM',
|
||||
},
|
||||
// !! Need to be kept in sync with the deepLinks in x-pack/plugins/apm/public/plugin.ts
|
||||
{
|
||||
exact: true,
|
||||
path: '/services',
|
||||
component: HomeServices,
|
||||
breadcrumb: i18n.translate('xpack.apm.breadcrumb.servicesTitle', {
|
||||
defaultMessage: 'Services',
|
||||
}),
|
||||
},
|
||||
// !! Need to be kept in sync with the deepLinks in x-pack/plugins/apm/public/plugin.ts
|
||||
{
|
||||
exact: true,
|
||||
path: '/traces',
|
||||
component: HomeTraces,
|
||||
breadcrumb: i18n.translate('xpack.apm.breadcrumb.tracesTitle', {
|
||||
defaultMessage: 'Traces',
|
||||
}),
|
||||
},
|
||||
{
|
||||
exact: true,
|
||||
path: '/settings',
|
||||
render: renderAsRedirectTo('/settings/agent-configuration'),
|
||||
breadcrumb: i18n.translate('xpack.apm.breadcrumb.listSettingsTitle', {
|
||||
defaultMessage: 'Settings',
|
||||
}),
|
||||
},
|
||||
{
|
||||
exact: true,
|
||||
path: '/settings/apm-indices',
|
||||
component: SettingsApmIndices,
|
||||
breadcrumb: i18n.translate('xpack.apm.breadcrumb.settings.indicesTitle', {
|
||||
defaultMessage: 'Indices',
|
||||
}),
|
||||
},
|
||||
{
|
||||
exact: true,
|
||||
path: '/settings/agent-configuration',
|
||||
component: SettingsAgentConfiguration,
|
||||
breadcrumb: i18n.translate(
|
||||
'xpack.apm.breadcrumb.settings.agentConfigurationTitle',
|
||||
{ defaultMessage: 'Agent Configuration' }
|
||||
),
|
||||
},
|
||||
{
|
||||
exact: true,
|
||||
path: '/settings/agent-configuration/create',
|
||||
breadcrumb: i18n.translate(
|
||||
'xpack.apm.breadcrumb.settings.createAgentConfigurationTitle',
|
||||
{ defaultMessage: 'Create Agent Configuration' }
|
||||
),
|
||||
component: CreateAgentConfigurationRouteHandler,
|
||||
},
|
||||
{
|
||||
exact: true,
|
||||
path: '/settings/agent-configuration/edit',
|
||||
breadcrumb: i18n.translate(
|
||||
'xpack.apm.breadcrumb.settings.editAgentConfigurationTitle',
|
||||
{ defaultMessage: 'Edit Agent Configuration' }
|
||||
),
|
||||
component: EditAgentConfigurationRouteHandler,
|
||||
},
|
||||
{
|
||||
exact: true,
|
||||
path: '/services/:serviceName',
|
||||
breadcrumb: ({ match }) => match.params.serviceName,
|
||||
component: DefaultServicePageRouteHandler,
|
||||
} as APMRouteDefinition<{ serviceName: string }>,
|
||||
{
|
||||
exact: true,
|
||||
path: '/services/:serviceName/overview',
|
||||
breadcrumb: i18n.translate('xpack.apm.breadcrumb.overviewTitle', {
|
||||
defaultMessage: 'Overview',
|
||||
}),
|
||||
component: withApmServiceContext(ServiceDetailsOverview),
|
||||
} as APMRouteDefinition<{ serviceName: string }>,
|
||||
// errors
|
||||
{
|
||||
exact: true,
|
||||
path: '/services/:serviceName/errors/:groupId',
|
||||
component: withApmServiceContext(ErrorGroupDetails),
|
||||
breadcrumb: ({ match }) => match.params.groupId,
|
||||
} as APMRouteDefinition<{ groupId: string; serviceName: string }>,
|
||||
{
|
||||
exact: true,
|
||||
path: '/services/:serviceName/errors',
|
||||
component: withApmServiceContext(ServiceDetailsErrors),
|
||||
breadcrumb: i18n.translate('xpack.apm.breadcrumb.errorsTitle', {
|
||||
defaultMessage: 'Errors',
|
||||
}),
|
||||
},
|
||||
// transactions
|
||||
{
|
||||
exact: true,
|
||||
path: '/services/:serviceName/transactions',
|
||||
component: withApmServiceContext(ServiceDetailsTransactions),
|
||||
breadcrumb: i18n.translate('xpack.apm.breadcrumb.transactionsTitle', {
|
||||
defaultMessage: 'Transactions',
|
||||
}),
|
||||
},
|
||||
// metrics
|
||||
{
|
||||
exact: true,
|
||||
path: '/services/:serviceName/metrics',
|
||||
component: withApmServiceContext(ServiceDetailsMetrics),
|
||||
breadcrumb: i18n.translate('xpack.apm.breadcrumb.metricsTitle', {
|
||||
defaultMessage: 'Metrics',
|
||||
}),
|
||||
},
|
||||
// service nodes, only enabled for java agents for now
|
||||
{
|
||||
exact: true,
|
||||
path: '/services/:serviceName/nodes',
|
||||
component: withApmServiceContext(ServiceDetailsNodes),
|
||||
breadcrumb: i18n.translate('xpack.apm.breadcrumb.nodesTitle', {
|
||||
defaultMessage: 'JVMs',
|
||||
}),
|
||||
},
|
||||
// node metrics
|
||||
{
|
||||
exact: true,
|
||||
path: '/services/:serviceName/nodes/:serviceNodeName/metrics',
|
||||
component: withApmServiceContext(ServiceNodeMetrics),
|
||||
breadcrumb: ({ match }) => getServiceNodeName(match.params.serviceNodeName),
|
||||
},
|
||||
{
|
||||
exact: true,
|
||||
path: '/services/:serviceName/transactions/view',
|
||||
component: withApmServiceContext(TransactionDetails),
|
||||
breadcrumb: ({ location }) => {
|
||||
const query = toQuery(location.search);
|
||||
return query.transactionName as string;
|
||||
},
|
||||
},
|
||||
{
|
||||
exact: true,
|
||||
path: '/services/:serviceName/profiling',
|
||||
component: withApmServiceContext(ServiceDetailsProfiling),
|
||||
breadcrumb: i18n.translate('xpack.apm.breadcrumb.serviceProfilingTitle', {
|
||||
defaultMessage: 'Profiling',
|
||||
}),
|
||||
},
|
||||
{
|
||||
exact: true,
|
||||
path: '/services/:serviceName/service-map',
|
||||
component: withApmServiceContext(ServiceDetailsServiceMap),
|
||||
breadcrumb: i18n.translate('xpack.apm.breadcrumb.serviceMapTitle', {
|
||||
defaultMessage: 'Service Map',
|
||||
}),
|
||||
},
|
||||
{
|
||||
exact: true,
|
||||
path: '/link-to/trace/:traceId',
|
||||
component: TraceLink,
|
||||
breadcrumb: null,
|
||||
},
|
||||
// !! Need to be kept in sync with the deepLinks in x-pack/plugins/apm/public/plugin.ts
|
||||
{
|
||||
exact: true,
|
||||
path: '/service-map',
|
||||
component: HomeServiceMap,
|
||||
breadcrumb: i18n.translate('xpack.apm.breadcrumb.serviceMapTitle', {
|
||||
defaultMessage: 'Service Map',
|
||||
}),
|
||||
},
|
||||
{
|
||||
exact: true,
|
||||
path: '/settings/customize-ui',
|
||||
component: SettingsCustomizeUI,
|
||||
breadcrumb: i18n.translate('xpack.apm.breadcrumb.settings.customizeUI', {
|
||||
defaultMessage: 'Customize UI',
|
||||
}),
|
||||
},
|
||||
{
|
||||
exact: true,
|
||||
path: '/settings/anomaly-detection',
|
||||
component: SettingsAnomalyDetection,
|
||||
breadcrumb: i18n.translate(
|
||||
'xpack.apm.breadcrumb.settings.anomalyDetection',
|
||||
{
|
||||
defaultMessage: 'Anomaly detection',
|
||||
}
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
function withApmServiceContext(WrappedComponent: React.ComponentType<any>) {
|
||||
return (props: any) => {
|
||||
return (
|
||||
<ApmServiceContextProvider>
|
||||
<WrappedComponent {...props} />
|
||||
</ApmServiceContextProvider>
|
||||
);
|
||||
};
|
||||
}
|
|
@ -42,12 +42,12 @@ export function AgentConfigurations() {
|
|||
|
||||
return (
|
||||
<>
|
||||
<EuiTitle size="l">
|
||||
<h1>
|
||||
<EuiTitle>
|
||||
<h2>
|
||||
{i18n.translate('xpack.apm.agentConfig.titleText', {
|
||||
defaultMessage: 'Agent central configuration',
|
||||
})}
|
||||
</h1>
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiText color="subdued">
|
||||
|
|
|
@ -25,11 +25,11 @@ describe('ApmIndices', () => {
|
|||
);
|
||||
|
||||
expect(getByText('Indices')).toMatchInlineSnapshot(`
|
||||
<h1
|
||||
class="euiTitle euiTitle--large"
|
||||
<h2
|
||||
class="euiTitle euiTitle--medium"
|
||||
>
|
||||
Indices
|
||||
</h1>
|
||||
</h2>
|
||||
`);
|
||||
|
||||
expect(spy).toHaveBeenCalledTimes(2);
|
||||
|
|
|
@ -176,12 +176,12 @@ export function ApmIndices() {
|
|||
|
||||
return (
|
||||
<>
|
||||
<EuiTitle size="l">
|
||||
<h1>
|
||||
<EuiTitle>
|
||||
<h2>
|
||||
{i18n.translate('xpack.apm.settings.apmIndices.title', {
|
||||
defaultMessage: 'Indices',
|
||||
})}
|
||||
</h1>
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiText color="subdued">
|
||||
|
|
|
@ -13,12 +13,12 @@ import { CustomLinkOverview } from './CustomLink';
|
|||
export function CustomizeUI() {
|
||||
return (
|
||||
<>
|
||||
<EuiTitle size="l">
|
||||
<h1>
|
||||
<EuiTitle>
|
||||
<h2>
|
||||
{i18n.translate('xpack.apm.settings.customizeApp.title', {
|
||||
defaultMessage: 'Customize app',
|
||||
})}
|
||||
</h1>
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiText color="subdued">
|
||||
|
|
|
@ -66,12 +66,12 @@ export function AnomalyDetection() {
|
|||
|
||||
return (
|
||||
<>
|
||||
<EuiTitle size="l">
|
||||
<h1>
|
||||
<EuiTitle>
|
||||
<h2>
|
||||
{i18n.translate('xpack.apm.settings.anomalyDetection.titleText', {
|
||||
defaultMessage: 'Anomaly detection',
|
||||
})}
|
||||
</h1>
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiText color="subdued">
|
||||
|
|
|
@ -5,32 +5,19 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import {
|
||||
EuiButtonEmpty,
|
||||
EuiPage,
|
||||
EuiPageBody,
|
||||
EuiPageSideBar,
|
||||
EuiSideNav,
|
||||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
import { EuiPage, EuiPageBody, EuiPageSideBar, EuiSideNav } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { ReactNode, useState } from 'react';
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
import { HeaderMenuPortal } from '../../../../../observability/public';
|
||||
import { ActionMenu } from '../../../application/action_menu';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context';
|
||||
import { getAPMHref } from '../../shared/Links/apm/APMLink';
|
||||
import { HomeLink } from '../../shared/Links/apm/HomeLink';
|
||||
|
||||
interface SettingsProps extends RouteComponentProps<{}> {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export function Settings({ children, location }: SettingsProps) {
|
||||
const { appMountParameters, core } = useApmPluginContext();
|
||||
export function Settings({ children }: { children: ReactNode }) {
|
||||
const { core } = useApmPluginContext();
|
||||
const history = useHistory();
|
||||
const { basePath } = core.http;
|
||||
const canAccessML = !!core.application.capabilities.ml?.canAccessML;
|
||||
const { search, pathname } = location;
|
||||
const { search, pathname } = history.location;
|
||||
|
||||
const [isSideNavOpenOnMobile, setisSideNavOpenOnMobile] = useState(false);
|
||||
|
||||
|
@ -43,86 +30,65 @@ export function Settings({ children, location }: SettingsProps) {
|
|||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<HeaderMenuPortal
|
||||
setHeaderActionMenu={appMountParameters.setHeaderActionMenu}
|
||||
>
|
||||
<ActionMenu />
|
||||
</HeaderMenuPortal>
|
||||
<EuiPage>
|
||||
<EuiPageSideBar>
|
||||
<HomeLink>
|
||||
<EuiButtonEmpty
|
||||
flush="left"
|
||||
size="s"
|
||||
color="primary"
|
||||
iconType="arrowLeft"
|
||||
>
|
||||
{i18n.translate('xpack.apm.settings.returnLinkLabel', {
|
||||
defaultMessage: 'Return to inventory',
|
||||
})}
|
||||
</EuiButtonEmpty>
|
||||
<EuiSpacer size="s" />
|
||||
</HomeLink>
|
||||
<EuiSideNav
|
||||
toggleOpenOnMobile={() => toggleOpenOnMobile()}
|
||||
isOpenOnMobile={isSideNavOpenOnMobile}
|
||||
items={[
|
||||
{
|
||||
name: i18n.translate('xpack.apm.settings.pageTitle', {
|
||||
defaultMessage: 'Settings',
|
||||
}),
|
||||
id: 0,
|
||||
items: [
|
||||
{
|
||||
name: i18n.translate('xpack.apm.settings.agentConfig', {
|
||||
defaultMessage: 'Agent Configuration',
|
||||
}),
|
||||
id: '1',
|
||||
href: getSettingsHref('/agent-configuration'),
|
||||
isSelected: pathname.startsWith(
|
||||
'/settings/agent-configuration'
|
||||
),
|
||||
},
|
||||
...(canAccessML
|
||||
? [
|
||||
{
|
||||
name: i18n.translate(
|
||||
'xpack.apm.settings.anomalyDetection',
|
||||
{
|
||||
defaultMessage: 'Anomaly detection',
|
||||
}
|
||||
),
|
||||
id: '4',
|
||||
href: getSettingsHref('/anomaly-detection'),
|
||||
isSelected:
|
||||
pathname === '/settings/anomaly-detection',
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
name: i18n.translate('xpack.apm.settings.customizeApp', {
|
||||
defaultMessage: 'Customize app',
|
||||
}),
|
||||
id: '3',
|
||||
href: getSettingsHref('/customize-ui'),
|
||||
isSelected: pathname === '/settings/customize-ui',
|
||||
},
|
||||
{
|
||||
name: i18n.translate('xpack.apm.settings.indices', {
|
||||
defaultMessage: 'Indices',
|
||||
}),
|
||||
id: '2',
|
||||
href: getSettingsHref('/apm-indices'),
|
||||
isSelected: pathname === '/settings/apm-indices',
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</EuiPageSideBar>
|
||||
<EuiPageBody>{children}</EuiPageBody>
|
||||
</EuiPage>
|
||||
</>
|
||||
<EuiPage>
|
||||
<EuiPageSideBar>
|
||||
<EuiSideNav
|
||||
toggleOpenOnMobile={() => toggleOpenOnMobile()}
|
||||
isOpenOnMobile={isSideNavOpenOnMobile}
|
||||
items={[
|
||||
{
|
||||
name: i18n.translate('xpack.apm.settings.pageTitle', {
|
||||
defaultMessage: 'Settings',
|
||||
}),
|
||||
id: 0,
|
||||
items: [
|
||||
{
|
||||
name: i18n.translate('xpack.apm.settings.agentConfig', {
|
||||
defaultMessage: 'Agent Configuration',
|
||||
}),
|
||||
id: '1',
|
||||
href: getSettingsHref('/agent-configuration'),
|
||||
isSelected: pathname.startsWith(
|
||||
'/settings/agent-configuration'
|
||||
),
|
||||
},
|
||||
...(canAccessML
|
||||
? [
|
||||
{
|
||||
name: i18n.translate(
|
||||
'xpack.apm.settings.anomalyDetection',
|
||||
{
|
||||
defaultMessage: 'Anomaly detection',
|
||||
}
|
||||
),
|
||||
id: '4',
|
||||
href: getSettingsHref('/anomaly-detection'),
|
||||
isSelected: pathname === '/settings/anomaly-detection',
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
name: i18n.translate('xpack.apm.settings.customizeApp', {
|
||||
defaultMessage: 'Customize app',
|
||||
}),
|
||||
id: '3',
|
||||
href: getSettingsHref('/customize-ui'),
|
||||
isSelected: pathname === '/settings/customize-ui',
|
||||
},
|
||||
{
|
||||
name: i18n.translate('xpack.apm.settings.indices', {
|
||||
defaultMessage: 'Indices',
|
||||
}),
|
||||
id: '2',
|
||||
href: getSettingsHref('/apm-indices'),
|
||||
isSelected: pathname === '/settings/apm-indices',
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</EuiPageSideBar>
|
||||
<EuiPageBody>{children}</EuiPageBody>
|
||||
</EuiPage>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiPage,
|
||||
EuiFlexItem,
|
||||
EuiPanel,
|
||||
EuiSpacer,
|
||||
EuiTitle,
|
||||
|
@ -18,7 +18,6 @@ import { useTrackPageview } from '../../../../../observability/public';
|
|||
import { useUrlParams } from '../../../context/url_params_context/use_url_params';
|
||||
import { useErrorGroupDistributionFetcher } from '../../../hooks/use_error_group_distribution_fetcher';
|
||||
import { useFetcher } from '../../../hooks/use_fetcher';
|
||||
import { SearchBar } from '../../shared/search_bar';
|
||||
import { ErrorDistribution } from '../ErrorGroupDetails/Distribution';
|
||||
import { ErrorGroupList } from './List';
|
||||
|
||||
|
@ -68,41 +67,41 @@ export function ErrorGroupOverview({ serviceName }: ErrorGroupOverviewProps) {
|
|||
useTrackPageview({ app: 'apm', path: 'error_group_overview', delay: 15000 });
|
||||
|
||||
if (!errorDistributionData || !errorGroupListData) {
|
||||
return <SearchBar />;
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<SearchBar />
|
||||
<EuiPage>
|
||||
<EuiFlexGroup direction="column" gutterSize="s">
|
||||
<EuiPanel>
|
||||
<ErrorDistribution
|
||||
distribution={errorDistributionData}
|
||||
title={i18n.translate(
|
||||
'xpack.apm.serviceDetails.metrics.errorOccurrencesChartTitle',
|
||||
{
|
||||
defaultMessage: 'Error occurrences',
|
||||
}
|
||||
)}
|
||||
/>
|
||||
</EuiPanel>
|
||||
<EuiFlexGroup direction="column" gutterSize="s">
|
||||
<EuiFlexItem>
|
||||
<EuiPanel>
|
||||
<ErrorDistribution
|
||||
distribution={errorDistributionData}
|
||||
title={i18n.translate(
|
||||
'xpack.apm.serviceDetails.metrics.errorOccurrencesChart.title',
|
||||
{ defaultMessage: 'Error occurrences' }
|
||||
)}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem>
|
||||
<EuiPanel>
|
||||
<EuiTitle size="xs">
|
||||
<h3>
|
||||
{i18n.translate(
|
||||
'xpack.apm.serviceDetails.metrics.errorsList.title',
|
||||
{ defaultMessage: 'Errors' }
|
||||
)}
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
<EuiPanel>
|
||||
<EuiTitle size="xs">
|
||||
<h3>Errors</h3>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
<ErrorGroupList
|
||||
items={errorGroupListData.errorGroups}
|
||||
serviceName={serviceName}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</EuiFlexGroup>
|
||||
</EuiPage>
|
||||
</>
|
||||
<ErrorGroupList
|
||||
items={errorGroupListData.errorGroups}
|
||||
serviceName={serviceName}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,39 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
import { ApmHeader } from '../../shared/ApmHeader';
|
||||
import { ServiceIcons } from './service_icons';
|
||||
import { ServiceDetailTabs } from './service_detail_tabs';
|
||||
|
||||
interface Props extends RouteComponentProps<{ serviceName: string }> {
|
||||
tab: React.ComponentProps<typeof ServiceDetailTabs>['tab'];
|
||||
}
|
||||
|
||||
export function ServiceDetails({ match, tab }: Props) {
|
||||
const { serviceName } = match.params;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ApmHeader>
|
||||
<EuiFlexGroup alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiTitle>
|
||||
<h1>{serviceName}</h1>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<ServiceIcons serviceName={serviceName} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</ApmHeader>
|
||||
<ServiceDetailTabs serviceName={serviceName} tab={tab} />
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -1,202 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiTab } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { ReactNode } from 'react';
|
||||
import { EuiBetaBadge } from '@elastic/eui';
|
||||
import { EuiFlexItem } from '@elastic/eui';
|
||||
import { EuiFlexGroup } from '@elastic/eui';
|
||||
import { isJavaAgentName, isRumAgentName } from '../../../../common/agent_name';
|
||||
import { enableServiceOverview } from '../../../../common/ui_settings_keys';
|
||||
import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context';
|
||||
import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context';
|
||||
import { useUrlParams } from '../../../context/url_params_context/use_url_params';
|
||||
import { useErrorOverviewHref } from '../../shared/Links/apm/ErrorOverviewLink';
|
||||
import { useMetricOverviewHref } from '../../shared/Links/apm/MetricOverviewLink';
|
||||
import { useServiceMapHref } from '../../shared/Links/apm/ServiceMapLink';
|
||||
import { useServiceNodeOverviewHref } from '../../shared/Links/apm/ServiceNodeOverviewLink';
|
||||
import { useServiceOverviewHref } from '../../shared/Links/apm/service_overview_link';
|
||||
import { useTransactionsOverviewHref } from '../../shared/Links/apm/transaction_overview_link';
|
||||
import { useServiceProfilingHref } from '../../shared/Links/apm/service_profiling_link';
|
||||
import { MainTabs } from '../../shared/main_tabs';
|
||||
import { ErrorGroupOverview } from '../error_group_overview';
|
||||
import { ServiceMap } from '../ServiceMap';
|
||||
import { ServiceNodeOverview } from '../service_node_overview';
|
||||
import { ServiceMetrics } from '../service_metrics';
|
||||
import { ServiceOverview } from '../service_overview';
|
||||
import { TransactionOverview } from '../transaction_overview';
|
||||
import { ServiceProfiling } from '../service_profiling';
|
||||
import { Correlations } from '../correlations';
|
||||
|
||||
interface Tab {
|
||||
key: string;
|
||||
href: string;
|
||||
text: ReactNode;
|
||||
hidden?: boolean;
|
||||
render: () => ReactNode;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
serviceName: string;
|
||||
tab:
|
||||
| 'errors'
|
||||
| 'metrics'
|
||||
| 'nodes'
|
||||
| 'overview'
|
||||
| 'service-map'
|
||||
| 'profiling'
|
||||
| 'transactions';
|
||||
}
|
||||
|
||||
export function ServiceDetailTabs({ serviceName, tab }: Props) {
|
||||
const { agentName, transactionType } = useApmServiceContext();
|
||||
const {
|
||||
core: { uiSettings },
|
||||
config,
|
||||
} = useApmPluginContext();
|
||||
const {
|
||||
urlParams: { latencyAggregationType },
|
||||
} = useUrlParams();
|
||||
|
||||
const overviewTab = {
|
||||
key: 'overview',
|
||||
href: useServiceOverviewHref({ serviceName, transactionType }),
|
||||
text: i18n.translate('xpack.apm.serviceDetails.overviewTabLabel', {
|
||||
defaultMessage: 'Overview',
|
||||
}),
|
||||
render: () => (
|
||||
<ServiceOverview agentName={agentName} serviceName={serviceName} />
|
||||
),
|
||||
};
|
||||
|
||||
const transactionsTab = {
|
||||
key: 'transactions',
|
||||
href: useTransactionsOverviewHref({
|
||||
serviceName,
|
||||
latencyAggregationType,
|
||||
transactionType,
|
||||
}),
|
||||
text: i18n.translate('xpack.apm.serviceDetails.transactionsTabLabel', {
|
||||
defaultMessage: 'Transactions',
|
||||
}),
|
||||
render: () => <TransactionOverview serviceName={serviceName} />,
|
||||
};
|
||||
|
||||
const errorsTab = {
|
||||
key: 'errors',
|
||||
href: useErrorOverviewHref(serviceName),
|
||||
text: i18n.translate('xpack.apm.serviceDetails.errorsTabLabel', {
|
||||
defaultMessage: 'Errors',
|
||||
}),
|
||||
render: () => {
|
||||
return <ErrorGroupOverview serviceName={serviceName} />;
|
||||
},
|
||||
};
|
||||
|
||||
const serviceMapTab = {
|
||||
key: 'service-map',
|
||||
href: useServiceMapHref(serviceName),
|
||||
text: i18n.translate('xpack.apm.home.serviceMapTabLabel', {
|
||||
defaultMessage: 'Service Map',
|
||||
}),
|
||||
render: () => <ServiceMap serviceName={serviceName} />,
|
||||
};
|
||||
|
||||
const nodesListTab = {
|
||||
key: 'nodes',
|
||||
href: useServiceNodeOverviewHref(serviceName),
|
||||
text: i18n.translate('xpack.apm.serviceDetails.nodesTabLabel', {
|
||||
defaultMessage: 'JVMs',
|
||||
}),
|
||||
render: () => <ServiceNodeOverview serviceName={serviceName} />,
|
||||
};
|
||||
|
||||
const metricsTab = {
|
||||
key: 'metrics',
|
||||
href: useMetricOverviewHref(serviceName),
|
||||
text: i18n.translate('xpack.apm.serviceDetails.metricsTabLabel', {
|
||||
defaultMessage: 'Metrics',
|
||||
}),
|
||||
render: () =>
|
||||
agentName ? (
|
||||
<ServiceMetrics agentName={agentName} serviceName={serviceName} />
|
||||
) : null,
|
||||
};
|
||||
|
||||
const profilingTab = {
|
||||
key: 'profiling',
|
||||
href: useServiceProfilingHref({ serviceName }),
|
||||
hidden: !config.profilingEnabled,
|
||||
text: (
|
||||
<EuiFlexGroup direction="row" gutterSize="s">
|
||||
<EuiFlexItem>
|
||||
{i18n.translate('xpack.apm.serviceDetails.profilingTabLabel', {
|
||||
defaultMessage: 'Profiling',
|
||||
})}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiBetaBadge
|
||||
label={i18n.translate(
|
||||
'xpack.apm.serviceDetails.profilingTabExperimentalLabel',
|
||||
{
|
||||
defaultMessage: 'Experimental',
|
||||
}
|
||||
)}
|
||||
tooltipContent={i18n.translate(
|
||||
'xpack.apm.serviceDetails.profilingTabExperimentalDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
'Profiling is highly experimental and for internal use only.',
|
||||
}
|
||||
)}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
),
|
||||
render: () => <ServiceProfiling serviceName={serviceName} />,
|
||||
};
|
||||
|
||||
const tabs: Tab[] = [transactionsTab, errorsTab];
|
||||
|
||||
if (uiSettings.get(enableServiceOverview)) {
|
||||
tabs.unshift(overviewTab);
|
||||
}
|
||||
|
||||
if (isJavaAgentName(agentName)) {
|
||||
tabs.push(nodesListTab);
|
||||
} else if (agentName && !isRumAgentName(agentName)) {
|
||||
tabs.push(metricsTab);
|
||||
}
|
||||
|
||||
tabs.push(serviceMapTab, profilingTab);
|
||||
|
||||
const selectedTab = tabs.find((serviceTab) => serviceTab.key === tab);
|
||||
|
||||
return (
|
||||
<>
|
||||
<MainTabs>
|
||||
{tabs
|
||||
.filter((t) => !t.hidden)
|
||||
.map(({ href, key, text }) => (
|
||||
<EuiTab
|
||||
data-test-subj={`tab_${key}`}
|
||||
href={href}
|
||||
isSelected={key === tab}
|
||||
key={key}
|
||||
>
|
||||
{text}
|
||||
</EuiTab>
|
||||
))}
|
||||
<div style={{ marginLeft: 'auto' }}>
|
||||
<Correlations />
|
||||
</div>
|
||||
</MainTabs>
|
||||
{selectedTab ? selectedTab.render() : null}
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -5,13 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiLink,
|
||||
EuiPage,
|
||||
EuiPanel,
|
||||
} from '@elastic/eui';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiLink, EuiPanel } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { useEffect } from 'react';
|
||||
import { toMountPoint } from '../../../../../../../src/plugins/kibana_react/public';
|
||||
|
@ -126,28 +120,26 @@ export function ServiceInventory() {
|
|||
return (
|
||||
<>
|
||||
<SearchBar />
|
||||
<EuiPage>
|
||||
<EuiFlexGroup direction="column" gutterSize="s">
|
||||
{displayMlCallout ? (
|
||||
<EuiFlexItem>
|
||||
<MLCallout onDismiss={() => setUserHasDismissedCallout(true)} />
|
||||
</EuiFlexItem>
|
||||
) : null}
|
||||
<EuiFlexGroup direction="column" gutterSize="s">
|
||||
{displayMlCallout && (
|
||||
<EuiFlexItem>
|
||||
<EuiPanel>
|
||||
<ServiceList
|
||||
items={servicesData.items}
|
||||
noItemsMessage={
|
||||
<NoServicesMessage
|
||||
historicalDataFound={servicesData.hasHistoricalData}
|
||||
status={servicesStatus}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</EuiPanel>
|
||||
<MLCallout onDismiss={() => setUserHasDismissedCallout(true)} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPage>
|
||||
)}
|
||||
<EuiFlexItem>
|
||||
<EuiPanel hasShadow={false}>
|
||||
<ServiceList
|
||||
items={servicesData.items}
|
||||
noItemsMessage={
|
||||
<NoServicesMessage
|
||||
historicalDataFound={servicesData.hasHistoricalData}
|
||||
status={servicesStatus}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -13,11 +13,11 @@ import { MockApmPluginContextWrapper } from '../../../../context/apm_plugin/mock
|
|||
import { MockUrlParamsContextProvider } from '../../../../context/url_params_context/mock_url_params_context_provider';
|
||||
import { createCallApmApi } from '../../../../services/rest/createCallApmApi';
|
||||
import { CytoscapeContext } from '../Cytoscape';
|
||||
import { Popover } from './';
|
||||
import { Popover } from '.';
|
||||
import exampleGroupedConnectionsData from '../__stories__/example_grouped_connections.json';
|
||||
|
||||
export default {
|
||||
title: 'app/ServiceMap/Popover',
|
||||
title: 'app/service_map/Popover',
|
||||
component: Popover,
|
||||
decorators: [
|
||||
(Story: ComponentType) => {
|
|
@ -10,7 +10,7 @@ import { EuiThemeProvider } from '../../../../../../../../src/plugins/kibana_rea
|
|||
import { ServiceStatsList } from './ServiceStatsList';
|
||||
|
||||
export default {
|
||||
title: 'app/ServiceMap/Popover/ServiceStatsList',
|
||||
title: 'app/service_map/Popover/ServiceStatsList',
|
||||
component: ServiceStatsList,
|
||||
decorators: [
|
||||
(Story: ComponentType) => (
|
|
@ -14,7 +14,7 @@ import { iconForNode } from '../icons';
|
|||
import { Centerer } from './centerer';
|
||||
|
||||
export default {
|
||||
title: 'app/ServiceMap/Cytoscape',
|
||||
title: 'app/service_map/Cytoscape',
|
||||
component: Cytoscape,
|
||||
decorators: [
|
||||
(Story: ComponentType) => (
|
|
@ -25,7 +25,7 @@ import exampleResponseOpbeansBeats from './example_response_opbeans_beats.json';
|
|||
import exampleResponseTodo from './example_response_todo.json';
|
||||
import { generateServiceMapElements } from './generate_service_map_elements';
|
||||
|
||||
const STORYBOOK_PATH = 'app/ServiceMap/Cytoscape/Example data';
|
||||
const STORYBOOK_PATH = 'app/service_map/Cytoscape/Example data';
|
||||
|
||||
const SESSION_STORAGE_KEY = `${STORYBOOK_PATH}/pre-loaded map`;
|
||||
function getSessionJson() {
|
||||
|
@ -40,7 +40,7 @@ function getHeight() {
|
|||
}
|
||||
|
||||
export default {
|
||||
title: 'app/ServiceMap/Cytoscape/Example data',
|
||||
title: 'app/service_map/Cytoscape/Example data',
|
||||
component: Cytoscape,
|
||||
decorators: [
|
||||
(Story: ComponentType) => (
|
|
@ -15,7 +15,7 @@ import { EuiThemeProvider } from '../../../../../../../src/plugins/kibana_react/
|
|||
import { MockApmPluginContextWrapper } from '../../../context/apm_plugin/mock_apm_plugin_context';
|
||||
import { LicenseContext } from '../../../context/license/license_context';
|
||||
import * as useFetcherModule from '../../../hooks/use_fetcher';
|
||||
import { ServiceMap } from './';
|
||||
import { ServiceMap } from '.';
|
||||
import { UrlParamsProvider } from '../../../context/url_params_context/url_params_context';
|
||||
import { Router } from 'react-router-dom';
|
||||
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui';
|
||||
import React, { PropsWithChildren, ReactNode } from 'react';
|
||||
import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common';
|
||||
import { isActivePlatinumLicense } from '../../../../common/license_check';
|
||||
import { useTrackPageview } from '../../../../../observability/public';
|
||||
import {
|
||||
|
@ -18,7 +17,6 @@ import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher';
|
|||
import { useLicenseContext } from '../../../context/license/use_license_context';
|
||||
import { useTheme } from '../../../hooks/use_theme';
|
||||
import { useUrlParams } from '../../../context/url_params_context/use_url_params';
|
||||
import { DatePicker } from '../../shared/DatePicker';
|
||||
import { LicensePrompt } from '../../shared/license_prompt';
|
||||
import { Controls } from './Controls';
|
||||
import { Cytoscape } from './Cytoscape';
|
||||
|
@ -28,31 +26,16 @@ import { EmptyPrompt } from './empty_prompt';
|
|||
import { Popover } from './Popover';
|
||||
import { TimeoutPrompt } from './timeout_prompt';
|
||||
import { useRefDimensions } from './useRefDimensions';
|
||||
import { SearchBar } from '../../shared/search_bar';
|
||||
|
||||
interface ServiceMapProps {
|
||||
serviceName?: string;
|
||||
}
|
||||
|
||||
const ServiceMapDatePickerFlexGroup = euiStyled(EuiFlexGroup)`
|
||||
padding: ${({ theme }) => theme.eui.euiSizeM};
|
||||
border-bottom: ${({ theme }) => theme.eui.euiBorderThin};
|
||||
margin: 0;
|
||||
`;
|
||||
|
||||
function DatePickerSection() {
|
||||
return (
|
||||
<ServiceMapDatePickerFlexGroup justifyContent="flexEnd" gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
<DatePicker />
|
||||
</EuiFlexItem>
|
||||
</ServiceMapDatePickerFlexGroup>
|
||||
);
|
||||
}
|
||||
|
||||
function PromptContainer({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<>
|
||||
<DatePickerSection />
|
||||
<SearchBar showKueryBar={false} />
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
justifyContent="spaceAround"
|
||||
|
@ -153,7 +136,8 @@ export function ServiceMap({
|
|||
|
||||
return (
|
||||
<>
|
||||
<DatePickerSection />
|
||||
<SearchBar showKueryBar={false} />
|
||||
|
||||
<div data-test-subj="ServiceMap" style={{ height }} ref={ref}>
|
||||
<Cytoscape
|
||||
elements={data.elements}
|
|
@ -5,30 +5,14 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import {
|
||||
EuiFlexGrid,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiPage,
|
||||
EuiPanel,
|
||||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
import { EuiFlexGrid, EuiFlexItem, EuiPanel, EuiSpacer } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { ChartPointerEventContextProvider } from '../../../context/chart_pointer_event/chart_pointer_event_context';
|
||||
import { useUrlParams } from '../../../context/url_params_context/use_url_params';
|
||||
import { useServiceMetricChartsFetcher } from '../../../hooks/use_service_metric_charts_fetcher';
|
||||
import { MetricsChart } from '../../shared/charts/metrics_chart';
|
||||
import { SearchBar } from '../../shared/search_bar';
|
||||
|
||||
interface ServiceMetricsProps {
|
||||
agentName: string;
|
||||
serviceName: string;
|
||||
}
|
||||
|
||||
export function ServiceMetrics({
|
||||
agentName,
|
||||
serviceName,
|
||||
}: ServiceMetricsProps) {
|
||||
export function ServiceMetrics() {
|
||||
const { urlParams } = useUrlParams();
|
||||
const { data, status } = useServiceMetricChartsFetcher({
|
||||
serviceNodeName: undefined,
|
||||
|
@ -36,29 +20,22 @@ export function ServiceMetrics({
|
|||
const { start, end } = urlParams;
|
||||
|
||||
return (
|
||||
<>
|
||||
<SearchBar />
|
||||
<EuiPage>
|
||||
<EuiFlexGroup direction="column" gutterSize="s">
|
||||
<ChartPointerEventContextProvider>
|
||||
<EuiFlexGrid columns={2} gutterSize="s">
|
||||
{data.charts.map((chart) => (
|
||||
<EuiFlexItem key={chart.key}>
|
||||
<EuiPanel>
|
||||
<MetricsChart
|
||||
start={start}
|
||||
end={end}
|
||||
chart={chart}
|
||||
fetchStatus={status}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
))}
|
||||
</EuiFlexGrid>
|
||||
<EuiSpacer size="xxl" />
|
||||
</ChartPointerEventContextProvider>
|
||||
</EuiFlexGroup>
|
||||
</EuiPage>
|
||||
</>
|
||||
<ChartPointerEventContextProvider>
|
||||
<EuiFlexGrid columns={2} gutterSize="s">
|
||||
{data.charts.map((chart) => (
|
||||
<EuiFlexItem key={chart.key}>
|
||||
<EuiPanel>
|
||||
<MetricsChart
|
||||
start={start}
|
||||
end={end}
|
||||
chart={chart}
|
||||
fetchStatus={status}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
))}
|
||||
</EuiFlexGrid>
|
||||
<EuiSpacer size="xxl" />
|
||||
</ChartPointerEventContextProvider>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -9,20 +9,17 @@ import React from 'react';
|
|||
import { shallow } from 'enzyme';
|
||||
import { ServiceNodeMetrics } from '.';
|
||||
import { MockApmPluginContextWrapper } from '../../../context/apm_plugin/mock_apm_plugin_context';
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
|
||||
describe('ServiceNodeMetrics', () => {
|
||||
describe('render', () => {
|
||||
it('renders', () => {
|
||||
const props = ({} as unknown) as RouteComponentProps<{
|
||||
serviceName: string;
|
||||
serviceNodeName: string;
|
||||
}>;
|
||||
|
||||
expect(() =>
|
||||
shallow(
|
||||
<MockApmPluginContextWrapper>
|
||||
<ServiceNodeMetrics {...props} />
|
||||
<ServiceNodeMetrics
|
||||
serviceName="my-service"
|
||||
serviceNodeName="node-name"
|
||||
/>
|
||||
</MockApmPluginContextWrapper>
|
||||
)
|
||||
).not.toThrowError();
|
||||
|
|
|
@ -10,17 +10,14 @@ import {
|
|||
EuiFlexGrid,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiPage,
|
||||
EuiPanel,
|
||||
EuiSpacer,
|
||||
EuiStat,
|
||||
EuiTitle,
|
||||
EuiToolTip,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import React from 'react';
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common';
|
||||
import { SERVICE_NODE_NAME_MISSING } from '../../../../common/service_nodes';
|
||||
import { ChartPointerEventContextProvider } from '../../../context/chart_pointer_event/chart_pointer_event_context';
|
||||
|
@ -29,10 +26,8 @@ import { useServiceMetricChartsFetcher } from '../../../hooks/use_service_metric
|
|||
import { useUrlParams } from '../../../context/url_params_context/use_url_params';
|
||||
import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context';
|
||||
import { px, truncate, unit } from '../../../style/variables';
|
||||
import { ApmHeader } from '../../shared/ApmHeader';
|
||||
import { MetricsChart } from '../../shared/charts/metrics_chart';
|
||||
import { ElasticDocsLink } from '../../shared/Links/ElasticDocsLink';
|
||||
import { SearchBar } from '../../shared/search_bar';
|
||||
|
||||
const INITIAL_DATA = {
|
||||
host: '',
|
||||
|
@ -51,16 +46,18 @@ const MetadataFlexGroup = euiStyled(EuiFlexGroup)`
|
|||
`${theme.eui.paddingSizes.m} 0 0 ${theme.eui.paddingSizes.m}`};
|
||||
`;
|
||||
|
||||
type ServiceNodeMetricsProps = RouteComponentProps<{
|
||||
interface ServiceNodeMetricsProps {
|
||||
serviceName: string;
|
||||
serviceNodeName: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export function ServiceNodeMetrics({ match }: ServiceNodeMetricsProps) {
|
||||
export function ServiceNodeMetrics({
|
||||
serviceName,
|
||||
serviceNodeName,
|
||||
}: ServiceNodeMetricsProps) {
|
||||
const {
|
||||
urlParams: { kuery, start, end },
|
||||
} = useUrlParams();
|
||||
const { serviceName, serviceNodeName } = match.params;
|
||||
const { agentName } = useApmServiceContext();
|
||||
const { data } = useServiceMetricChartsFetcher({ serviceNodeName });
|
||||
|
||||
|
@ -89,15 +86,6 @@ export function ServiceNodeMetrics({ match }: ServiceNodeMetricsProps) {
|
|||
|
||||
return (
|
||||
<>
|
||||
<ApmHeader>
|
||||
<EuiFlexGroup alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiTitle>
|
||||
<h1>{serviceName}</h1>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</ApmHeader>
|
||||
{isAggregatedData ? (
|
||||
<EuiCallOut
|
||||
title={i18n.translate(
|
||||
|
@ -179,28 +167,26 @@ export function ServiceNodeMetrics({ match }: ServiceNodeMetricsProps) {
|
|||
</EuiFlexItem>
|
||||
</MetadataFlexGroup>
|
||||
)}
|
||||
<SearchBar />
|
||||
<EuiPage>
|
||||
{agentName && (
|
||||
<ChartPointerEventContextProvider>
|
||||
<EuiFlexGrid columns={2} gutterSize="s">
|
||||
{data.charts.map((chart) => (
|
||||
<EuiFlexItem key={chart.key}>
|
||||
<EuiPanel>
|
||||
<MetricsChart
|
||||
start={start}
|
||||
end={end}
|
||||
chart={chart}
|
||||
fetchStatus={status}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
))}
|
||||
</EuiFlexGrid>
|
||||
<EuiSpacer size="xxl" />
|
||||
</ChartPointerEventContextProvider>
|
||||
)}
|
||||
</EuiPage>
|
||||
|
||||
{agentName && (
|
||||
<ChartPointerEventContextProvider>
|
||||
<EuiFlexGrid columns={2} gutterSize="s">
|
||||
{data.charts.map((chart) => (
|
||||
<EuiFlexItem key={chart.key}>
|
||||
<EuiPanel>
|
||||
<MetricsChart
|
||||
start={start}
|
||||
end={end}
|
||||
chart={chart}
|
||||
fetchStatus={status}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
))}
|
||||
</EuiFlexGrid>
|
||||
<EuiSpacer size="xxl" />
|
||||
</ChartPointerEventContextProvider>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { EuiFlexGroup, EuiPage, EuiPanel, EuiToolTip } from '@elastic/eui';
|
||||
import { EuiPanel, EuiToolTip } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common';
|
||||
|
@ -22,7 +22,6 @@ import { useFetcher } from '../../../hooks/use_fetcher';
|
|||
import { px, truncate, unit } from '../../../style/variables';
|
||||
import { ServiceNodeMetricOverviewLink } from '../../shared/Links/apm/ServiceNodeMetricOverviewLink';
|
||||
import { ITableColumn, ManagedTable } from '../../shared/ManagedTable';
|
||||
import { SearchBar } from '../../shared/search_bar';
|
||||
|
||||
const INITIAL_PAGE_SIZE = 25;
|
||||
const INITIAL_SORT_FIELD = 'cpu';
|
||||
|
@ -143,28 +142,18 @@ function ServiceNodeOverview({ serviceName }: ServiceNodeOverviewProps) {
|
|||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<SearchBar />
|
||||
<EuiPage>
|
||||
<EuiFlexGroup direction="column" gutterSize="s">
|
||||
<EuiPanel>
|
||||
<ManagedTable
|
||||
noItemsMessage={i18n.translate(
|
||||
'xpack.apm.jvmsTable.noJvmsLabel',
|
||||
{
|
||||
defaultMessage: 'No JVMs were found',
|
||||
}
|
||||
)}
|
||||
items={items}
|
||||
columns={columns}
|
||||
initialPageSize={INITIAL_PAGE_SIZE}
|
||||
initialSortField={INITIAL_SORT_FIELD}
|
||||
initialSortDirection={INITIAL_SORT_DIRECTION}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</EuiFlexGroup>
|
||||
</EuiPage>
|
||||
</>
|
||||
<EuiPanel hasShadow={false}>
|
||||
<ManagedTable
|
||||
noItemsMessage={i18n.translate('xpack.apm.jvmsTable.noJvmsLabel', {
|
||||
defaultMessage: 'No JVMs were found',
|
||||
})}
|
||||
items={items}
|
||||
columns={columns}
|
||||
initialPageSize={INITIAL_PAGE_SIZE}
|
||||
initialSortField={INITIAL_SORT_FIELD}
|
||||
initialSortDirection={INITIAL_SORT_DIRECTION}
|
||||
/>
|
||||
</EuiPanel>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -5,17 +5,17 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiPage, EuiPanel } from '@elastic/eui';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { useTrackPageview } from '../../../../../observability/public';
|
||||
import { isRumAgentName } from '../../../../common/agent_name';
|
||||
import { AnnotationsContextProvider } from '../../../context/annotations/annotations_context';
|
||||
import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context';
|
||||
import { ChartPointerEventContextProvider } from '../../../context/chart_pointer_event/chart_pointer_event_context';
|
||||
import { useBreakPoints } from '../../../hooks/use_break_points';
|
||||
import { LatencyChart } from '../../shared/charts/latency_chart';
|
||||
import { TransactionBreakdownChart } from '../../shared/charts/transaction_breakdown_chart';
|
||||
import { TransactionErrorRateChart } from '../../shared/charts/transaction_error_rate_chart';
|
||||
import { SearchBar } from '../../shared/search_bar';
|
||||
import { ServiceOverviewDependenciesTable } from './service_overview_dependencies_table';
|
||||
import { ServiceOverviewErrorsTable } from './service_overview_errors_table';
|
||||
import { ServiceOverviewInstancesChartAndTable } from './service_overview_instances_chart_and_table';
|
||||
|
@ -29,14 +29,12 @@ import { ServiceOverviewTransactionsTable } from './service_overview_transaction
|
|||
export const chartHeight = 288;
|
||||
|
||||
interface ServiceOverviewProps {
|
||||
agentName?: string;
|
||||
serviceName: string;
|
||||
}
|
||||
|
||||
export function ServiceOverview({
|
||||
agentName,
|
||||
serviceName,
|
||||
}: ServiceOverviewProps) {
|
||||
export function ServiceOverview({ serviceName }: ServiceOverviewProps) {
|
||||
const { agentName } = useApmServiceContext();
|
||||
|
||||
useTrackPageview({ app: 'apm', path: 'service_overview' });
|
||||
useTrackPageview({ app: 'apm', path: 'service_overview', delay: 15000 });
|
||||
|
||||
|
@ -49,89 +47,84 @@ export function ServiceOverview({
|
|||
return (
|
||||
<AnnotationsContextProvider>
|
||||
<ChartPointerEventContextProvider>
|
||||
<SearchBar showTransactionTypeSelector showTimeComparison />
|
||||
<EuiPage>
|
||||
<EuiFlexGroup direction="column" gutterSize="s">
|
||||
<EuiFlexItem>
|
||||
<EuiPanel>
|
||||
<LatencyChart height={200} />
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup
|
||||
direction={rowDirection}
|
||||
gutterSize="s"
|
||||
responsive={false}
|
||||
>
|
||||
<EuiFlexGroup direction="column" gutterSize="s">
|
||||
<EuiFlexItem>
|
||||
<EuiPanel>
|
||||
<LatencyChart height={200} />
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup
|
||||
direction={rowDirection}
|
||||
gutterSize="s"
|
||||
responsive={false}
|
||||
>
|
||||
<EuiFlexItem grow={3}>
|
||||
<ServiceOverviewThroughputChart height={chartHeight} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={7}>
|
||||
<EuiPanel>
|
||||
<ServiceOverviewTransactionsTable serviceName={serviceName} />
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup
|
||||
direction={rowDirection}
|
||||
gutterSize="s"
|
||||
responsive={false}
|
||||
>
|
||||
{!isRumAgent && (
|
||||
<EuiFlexItem grow={3}>
|
||||
<ServiceOverviewThroughputChart height={chartHeight} />
|
||||
<TransactionErrorRateChart
|
||||
height={chartHeight}
|
||||
showAnnotations={false}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
<EuiFlexItem grow={7}>
|
||||
<EuiPanel>
|
||||
<ServiceOverviewErrorsTable serviceName={serviceName} />
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup
|
||||
direction={rowDirection}
|
||||
gutterSize="s"
|
||||
responsive={false}
|
||||
>
|
||||
<EuiFlexItem grow={3}>
|
||||
<TransactionBreakdownChart showAnnotations={false} />
|
||||
</EuiFlexItem>
|
||||
{!isRumAgent && (
|
||||
<EuiFlexItem grow={7}>
|
||||
<EuiPanel>
|
||||
<ServiceOverviewTransactionsTable
|
||||
<ServiceOverviewDependenciesTable
|
||||
serviceName={serviceName}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
{!isRumAgent && (
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup
|
||||
direction={rowDirection}
|
||||
direction="column"
|
||||
gutterSize="s"
|
||||
responsive={false}
|
||||
>
|
||||
{!isRumAgent && (
|
||||
<EuiFlexItem grow={3}>
|
||||
<TransactionErrorRateChart
|
||||
height={chartHeight}
|
||||
showAnnotations={false}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
<EuiFlexItem grow={7}>
|
||||
<EuiPanel>
|
||||
<ServiceOverviewErrorsTable serviceName={serviceName} />
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
<ServiceOverviewInstancesChartAndTable
|
||||
chartHeight={chartHeight}
|
||||
serviceName={serviceName}
|
||||
/>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup
|
||||
direction={rowDirection}
|
||||
gutterSize="s"
|
||||
responsive={false}
|
||||
>
|
||||
<EuiFlexItem grow={3}>
|
||||
<TransactionBreakdownChart showAnnotations={false} />
|
||||
</EuiFlexItem>
|
||||
{!isRumAgent && (
|
||||
<EuiFlexItem grow={7}>
|
||||
<EuiPanel>
|
||||
<ServiceOverviewDependenciesTable
|
||||
serviceName={serviceName}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
{!isRumAgent && (
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup
|
||||
direction="column"
|
||||
gutterSize="s"
|
||||
responsive={false}
|
||||
>
|
||||
<ServiceOverviewInstancesChartAndTable
|
||||
chartHeight={chartHeight}
|
||||
serviceName={serviceName}
|
||||
/>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiPage>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</ChartPointerEventContextProvider>
|
||||
</AnnotationsContextProvider>
|
||||
);
|
||||
|
|
|
@ -234,6 +234,7 @@ export function getColumns({
|
|||
anchorPosition="leftCenter"
|
||||
button={
|
||||
<EuiButtonIcon
|
||||
aria-label="Edit"
|
||||
data-test-subj={`instanceActionsButton_${instanceItem.serviceNodeName}`}
|
||||
iconType="boxesHorizontal"
|
||||
onClick={() =>
|
||||
|
|
|
@ -32,10 +32,7 @@ import { pct } from '../../../../style/variables';
|
|||
import { getAgentIcon } from '../../../shared/AgentIcon/get_agent_icon';
|
||||
import { KeyValueFilterList } from '../../../shared/key_value_filter_list';
|
||||
import { pushNewItemToKueryBar } from '../../../shared/KueryBar/utils';
|
||||
import {
|
||||
getCloudIcon,
|
||||
getContainerIcon,
|
||||
} from '../../service_details/service_icons';
|
||||
import { getCloudIcon, getContainerIcon } from '../../../shared/service_icons';
|
||||
import { useInstanceDetailsFetcher } from './use_instance_details_fetcher';
|
||||
|
||||
type ServiceInstanceDetails = APIReturnType<'GET /api/apm/services/{serviceName}/service_overview_instances/details/{serviceNodeName}'>;
|
||||
|
|
|
@ -4,14 +4,7 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiPage,
|
||||
EuiPanel,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiTitle } from '@elastic/eui';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import {
|
||||
getValueTypeConfig,
|
||||
|
@ -20,7 +13,6 @@ import {
|
|||
import { useUrlParams } from '../../../context/url_params_context/use_url_params';
|
||||
import { useFetcher } from '../../../hooks/use_fetcher';
|
||||
import { APIReturnType } from '../../../services/rest/createCallApmApi';
|
||||
import { SearchBar } from '../../shared/search_bar';
|
||||
import { ServiceProfilingFlamegraph } from './service_profiling_flamegraph';
|
||||
import { ServiceProfilingTimeline } from './service_profiling_timeline';
|
||||
|
||||
|
@ -90,54 +82,38 @@ export function ServiceProfiling({
|
|||
|
||||
return (
|
||||
<>
|
||||
<SearchBar />
|
||||
<EuiPage>
|
||||
<EuiFlexGroup direction="column" gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiTitle size="s">
|
||||
<h2>
|
||||
{i18n.translate('xpack.apm.profilingOverviewTitle', {
|
||||
defaultMessage: 'Profiling',
|
||||
})}
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiPanel>
|
||||
<EuiFlexGroup direction="column">
|
||||
<EuiFlexItem>
|
||||
<EuiPanel>
|
||||
<EuiFlexGroup direction="column">
|
||||
<EuiFlexItem>
|
||||
<ServiceProfilingTimeline
|
||||
start={start!}
|
||||
end={end!}
|
||||
series={profilingTimeline}
|
||||
onValueTypeSelect={(type) => {
|
||||
setValueType(type);
|
||||
}}
|
||||
selectedValueType={valueType}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
{valueType ? (
|
||||
<EuiFlexItem>
|
||||
<EuiTitle size="s">
|
||||
<h3>{getValueTypeConfig(valueType).label}</h3>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
) : null}
|
||||
<EuiFlexItem>
|
||||
<ServiceProfilingFlamegraph
|
||||
serviceName={serviceName}
|
||||
environment={environment}
|
||||
valueType={valueType}
|
||||
start={start}
|
||||
end={end}
|
||||
kuery={kuery}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
<ServiceProfilingTimeline
|
||||
start={start!}
|
||||
end={end!}
|
||||
series={profilingTimeline}
|
||||
onValueTypeSelect={(type) => {
|
||||
setValueType(type);
|
||||
}}
|
||||
selectedValueType={valueType}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
{valueType ? (
|
||||
<EuiFlexItem>
|
||||
<EuiTitle size="s">
|
||||
<h3>{getValueTypeConfig(valueType).label}</h3>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
) : null}
|
||||
<EuiFlexItem>
|
||||
<ServiceProfilingFlamegraph
|
||||
serviceName={serviceName}
|
||||
environment={environment}
|
||||
valueType={valueType}
|
||||
start={start}
|
||||
end={end}
|
||||
kuery={kuery}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPage>
|
||||
</EuiPanel>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiFlexGroup, EuiPage, EuiPanel } from '@elastic/eui';
|
||||
import { EuiPanel } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { useTrackPageview } from '../../../../../observability/public';
|
||||
import { useUrlParams } from '../../../context/url_params_context/use_url_params';
|
||||
|
@ -50,16 +50,13 @@ export function TraceOverview() {
|
|||
return (
|
||||
<>
|
||||
<SearchBar />
|
||||
<EuiPage>
|
||||
<EuiFlexGroup direction="column" gutterSize="s">
|
||||
<EuiPanel>
|
||||
<TraceList
|
||||
items={data.items}
|
||||
isLoading={status === FETCH_STATUS.LOADING}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</EuiFlexGroup>
|
||||
</EuiPage>
|
||||
|
||||
<EuiPanel hasShadow={false}>
|
||||
<TraceList
|
||||
items={data.items}
|
||||
isLoading={status === FETCH_STATUS.LOADING}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
import { EuiSpacer, EuiTab, EuiTabs } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { Location } from 'history';
|
||||
import React from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { LogStream } from '../../../../../../infra/public';
|
||||
|
@ -19,7 +18,6 @@ import { WaterfallContainer } from './WaterfallContainer';
|
|||
import { IWaterfall } from './WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers';
|
||||
|
||||
interface Props {
|
||||
location: Location;
|
||||
transaction: Transaction;
|
||||
urlParams: IUrlParams;
|
||||
waterfall: IWaterfall;
|
||||
|
@ -27,7 +25,6 @@ interface Props {
|
|||
}
|
||||
|
||||
export function TransactionTabs({
|
||||
location,
|
||||
transaction,
|
||||
urlParams,
|
||||
waterfall,
|
||||
|
@ -47,9 +44,9 @@ export function TransactionTabs({
|
|||
<EuiTab
|
||||
onClick={() => {
|
||||
history.replace({
|
||||
...location,
|
||||
...history.location,
|
||||
search: fromQuery({
|
||||
...toQuery(location.search),
|
||||
...toQuery(history.location.search),
|
||||
detailTab: key,
|
||||
}),
|
||||
});
|
||||
|
@ -66,7 +63,6 @@ export function TransactionTabs({
|
|||
<EuiSpacer />
|
||||
|
||||
<TabContent
|
||||
location={location}
|
||||
urlParams={urlParams}
|
||||
waterfall={waterfall}
|
||||
exceedsMax={exceedsMax}
|
||||
|
@ -101,19 +97,16 @@ const logsTab = {
|
|||
};
|
||||
|
||||
function TimelineTabContent({
|
||||
location,
|
||||
urlParams,
|
||||
waterfall,
|
||||
exceedsMax,
|
||||
}: {
|
||||
location: Location<any>;
|
||||
urlParams: IUrlParams;
|
||||
waterfall: IWaterfall;
|
||||
exceedsMax: boolean;
|
||||
}) {
|
||||
return (
|
||||
<WaterfallContainer
|
||||
location={location}
|
||||
urlParams={urlParams}
|
||||
waterfall={waterfall}
|
||||
exceedsMax={exceedsMax}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { History, Location } from 'history';
|
||||
import { History } from 'history';
|
||||
import React from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { SpanFlyout } from './SpanFlyout';
|
||||
|
@ -15,20 +15,12 @@ import { IWaterfall } from './waterfall_helpers/waterfall_helpers';
|
|||
interface Props {
|
||||
waterfallItemId?: string;
|
||||
waterfall: IWaterfall;
|
||||
location: Location;
|
||||
toggleFlyout: ({
|
||||
history,
|
||||
location,
|
||||
}: {
|
||||
history: History;
|
||||
location: Location;
|
||||
}) => void;
|
||||
toggleFlyout: ({ history }: { history: History }) => void;
|
||||
}
|
||||
|
||||
export function WaterfallFlyout({
|
||||
waterfallItemId,
|
||||
waterfall,
|
||||
location,
|
||||
toggleFlyout,
|
||||
}: Props) {
|
||||
const history = useHistory();
|
||||
|
@ -52,14 +44,14 @@ export function WaterfallFlyout({
|
|||
totalDuration={waterfall.duration}
|
||||
span={currentItem.doc}
|
||||
parentTransaction={parentTransaction}
|
||||
onClose={() => toggleFlyout({ history, location })}
|
||||
onClose={() => toggleFlyout({ history })}
|
||||
/>
|
||||
);
|
||||
case 'transaction':
|
||||
return (
|
||||
<TransactionFlyout
|
||||
transaction={currentItem.doc}
|
||||
onClose={() => toggleFlyout({ history, location })}
|
||||
onClose={() => toggleFlyout({ history })}
|
||||
rootTransactionDuration={
|
||||
waterfall.rootTransaction?.transaction.duration.us
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
*/
|
||||
|
||||
import { EuiAccordion, EuiAccordionProps } from '@elastic/eui';
|
||||
import { Location } from 'history';
|
||||
import { isEmpty } from 'lodash';
|
||||
import React, { useState } from 'react';
|
||||
import { euiStyled } from '../../../../../../../../../../src/plugins/kibana_react/common';
|
||||
|
@ -23,7 +22,6 @@ interface AccordionWaterfallProps {
|
|||
level: number;
|
||||
duration: IWaterfall['duration'];
|
||||
waterfallItemId?: string;
|
||||
location: Location;
|
||||
errorsPerTransaction: IWaterfall['errorsPerTransaction'];
|
||||
childrenByParentId: Record<string, IWaterfallSpanOrTransaction[]>;
|
||||
onToggleEntryTransaction?: () => void;
|
||||
|
@ -100,7 +98,6 @@ export function AccordionWaterfall(props: AccordionWaterfallProps) {
|
|||
duration,
|
||||
childrenByParentId,
|
||||
waterfallItemId,
|
||||
location,
|
||||
errorsPerTransaction,
|
||||
timelineMargins,
|
||||
onClickWaterfallItem,
|
||||
|
@ -160,7 +157,6 @@ export function AccordionWaterfall(props: AccordionWaterfallProps) {
|
|||
item={child}
|
||||
level={nextLevel}
|
||||
waterfallItemId={waterfallItemId}
|
||||
location={location}
|
||||
errorsPerTransaction={errorsPerTransaction}
|
||||
duration={duration}
|
||||
childrenByParentId={childrenByParentId}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import { EuiButtonEmpty, EuiCallOut } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { History, Location } from 'history';
|
||||
import { History } from 'history';
|
||||
import React, { useState } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { euiStyled } from '../../../../../../../../../../src/plugins/kibana_react/common';
|
||||
|
@ -40,14 +40,12 @@ const TIMELINE_MARGINS = {
|
|||
const toggleFlyout = ({
|
||||
history,
|
||||
item,
|
||||
location,
|
||||
}: {
|
||||
history: History;
|
||||
item?: IWaterfallItem;
|
||||
location: Location;
|
||||
}) => {
|
||||
history.replace({
|
||||
...location,
|
||||
...history.location,
|
||||
search: fromQuery({
|
||||
...toQuery(location.search),
|
||||
flyoutDetailTab: undefined,
|
||||
|
@ -63,15 +61,9 @@ const WaterfallItemsContainer = euiStyled.div`
|
|||
interface Props {
|
||||
waterfallItemId?: string;
|
||||
waterfall: IWaterfall;
|
||||
location: Location;
|
||||
exceedsMax: boolean;
|
||||
}
|
||||
export function Waterfall({
|
||||
waterfall,
|
||||
exceedsMax,
|
||||
waterfallItemId,
|
||||
location,
|
||||
}: Props) {
|
||||
export function Waterfall({ waterfall, exceedsMax, waterfallItemId }: Props) {
|
||||
const history = useHistory();
|
||||
const [isAccordionOpen, setIsAccordionOpen] = useState(true);
|
||||
const itemContainerHeight = 58; // TODO: This is a nasty way to calculate the height of the svg element. A better approach should be found
|
||||
|
@ -97,13 +89,12 @@ export function Waterfall({
|
|||
item={entryWaterfallTransaction}
|
||||
level={0}
|
||||
waterfallItemId={waterfallItemId}
|
||||
location={location}
|
||||
errorsPerTransaction={waterfall.errorsPerTransaction}
|
||||
duration={duration}
|
||||
childrenByParentId={childrenByParentId}
|
||||
timelineMargins={TIMELINE_MARGINS}
|
||||
onClickWaterfallItem={(item: IWaterfallItem) =>
|
||||
toggleFlyout({ history, item, location })
|
||||
toggleFlyout({ history, item })
|
||||
}
|
||||
onToggleEntryTransaction={() => setIsAccordionOpen((isOpen) => !isOpen)}
|
||||
/>
|
||||
|
@ -148,7 +139,6 @@ export function Waterfall({
|
|||
<WaterfallFlyout
|
||||
waterfallItemId={waterfallItemId}
|
||||
waterfall={waterfall}
|
||||
location={location}
|
||||
toggleFlyout={toggleFlyout}
|
||||
/>
|
||||
</Container>
|
||||
|
|
|
@ -15,7 +15,6 @@ import { WaterfallContainer } from './index';
|
|||
import { getWaterfall } from './Waterfall/waterfall_helpers/waterfall_helpers';
|
||||
import {
|
||||
inferredSpans,
|
||||
location,
|
||||
simpleTrace,
|
||||
traceChildStartBeforeParent,
|
||||
traceWithErrors,
|
||||
|
@ -45,7 +44,6 @@ export function Example() {
|
|||
);
|
||||
return (
|
||||
<WaterfallContainer
|
||||
location={location}
|
||||
urlParams={urlParams}
|
||||
waterfall={waterfall}
|
||||
exceedsMax={false}
|
||||
|
@ -60,7 +58,6 @@ export function WithErrors() {
|
|||
);
|
||||
return (
|
||||
<WaterfallContainer
|
||||
location={location}
|
||||
urlParams={urlParams}
|
||||
waterfall={waterfall}
|
||||
exceedsMax={false}
|
||||
|
@ -75,7 +72,6 @@ export function ChildStartsBeforeParent() {
|
|||
);
|
||||
return (
|
||||
<WaterfallContainer
|
||||
location={location}
|
||||
urlParams={urlParams}
|
||||
waterfall={waterfall}
|
||||
exceedsMax={false}
|
||||
|
@ -90,7 +86,6 @@ export function InferredSpans() {
|
|||
);
|
||||
return (
|
||||
<WaterfallContainer
|
||||
location={location}
|
||||
urlParams={urlParams}
|
||||
waterfall={waterfall}
|
||||
exceedsMax={false}
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { Location } from 'history';
|
||||
import React from 'react';
|
||||
import { keyBy } from 'lodash';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
@ -19,13 +18,11 @@ import { WaterfallLegends } from './WaterfallLegends';
|
|||
|
||||
interface Props {
|
||||
urlParams: IUrlParams;
|
||||
location: Location;
|
||||
waterfall: IWaterfall;
|
||||
exceedsMax: boolean;
|
||||
}
|
||||
|
||||
export function WaterfallContainer({
|
||||
location,
|
||||
urlParams,
|
||||
waterfall,
|
||||
exceedsMax,
|
||||
|
@ -84,7 +81,6 @@ export function WaterfallContainer({
|
|||
<div>
|
||||
<WaterfallLegends legends={legendsWithFallbackLabel} type={colorBy} />
|
||||
<Waterfall
|
||||
location={location}
|
||||
waterfallItemId={urlParams.waterfallItemId}
|
||||
waterfall={waterfall}
|
||||
exceedsMax={exceedsMax}
|
||||
|
|
|
@ -15,7 +15,6 @@ import {
|
|||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { Location } from 'history';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { APIReturnType } from '../../../../services/rest/createCallApmApi';
|
||||
|
@ -34,7 +33,6 @@ type DistributionBucket = DistributionApiResponse['buckets'][0];
|
|||
|
||||
interface Props {
|
||||
urlParams: IUrlParams;
|
||||
location: Location;
|
||||
waterfall: IWaterfall;
|
||||
exceedsMax: boolean;
|
||||
isLoading: boolean;
|
||||
|
@ -43,7 +41,6 @@ interface Props {
|
|||
|
||||
export function WaterfallWithSummmary({
|
||||
urlParams,
|
||||
location,
|
||||
waterfall,
|
||||
exceedsMax,
|
||||
isLoading,
|
||||
|
@ -135,7 +132,6 @@ export function WaterfallWithSummmary({
|
|||
|
||||
<TransactionTabs
|
||||
transaction={entryTransaction}
|
||||
location={location}
|
||||
urlParams={urlParams}
|
||||
waterfall={waterfall}
|
||||
exceedsMax={exceedsMax}
|
||||
|
|
|
@ -5,27 +5,18 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiHorizontalRule,
|
||||
EuiPage,
|
||||
EuiPanel,
|
||||
EuiSpacer,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
import { EuiHorizontalRule, EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui';
|
||||
import { flatten, isEmpty } from 'lodash';
|
||||
import React from 'react';
|
||||
import { RouteComponentProps, useHistory } from 'react-router-dom';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { useTrackPageview } from '../../../../../observability/public';
|
||||
import { ChartPointerEventContextProvider } from '../../../context/chart_pointer_event/chart_pointer_event_context';
|
||||
import { useUrlParams } from '../../../context/url_params_context/use_url_params';
|
||||
import { FETCH_STATUS } from '../../../hooks/use_fetcher';
|
||||
import { useTransactionDistributionFetcher } from '../../../hooks/use_transaction_distribution_fetcher';
|
||||
import { ApmHeader } from '../../shared/ApmHeader';
|
||||
import { TransactionCharts } from '../../shared/charts/transaction_charts';
|
||||
import { HeightRetainer } from '../../shared/HeightRetainer';
|
||||
import { fromQuery, toQuery } from '../../shared/Links/url_helpers';
|
||||
import { SearchBar } from '../../shared/search_bar';
|
||||
import { TransactionDistribution } from './Distribution';
|
||||
import { useWaterfallFetcher } from './use_waterfall_fetcher';
|
||||
import { WaterfallWithSummmary } from './WaterfallWithSummmary';
|
||||
|
@ -35,12 +26,7 @@ interface Sample {
|
|||
transactionId: string;
|
||||
}
|
||||
|
||||
type TransactionDetailsProps = RouteComponentProps<{ serviceName: string }>;
|
||||
|
||||
export function TransactionDetails({
|
||||
location,
|
||||
match,
|
||||
}: TransactionDetailsProps) {
|
||||
export function TransactionDetails() {
|
||||
const { urlParams } = useUrlParams();
|
||||
const history = useHistory();
|
||||
const {
|
||||
|
@ -90,48 +76,43 @@ export function TransactionDetails({
|
|||
|
||||
return (
|
||||
<>
|
||||
<ApmHeader>
|
||||
<EuiTitle>
|
||||
<h1>{transactionName}</h1>
|
||||
</EuiTitle>
|
||||
</ApmHeader>
|
||||
<SearchBar />
|
||||
<EuiPage>
|
||||
<EuiFlexGroup direction="column" gutterSize="s">
|
||||
<ChartPointerEventContextProvider>
|
||||
<TransactionCharts />
|
||||
</ChartPointerEventContextProvider>
|
||||
<EuiTitle>
|
||||
<h2>{transactionName}</h2>
|
||||
</EuiTitle>
|
||||
|
||||
<EuiHorizontalRule size="full" margin="l" />
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
<EuiPanel>
|
||||
<TransactionDistribution
|
||||
distribution={distributionData}
|
||||
fetchStatus={distributionStatus}
|
||||
urlParams={urlParams}
|
||||
bucketIndex={bucketIndex}
|
||||
onBucketClick={(bucket) => {
|
||||
if (!isEmpty(bucket.samples)) {
|
||||
selectSampleFromBucketClick(bucket.samples[0]);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</EuiPanel>
|
||||
<ChartPointerEventContextProvider>
|
||||
<TransactionCharts />
|
||||
</ChartPointerEventContextProvider>
|
||||
|
||||
<EuiSpacer size="s" />
|
||||
<EuiHorizontalRule size="full" margin="l" />
|
||||
|
||||
<HeightRetainer>
|
||||
<WaterfallWithSummmary
|
||||
location={location}
|
||||
urlParams={urlParams}
|
||||
waterfall={waterfall}
|
||||
isLoading={waterfallStatus === FETCH_STATUS.LOADING}
|
||||
exceedsMax={exceedsMax}
|
||||
traceSamples={traceSamples}
|
||||
/>
|
||||
</HeightRetainer>
|
||||
</EuiFlexGroup>
|
||||
</EuiPage>
|
||||
<EuiPanel>
|
||||
<TransactionDistribution
|
||||
distribution={distributionData}
|
||||
fetchStatus={distributionStatus}
|
||||
urlParams={urlParams}
|
||||
bucketIndex={bucketIndex}
|
||||
onBucketClick={(bucket) => {
|
||||
if (!isEmpty(bucket.samples)) {
|
||||
selectSampleFromBucketClick(bucket.samples[0]);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</EuiPanel>
|
||||
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
<HeightRetainer>
|
||||
<WaterfallWithSummmary
|
||||
urlParams={urlParams}
|
||||
waterfall={waterfall}
|
||||
isLoading={waterfallStatus === FETCH_STATUS.LOADING}
|
||||
exceedsMax={exceedsMax}
|
||||
traceSamples={traceSamples}
|
||||
/>
|
||||
</HeightRetainer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -8,8 +8,6 @@
|
|||
import {
|
||||
EuiCallOut,
|
||||
EuiCode,
|
||||
EuiFlexGroup,
|
||||
EuiPage,
|
||||
EuiPanel,
|
||||
EuiSpacer,
|
||||
EuiTitle,
|
||||
|
@ -26,7 +24,6 @@ import { useUrlParams } from '../../../context/url_params_context/use_url_params
|
|||
import { TransactionCharts } from '../../shared/charts/transaction_charts';
|
||||
import { ElasticDocsLink } from '../../shared/Links/ElasticDocsLink';
|
||||
import { fromQuery, toQuery } from '../../shared/Links/url_helpers';
|
||||
import { SearchBar } from '../../shared/search_bar';
|
||||
import { TransactionList } from './TransactionList';
|
||||
import { useRedirect } from './useRedirect';
|
||||
import { useTransactionListFetcher } from './use_transaction_list';
|
||||
|
@ -80,62 +77,55 @@ export function TransactionOverview({ serviceName }: TransactionOverviewProps) {
|
|||
|
||||
return (
|
||||
<>
|
||||
<SearchBar showTransactionTypeSelector />
|
||||
<EuiPage>
|
||||
<EuiFlexGroup direction="column" gutterSize="s">
|
||||
<TransactionCharts />
|
||||
<EuiSpacer size="s" />
|
||||
<EuiPanel>
|
||||
<EuiTitle size="xs">
|
||||
<h3>Transactions</h3>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
{!transactionListData.isAggregationAccurate && (
|
||||
<EuiCallOut
|
||||
title={i18n.translate(
|
||||
'xpack.apm.transactionCardinalityWarning.title',
|
||||
{
|
||||
defaultMessage:
|
||||
'This view shows a subset of reported transactions.',
|
||||
}
|
||||
)}
|
||||
color="danger"
|
||||
iconType="alert"
|
||||
>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.apm.transactionCardinalityWarning.body"
|
||||
defaultMessage="The number of unique transaction names exceeds the configured value of {bucketSize}. Try reconfiguring your agents to group similar transactions or increase the value of {codeBlock}"
|
||||
values={{
|
||||
bucketSize: transactionListData.bucketSize,
|
||||
codeBlock: (
|
||||
<EuiCode>
|
||||
xpack.apm.ui.transactionGroupBucketSize
|
||||
</EuiCode>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
|
||||
<ElasticDocsLink
|
||||
section="/kibana"
|
||||
path="/troubleshooting.html#troubleshooting-too-many-transactions"
|
||||
>
|
||||
{i18n.translate(
|
||||
'xpack.apm.transactionCardinalityWarning.docsLink',
|
||||
{ defaultMessage: 'Learn more in the docs' }
|
||||
)}
|
||||
</ElasticDocsLink>
|
||||
</p>
|
||||
</EuiCallOut>
|
||||
<TransactionCharts />
|
||||
<EuiSpacer size="s" />
|
||||
<EuiPanel>
|
||||
<EuiTitle size="xs">
|
||||
<h3>Transactions</h3>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
{!transactionListData.isAggregationAccurate && (
|
||||
<EuiCallOut
|
||||
title={i18n.translate(
|
||||
'xpack.apm.transactionCardinalityWarning.title',
|
||||
{
|
||||
defaultMessage:
|
||||
'This view shows a subset of reported transactions.',
|
||||
}
|
||||
)}
|
||||
<EuiSpacer size="s" />
|
||||
<TransactionList
|
||||
isLoading={transactionListStatus === 'loading'}
|
||||
items={transactionListData.items || []}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</EuiFlexGroup>
|
||||
</EuiPage>
|
||||
color="danger"
|
||||
iconType="alert"
|
||||
>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.apm.transactionCardinalityWarning.body"
|
||||
defaultMessage="The number of unique transaction names exceeds the configured value of {bucketSize}. Try reconfiguring your agents to group similar transactions or increase the value of {codeBlock}"
|
||||
values={{
|
||||
bucketSize: transactionListData.bucketSize,
|
||||
codeBlock: (
|
||||
<EuiCode>xpack.apm.ui.transactionGroupBucketSize</EuiCode>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
|
||||
<ElasticDocsLink
|
||||
section="/kibana"
|
||||
path="/troubleshooting.html#troubleshooting-too-many-transactions"
|
||||
>
|
||||
{i18n.translate(
|
||||
'xpack.apm.transactionCardinalityWarning.docsLink',
|
||||
{ defaultMessage: 'Learn more in the docs' }
|
||||
)}
|
||||
</ElasticDocsLink>
|
||||
</p>
|
||||
</EuiCallOut>
|
||||
)}
|
||||
<EuiSpacer size="s" />
|
||||
<TransactionList
|
||||
isLoading={transactionListStatus === 'loading'}
|
||||
items={transactionListData.items || []}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { fireEvent, getByText, queryByLabelText } from '@testing-library/react';
|
||||
import { queryByLabelText } from '@testing-library/react';
|
||||
import { createMemoryHistory } from 'history';
|
||||
import { CoreStart } from 'kibana/public';
|
||||
import React from 'react';
|
||||
|
@ -107,46 +107,6 @@ describe('TransactionOverview', () => {
|
|||
|
||||
const FILTER_BY_TYPE_LABEL = 'Transaction type';
|
||||
|
||||
describe('when transactionType is selected and multiple transaction types are given', () => {
|
||||
it('renders a radio group with transaction types', () => {
|
||||
const { container } = setup({
|
||||
serviceTransactionTypes: ['firstType', 'secondType'],
|
||||
urlParams: {
|
||||
transactionType: 'secondType',
|
||||
},
|
||||
});
|
||||
|
||||
expect(getByText(container, 'firstType')).toBeInTheDocument();
|
||||
expect(getByText(container, 'secondType')).toBeInTheDocument();
|
||||
|
||||
expect(getByText(container, 'firstType')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should update the URL when a transaction type is selected', () => {
|
||||
const { container } = setup({
|
||||
serviceTransactionTypes: ['firstType', 'secondType'],
|
||||
urlParams: {
|
||||
transactionType: 'secondType',
|
||||
},
|
||||
});
|
||||
|
||||
expect(history.location.search).toEqual(
|
||||
'?transactionType=secondType&rangeFrom=now-15m&rangeTo=now'
|
||||
);
|
||||
expect(getByText(container, 'firstType')).toBeInTheDocument();
|
||||
expect(getByText(container, 'secondType')).toBeInTheDocument();
|
||||
|
||||
fireEvent.change(getByText(container, 'firstType').parentElement!, {
|
||||
target: { value: 'firstType' },
|
||||
});
|
||||
|
||||
expect(history.push).toHaveBeenCalled();
|
||||
expect(history.location.search).toEqual(
|
||||
'?transactionType=firstType&rangeFrom=now-15m&rangeTo=now'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when a transaction type is selected, and there are no other transaction types', () => {
|
||||
it('does not render a radio group with transaction types', () => {
|
||||
const { container } = setup({
|
||||
|
|
|
@ -0,0 +1,478 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
import { getServiceNodeName } from '../../../common/service_nodes';
|
||||
import { APMRouteDefinition } from '../../application/routes';
|
||||
import { toQuery } from '../shared/Links/url_helpers';
|
||||
import { ErrorGroupDetails } from '../app/ErrorGroupDetails';
|
||||
import { useApmPluginContext } from '../../context/apm_plugin/use_apm_plugin_context';
|
||||
import { ServiceNodeMetrics } from '../app/service_node_metrics';
|
||||
import { Settings } from '../app/Settings';
|
||||
import { AgentConfigurations } from '../app/Settings/AgentConfigurations';
|
||||
import { AnomalyDetection } from '../app/Settings/anomaly_detection';
|
||||
import { ApmIndices } from '../app/Settings/ApmIndices';
|
||||
import { CustomizeUI } from '../app/Settings/CustomizeUI';
|
||||
import { TraceLink } from '../app/TraceLink';
|
||||
import { TransactionDetails } from '../app/transaction_details';
|
||||
import {
|
||||
CreateAgentConfigurationRouteHandler,
|
||||
EditAgentConfigurationRouteHandler,
|
||||
} from './route_handlers/agent_configuration';
|
||||
import { enableServiceOverview } from '../../../common/ui_settings_keys';
|
||||
import { redirectTo } from './redirect_to';
|
||||
import { ApmMainTemplate } from './templates/apm_main_template';
|
||||
import { ApmServiceTemplate } from './templates/apm_service_template';
|
||||
import { ServiceProfiling } from '../app/service_profiling';
|
||||
import { ErrorGroupOverview } from '../app/error_group_overview';
|
||||
import { ServiceMap } from '../app/service_map';
|
||||
import { ServiceNodeOverview } from '../app/service_node_overview';
|
||||
import { ServiceMetrics } from '../app/service_metrics';
|
||||
import { ServiceOverview } from '../app/service_overview';
|
||||
import { TransactionOverview } from '../app/transaction_overview';
|
||||
import { ServiceInventory } from '../app/service_inventory';
|
||||
import { TraceOverview } from '../app/trace_overview';
|
||||
|
||||
// These component function definitions are used below with the `component`
|
||||
// property of the route definitions.
|
||||
//
|
||||
// If you provide an inline function to the component prop, you would create a
|
||||
// new component every render. This results in the existing component unmounting
|
||||
// and the new component mounting instead of just updating the existing component.
|
||||
|
||||
const ServiceInventoryTitle = i18n.translate(
|
||||
'xpack.apm.views.serviceInventory.title',
|
||||
{ defaultMessage: 'Services' }
|
||||
);
|
||||
|
||||
function ServiceInventoryView() {
|
||||
return (
|
||||
<ApmMainTemplate pageTitle={ServiceInventoryTitle}>
|
||||
<ServiceInventory />
|
||||
</ApmMainTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
const TraceOverviewTitle = i18n.translate(
|
||||
'xpack.apm.views.traceOverview.title',
|
||||
{
|
||||
defaultMessage: 'Traces',
|
||||
}
|
||||
);
|
||||
|
||||
function TraceOverviewView() {
|
||||
return (
|
||||
<ApmMainTemplate pageTitle={TraceOverviewTitle}>
|
||||
<TraceOverview />
|
||||
</ApmMainTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
const ServiceMapTitle = i18n.translate('xpack.apm.views.serviceMap.title', {
|
||||
defaultMessage: 'Service Map',
|
||||
});
|
||||
|
||||
function ServiceMapView() {
|
||||
return (
|
||||
<ApmMainTemplate pageTitle={ServiceMapTitle}>
|
||||
<ServiceMap />
|
||||
</ApmMainTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
function ServiceDetailsErrorsRouteView(
|
||||
props: RouteComponentProps<{ serviceName: string }>
|
||||
) {
|
||||
const { serviceName } = props.match.params;
|
||||
return (
|
||||
<ApmServiceTemplate serviceName={serviceName} selectedTab="errors">
|
||||
<ErrorGroupOverview serviceName={serviceName} />
|
||||
</ApmServiceTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
function ErrorGroupDetailsRouteView(
|
||||
props: RouteComponentProps<{ serviceName: string; groupId: string }>
|
||||
) {
|
||||
const { serviceName, groupId } = props.match.params;
|
||||
return (
|
||||
<ApmServiceTemplate serviceName={serviceName} selectedTab="errors">
|
||||
<ErrorGroupDetails serviceName={serviceName} groupId={groupId} />
|
||||
</ApmServiceTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
function ServiceDetailsMetricsRouteView(
|
||||
props: RouteComponentProps<{ serviceName: string }>
|
||||
) {
|
||||
const { serviceName } = props.match.params;
|
||||
return (
|
||||
<ApmServiceTemplate serviceName={serviceName} selectedTab="metrics">
|
||||
<ServiceMetrics />
|
||||
</ApmServiceTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
function ServiceDetailsNodesRouteView(
|
||||
props: RouteComponentProps<{ serviceName: string }>
|
||||
) {
|
||||
const { serviceName } = props.match.params;
|
||||
return (
|
||||
<ApmServiceTemplate serviceName={serviceName} selectedTab="nodes">
|
||||
<ServiceNodeOverview serviceName={serviceName} />
|
||||
</ApmServiceTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
function ServiceDetailsOverviewRouteView(
|
||||
props: RouteComponentProps<{ serviceName: string }>
|
||||
) {
|
||||
const { serviceName } = props.match.params;
|
||||
return (
|
||||
<ApmServiceTemplate
|
||||
serviceName={serviceName}
|
||||
selectedTab="overview"
|
||||
searchBarOptions={{
|
||||
showTransactionTypeSelector: true,
|
||||
showTimeComparison: true,
|
||||
}}
|
||||
>
|
||||
<ServiceOverview serviceName={serviceName} />
|
||||
</ApmServiceTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
function ServiceDetailsServiceMapRouteView(
|
||||
props: RouteComponentProps<{ serviceName: string }>
|
||||
) {
|
||||
const { serviceName } = props.match.params;
|
||||
return (
|
||||
<ApmServiceTemplate
|
||||
serviceName={serviceName}
|
||||
selectedTab="service-map"
|
||||
searchBarOptions={{ hidden: true }}
|
||||
>
|
||||
<ServiceMap serviceName={serviceName} />
|
||||
</ApmServiceTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
function ServiceDetailsTransactionsRouteView(
|
||||
props: RouteComponentProps<{ serviceName: string }>
|
||||
) {
|
||||
const { serviceName } = props.match.params;
|
||||
return (
|
||||
<ApmServiceTemplate
|
||||
serviceName={serviceName}
|
||||
selectedTab="transactions"
|
||||
searchBarOptions={{
|
||||
showTransactionTypeSelector: true,
|
||||
}}
|
||||
>
|
||||
<TransactionOverview serviceName={serviceName} />
|
||||
</ApmServiceTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
function ServiceDetailsProfilingRouteView(
|
||||
props: RouteComponentProps<{ serviceName: string }>
|
||||
) {
|
||||
const { serviceName } = props.match.params;
|
||||
return (
|
||||
<ApmServiceTemplate serviceName={serviceName} selectedTab="profiling">
|
||||
<ServiceProfiling serviceName={serviceName} />
|
||||
</ApmServiceTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
function ServiceNodeMetricsRouteView(
|
||||
props: RouteComponentProps<{
|
||||
serviceName: string;
|
||||
serviceNodeName: string;
|
||||
}>
|
||||
) {
|
||||
const { serviceName, serviceNodeName } = props.match.params;
|
||||
return (
|
||||
<ApmServiceTemplate serviceName={serviceName} selectedTab="nodes">
|
||||
<ServiceNodeMetrics
|
||||
serviceName={serviceName}
|
||||
serviceNodeName={serviceNodeName}
|
||||
/>
|
||||
</ApmServiceTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
function TransactionDetailsRouteView(
|
||||
props: RouteComponentProps<{ serviceName: string }>
|
||||
) {
|
||||
const { serviceName } = props.match.params;
|
||||
return (
|
||||
<ApmServiceTemplate serviceName={serviceName} selectedTab="transactions">
|
||||
<TransactionDetails />
|
||||
</ApmServiceTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
function SettingsAgentConfigurationRouteView() {
|
||||
return (
|
||||
<ApmMainTemplate pageTitle="Settings">
|
||||
<Settings>
|
||||
<AgentConfigurations />
|
||||
</Settings>
|
||||
</ApmMainTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
function SettingsAnomalyDetectionRouteView() {
|
||||
return (
|
||||
<ApmMainTemplate pageTitle="Settings">
|
||||
<Settings>
|
||||
<AnomalyDetection />
|
||||
</Settings>
|
||||
</ApmMainTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
function SettingsApmIndicesRouteView() {
|
||||
return (
|
||||
<ApmMainTemplate pageTitle="Settings">
|
||||
<Settings>
|
||||
<ApmIndices />
|
||||
</Settings>
|
||||
</ApmMainTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
function SettingsCustomizeUI() {
|
||||
return (
|
||||
<ApmMainTemplate pageTitle="Settings">
|
||||
<Settings>
|
||||
<CustomizeUI />
|
||||
</Settings>
|
||||
</ApmMainTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
const SettingsApmIndicesTitle = i18n.translate(
|
||||
'xpack.apm.views.settings.indices.title',
|
||||
{ defaultMessage: 'Indices' }
|
||||
);
|
||||
|
||||
const SettingsAgentConfigurationTitle = i18n.translate(
|
||||
'xpack.apm.views.settings.agentConfiguration.title',
|
||||
{ defaultMessage: 'Agent Configuration' }
|
||||
);
|
||||
const CreateAgentConfigurationTitle = i18n.translate(
|
||||
'xpack.apm.views.settings.createAgentConfiguration.title',
|
||||
{ defaultMessage: 'Create Agent Configuration' }
|
||||
);
|
||||
const EditAgentConfigurationTitle = i18n.translate(
|
||||
'xpack.apm.views.settings.editAgentConfiguration.title',
|
||||
{ defaultMessage: 'Edit Agent Configuration' }
|
||||
);
|
||||
const SettingsCustomizeUITitle = i18n.translate(
|
||||
'xpack.apm.views.settings.customizeUI.title',
|
||||
{ defaultMessage: 'Customize app' }
|
||||
);
|
||||
const SettingsAnomalyDetectionTitle = i18n.translate(
|
||||
'xpack.apm.views.settings.anomalyDetection.title',
|
||||
{ defaultMessage: 'Anomaly detection' }
|
||||
);
|
||||
const SettingsTitle = i18n.translate('xpack.apm.views.listSettings.title', {
|
||||
defaultMessage: 'Settings',
|
||||
});
|
||||
|
||||
/**
|
||||
* The array of route definitions to be used when the application
|
||||
* creates the routes.
|
||||
*/
|
||||
export const apmRouteConfig: APMRouteDefinition[] = [
|
||||
/*
|
||||
* Home routes
|
||||
*/
|
||||
{
|
||||
exact: true,
|
||||
path: '/',
|
||||
render: redirectTo('/services'),
|
||||
breadcrumb: 'APM',
|
||||
},
|
||||
{
|
||||
exact: true,
|
||||
path: '/services', // !! Need to be kept in sync with the deepLinks in x-pack/plugins/apm/public/plugin.ts
|
||||
component: ServiceInventoryView,
|
||||
breadcrumb: ServiceInventoryTitle,
|
||||
},
|
||||
{
|
||||
exact: true,
|
||||
path: '/traces', // !! Need to be kept in sync with the deepLinks in x-pack/plugins/apm/public/plugin.ts
|
||||
component: TraceOverviewView,
|
||||
breadcrumb: TraceOverviewTitle,
|
||||
},
|
||||
{
|
||||
exact: true,
|
||||
path: '/service-map', // !! Need to be kept in sync with the deepLinks in x-pack/plugins/apm/public/plugin.ts
|
||||
component: ServiceMapView,
|
||||
breadcrumb: ServiceMapTitle,
|
||||
},
|
||||
|
||||
/*
|
||||
* Settings routes
|
||||
*/
|
||||
{
|
||||
exact: true,
|
||||
path: '/settings',
|
||||
render: redirectTo('/settings/agent-configuration'),
|
||||
breadcrumb: SettingsTitle,
|
||||
},
|
||||
{
|
||||
exact: true,
|
||||
path: '/settings/agent-configuration',
|
||||
component: SettingsAgentConfigurationRouteView,
|
||||
breadcrumb: SettingsAgentConfigurationTitle,
|
||||
},
|
||||
{
|
||||
exact: true,
|
||||
path: '/settings/agent-configuration/create',
|
||||
component: CreateAgentConfigurationRouteHandler,
|
||||
breadcrumb: CreateAgentConfigurationTitle,
|
||||
},
|
||||
{
|
||||
exact: true,
|
||||
path: '/settings/agent-configuration/edit',
|
||||
breadcrumb: EditAgentConfigurationTitle,
|
||||
component: EditAgentConfigurationRouteHandler,
|
||||
},
|
||||
{
|
||||
exact: true,
|
||||
path: '/settings/apm-indices',
|
||||
component: SettingsApmIndicesRouteView,
|
||||
breadcrumb: SettingsApmIndicesTitle,
|
||||
},
|
||||
{
|
||||
exact: true,
|
||||
path: '/settings/customize-ui',
|
||||
component: SettingsCustomizeUI,
|
||||
breadcrumb: SettingsCustomizeUITitle,
|
||||
},
|
||||
{
|
||||
exact: true,
|
||||
path: '/settings/anomaly-detection',
|
||||
component: SettingsAnomalyDetectionRouteView,
|
||||
breadcrumb: SettingsAnomalyDetectionTitle,
|
||||
},
|
||||
|
||||
/*
|
||||
* Services routes (with APM Service context)
|
||||
*/
|
||||
{
|
||||
exact: true,
|
||||
path: '/services/:serviceName',
|
||||
breadcrumb: ({ match }) => match.params.serviceName,
|
||||
component: RedirectToDefaultServiceRouteView,
|
||||
},
|
||||
{
|
||||
exact: true,
|
||||
path: '/services/:serviceName/overview',
|
||||
breadcrumb: i18n.translate('xpack.apm.views.overview.title', {
|
||||
defaultMessage: 'Overview',
|
||||
}),
|
||||
component: ServiceDetailsOverviewRouteView,
|
||||
},
|
||||
{
|
||||
exact: true,
|
||||
path: '/services/:serviceName/transactions',
|
||||
component: ServiceDetailsTransactionsRouteView,
|
||||
breadcrumb: i18n.translate('xpack.apm.views.transactions.title', {
|
||||
defaultMessage: 'Transactions',
|
||||
}),
|
||||
},
|
||||
{
|
||||
exact: true,
|
||||
path: '/services/:serviceName/errors/:groupId',
|
||||
component: ErrorGroupDetailsRouteView,
|
||||
breadcrumb: ({ match }) => match.params.groupId,
|
||||
},
|
||||
{
|
||||
exact: true,
|
||||
path: '/services/:serviceName/errors',
|
||||
component: ServiceDetailsErrorsRouteView,
|
||||
breadcrumb: i18n.translate('xpack.apm.views.errors.title', {
|
||||
defaultMessage: 'Errors',
|
||||
}),
|
||||
},
|
||||
{
|
||||
exact: true,
|
||||
path: '/services/:serviceName/metrics',
|
||||
component: ServiceDetailsMetricsRouteView,
|
||||
breadcrumb: i18n.translate('xpack.apm.views.metrics.title', {
|
||||
defaultMessage: 'Metrics',
|
||||
}),
|
||||
},
|
||||
// service nodes, only enabled for java agents for now
|
||||
{
|
||||
exact: true,
|
||||
path: '/services/:serviceName/nodes',
|
||||
component: ServiceDetailsNodesRouteView,
|
||||
breadcrumb: i18n.translate('xpack.apm.views.nodes.title', {
|
||||
defaultMessage: 'JVMs',
|
||||
}),
|
||||
},
|
||||
// node metrics
|
||||
{
|
||||
exact: true,
|
||||
path: '/services/:serviceName/nodes/:serviceNodeName/metrics',
|
||||
component: ServiceNodeMetricsRouteView,
|
||||
breadcrumb: ({ match }) => getServiceNodeName(match.params.serviceNodeName),
|
||||
},
|
||||
{
|
||||
exact: true,
|
||||
path: '/services/:serviceName/transactions/view',
|
||||
component: TransactionDetailsRouteView,
|
||||
breadcrumb: ({ location }) => {
|
||||
const query = toQuery(location.search);
|
||||
return query.transactionName as string;
|
||||
},
|
||||
},
|
||||
{
|
||||
exact: true,
|
||||
path: '/services/:serviceName/profiling',
|
||||
component: ServiceDetailsProfilingRouteView,
|
||||
breadcrumb: i18n.translate('xpack.apm.views.serviceProfiling.title', {
|
||||
defaultMessage: 'Profiling',
|
||||
}),
|
||||
},
|
||||
{
|
||||
exact: true,
|
||||
path: '/services/:serviceName/service-map',
|
||||
component: ServiceDetailsServiceMapRouteView,
|
||||
breadcrumb: i18n.translate('xpack.apm.views.serviceMap.title', {
|
||||
defaultMessage: 'Service Map',
|
||||
}),
|
||||
},
|
||||
/*
|
||||
* Utilility routes
|
||||
*/
|
||||
{
|
||||
exact: true,
|
||||
path: '/link-to/trace/:traceId',
|
||||
component: TraceLink,
|
||||
breadcrumb: null,
|
||||
},
|
||||
];
|
||||
|
||||
function RedirectToDefaultServiceRouteView(
|
||||
props: RouteComponentProps<{ serviceName: string }>
|
||||
) {
|
||||
const { uiSettings } = useApmPluginContext().core;
|
||||
const { serviceName } = props.match.params;
|
||||
if (uiSettings.get(enableServiceOverview)) {
|
||||
return redirectTo(`/services/${serviceName}/overview`)(props);
|
||||
}
|
||||
return redirectTo(`/services/${serviceName}/transactions`)(props);
|
||||
}
|
110
x-pack/plugins/apm/public/components/routing/app_root.tsx
Normal file
110
x-pack/plugins/apm/public/components/routing/app_root.tsx
Normal file
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { ApmRoute } from '@elastic/apm-rum-react';
|
||||
import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json';
|
||||
import euiLightVars from '@elastic/eui/dist/eui_theme_light.json';
|
||||
import React from 'react';
|
||||
import { Route, Router, Switch } from 'react-router-dom';
|
||||
import { DefaultTheme, ThemeProvider } from 'styled-components';
|
||||
import { euiStyled } from '../../../../../../src/plugins/kibana_react/common';
|
||||
import {
|
||||
KibanaContextProvider,
|
||||
RedirectAppLinks,
|
||||
useUiSetting$,
|
||||
} from '../../../../../../src/plugins/kibana_react/public';
|
||||
import { ScrollToTopOnPathChange } from '../../components/app/Main/ScrollToTopOnPathChange';
|
||||
import {
|
||||
ApmPluginContext,
|
||||
ApmPluginContextValue,
|
||||
} from '../../context/apm_plugin/apm_plugin_context';
|
||||
import { LicenseProvider } from '../../context/license/license_context';
|
||||
import { UrlParamsProvider } from '../../context/url_params_context/url_params_context';
|
||||
import { useBreadcrumbs } from '../../hooks/use_breadcrumbs';
|
||||
import { ApmPluginStartDeps } from '../../plugin';
|
||||
import { HeaderMenuPortal } from '../../../../observability/public';
|
||||
import { ApmHeaderActionMenu } from '../shared/apm_header_action_menu';
|
||||
import { useApmPluginContext } from '../../context/apm_plugin/use_apm_plugin_context';
|
||||
import { AnomalyDetectionJobsContextProvider } from '../../context/anomaly_detection_jobs/anomaly_detection_jobs_context';
|
||||
import { apmRouteConfig } from './apm_route_config';
|
||||
|
||||
const MainContainer = euiStyled.div`
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
export function ApmAppRoot({
|
||||
apmPluginContextValue,
|
||||
pluginsStart,
|
||||
}: {
|
||||
apmPluginContextValue: ApmPluginContextValue;
|
||||
pluginsStart: ApmPluginStartDeps;
|
||||
}) {
|
||||
const { appMountParameters, core } = apmPluginContextValue;
|
||||
const { history } = appMountParameters;
|
||||
const i18nCore = core.i18n;
|
||||
|
||||
return (
|
||||
<RedirectAppLinks application={core.application}>
|
||||
<ApmPluginContext.Provider value={apmPluginContextValue}>
|
||||
<KibanaContextProvider services={{ ...core, ...pluginsStart }}>
|
||||
<i18nCore.Context>
|
||||
<Router history={history}>
|
||||
<UrlParamsProvider>
|
||||
<LicenseProvider>
|
||||
<AnomalyDetectionJobsContextProvider>
|
||||
<ApmThemeProvider>
|
||||
<MainContainer
|
||||
data-test-subj="apmMainContainer"
|
||||
role="main"
|
||||
>
|
||||
<MountApmHeaderActionMenu />
|
||||
|
||||
<Route component={ScrollToTopOnPathChange} />
|
||||
<Switch>
|
||||
{apmRouteConfig.map((route, i) => (
|
||||
<ApmRoute key={i} {...route} />
|
||||
))}
|
||||
</Switch>
|
||||
</MainContainer>
|
||||
</ApmThemeProvider>
|
||||
</AnomalyDetectionJobsContextProvider>
|
||||
</LicenseProvider>
|
||||
</UrlParamsProvider>
|
||||
</Router>
|
||||
</i18nCore.Context>
|
||||
</KibanaContextProvider>
|
||||
</ApmPluginContext.Provider>
|
||||
</RedirectAppLinks>
|
||||
);
|
||||
}
|
||||
|
||||
function MountApmHeaderActionMenu() {
|
||||
useBreadcrumbs(apmRouteConfig);
|
||||
const { setHeaderActionMenu } = useApmPluginContext().appMountParameters;
|
||||
|
||||
return (
|
||||
<HeaderMenuPortal setHeaderActionMenu={setHeaderActionMenu}>
|
||||
<ApmHeaderActionMenu />
|
||||
</HeaderMenuPortal>
|
||||
);
|
||||
}
|
||||
|
||||
function ApmThemeProvider({ children }: { children: React.ReactNode }) {
|
||||
const [darkMode] = useUiSetting$<boolean>('theme:darkMode');
|
||||
|
||||
return (
|
||||
<ThemeProvider
|
||||
theme={(outerTheme?: DefaultTheme) => ({
|
||||
...outerTheme,
|
||||
eui: darkMode ? euiDarkVars : euiLightVars,
|
||||
darkMode,
|
||||
})}
|
||||
>
|
||||
{children}
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
38
x-pack/plugins/apm/public/components/routing/redirect_to.tsx
Normal file
38
x-pack/plugins/apm/public/components/routing/redirect_to.tsx
Normal file
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Redirect, RouteComponentProps } from 'react-router-dom';
|
||||
|
||||
/**
|
||||
* Given a path, redirect to that location, preserving the search and maintaining
|
||||
* backward-compatibilty with legacy (pre-7.9) hash-based URLs.
|
||||
*/
|
||||
export function redirectTo(to: string) {
|
||||
return ({ location }: RouteComponentProps<{}>) => {
|
||||
let resolvedUrl: URL | undefined;
|
||||
|
||||
// Redirect root URLs with a hash to support backward compatibility with URLs
|
||||
// from before we switched to the non-hash platform history.
|
||||
if (location.pathname === '' && location.hash.length > 0) {
|
||||
// We just want the search and pathname so the host doesn't matter
|
||||
resolvedUrl = new URL(location.hash.slice(1), 'http://localhost');
|
||||
to = resolvedUrl.pathname;
|
||||
}
|
||||
|
||||
return (
|
||||
<Redirect
|
||||
to={{
|
||||
...location,
|
||||
hash: '',
|
||||
pathname: to,
|
||||
search: resolvedUrl ? resolvedUrl.search : location.search,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
}
|
|
@ -5,11 +5,11 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { routes } from './';
|
||||
import { apmRouteConfig } from './apm_route_config';
|
||||
|
||||
describe('routes', () => {
|
||||
describe('/', () => {
|
||||
const route = routes.find((r) => r.path === '/');
|
||||
const route = apmRouteConfig.find((r) => r.path === '/');
|
||||
|
||||
describe('with no hash path', () => {
|
||||
it('redirects to /services', () => {
|
|
@ -7,10 +7,10 @@
|
|||
|
||||
import React from 'react';
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
import { useFetcher } from '../../../../../hooks/use_fetcher';
|
||||
import { toQuery } from '../../../../shared/Links/url_helpers';
|
||||
import { Settings } from '../../../Settings';
|
||||
import { AgentConfigurationCreateEdit } from '../../../Settings/AgentConfigurations/AgentConfigurationCreateEdit';
|
||||
import { useFetcher } from '../../../hooks/use_fetcher';
|
||||
import { toQuery } from '../../shared/Links/url_helpers';
|
||||
import { Settings } from '../../app/Settings';
|
||||
import { AgentConfigurationCreateEdit } from '../../app/Settings/AgentConfigurations/AgentConfigurationCreateEdit';
|
||||
|
||||
type EditAgentConfigurationRouteHandler = RouteComponentProps<{}>;
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
|
||||
import { ApmPluginStartDeps } from '../../../plugin';
|
||||
import { EnvironmentFilter } from '../../shared/EnvironmentFilter';
|
||||
|
||||
/*
|
||||
* This template contains:
|
||||
* - The Shared Observability Nav (https://github.com/elastic/kibana/blob/f7698bd8aa8787d683c728300ba4ca52b202369c/x-pack/plugins/observability/public/components/shared/page_template/README.md)
|
||||
* - The APM Header Action Menu
|
||||
* - Page title
|
||||
*
|
||||
* Optionally:
|
||||
* - EnvironmentFilter
|
||||
*/
|
||||
export function ApmMainTemplate({
|
||||
pageTitle,
|
||||
children,
|
||||
}: {
|
||||
pageTitle: React.ReactNode;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const { services } = useKibana<ApmPluginStartDeps>();
|
||||
const ObservabilityPageTemplate =
|
||||
services.observability.navigation.PageTemplate;
|
||||
|
||||
return (
|
||||
<ObservabilityPageTemplate
|
||||
pageHeader={{
|
||||
pageTitle,
|
||||
rightSideItems: [<EnvironmentFilter />],
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</ObservabilityPageTemplate>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,216 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiTitle,
|
||||
EuiTabs,
|
||||
EuiTab,
|
||||
EuiBetaBadge,
|
||||
} from '@elastic/eui';
|
||||
import { ApmMainTemplate } from './apm_main_template';
|
||||
import { ApmServiceContextProvider } from '../../../context/apm_service/apm_service_context';
|
||||
import { enableServiceOverview } from '../../../../common/ui_settings_keys';
|
||||
import { isJavaAgentName, isRumAgentName } from '../../../../common/agent_name';
|
||||
import { ServiceIcons } from '../../shared/service_icons';
|
||||
import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context';
|
||||
import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context';
|
||||
import { useErrorOverviewHref } from '../../shared/Links/apm/ErrorOverviewLink';
|
||||
import { useMetricOverviewHref } from '../../shared/Links/apm/MetricOverviewLink';
|
||||
import { useServiceMapHref } from '../../shared/Links/apm/ServiceMapLink';
|
||||
import { useServiceNodeOverviewHref } from '../../shared/Links/apm/ServiceNodeOverviewLink';
|
||||
import { useServiceOverviewHref } from '../../shared/Links/apm/service_overview_link';
|
||||
import { useServiceProfilingHref } from '../../shared/Links/apm/service_profiling_link';
|
||||
import { useTransactionsOverviewHref } from '../../shared/Links/apm/transaction_overview_link';
|
||||
import { useUrlParams } from '../../../context/url_params_context/use_url_params';
|
||||
import { Correlations } from '../../app/correlations';
|
||||
import { SearchBar } from '../../shared/search_bar';
|
||||
|
||||
interface Tab {
|
||||
key: TabKey;
|
||||
href: string;
|
||||
text: React.ReactNode;
|
||||
hidden?: boolean;
|
||||
}
|
||||
|
||||
type TabKey =
|
||||
| 'errors'
|
||||
| 'metrics'
|
||||
| 'nodes'
|
||||
| 'overview'
|
||||
| 'service-map'
|
||||
| 'profiling'
|
||||
| 'transactions';
|
||||
|
||||
export function ApmServiceTemplate({
|
||||
children,
|
||||
serviceName,
|
||||
selectedTab,
|
||||
searchBarOptions,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
serviceName: string;
|
||||
selectedTab: TabKey;
|
||||
searchBarOptions?: {
|
||||
hidden?: boolean;
|
||||
showTransactionTypeSelector?: boolean;
|
||||
showTimeComparison?: boolean;
|
||||
};
|
||||
}) {
|
||||
return (
|
||||
<ApmMainTemplate
|
||||
pageTitle={
|
||||
<>
|
||||
<EuiFlexGroup alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiTitle size="l">
|
||||
<h1>{serviceName}</h1>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<ServiceIcons serviceName={serviceName} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<ApmServiceContextProvider>
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem>
|
||||
<TabNavigation
|
||||
serviceName={serviceName}
|
||||
selectedTab={selectedTab}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<Correlations />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
<SearchBar {...searchBarOptions} />
|
||||
|
||||
{children}
|
||||
</ApmServiceContextProvider>
|
||||
</ApmMainTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
function TabNavigation({
|
||||
serviceName,
|
||||
selectedTab,
|
||||
}: {
|
||||
serviceName: string;
|
||||
selectedTab: TabKey;
|
||||
}) {
|
||||
const { agentName, transactionType } = useApmServiceContext();
|
||||
const { core, config } = useApmPluginContext();
|
||||
const { urlParams } = useUrlParams();
|
||||
|
||||
const tabs: Tab[] = [
|
||||
{
|
||||
key: 'overview',
|
||||
href: useServiceOverviewHref({ serviceName, transactionType }),
|
||||
text: i18n.translate('xpack.apm.serviceDetails.overviewTabLabel', {
|
||||
defaultMessage: 'Overview',
|
||||
}),
|
||||
hidden: !core.uiSettings.get(enableServiceOverview),
|
||||
},
|
||||
{
|
||||
key: 'transactions',
|
||||
href: useTransactionsOverviewHref({
|
||||
serviceName,
|
||||
latencyAggregationType: urlParams.latencyAggregationType,
|
||||
transactionType,
|
||||
}),
|
||||
text: i18n.translate('xpack.apm.serviceDetails.transactionsTabLabel', {
|
||||
defaultMessage: 'Transactions',
|
||||
}),
|
||||
},
|
||||
{
|
||||
key: 'errors',
|
||||
href: useErrorOverviewHref(serviceName),
|
||||
text: i18n.translate('xpack.apm.serviceDetails.errorsTabLabel', {
|
||||
defaultMessage: 'Errors',
|
||||
}),
|
||||
},
|
||||
{
|
||||
key: 'nodes',
|
||||
href: useServiceNodeOverviewHref(serviceName),
|
||||
text: i18n.translate('xpack.apm.serviceDetails.nodesTabLabel', {
|
||||
defaultMessage: 'JVMs',
|
||||
}),
|
||||
hidden: !isJavaAgentName(agentName),
|
||||
},
|
||||
{
|
||||
key: 'metrics',
|
||||
href: useMetricOverviewHref(serviceName),
|
||||
text: i18n.translate('xpack.apm.serviceDetails.metricsTabLabel', {
|
||||
defaultMessage: 'Metrics',
|
||||
}),
|
||||
hidden:
|
||||
!agentName || isRumAgentName(agentName) || isJavaAgentName(agentName),
|
||||
},
|
||||
{
|
||||
key: 'service-map',
|
||||
href: useServiceMapHref(serviceName),
|
||||
text: i18n.translate('xpack.apm.home.serviceMapTabLabel', {
|
||||
defaultMessage: 'Service Map',
|
||||
}),
|
||||
},
|
||||
{
|
||||
key: 'profiling',
|
||||
href: useServiceProfilingHref({ serviceName }),
|
||||
hidden: !config.profilingEnabled,
|
||||
text: (
|
||||
<EuiFlexGroup direction="row" gutterSize="s">
|
||||
<EuiFlexItem>
|
||||
{i18n.translate('xpack.apm.serviceDetails.profilingTabLabel', {
|
||||
defaultMessage: 'Profiling',
|
||||
})}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiBetaBadge
|
||||
label={i18n.translate(
|
||||
'xpack.apm.serviceDetails.profilingTabExperimentalLabel',
|
||||
{
|
||||
defaultMessage: 'Experimental',
|
||||
}
|
||||
)}
|
||||
tooltipContent={i18n.translate(
|
||||
'xpack.apm.serviceDetails.profilingTabExperimentalDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
'Profiling is highly experimental and for internal use only.',
|
||||
}
|
||||
)}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<EuiTabs display="condensed">
|
||||
{tabs
|
||||
.filter((t) => !t.hidden)
|
||||
.map(({ href, key, text }) => (
|
||||
<EuiTab
|
||||
data-test-subj={`tab_${key}`}
|
||||
href={href}
|
||||
isSelected={key === selectedTab}
|
||||
key={key}
|
||||
>
|
||||
{text}
|
||||
</EuiTab>
|
||||
))}
|
||||
</EuiTabs>
|
||||
);
|
||||
}
|
|
@ -1,53 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiTitle } from '@elastic/eui';
|
||||
import React, { ComponentType } from 'react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { CoreStart } from '../../../../../../../src/core/public';
|
||||
import { EuiThemeProvider } from '../../../../../../../src/plugins/kibana_react/common';
|
||||
import { MockApmPluginContextWrapper } from '../../../context/apm_plugin/mock_apm_plugin_context';
|
||||
import { MockUrlParamsContextProvider } from '../../../context/url_params_context/mock_url_params_context_provider';
|
||||
import { createCallApmApi } from '../../../services/rest/createCallApmApi';
|
||||
import { ApmHeader } from './';
|
||||
|
||||
export default {
|
||||
title: 'shared/ApmHeader',
|
||||
component: ApmHeader,
|
||||
decorators: [
|
||||
(Story: ComponentType) => {
|
||||
createCallApmApi(({} as unknown) as CoreStart);
|
||||
|
||||
return (
|
||||
<EuiThemeProvider>
|
||||
<MockUrlParamsContextProvider
|
||||
params={{ rangeFrom: 'now-15m', rangeTo: 'now' }}
|
||||
>
|
||||
<MockApmPluginContextWrapper>
|
||||
<MemoryRouter>
|
||||
<Story />
|
||||
</MemoryRouter>
|
||||
</MockApmPluginContextWrapper>
|
||||
</MockUrlParamsContextProvider>
|
||||
</EuiThemeProvider>
|
||||
);
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export function Example() {
|
||||
return (
|
||||
<ApmHeader>
|
||||
<EuiTitle>
|
||||
<h1>
|
||||
GET
|
||||
/api/v1/regions/azure-eastus2/clusters/elasticsearch/xc18de071deb4262be54baebf5f6a1ce/proxy/_snapshot/found-snapshots/_all
|
||||
</h1>
|
||||
</EuiTitle>
|
||||
</ApmHeader>
|
||||
);
|
||||
}
|
|
@ -1,36 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import React, { ReactNode } from 'react';
|
||||
import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common';
|
||||
import { HeaderMenuPortal } from '../../../../../observability/public';
|
||||
import { ActionMenu } from '../../../application/action_menu';
|
||||
import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context';
|
||||
import { EnvironmentFilter } from '../EnvironmentFilter';
|
||||
|
||||
const HeaderFlexGroup = euiStyled(EuiFlexGroup)`
|
||||
padding: ${({ theme }) => theme.eui.gutterTypes.gutterMedium};
|
||||
background: ${({ theme }) => theme.eui.euiColorEmptyShade};
|
||||
border-bottom: ${({ theme }) => theme.eui.euiBorderThin};
|
||||
`;
|
||||
|
||||
export function ApmHeader({ children }: { children: ReactNode }) {
|
||||
const { setHeaderActionMenu } = useApmPluginContext().appMountParameters;
|
||||
|
||||
return (
|
||||
<HeaderFlexGroup alignItems="center" gutterSize="s" wrap={true}>
|
||||
<HeaderMenuPortal setHeaderActionMenu={setHeaderActionMenu}>
|
||||
<ActionMenu />
|
||||
</HeaderMenuPortal>
|
||||
<EuiFlexItem>{children}</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EnvironmentFilter />
|
||||
</EuiFlexItem>
|
||||
</HeaderFlexGroup>
|
||||
);
|
||||
}
|
|
@ -13,6 +13,7 @@ import { useHistory, useLocation, useParams } from 'react-router-dom';
|
|||
import {
|
||||
ENVIRONMENT_ALL,
|
||||
ENVIRONMENT_NOT_DEFINED,
|
||||
omitEsFieldValue,
|
||||
} from '../../../../common/environment_filter_values';
|
||||
import { useEnvironmentsFetcher } from '../../../hooks/use_environments_fetcher';
|
||||
import { useUrlParams } from '../../../context/url_params_context/use_url_params';
|
||||
|
@ -51,9 +52,9 @@ function getOptions(environments: string[]) {
|
|||
}));
|
||||
|
||||
return [
|
||||
ENVIRONMENT_ALL,
|
||||
omitEsFieldValue(ENVIRONMENT_ALL),
|
||||
...(environments.includes(ENVIRONMENT_NOT_DEFINED.value)
|
||||
? [ENVIRONMENT_NOT_DEFINED]
|
||||
? [omitEsFieldValue(ENVIRONMENT_NOT_DEFINED)]
|
||||
: []),
|
||||
...(environmentOptions.length > 0 ? [SEPARATOR_OPTION] : []),
|
||||
...environmentOptions,
|
||||
|
@ -78,12 +79,14 @@ export function EnvironmentFilter() {
|
|||
// the contents.
|
||||
const minWidth = 200;
|
||||
|
||||
const options = getOptions(environments);
|
||||
|
||||
return (
|
||||
<EuiSelect
|
||||
prepend={i18n.translate('xpack.apm.filter.environment.label', {
|
||||
defaultMessage: 'Environment',
|
||||
})}
|
||||
options={getOptions(environments)}
|
||||
options={options}
|
||||
value={environment || ENVIRONMENT_ALL.value}
|
||||
onChange={(event) => {
|
||||
updateEnvironmentUrl(history, location, event.target.value);
|
||||
|
|
|
@ -13,9 +13,9 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { useState } from 'react';
|
||||
import { IBasePath } from '../../../../../../src/core/public';
|
||||
import { AlertType } from '../../../common/alert_types';
|
||||
import { AlertingFlyout } from '../../components/alerting/alerting_flyout';
|
||||
import { IBasePath } from '../../../../../../../src/core/public';
|
||||
import { AlertType } from '../../../../common/alert_types';
|
||||
import { AlertingFlyout } from '../../alerting/alerting_flyout';
|
||||
|
||||
const alertLabel = i18n.translate('xpack.apm.home.alertsMenu.alerts', {
|
||||
defaultMessage: 'Alerts',
|
|
@ -8,8 +8,8 @@
|
|||
import React from 'react';
|
||||
import { render, fireEvent, waitFor } from '@testing-library/react';
|
||||
import { MissingJobsAlert } from './anomaly_detection_setup_link';
|
||||
import * as hooks from '../../context/anomaly_detection_jobs/use_anomaly_detection_jobs_context';
|
||||
import { FETCH_STATUS } from '../../hooks/use_fetcher';
|
||||
import * as hooks from '../../../context/anomaly_detection_jobs/use_anomaly_detection_jobs_context';
|
||||
import { FETCH_STATUS } from '../../../hooks/use_fetcher';
|
||||
|
||||
async function renderTooltipAnchor({
|
||||
jobs,
|
|
@ -16,15 +16,15 @@ import React from 'react';
|
|||
import {
|
||||
ENVIRONMENT_ALL,
|
||||
getEnvironmentLabel,
|
||||
} from '../../../common/environment_filter_values';
|
||||
import { getAPMHref } from '../../components/shared/Links/apm/APMLink';
|
||||
import { useAnomalyDetectionJobsContext } from '../../context/anomaly_detection_jobs/use_anomaly_detection_jobs_context';
|
||||
import { useApmPluginContext } from '../../context/apm_plugin/use_apm_plugin_context';
|
||||
import { useLicenseContext } from '../../context/license/use_license_context';
|
||||
import { useUrlParams } from '../../context/url_params_context/use_url_params';
|
||||
import { FETCH_STATUS } from '../../hooks/use_fetcher';
|
||||
import { APIReturnType } from '../../services/rest/createCallApmApi';
|
||||
import { units } from '../../style/variables';
|
||||
} from '../../../../common/environment_filter_values';
|
||||
import { getAPMHref } from '../Links/apm/APMLink';
|
||||
import { useAnomalyDetectionJobsContext } from '../../../context/anomaly_detection_jobs/use_anomaly_detection_jobs_context';
|
||||
import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context';
|
||||
import { useLicenseContext } from '../../../context/license/use_license_context';
|
||||
import { useUrlParams } from '../../../context/url_params_context/use_url_params';
|
||||
import { FETCH_STATUS } from '../../../hooks/use_fetcher';
|
||||
import { APIReturnType } from '../../../services/rest/createCallApmApi';
|
||||
import { units } from '../../../style/variables';
|
||||
|
||||
export type AnomalyDetectionApiResponse = APIReturnType<'GET /api/apm/settings/anomaly-detection/jobs'>;
|
||||
|
|
@ -9,13 +9,13 @@ import { EuiHeaderLink, EuiHeaderLinks } from '@elastic/eui';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { getAlertingCapabilities } from '../../components/alerting/get_alerting_capabilities';
|
||||
import { getAPMHref } from '../../components/shared/Links/apm/APMLink';
|
||||
import { useApmPluginContext } from '../../context/apm_plugin/use_apm_plugin_context';
|
||||
import { getAlertingCapabilities } from '../../alerting/get_alerting_capabilities';
|
||||
import { getAPMHref } from '../Links/apm/APMLink';
|
||||
import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context';
|
||||
import { AlertingPopoverAndFlyout } from './alerting_popover_flyout';
|
||||
import { AnomalyDetectionSetupLink } from './anomaly_detection_setup_link';
|
||||
|
||||
export function ActionMenu() {
|
||||
export function ApmHeaderActionMenu() {
|
||||
const { core, plugins } = useApmPluginContext();
|
||||
const { serviceName } = useParams<{ serviceName?: string }>();
|
||||
const { search } = window.location;
|
|
@ -1,24 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiTabs } from '@elastic/eui';
|
||||
import React, { ReactNode } from 'react';
|
||||
import { euiStyled } from '../../../../../../src/plugins/kibana_react/common';
|
||||
|
||||
// Since our `EuiTab` components have `APMLink`s inside of them and not just
|
||||
// `href`s, we need to override the color of the links inside or they will all
|
||||
// be the primary color.
|
||||
const StyledTabs = euiStyled(EuiTabs)`
|
||||
padding: ${({ theme }) => `${theme.eui.gutterTypes.gutterMedium}`};
|
||||
border-bottom: ${({ theme }) => theme.eui.euiBorderThin};
|
||||
border-top: ${({ theme }) => theme.eui.euiBorderThin};
|
||||
background: ${({ theme }) => theme.eui.euiColorEmptyShade};
|
||||
`;
|
||||
|
||||
export function MainTabs({ children }: { children: ReactNode }) {
|
||||
return <StyledTabs display="condensed">{children}</StyledTabs>;
|
||||
}
|
120
x-pack/plugins/apm/public/components/shared/search_bar.test.tsx
Normal file
120
x-pack/plugins/apm/public/components/shared/search_bar.test.tsx
Normal file
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { getByTestId, fireEvent, getByText } from '@testing-library/react';
|
||||
import { createMemoryHistory, MemoryHistory } from 'history';
|
||||
import React from 'react';
|
||||
import { Router } from 'react-router-dom';
|
||||
import { createKibanaReactContext } from 'src/plugins/kibana_react/public';
|
||||
import { MockApmPluginContextWrapper } from '../../context/apm_plugin/mock_apm_plugin_context';
|
||||
import { ApmServiceContextProvider } from '../../context/apm_service/apm_service_context';
|
||||
import { UrlParamsProvider } from '../../context/url_params_context/url_params_context';
|
||||
import { IUrlParams } from '../../context/url_params_context/types';
|
||||
import * as useFetcherHook from '../../hooks/use_fetcher';
|
||||
import * as useServiceTransactionTypesHook from '../../context/apm_service/use_service_transaction_types_fetcher';
|
||||
import { renderWithTheme } from '../../utils/testHelpers';
|
||||
import { fromQuery } from './Links/url_helpers';
|
||||
import { CoreStart } from 'kibana/public';
|
||||
import { SearchBar } from './search_bar';
|
||||
|
||||
function setup({
|
||||
urlParams,
|
||||
serviceTransactionTypes,
|
||||
history,
|
||||
}: {
|
||||
urlParams: IUrlParams;
|
||||
serviceTransactionTypes: string[];
|
||||
history: MemoryHistory;
|
||||
}) {
|
||||
history.replace({
|
||||
pathname: '/services/foo/transactions',
|
||||
search: fromQuery(urlParams),
|
||||
});
|
||||
|
||||
const KibanaReactContext = createKibanaReactContext({
|
||||
usageCollection: { reportUiCounter: () => {} },
|
||||
} as Partial<CoreStart>);
|
||||
|
||||
// mock transaction types
|
||||
jest
|
||||
.spyOn(useServiceTransactionTypesHook, 'useServiceTransactionTypesFetcher')
|
||||
.mockReturnValue(serviceTransactionTypes);
|
||||
|
||||
jest.spyOn(useFetcherHook, 'useFetcher').mockReturnValue({} as any);
|
||||
|
||||
return renderWithTheme(
|
||||
<KibanaReactContext.Provider>
|
||||
<MockApmPluginContextWrapper>
|
||||
<Router history={history}>
|
||||
<UrlParamsProvider>
|
||||
<ApmServiceContextProvider>
|
||||
<SearchBar showTransactionTypeSelector />
|
||||
</ApmServiceContextProvider>
|
||||
</UrlParamsProvider>
|
||||
</Router>
|
||||
</MockApmPluginContextWrapper>
|
||||
</KibanaReactContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
describe('when transactionType is selected and multiple transaction types are given', () => {
|
||||
let history: MemoryHistory;
|
||||
beforeEach(() => {
|
||||
history = createMemoryHistory();
|
||||
jest.spyOn(history, 'push');
|
||||
jest.spyOn(history, 'replace');
|
||||
});
|
||||
|
||||
it('renders a radio group with transaction types', () => {
|
||||
const { container } = setup({
|
||||
history,
|
||||
serviceTransactionTypes: ['firstType', 'secondType'],
|
||||
urlParams: {
|
||||
transactionType: 'secondType',
|
||||
},
|
||||
});
|
||||
|
||||
// transaction type selector
|
||||
const dropdown = getByTestId(container, 'headerFilterTransactionType');
|
||||
|
||||
// both options should be listed
|
||||
expect(getByText(dropdown, 'firstType')).toBeInTheDocument();
|
||||
expect(getByText(dropdown, 'secondType')).toBeInTheDocument();
|
||||
|
||||
// second option should be selected
|
||||
expect(dropdown).toHaveValue('secondType');
|
||||
});
|
||||
|
||||
it('should update the URL when a transaction type is selected', () => {
|
||||
const { container } = setup({
|
||||
history,
|
||||
serviceTransactionTypes: ['firstType', 'secondType'],
|
||||
urlParams: {
|
||||
transactionType: 'secondType',
|
||||
},
|
||||
});
|
||||
|
||||
expect(history.location.search).toEqual(
|
||||
'?transactionType=secondType&rangeFrom=now-15m&rangeTo=now'
|
||||
);
|
||||
|
||||
// transaction type selector
|
||||
const dropdown = getByTestId(container, 'headerFilterTransactionType');
|
||||
expect(getByText(dropdown, 'firstType')).toBeInTheDocument();
|
||||
expect(getByText(dropdown, 'secondType')).toBeInTheDocument();
|
||||
|
||||
// change dropdown value
|
||||
fireEvent.change(dropdown, { target: { value: 'firstType' } });
|
||||
|
||||
// assert that value was changed
|
||||
expect(dropdown).toHaveValue('firstType');
|
||||
expect(history.push).toHaveBeenCalled();
|
||||
expect(history.location.search).toEqual(
|
||||
'?transactionType=firstType&rangeFrom=now-15m&rangeTo=now'
|
||||
);
|
||||
});
|
||||
});
|
|
@ -15,7 +15,6 @@ import {
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import React from 'react';
|
||||
import { euiStyled } from '../../../../../../src/plugins/kibana_react/common';
|
||||
import { enableInspectEsQueries } from '../../../../observability/public';
|
||||
import { useApmPluginContext } from '../../context/apm_plugin/use_apm_plugin_context';
|
||||
import { useKibanaUrl } from '../../hooks/useKibanaUrl';
|
||||
|
@ -26,12 +25,9 @@ import { KueryBar } from './KueryBar';
|
|||
import { TimeComparison } from './time_comparison';
|
||||
import { TransactionTypeSelect } from './transaction_type_select';
|
||||
|
||||
const EuiFlexGroupSpaced = euiStyled(EuiFlexGroup)`
|
||||
margin: ${({ theme }) =>
|
||||
`${theme.eui.euiSizeS} ${theme.eui.euiSizeS} -${theme.eui.gutterTypes.gutterMedium} ${theme.eui.euiSizeS}`};
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
hidden?: boolean;
|
||||
showKueryBar?: boolean;
|
||||
showTimeComparison?: boolean;
|
||||
showTransactionTypeSelector?: boolean;
|
||||
}
|
||||
|
@ -49,7 +45,7 @@ function DebugQueryCallout() {
|
|||
}
|
||||
|
||||
return (
|
||||
<EuiFlexGroupSpaced>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<EuiCallOut
|
||||
title={i18n.translate(
|
||||
|
@ -78,19 +74,26 @@ function DebugQueryCallout() {
|
|||
/>
|
||||
</EuiCallOut>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroupSpaced>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
||||
export function SearchBar({
|
||||
hidden = false,
|
||||
showKueryBar = true,
|
||||
showTimeComparison = false,
|
||||
showTransactionTypeSelector = false,
|
||||
}: Props) {
|
||||
const { isSmall, isMedium, isLarge, isXl, isXXL } = useBreakPoints();
|
||||
|
||||
if (hidden) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<DebugQueryCallout />
|
||||
<EuiFlexGroupSpaced
|
||||
<EuiFlexGroup
|
||||
gutterSize="s"
|
||||
responsive={false}
|
||||
direction={isXXL ? 'row' : 'column'}
|
||||
|
@ -106,9 +109,12 @@ export function SearchBar({
|
|||
<TransactionTypeSelect />
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
<EuiFlexItem>
|
||||
<KueryBar />
|
||||
</EuiFlexItem>
|
||||
|
||||
{showKueryBar && (
|
||||
<EuiFlexItem>
|
||||
<KueryBar />
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={showTimeComparison && !isXXL}>
|
||||
|
@ -128,7 +134,7 @@ export function SearchBar({
|
|||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroupSpaced>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="s" />
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -14,12 +14,12 @@ import {
|
|||
RULE_ID,
|
||||
RULE_NAME,
|
||||
} from '@kbn/rule-data-utils/target/technical_field_names';
|
||||
import { parseTechnicalFields } from '../../../../../../rule_registry/common';
|
||||
import { useUrlParams } from '../../../../context/url_params_context/use_url_params';
|
||||
import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context';
|
||||
import { APIReturnType } from '../../../../services/rest/createCallApmApi';
|
||||
import { asPercent, asDuration } from '../../../../../common/utils/formatters';
|
||||
import { TimestampTooltip } from '../../../shared/TimestampTooltip';
|
||||
import { parseTechnicalFields } from '../../../../../rule_registry/common';
|
||||
import { useUrlParams } from '../../../context/url_params_context/use_url_params';
|
||||
import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context';
|
||||
import { APIReturnType } from '../../../services/rest/createCallApmApi';
|
||||
import { asPercent, asDuration } from '../../../../common/utils/formatters';
|
||||
import { TimestampTooltip } from '../TimestampTooltip';
|
||||
|
||||
interface AlertDetailProps {
|
||||
alerts: APIReturnType<'GET /api/apm/services/{serviceName}/alerts'>['alerts'];
|
|
@ -9,7 +9,7 @@ import { EuiBadge, EuiDescriptionList } from '@elastic/eui';
|
|||
import { EuiDescriptionListProps } from '@elastic/eui/src/components/description_list/description_list';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
import { APIReturnType } from '../../../../services/rest/createCallApmApi';
|
||||
import { APIReturnType } from '../../../services/rest/createCallApmApi';
|
||||
|
||||
type ServiceDetailsReturnType = APIReturnType<'GET /api/apm/services/{serviceName}/metadata/details'>;
|
||||
|
|
@ -9,8 +9,8 @@ import { EuiDescriptionList } from '@elastic/eui';
|
|||
import { EuiDescriptionListProps } from '@elastic/eui/src/components/description_list/description_list';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
import { asInteger } from '../../../../../common/utils/formatters';
|
||||
import { APIReturnType } from '../../../../services/rest/createCallApmApi';
|
||||
import { asInteger } from '../../../../common/utils/formatters';
|
||||
import { APIReturnType } from '../../../services/rest/createCallApmApi';
|
||||
|
||||
type ServiceDetailsReturnType = APIReturnType<'GET /api/apm/services/{serviceName}/metadata/details'>;
|
||||
|
|
@ -13,8 +13,8 @@ import {
|
|||
EuiPopoverTitle,
|
||||
} from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { FETCH_STATUS } from '../../../../hooks/use_fetcher';
|
||||
import { px } from '../../../../style/variables';
|
||||
import { FETCH_STATUS } from '../../../hooks/use_fetcher';
|
||||
import { px } from '../../../style/variables';
|
||||
|
||||
interface IconPopoverProps {
|
||||
title: string;
|
|
@ -11,14 +11,14 @@ import { merge } from 'lodash';
|
|||
// import { renderWithTheme } from '../../../../utils/testHelpers';
|
||||
import React, { ReactNode } from 'react';
|
||||
import { createKibanaReactContext } from 'src/plugins/kibana_react/public';
|
||||
import { MockUrlParamsContextProvider } from '../../../../context/url_params_context/mock_url_params_context_provider';
|
||||
import { ApmPluginContextValue } from '../../../../context/apm_plugin/apm_plugin_context';
|
||||
import { MockUrlParamsContextProvider } from '../../../context/url_params_context/mock_url_params_context_provider';
|
||||
import { ApmPluginContextValue } from '../../../context/apm_plugin/apm_plugin_context';
|
||||
import {
|
||||
mockApmPluginContextValue,
|
||||
MockApmPluginContextWrapper,
|
||||
} from '../../../../context/apm_plugin/mock_apm_plugin_context';
|
||||
import * as fetcherHook from '../../../../hooks/use_fetcher';
|
||||
import { ServiceIcons } from './';
|
||||
} from '../../../context/apm_plugin/mock_apm_plugin_context';
|
||||
import * as fetcherHook from '../../../hooks/use_fetcher';
|
||||
import { ServiceIcons } from '.';
|
||||
import { EuiThemeProvider } from 'src/plugins/kibana_react/common';
|
||||
|
||||
const KibanaReactContext = createKibanaReactContext({
|
|
@ -8,12 +8,12 @@
|
|||
import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { ReactChild, useState } from 'react';
|
||||
import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context';
|
||||
import { useTheme } from '../../../../hooks/use_theme';
|
||||
import { ContainerType } from '../../../../../common/service_metadata';
|
||||
import { useUrlParams } from '../../../../context/url_params_context/use_url_params';
|
||||
import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher';
|
||||
import { getAgentIcon } from '../../../shared/AgentIcon/get_agent_icon';
|
||||
import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context';
|
||||
import { useTheme } from '../../../hooks/use_theme';
|
||||
import { ContainerType } from '../../../../common/service_metadata';
|
||||
import { useUrlParams } from '../../../context/url_params_context/use_url_params';
|
||||
import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher';
|
||||
import { getAgentIcon } from '../AgentIcon/get_agent_icon';
|
||||
import { CloudDetails } from './cloud_details';
|
||||
import { ContainerDetails } from './container_details';
|
||||
import { IconPopover } from './icon_popover';
|
|
@ -9,7 +9,7 @@ import { EuiDescriptionList } from '@elastic/eui';
|
|||
import { EuiDescriptionListProps } from '@elastic/eui/src/components/description_list/description_list';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
import { APIReturnType } from '../../../../services/rest/createCallApmApi';
|
||||
import { APIReturnType } from '../../../services/rest/createCallApmApi';
|
||||
|
||||
type ServiceDetailsReturnType = APIReturnType<'GET /api/apm/services/{serviceName}/metadata/details'>;
|
||||
|
|
@ -9,7 +9,7 @@ import { renderHook } from '@testing-library/react-hooks';
|
|||
import produce from 'immer';
|
||||
import React, { ReactNode } from 'react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { routes } from '../components/app/Main/route_config';
|
||||
import { apmRouteConfig } from '../components/routing/apm_route_config';
|
||||
import { ApmPluginContextValue } from '../context/apm_plugin/apm_plugin_context';
|
||||
import {
|
||||
mockApmPluginContextValue,
|
||||
|
@ -36,7 +36,9 @@ function createWrapper(path: string) {
|
|||
}
|
||||
|
||||
function mountBreadcrumb(path: string) {
|
||||
renderHook(() => useBreadcrumbs(routes), { wrapper: createWrapper(path) });
|
||||
renderHook(() => useBreadcrumbs(apmRouteConfig), {
|
||||
wrapper: createWrapper(path),
|
||||
});
|
||||
}
|
||||
|
||||
const changeTitle = jest.fn();
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { of } from 'rxjs';
|
||||
import type { ConfigSchema } from '.';
|
||||
import {
|
||||
AppMountParameters,
|
||||
|
@ -34,6 +35,7 @@ import type {
|
|||
FetchDataParams,
|
||||
HasDataParams,
|
||||
ObservabilityPublicSetup,
|
||||
ObservabilityPublicStart,
|
||||
} from '../../observability/public';
|
||||
import type {
|
||||
TriggersAndActionsUIPublicPluginSetup,
|
||||
|
@ -48,24 +50,25 @@ export type ApmPluginStart = void;
|
|||
|
||||
export interface ApmPluginSetupDeps {
|
||||
alerting?: AlertingPluginPublicSetup;
|
||||
ml?: MlPluginSetup;
|
||||
data: DataPublicPluginSetup;
|
||||
features: FeaturesPluginSetup;
|
||||
home?: HomePublicPluginSetup;
|
||||
licensing: LicensingPluginSetup;
|
||||
triggersActionsUi: TriggersAndActionsUIPublicPluginSetup;
|
||||
ml?: MlPluginSetup;
|
||||
observability: ObservabilityPublicSetup;
|
||||
triggersActionsUi: TriggersAndActionsUIPublicPluginSetup;
|
||||
}
|
||||
|
||||
export interface ApmPluginStartDeps {
|
||||
alerting?: AlertingPluginPublicStart;
|
||||
ml?: MlPluginStart;
|
||||
data: DataPublicPluginStart;
|
||||
embeddable: EmbeddableStart;
|
||||
home: void;
|
||||
licensing: void;
|
||||
triggersActionsUi: TriggersAndActionsUIPublicPluginStart;
|
||||
embeddable: EmbeddableStart;
|
||||
maps?: MapsStartApi;
|
||||
ml?: MlPluginStart;
|
||||
observability: ObservabilityPublicStart;
|
||||
triggersActionsUi: TriggersAndActionsUIPublicPluginStart;
|
||||
}
|
||||
|
||||
export class ApmPlugin implements Plugin<ApmPluginSetup, ApmPluginStart> {
|
||||
|
@ -83,6 +86,21 @@ export class ApmPlugin implements Plugin<ApmPluginSetup, ApmPluginStart> {
|
|||
pluginSetupDeps.home.featureCatalogue.register(featureCatalogueEntry);
|
||||
}
|
||||
|
||||
// register observability nav
|
||||
plugins.observability.navigation.registerSections(
|
||||
of([
|
||||
{
|
||||
label: 'APM',
|
||||
sortKey: 200,
|
||||
entries: [
|
||||
{ label: 'Services', app: 'apm', path: '/services' },
|
||||
{ label: 'Traces', app: 'apm', path: '/traces' },
|
||||
{ label: 'Service Map', app: 'apm', path: '/service-map' },
|
||||
],
|
||||
},
|
||||
])
|
||||
);
|
||||
|
||||
const getApmDataHelper = async () => {
|
||||
const {
|
||||
fetchObservabilityOverviewPageData,
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue