mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[User Experience App] Move UX public code out of apm (#88645)
This commit is contained in:
parent
8989ead2d6
commit
e445280fe6
138 changed files with 2926 additions and 971 deletions
|
@ -853,12 +853,13 @@ module.exports = {
|
|||
},
|
||||
|
||||
/**
|
||||
* APM and Observability overrides
|
||||
* APM, UX and Observability overrides
|
||||
*/
|
||||
{
|
||||
files: [
|
||||
'x-pack/plugins/apm/**/*.{js,mjs,ts,tsx}',
|
||||
'x-pack/plugins/observability/**/*.{js,mjs,ts,tsx}',
|
||||
'x-pack/plugins/ux/**/*.{js,mjs,ts,tsx}',
|
||||
],
|
||||
rules: {
|
||||
'no-console': ['warn', { allow: ['error'] }],
|
||||
|
|
3
.github/CODEOWNERS
vendored
3
.github/CODEOWNERS
vendored
|
@ -96,6 +96,7 @@
|
|||
|
||||
# Observability Shared
|
||||
/x-pack/plugins/observability/ @elastic/observability-ui
|
||||
/x-pack/plugins/observability/public/components/shared/date_picker/ @elastic/uptime
|
||||
|
||||
# Unified Observability
|
||||
/x-pack/plugins/observability/public/components/shared/exploratory_view @elastic/unified-observability
|
||||
|
@ -136,6 +137,8 @@
|
|||
|
||||
# Uptime
|
||||
/x-pack/plugins/uptime @elastic/uptime
|
||||
/x-pack/plugins/ux @elastic/uptime
|
||||
/x-pack/plugins/observability/public/components/shared/exploratory_view @elastic/uptime
|
||||
/x-pack/test/functional_with_es_ssl/apps/uptime @elastic/uptime
|
||||
/x-pack/test/functional/apps/uptime @elastic/uptime
|
||||
/x-pack/test/functional/es_archives/uptime @elastic/uptime
|
||||
|
|
|
@ -635,6 +635,10 @@ in their infrastructure.
|
|||
|NOTE: This plugin contains implementation of URL drilldown. For drilldowns infrastructure code refer to ui_actions_enhanced plugin.
|
||||
|
||||
|
||||
|{kib-repo}blob/{branch}/x-pack/plugins/ux/readme.md[ux]
|
||||
|https://docs.elastic.dev/kibana-dev-docs/welcome
|
||||
|
||||
|
||||
|{kib-repo}blob/{branch}/x-pack/plugins/watcher/README.md[watcher]
|
||||
|This plugins adopts some conventions in addition to or in place of conventions in Kibana (at the time of the plugin's creation):
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ module.exports = {
|
|||
USES_STYLED_COMPONENTS: [
|
||||
/packages[\/\\]kbn-ui-shared-deps-(npm|src)[\/\\]/,
|
||||
/src[\/\\]plugins[\/\\](data|kibana_react)[\/\\]/,
|
||||
/x-pack[\/\\]plugins[\/\\](apm|beats_management|cases|fleet|infra|lists|observability|osquery|security_solution|timelines|uptime)[\/\\]/,
|
||||
/x-pack[\/\\]plugins[\/\\](apm|beats_management|cases|fleet|infra|lists|observability|osquery|security_solution|timelines|uptime|ux)[\/\\]/,
|
||||
/x-pack[\/\\]test[\/\\]plugin_functional[\/\\]plugins[\/\\]resolver_test[\/\\]/,
|
||||
],
|
||||
};
|
||||
|
|
|
@ -121,3 +121,4 @@ pageLoadAssetSize:
|
|||
controls: 34788
|
||||
expressionPie: 26338
|
||||
sharedUX: 16225
|
||||
ux: 20784
|
||||
|
|
|
@ -60,6 +60,7 @@
|
|||
"xpack.triggersActionsUI": "plugins/triggers_actions_ui",
|
||||
"xpack.upgradeAssistant": "plugins/upgrade_assistant",
|
||||
"xpack.uptime": ["plugins/uptime"],
|
||||
"xpack.ux": ["plugins/ux"],
|
||||
"xpack.urlDrilldown": "plugins/drilldowns/url_drilldown",
|
||||
"xpack.watcher": "plugins/watcher",
|
||||
"xpack.observability": "plugins/observability",
|
||||
|
|
|
@ -118,3 +118,9 @@ export const uxLocalUIFilters = uxLocalUIFilterNames.reduce((acc, key) => {
|
|||
},
|
||||
};
|
||||
}, {} as UxLocalUIFilterMap);
|
||||
|
||||
export type UxUIFilters = {
|
||||
environment?: string;
|
||||
} & {
|
||||
[key in UxLocalUIFilterName]?: string[];
|
||||
};
|
||||
|
|
|
@ -24,7 +24,6 @@
|
|||
"cloud",
|
||||
"fleet",
|
||||
"home",
|
||||
"maps",
|
||||
"ml",
|
||||
"security",
|
||||
"spaces",
|
||||
|
@ -39,7 +38,6 @@
|
|||
"home",
|
||||
"kibanaReact",
|
||||
"kibanaUtils",
|
||||
"maps",
|
||||
"ml",
|
||||
"observability"
|
||||
]
|
||||
|
|
|
@ -7,29 +7,21 @@
|
|||
|
||||
import React from 'react';
|
||||
import { act } from '@testing-library/react';
|
||||
import { EuiErrorBoundary } from '@elastic/eui';
|
||||
import { mount } from 'enzyme';
|
||||
import { createMemoryHistory } from 'history';
|
||||
import { Observable } from 'rxjs';
|
||||
import { AppMountParameters, DocLinksStart, HttpStart } from 'src/core/public';
|
||||
import { mockApmPluginContextValue } from '../context/apm_plugin/mock_apm_plugin_context';
|
||||
import { createCallApmApi } from '../services/rest/create_call_apm_api';
|
||||
import { renderApp as renderApmApp } from './';
|
||||
import { UXAppRoot } from './ux_app';
|
||||
import { disableConsoleWarning } from '../utils/test_helpers';
|
||||
import { dataPluginMock } from 'src/plugins/data/public/mocks';
|
||||
import { embeddablePluginMock } from 'src/plugins/embeddable/public/mocks';
|
||||
import { ApmPluginSetupDeps, ApmPluginStartDeps } from '../plugin';
|
||||
import { RumHome } from '../components/app/rum_dashboard/rum_home';
|
||||
|
||||
jest.mock('../services/rest/data_view', () => ({
|
||||
createStaticDataView: () => Promise.resolve(undefined),
|
||||
}));
|
||||
|
||||
jest.mock('../components/app/rum_dashboard/rum_home', () => ({
|
||||
RumHome: () => <p>Home Mock</p>,
|
||||
}));
|
||||
|
||||
describe('renderApp (APM)', () => {
|
||||
let mockConsole: jest.SpyInstance;
|
||||
beforeAll(() => {
|
||||
|
@ -148,21 +140,3 @@ describe('renderApp (APM)', () => {
|
|||
}).not.toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('renderUxApp', () => {
|
||||
it('has an error boundary for the UXAppRoot', async () => {
|
||||
const uxMountProps = mockApmPluginContextValue;
|
||||
|
||||
const wrapper = mount(<UXAppRoot {...(uxMountProps as any)} />);
|
||||
|
||||
wrapper
|
||||
.find(RumHome)
|
||||
.simulateError(new Error('Oh no, an unexpected error!'));
|
||||
|
||||
expect(wrapper.find(RumHome)).toHaveLength(0);
|
||||
expect(wrapper.find(EuiErrorBoundary)).toHaveLength(1);
|
||||
expect(wrapper.find(EuiErrorBoundary).text()).toMatch(
|
||||
/Error: Oh no, an unexpected error!/
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,19 +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 { useMemo } from 'react';
|
||||
import { callApi } from '../../../../services/rest/call_api';
|
||||
import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context';
|
||||
import { FetchOptions } from '../../../../../common/fetch_options';
|
||||
|
||||
export function useCallApi() {
|
||||
const { core } = useApmPluginContext();
|
||||
|
||||
return useMemo(() => {
|
||||
return <T = void>(options: FetchOptions) => callApi<T>(core, options);
|
||||
}, [core]);
|
||||
}
|
|
@ -1,44 +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 React from 'react';
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import * as dynamicDataView from '../../../../hooks/use_dynamic_data_view';
|
||||
import { useDataView } from './use_data_view';
|
||||
import { MockApmPluginContextWrapper } from '../../../../context/apm_plugin/mock_apm_plugin_context';
|
||||
import { KibanaContextProvider } from '../../../../../../../../src/plugins/kibana_react/public';
|
||||
|
||||
describe('useDataView', () => {
|
||||
const create = jest.fn();
|
||||
const mockDataService = {
|
||||
data: {
|
||||
dataViews: {
|
||||
create,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const title = 'apm-*';
|
||||
jest
|
||||
.spyOn(dynamicDataView, 'useDynamicDataViewFetcher')
|
||||
.mockReturnValue({ dataView: { title } });
|
||||
|
||||
it('returns result as expected', async () => {
|
||||
const { waitForNextUpdate } = renderHook(() => useDataView(), {
|
||||
wrapper: ({ children }) => (
|
||||
<MockApmPluginContextWrapper>
|
||||
<KibanaContextProvider services={mockDataService}>
|
||||
{children}
|
||||
</KibanaContextProvider>
|
||||
</MockApmPluginContextWrapper>
|
||||
),
|
||||
});
|
||||
|
||||
await waitForNextUpdate();
|
||||
|
||||
expect(create).toBeCalledWith({ title });
|
||||
});
|
||||
});
|
|
@ -1,188 +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';
|
||||
|
||||
export const I18LABELS = {
|
||||
dataMissing: i18n.translate('xpack.apm.rum.dashboard.dataMissing', {
|
||||
defaultMessage: 'N/A',
|
||||
}),
|
||||
totalPageLoad: i18n.translate('xpack.apm.rum.dashboard.totalPageLoad', {
|
||||
defaultMessage: 'Total',
|
||||
}),
|
||||
backEnd: i18n.translate('xpack.apm.rum.dashboard.backend', {
|
||||
defaultMessage: 'Backend',
|
||||
}),
|
||||
frontEnd: i18n.translate('xpack.apm.rum.dashboard.frontend', {
|
||||
defaultMessage: 'Frontend',
|
||||
}),
|
||||
pageViews: i18n.translate('xpack.apm.rum.dashboard.pageViews', {
|
||||
defaultMessage: 'Total page views',
|
||||
}),
|
||||
percPageLoaded: i18n.translate('xpack.apm.rum.dashboard.pagesLoaded.label', {
|
||||
defaultMessage: 'Pages loaded',
|
||||
}),
|
||||
pageLoadTime: i18n.translate('xpack.apm.rum.dashboard.pageLoadTime.label', {
|
||||
defaultMessage: 'Page load time (seconds)',
|
||||
}),
|
||||
pageLoadTimes: i18n.translate('xpack.apm.rum.dashboard.pageLoadTimes.label', {
|
||||
defaultMessage: 'Page load times',
|
||||
}),
|
||||
pageLoadDuration: i18n.translate(
|
||||
'xpack.apm.rum.dashboard.pageLoadDuration.label',
|
||||
{
|
||||
defaultMessage: 'Page load duration',
|
||||
}
|
||||
),
|
||||
pageLoad: i18n.translate('xpack.apm.rum.dashboard.pageLoad.label', {
|
||||
defaultMessage: 'Page load',
|
||||
}),
|
||||
pageLoadDistribution: i18n.translate(
|
||||
'xpack.apm.rum.dashboard.pageLoadDistribution.label',
|
||||
{
|
||||
defaultMessage: 'Page load distribution',
|
||||
}
|
||||
),
|
||||
jsErrors: i18n.translate(
|
||||
'xpack.apm.rum.dashboard.impactfulMetrics.jsErrors',
|
||||
{
|
||||
defaultMessage: 'JavaScript errors',
|
||||
}
|
||||
),
|
||||
highTrafficPages: i18n.translate(
|
||||
'xpack.apm.rum.dashboard.impactfulMetrics.highTrafficPages',
|
||||
{
|
||||
defaultMessage: 'High traffic pages',
|
||||
}
|
||||
),
|
||||
resetZoom: i18n.translate('xpack.apm.rum.dashboard.resetZoom.label', {
|
||||
defaultMessage: 'Reset zoom',
|
||||
}),
|
||||
overall: i18n.translate('xpack.apm.rum.dashboard.overall.label', {
|
||||
defaultMessage: 'Overall',
|
||||
}),
|
||||
selectBreakdown: i18n.translate('xpack.apm.rum.filterGroup.selectBreakdown', {
|
||||
defaultMessage: 'Select breakdown',
|
||||
}),
|
||||
breakdown: i18n.translate('xpack.apm.rum.filterGroup.breakdown', {
|
||||
defaultMessage: 'Breakdown',
|
||||
}),
|
||||
seconds: i18n.translate('xpack.apm.rum.filterGroup.seconds', {
|
||||
defaultMessage: 'seconds',
|
||||
}),
|
||||
coreWebVitals: i18n.translate('xpack.apm.rum.filterGroup.coreWebVitals', {
|
||||
defaultMessage: 'Core web vitals',
|
||||
}),
|
||||
browser: i18n.translate('xpack.apm.rum.visitorBreakdown.browser', {
|
||||
defaultMessage: 'Browser',
|
||||
}),
|
||||
operatingSystem: i18n.translate(
|
||||
'xpack.apm.rum.visitorBreakdown.operatingSystem',
|
||||
{
|
||||
defaultMessage: 'Operating system',
|
||||
}
|
||||
),
|
||||
metrics: i18n.translate('xpack.apm.ux.metrics', {
|
||||
defaultMessage: 'Metrics',
|
||||
}),
|
||||
median: i18n.translate('xpack.apm.ux.median', {
|
||||
defaultMessage: 'median',
|
||||
}),
|
||||
avgPageLoadDuration: i18n.translate(
|
||||
'xpack.apm.rum.visitorBreakdownMap.avgPageLoadDuration',
|
||||
{
|
||||
defaultMessage: 'Average page load duration',
|
||||
}
|
||||
),
|
||||
pageLoadDurationByRegion: i18n.translate(
|
||||
'xpack.apm.rum.visitorBreakdownMap.pageLoadDurationByRegion',
|
||||
{
|
||||
defaultMessage: 'Page load duration by region (avg.)',
|
||||
}
|
||||
),
|
||||
filterByUrl: i18n.translate('xpack.apm.rum.filters.filterByUrl', {
|
||||
defaultMessage: 'Filter by URL',
|
||||
}),
|
||||
getSearchResultsLabel: (total: number) =>
|
||||
i18n.translate('xpack.apm.rum.filters.searchResults', {
|
||||
defaultMessage: '{total} Search results',
|
||||
values: { total },
|
||||
}),
|
||||
topPages: i18n.translate('xpack.apm.rum.filters.topPages', {
|
||||
defaultMessage: 'Top pages',
|
||||
}),
|
||||
select: i18n.translate('xpack.apm.rum.filters.select', {
|
||||
defaultMessage: 'Select',
|
||||
}),
|
||||
url: i18n.translate('xpack.apm.rum.filters.url', {
|
||||
defaultMessage: 'Url',
|
||||
}),
|
||||
loadingResults: i18n.translate('xpack.apm.rum.filters.url.loadingResults', {
|
||||
defaultMessage: 'Loading results',
|
||||
}),
|
||||
noResults: i18n.translate('xpack.apm.rum.filters.url.noResults', {
|
||||
defaultMessage: 'No results available',
|
||||
}),
|
||||
totalErrors: i18n.translate('xpack.apm.rum.jsErrors.totalErrors', {
|
||||
defaultMessage: 'Total errors',
|
||||
}),
|
||||
errorRate: i18n.translate('xpack.apm.rum.jsErrors.errorRate', {
|
||||
defaultMessage: 'Error rate',
|
||||
}),
|
||||
errorMessage: i18n.translate('xpack.apm.rum.jsErrors.errorMessage', {
|
||||
defaultMessage: 'Error message',
|
||||
}),
|
||||
impactedPageLoads: i18n.translate(
|
||||
'xpack.apm.rum.jsErrors.impactedPageLoads',
|
||||
{
|
||||
defaultMessage: 'Impacted page loads',
|
||||
}
|
||||
),
|
||||
percentile: i18n.translate('xpack.apm.ux.percentile.label', {
|
||||
defaultMessage: 'Percentile',
|
||||
}),
|
||||
percentile50thMedian: i18n.translate('xpack.apm.ux.percentile.50thMedian', {
|
||||
defaultMessage: '50th (Median)',
|
||||
}),
|
||||
percentile75th: i18n.translate('xpack.apm.ux.percentile.75th', {
|
||||
defaultMessage: '75th',
|
||||
}),
|
||||
percentile90th: i18n.translate('xpack.apm.ux.percentile.90th', {
|
||||
defaultMessage: '90th',
|
||||
}),
|
||||
percentile95th: i18n.translate('xpack.apm.ux.percentile.95th', {
|
||||
defaultMessage: '95th',
|
||||
}),
|
||||
percentile99th: i18n.translate('xpack.apm.ux.percentile.99th', {
|
||||
defaultMessage: '99th',
|
||||
}),
|
||||
noData: i18n.translate('xpack.apm.ux.visitorBreakdown.noData', {
|
||||
defaultMessage: 'No data.',
|
||||
}),
|
||||
// Helper tooltips
|
||||
totalPageLoadTooltip: i18n.translate(
|
||||
'xpack.apm.rum.dashboard.tooltips.totalPageLoad',
|
||||
{
|
||||
defaultMessage: 'Total represents the full page load duration',
|
||||
}
|
||||
),
|
||||
frontEndTooltip: i18n.translate('xpack.apm.rum.dashboard.tooltips.frontEnd', {
|
||||
defaultMessage:
|
||||
'Frontend time represents the total page load duration minus the backend time',
|
||||
}),
|
||||
backEndTooltip: i18n.translate('xpack.apm.rum.dashboard.tooltips.backEnd', {
|
||||
defaultMessage:
|
||||
'Backend time represents time to first byte (TTFB), which is when the first response packet is received after the request has been made',
|
||||
}),
|
||||
};
|
||||
|
||||
export const VisitorBreakdownLabel = i18n.translate(
|
||||
'xpack.apm.rum.visitorBreakdown',
|
||||
{
|
||||
defaultMessage: 'Visitor breakdown',
|
||||
}
|
||||
);
|
|
@ -14,9 +14,7 @@ import {
|
|||
ENVIRONMENT_ALL,
|
||||
ENVIRONMENT_NOT_DEFINED,
|
||||
} from '../../../../common/environment_filter_values';
|
||||
import { useEnvironmentsFetcher } from '../../../hooks/use_environments_fetcher';
|
||||
import { fromQuery, toQuery } from '../links/url_helpers';
|
||||
import { useUxUrlParams } from '../../../context/url_params_context/use_ux_url_params';
|
||||
import { FETCH_STATUS } from '../../../hooks/use_fetcher';
|
||||
import { Environment } from '../../../../common/environment_rt';
|
||||
import { useEnvironmentsContext } from '../../../context/environments_context/use_environments_context';
|
||||
|
@ -74,26 +72,6 @@ export function ApmEnvironmentFilter() {
|
|||
);
|
||||
}
|
||||
|
||||
export function UxEnvironmentFilter() {
|
||||
const {
|
||||
urlParams: { start, end, environment, serviceName },
|
||||
} = useUxUrlParams();
|
||||
|
||||
const { environments, status } = useEnvironmentsFetcher({
|
||||
serviceName,
|
||||
start,
|
||||
end,
|
||||
});
|
||||
|
||||
return (
|
||||
<EnvironmentFilter
|
||||
environment={(environment || ENVIRONMENT_ALL.value) as Environment}
|
||||
status={status}
|
||||
environments={environments as Environment[]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function EnvironmentFilter({
|
||||
environment,
|
||||
environments,
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
import { History } from 'history';
|
||||
import { parse, stringify } from 'query-string';
|
||||
import { url } from '../../../../../../../src/plugins/kibana_utils/public';
|
||||
import { UxLocalUIFilterName } from '../../../../common/ux_ui_filter';
|
||||
|
||||
export function toQuery(search?: string): APMQueryParamsRaw {
|
||||
return search ? parse(search.slice(1), { sort: false }) : {};
|
||||
|
@ -64,7 +63,7 @@ export function createHref(
|
|||
return history.createHref(location);
|
||||
}
|
||||
|
||||
export type APMQueryParams = {
|
||||
export interface APMQueryParams {
|
||||
sampleRangeFrom?: number;
|
||||
sampleRangeTo?: number;
|
||||
transactionId?: string;
|
||||
|
@ -96,7 +95,7 @@ export type APMQueryParams = {
|
|||
podName?: string;
|
||||
agentName?: string;
|
||||
serviceVersion?: string;
|
||||
} & { [key in UxLocalUIFilterName]?: string };
|
||||
}
|
||||
|
||||
// forces every value of T[K] to be type: string
|
||||
type StringifyAll<T> = { [K in keyof T]: string };
|
||||
|
|
|
@ -35,7 +35,6 @@ export function MockUrlParamsContextProvider({
|
|||
rangeId: 0,
|
||||
refreshTimeRange,
|
||||
urlParams,
|
||||
uxUiFilters: {},
|
||||
}}
|
||||
children={children}
|
||||
/>
|
||||
|
|
|
@ -7,10 +7,8 @@
|
|||
|
||||
import { Location } from 'history';
|
||||
import { TimeRangeComparisonType } from '../../../common/runtime_types/comparison_type_rt';
|
||||
import { uxLocalUIFilterNames } from '../../../common/ux_ui_filter';
|
||||
import { ENVIRONMENT_ALL } from '../../../common/environment_filter_values';
|
||||
import { LatencyAggregationType } from '../../../common/latency_aggregation_types';
|
||||
import { pickKeys } from '../../../common/utils/pick_keys';
|
||||
import { toQuery } from '../../components/shared/links/url_helpers';
|
||||
import {
|
||||
getDateRange,
|
||||
|
@ -57,8 +55,6 @@ export function resolveUrlParams(location: Location, state: TimeUrlParams) {
|
|||
comparisonType,
|
||||
} = query;
|
||||
|
||||
const localUIFilters = pickKeys(query, ...uxLocalUIFilterNames);
|
||||
|
||||
return removeUndefinedProps({
|
||||
// date params
|
||||
...getDateRange({ state, rangeFrom, rangeTo }),
|
||||
|
@ -91,7 +87,5 @@ export function resolveUrlParams(location: Location, state: TimeUrlParams) {
|
|||
? toBoolean(comparisonEnabled)
|
||||
: undefined,
|
||||
comparisonType: comparisonType as TimeRangeComparisonType | undefined,
|
||||
// ui filters
|
||||
...localUIFilters,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -7,9 +7,8 @@
|
|||
|
||||
import { TimeRangeComparisonType } from '../../../common/runtime_types/comparison_type_rt';
|
||||
import { LatencyAggregationType } from '../../../common/latency_aggregation_types';
|
||||
import { UxLocalUIFilterName } from '../../../common/ux_ui_filter';
|
||||
|
||||
export type UrlParams = {
|
||||
export interface UrlParams {
|
||||
detailTab?: string;
|
||||
end?: string;
|
||||
flyoutDetailTab?: string;
|
||||
|
@ -38,7 +37,7 @@ export type UrlParams = {
|
|||
latencyAggregationType?: LatencyAggregationType;
|
||||
comparisonEnabled?: boolean;
|
||||
comparisonType?: TimeRangeComparisonType;
|
||||
} & Partial<Record<UxLocalUIFilterName, string>>;
|
||||
}
|
||||
|
||||
export type UxUrlParams = UrlParams;
|
||||
export type ApmUrlParams = Omit<UrlParams, 'environment' | 'kuery'>;
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { mapValues } from 'lodash';
|
||||
import React, {
|
||||
createContext,
|
||||
useCallback,
|
||||
|
@ -14,13 +13,6 @@ import React, {
|
|||
useState,
|
||||
} from 'react';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import {
|
||||
UxLocalUIFilterName,
|
||||
uxLocalUIFilterNames,
|
||||
} from '../../../common/ux_ui_filter';
|
||||
import { pickKeys } from '../../../common/utils/pick_keys';
|
||||
import { UxUIFilters } from '../../../typings/ui_filters';
|
||||
import { useDeepObjectIdentity } from '../../hooks/use_deep_object_identity';
|
||||
import { getDateRange } from './helpers';
|
||||
import { resolveUrlParams } from './resolve_url_params';
|
||||
import { UrlParams } from './types';
|
||||
|
@ -30,24 +22,11 @@ export interface TimeRange {
|
|||
rangeTo: string;
|
||||
}
|
||||
|
||||
function useUxUiFilters(params: UrlParams): UxUIFilters {
|
||||
const localUiFilters = mapValues(
|
||||
pickKeys(params, ...uxLocalUIFilterNames),
|
||||
(val) => (val ? val.split(',') : [])
|
||||
) as Partial<Record<UxLocalUIFilterName, string[]>>;
|
||||
|
||||
return useDeepObjectIdentity({
|
||||
environment: params.environment,
|
||||
...localUiFilters,
|
||||
});
|
||||
}
|
||||
|
||||
const defaultRefresh = (_time: TimeRange) => {};
|
||||
|
||||
const UrlParamsContext = createContext({
|
||||
rangeId: 0,
|
||||
refreshTimeRange: defaultRefresh,
|
||||
uxUiFilters: {} as UxUIFilters,
|
||||
urlParams: {} as UrlParams,
|
||||
});
|
||||
|
||||
|
@ -85,16 +64,13 @@ const UrlParamsProvider: React.ComponentClass<{}> = withRouter(
|
|||
setRangeId((prevRangeId) => prevRangeId + 1);
|
||||
}, []);
|
||||
|
||||
const uxUiFilters = useUxUiFilters(urlParams);
|
||||
|
||||
const contextValue = useMemo(() => {
|
||||
return {
|
||||
rangeId,
|
||||
refreshTimeRange,
|
||||
urlParams,
|
||||
uxUiFilters,
|
||||
};
|
||||
}, [rangeId, refreshTimeRange, uxUiFilters, urlParams]);
|
||||
}, [rangeId, refreshTimeRange, urlParams]);
|
||||
|
||||
return (
|
||||
<UrlParamsContext.Provider children={children} value={contextValue} />
|
||||
|
@ -102,4 +78,4 @@ const UrlParamsProvider: React.ComponentClass<{}> = withRouter(
|
|||
}
|
||||
);
|
||||
|
||||
export { UrlParamsContext, UrlParamsProvider, useUxUiFilters };
|
||||
export { UrlParamsContext, UrlParamsProvider };
|
||||
|
|
|
@ -11,7 +11,6 @@ import { UsageCollectionStart } from 'src/plugins/usage_collection/public';
|
|||
import type { ConfigSchema } from '.';
|
||||
import {
|
||||
AppMountParameters,
|
||||
AppNavLinkStatus,
|
||||
CoreSetup,
|
||||
CoreStart,
|
||||
DEFAULT_APP_CATEGORIES,
|
||||
|
@ -36,7 +35,6 @@ import type { MapsStartApi } from '../../maps/public';
|
|||
import type { MlPluginSetup, MlPluginStart } from '../../ml/public';
|
||||
import {
|
||||
FetchDataParams,
|
||||
HasDataParams,
|
||||
METRIC_TYPE,
|
||||
ObservabilityPublicSetup,
|
||||
ObservabilityPublicStart,
|
||||
|
@ -152,23 +150,6 @@ export class ApmPlugin implements Plugin<ApmPluginSetup, ApmPluginStart> {
|
|||
{ label: serviceMapTitle, app: 'apm', path: '/service-map' },
|
||||
],
|
||||
},
|
||||
|
||||
// UX navigation
|
||||
{
|
||||
label: 'User Experience',
|
||||
sortKey: 600,
|
||||
entries: [
|
||||
{
|
||||
label: i18n.translate('xpack.apm.ux.overview.heading', {
|
||||
defaultMessage: 'Dashboard',
|
||||
}),
|
||||
app: 'ux',
|
||||
path: '/',
|
||||
matchFullPath: true,
|
||||
ignoreTrailingSlash: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -236,33 +217,8 @@ export class ApmPlugin implements Plugin<ApmPluginSetup, ApmPluginStart> {
|
|||
},
|
||||
});
|
||||
|
||||
const getUxDataHelper = async () => {
|
||||
const { fetchUxOverviewDate, hasRumData } = await import(
|
||||
'./components/app/rum_dashboard/ux_overview_fetchers'
|
||||
);
|
||||
const { createCallApmApi } = await import(
|
||||
'./services/rest/create_call_apm_api'
|
||||
);
|
||||
// have to do this here as well in case app isn't mounted yet
|
||||
createCallApmApi(core);
|
||||
|
||||
return { fetchUxOverviewDate, hasRumData };
|
||||
};
|
||||
|
||||
const { observabilityRuleTypeRegistry } = plugins.observability;
|
||||
|
||||
plugins.observability.dashboard.register({
|
||||
appName: 'ux',
|
||||
hasData: async (params?: HasDataParams) => {
|
||||
const dataHelper = await getUxDataHelper();
|
||||
return await dataHelper.hasRumData(params!);
|
||||
},
|
||||
fetchData: async (params: FetchDataParams) => {
|
||||
const dataHelper = await getUxDataHelper();
|
||||
return await dataHelper.fetchUxOverviewDate(params);
|
||||
},
|
||||
});
|
||||
|
||||
core.application.register({
|
||||
id: 'apm',
|
||||
title: 'APM',
|
||||
|
@ -298,49 +254,6 @@ export class ApmPlugin implements Plugin<ApmPluginSetup, ApmPluginStart> {
|
|||
|
||||
registerApmAlerts(observabilityRuleTypeRegistry);
|
||||
|
||||
core.application.register({
|
||||
id: 'ux',
|
||||
title: 'User Experience',
|
||||
order: 8500,
|
||||
euiIconType: 'logoObservability',
|
||||
category: DEFAULT_APP_CATEGORIES.observability,
|
||||
navLinkStatus: config.ui.enabled
|
||||
? AppNavLinkStatus.default
|
||||
: AppNavLinkStatus.hidden,
|
||||
keywords: [
|
||||
'RUM',
|
||||
'Real User Monitoring',
|
||||
'DEM',
|
||||
'Digital Experience Monitoring',
|
||||
'EUM',
|
||||
'End User Monitoring',
|
||||
'UX',
|
||||
'Javascript',
|
||||
'APM',
|
||||
'Mobile',
|
||||
'digital',
|
||||
'performance',
|
||||
'web performance',
|
||||
'web perf',
|
||||
],
|
||||
async mount(appMountParameters: AppMountParameters<unknown>) {
|
||||
// Load application bundle and Get start service
|
||||
const [{ renderApp }, [coreStart, corePlugins]] = await Promise.all([
|
||||
import('./application/ux_app'),
|
||||
core.getStartServices(),
|
||||
]);
|
||||
|
||||
return renderApp({
|
||||
core: coreStart,
|
||||
deps: pluginSetupDeps,
|
||||
appMountParameters,
|
||||
config,
|
||||
corePlugins: corePlugins as ApmPluginStartDeps,
|
||||
observabilityRuleTypeRegistry,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return {};
|
||||
}
|
||||
public start(core: CoreStart, plugins: ApmPluginStartDeps) {
|
||||
|
|
|
@ -25,7 +25,6 @@ import {
|
|||
} from '../../../../../src/core/types/elasticsearch';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { APMConfig } from '../../server';
|
||||
import { UxUIFilters } from '../../typings/ui_filters';
|
||||
import { MockApmPluginContextWrapper } from '../context/apm_plugin/mock_apm_plugin_context';
|
||||
import { UrlParamsProvider } from '../context/url_params_context/url_params_context';
|
||||
|
||||
|
@ -119,7 +118,6 @@ interface MockSetup {
|
|||
apmEventClient: any;
|
||||
internalClient: any;
|
||||
config: APMConfig;
|
||||
uiFilters: UxUIFilters;
|
||||
indices: {
|
||||
sourcemap: string;
|
||||
error: string;
|
||||
|
|
|
@ -8,7 +8,13 @@
|
|||
import { getRumPageLoadTransactionsProjection } from '../../projections/rum_page_load_transactions';
|
||||
import { mergeProjection } from '../../projections/util/merge_projection';
|
||||
import { SetupUX } from './route';
|
||||
import { BreakdownItem } from '../../../typings/ui_filters';
|
||||
|
||||
export interface BreakdownItem {
|
||||
name: string;
|
||||
type: string;
|
||||
fieldName: string;
|
||||
selected?: boolean;
|
||||
}
|
||||
|
||||
export async function getPageViewTrends({
|
||||
setup,
|
||||
|
|
|
@ -21,8 +21,8 @@ import { getWebCoreVitals } from './get_web_core_vitals';
|
|||
import { hasRumData } from './has_rum_data';
|
||||
import { createApmServerRoute } from '../apm_routes/create_apm_server_route';
|
||||
import { rangeRt } from '../default_api_types';
|
||||
import { UxUIFilters } from '../../../typings/ui_filters';
|
||||
import { APMRouteHandlerResources } from '../typings';
|
||||
import { UxUIFilters } from '../../../common/ux_ui_filter';
|
||||
|
||||
export type SetupUX = Setup & {
|
||||
uiFilters: UxUIFilters;
|
||||
|
|
|
@ -5,14 +5,14 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { ESFilter } from '../../../../../../../src/core/types/elasticsearch';
|
||||
import { environmentQuery } from '../../../../common/utils/environment_query';
|
||||
import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values';
|
||||
import {
|
||||
uxLocalUIFilterNames,
|
||||
uxLocalUIFilters,
|
||||
UxUIFilters,
|
||||
} from '../../../../common/ux_ui_filter';
|
||||
import { ESFilter } from '../../../../../../../src/core/types/elasticsearch';
|
||||
import { UxUIFilters } from '../../../../typings/ui_filters';
|
||||
import { environmentQuery } from '../../../../common/utils/environment_query';
|
||||
import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values';
|
||||
|
||||
export function getEsFilter(uiFilters: UxUIFilters, exclude?: boolean) {
|
||||
const localFilterValues = uiFilters;
|
||||
|
|
|
@ -22,7 +22,7 @@ import {
|
|||
APMPluginStartDependencies,
|
||||
} from '../types';
|
||||
import { UsageCollectionSetup } from '../../../../../src/plugins/usage_collection/server';
|
||||
import { UxUIFilters } from '../../typings/ui_filters';
|
||||
import { UxUIFilters } from '../../common/ux_ui_filter';
|
||||
|
||||
export interface ApmPluginRequestHandlerContext extends RequestHandlerContext {
|
||||
licensing: LicensingApiRequestHandlerContext;
|
||||
|
|
|
@ -10,8 +10,8 @@ import {
|
|||
ESSearchRequest,
|
||||
ESSearchResponse,
|
||||
} from '../../../../../src/core/types/elasticsearch';
|
||||
import { UxUIFilters } from '../../typings/ui_filters';
|
||||
import { ApmIndicesConfig } from '../routes/settings/apm_indices/get_apm_indices';
|
||||
import { UxUIFilters } from '../../common/ux_ui_filter';
|
||||
|
||||
interface Options {
|
||||
mockResponse?: (
|
||||
|
|
|
@ -27,7 +27,8 @@
|
|||
"inspector",
|
||||
"ruleRegistry",
|
||||
"timelines",
|
||||
"triggersActionsUi"
|
||||
"triggersActionsUi",
|
||||
"inspector"
|
||||
],
|
||||
"ui": true,
|
||||
"server": true,
|
||||
|
|
|
@ -9,86 +9,89 @@ import { EuiSuperDatePicker } from '@elastic/eui';
|
|||
import { waitFor } from '@testing-library/react';
|
||||
import { mount } from 'enzyme';
|
||||
import { createMemoryHistory, MemoryHistory } from 'history';
|
||||
import React, { ReactNode } from 'react';
|
||||
import React from 'react';
|
||||
import { Router, useLocation } from 'react-router-dom';
|
||||
import qs from 'query-string';
|
||||
import { MockApmPluginContextWrapper } from '../../../../context/apm_plugin/mock_apm_plugin_context';
|
||||
import { UrlParamsContext } from '../../../../context/url_params_context/url_params_context';
|
||||
import { RumDatePicker } from './';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { DatePicker } from './';
|
||||
import { KibanaContextProvider } from '../../../../../../../src/plugins/kibana_react/public';
|
||||
import { of } from 'rxjs';
|
||||
|
||||
let history: MemoryHistory;
|
||||
|
||||
const mockRefreshTimeRange = jest.fn();
|
||||
let mockHistoryPush: jest.SpyInstance;
|
||||
let mockHistoryReplace: jest.SpyInstance;
|
||||
|
||||
const mockRefreshTimeRange = jest.fn();
|
||||
|
||||
function MockUrlParamsProvider({ children }: { children: ReactNode }) {
|
||||
function DatePickerWrapper() {
|
||||
const location = useLocation();
|
||||
|
||||
const urlParams = qs.parse(location.search, {
|
||||
parseBooleans: true,
|
||||
const { rangeFrom, rangeTo, refreshInterval, refreshPaused } = qs.parse(location.search, {
|
||||
parseNumbers: true,
|
||||
});
|
||||
parseBooleans: true,
|
||||
}) as {
|
||||
rangeFrom?: string;
|
||||
rangeTo?: string;
|
||||
refreshInterval?: number;
|
||||
refreshPaused?: boolean;
|
||||
};
|
||||
|
||||
return (
|
||||
<UrlParamsContext.Provider
|
||||
value={{
|
||||
rangeId: 0,
|
||||
refreshTimeRange: mockRefreshTimeRange,
|
||||
urlParams,
|
||||
uxUiFilters: {},
|
||||
}}
|
||||
children={children}
|
||||
<DatePicker
|
||||
rangeFrom={rangeFrom}
|
||||
rangeTo={rangeTo}
|
||||
refreshInterval={refreshInterval}
|
||||
refreshPaused={refreshPaused}
|
||||
onTimeRangeRefresh={mockRefreshTimeRange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function mountDatePicker(
|
||||
params: {
|
||||
rangeFrom?: string;
|
||||
rangeTo?: string;
|
||||
refreshPaused?: boolean;
|
||||
refreshInterval?: number;
|
||||
} = {}
|
||||
) {
|
||||
function mountDatePicker(initialParams: {
|
||||
rangeFrom?: string;
|
||||
rangeTo?: string;
|
||||
refreshInterval?: number;
|
||||
refreshPaused?: boolean;
|
||||
}) {
|
||||
const setTimeSpy = jest.fn();
|
||||
const getTimeSpy = jest.fn().mockReturnValue({});
|
||||
|
||||
history = createMemoryHistory({
|
||||
initialEntries: [`/?${qs.stringify(params)}`],
|
||||
initialEntries: [`/?${qs.stringify(initialParams)}`],
|
||||
});
|
||||
|
||||
jest.spyOn(console, 'error').mockImplementation(() => null);
|
||||
mockHistoryPush = jest.spyOn(history, 'push');
|
||||
mockHistoryReplace = jest.spyOn(history, 'replace');
|
||||
|
||||
const wrapper = mount(
|
||||
<MockApmPluginContextWrapper
|
||||
history={history}
|
||||
value={
|
||||
{
|
||||
plugins: {
|
||||
data: {
|
||||
query: {
|
||||
timefilter: {
|
||||
timefilter: { setTime: setTimeSpy, getTime: getTimeSpy },
|
||||
},
|
||||
<Router history={history}>
|
||||
<KibanaContextProvider
|
||||
services={{
|
||||
data: {
|
||||
query: {
|
||||
timefilter: {
|
||||
timefilter: { setTime: setTimeSpy, getTime: getTimeSpy },
|
||||
},
|
||||
},
|
||||
},
|
||||
} as any
|
||||
}
|
||||
>
|
||||
<MockUrlParamsProvider>
|
||||
<RumDatePicker />
|
||||
</MockUrlParamsProvider>
|
||||
</MockApmPluginContextWrapper>
|
||||
uiSettings: {
|
||||
get: (key: string) => [],
|
||||
get$: (key: string) => of(true),
|
||||
},
|
||||
}}
|
||||
>
|
||||
<DatePickerWrapper />
|
||||
</KibanaContextProvider>
|
||||
</Router>
|
||||
);
|
||||
|
||||
return { wrapper, setTimeSpy, getTimeSpy };
|
||||
}
|
||||
|
||||
describe('RumDatePicker', () => {
|
||||
describe('DatePicker', () => {
|
||||
beforeAll(() => {
|
||||
jest.spyOn(console, 'error').mockImplementation(() => null);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
@ -97,40 +100,13 @@ describe('RumDatePicker', () => {
|
|||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('sets default query params in the URL', () => {
|
||||
mountDatePicker();
|
||||
expect(mockHistoryReplace).toHaveBeenCalledTimes(1);
|
||||
expect(mockHistoryReplace).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
search: 'rangeFrom=now-15m&rangeTo=now',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('adds missing `rangeFrom` to url', () => {
|
||||
mountDatePicker({ rangeTo: 'now', refreshInterval: 5000 });
|
||||
expect(mockHistoryReplace).toHaveBeenCalledTimes(1);
|
||||
expect(mockHistoryReplace).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
search: 'rangeFrom=now-15m&rangeTo=now&refreshInterval=5000',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('does not set default query params in the URL when values already defined', () => {
|
||||
mountDatePicker({
|
||||
rangeFrom: 'now-1d',
|
||||
rangeTo: 'now',
|
||||
refreshPaused: false,
|
||||
refreshInterval: 5000,
|
||||
});
|
||||
expect(mockHistoryReplace).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('updates the URL when the date range changes', () => {
|
||||
const { wrapper } = mountDatePicker();
|
||||
const { wrapper } = mountDatePicker({
|
||||
rangeFrom: 'now-15m',
|
||||
rangeTo: 'now',
|
||||
});
|
||||
|
||||
expect(mockHistoryReplace).toHaveBeenCalledTimes(1);
|
||||
expect(mockHistoryReplace).toHaveBeenCalledTimes(0);
|
||||
|
||||
wrapper.find(EuiSuperDatePicker).props().onTimeChange({
|
||||
start: 'now-90m',
|
||||
|
@ -149,11 +125,13 @@ describe('RumDatePicker', () => {
|
|||
it('enables auto-refresh when refreshPaused is false', async () => {
|
||||
jest.useFakeTimers();
|
||||
const { wrapper } = mountDatePicker({
|
||||
rangeFrom: 'now-15m',
|
||||
rangeTo: 'now',
|
||||
refreshPaused: false,
|
||||
refreshInterval: 1000,
|
||||
});
|
||||
expect(mockRefreshTimeRange).not.toHaveBeenCalled();
|
||||
jest.advanceTimersByTime(2500);
|
||||
jest.advanceTimersByTime(1000);
|
||||
await waitFor(() => {});
|
||||
expect(mockRefreshTimeRange).toHaveBeenCalled();
|
||||
wrapper.unmount();
|
||||
|
@ -161,7 +139,12 @@ describe('RumDatePicker', () => {
|
|||
|
||||
it('disables auto-refresh when refreshPaused is true', async () => {
|
||||
jest.useFakeTimers();
|
||||
mountDatePicker({ refreshPaused: true, refreshInterval: 1000 });
|
||||
mountDatePicker({
|
||||
rangeFrom: 'now-15m',
|
||||
rangeTo: 'now',
|
||||
refreshPaused: true,
|
||||
refreshInterval: 1000,
|
||||
});
|
||||
expect(mockRefreshTimeRange).not.toHaveBeenCalled();
|
||||
jest.advanceTimersByTime(1000);
|
||||
await waitFor(() => {});
|
||||
|
@ -184,23 +167,4 @@ describe('RumDatePicker', () => {
|
|||
expect(mockHistoryReplace).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('if `rangeFrom` is missing from the urlParams', () => {
|
||||
beforeEach(() => {
|
||||
mountDatePicker({ rangeTo: 'now-5m' });
|
||||
});
|
||||
|
||||
it('updates the url with the default `rangeFrom` ', async () => {
|
||||
expect(mockHistoryReplace).toHaveBeenCalledTimes(1);
|
||||
expect(mockHistoryReplace.mock.calls[0][0].search).toContain(
|
||||
'rangeFrom=now-15m'
|
||||
);
|
||||
});
|
||||
|
||||
it('preserves `rangeTo`', () => {
|
||||
expect(mockHistoryReplace.mock.calls[0][0].search).toContain(
|
||||
'rangeTo=now-5m'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -8,44 +8,40 @@
|
|||
import { EuiSuperDatePicker } from '@elastic/eui';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import { useHasData } from '../../../hooks/use_has_data';
|
||||
import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
|
||||
import { UI_SETTINGS, useKibanaUISettings } from '../../../hooks/use_kibana_ui_settings';
|
||||
import { usePluginContext } from '../../../hooks/use_plugin_context';
|
||||
import { fromQuery, toQuery } from '../../../utils/url';
|
||||
import { TimePickerQuickRange } from './typings';
|
||||
import { ObservabilityPublicPluginsStart } from '../../../plugin';
|
||||
|
||||
export interface TimePickerTime {
|
||||
from: string;
|
||||
to: string;
|
||||
export interface DatePickerProps {
|
||||
rangeFrom?: string;
|
||||
rangeTo?: string;
|
||||
refreshPaused?: boolean;
|
||||
refreshInterval?: number;
|
||||
onTimeRangeRefresh?: (range: { start: string; end: string }) => void;
|
||||
}
|
||||
|
||||
export interface TimePickerQuickRange extends TimePickerTime {
|
||||
display: string;
|
||||
}
|
||||
|
||||
export interface TimePickerRefreshInterval {
|
||||
pause: boolean;
|
||||
value: number;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
rangeFrom: string;
|
||||
rangeTo: string;
|
||||
refreshPaused: boolean;
|
||||
refreshInterval: number;
|
||||
}
|
||||
|
||||
export function DatePicker({ rangeFrom, rangeTo, refreshPaused, refreshInterval }: Props) {
|
||||
export function DatePicker({
|
||||
rangeFrom,
|
||||
rangeTo,
|
||||
refreshPaused,
|
||||
refreshInterval,
|
||||
onTimeRangeRefresh,
|
||||
}: DatePickerProps) {
|
||||
const location = useLocation();
|
||||
const history = useHistory();
|
||||
const { plugins } = usePluginContext();
|
||||
const { onRefreshTimeRange } = useHasData();
|
||||
const { data } = useKibana<ObservabilityPublicPluginsStart>().services;
|
||||
|
||||
useEffect(() => {
|
||||
plugins.data.query.timefilter.timefilter.setTime({
|
||||
from: rangeFrom,
|
||||
to: rangeTo,
|
||||
});
|
||||
}, [plugins, rangeFrom, rangeTo]);
|
||||
// set time if both to and from are given in the url
|
||||
if (rangeFrom && rangeTo) {
|
||||
data.query.timefilter.timefilter.setTime({
|
||||
from: rangeFrom,
|
||||
to: rangeTo,
|
||||
});
|
||||
}
|
||||
}, [data, rangeFrom, rangeTo]);
|
||||
|
||||
const timePickerQuickRanges = useKibanaUISettings<TimePickerQuickRange[]>(
|
||||
UI_SETTINGS.TIMEPICKER_QUICK_RANGES
|
||||
|
@ -95,7 +91,10 @@ export function DatePicker({ rangeFrom, rangeTo, refreshPaused, refreshInterval
|
|||
refreshInterval={refreshInterval}
|
||||
onRefreshChange={onRefreshChange}
|
||||
commonlyUsedRanges={commonlyUsedRanges}
|
||||
onRefresh={onRefreshTimeRange}
|
||||
onRefresh={onTimeRangeRefresh}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default DatePicker;
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export interface TimePickerQuickRange {
|
||||
from: string;
|
||||
to: string;
|
||||
display: string;
|
||||
}
|
||||
|
||||
export interface TimePickerRefreshInterval {
|
||||
pause: boolean;
|
||||
value: number;
|
||||
}
|
||||
|
||||
export interface TimePickerTimeDefaults {
|
||||
from: string;
|
||||
to: string;
|
||||
}
|
|
@ -9,6 +9,7 @@ import React, { lazy, Suspense } from 'react';
|
|||
import { EuiLoadingSpinner } from '@elastic/eui';
|
||||
import type { CoreVitalProps, HeaderMenuPortalProps } from './types';
|
||||
import type { FieldValueSuggestionsProps } from './field_value_suggestions/types';
|
||||
import type { DatePickerProps } from './date_picker/index';
|
||||
import type { FilterValueLabelProps } from './filter_value_label/filter_value_label';
|
||||
import type { SelectableUrlListProps } from './exploratory_view/components/url_search/selectable_url_list';
|
||||
import type { ExploratoryViewPageProps } from './exploratory_view/index';
|
||||
|
@ -76,3 +77,13 @@ export function ExploratoryView(props: ExploratoryViewPageProps) {
|
|||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
const DatePickerLazy = lazy(() => import('./date_picker/index'));
|
||||
|
||||
export function DatePicker(props: DatePickerProps) {
|
||||
return (
|
||||
<Suspense fallback={<EuiLoadingSpinner />}>
|
||||
<DatePickerLazy {...props} />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -9,15 +9,17 @@ import { useLocation } from 'react-router-dom';
|
|||
import { useMemo } from 'react';
|
||||
import { parse } from 'query-string';
|
||||
import { UI_SETTINGS, useKibanaUISettings } from './use_kibana_ui_settings';
|
||||
import { TimePickerTime } from '../components/shared/date_picker';
|
||||
import { getAbsoluteTime } from '../utils/date';
|
||||
import { TimePickerTimeDefaults } from '../components/shared/date_picker/typings';
|
||||
|
||||
const getParsedParams = (search: string) => {
|
||||
return search ? parse(search[0] === '?' ? search.slice(1) : search, { sort: false }) : {};
|
||||
};
|
||||
|
||||
export function useQueryParams() {
|
||||
const { from, to } = useKibanaUISettings<TimePickerTime>(UI_SETTINGS.TIMEPICKER_TIME_DEFAULTS);
|
||||
const { from, to } = useKibanaUISettings<TimePickerTimeDefaults>(
|
||||
UI_SETTINGS.TIMEPICKER_TIME_DEFAULTS
|
||||
);
|
||||
|
||||
const { rangeFrom, rangeTo } = getParsedParams(useLocation().search);
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import { parse } from 'query-string';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { TimePickerTime } from '../components/shared/date_picker';
|
||||
import { TimePickerTimeDefaults } from '../components/shared/date_picker/typings';
|
||||
import { getAbsoluteTime } from '../utils/date';
|
||||
import { UI_SETTINGS, useKibanaUISettings } from './use_kibana_ui_settings';
|
||||
import { usePluginContext } from './use_plugin_context';
|
||||
|
@ -19,7 +19,7 @@ const getParsedParams = (search: string) => {
|
|||
export function useTimeRange() {
|
||||
const { plugins } = usePluginContext();
|
||||
|
||||
const timePickerTimeDefaults = useKibanaUISettings<TimePickerTime>(
|
||||
const timePickerTimeDefaults = useKibanaUISettings<TimePickerTimeDefaults>(
|
||||
UI_SETTINGS.TIMEPICKER_TIME_DEFAULTS
|
||||
);
|
||||
|
||||
|
|
|
@ -57,6 +57,7 @@ export {
|
|||
FilterValueLabel,
|
||||
SelectableUrlList,
|
||||
ExploratoryView,
|
||||
DatePicker,
|
||||
} from './components/shared/';
|
||||
|
||||
export type { LazyObservabilityPageTemplateProps } from './components/shared';
|
||||
|
@ -106,3 +107,4 @@ export {
|
|||
RECORDS_FIELD,
|
||||
} from './components/shared/exploratory_view/configurations/constants';
|
||||
export { ExploratoryViewContextProvider } from './components/shared/exploratory_view/contexts/exploratory_view_config';
|
||||
export { fromQuery, toQuery } from './utils/url';
|
||||
|
|
|
@ -6390,8 +6390,6 @@
|
|||
"xpack.apm.alertTypes.transactionErrorRate.description": "サービスのトランザクションエラー率が定義されたしきい値を超過したときにアラートを発行します。",
|
||||
"xpack.apm.analyzeDataButton.label": "データの探索",
|
||||
"xpack.apm.analyzeDataButton.tooltip": "データの探索では、任意のディメンションの結果データを選択してフィルタリングし、パフォーマンスの問題の原因または影響を調査することができます。",
|
||||
"xpack.apm.analyzeDataButtonLabel": "データの探索",
|
||||
"xpack.apm.analyzeDataButtonLabel.message": "データの探索では、任意のディメンションの結果データを選択してフィルタリングし、パフォーマンスの問題の原因または影響を調査することができます。",
|
||||
"xpack.apm.anomaly_detection.error.invalid_license": "異常検知を使用するには、Elastic Platinumライセンスのサブスクリプションが必要です。このライセンスがあれば、機械学習を活用して、サービスを監視できます。",
|
||||
"xpack.apm.anomaly_detection.error.missing_read_privileges": "異常検知ジョブを表示するには、機械学習およびAPMの「読み取り」権限が必要です",
|
||||
"xpack.apm.anomaly_detection.error.missing_write_privileges": "異常検知ジョブを作成するには、機械学習およびAPMの「書き込み」権限が必要です",
|
||||
|
@ -6433,7 +6431,6 @@
|
|||
"xpack.apm.chart.error": "データの取得時にエラーが発生しました。再試行してください",
|
||||
"xpack.apm.chart.memorySeries.systemAverageLabel": "平均",
|
||||
"xpack.apm.chart.memorySeries.systemMaxLabel": "最高",
|
||||
"xpack.apm.clearFilters": "フィルターを消去",
|
||||
"xpack.apm.compositeSpanCallsLabel": "、{count}件の呼び出し、平均{duration}",
|
||||
"xpack.apm.compositeSpanDurationLabel": "平均時間",
|
||||
"xpack.apm.correlations.cancelButtonTitle": "キャンセル",
|
||||
|
@ -6501,12 +6498,6 @@
|
|||
"xpack.apm.correlations.progressAriaLabel": "進捗",
|
||||
"xpack.apm.correlations.progressTitle": "進捗状況: {progress}%",
|
||||
"xpack.apm.correlations.refreshButtonTitle": "更新",
|
||||
"xpack.apm.csm.breakdownFilter.browser": "ブラウザー",
|
||||
"xpack.apm.csm.breakdownFilter.device": "デバイス",
|
||||
"xpack.apm.csm.breakdownFilter.location": "場所",
|
||||
"xpack.apm.csm.breakDownFilter.noBreakdown": "内訳なし",
|
||||
"xpack.apm.csm.breakdownFilter.os": "OS",
|
||||
"xpack.apm.csm.pageViews.analyze": "分析",
|
||||
"xpack.apm.customLink.buttom.create": "カスタムリンクを作成",
|
||||
"xpack.apm.customLink.buttom.create.title": "作成",
|
||||
"xpack.apm.customLink.buttom.manage": "カスタムリンクを管理",
|
||||
|
@ -6526,7 +6517,6 @@
|
|||
"xpack.apm.deprecations.steps.switch": "[Elasticエージェントに切り替える]をクリックします。手順が案内されます。",
|
||||
"xpack.apm.emptyMessage.noDataFoundDescription": "別の時間範囲を試すか検索フィルターをリセットしてください。",
|
||||
"xpack.apm.emptyMessage.noDataFoundLabel": "データが見つかりません。",
|
||||
"xpack.apm.emptyState.loadingMessage": "読み込み中…",
|
||||
"xpack.apm.environmentsSelectCustomOptionText": "新しい環境として\\{searchValue\\}を追加",
|
||||
"xpack.apm.environmentsSelectPlaceholder": "環境を選択",
|
||||
"xpack.apm.error.prompt.body": "詳細はブラウザの開発者コンソールをご確認ください。",
|
||||
|
@ -6734,7 +6724,6 @@
|
|||
"xpack.apm.localFilters.titles.os": "OS",
|
||||
"xpack.apm.localFilters.titles.serviceName": "サービス名",
|
||||
"xpack.apm.localFilters.titles.transactionUrl": "URL",
|
||||
"xpack.apm.localFiltersTitle": "フィルター",
|
||||
"xpack.apm.managedTable.errorMessage": "取得できませんでした",
|
||||
"xpack.apm.managedTable.loadingDescription": "読み込み中…",
|
||||
"xpack.apm.metrics.transactionChart.machineLearningLabel": "機械学習:",
|
||||
|
@ -6769,56 +6758,6 @@
|
|||
"xpack.apm.propertiesTable.tabs.logStacktraceLabel": "スタックトレース",
|
||||
"xpack.apm.propertiesTable.tabs.metadataLabel": "メタデータ",
|
||||
"xpack.apm.propertiesTable.tabs.timelineLabel": "Timeline",
|
||||
"xpack.apm.rum.coreVitals.dataUndefined": "N/A",
|
||||
"xpack.apm.rum.coreVitals.fcp": "初回コンテンツの描画",
|
||||
"xpack.apm.rum.coreVitals.fcpTooltip": "初回コンテンツの描画(FCP)は初期のレンダリングに集中し、ページの読み込みが開始してから、ページのコンテンツのいずれかの部分が画面に表示されるときまでの時間を測定します。",
|
||||
"xpack.apm.rum.coreVitals.tbt": "合計ブロック時間",
|
||||
"xpack.apm.rum.coreVitals.tbtTooltip": "合計ブロック時間(TBT)は、初回コンテンツの描画からトランザクションが完了したときまでに発生する、各長いタスクのブロック時間(50 ミリ秒超)の合計です。",
|
||||
"xpack.apm.rum.dashboard.backend": "バックエンド",
|
||||
"xpack.apm.rum.dashboard.dataMissing": "N/A",
|
||||
"xpack.apm.rum.dashboard.frontend": "フロントエンド",
|
||||
"xpack.apm.rum.dashboard.impactfulMetrics.highTrafficPages": "高トラフィックページ",
|
||||
"xpack.apm.rum.dashboard.impactfulMetrics.jsErrors": "JavaScript エラー",
|
||||
"xpack.apm.rum.dashboard.overall.label": "全体",
|
||||
"xpack.apm.rum.dashboard.pageLoad.label": "ページの読み込み",
|
||||
"xpack.apm.rum.dashboard.pageLoadDistribution.label": "ページ読み込み分布",
|
||||
"xpack.apm.rum.dashboard.pageLoadDuration.label": "ページ読み込み時間",
|
||||
"xpack.apm.rum.dashboard.pageLoadTime.label": "ページ読み込み時間(秒)",
|
||||
"xpack.apm.rum.dashboard.pageLoadTimes.label": "ページ読み込み時間",
|
||||
"xpack.apm.rum.dashboard.pagesLoaded.label": "ページが読み込まれました",
|
||||
"xpack.apm.rum.dashboard.pageViews": "合計ページビュー",
|
||||
"xpack.apm.rum.dashboard.resetZoom.label": "ズームをリセット",
|
||||
"xpack.apm.rum.dashboard.tooltips.backEnd": "バックエンド時間は、最初の 1 バイトを受信するまでの時間(TTFB)です。これは、要求が実行された後、最初の応答パケットが受信された時点です。",
|
||||
"xpack.apm.rum.dashboard.tooltips.frontEnd": "フロントエンド時間は、合計ページ読み込み時間からバックエンド時間を減算した時間です。",
|
||||
"xpack.apm.rum.dashboard.tooltips.totalPageLoad": "合計はすべてのページ読み込み時間です。",
|
||||
"xpack.apm.rum.dashboard.totalPageLoad": "合計",
|
||||
"xpack.apm.rum.filterGroup.breakdown": "内訳",
|
||||
"xpack.apm.rum.filterGroup.coreWebVitals": "コアWebバイタル",
|
||||
"xpack.apm.rum.filterGroup.seconds": "秒",
|
||||
"xpack.apm.rum.filterGroup.selectBreakdown": "内訳を選択",
|
||||
"xpack.apm.rum.filters.filterByUrl": "IDでフィルタリング",
|
||||
"xpack.apm.rum.filters.searchResults": "{total}件の検索結果",
|
||||
"xpack.apm.rum.filters.select": "選択してください",
|
||||
"xpack.apm.rum.filters.topPages": "上位のページ",
|
||||
"xpack.apm.rum.filters.url": "Url",
|
||||
"xpack.apm.rum.filters.url.loadingResults": "結果を読み込み中",
|
||||
"xpack.apm.rum.filters.url.noResults": "結果がありません",
|
||||
"xpack.apm.rum.jsErrors.errorMessage": "エラーメッセージ",
|
||||
"xpack.apm.rum.jsErrors.errorRate": "エラー率",
|
||||
"xpack.apm.rum.jsErrors.impactedPageLoads": "影響を受けるページ読み込み数",
|
||||
"xpack.apm.rum.jsErrors.totalErrors": "合計エラー数",
|
||||
"xpack.apm.rum.jsErrorsTable.errorMessage": "取得できませんでした",
|
||||
"xpack.apm.rum.uxMetrics.longestLongTasks": "最長タスク時間",
|
||||
"xpack.apm.rum.uxMetrics.longestLongTasksTooltip": "最も長いタスクの時間。長いタスクは、UI スレッドを長時間(50 ミリ秒以上)独占し、他の重要なタスク(フレームレートや入力レイテンシ)の実行を妨害するユーザーアクティビティまたはブラウザータスクとして定義されます。",
|
||||
"xpack.apm.rum.uxMetrics.noOfLongTasks": "時間がかかるタスク数",
|
||||
"xpack.apm.rum.uxMetrics.noOfLongTasksTooltip": "長いタスクの数。長いタスクは、UI スレッドを長時間(50 ミリ秒以上)独占し、他の重要なタスク(フレームレートや入力レイテンシ)の実行を妨害するユーザーアクティビティまたはブラウザータスクとして定義されます。",
|
||||
"xpack.apm.rum.uxMetrics.sumLongTasks": "時間がかかるタスクの合計時間",
|
||||
"xpack.apm.rum.uxMetrics.sumLongTasksTooltip": "長いタスクの合計時間。長いタスクは、UI スレッドを長時間(50 ミリ秒以上)独占し、他の重要なタスク(フレームレートや入力レイテンシ)の実行を妨害するユーザーアクティビティまたはブラウザータスクとして定義されます。",
|
||||
"xpack.apm.rum.visitorBreakdown": "アクセスユーザー内訳",
|
||||
"xpack.apm.rum.visitorBreakdown.browser": "ブラウザー",
|
||||
"xpack.apm.rum.visitorBreakdown.operatingSystem": "オペレーティングシステム",
|
||||
"xpack.apm.rum.visitorBreakdownMap.avgPageLoadDuration": "平均ページ読み込み時間",
|
||||
"xpack.apm.rum.visitorBreakdownMap.pageLoadDurationByRegion": "地域別ページ読み込み時間(平均)",
|
||||
"xpack.apm.searchInput.filter": "フィルター...",
|
||||
"xpack.apm.selectPlaceholder": "オプションを選択:",
|
||||
"xpack.apm.serviceDependencies.breakdownChartTitle": "依存関係にかかった時間",
|
||||
|
@ -7349,28 +7288,6 @@
|
|||
"xpack.apm.tutorial.windowsServerInstructions.textPost": "注:システムでスクリプトの実行が無効な場合、スクリプトを実行するために現在のセッションの実行ポリシーの設定が必要となります。例:{command}。",
|
||||
"xpack.apm.tutorial.windowsServerInstructions.textPre": "1.[ダウンロードページ]({downloadPageLink})から APM Server Windows zip ファイルをダウンロードします。\n2.zip ファイルの内容を {zipFileExtractFolder} に抽出します。\n3.「{apmServerDirectory} ディレクトリの名前を「APM-Server」に変更します。\n4.管理者としてPowerShellプロンプトを開きます(PowerShellアイコンを右クリックして「管理者として実行」を選択します)。Windows XPをご使用の場合、PowerShellのダウンロードとインストールが必要な場合があります。\n5.PowerShell プロンプトで次のコマンドを実行し、APM Server を Windows サービスとしてインストールします。",
|
||||
"xpack.apm.unitLabel": "単位を選択",
|
||||
"xpack.apm.urlFilter.wildcard": "ワイルドカード*{wildcard}*を使用",
|
||||
"xpack.apm.ux.breadcrumbs.dashboard": "ダッシュボード",
|
||||
"xpack.apm.ux.breadcrumbs.root": "ユーザーエクスペリエンス",
|
||||
"xpack.apm.ux.jsErrors.percent": "{pageLoadPercent} %",
|
||||
"xpack.apm.ux.localFilters.titles.webApplication": "Webアプリケーション",
|
||||
"xpack.apm.ux.median": "中間",
|
||||
"xpack.apm.ux.metrics": "メトリック",
|
||||
"xpack.apm.ux.overview.agent.description": "APMエージェントを使用して、APMデータを収集します。多数の一般的な言語では、エージェントを使用することで処理が簡単になっています。",
|
||||
"xpack.apm.ux.overview.agent.title": "APM統合を追加",
|
||||
"xpack.apm.ux.overview.beatsCard.description": "APMエージェントでRUMを有効にして、ユーザーエクスペリエンスデータを収集します。",
|
||||
"xpack.apm.ux.overview.beatsCard.title": "APM統合を追加",
|
||||
"xpack.apm.ux.overview.heading": "ダッシュボード",
|
||||
"xpack.apm.ux.overview.solutionName": "Observability",
|
||||
"xpack.apm.ux.percentile.50thMedian": "50 番目(中央値)",
|
||||
"xpack.apm.ux.percentile.75th": "75番目",
|
||||
"xpack.apm.ux.percentile.90th": "90番目",
|
||||
"xpack.apm.ux.percentile.95th": "95番目",
|
||||
"xpack.apm.ux.percentile.99th": "99番目",
|
||||
"xpack.apm.ux.percentile.label": "パーセンタイル",
|
||||
"xpack.apm.ux.percentiles.label": "{value} パーセンタイル",
|
||||
"xpack.apm.ux.title": "ダッシュボード",
|
||||
"xpack.apm.ux.visitorBreakdown.noData": "データがありません。",
|
||||
"xpack.apm.views.dependencies.title": "依存関係",
|
||||
"xpack.apm.views.dependenciesInventory.title": "依存関係",
|
||||
"xpack.apm.views.errors.title": "エラー",
|
||||
|
@ -7393,6 +7310,81 @@
|
|||
"xpack.apm.views.transactions.title": "トランザクション",
|
||||
"xpack.apm.waterfall.errorCount": "{errorCount, plural, one {関連するエラーを表示} other {View # 件の関連するエラーを表示}}",
|
||||
"xpack.apm.waterfall.exceedsMax": "このトレースの項目数は表示されている範囲を超えています",
|
||||
"xpack.ux.breakdownFilter.browser": "ブラウザー",
|
||||
"xpack.ux.breakdownFilter.device": "デバイス",
|
||||
"xpack.ux.breakdownFilter.location": "場所",
|
||||
"xpack.ux.breakDownFilter.noBreakdown": "内訳なし",
|
||||
"xpack.ux.breakdownFilter.os": "OS",
|
||||
"xpack.ux.pageViews.analyze": "分析",
|
||||
"xpack.ux.coreVitals.dataUndefined": "N/A",
|
||||
"xpack.ux.coreVitals.fcp": "初回コンテンツの描画",
|
||||
"xpack.ux.coreVitals.fcpTooltip": "初回コンテンツの描画(FCP)は初期のレンダリングに集中し、ページの読み込みが開始してから、ページのコンテンツのいずれかの部分が画面に表示されるときまでの時間を測定します。",
|
||||
"xpack.ux.coreVitals.tbt": "合計ブロック時間",
|
||||
"xpack.ux.coreVitals.tbtTooltip": "合計ブロック時間(TBT)は、初回コンテンツの描画からトランザクションが完了したときまでに発生する、各長いタスクのブロック時間(50 ミリ秒超)の合計です。",
|
||||
"xpack.ux.dashboard.backend": "バックエンド",
|
||||
"xpack.ux.dashboard.dataMissing": "N/A",
|
||||
"xpack.ux.dashboard.frontend": "フロントエンド",
|
||||
"xpack.ux.dashboard.impactfulMetrics.highTrafficPages": "高トラフィックページ",
|
||||
"xpack.ux.dashboard.impactfulMetrics.jsErrors": "JavaScript エラー",
|
||||
"xpack.ux.dashboard.overall.label": "全体",
|
||||
"xpack.ux.dashboard.pageLoad.label": "ページの読み込み",
|
||||
"xpack.ux.dashboard.pageLoadDistribution.label": "ページ読み込み分布",
|
||||
"xpack.ux.dashboard.pageLoadDuration.label": "ページ読み込み時間",
|
||||
"xpack.ux.dashboard.pageLoadTime.label": "ページ読み込み時間(秒)",
|
||||
"xpack.ux.dashboard.pageLoadTimes.label": "ページ読み込み時間",
|
||||
"xpack.ux.dashboard.pagesLoaded.label": "ページが読み込まれました",
|
||||
"xpack.ux.dashboard.pageViews": "合計ページビュー",
|
||||
"xpack.ux.dashboard.resetZoom.label": "ズームをリセット",
|
||||
"xpack.ux.dashboard.tooltips.backEnd": "バックエンド時間は、最初の 1 バイトを受信するまでの時間(TTFB)です。これは、要求が実行された後、最初の応答パケットが受信された時点です。",
|
||||
"xpack.ux.dashboard.tooltips.frontEnd": "フロントエンド時間は、合計ページ読み込み時間からバックエンド時間を減算した時間です。",
|
||||
"xpack.ux.dashboard.tooltips.totalPageLoad": "合計はすべてのページ読み込み時間です。",
|
||||
"xpack.ux.dashboard.totalPageLoad": "合計",
|
||||
"xpack.ux.filterGroup.breakdown": "内訳",
|
||||
"xpack.ux.filterGroup.coreWebVitals": "コアWebバイタル",
|
||||
"xpack.ux.filterGroup.seconds": "秒",
|
||||
"xpack.ux.filterGroup.selectBreakdown": "内訳を選択",
|
||||
"xpack.ux.filters.filterByUrl": "IDでフィルタリング",
|
||||
"xpack.ux.filters.searchResults": "{total}件の検索結果",
|
||||
"xpack.ux.filters.select": "選択してください",
|
||||
"xpack.ux.filters.topPages": "上位のページ",
|
||||
"xpack.ux.filters.url": "Url",
|
||||
"xpack.ux.filters.url.loadingResults": "結果を読み込み中",
|
||||
"xpack.ux.filters.url.noResults": "結果がありません",
|
||||
"xpack.ux.jsErrors.errorMessage": "エラーメッセージ",
|
||||
"xpack.ux.jsErrors.errorRate": "エラー率",
|
||||
"xpack.ux.jsErrors.impactedPageLoads": "影響を受けるページ読み込み数",
|
||||
"xpack.ux.jsErrors.totalErrors": "合計エラー数",
|
||||
"xpack.ux.jsErrorsTable.errorMessage": "取得できませんでした",
|
||||
"xpack.ux.uxMetrics.longestLongTasks": "最長タスク時間",
|
||||
"xpack.ux.uxMetrics.longestLongTasksTooltip": "最も長いタスクの時間。長いタスクは、UI スレッドを長時間(50 ミリ秒以上)独占し、他の重要なタスク(フレームレートや入力レイテンシ)の実行を妨害するユーザーアクティビティまたはブラウザータスクとして定義されます。",
|
||||
"xpack.ux.uxMetrics.noOfLongTasks": "時間がかかるタスク数",
|
||||
"xpack.ux.uxMetrics.noOfLongTasksTooltip": "長いタスクの数。長いタスクは、UI スレッドを長時間(50 ミリ秒以上)独占し、他の重要なタスク(フレームレートや入力レイテンシ)の実行を妨害するユーザーアクティビティまたはブラウザータスクとして定義されます。",
|
||||
"xpack.ux.uxMetrics.sumLongTasks": "時間がかかるタスクの合計時間",
|
||||
"xpack.ux.uxMetrics.sumLongTasksTooltip": "長いタスクの合計時間。長いタスクは、UI スレッドを長時間(50 ミリ秒以上)独占し、他の重要なタスク(フレームレートや入力レイテンシ)の実行を妨害するユーザーアクティビティまたはブラウザータスクとして定義されます。",
|
||||
"xpack.ux.visitorBreakdown": "アクセスユーザー内訳",
|
||||
"xpack.ux.visitorBreakdown.browser": "ブラウザー",
|
||||
"xpack.ux.visitorBreakdown.operatingSystem": "オペレーティングシステム",
|
||||
"xpack.ux.visitorBreakdownMap.avgPageLoadDuration": "平均ページ読み込み時間",
|
||||
"xpack.ux.visitorBreakdownMap.pageLoadDurationByRegion": "地域別ページ読み込み時間(平均)",
|
||||
"xpack.ux.breadcrumbs.dashboard": "ダッシュボード",
|
||||
"xpack.ux.breadcrumbs.root": "ユーザーエクスペリエンス",
|
||||
"xpack.ux.jsErrors.percent": "{pageLoadPercent} %",
|
||||
"xpack.ux.localFilters.titles.webApplication": "Webアプリケーション",
|
||||
"xpack.ux.median": "中間",
|
||||
"xpack.ux.metrics": "メトリック",
|
||||
"xpack.ux.overview.beatsCard.description": "APMエージェントでRUMを有効にして、ユーザーエクスペリエンスデータを収集します。",
|
||||
"xpack.ux.overview.beatsCard.title": "APM統合を追加",
|
||||
"xpack.ux.overview.heading": "ダッシュボード",
|
||||
"xpack.ux.overview.solutionName": "オブザーバビリティ",
|
||||
"xpack.ux.percentile.50thMedian": "50 番目(中央値)",
|
||||
"xpack.ux.percentile.75th": "75番目",
|
||||
"xpack.ux.percentile.90th": "90番目",
|
||||
"xpack.ux.percentile.95th": "95番目",
|
||||
"xpack.ux.percentile.99th": "99番目",
|
||||
"xpack.ux.percentile.label": "パーセンタイル",
|
||||
"xpack.ux.percentiles.label": "{value} パーセンタイル",
|
||||
"xpack.ux.title": "ダッシュボード",
|
||||
"xpack.ux.visitorBreakdown.noData": "データがありません。",
|
||||
"xpack.banners.settings.backgroundColor.description": "バナーの背景色。{subscriptionLink}",
|
||||
"xpack.banners.settings.backgroundColor.title": "バナー背景色",
|
||||
"xpack.banners.settings.placement.description": "Elasticヘッダーの上に、このスペースの上部のバナーを表示します。{subscriptionLink}",
|
||||
|
|
|
@ -6442,8 +6442,6 @@
|
|||
"xpack.apm.alertTypes.transactionErrorRate.description": "当服务中的事务错误率超过定义的阈值时告警。",
|
||||
"xpack.apm.analyzeDataButton.label": "浏览数据",
|
||||
"xpack.apm.analyzeDataButton.tooltip": "“浏览数据”允许您选择和筛选任意维度中的结果数据以及查找性能问题的原因或影响",
|
||||
"xpack.apm.analyzeDataButtonLabel": "浏览数据",
|
||||
"xpack.apm.analyzeDataButtonLabel.message": "“浏览数据”允许您选择和筛选任意维度中的结果数据以及查找性能问题的原因或影响。",
|
||||
"xpack.apm.anomaly_detection.error.invalid_license": "要使用异常检测,必须订阅 Elastic 白金级许可证。有了该许可证,您便可借助 Machine Learning 监测服务。",
|
||||
"xpack.apm.anomaly_detection.error.missing_read_privileges": "必须对 Machine Learning 和 APM 具有“读”权限,才能查看“异常检测”作业",
|
||||
"xpack.apm.anomaly_detection.error.missing_write_privileges": "必须对 Machine Learning 和 APM 具有“写”权限,才能创建“异常检测”作业",
|
||||
|
@ -6485,7 +6483,6 @@
|
|||
"xpack.apm.chart.error": "尝试提取数据时发生错误。请重试",
|
||||
"xpack.apm.chart.memorySeries.systemAverageLabel": "平均值",
|
||||
"xpack.apm.chart.memorySeries.systemMaxLabel": "最大值",
|
||||
"xpack.apm.clearFilters": "清除筛选",
|
||||
"xpack.apm.compositeSpanCallsLabel": ",{count} 个调用,平均 {duration}",
|
||||
"xpack.apm.compositeSpanDurationLabel": "平均持续时间",
|
||||
"xpack.apm.correlations.cancelButtonTitle": "取消",
|
||||
|
@ -6553,12 +6550,6 @@
|
|||
"xpack.apm.correlations.progressAriaLabel": "进度",
|
||||
"xpack.apm.correlations.progressTitle": "进度:{progress}%",
|
||||
"xpack.apm.correlations.refreshButtonTitle": "刷新",
|
||||
"xpack.apm.csm.breakdownFilter.browser": "浏览器",
|
||||
"xpack.apm.csm.breakdownFilter.device": "设备",
|
||||
"xpack.apm.csm.breakdownFilter.location": "位置",
|
||||
"xpack.apm.csm.breakDownFilter.noBreakdown": "无细目",
|
||||
"xpack.apm.csm.breakdownFilter.os": "OS",
|
||||
"xpack.apm.csm.pageViews.analyze": "分析",
|
||||
"xpack.apm.customLink.buttom.create": "创建定制链接",
|
||||
"xpack.apm.customLink.buttom.create.title": "创建",
|
||||
"xpack.apm.customLink.buttom.manage": "管理定制链接",
|
||||
|
@ -6578,7 +6569,6 @@
|
|||
"xpack.apm.deprecations.steps.switch": "单击“切换到 Elastic 代理”。将指导您完成此过程",
|
||||
"xpack.apm.emptyMessage.noDataFoundDescription": "尝试其他时间范围或重置搜索筛选。",
|
||||
"xpack.apm.emptyMessage.noDataFoundLabel": "未找到任何数据。",
|
||||
"xpack.apm.emptyState.loadingMessage": "正在加载……",
|
||||
"xpack.apm.environmentsSelectCustomOptionText": "将 \\{searchValue\\} 添加为新环境",
|
||||
"xpack.apm.environmentsSelectPlaceholder": "选择环境",
|
||||
"xpack.apm.error.prompt.body": "有关详情,请查看您的浏览器开发者控制台。",
|
||||
|
@ -6789,7 +6779,6 @@
|
|||
"xpack.apm.localFilters.titles.os": "OS",
|
||||
"xpack.apm.localFilters.titles.serviceName": "服务名称",
|
||||
"xpack.apm.localFilters.titles.transactionUrl": "URL",
|
||||
"xpack.apm.localFiltersTitle": "筛选",
|
||||
"xpack.apm.managedTable.errorMessage": "无法提取",
|
||||
"xpack.apm.managedTable.loadingDescription": "正在加载……",
|
||||
"xpack.apm.metrics.transactionChart.machineLearningLabel": "Machine Learning",
|
||||
|
@ -6825,56 +6814,6 @@
|
|||
"xpack.apm.propertiesTable.tabs.logStacktraceLabel": "日志堆栈跟踪",
|
||||
"xpack.apm.propertiesTable.tabs.metadataLabel": "元数据",
|
||||
"xpack.apm.propertiesTable.tabs.timelineLabel": "时间线",
|
||||
"xpack.apm.rum.coreVitals.dataUndefined": "不可用",
|
||||
"xpack.apm.rum.coreVitals.fcp": "首次内容绘制",
|
||||
"xpack.apm.rum.coreVitals.fcpTooltip": "首次内容绘制 (FCP) 侧重于初始渲染,并度量从页面开始加载到页面内容的任何部分显示在屏幕的时间。",
|
||||
"xpack.apm.rum.coreVitals.tbt": "阻止总时间",
|
||||
"xpack.apm.rum.coreVitals.tbtTooltip": "总阻塞时间 (TBT) 是指在首次内容绘制与事务完成之间发生的各个长任务的阻塞时间(持续时间超过50毫秒)之和。",
|
||||
"xpack.apm.rum.dashboard.backend": "后端",
|
||||
"xpack.apm.rum.dashboard.dataMissing": "不可用",
|
||||
"xpack.apm.rum.dashboard.frontend": "前端",
|
||||
"xpack.apm.rum.dashboard.impactfulMetrics.highTrafficPages": "高流量页面",
|
||||
"xpack.apm.rum.dashboard.impactfulMetrics.jsErrors": "JavaScript 错误",
|
||||
"xpack.apm.rum.dashboard.overall.label": "总体",
|
||||
"xpack.apm.rum.dashboard.pageLoad.label": "页面加载",
|
||||
"xpack.apm.rum.dashboard.pageLoadDistribution.label": "页面加载分布",
|
||||
"xpack.apm.rum.dashboard.pageLoadDuration.label": "页面加载持续时间",
|
||||
"xpack.apm.rum.dashboard.pageLoadTime.label": "页面加载时间(秒)",
|
||||
"xpack.apm.rum.dashboard.pageLoadTimes.label": "页面加载时间",
|
||||
"xpack.apm.rum.dashboard.pagesLoaded.label": "已加载页面",
|
||||
"xpack.apm.rum.dashboard.pageViews": "页面总查看次数",
|
||||
"xpack.apm.rum.dashboard.resetZoom.label": "重置缩放比例",
|
||||
"xpack.apm.rum.dashboard.tooltips.backEnd": "后端时间表示接收到第一个字节所需的时间 (TTFB),即从请求发出后接收到第一个响应数据包的时间",
|
||||
"xpack.apm.rum.dashboard.tooltips.frontEnd": "前端时间表示页面加载总持续时间减去后端时间",
|
||||
"xpack.apm.rum.dashboard.tooltips.totalPageLoad": "合计表示整个页面加载持续时间",
|
||||
"xpack.apm.rum.dashboard.totalPageLoad": "合计",
|
||||
"xpack.apm.rum.filterGroup.breakdown": "细目",
|
||||
"xpack.apm.rum.filterGroup.coreWebVitals": "网站体验核心指标",
|
||||
"xpack.apm.rum.filterGroup.seconds": "秒",
|
||||
"xpack.apm.rum.filterGroup.selectBreakdown": "选择细分",
|
||||
"xpack.apm.rum.filters.filterByUrl": "按 URL 筛选",
|
||||
"xpack.apm.rum.filters.searchResults": "{total} 项搜索结果",
|
||||
"xpack.apm.rum.filters.select": "选择",
|
||||
"xpack.apm.rum.filters.topPages": "排名靠前页面",
|
||||
"xpack.apm.rum.filters.url": "URL",
|
||||
"xpack.apm.rum.filters.url.loadingResults": "正在加载结果",
|
||||
"xpack.apm.rum.filters.url.noResults": "没有可用结果",
|
||||
"xpack.apm.rum.jsErrors.errorMessage": "错误消息",
|
||||
"xpack.apm.rum.jsErrors.errorRate": "错误率",
|
||||
"xpack.apm.rum.jsErrors.impactedPageLoads": "受影响的页面加载",
|
||||
"xpack.apm.rum.jsErrors.totalErrors": "错误总数",
|
||||
"xpack.apm.rum.jsErrorsTable.errorMessage": "无法提取",
|
||||
"xpack.apm.rum.uxMetrics.longestLongTasks": "长任务最长持续时间",
|
||||
"xpack.apm.rum.uxMetrics.longestLongTasksTooltip": "最长任务的持续时间。长任务是指独占用户界面线程较长时间(大于50毫秒)并阻止其他关键任务(帧速率或输入延迟)执行的任何用户活动或浏览器任务。",
|
||||
"xpack.apm.rum.uxMetrics.noOfLongTasks": "长任务数目",
|
||||
"xpack.apm.rum.uxMetrics.noOfLongTasksTooltip": "长任务的数量。长任务是指独占用户界面线程较长时间(大于50毫秒)并阻止其他关键任务(帧速率或输入延迟)执行的任何用户活动或浏览器任务。",
|
||||
"xpack.apm.rum.uxMetrics.sumLongTasks": "长任务总持续时间",
|
||||
"xpack.apm.rum.uxMetrics.sumLongTasksTooltip": "长任务的总持续时间。长任务是指独占用户界面线程较长时间(大于50毫秒)并阻止其他关键任务(帧速率或输入延迟)执行的任何用户活动或浏览器任务。",
|
||||
"xpack.apm.rum.visitorBreakdown": "访问者细分",
|
||||
"xpack.apm.rum.visitorBreakdown.browser": "浏览器",
|
||||
"xpack.apm.rum.visitorBreakdown.operatingSystem": "操作系统",
|
||||
"xpack.apm.rum.visitorBreakdownMap.avgPageLoadDuration": "页面加载平均持续时间",
|
||||
"xpack.apm.rum.visitorBreakdownMap.pageLoadDurationByRegion": "按区域列出的页面加载持续时间(平均值)",
|
||||
"xpack.apm.searchInput.filter": "筛选...",
|
||||
"xpack.apm.selectPlaceholder": "选择选项:",
|
||||
"xpack.apm.serviceDependencies.breakdownChartTitle": "依赖项花费的时间",
|
||||
|
@ -7410,28 +7349,6 @@
|
|||
"xpack.apm.tutorial.windowsServerInstructions.textPre": "1.从[下载页面]({downloadPageLink})下载 APM Server Windows zip 文件。\n2.将 zip 文件的内容解压缩到 {zipFileExtractFolder}。\n3.将 {apmServerDirectory} 目录重命名为 `APM-Server`。\n4.以管理员身份打开 PowerShell 提示符(右键单击 PowerShell 图标,然后选择**以管理员身份运行**)。如果运行的是 Windows XP,则可能需要下载并安装 PowerShell。\n5.从 PowerShell 提示符处,运行以下命令以将 APM Server 安装为 Windows 服务:",
|
||||
"xpack.apm.unitLabel": "选择单位",
|
||||
"xpack.apm.unsavedChanges": "{unsavedChangesCount, plural, =0{0 个未保存更改} one {1 个未保存更改} other {# 个未保存更改}} ",
|
||||
"xpack.apm.urlFilter.wildcard": "使用通配符 *{wildcard}*",
|
||||
"xpack.apm.ux.breadcrumbs.dashboard": "仪表板",
|
||||
"xpack.apm.ux.breadcrumbs.root": "用户体验",
|
||||
"xpack.apm.ux.jsErrors.percent": "{pageLoadPercent}%",
|
||||
"xpack.apm.ux.localFilters.titles.webApplication": "Web 应用程序",
|
||||
"xpack.apm.ux.median": "中值",
|
||||
"xpack.apm.ux.metrics": "指标",
|
||||
"xpack.apm.ux.overview.agent.description": "使用 APM 代理来收集 APM 数据。我们的代理支持许多流行语言,可以轻松使用。",
|
||||
"xpack.apm.ux.overview.agent.title": "添加 APM 集成",
|
||||
"xpack.apm.ux.overview.beatsCard.description": "通过 APM 代理启用 RUM,以收集用户体验数据。",
|
||||
"xpack.apm.ux.overview.beatsCard.title": "添加 APM 集成",
|
||||
"xpack.apm.ux.overview.heading": "仪表板",
|
||||
"xpack.apm.ux.overview.solutionName": "Observability",
|
||||
"xpack.apm.ux.percentile.50thMedian": "第 50 个(中值)",
|
||||
"xpack.apm.ux.percentile.75th": "第 75 个",
|
||||
"xpack.apm.ux.percentile.90th": "第 90 个",
|
||||
"xpack.apm.ux.percentile.95th": "第 95 个",
|
||||
"xpack.apm.ux.percentile.99th": "第 99 个",
|
||||
"xpack.apm.ux.percentile.label": "百分位数",
|
||||
"xpack.apm.ux.percentiles.label": "第 {value} 个百分位",
|
||||
"xpack.apm.ux.title": "仪表板",
|
||||
"xpack.apm.ux.visitorBreakdown.noData": "无数据。",
|
||||
"xpack.apm.views.dependencies.title": "依赖项",
|
||||
"xpack.apm.views.dependenciesInventory.title": "依赖项",
|
||||
"xpack.apm.views.errors.title": "错误",
|
||||
|
@ -7454,6 +7371,81 @@
|
|||
"xpack.apm.views.transactions.title": "事务",
|
||||
"xpack.apm.waterfall.errorCount": "{errorCount, plural, one {查看相关错误} other {查看 # 个相关错误}}",
|
||||
"xpack.apm.waterfall.exceedsMax": "此跟踪中的项目数超过显示的项目数",
|
||||
"xpack.ux.breakdownFilter.browser": "浏览器",
|
||||
"xpack.ux.breakdownFilter.device": "设备",
|
||||
"xpack.ux.breakdownFilter.location": "位置",
|
||||
"xpack.ux.breakDownFilter.noBreakdown": "无细目",
|
||||
"xpack.ux.breakdownFilter.os": "OS",
|
||||
"xpack.ux.pageViews.analyze": "分析",
|
||||
"xpack.ux.coreVitals.dataUndefined": "不可用",
|
||||
"xpack.ux.coreVitals.fcp": "首次内容绘制",
|
||||
"xpack.ux.coreVitals.fcpTooltip": "首次内容绘制 (FCP) 侧重于初始渲染,并度量从页面开始加载到页面内容的任何部分显示在屏幕的时间。",
|
||||
"xpack.ux.coreVitals.tbt": "阻止总时间",
|
||||
"xpack.ux.coreVitals.tbtTooltip": "总阻塞时间 (TBT) 是指在首次内容绘制与事务完成之间发生的各个长任务的阻塞时间(持续时间超过50毫秒)之和。",
|
||||
"xpack.ux.dashboard.backend": "后端",
|
||||
"xpack.ux.dashboard.dataMissing": "不可用",
|
||||
"xpack.ux.dashboard.frontend": "前端",
|
||||
"xpack.ux.dashboard.impactfulMetrics.highTrafficPages": "高流量页面",
|
||||
"xpack.ux.dashboard.impactfulMetrics.jsErrors": "JavaScript 错误",
|
||||
"xpack.ux.dashboard.overall.label": "总体",
|
||||
"xpack.ux.dashboard.pageLoad.label": "页面加载",
|
||||
"xpack.ux.dashboard.pageLoadDistribution.label": "页面加载分布",
|
||||
"xpack.ux.dashboard.pageLoadDuration.label": "页面加载持续时间",
|
||||
"xpack.ux.dashboard.pageLoadTime.label": "页面加载时间(秒)",
|
||||
"xpack.ux.dashboard.pageLoadTimes.label": "页面加载时间",
|
||||
"xpack.ux.dashboard.pagesLoaded.label": "已加载页面",
|
||||
"xpack.ux.dashboard.pageViews": "页面总查看次数",
|
||||
"xpack.ux.dashboard.resetZoom.label": "重置缩放比例",
|
||||
"xpack.ux.dashboard.tooltips.backEnd": "后端时间表示接收到第一个字节所需的时间 (TTFB),即从请求发出后接收到第一个响应数据包的时间",
|
||||
"xpack.ux.dashboard.tooltips.frontEnd": "前端时间表示页面加载总持续时间减去后端时间",
|
||||
"xpack.ux.dashboard.tooltips.totalPageLoad": "合计表示整个页面加载持续时间",
|
||||
"xpack.ux.dashboard.totalPageLoad": "合计",
|
||||
"xpack.ux.filterGroup.breakdown": "细目",
|
||||
"xpack.ux.filterGroup.coreWebVitals": "网站体验核心指标",
|
||||
"xpack.ux.filterGroup.seconds": "秒",
|
||||
"xpack.ux.filterGroup.selectBreakdown": "选择细分",
|
||||
"xpack.ux.filters.filterByUrl": "按 URL 筛选",
|
||||
"xpack.ux.filters.searchResults": "{total} 项搜索结果",
|
||||
"xpack.ux.filters.select": "选择",
|
||||
"xpack.ux.filters.topPages": "排名靠前页面",
|
||||
"xpack.ux.filters.url": "URL",
|
||||
"xpack.ux.filters.url.loadingResults": "正在加载结果",
|
||||
"xpack.ux.filters.url.noResults": "没有可用结果",
|
||||
"xpack.ux.jsErrors.errorMessage": "错误消息",
|
||||
"xpack.ux.jsErrors.errorRate": "错误率",
|
||||
"xpack.ux.jsErrors.impactedPageLoads": "受影响的页面加载",
|
||||
"xpack.ux.jsErrors.totalErrors": "错误总数",
|
||||
"xpack.ux.jsErrorsTable.errorMessage": "无法提取",
|
||||
"xpack.ux.uxMetrics.longestLongTasks": "长任务最长持续时间",
|
||||
"xpack.ux.uxMetrics.longestLongTasksTooltip": "最长任务的持续时间。长任务是指独占用户界面线程较长时间(大于50毫秒)并阻止其他关键任务(帧速率或输入延迟)执行的任何用户活动或浏览器任务。",
|
||||
"xpack.ux.uxMetrics.noOfLongTasks": "长任务数目",
|
||||
"xpack.ux.uxMetrics.noOfLongTasksTooltip": "长任务的数量。长任务是指独占用户界面线程较长时间(大于50毫秒)并阻止其他关键任务(帧速率或输入延迟)执行的任何用户活动或浏览器任务。",
|
||||
"xpack.ux.uxMetrics.sumLongTasks": "长任务总持续时间",
|
||||
"xpack.ux.uxMetrics.sumLongTasksTooltip": "长任务的总持续时间。长任务是指独占用户界面线程较长时间(大于50毫秒)并阻止其他关键任务(帧速率或输入延迟)执行的任何用户活动或浏览器任务。",
|
||||
"xpack.ux.visitorBreakdown": "访问者细分",
|
||||
"xpack.ux.visitorBreakdown.browser": "浏览器",
|
||||
"xpack.ux.visitorBreakdown.operatingSystem": "操作系统",
|
||||
"xpack.ux.visitorBreakdownMap.avgPageLoadDuration": "页面加载平均持续时间",
|
||||
"xpack.ux.visitorBreakdownMap.pageLoadDurationByRegion": "按区域列出的页面加载持续时间(平均值)",
|
||||
"xpack.ux.breadcrumbs.dashboard": "仪表板",
|
||||
"xpack.ux.breadcrumbs.root": "用户体验",
|
||||
"xpack.ux.jsErrors.percent": "{pageLoadPercent}%",
|
||||
"xpack.ux.localFilters.titles.webApplication": "Web 应用程序",
|
||||
"xpack.ux.median": "中值",
|
||||
"xpack.ux.metrics": "指标",
|
||||
"xpack.ux.overview.beatsCard.description": "通过 APM 代理启用 RUM,以收集用户体验数据。",
|
||||
"xpack.ux.overview.beatsCard.title": "添加 APM 集成",
|
||||
"xpack.ux.overview.heading": "仪表板",
|
||||
"xpack.ux.overview.solutionName": "可观测性",
|
||||
"xpack.ux.percentile.50thMedian": "第 50 个(中值)",
|
||||
"xpack.ux.percentile.75th": "第 75 个",
|
||||
"xpack.ux.percentile.90th": "第 90 个",
|
||||
"xpack.ux.percentile.95th": "第 95 个",
|
||||
"xpack.ux.percentile.99th": "第 99 个",
|
||||
"xpack.ux.percentile.label": "百分位数",
|
||||
"xpack.ux.percentiles.label": "第 {value} 个百分位",
|
||||
"xpack.ux.title": "仪表板",
|
||||
"xpack.ux.visitorBreakdown.noData": "无数据。",
|
||||
"xpack.banners.settings.backgroundColor.description": "设置横幅广告的背景色。{subscriptionLink}",
|
||||
"xpack.banners.settings.backgroundColor.title": "横幅广告背景色",
|
||||
"xpack.banners.settings.placement.description": "在 Elastic 页眉上显示此工作区的顶部横幅广告。{subscriptionLink}",
|
||||
|
|
4
x-pack/plugins/ux/.prettierrc
Normal file
4
x-pack/plugins/ux/.prettierrc
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"singleQuote": true,
|
||||
"semi": true
|
||||
}
|
8
x-pack/plugins/ux/.storybook/main.js
Normal file
8
x-pack/plugins/ux/.storybook/main.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
module.exports = require('@kbn/storybook').defaultConfig;
|
1
x-pack/plugins/ux/CONTRIBUTING.md
Normal file
1
x-pack/plugins/ux/CONTRIBUTING.md
Normal file
|
@ -0,0 +1 @@
|
|||
## Observability User Experience Application
|
20
x-pack/plugins/ux/common/agent_name.ts
Normal file
20
x-pack/plugins/ux/common/agent_name.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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 { AgentName } from '../../apm/typings/es_schemas/ui/fields/agent';
|
||||
|
||||
export const RUM_AGENT_NAMES: AgentName[] = [
|
||||
'js-base',
|
||||
'rum-js',
|
||||
'opentelemetry/webjs',
|
||||
];
|
||||
|
||||
export function isRumAgentName(
|
||||
agentName?: string
|
||||
): agentName is 'js-base' | 'rum-js' | 'opentelemetry/webjs' {
|
||||
return RUM_AGENT_NAMES.includes(agentName! as AgentName);
|
||||
}
|
42
x-pack/plugins/ux/common/elasticsearch_fieldnames.ts
Normal file
42
x-pack/plugins/ux/common/elasticsearch_fieldnames.ts
Normal file
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export const SERVICE = 'service';
|
||||
export const SERVICE_NAME = 'service.name';
|
||||
export const SERVICE_ENVIRONMENT = 'service.environment';
|
||||
|
||||
export const AGENT = 'agent';
|
||||
export const AGENT_NAME = 'agent.name';
|
||||
export const AGENT_VERSION = 'agent.version';
|
||||
|
||||
export const URL_FULL = 'url.full';
|
||||
export const USER_AGENT_NAME = 'user_agent.name';
|
||||
|
||||
export const TRANSACTION_DURATION = 'transaction.duration.us';
|
||||
export const TRANSACTION_TYPE = 'transaction.type';
|
||||
export const TRANSACTION_RESULT = 'transaction.result';
|
||||
export const TRANSACTION_NAME = 'transaction.name';
|
||||
export const TRANSACTION_ID = 'transaction.id';
|
||||
|
||||
export const CLIENT_GEO_COUNTRY_ISO_CODE = 'client.geo.country_iso_code';
|
||||
|
||||
// RUM Labels
|
||||
export const TRANSACTION_URL = 'url.full';
|
||||
export const CLIENT_GEO = 'client.geo';
|
||||
export const USER_AGENT_DEVICE = 'user_agent.device.name';
|
||||
export const USER_AGENT_OS = 'user_agent.os.name';
|
||||
|
||||
export const TRANSACTION_TIME_TO_FIRST_BYTE =
|
||||
'transaction.marks.agent.timeToFirstByte';
|
||||
export const TRANSACTION_DOM_INTERACTIVE =
|
||||
'transaction.marks.agent.domInteractive';
|
||||
|
||||
export const FCP_FIELD = 'transaction.marks.agent.firstContentfulPaint';
|
||||
export const LCP_FIELD = 'transaction.marks.agent.largestContentfulPaint';
|
||||
export const TBT_FIELD = 'transaction.experience.tbt';
|
||||
export const FID_FIELD = 'transaction.experience.fid';
|
||||
export const CLS_FIELD = 'transaction.experience.cls';
|
75
x-pack/plugins/ux/common/environment_filter_values.ts
Normal file
75
x-pack/plugins/ux/common/environment_filter_values.ts
Normal file
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* 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 { SERVICE_ENVIRONMENT } from './elasticsearch_fieldnames';
|
||||
import { Environment } from './environment_rt';
|
||||
|
||||
const ENVIRONMENT_ALL_VALUE = 'ENVIRONMENT_ALL' as const;
|
||||
const ENVIRONMENT_NOT_DEFINED_VALUE = 'ENVIRONMENT_NOT_DEFINED' as const;
|
||||
|
||||
export function getEnvironmentLabel(environment: string) {
|
||||
if (!environment || environment === ENVIRONMENT_NOT_DEFINED_VALUE) {
|
||||
return i18n.translate('xpack.ux.filter.environment.notDefinedLabel', {
|
||||
defaultMessage: 'Not defined',
|
||||
});
|
||||
}
|
||||
|
||||
if (environment === ENVIRONMENT_ALL_VALUE) {
|
||||
return i18n.translate('xpack.ux.filter.environment.allLabel', {
|
||||
defaultMessage: 'All',
|
||||
});
|
||||
}
|
||||
|
||||
return environment;
|
||||
}
|
||||
|
||||
export const ENVIRONMENT_ALL = {
|
||||
value: ENVIRONMENT_ALL_VALUE,
|
||||
text: getEnvironmentLabel(ENVIRONMENT_ALL_VALUE),
|
||||
};
|
||||
|
||||
export const ENVIRONMENT_NOT_DEFINED = {
|
||||
value: ENVIRONMENT_NOT_DEFINED_VALUE,
|
||||
text: getEnvironmentLabel(ENVIRONMENT_NOT_DEFINED_VALUE),
|
||||
};
|
||||
|
||||
export function getEnvironmentEsField(environment: string) {
|
||||
if (
|
||||
!environment ||
|
||||
environment === ENVIRONMENT_NOT_DEFINED_VALUE ||
|
||||
environment === ENVIRONMENT_ALL_VALUE
|
||||
) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return { [SERVICE_ENVIRONMENT]: environment };
|
||||
}
|
||||
|
||||
// returns the environment url param that should be used
|
||||
// based on the requested environment. If the requested
|
||||
// environment is different from the URL parameter, we'll
|
||||
// return ENVIRONMENT_ALL. If it's not, we'll just return
|
||||
// the current environment URL param
|
||||
export function getNextEnvironmentUrlParam({
|
||||
requestedEnvironment,
|
||||
currentEnvironmentUrlParam,
|
||||
}: {
|
||||
requestedEnvironment?: string;
|
||||
currentEnvironmentUrlParam: Environment;
|
||||
}) {
|
||||
const normalizedRequestedEnvironment =
|
||||
requestedEnvironment || ENVIRONMENT_NOT_DEFINED.value;
|
||||
const normalizedQueryEnvironment =
|
||||
currentEnvironmentUrlParam || ENVIRONMENT_ALL.value;
|
||||
|
||||
if (normalizedRequestedEnvironment === normalizedQueryEnvironment) {
|
||||
return currentEnvironmentUrlParam || ENVIRONMENT_ALL.value;
|
||||
}
|
||||
|
||||
return ENVIRONMENT_ALL.value;
|
||||
}
|
22
x-pack/plugins/ux/common/environment_rt.ts
Normal file
22
x-pack/plugins/ux/common/environment_rt.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import * as t from 'io-ts';
|
||||
import { nonEmptyStringRt } from '@kbn/io-ts-utils';
|
||||
import {
|
||||
ENVIRONMENT_ALL,
|
||||
ENVIRONMENT_NOT_DEFINED,
|
||||
} from './environment_filter_values';
|
||||
|
||||
export const environmentRt = t.type({
|
||||
environment: t.union([
|
||||
t.literal(ENVIRONMENT_NOT_DEFINED.value),
|
||||
t.literal(ENVIRONMENT_ALL.value),
|
||||
nonEmptyStringRt,
|
||||
]),
|
||||
});
|
||||
|
||||
export type Environment = t.TypeOf<typeof environmentRt>['environment'];
|
15
x-pack/plugins/ux/common/fetch_options.ts
Normal file
15
x-pack/plugins/ux/common/fetch_options.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* 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 { HttpFetchOptions } from 'kibana/public';
|
||||
|
||||
export type FetchOptions = Omit<HttpFetchOptions, 'body'> & {
|
||||
pathname: string;
|
||||
isCachable?: boolean;
|
||||
method?: string;
|
||||
body?: any;
|
||||
};
|
8
x-pack/plugins/ux/common/index_pattern_constants.ts
Normal file
8
x-pack/plugins/ux/common/index_pattern_constants.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export const APM_STATIC_INDEX_PATTERN_ID = 'apm_static_index_pattern_id';
|
10
x-pack/plugins/ux/common/transaction_types.ts
Normal file
10
x-pack/plugins/ux/common/transaction_types.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export const TRANSACTION_PAGE_LOAD = 'page-load';
|
||||
export const TRANSACTION_REQUEST = 'request';
|
||||
export const TRANSACTION_ROUTE_CHANGE = 'route-change';
|
12
x-pack/plugins/ux/common/utils/pick_keys.ts
Normal file
12
x-pack/plugins/ux/common/utils/pick_keys.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* 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 { pick } from 'lodash';
|
||||
|
||||
export function pickKeys<T, K extends keyof T>(obj: T, ...keys: K[]) {
|
||||
return pick(obj, keys) as Pick<T, K>;
|
||||
}
|
120
x-pack/plugins/ux/common/ux_ui_filter.ts
Normal file
120
x-pack/plugins/ux/common/ux_ui_filter.ts
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 { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
CLIENT_GEO_COUNTRY_ISO_CODE,
|
||||
SERVICE_NAME,
|
||||
TRANSACTION_URL,
|
||||
USER_AGENT_DEVICE,
|
||||
USER_AGENT_NAME,
|
||||
USER_AGENT_OS,
|
||||
} from './elasticsearch_fieldnames';
|
||||
|
||||
export const uxFiltersByName = {
|
||||
transactionUrl: {
|
||||
title: i18n.translate('xpack.ux.localFilters.titles.transactionUrl', {
|
||||
defaultMessage: 'URL',
|
||||
}),
|
||||
fieldName: TRANSACTION_URL,
|
||||
},
|
||||
transactionUrlExcluded: {
|
||||
title: i18n.translate('xpack.ux.localFilters.titles.transactionUrl', {
|
||||
defaultMessage: 'URL',
|
||||
}),
|
||||
fieldName: TRANSACTION_URL,
|
||||
excluded: true,
|
||||
},
|
||||
browser: {
|
||||
title: i18n.translate('xpack.ux.localFilters.titles.browser', {
|
||||
defaultMessage: 'Browser',
|
||||
}),
|
||||
fieldName: USER_AGENT_NAME,
|
||||
},
|
||||
browserExcluded: {
|
||||
title: i18n.translate('xpack.ux.localFilters.titles.browser', {
|
||||
defaultMessage: 'Browser',
|
||||
}),
|
||||
fieldName: USER_AGENT_NAME,
|
||||
excluded: true,
|
||||
},
|
||||
device: {
|
||||
title: i18n.translate('xpack.ux.localFilters.titles.device', {
|
||||
defaultMessage: 'Device',
|
||||
}),
|
||||
fieldName: USER_AGENT_DEVICE,
|
||||
},
|
||||
deviceExcluded: {
|
||||
title: i18n.translate('xpack.ux.localFilters.titles.device', {
|
||||
defaultMessage: 'Device',
|
||||
}),
|
||||
fieldName: USER_AGENT_DEVICE,
|
||||
excluded: true,
|
||||
},
|
||||
location: {
|
||||
title: i18n.translate('xpack.ux.localFilters.titles.location', {
|
||||
defaultMessage: 'Location',
|
||||
}),
|
||||
fieldName: CLIENT_GEO_COUNTRY_ISO_CODE,
|
||||
},
|
||||
locationExcluded: {
|
||||
title: i18n.translate('xpack.ux.localFilters.titles.location', {
|
||||
defaultMessage: 'Location',
|
||||
}),
|
||||
fieldName: CLIENT_GEO_COUNTRY_ISO_CODE,
|
||||
excluded: true,
|
||||
},
|
||||
os: {
|
||||
title: i18n.translate('xpack.ux.localFilters.titles.os', {
|
||||
defaultMessage: 'OS',
|
||||
}),
|
||||
fieldName: USER_AGENT_OS,
|
||||
},
|
||||
osExcluded: {
|
||||
title: i18n.translate('xpack.ux.localFilters.titles.os', {
|
||||
defaultMessage: 'OS',
|
||||
}),
|
||||
fieldName: USER_AGENT_OS,
|
||||
excluded: true,
|
||||
},
|
||||
serviceName: {
|
||||
title: i18n.translate('xpack.ux.localFilters.titles.serviceName', {
|
||||
defaultMessage: 'Service name',
|
||||
}),
|
||||
fieldName: SERVICE_NAME,
|
||||
},
|
||||
};
|
||||
|
||||
export type UxLocalUIFilterName = keyof typeof uxFiltersByName;
|
||||
|
||||
export interface UxLocalUIFilter {
|
||||
name: UxLocalUIFilterName;
|
||||
title: string;
|
||||
fieldName: string;
|
||||
excluded?: boolean;
|
||||
value: string[];
|
||||
}
|
||||
|
||||
type UxLocalUIFilterMap = {
|
||||
[key in UxLocalUIFilterName]: UxLocalUIFilter;
|
||||
};
|
||||
|
||||
export const uxLocalUIFilterNames = Object.keys(
|
||||
uxFiltersByName
|
||||
) as UxLocalUIFilterName[];
|
||||
|
||||
export const uxLocalUIFilters = uxLocalUIFilterNames.reduce((acc, key) => {
|
||||
const field = uxFiltersByName[key];
|
||||
|
||||
return {
|
||||
...acc,
|
||||
[key]: {
|
||||
...field,
|
||||
name: key,
|
||||
},
|
||||
};
|
||||
}, {} as UxLocalUIFilterMap);
|
14
x-pack/plugins/ux/jest.config.js
Normal file
14
x-pack/plugins/ux/jest.config.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
preset: '@kbn/test',
|
||||
rootDir: path.resolve(__dirname, '../../..'),
|
||||
roots: ['<rootDir>/x-pack/plugins/ux'],
|
||||
};
|
33
x-pack/plugins/ux/kibana.json
Normal file
33
x-pack/plugins/ux/kibana.json
Normal file
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"id": "ux",
|
||||
"version": "8.0.0",
|
||||
"kibanaVersion": "kibana",
|
||||
"requiredPlugins": [
|
||||
"features",
|
||||
"data",
|
||||
"licensing",
|
||||
"triggersActionsUi",
|
||||
"embeddable",
|
||||
"infra",
|
||||
"inspector",
|
||||
"apm"
|
||||
],
|
||||
"optionalPlugins": [
|
||||
"cloud",
|
||||
"usageCollection",
|
||||
"taskManager",
|
||||
"actions",
|
||||
"alerts",
|
||||
"observability",
|
||||
"security",
|
||||
"maps"
|
||||
],
|
||||
"server": false,
|
||||
"ui": true,
|
||||
"configPath": ["xpack", "ux"],
|
||||
"requiredBundles": ["kibanaReact", "observability", "maps"],
|
||||
"owner": {
|
||||
"name": "Uptime",
|
||||
"githubTeam": "uptime"
|
||||
}
|
||||
}
|
126
x-pack/plugins/ux/public/application/application.test.tsx
Normal file
126
x-pack/plugins/ux/public/application/application.test.tsx
Normal file
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* 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 { EuiErrorBoundary } from '@elastic/eui';
|
||||
import { mount } from 'enzyme';
|
||||
|
||||
import { UXAppRoot } from './ux_app';
|
||||
import { RumHome } from '../components/app/rum_dashboard/rum_home';
|
||||
import { coreMock } from '../../../../../src/core/public/mocks';
|
||||
import { createObservabilityRuleTypeRegistryMock } from '../../../observability/public';
|
||||
import { merge } from 'lodash';
|
||||
import { UI_SETTINGS } from '../../../../../src/plugins/data/common';
|
||||
import { embeddablePluginMock } from '../../../../../src/plugins/embeddable/public/mocks';
|
||||
|
||||
jest.mock('../services/rest/data_view', () => ({
|
||||
createStaticDataView: () => Promise.resolve(undefined),
|
||||
}));
|
||||
|
||||
jest.mock('../components/app/rum_dashboard/rum_home', () => ({
|
||||
RumHome: () => <p>Home Mock</p>,
|
||||
}));
|
||||
|
||||
const mockPlugin = {
|
||||
data: {
|
||||
query: {
|
||||
timefilter: { timefilter: { setTime: () => {}, getTime: () => ({}) } },
|
||||
},
|
||||
},
|
||||
observability: {
|
||||
isAlertingExperienceEnabled: () => false,
|
||||
},
|
||||
};
|
||||
|
||||
const mockEmbeddable = embeddablePluginMock.createStartContract();
|
||||
|
||||
mockEmbeddable.getEmbeddableFactory = jest.fn().mockImplementation(() => ({
|
||||
create: () => ({
|
||||
reload: jest.fn(),
|
||||
setRenderTooltipContent: jest.fn(),
|
||||
setLayerList: jest.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
const mockCorePlugins = {
|
||||
embeddable: mockEmbeddable,
|
||||
inspector: {},
|
||||
maps: {},
|
||||
observability: {
|
||||
navigation: {
|
||||
registerSections: () => jest.fn(),
|
||||
PageTemplate: ({ children }: { children: React.ReactNode }) => (
|
||||
<div>hello worlds {children}</div>
|
||||
),
|
||||
},
|
||||
},
|
||||
data: {},
|
||||
};
|
||||
const coreStart = coreMock.createStart({ basePath: '/basepath' });
|
||||
|
||||
const mockCore = merge({}, coreStart, {
|
||||
application: {
|
||||
capabilities: {
|
||||
apm: {},
|
||||
ml: {},
|
||||
},
|
||||
},
|
||||
uiSettings: {
|
||||
get: (key: string) => {
|
||||
const uiSettings: Record<string, unknown> = {
|
||||
[UI_SETTINGS.TIMEPICKER_QUICK_RANGES]: [
|
||||
{
|
||||
from: 'now/d',
|
||||
to: 'now/d',
|
||||
display: 'Today',
|
||||
},
|
||||
{
|
||||
from: 'now/w',
|
||||
to: 'now/w',
|
||||
display: 'This week',
|
||||
},
|
||||
],
|
||||
[UI_SETTINGS.TIMEPICKER_TIME_DEFAULTS]: {
|
||||
from: 'now-15m',
|
||||
to: 'now',
|
||||
},
|
||||
[UI_SETTINGS.TIMEPICKER_REFRESH_INTERVAL_DEFAULTS]: {
|
||||
pause: false,
|
||||
value: 100000,
|
||||
},
|
||||
};
|
||||
return uiSettings[key];
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const mockApmPluginContextValue = {
|
||||
appMountParameters: coreMock.createAppMountParameters('/basepath'),
|
||||
core: mockCore,
|
||||
plugins: mockPlugin,
|
||||
observabilityRuleTypeRegistry: createObservabilityRuleTypeRegistryMock(),
|
||||
corePlugins: mockCorePlugins,
|
||||
deps: {},
|
||||
};
|
||||
|
||||
describe('renderUxApp', () => {
|
||||
it('has an error boundary for the UXAppRoot', async () => {
|
||||
const wrapper = mount(
|
||||
<UXAppRoot {...(mockApmPluginContextValue as any)} />
|
||||
);
|
||||
|
||||
wrapper
|
||||
.find(RumHome)
|
||||
.simulateError(new Error('Oh no, an unexpected error!'));
|
||||
|
||||
expect(wrapper.find(RumHome)).toHaveLength(0);
|
||||
expect(wrapper.find(EuiErrorBoundary)).toHaveLength(1);
|
||||
expect(wrapper.find(EuiErrorBoundary).text()).toMatch(
|
||||
/Error: Oh no, an unexpected error!/
|
||||
);
|
||||
});
|
||||
});
|
|
@ -7,46 +7,55 @@
|
|||
|
||||
import { euiLightVars, euiDarkVars } from '@kbn/ui-theme';
|
||||
import { EuiErrorBoundary } from '@elastic/eui';
|
||||
import { AppMountParameters, CoreStart } from 'kibana/public';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { Route as ReactRouterRoute } from 'react-router-dom';
|
||||
import { RouterProvider, createRouter } from '@kbn/typed-react-router-config';
|
||||
import { Redirect } from 'react-router-dom';
|
||||
import { DefaultTheme, ThemeProvider } from 'styled-components';
|
||||
import { RouterProvider, createRouter } from '@kbn/typed-react-router-config';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { ObservabilityRuleTypeRegistry } from '../../../observability/public';
|
||||
import { RouteComponentProps, RouteProps } from 'react-router-dom';
|
||||
import {
|
||||
AppMountParameters,
|
||||
CoreStart,
|
||||
APP_WRAPPER_CLASS,
|
||||
} from '../../../../../src/core/public';
|
||||
|
||||
import {
|
||||
KibanaContextProvider,
|
||||
RedirectAppLinks,
|
||||
useUiSetting$,
|
||||
} from '../../../../../src/plugins/kibana_react/public';
|
||||
import { APMRouteDefinition } from '../application/routes';
|
||||
import { ScrollToTopOnPathChange } from '../components/app/main/scroll_to_top_on_path_change';
|
||||
|
||||
import {
|
||||
RumHome,
|
||||
DASHBOARD_LABEL,
|
||||
RumHome,
|
||||
} from '../components/app/rum_dashboard/rum_home';
|
||||
import { ApmPluginContext } from '../context/apm_plugin/apm_plugin_context';
|
||||
import { UrlParamsProvider } from '../context/url_params_context/url_params_context';
|
||||
import { ConfigSchema } from '../index';
|
||||
import { ApmPluginSetupDeps, ApmPluginStartDeps } from '../plugin';
|
||||
import { createCallApmApi } from '../services/rest/create_call_apm_api';
|
||||
import { createStaticDataView } from '../services/rest/data_view';
|
||||
import { UXActionMenu } from '../components/app/rum_dashboard/action_menu';
|
||||
import { redirectTo } from '../components/routing/redirect_to';
|
||||
|
||||
import {
|
||||
InspectorContextProvider,
|
||||
useBreadcrumbs,
|
||||
} from '../../../observability/public';
|
||||
import { useApmPluginContext } from '../context/apm_plugin/use_apm_plugin_context';
|
||||
import { APP_WRAPPER_CLASS } from '../../../../../src/core/public';
|
||||
import { KibanaThemeProvider } from '../../../../../src/plugins/kibana_react/public';
|
||||
import { UrlParamsProvider } from '../context/url_params_context/url_params_context';
|
||||
import { createStaticDataView } from '../services/rest/data_view';
|
||||
import { createCallApmApi } from '../services/rest/create_call_apm_api';
|
||||
import { useKibanaServices } from '../hooks/use_kibana_services';
|
||||
|
||||
export const uxRoutes: APMRouteDefinition[] = [
|
||||
export type BreadcrumbTitle<T = {}> =
|
||||
| string
|
||||
| ((props: RouteComponentProps<T>) => string)
|
||||
| null;
|
||||
|
||||
export interface RouteDefinition<T = any> extends RouteProps {
|
||||
breadcrumb: BreadcrumbTitle<T>;
|
||||
}
|
||||
|
||||
export const uxRoutes: RouteDefinition[] = [
|
||||
{
|
||||
exact: true,
|
||||
path: '/',
|
||||
render: redirectTo('/ux'),
|
||||
render: () => <Redirect to="/ux" />,
|
||||
breadcrumb: DASHBOARD_LABEL,
|
||||
},
|
||||
];
|
||||
|
@ -54,18 +63,18 @@ export const uxRoutes: APMRouteDefinition[] = [
|
|||
function UxApp() {
|
||||
const [darkMode] = useUiSetting$<boolean>('theme:darkMode');
|
||||
|
||||
const { core } = useApmPluginContext();
|
||||
const basePath = core.http.basePath.get();
|
||||
const { http } = useKibanaServices();
|
||||
const basePath = http.basePath.get();
|
||||
|
||||
useBreadcrumbs([
|
||||
{
|
||||
text: i18n.translate('xpack.apm.ux.breadcrumbs.root', {
|
||||
text: i18n.translate('xpack.ux.breadcrumbs.root', {
|
||||
defaultMessage: 'User Experience',
|
||||
}),
|
||||
href: basePath + '/app/ux',
|
||||
},
|
||||
{
|
||||
text: i18n.translate('xpack.apm.ux.breadcrumbs.dashboard', {
|
||||
text: i18n.translate('xpack.ux.breadcrumbs.dashboard', {
|
||||
defaultMessage: 'Dashboard',
|
||||
}),
|
||||
},
|
||||
|
@ -84,66 +93,57 @@ function UxApp() {
|
|||
data-test-subj="csmMainContainer"
|
||||
role="main"
|
||||
>
|
||||
<ReactRouterRoute component={ScrollToTopOnPathChange} />
|
||||
<RumHome />
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
|
||||
const uxRouter = createRouter({});
|
||||
export const uxRouter = createRouter({});
|
||||
|
||||
export function UXAppRoot({
|
||||
appMountParameters,
|
||||
core,
|
||||
deps,
|
||||
config,
|
||||
corePlugins: { embeddable, inspector, maps, observability, data },
|
||||
observabilityRuleTypeRegistry,
|
||||
}: {
|
||||
appMountParameters: AppMountParameters;
|
||||
core: CoreStart;
|
||||
deps: ApmPluginSetupDeps;
|
||||
config: ConfigSchema;
|
||||
corePlugins: ApmPluginStartDeps;
|
||||
observabilityRuleTypeRegistry: ObservabilityRuleTypeRegistry;
|
||||
}) {
|
||||
const { history } = appMountParameters;
|
||||
const i18nCore = core.i18n;
|
||||
const plugins = { ...deps, maps };
|
||||
const apmPluginContextValue = {
|
||||
appMountParameters,
|
||||
config,
|
||||
core,
|
||||
inspector,
|
||||
plugins,
|
||||
observability,
|
||||
observabilityRuleTypeRegistry,
|
||||
};
|
||||
|
||||
return (
|
||||
<RedirectAppLinks
|
||||
className={APP_WRAPPER_CLASS}
|
||||
application={core.application}
|
||||
>
|
||||
<ApmPluginContext.Provider value={apmPluginContextValue}>
|
||||
<KibanaContextProvider
|
||||
services={{ ...core, ...plugins, embeddable, data }}
|
||||
>
|
||||
<i18nCore.Context>
|
||||
<RouterProvider history={history} router={uxRouter}>
|
||||
<InspectorContextProvider>
|
||||
<UrlParamsProvider>
|
||||
<EuiErrorBoundary>
|
||||
<UxApp />
|
||||
</EuiErrorBoundary>
|
||||
<UXActionMenu appMountParameters={appMountParameters} />
|
||||
</UrlParamsProvider>
|
||||
</InspectorContextProvider>
|
||||
</RouterProvider>
|
||||
</i18nCore.Context>
|
||||
</KibanaContextProvider>
|
||||
</ApmPluginContext.Provider>
|
||||
<KibanaContextProvider
|
||||
services={{
|
||||
...core,
|
||||
...plugins,
|
||||
inspector,
|
||||
observability,
|
||||
embeddable,
|
||||
data,
|
||||
}}
|
||||
>
|
||||
<i18nCore.Context>
|
||||
<RouterProvider history={history} router={uxRouter}>
|
||||
<InspectorContextProvider>
|
||||
<UrlParamsProvider>
|
||||
<EuiErrorBoundary>
|
||||
<UxApp />
|
||||
</EuiErrorBoundary>
|
||||
<UXActionMenu appMountParameters={appMountParameters} />
|
||||
</UrlParamsProvider>
|
||||
</InspectorContextProvider>
|
||||
</RouterProvider>
|
||||
</i18nCore.Context>
|
||||
</KibanaContextProvider>
|
||||
</RedirectAppLinks>
|
||||
);
|
||||
}
|
||||
|
@ -156,18 +156,14 @@ export const renderApp = ({
|
|||
core,
|
||||
deps,
|
||||
appMountParameters,
|
||||
config,
|
||||
corePlugins,
|
||||
observabilityRuleTypeRegistry,
|
||||
}: {
|
||||
core: CoreStart;
|
||||
deps: ApmPluginSetupDeps;
|
||||
appMountParameters: AppMountParameters;
|
||||
config: ConfigSchema;
|
||||
corePlugins: ApmPluginStartDeps;
|
||||
observabilityRuleTypeRegistry: ObservabilityRuleTypeRegistry;
|
||||
}) => {
|
||||
const { element, theme$ } = appMountParameters;
|
||||
const { element } = appMountParameters;
|
||||
|
||||
createCallApmApi(core);
|
||||
|
||||
|
@ -178,16 +174,12 @@ export const renderApp = ({
|
|||
});
|
||||
|
||||
ReactDOM.render(
|
||||
<KibanaThemeProvider theme$={theme$}>
|
||||
<UXAppRoot
|
||||
appMountParameters={appMountParameters}
|
||||
core={core}
|
||||
deps={deps}
|
||||
config={config}
|
||||
corePlugins={corePlugins}
|
||||
observabilityRuleTypeRegistry={observabilityRuleTypeRegistry}
|
||||
/>
|
||||
</KibanaThemeProvider>,
|
||||
<UXAppRoot
|
||||
appMountParameters={appMountParameters}
|
||||
core={core}
|
||||
deps={deps}
|
||||
corePlugins={corePlugins}
|
||||
/>,
|
||||
element
|
||||
);
|
||||
return () => {
|
|
@ -14,17 +14,17 @@ import {
|
|||
HeaderMenuPortal,
|
||||
} from '../../../../../../observability/public';
|
||||
import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params';
|
||||
import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public';
|
||||
import { AppMountParameters } from '../../../../../../../../src/core/public';
|
||||
import { InspectorHeaderLink } from '../../../shared/apm_header_action_menu/inspector_header_link';
|
||||
import { SERVICE_NAME } from '../../../../../common/elasticsearch_fieldnames';
|
||||
import { UxInspectorHeaderLink } from './inpector_link';
|
||||
import { useKibanaServices } from '../../../../hooks/use_kibana_services';
|
||||
|
||||
const ANALYZE_DATA = i18n.translate('xpack.apm.analyzeDataButtonLabel', {
|
||||
const ANALYZE_DATA = i18n.translate('xpack.ux.analyzeDataButtonLabel', {
|
||||
defaultMessage: 'Explore data',
|
||||
});
|
||||
|
||||
const ANALYZE_MESSAGE = i18n.translate(
|
||||
'xpack.apm.analyzeDataButtonLabel.message',
|
||||
'xpack.ux.analyzeDataButtonLabel.message',
|
||||
{
|
||||
defaultMessage:
|
||||
'Explore Data allows you to select and filter result data in any dimension and look for the cause or impact of performance problems.',
|
||||
|
@ -36,9 +36,7 @@ export function UXActionMenu({
|
|||
}: {
|
||||
appMountParameters: AppMountParameters;
|
||||
}) {
|
||||
const {
|
||||
services: { http },
|
||||
} = useKibana();
|
||||
const { http, application } = useKibanaServices();
|
||||
const { urlParams } = useLegacyUrlParams();
|
||||
const { rangeTo, rangeFrom, serviceName } = urlParams;
|
||||
|
||||
|
@ -57,11 +55,9 @@ export function UXActionMenu({
|
|||
},
|
||||
],
|
||||
},
|
||||
http?.basePath.get()
|
||||
http.basePath.get()
|
||||
);
|
||||
|
||||
const kibana = useKibana();
|
||||
|
||||
return (
|
||||
<HeaderMenuPortal
|
||||
setHeaderActionMenu={appMountParameters.setHeaderActionMenu}
|
||||
|
@ -82,15 +78,13 @@ export function UXActionMenu({
|
|||
color="primary"
|
||||
iconType="indexOpen"
|
||||
iconSide="left"
|
||||
href={kibana.services?.application?.getUrlForApp(
|
||||
'/home#/tutorial/apm'
|
||||
)}
|
||||
href={application.getUrlForApp('/home#/tutorial/apm')}
|
||||
>
|
||||
{i18n.translate('xpack.apm.addDataButtonLabel', {
|
||||
{i18n.translate('xpack.ux.addDataButtonLabel', {
|
||||
defaultMessage: 'Add data',
|
||||
})}
|
||||
</EuiHeaderLink>
|
||||
<InspectorHeaderLink />
|
||||
<UxInspectorHeaderLink />
|
||||
</EuiHeaderLinks>
|
||||
</HeaderMenuPortal>
|
||||
);
|
|
@ -8,19 +8,17 @@
|
|||
import { EuiHeaderLink } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context';
|
||||
import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public';
|
||||
import { enableInspectEsQueries } from '../../../../../../observability/common/ui_settings_keys';
|
||||
import { useInspectorContext } from '../../../../../../observability/public';
|
||||
import {
|
||||
useInspectorContext,
|
||||
enableInspectEsQueries,
|
||||
} from '../../../../../../observability/public';
|
||||
import { useKibanaServices } from '../../../../hooks/use_kibana_services';
|
||||
|
||||
export function UxInspectorHeaderLink() {
|
||||
const { inspector } = useApmPluginContext();
|
||||
const { inspectorAdapters } = useInspectorContext();
|
||||
const {
|
||||
services: { uiSettings },
|
||||
} = useKibana();
|
||||
const { uiSettings, inspector } = useKibanaServices();
|
||||
|
||||
const isInspectorEnabled = uiSettings?.get<boolean>(enableInspectEsQueries);
|
||||
const isInspectorEnabled = uiSettings.get<boolean>(enableInspectEsQueries);
|
||||
|
||||
const inspect = () => {
|
||||
inspector.open(inspectorAdapters);
|
||||
|
@ -32,7 +30,7 @@ export function UxInspectorHeaderLink() {
|
|||
|
||||
return (
|
||||
<EuiHeaderLink color="primary" onClick={inspect}>
|
||||
{i18n.translate('xpack.apm.inspectButtonText', {
|
||||
{i18n.translate('xpack.ux.inspectButtonText', {
|
||||
defaultMessage: 'Inspect',
|
||||
})}
|
||||
</EuiHeaderLink>
|
|
@ -8,6 +8,7 @@
|
|||
import React from 'react';
|
||||
import { EuiSuperSelect } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import {
|
||||
CLIENT_GEO_COUNTRY_ISO_CODE,
|
||||
USER_AGENT_DEVICE,
|
||||
|
@ -31,35 +32,35 @@ export function BreakdownFilter({
|
|||
|
||||
const items: BreakdownItem[] = [
|
||||
{
|
||||
name: i18n.translate('xpack.apm.csm.breakDownFilter.noBreakdown', {
|
||||
name: i18n.translate('xpack.ux.breakDownFilter.noBreakdown', {
|
||||
defaultMessage: 'No breakdown',
|
||||
}),
|
||||
fieldName: NO_BREAKDOWN,
|
||||
type: 'category',
|
||||
},
|
||||
{
|
||||
name: i18n.translate('xpack.apm.csm.breakdownFilter.browser', {
|
||||
name: i18n.translate('xpack.ux.breakdownFilter.browser', {
|
||||
defaultMessage: 'Browser',
|
||||
}),
|
||||
fieldName: USER_AGENT_NAME,
|
||||
type: 'category',
|
||||
},
|
||||
{
|
||||
name: i18n.translate('xpack.apm.csm.breakdownFilter.os', {
|
||||
name: i18n.translate('xpack.ux.breakdownFilter.os', {
|
||||
defaultMessage: 'OS',
|
||||
}),
|
||||
fieldName: USER_AGENT_OS,
|
||||
type: 'category',
|
||||
},
|
||||
{
|
||||
name: i18n.translate('xpack.apm.csm.breakdownFilter.device', {
|
||||
name: i18n.translate('xpack.ux.breakdownFilter.device', {
|
||||
defaultMessage: 'Device',
|
||||
}),
|
||||
fieldName: USER_AGENT_DEVICE,
|
||||
type: 'category',
|
||||
},
|
||||
{
|
||||
name: i18n.translate('xpack.apm.csm.breakdownFilter.location', {
|
||||
name: i18n.translate('xpack.ux.breakdownFilter.location', {
|
||||
defaultMessage: 'Location',
|
||||
}),
|
||||
fieldName: CLIENT_GEO_COUNTRY_ISO_CODE,
|
|
@ -32,9 +32,9 @@ import { PercentileAnnotations } from '../page_load_distribution/percentile_anno
|
|||
import { I18LABELS } from '../translations';
|
||||
import { ChartWrapper } from '../chart_wrapper';
|
||||
import { PercentileRange } from '../page_load_distribution';
|
||||
import { BreakdownItem } from '../../../../../typings/ui_filters';
|
||||
import { useUiSetting$ } from '../../../../../../../../src/plugins/kibana_react/public';
|
||||
import { BreakdownSeries } from '../page_load_distribution/breakdown_series';
|
||||
import { BreakdownItem } from '../../../../../typings/ui_filters';
|
||||
|
||||
interface PageLoadData {
|
||||
pageLoadDistribution: Array<{ x: number; y: number }>;
|
|
@ -29,9 +29,9 @@ import React from 'react';
|
|||
import { useHistory } from 'react-router-dom';
|
||||
import { useUiSetting$ } from '../../../../../../../../src/plugins/kibana_react/public';
|
||||
import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params';
|
||||
import { fromQuery, toQuery } from '../../../shared/links/url_helpers';
|
||||
import { ChartWrapper } from '../chart_wrapper';
|
||||
import { I18LABELS } from '../translations';
|
||||
import { fromQuery, toQuery } from '../../../../../../observability/public';
|
||||
|
||||
interface Props {
|
||||
data?: {
|
|
@ -23,7 +23,7 @@ export function EmptyStateLoading() {
|
|||
<EuiSpacer />
|
||||
<EuiTitle size="l">
|
||||
<h2>
|
||||
{i18n.translate('xpack.apm.emptyState.loadingMessage', {
|
||||
{i18n.translate('xpack.ux.emptyState.loadingMessage', {
|
||||
defaultMessage: 'Loading…',
|
||||
})}
|
||||
</h2>
|
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
* 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 { EuiSelect } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { History } from 'history';
|
||||
import React from 'react';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import { fromQuery, toQuery } from '../../../../../../observability/public';
|
||||
import { useEnvironmentsFetcher } from '../../../../hooks/use_environments_fetcher';
|
||||
import {
|
||||
ENVIRONMENT_ALL,
|
||||
ENVIRONMENT_NOT_DEFINED,
|
||||
} from '../../../../../common/environment_filter_values';
|
||||
import { useUxUrlParams } from '../../../../context/url_params_context/use_ux_url_params';
|
||||
|
||||
function updateEnvironmentUrl(
|
||||
history: History,
|
||||
location: ReturnType<typeof useLocation>,
|
||||
environment: string
|
||||
) {
|
||||
history.push({
|
||||
...location,
|
||||
search: fromQuery({
|
||||
...toQuery(location.search),
|
||||
environment,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
const SEPARATOR_OPTION = {
|
||||
text: `- ${i18n.translate(
|
||||
'xpack.ux.filter.environment.selectEnvironmentLabel',
|
||||
{ defaultMessage: 'Select environment' }
|
||||
)} -`,
|
||||
disabled: true,
|
||||
};
|
||||
|
||||
function getOptions(environments: string[]) {
|
||||
const environmentOptions = environments
|
||||
.filter((env) => env !== ENVIRONMENT_NOT_DEFINED.value)
|
||||
.map((environment) => ({
|
||||
value: environment,
|
||||
text: environment,
|
||||
}));
|
||||
|
||||
return [
|
||||
ENVIRONMENT_ALL,
|
||||
...(environments.includes(ENVIRONMENT_NOT_DEFINED.value)
|
||||
? [ENVIRONMENT_NOT_DEFINED]
|
||||
: []),
|
||||
...(environmentOptions.length > 0 ? [SEPARATOR_OPTION] : []),
|
||||
...environmentOptions,
|
||||
];
|
||||
}
|
||||
|
||||
export interface EnvironmentFilterProps {
|
||||
start?: string;
|
||||
end?: string;
|
||||
environment?: string;
|
||||
serviceName?: string;
|
||||
}
|
||||
|
||||
export function EnvironmentFilter({
|
||||
start,
|
||||
end,
|
||||
environment,
|
||||
serviceName,
|
||||
}: EnvironmentFilterProps) {
|
||||
const history = useHistory();
|
||||
const location = useLocation();
|
||||
const { environments, status = 'loading' } = useEnvironmentsFetcher({
|
||||
serviceName,
|
||||
start,
|
||||
end,
|
||||
});
|
||||
|
||||
// Set the min-width so we don't see as much collapsing of the select during
|
||||
// the loading state. 200px is what is looks like if "production" is
|
||||
// the contents.
|
||||
const minWidth = 200;
|
||||
|
||||
const options = getOptions(environments);
|
||||
|
||||
return (
|
||||
<EuiSelect
|
||||
fullWidth
|
||||
prepend={i18n.translate('xpack.ux.filter.environment.label', {
|
||||
defaultMessage: 'Environment',
|
||||
})}
|
||||
options={options}
|
||||
value={environment}
|
||||
onChange={(event) => {
|
||||
updateEnvironmentUrl(history, location, event.target.value);
|
||||
}}
|
||||
isLoading={status === 'loading'}
|
||||
style={{ minWidth }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function UxEnvironmentFilter() {
|
||||
const {
|
||||
urlParams: { start, end, environment, serviceName },
|
||||
} = useUxUrlParams();
|
||||
|
||||
return (
|
||||
<EnvironmentFilter
|
||||
start={start}
|
||||
end={end}
|
||||
environment={environment}
|
||||
serviceName={serviceName}
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -9,6 +9,6 @@ import { useFetcher } from '../../../../hooks/use_fetcher';
|
|||
|
||||
export function useHasRumData() {
|
||||
return useFetcher((callApmApi) => {
|
||||
return callApmApi('GET /api/apm/observability_overview/has_rum_data');
|
||||
return callApmApi('GET /api/apm/observability_overview/has_rum_data', {});
|
||||
}, []);
|
||||
}
|
|
@ -7,18 +7,16 @@
|
|||
|
||||
import { omit } from 'lodash';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params';
|
||||
import { getExcludedName } from '../local_uifilters';
|
||||
|
||||
import { fromQuery, toQuery } from '../../../../../../observability/public';
|
||||
import { removeUndefinedProps } from '../../../../context/url_params_context/helpers';
|
||||
import {
|
||||
uxFiltersByName,
|
||||
UxLocalUIFilter,
|
||||
UxLocalUIFilterName,
|
||||
} from '../../../../../common/ux_ui_filter';
|
||||
import {
|
||||
fromQuery,
|
||||
toQuery,
|
||||
} from '../../../../components/shared/links/url_helpers';
|
||||
import { removeUndefinedProps } from '../../../../context/url_params_context/helpers';
|
||||
import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params';
|
||||
import { getExcludedName } from '../local_ui_filters';
|
||||
|
||||
export type FiltersUIHook = ReturnType<typeof useLocalUIFilters>;
|
||||
|
|
@ -14,15 +14,16 @@ import {
|
|||
EuiTitle,
|
||||
EuiStat,
|
||||
EuiToolTip,
|
||||
EuiLink,
|
||||
} from '@elastic/eui';
|
||||
import numeral from '@elastic/numeral';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params';
|
||||
import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher';
|
||||
import { useFetcher } from '../../../../hooks/use_fetcher';
|
||||
import { I18LABELS } from '../translations';
|
||||
import { CsmSharedContext } from '../csm_shared_context';
|
||||
import { ErrorDetailLink } from '../../../shared/links/apm/error_detail_link';
|
||||
import { FETCH_STATUS } from '../../../../../../observability/public';
|
||||
|
||||
interface JSErrorItem {
|
||||
errorMessage: string;
|
||||
|
@ -67,12 +68,9 @@ export function JSErrors() {
|
|||
field: 'errorMessage',
|
||||
name: I18LABELS.errorMessage,
|
||||
render: (errorMessage: string, item: JSErrorItem) => (
|
||||
<ErrorDetailLink
|
||||
serviceName={serviceName!}
|
||||
errorGroupId={item.errorGroupId as string}
|
||||
>
|
||||
<EuiLink href={`/services/${serviceName}/errors/${item.errorGroupId}`}>
|
||||
{errorMessage}
|
||||
</ErrorDetailLink>
|
||||
</EuiLink>
|
||||
),
|
||||
},
|
||||
{
|
||||
|
@ -81,7 +79,7 @@ export function JSErrors() {
|
|||
align: 'right' as const,
|
||||
render: (count: number) => (
|
||||
<FormattedMessage
|
||||
id="xpack.apm.ux.jsErrors.percent"
|
||||
id="xpack.ux.jsErrors.percent"
|
||||
defaultMessage="{pageLoadPercent} %"
|
||||
values={{
|
||||
pageLoadPercent: ((count / totalPageViews) * 100).toFixed(1),
|
||||
|
@ -135,7 +133,7 @@ export function JSErrors() {
|
|||
loading={status === FETCH_STATUS.LOADING}
|
||||
error={
|
||||
status === FETCH_STATUS.FAILURE
|
||||
? i18n.translate('xpack.apm.rum.jsErrorsTable.errorMessage', {
|
||||
? i18n.translate('xpack.ux.jsErrorsTable.errorMessage', {
|
||||
defaultMessage: 'Failed to fetch',
|
||||
})
|
||||
: ''
|
|
@ -8,7 +8,7 @@
|
|||
import { EuiSpacer } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { useTrackPageview } from '../../../../../observability/public';
|
||||
import { LocalUIFilters } from './local_ui_filters';
|
||||
import { LocalUIFilters } from './local_uifilters';
|
||||
import { RumDashboard } from './rum_dashboard';
|
||||
|
||||
export function RumOverview() {
|
|
@ -16,25 +16,27 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { ESFilter } from 'src/core/types/elasticsearch';
|
||||
import { useLocalUIFilters } from '../hooks/use_local_ui_filters';
|
||||
import { useLocalUIFilters } from '../hooks/use_local_uifilters';
|
||||
|
||||
import { useBreakpoints } from '../../../../hooks/use_breakpoints';
|
||||
import { FieldValueSuggestions } from '../../../../../../observability/public';
|
||||
import { URLFilter } from '../url_filter';
|
||||
import { SelectedFilters } from './selected_filters';
|
||||
import { useDataView } from './use_data_view';
|
||||
import { environmentQuery } from './queries';
|
||||
import { useUxUrlParams } from '../../../../context/url_params_context/use_ux_url_params';
|
||||
|
||||
import {
|
||||
uxFiltersByName,
|
||||
UxLocalUIFilterName,
|
||||
uxLocalUIFilterNames,
|
||||
} from '../../../../../common/ux_ui_filter';
|
||||
import { useBreakpoints } from '../../../../hooks/use_breakpoints';
|
||||
import { FieldValueSuggestions } from '../../../../../../observability/public';
|
||||
import { URLFilter } from '../url_filter';
|
||||
import { SelectedFilters } from './selected_filters';
|
||||
import { ENVIRONMENT_ALL } from '../../../../../common/environment_filter_values';
|
||||
import { TRANSACTION_PAGE_LOAD } from '../../../../../common/transaction_types';
|
||||
import {
|
||||
SERVICE_NAME,
|
||||
TRANSACTION_TYPE,
|
||||
} from '../../../../../common/elasticsearch_fieldnames';
|
||||
import { TRANSACTION_PAGE_LOAD } from '../../../../../common/transaction_types';
|
||||
import { useDataView } from './use_data_view';
|
||||
import { environmentQuery } from './queries';
|
||||
import { ENVIRONMENT_ALL } from '../../../../../common/environment_filter_values';
|
||||
import { useUxUrlParams } from '../../../../context/url_params_context/use_ux_url_params';
|
||||
|
||||
const filterNames: UxLocalUIFilterName[] = [
|
||||
'location',
|
||||
|
@ -89,7 +91,7 @@ function LocalUIFilters() {
|
|||
const title = (
|
||||
<EuiTitle size="s">
|
||||
<h3>
|
||||
{i18n.translate('xpack.apm.localFiltersTitle', {
|
||||
{i18n.translate('xpack.ux.localFiltersTitle', {
|
||||
defaultMessage: 'Filters',
|
||||
})}
|
||||
</h3>
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import { ESFilter } from 'src/core/types/elasticsearch';
|
||||
|
||||
import { SERVICE_ENVIRONMENT } from '../../../../../common/elasticsearch_fieldnames';
|
||||
import {
|
||||
ENVIRONMENT_ALL,
|
|
@ -11,10 +11,10 @@ import { i18n } from '@kbn/i18n';
|
|||
import styled from 'styled-components';
|
||||
import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params';
|
||||
import { FilterValueLabel } from '../../../../../../observability/public';
|
||||
import { FiltersUIHook } from '../hooks/use_local_ui_filters';
|
||||
import { UxLocalUIFilterName } from '../../../../../common/ux_ui_filter';
|
||||
import { FiltersUIHook } from '../hooks/use_local_uifilters';
|
||||
import { IndexPattern } from '../../../../../../../../src/plugins/data/common';
|
||||
import { SelectedWildcards } from './selected_wildcards';
|
||||
import { UxLocalUIFilterName } from '../../../../../common/ux_ui_filter';
|
||||
|
||||
interface Props {
|
||||
indexPattern?: IndexPattern;
|
||||
|
@ -84,7 +84,7 @@ export function SelectedFilters({
|
|||
onClick={clearValues}
|
||||
data-cy="clearFilters"
|
||||
>
|
||||
{i18n.translate('xpack.apm.clearFilters', {
|
||||
{i18n.translate('xpack.ux.clearFilters', {
|
||||
defaultMessage: 'Clear filters',
|
||||
})}
|
||||
</EuiButtonEmpty>
|
|
@ -8,9 +8,12 @@
|
|||
import * as React from 'react';
|
||||
import { useCallback } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { FilterValueLabel } from '../../../../../../observability/public';
|
||||
import {
|
||||
FilterValueLabel,
|
||||
fromQuery,
|
||||
toQuery,
|
||||
} from '../../../../../../observability/public';
|
||||
import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params';
|
||||
import { fromQuery, toQuery } from '../../../shared/links/url_helpers';
|
||||
import { TRANSACTION_URL } from '../../../../../common/elasticsearch_fieldnames';
|
||||
import { IndexPattern } from '../../../../../../../../src/plugins/data_views/common';
|
||||
|
|
@ -5,11 +5,11 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { useDynamicDataViewFetcher } from '../../../../hooks/use_dynamic_data_view';
|
||||
import { DataView } from '../../../../../../../../src/plugins/data/common';
|
||||
import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public';
|
||||
import { useFetcher } from '../../../../hooks/use_fetcher';
|
||||
import { DataPublicPluginStart } from '../../../../../../../../src/plugins/data/public';
|
||||
import { useDynamicDataViewFetcher } from '../../../../hooks/use_dynamic_data_view';
|
||||
|
||||
export function useDataView() {
|
||||
const { dataView } = useDynamicDataViewFetcher();
|
|
@ -19,10 +19,10 @@ import { useFetcher } from '../../../../hooks/use_fetcher';
|
|||
import { I18LABELS } from '../translations';
|
||||
import { BreakdownFilter } from '../breakdowns/breakdown_filter';
|
||||
import { PageLoadDistChart } from '../charts/page_load_dist_chart';
|
||||
import { BreakdownItem } from '../../../../../typings/ui_filters';
|
||||
import { ResetPercentileZoom } from './reset_percentile_zoom';
|
||||
import { createExploratoryViewUrl } from '../../../../../../observability/public';
|
||||
import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public';
|
||||
import { useKibanaServices } from '../../../../hooks/use_kibana_services';
|
||||
import { BreakdownItem } from '../../../../../typings/ui_filters';
|
||||
|
||||
export interface PercentileRange {
|
||||
min?: number | null;
|
||||
|
@ -30,9 +30,7 @@ export interface PercentileRange {
|
|||
}
|
||||
|
||||
export function PageLoadDistribution() {
|
||||
const {
|
||||
services: { http },
|
||||
} = useKibana();
|
||||
const { http } = useKibanaServices();
|
||||
|
||||
const { urlParams, uxUiFilters } = useLegacyUrlParams();
|
||||
|
||||
|
@ -99,7 +97,7 @@ export function PageLoadDistribution() {
|
|||
},
|
||||
],
|
||||
},
|
||||
http?.basePath.get()
|
||||
http.basePath.get()
|
||||
);
|
||||
|
||||
const showAnalyzeButton = false;
|
||||
|
@ -131,7 +129,7 @@ export function PageLoadDistribution() {
|
|||
href={exploratoryViewLink}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.apm.csm.pageViews.analyze"
|
||||
id="xpack.ux.pageViews.analyze"
|
||||
defaultMessage="Analyze"
|
||||
/>
|
||||
</EuiButton>
|
|
@ -19,14 +19,12 @@ import { useFetcher } from '../../../../hooks/use_fetcher';
|
|||
import { I18LABELS } from '../translations';
|
||||
import { BreakdownFilter } from '../breakdowns/breakdown_filter';
|
||||
import { PageViewsChart } from '../charts/page_views_chart';
|
||||
import { BreakdownItem } from '../../../../../typings/ui_filters';
|
||||
import { createExploratoryViewUrl } from '../../../../../../observability/public';
|
||||
import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public';
|
||||
import { useKibanaServices } from '../../../../hooks/use_kibana_services';
|
||||
import { BreakdownItem } from '../../../../../typings/ui_filters';
|
||||
|
||||
export function PageViewsTrend() {
|
||||
const {
|
||||
services: { http },
|
||||
} = useKibana();
|
||||
const { http } = useKibanaServices();
|
||||
|
||||
const { urlParams, uxUiFilters } = useLegacyUrlParams();
|
||||
const { serviceName } = uxUiFilters;
|
||||
|
@ -74,7 +72,7 @@ export function PageViewsTrend() {
|
|||
},
|
||||
],
|
||||
},
|
||||
http?.basePath.get()
|
||||
http.basePath.get()
|
||||
);
|
||||
|
||||
const showAnalyzeButton = false;
|
||||
|
@ -102,7 +100,7 @@ export function PageViewsTrend() {
|
|||
href={exploratoryViewLink}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.apm.csm.pageViews.analyze"
|
||||
id="xpack.ux.pageViews.analyze"
|
||||
defaultMessage="Analyze"
|
||||
/>
|
||||
</EuiButton>
|
|
@ -8,8 +8,8 @@
|
|||
import React from 'react';
|
||||
import { ServiceNameFilter } from '../url_filter/service_name_filter';
|
||||
import { useFetcher } from '../../../../hooks/use_fetcher';
|
||||
import { RUM_AGENT_NAMES } from '../../../../../common/agent_name';
|
||||
import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params';
|
||||
import { RUM_AGENT_NAMES } from '../../../../../common/agent_name';
|
||||
|
||||
export function WebApplicationSelect() {
|
||||
const {
|
|
@ -7,7 +7,8 @@
|
|||
import React from 'react';
|
||||
import { useUxUrlParams } from '../../../../context/url_params_context/use_ux_url_params';
|
||||
import { useDateRangeRedirect } from '../../../../hooks/use_date_range_redirect';
|
||||
import { DatePicker } from '../../../shared/date_picker';
|
||||
import { DatePicker } from '../../../../../../observability/public';
|
||||
import { clearCache } from '../../../../services/rest/call_api';
|
||||
|
||||
export function RumDatePicker() {
|
||||
const {
|
||||
|
@ -28,6 +29,7 @@ export function RumDatePicker() {
|
|||
refreshPaused={refreshPaused}
|
||||
refreshInterval={refreshInterval}
|
||||
onTimeRangeRefresh={({ start, end }) => {
|
||||
clearCache();
|
||||
refreshTimeRange({ rangeFrom: start, rangeTo: end });
|
||||
}}
|
||||
/>
|
|
@ -8,24 +8,25 @@
|
|||
import React, { Fragment } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiFlexGroup, EuiTitle, EuiFlexItem } from '@elastic/eui';
|
||||
import { RumOverview } from '../rum_dashboard';
|
||||
import { RumDashboard } from './rum_dashboard';
|
||||
import { CsmSharedContextProvider } from './csm_shared_context';
|
||||
import { WebApplicationSelect } from './panels/web_application_select';
|
||||
import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context';
|
||||
import { UxEnvironmentFilter } from '../../shared/environment_filter';
|
||||
import { UserPercentile } from './user_percentile';
|
||||
import { useBreakpoints } from '../../../hooks/use_breakpoints';
|
||||
import { KibanaPageTemplateProps } from '../../../../../../../src/plugins/kibana_react/public';
|
||||
import { useHasRumData } from './hooks/use_has_rum_data';
|
||||
import { RumDatePicker } from './rum_datepicker';
|
||||
import { EmptyStateLoading } from './empty_state_loading';
|
||||
import { useKibanaServices } from '../../../hooks/use_kibana_services';
|
||||
import { UxEnvironmentFilter } from './environment_filter';
|
||||
|
||||
export const DASHBOARD_LABEL = i18n.translate('xpack.apm.ux.title', {
|
||||
export const DASHBOARD_LABEL = i18n.translate('xpack.ux.title', {
|
||||
defaultMessage: 'Dashboard',
|
||||
});
|
||||
|
||||
export function RumHome() {
|
||||
const { core, observability } = useApmPluginContext();
|
||||
const { docLinks, http, observability } = useKibanaServices();
|
||||
|
||||
const PageTemplateComponent = observability.navigation.PageTemplate;
|
||||
|
||||
const { data: rumHasData, status } = useHasRumData();
|
||||
|
@ -33,25 +34,25 @@ export function RumHome() {
|
|||
const noDataConfig: KibanaPageTemplateProps['noDataConfig'] =
|
||||
!rumHasData?.hasData
|
||||
? {
|
||||
solution: i18n.translate('xpack.apm.ux.overview.solutionName', {
|
||||
solution: i18n.translate('xpack.ux.overview.solutionName', {
|
||||
defaultMessage: 'Observability',
|
||||
}),
|
||||
actions: {
|
||||
elasticAgent: {
|
||||
title: i18n.translate('xpack.apm.ux.overview.beatsCard.title', {
|
||||
defaultMessage: 'Add the APM integration',
|
||||
title: i18n.translate('xpack.ux.overview.beatsCard.title', {
|
||||
defaultMessage: 'Add RUM data',
|
||||
}),
|
||||
description: i18n.translate(
|
||||
'xpack.apm.ux.overview.beatsCard.description',
|
||||
'xpack.ux.overview.beatsCard.description',
|
||||
{
|
||||
defaultMessage:
|
||||
'Enable RUM with the APM agent to collect user experience data.',
|
||||
}
|
||||
),
|
||||
href: core.http.basePath.prepend(`/app/home#/tutorial/apm`),
|
||||
href: http.basePath.prepend(`/app/home#/tutorial/apm`),
|
||||
},
|
||||
},
|
||||
docsLink: core.docLinks.links.observability.guide,
|
||||
docsLink: docLinks.links.observability.guide,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
|
@ -66,7 +67,7 @@ export function RumHome() {
|
|||
>
|
||||
{isLoading && <EmptyStateLoading />}
|
||||
<div style={{ visibility: isLoading ? 'hidden' : 'initial' }}>
|
||||
<RumOverview />
|
||||
<RumDashboard />
|
||||
</div>
|
||||
</PageTemplateComponent>
|
||||
</CsmSharedContextProvider>
|
|
@ -0,0 +1,179 @@
|
|||
/*
|
||||
* 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';
|
||||
|
||||
export const I18LABELS = {
|
||||
dataMissing: i18n.translate('xpack.ux.dashboard.dataMissing', {
|
||||
defaultMessage: 'N/A',
|
||||
}),
|
||||
totalPageLoad: i18n.translate('xpack.ux.dashboard.totalPageLoad', {
|
||||
defaultMessage: 'Total',
|
||||
}),
|
||||
backEnd: i18n.translate('xpack.ux.dashboard.backend', {
|
||||
defaultMessage: 'Backend',
|
||||
}),
|
||||
frontEnd: i18n.translate('xpack.ux.dashboard.frontend', {
|
||||
defaultMessage: 'Frontend',
|
||||
}),
|
||||
pageViews: i18n.translate('xpack.ux.dashboard.pageViews', {
|
||||
defaultMessage: 'Total page views',
|
||||
}),
|
||||
percPageLoaded: i18n.translate('xpack.ux.dashboard.pagesLoaded.label', {
|
||||
defaultMessage: 'Pages loaded',
|
||||
}),
|
||||
pageLoadTime: i18n.translate('xpack.ux.dashboard.pageLoadTime.label', {
|
||||
defaultMessage: 'Page load time (seconds)',
|
||||
}),
|
||||
pageLoadTimes: i18n.translate('xpack.ux.dashboard.pageLoadTimes.label', {
|
||||
defaultMessage: 'Page load times',
|
||||
}),
|
||||
pageLoadDuration: i18n.translate(
|
||||
'xpack.ux.dashboard.pageLoadDuration.label',
|
||||
{
|
||||
defaultMessage: 'Page load duration',
|
||||
}
|
||||
),
|
||||
pageLoad: i18n.translate('xpack.ux.dashboard.pageLoad.label', {
|
||||
defaultMessage: 'Page load',
|
||||
}),
|
||||
pageLoadDistribution: i18n.translate(
|
||||
'xpack.ux.dashboard.pageLoadDistribution.label',
|
||||
{
|
||||
defaultMessage: 'Page load distribution',
|
||||
}
|
||||
),
|
||||
jsErrors: i18n.translate('xpack.ux.dashboard.impactfulMetrics.jsErrors', {
|
||||
defaultMessage: 'JavaScript errors',
|
||||
}),
|
||||
highTrafficPages: i18n.translate(
|
||||
'xpack.ux.dashboard.impactfulMetrics.highTrafficPages',
|
||||
{
|
||||
defaultMessage: 'High traffic pages',
|
||||
}
|
||||
),
|
||||
resetZoom: i18n.translate('xpack.ux.dashboard.resetZoom.label', {
|
||||
defaultMessage: 'Reset zoom',
|
||||
}),
|
||||
overall: i18n.translate('xpack.ux.dashboard.overall.label', {
|
||||
defaultMessage: 'Overall',
|
||||
}),
|
||||
selectBreakdown: i18n.translate('xpack.ux.filterGroup.selectBreakdown', {
|
||||
defaultMessage: 'Select breakdown',
|
||||
}),
|
||||
breakdown: i18n.translate('xpack.ux.filterGroup.breakdown', {
|
||||
defaultMessage: 'Breakdown',
|
||||
}),
|
||||
seconds: i18n.translate('xpack.ux.filterGroup.seconds', {
|
||||
defaultMessage: 'seconds',
|
||||
}),
|
||||
coreWebVitals: i18n.translate('xpack.ux.filterGroup.coreWebVitals', {
|
||||
defaultMessage: 'Core web vitals',
|
||||
}),
|
||||
browser: i18n.translate('xpack.ux.visitorBreakdown.browser', {
|
||||
defaultMessage: 'Browser',
|
||||
}),
|
||||
operatingSystem: i18n.translate('xpack.ux.visitorBreakdown.operatingSystem', {
|
||||
defaultMessage: 'Operating system',
|
||||
}),
|
||||
metrics: i18n.translate('xpack.ux.metrics', {
|
||||
defaultMessage: 'Metrics',
|
||||
}),
|
||||
median: i18n.translate('xpack.ux.median', {
|
||||
defaultMessage: 'median',
|
||||
}),
|
||||
avgPageLoadDuration: i18n.translate(
|
||||
'xpack.ux.visitorBreakdownMap.avgPageLoadDuration',
|
||||
{
|
||||
defaultMessage: 'Average page load duration',
|
||||
}
|
||||
),
|
||||
pageLoadDurationByRegion: i18n.translate(
|
||||
'xpack.ux.visitorBreakdownMap.pageLoadDurationByRegion',
|
||||
{
|
||||
defaultMessage: 'Page load duration by region (avg.)',
|
||||
}
|
||||
),
|
||||
filterByUrl: i18n.translate('xpack.ux.filters.filterByUrl', {
|
||||
defaultMessage: 'Filter by URL',
|
||||
}),
|
||||
getSearchResultsLabel: (total: number) =>
|
||||
i18n.translate('xpack.ux.filters.searchResults', {
|
||||
defaultMessage: '{total} Search results',
|
||||
values: { total },
|
||||
}),
|
||||
topPages: i18n.translate('xpack.ux.filters.topPages', {
|
||||
defaultMessage: 'Top pages',
|
||||
}),
|
||||
select: i18n.translate('xpack.ux.filters.select', {
|
||||
defaultMessage: 'Select',
|
||||
}),
|
||||
url: i18n.translate('xpack.ux.filters.url', {
|
||||
defaultMessage: 'Url',
|
||||
}),
|
||||
loadingResults: i18n.translate('xpack.ux.filters.url.loadingResults', {
|
||||
defaultMessage: 'Loading results',
|
||||
}),
|
||||
noResults: i18n.translate('xpack.ux.filters.url.noResults', {
|
||||
defaultMessage: 'No results available',
|
||||
}),
|
||||
totalErrors: i18n.translate('xpack.ux.jsErrors.totalErrors', {
|
||||
defaultMessage: 'Total errors',
|
||||
}),
|
||||
errorRate: i18n.translate('xpack.ux.jsErrors.errorRate', {
|
||||
defaultMessage: 'Error rate',
|
||||
}),
|
||||
errorMessage: i18n.translate('xpack.ux.jsErrors.errorMessage', {
|
||||
defaultMessage: 'Error message',
|
||||
}),
|
||||
impactedPageLoads: i18n.translate('xpack.ux.jsErrors.impactedPageLoads', {
|
||||
defaultMessage: 'Impacted page loads',
|
||||
}),
|
||||
percentile: i18n.translate('xpack.ux.percentile.label', {
|
||||
defaultMessage: 'Percentile',
|
||||
}),
|
||||
percentile50thMedian: i18n.translate('xpack.ux.percentile.50thMedian', {
|
||||
defaultMessage: '50th (Median)',
|
||||
}),
|
||||
percentile75th: i18n.translate('xpack.ux.percentile.75th', {
|
||||
defaultMessage: '75th',
|
||||
}),
|
||||
percentile90th: i18n.translate('xpack.ux.percentile.90th', {
|
||||
defaultMessage: '90th',
|
||||
}),
|
||||
percentile95th: i18n.translate('xpack.ux.percentile.95th', {
|
||||
defaultMessage: '95th',
|
||||
}),
|
||||
percentile99th: i18n.translate('xpack.ux.percentile.99th', {
|
||||
defaultMessage: '99th',
|
||||
}),
|
||||
noData: i18n.translate('xpack.ux.visitorBreakdown.noData', {
|
||||
defaultMessage: 'No data.',
|
||||
}),
|
||||
// Helper tooltips
|
||||
totalPageLoadTooltip: i18n.translate(
|
||||
'xpack.ux.dashboard.tooltips.totalPageLoad',
|
||||
{
|
||||
defaultMessage: 'Total represents the full page load duration',
|
||||
}
|
||||
),
|
||||
frontEndTooltip: i18n.translate('xpack.ux.dashboard.tooltips.frontEnd', {
|
||||
defaultMessage:
|
||||
'Frontend time represents the total page load duration minus the backend time',
|
||||
}),
|
||||
backEndTooltip: i18n.translate('xpack.ux.dashboard.tooltips.backEnd', {
|
||||
defaultMessage:
|
||||
'Backend time represents time to first byte (TTFB), which is when the first response packet is received after the request has been made',
|
||||
}),
|
||||
};
|
||||
|
||||
export const VisitorBreakdownLabel = i18n.translate(
|
||||
'xpack.ux.visitorBreakdown',
|
||||
{
|
||||
defaultMessage: 'Visitor breakdown',
|
||||
}
|
||||
);
|
|
@ -9,7 +9,7 @@ import React, { useCallback } from 'react';
|
|||
import { useHistory } from 'react-router-dom';
|
||||
import { omit } from 'lodash';
|
||||
import { URLSearch } from './url_search';
|
||||
import { fromQuery, toQuery } from '../../../shared/links/url_helpers';
|
||||
import { fromQuery, toQuery } from '../../../../../../observability/public';
|
||||
import { removeUndefinedProps } from '../../../../context/url_params_context/helpers';
|
||||
|
||||
export function URLFilter() {
|
|
@ -10,10 +10,10 @@ import { i18n } from '@kbn/i18n';
|
|||
import React, { useEffect, useCallback } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { useLegacyUrlParams } from '../../../../../context/url_params_context/use_url_params';
|
||||
import { fromQuery, toQuery } from '../../../../shared/links/url_helpers';
|
||||
import { fromQuery, toQuery } from '../../../../../../../observability/public';
|
||||
|
||||
interface Props {
|
||||
serviceNames: string[];
|
||||
serviceNames?: string[];
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,7 @@ function ServiceNameFilter({ loading, serviceNames }: Props) {
|
|||
urlParams: { serviceName: selectedServiceName },
|
||||
} = useLegacyUrlParams();
|
||||
|
||||
const options = serviceNames.map((type) => ({
|
||||
const options = (serviceNames ?? []).map((type) => ({
|
||||
text: type,
|
||||
value: type,
|
||||
}));
|
||||
|
@ -47,7 +47,7 @@ function ServiceNameFilter({ loading, serviceNames }: Props) {
|
|||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (serviceNames?.length > 0) {
|
||||
if (serviceNames && serviceNames?.length > 0) {
|
||||
// select first from the list
|
||||
if (!selectedServiceName) {
|
||||
updateServiceName(serviceNames[0], true);
|
||||
|
@ -59,7 +59,7 @@ function ServiceNameFilter({ loading, serviceNames }: Props) {
|
|||
}
|
||||
}
|
||||
|
||||
if (selectedServiceName && serviceNames.length === 0 && !loading) {
|
||||
if (selectedServiceName && serviceNames?.length === 0 && !loading) {
|
||||
updateServiceName('');
|
||||
}
|
||||
}, [serviceNames, selectedServiceName, updateServiceName, loading]);
|
||||
|
@ -67,12 +67,9 @@ function ServiceNameFilter({ loading, serviceNames }: Props) {
|
|||
return (
|
||||
<EuiSelect
|
||||
fullWidth
|
||||
prepend={i18n.translate(
|
||||
'xpack.apm.ux.localFilters.titles.webApplication',
|
||||
{
|
||||
defaultMessage: 'Web application',
|
||||
}
|
||||
)}
|
||||
prepend={i18n.translate('xpack.ux.localFilters.titles.webApplication', {
|
||||
defaultMessage: 'Web application',
|
||||
})}
|
||||
isLoading={loading}
|
||||
data-cy="serviceNameFilter"
|
||||
options={options}
|
|
@ -80,7 +80,7 @@ const processItems = (items: UrlOption[]) => {
|
|||
};
|
||||
|
||||
const getWildcardLabel = (wildcard: string) => {
|
||||
return i18n.translate('xpack.apm.urlFilter.wildcard', {
|
||||
return i18n.translate('xpack.ux.urlFilter.wildcard', {
|
||||
defaultMessage: 'Use wildcard *{wildcard}*',
|
||||
values: { wildcard },
|
||||
});
|
|
@ -10,8 +10,8 @@ import React, { useCallback, useEffect } from 'react';
|
|||
import { EuiSelect } from '@elastic/eui';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params';
|
||||
import { fromQuery, toQuery } from '../../../shared/links/url_helpers';
|
||||
import { I18LABELS } from '../translations';
|
||||
import { fromQuery, toQuery } from '../../../../../../observability/public';
|
||||
|
||||
const DEFAULT_P = 50;
|
||||
|
|
@ -6,9 +6,10 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { render, Matcher } from '@testing-library/react';
|
||||
import * as fetcherHook from '../../../../hooks/use_fetcher';
|
||||
import { KeyUXMetrics } from './key_ux_metrics';
|
||||
import { FETCH_STATUS } from '../../../../../../observability/public';
|
||||
|
||||
describe('KeyUXMetrics', () => {
|
||||
it('renders metrics with correct formats', () => {
|
||||
|
@ -18,7 +19,7 @@ describe('KeyUXMetrics', () => {
|
|||
sumOfLongTasks: 520.4375,
|
||||
longestLongTask: 271.4375,
|
||||
},
|
||||
status: fetcherHook.FETCH_STATUS.SUCCESS,
|
||||
status: FETCH_STATUS.SUCCESS,
|
||||
refetch: jest.fn(),
|
||||
});
|
||||
const { getAllByText } = render(
|
||||
|
@ -38,9 +39,9 @@ describe('KeyUXMetrics', () => {
|
|||
/>
|
||||
);
|
||||
|
||||
const checkText = (text: string) => {
|
||||
return (content: any, node: any) => {
|
||||
return node?.textContent?.includes(text);
|
||||
const checkText = (text: string): Matcher => {
|
||||
return (content: string, element?: Element | null) => {
|
||||
return Boolean(element?.textContent?.includes(text));
|
||||
};
|
||||
};
|
||||
|
|
@ -9,45 +9,39 @@ import { i18n } from '@kbn/i18n';
|
|||
import { I18LABELS } from '../translations';
|
||||
|
||||
export const DATA_UNDEFINED_LABEL = i18n.translate(
|
||||
'xpack.apm.rum.coreVitals.dataUndefined',
|
||||
'xpack.ux.coreVitals.dataUndefined',
|
||||
{
|
||||
defaultMessage: 'N/A',
|
||||
}
|
||||
);
|
||||
|
||||
export const FCP_LABEL = i18n.translate('xpack.apm.rum.coreVitals.fcp', {
|
||||
export const FCP_LABEL = i18n.translate('xpack.ux.coreVitals.fcp', {
|
||||
defaultMessage: 'First contentful paint',
|
||||
});
|
||||
|
||||
export const FCP_TOOLTIP = i18n.translate(
|
||||
'xpack.apm.rum.coreVitals.fcpTooltip',
|
||||
{
|
||||
defaultMessage:
|
||||
'First contentful paint (FCP) focusses on the initial rendering and measures the time from when the page starts loading to when any part of the page’s content is displayed on the screen.',
|
||||
}
|
||||
);
|
||||
export const FCP_TOOLTIP = i18n.translate('xpack.ux.coreVitals.fcpTooltip', {
|
||||
defaultMessage:
|
||||
'First contentful paint (FCP) focusses on the initial rendering and measures the time from when the page starts loading to when any part of the page’s content is displayed on the screen.',
|
||||
});
|
||||
|
||||
export const TBT_LABEL = i18n.translate('xpack.apm.rum.coreVitals.tbt', {
|
||||
export const TBT_LABEL = i18n.translate('xpack.ux.coreVitals.tbt', {
|
||||
defaultMessage: 'Total blocking time',
|
||||
});
|
||||
|
||||
export const TBT_TOOLTIP = i18n.translate(
|
||||
'xpack.apm.rum.coreVitals.tbtTooltip',
|
||||
{
|
||||
defaultMessage:
|
||||
'Total blocking time (TBT) is the sum of the blocking time (duration above 50 ms) for each long task that occurs between the First contentful paint and the time when the transaction is completed.',
|
||||
}
|
||||
);
|
||||
export const TBT_TOOLTIP = i18n.translate('xpack.ux.coreVitals.tbtTooltip', {
|
||||
defaultMessage:
|
||||
'Total blocking time (TBT) is the sum of the blocking time (duration above 50 ms) for each long task that occurs between the First contentful paint and the time when the transaction is completed.',
|
||||
});
|
||||
|
||||
export const NO_OF_LONG_TASK = i18n.translate(
|
||||
'xpack.apm.rum.uxMetrics.noOfLongTasks',
|
||||
'xpack.ux.uxMetrics.noOfLongTasks',
|
||||
{
|
||||
defaultMessage: 'No. of long tasks',
|
||||
}
|
||||
);
|
||||
|
||||
export const NO_OF_LONG_TASK_TOOLTIP = i18n.translate(
|
||||
'xpack.apm.rum.uxMetrics.noOfLongTasksTooltip',
|
||||
'xpack.ux.uxMetrics.noOfLongTasksTooltip',
|
||||
{
|
||||
defaultMessage:
|
||||
'The number of long tasks, a long task is defined as any user activity or browser task that monopolizes the UI thread for extended periods (greater than 50 milliseconds) and blocks other critical tasks (frame rate or input latency) from being executed.',
|
||||
|
@ -55,14 +49,14 @@ export const NO_OF_LONG_TASK_TOOLTIP = i18n.translate(
|
|||
);
|
||||
|
||||
export const LONGEST_LONG_TASK = i18n.translate(
|
||||
'xpack.apm.rum.uxMetrics.longestLongTasks',
|
||||
'xpack.ux.uxMetrics.longestLongTasks',
|
||||
{
|
||||
defaultMessage: 'Longest long task duration',
|
||||
}
|
||||
);
|
||||
|
||||
export const LONGEST_LONG_TASK_TOOLTIP = i18n.translate(
|
||||
'xpack.apm.rum.uxMetrics.longestLongTasksTooltip',
|
||||
'xpack.ux.uxMetrics.longestLongTasksTooltip',
|
||||
{
|
||||
defaultMessage:
|
||||
'The duration of the longest long task, a long task is defined as any user activity or browser task that monopolizes the UI thread for extended periods (greater than 50 milliseconds) and blocks other critical tasks (frame rate or input latency) from being executed.',
|
||||
|
@ -70,14 +64,14 @@ export const LONGEST_LONG_TASK_TOOLTIP = i18n.translate(
|
|||
);
|
||||
|
||||
export const SUM_LONG_TASKS = i18n.translate(
|
||||
'xpack.apm.rum.uxMetrics.sumLongTasks',
|
||||
'xpack.ux.uxMetrics.sumLongTasks',
|
||||
{
|
||||
defaultMessage: 'Total long tasks duration',
|
||||
}
|
||||
);
|
||||
|
||||
export const SUM_LONG_TASKS_TOOLTIP = i18n.translate(
|
||||
'xpack.apm.rum.uxMetrics.sumLongTasksTooltip',
|
||||
'xpack.ux.uxMetrics.sumLongTasksTooltip',
|
||||
{
|
||||
defaultMessage:
|
||||
'The total duration of long tasks, a long task is defined as any user activity or browser task that monopolizes the UI thread for extended periods (greater than 50 milliseconds) and blocks other critical tasks (frame rate or input latency) from being executed.',
|
||||
|
@ -87,7 +81,7 @@ export const SUM_LONG_TASKS_TOOLTIP = i18n.translate(
|
|||
export const getPercentileLabel = (value: number) => {
|
||||
if (value === 50) return I18LABELS.median;
|
||||
|
||||
return i18n.translate('xpack.apm.ux.percentiles.label', {
|
||||
return i18n.translate('xpack.ux.percentiles.label', {
|
||||
defaultMessage: '{value}th Perc.',
|
||||
values: {
|
||||
value,
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue