[User Experience App] Move UX public code out of apm (#88645)

This commit is contained in:
Shahzad 2022-02-04 11:47:52 +01:00 committed by GitHub
parent 8989ead2d6
commit e445280fe6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
138 changed files with 2926 additions and 971 deletions

View file

@ -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
View file

@ -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

View file

@ -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):

View file

@ -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[\/\\]/,
],
};

View file

@ -121,3 +121,4 @@ pageLoadAssetSize:
controls: 34788
expressionPie: 26338
sharedUX: 16225
ux: 20784

View file

@ -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",

View file

@ -118,3 +118,9 @@ export const uxLocalUIFilters = uxLocalUIFilterNames.reduce((acc, key) => {
},
};
}, {} as UxLocalUIFilterMap);
export type UxUIFilters = {
environment?: string;
} & {
[key in UxLocalUIFilterName]?: string[];
};

View file

@ -24,7 +24,6 @@
"cloud",
"fleet",
"home",
"maps",
"ml",
"security",
"spaces",
@ -39,7 +38,6 @@
"home",
"kibanaReact",
"kibanaUtils",
"maps",
"ml",
"observability"
]

View file

@ -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!/
);
});
});

View file

@ -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]);
}

View file

@ -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 });
});
});

View file

@ -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',
}
);

View file

@ -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,

View file

@ -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 };

View file

@ -35,7 +35,6 @@ export function MockUrlParamsContextProvider({
rangeId: 0,
refreshTimeRange,
urlParams,
uxUiFilters: {},
}}
children={children}
/>

View file

@ -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,
});
}

View file

@ -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'>;

View file

@ -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 };

View file

@ -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) {

View file

@ -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;

View file

@ -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,

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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?: (

View file

@ -27,7 +27,8 @@
"inspector",
"ruleRegistry",
"timelines",
"triggersActionsUi"
"triggersActionsUi",
"inspector"
],
"ui": true,
"server": true,

View file

@ -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'
);
});
});
});

View file

@ -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;

View file

@ -0,0 +1,22 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 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;
}

View file

@ -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>
);
}

View file

@ -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);

View file

@ -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
);

View file

@ -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';

View file

@ -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}",

View file

@ -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}",

View file

@ -0,0 +1,4 @@
{
"singleQuote": true,
"semi": true
}

View 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;

View file

@ -0,0 +1 @@
## Observability User Experience Application

View 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);
}

View 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';

View 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;
}

View file

@ -0,0 +1,22 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 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'];

View 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;
};

View 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';

View 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';

View 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>;
}

View 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);

View 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'],
};

View 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"
}
}

View 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!/
);
});
});

View file

@ -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 () => {

View file

@ -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>
);

View file

@ -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>

View file

@ -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,

View file

@ -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 }>;

View file

@ -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?: {

View file

@ -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>

View file

@ -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}
/>
);
}

View file

@ -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', {});
}, []);
}

View file

@ -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>;

View file

@ -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',
})
: ''

View file

@ -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() {

View file

@ -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>

View file

@ -6,6 +6,7 @@
*/
import { ESFilter } from 'src/core/types/elasticsearch';
import { SERVICE_ENVIRONMENT } from '../../../../../common/elasticsearch_fieldnames';
import {
ENVIRONMENT_ALL,

View file

@ -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>

View file

@ -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';

View file

@ -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();

View file

@ -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>

View file

@ -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>

View file

@ -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 {

View file

@ -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 });
}}
/>

View file

@ -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>

View file

@ -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',
}
);

View file

@ -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() {

View file

@ -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}

View file

@ -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 },
});

View file

@ -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;

View file

@ -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));
};
};

View file

@ -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 pages 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 pages 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