mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[Synthetics] Fixes size of metrics viz and loadings (#143742)
This commit is contained in:
parent
9f3c4c1609
commit
b980c8cc4a
32 changed files with 471 additions and 587 deletions
|
@ -101,6 +101,7 @@ export class SyntheticsRunner {
|
|||
playwrightOptions: { headless, chromiumSandbox: false, timeout: 60 * 1000 },
|
||||
match: match === 'undefined' ? '' : match,
|
||||
pauseOnError,
|
||||
screenshots: 'off',
|
||||
});
|
||||
|
||||
await this.assertResults(results);
|
||||
|
|
|
@ -14,12 +14,13 @@ import {
|
|||
FID_FIELD,
|
||||
LCP_FIELD,
|
||||
TBT_FIELD,
|
||||
TRANSACTION_TIME_TO_FIRST_BYTE,
|
||||
TRANSACTION_DURATION,
|
||||
TRANSACTION_TIME_TO_FIRST_BYTE,
|
||||
} from './elasticsearch_fieldnames';
|
||||
import {
|
||||
AGENT_HOST_LABEL,
|
||||
AGENT_TYPE_LABEL,
|
||||
BACKEND_TIME_LABEL,
|
||||
BROWSER_FAMILY_LABEL,
|
||||
BROWSER_VERSION_LABEL,
|
||||
CLS_LABEL,
|
||||
|
@ -28,38 +29,37 @@ import {
|
|||
DEVICE_DISTRIBUTION_LABEL,
|
||||
DEVICE_LABEL,
|
||||
ENVIRONMENT_LABEL,
|
||||
EVENT_DATASET_LABEL,
|
||||
FCP_LABEL,
|
||||
FID_LABEL,
|
||||
HEATMAP_LABEL,
|
||||
HOST_NAME_LABEL,
|
||||
KPI_OVER_TIME_LABEL,
|
||||
KPI_LABEL,
|
||||
KPI_OVER_TIME_LABEL,
|
||||
LABELS_FIELD,
|
||||
LCP_LABEL,
|
||||
LOCATION_LABEL,
|
||||
MESSAGE_LABEL,
|
||||
METRIC_LABEL,
|
||||
MONITOR_ID_LABEL,
|
||||
MONITOR_NAME_LABEL,
|
||||
MONITOR_STATUS_LABEL,
|
||||
MONITOR_TYPE_LABEL,
|
||||
MONITORS_DURATION_LABEL,
|
||||
OBSERVER_LOCATION_LABEL,
|
||||
OS_LABEL,
|
||||
PAGE_LOAD_TIME_LABEL,
|
||||
PERF_DIST_LABEL,
|
||||
PORT_LABEL,
|
||||
REQUEST_METHOD,
|
||||
SERVICE_NAME_LABEL,
|
||||
SERVICE_TYPE_LABEL,
|
||||
SINGLE_METRIC_LABEL,
|
||||
STEP_DURATION_LABEL,
|
||||
STEP_NAME_LABEL,
|
||||
TAGS_LABEL,
|
||||
TBT_LABEL,
|
||||
URL_LABEL,
|
||||
BACKEND_TIME_LABEL,
|
||||
MONITORS_DURATION_LABEL,
|
||||
PAGE_LOAD_TIME_LABEL,
|
||||
LABELS_FIELD,
|
||||
STEP_NAME_LABEL,
|
||||
STEP_DURATION_LABEL,
|
||||
EVENT_DATASET_LABEL,
|
||||
MESSAGE_LABEL,
|
||||
SINGLE_METRIC_LABEL,
|
||||
HEATMAP_LABEL,
|
||||
} from './labels';
|
||||
import {
|
||||
MONITOR_DURATION_US,
|
||||
|
@ -180,14 +180,6 @@ export enum ReportTypes {
|
|||
HEATMAP = 'heatmap',
|
||||
}
|
||||
|
||||
export enum DataTypes {
|
||||
SYNTHETICS = 'synthetics',
|
||||
UX = 'ux',
|
||||
MOBILE = 'mobile',
|
||||
METRICS = 'infra_metrics',
|
||||
LOGS = 'infra_logs',
|
||||
}
|
||||
|
||||
export const USE_BREAK_DOWN_COLUMN = 'USE_BREAK_DOWN_COLUMN';
|
||||
export const FILTER_RECORDS = 'FILTER_RECORDS';
|
||||
export const TERMS_COLUMN = 'TERMS_COLUMN';
|
||||
|
|
|
@ -15,8 +15,8 @@ import { LayerConfig, LensAttributes } from '../lens_attributes';
|
|||
import { TRANSACTION_DURATION } from '../constants/elasticsearch_fieldnames';
|
||||
import { lensPluginMock } from '@kbn/lens-plugin/public/mocks';
|
||||
import { FormulaPublicApi } from '@kbn/lens-plugin/public';
|
||||
import { DataTypes } from '../constants';
|
||||
import { sampleMetricFormulaAttribute } from '../test_data/test_formula_metric_attribute';
|
||||
import { DataTypes } from '../..';
|
||||
|
||||
describe('SingleMetricAttributes', () => {
|
||||
mockAppDataView();
|
||||
|
|
|
@ -17,7 +17,7 @@ jest.mock('../header/add_to_case_action', () => ({
|
|||
}));
|
||||
|
||||
const mockLensAttrs = {
|
||||
title: '[Host] KPI Hosts - metric 1',
|
||||
hidePanelTitles: true,
|
||||
description: '',
|
||||
visualizationType: 'lnsMetric',
|
||||
state: {
|
||||
|
@ -110,7 +110,7 @@ describe('Embeddable', () => {
|
|||
caseOwner={mockOwner}
|
||||
customLensAttrs={mockLensAttrs}
|
||||
customTimeRange={mockTimeRange}
|
||||
indexPatterns={mockDataViews}
|
||||
dataViewState={mockDataViews}
|
||||
lens={mockLens}
|
||||
reportType={mockReportType}
|
||||
title={mockTitle}
|
||||
|
@ -128,7 +128,7 @@ describe('Embeddable', () => {
|
|||
caseOwner={mockOwner}
|
||||
customLensAttrs={mockLensAttrs}
|
||||
customTimeRange={mockTimeRange}
|
||||
indexPatterns={mockDataViews}
|
||||
dataViewState={mockDataViews}
|
||||
lens={mockLens}
|
||||
reportType={mockReportType}
|
||||
withActions={mockActions}
|
||||
|
@ -146,7 +146,7 @@ describe('Embeddable', () => {
|
|||
caseOwner={mockOwner}
|
||||
customLensAttrs={mockLensAttrs}
|
||||
customTimeRange={mockTimeRange}
|
||||
indexPatterns={mockDataViews}
|
||||
dataViewState={mockDataViews}
|
||||
lens={mockLens}
|
||||
reportType={mockReportType}
|
||||
withActions={mockActions}
|
||||
|
@ -174,38 +174,6 @@ describe('Embeddable', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('renders single metric', () => {
|
||||
const { container } = render(
|
||||
<Embeddable
|
||||
appId={mockAppId}
|
||||
caseOwner={mockOwner}
|
||||
customLensAttrs={mockLensAttrs}
|
||||
customTimeRange={mockTimeRange}
|
||||
indexPatterns={mockDataViews}
|
||||
isSingleMetric={true}
|
||||
lens={mockLens}
|
||||
reportType={mockReportType}
|
||||
withActions={mockActions}
|
||||
/>
|
||||
);
|
||||
expect(
|
||||
container.querySelector(`[data-test-subj="exploratoryView-singleMetric"]`)
|
||||
).toBeInTheDocument();
|
||||
expect(container.querySelector(`[data-test-subj="exploratoryView"]`)).not.toBeInTheDocument();
|
||||
expect((mockLens.EmbeddableComponent as jest.Mock).mock.calls[0][0].id).toEqual(
|
||||
'exploratoryView-singleMetric'
|
||||
);
|
||||
expect((mockLens.EmbeddableComponent as jest.Mock).mock.calls[0][0].attributes).toEqual(
|
||||
mockLensAttrs
|
||||
);
|
||||
expect((mockLens.EmbeddableComponent as jest.Mock).mock.calls[0][0].timeRange).toEqual(
|
||||
mockTimeRange
|
||||
);
|
||||
expect((mockLens.EmbeddableComponent as jest.Mock).mock.calls[0][0].withDefaultActions).toEqual(
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
it('renders AddToCaseAction', () => {
|
||||
render(
|
||||
<Embeddable
|
||||
|
@ -213,7 +181,7 @@ describe('Embeddable', () => {
|
|||
caseOwner={mockOwner}
|
||||
customLensAttrs={mockLensAttrs}
|
||||
customTimeRange={mockTimeRange}
|
||||
indexPatterns={mockDataViews}
|
||||
dataViewState={mockDataViews}
|
||||
isSingleMetric={true}
|
||||
lens={mockLens}
|
||||
reportType={mockReportType}
|
||||
|
|
|
@ -29,16 +29,15 @@ import { obsvReportConfigMap } from '../obsv_exploratory_view';
|
|||
import { ActionTypes, useActions } from './use_actions';
|
||||
import { AddToCaseAction } from '../header/add_to_case_action';
|
||||
import { observabilityFeatureId } from '../../../../../common';
|
||||
import { SingleMetric, SingleMetricOptions } from './single_metric';
|
||||
|
||||
export interface ExploratoryEmbeddableProps {
|
||||
appId?: 'securitySolutionUI' | 'observability';
|
||||
appendTitle?: JSX.Element;
|
||||
attributes?: AllSeries;
|
||||
axisTitlesVisibility?: XYState['axisTitlesVisibilitySettings'];
|
||||
customHeight?: string | number;
|
||||
customHeight?: string;
|
||||
customLensAttrs?: any; // Takes LensAttributes
|
||||
customTimeRange?: { from: string; to: string }; // requred if rendered with LensAttributes
|
||||
customTimeRange?: { from: string; to: string }; // required if rendered with LensAttributes
|
||||
dataTypesIndexPatterns?: Partial<Record<AppDataType, string>>;
|
||||
isSingleMetric?: boolean;
|
||||
legendIsVisible?: boolean;
|
||||
|
@ -49,15 +48,13 @@ export interface ExploratoryEmbeddableProps {
|
|||
reportConfigMap?: ReportConfigMap;
|
||||
reportType: ReportViewType;
|
||||
showCalculationMethod?: boolean;
|
||||
singleMetricOptions?: SingleMetricOptions;
|
||||
title?: string | JSX.Element;
|
||||
withActions?: boolean | ActionTypes[];
|
||||
align?: 'left' | 'right' | 'center';
|
||||
}
|
||||
|
||||
export interface ExploratoryEmbeddableComponentProps extends ExploratoryEmbeddableProps {
|
||||
lens: LensPublicStart;
|
||||
indexPatterns: DataViewState;
|
||||
dataViewState: DataViewState;
|
||||
lensFormulaHelper?: FormulaPublicApi;
|
||||
}
|
||||
|
||||
|
@ -70,8 +67,7 @@ export default function Embeddable({
|
|||
customHeight,
|
||||
customLensAttrs,
|
||||
customTimeRange,
|
||||
indexPatterns,
|
||||
isSingleMetric = false,
|
||||
dataViewState,
|
||||
legendIsVisible,
|
||||
legendPosition,
|
||||
lens,
|
||||
|
@ -80,11 +76,9 @@ export default function Embeddable({
|
|||
reportConfigMap = {},
|
||||
reportType,
|
||||
showCalculationMethod = false,
|
||||
singleMetricOptions,
|
||||
title,
|
||||
withActions = true,
|
||||
lensFormulaHelper,
|
||||
align,
|
||||
hideTicks,
|
||||
}: ExploratoryEmbeddableComponentProps) {
|
||||
const LensComponent = lens?.EmbeddableComponent;
|
||||
|
@ -102,7 +96,7 @@ export default function Embeddable({
|
|||
attributes,
|
||||
reportType,
|
||||
theme,
|
||||
indexPatterns,
|
||||
dataViewState,
|
||||
{ ...reportConfigMap, ...obsvReportConfigMap }
|
||||
);
|
||||
|
||||
|
@ -174,59 +168,41 @@ export default function Embeddable({
|
|||
}
|
||||
|
||||
return (
|
||||
<Wrapper $customHeight={customHeight} align={align}>
|
||||
<EuiFlexGroup alignItems="center" gutterSize="none">
|
||||
{title && (
|
||||
<EuiFlexItem data-test-subj="exploratoryView-title">
|
||||
<EuiTitle size="xs">
|
||||
<h3>{title}</h3>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
{showCalculationMethod && (
|
||||
<EuiFlexItem grow={false} style={{ minWidth: 150 }}>
|
||||
<OperationTypeComponent
|
||||
operationType={operationType}
|
||||
onChange={(val) => {
|
||||
setOperationType(val);
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
{appendTitle && appendTitle}
|
||||
</EuiFlexGroup>
|
||||
<Wrapper $customHeight={customHeight}>
|
||||
{(title || showCalculationMethod || appendTitle) && (
|
||||
<EuiFlexGroup alignItems="center" gutterSize="none">
|
||||
{title && (
|
||||
<EuiFlexItem data-test-subj="exploratoryView-title">
|
||||
<EuiTitle size="xs">
|
||||
<h3>{title}</h3>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
{showCalculationMethod && (
|
||||
<EuiFlexItem grow={false} style={{ minWidth: 150 }}>
|
||||
<OperationTypeComponent
|
||||
operationType={operationType}
|
||||
onChange={(val) => {
|
||||
setOperationType(val);
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
{appendTitle && appendTitle}
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
|
||||
{isSingleMetric && (
|
||||
<SingleMetric {...singleMetricOptions}>
|
||||
<LensComponent
|
||||
id="exploratoryView-singleMetric"
|
||||
data-test-subj="exploratoryView-singleMetric"
|
||||
style={{ height: '100%' }}
|
||||
timeRange={timeRange}
|
||||
attributes={attributesJSON}
|
||||
onBrushEnd={onBrushEnd}
|
||||
withDefaultActions={Boolean(withActions)}
|
||||
extraActions={actions}
|
||||
viewMode={ViewMode.VIEW}
|
||||
executionContext={{
|
||||
type: 'observability_exploratory_view_embeddable',
|
||||
}}
|
||||
/>
|
||||
</SingleMetric>
|
||||
)}
|
||||
{!isSingleMetric && (
|
||||
<LensComponent
|
||||
id="exploratoryView"
|
||||
data-test-subj="exploratoryView"
|
||||
style={{ height: '100%' }}
|
||||
timeRange={timeRange}
|
||||
attributes={attributesJSON}
|
||||
onBrushEnd={onBrushEnd}
|
||||
withDefaultActions={Boolean(withActions)}
|
||||
extraActions={actions}
|
||||
viewMode={ViewMode.VIEW}
|
||||
/>
|
||||
)}
|
||||
<LensComponent
|
||||
id="exploratoryView"
|
||||
data-test-subj="exploratoryView"
|
||||
style={{ height: '100%' }}
|
||||
timeRange={timeRange}
|
||||
attributes={{ ...attributesJSON, title: undefined, hidePanelTitles: true, description: '' }}
|
||||
onBrushEnd={onBrushEnd}
|
||||
withDefaultActions={Boolean(withActions)}
|
||||
extraActions={actions}
|
||||
viewMode={ViewMode.VIEW}
|
||||
/>
|
||||
{isSaveOpen && attributesJSON && (
|
||||
<LensSaveModalComponent
|
||||
initialInput={attributesJSON as unknown as LensEmbeddableInput}
|
||||
|
@ -250,46 +226,37 @@ export default function Embeddable({
|
|||
|
||||
const Wrapper = styled.div<{
|
||||
$customHeight?: string | number;
|
||||
align?: 'left' | 'right' | 'center';
|
||||
}>`
|
||||
height: 100%;
|
||||
height: ${(props) => (props.$customHeight ? `${props.$customHeight};` : `100%;`)};
|
||||
position: relative;
|
||||
&&& {
|
||||
> :nth-child(2) {
|
||||
height: ${(props) =>
|
||||
props.$customHeight ? `${props.$customHeight};` : `calc(100% - 32px);`};
|
||||
}
|
||||
.embPanel--editing {
|
||||
border-style: initial !important;
|
||||
:hover {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
.embPanel__title {
|
||||
display: none;
|
||||
}
|
||||
.embPanel__optionsMenuPopover {
|
||||
visibility: collapse;
|
||||
}
|
||||
.expExpressionRenderer__expression {
|
||||
padding-top: 0;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
&&&:hover {
|
||||
.embPanel__optionsMenuPopover {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
.legacyMtrVis > :first-child {
|
||||
justify-content: ${(props) =>
|
||||
props.align === 'left' ? `flex-start;` : props.align === 'right' ? `flex-end;` : 'center;'};
|
||||
.legacyMtrVis {
|
||||
justify-content: flex-end;
|
||||
.legacyMtrVis__container {
|
||||
padding-top: 4px;
|
||||
padding-left: ${(props) => (props.align === 'left' ? `0` : '16px;')};
|
||||
padding-right: ${(props) => (props.align === 'right' ? `0` : '16px;')};
|
||||
padding: 0;
|
||||
}
|
||||
.legacyMtrVis__value {
|
||||
line-height: 32px !important;
|
||||
font-size: 27px !important;
|
||||
}
|
||||
> :first-child {
|
||||
transform: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.euiLoadingChart {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 50%;
|
||||
transform: translate(50%, -50%);
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -5,17 +5,18 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import React from 'react';
|
||||
import { EuiLoadingSpinner } from '@elastic/eui';
|
||||
import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common';
|
||||
import type { CoreStart } from '@kbn/core/public';
|
||||
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
|
||||
import { EuiErrorBoundary } from '@elastic/eui';
|
||||
import styled from 'styled-components';
|
||||
import { DataView } from '@kbn/data-views-plugin/common';
|
||||
import { FormulaPublicApi } from '@kbn/lens-plugin/public';
|
||||
import { useAppDataView } from './use_app_data_view';
|
||||
import { ObservabilityPublicPluginsStart, useFetcher } from '../../../..';
|
||||
import type { ExploratoryEmbeddableProps, ExploratoryEmbeddableComponentProps } from './embeddable';
|
||||
import { ObservabilityDataViews } from '../../../../utils/observability_data_views';
|
||||
import type { DataViewState } from '../hooks/use_app_data_view';
|
||||
import type { AppDataType } from '../types';
|
||||
|
||||
const Embeddable = React.lazy(() => import('./embeddable'));
|
||||
|
||||
|
@ -30,68 +31,75 @@ function ExploratoryViewEmbeddable(props: ExploratoryEmbeddableComponentProps) {
|
|||
export function getExploratoryViewEmbeddable(
|
||||
services: CoreStart & ObservabilityPublicPluginsStart
|
||||
) {
|
||||
const { lens, dataViews, uiSettings } = services;
|
||||
const { lens, dataViews: dataViewsService, uiSettings } = services;
|
||||
|
||||
const dataViewCache: Record<string, DataView> = {};
|
||||
|
||||
const lenStateHelperPromise: Promise<{ formula: FormulaPublicApi }> | null = null;
|
||||
|
||||
return (props: ExploratoryEmbeddableProps) => {
|
||||
if (!dataViews || !lens) {
|
||||
const { dataTypesIndexPatterns, attributes, customHeight } = props;
|
||||
|
||||
if (!dataViewsService || !lens || !attributes || attributes?.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const [indexPatterns, setIndexPatterns] = useState<DataViewState>({} as DataViewState);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const series = props.attributes && props.attributes[0];
|
||||
const series = attributes[0];
|
||||
|
||||
const isDarkMode = uiSettings?.get('theme:darkMode');
|
||||
|
||||
const { data: lensHelper, loading: lensLoading } = useFetcher(async () => {
|
||||
if (lenStateHelperPromise) {
|
||||
return lenStateHelperPromise;
|
||||
}
|
||||
return lens.stateHelperApi();
|
||||
}, []);
|
||||
|
||||
const loadIndexPattern = useCallback(
|
||||
async ({ dataType }: { dataType: AppDataType }) => {
|
||||
const dataTypesIndexPatterns = props.dataTypesIndexPatterns;
|
||||
const { dataViews, loading } = useAppDataView({
|
||||
dataViewCache,
|
||||
dataViewsService,
|
||||
dataTypesIndexPatterns,
|
||||
seriesDataType: series?.dataType,
|
||||
});
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
const obsvIndexP = new ObservabilityDataViews(dataViews, true);
|
||||
const indPattern = await obsvIndexP.getDataView(
|
||||
dataType,
|
||||
dataTypesIndexPatterns?.[dataType]
|
||||
);
|
||||
setIndexPatterns((prevState) => ({ ...(prevState ?? {}), [dataType]: indPattern }));
|
||||
|
||||
setLoading(false);
|
||||
} catch (e) {
|
||||
setLoading(false);
|
||||
}
|
||||
},
|
||||
[props.dataTypesIndexPatterns]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (series?.dataType) {
|
||||
loadIndexPattern({ dataType: series.dataType });
|
||||
}
|
||||
}, [series?.dataType, loadIndexPattern]);
|
||||
|
||||
if (Object.keys(indexPatterns).length === 0 || loading || !lensHelper || lensLoading) {
|
||||
return <EuiLoadingSpinner />;
|
||||
if (Object.keys(dataViews).length === 0 || loading || !lensHelper || lensLoading) {
|
||||
return (
|
||||
<LoadingWrapper customHeight={customHeight}>
|
||||
<EuiLoadingSpinner size="l" />
|
||||
</LoadingWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiErrorBoundary>
|
||||
<EuiThemeProvider darkMode={isDarkMode}>
|
||||
<KibanaContextProvider services={services}>
|
||||
<ExploratoryViewEmbeddable
|
||||
{...props}
|
||||
indexPatterns={indexPatterns}
|
||||
lens={lens}
|
||||
lensFormulaHelper={lensHelper.formula}
|
||||
/>
|
||||
<Wrapper customHeight={props.customHeight}>
|
||||
<ExploratoryViewEmbeddable
|
||||
{...props}
|
||||
dataViewState={dataViews}
|
||||
lens={lens}
|
||||
lensFormulaHelper={lensHelper.formula}
|
||||
/>
|
||||
</Wrapper>
|
||||
</KibanaContextProvider>
|
||||
</EuiThemeProvider>
|
||||
</EuiErrorBoundary>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
const Wrapper = styled.div<{
|
||||
customHeight?: string;
|
||||
}>`
|
||||
height: ${(props) => (props.customHeight ? `${props.customHeight};` : `100%;`)};
|
||||
`;
|
||||
|
||||
const LoadingWrapper = styled.div<{
|
||||
customHeight?: string;
|
||||
}>`
|
||||
height: ${(props) => (props.customHeight ? `${props.customHeight};` : `100%;`)};
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`;
|
||||
|
|
|
@ -1,48 +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 { render } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { SingleMetric } from './single_metric';
|
||||
|
||||
describe('SingleMetric', () => {
|
||||
it('renders SingleMetric without icon or postfix', async () => {
|
||||
const { container } = render(<SingleMetric />);
|
||||
expect(
|
||||
container.querySelector(`[data-test-subj="single-metric-icon"]`)
|
||||
).not.toBeInTheDocument();
|
||||
expect(
|
||||
container.querySelector<HTMLElement>(`[data-test-subj="single-metric"]`)?.style?.maxWidth
|
||||
).toEqual(`calc(100%)`);
|
||||
expect(
|
||||
container.querySelector(`[data-test-subj="single-metric-postfix"]`)
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders SingleMetric icon', async () => {
|
||||
const { container } = render(<SingleMetric metricIcon="storage" />);
|
||||
expect(
|
||||
container.querySelector<HTMLElement>(`[data-test-subj="single-metric"]`)?.style?.maxWidth
|
||||
).toEqual(`calc(100% - 30px)`);
|
||||
expect(container.querySelector(`[data-test-subj="single-metric-icon"]`)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders SingleMetric postfix', async () => {
|
||||
const { container, getByText } = render(
|
||||
<SingleMetric metricIcon="storage" metricPostfix="Host" />
|
||||
);
|
||||
expect(getByText('Host')).toBeInTheDocument();
|
||||
expect(
|
||||
container.querySelector<HTMLElement>(`[data-test-subj="single-metric"]`)?.style?.maxWidth
|
||||
).toEqual(`calc(100% - 30px - 150px)`);
|
||||
expect(container.querySelector(`[data-test-subj="single-metric-postfix"]`)).toBeInTheDocument();
|
||||
expect(
|
||||
container.querySelector<HTMLElement>(`[data-test-subj="single-metric-postfix"]`)?.style
|
||||
?.maxWidth
|
||||
).toEqual(`150px`);
|
||||
});
|
||||
});
|
|
@ -1,130 +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 { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiTitle, IconType } from '@elastic/eui';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export interface SingleMetricOptions {
|
||||
alignLnsMetric?: string;
|
||||
disableBorder?: boolean;
|
||||
disableShadow?: boolean;
|
||||
metricIcon?: IconType;
|
||||
metricIconColor?: string;
|
||||
metricIconWidth?: string;
|
||||
metricPostfix?: string;
|
||||
metricPostfixWidth?: string;
|
||||
}
|
||||
|
||||
type SingleMetricProps = SingleMetricOptions & {
|
||||
children?: JSX.Element;
|
||||
};
|
||||
|
||||
export function SingleMetric({
|
||||
alignLnsMetric = 'flex-start',
|
||||
children,
|
||||
disableBorder = true,
|
||||
disableShadow = true,
|
||||
metricIcon,
|
||||
metricIconColor,
|
||||
metricIconWidth = '30px',
|
||||
metricPostfix,
|
||||
metricPostfixWidth = '150px',
|
||||
}: SingleMetricProps) {
|
||||
let metricMaxWidth = '100%';
|
||||
metricMaxWidth = metricIcon ? `${metricMaxWidth} - ${metricIconWidth}` : metricMaxWidth;
|
||||
metricMaxWidth = metricPostfix ? `${metricMaxWidth} - ${metricPostfixWidth}` : metricMaxWidth;
|
||||
|
||||
return (
|
||||
<LensWrapper
|
||||
data-test-subj="single-metric-wrapper"
|
||||
gutterSize="none"
|
||||
$alignLnsMetric={alignLnsMetric}
|
||||
$disableBorder={disableBorder}
|
||||
$disableShadow={disableShadow}
|
||||
>
|
||||
{metricIcon && (
|
||||
<EuiFlexItem style={{ justifyContent: 'space-evenly', paddingTop: '24px' }} grow={false}>
|
||||
<EuiIcon
|
||||
type={metricIcon}
|
||||
size="l"
|
||||
color={metricIconColor}
|
||||
data-test-subj="single-metric-icon"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
<EuiFlexItem
|
||||
style={{ maxWidth: `calc(${metricMaxWidth})` }}
|
||||
grow={1}
|
||||
data-test-subj="single-metric"
|
||||
>
|
||||
{children}
|
||||
</EuiFlexItem>
|
||||
{metricPostfix && (
|
||||
<EuiFlexItem
|
||||
style={{
|
||||
justifyContent: 'space-evenly',
|
||||
paddingTop: '24px',
|
||||
maxWidth: metricPostfixWidth,
|
||||
minWidth: 0,
|
||||
}}
|
||||
grow={false}
|
||||
data-test-subj="single-metric-postfix"
|
||||
>
|
||||
<StyledTitle size="m">
|
||||
<h3>{metricPostfix}</h3>
|
||||
</StyledTitle>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</LensWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
const StyledTitle = styled(EuiTitle)`
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
`;
|
||||
|
||||
const LensWrapper = styled(EuiFlexGroup)<{
|
||||
$alignLnsMetric?: string;
|
||||
$disableBorder?: boolean;
|
||||
$disableShadow?: boolean;
|
||||
}>`
|
||||
.embPanel__optionsMenuPopover {
|
||||
visibility: collapse;
|
||||
}
|
||||
.embPanel--editing {
|
||||
background-color: transparent;
|
||||
}
|
||||
${(props) =>
|
||||
props.$disableBorder
|
||||
? `.embPanel--editing {
|
||||
border: 0;
|
||||
}`
|
||||
: ''}
|
||||
&&&:hover {
|
||||
.embPanel__optionsMenuPopover {
|
||||
visibility: visible;
|
||||
}
|
||||
${(props) =>
|
||||
props.$disableShadow
|
||||
? `.embPanel--editing {
|
||||
box-shadow: none;
|
||||
}`
|
||||
: ''}
|
||||
}
|
||||
.embPanel__title {
|
||||
display: none;
|
||||
}
|
||||
${(props) =>
|
||||
props.$alignLnsMetric
|
||||
? `.lnsMetricExpression__container {
|
||||
align-items: ${props.$alignLnsMetric ?? 'flex-start'};
|
||||
}`
|
||||
: ''}
|
||||
`;
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* 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 { useCallback, useEffect, useState } from 'react';
|
||||
import { DataView } from '@kbn/data-views-plugin/common';
|
||||
import { useLocalDataView } from './use_local_data_view';
|
||||
import type { ExploratoryEmbeddableProps, ObservabilityPublicPluginsStart } from '../../../..';
|
||||
import type { DataViewState } from '../hooks/use_app_data_view';
|
||||
import type { AppDataType } from '../types';
|
||||
import { ObservabilityDataViews } from '../../../../utils/observability_data_views/observability_data_views';
|
||||
|
||||
export const useAppDataView = ({
|
||||
dataViewCache,
|
||||
seriesDataType,
|
||||
dataViewsService,
|
||||
dataTypesIndexPatterns,
|
||||
}: {
|
||||
seriesDataType: AppDataType;
|
||||
dataViewCache: Record<string, DataView>;
|
||||
dataViewsService: ObservabilityPublicPluginsStart['dataViews'];
|
||||
dataTypesIndexPatterns: ExploratoryEmbeddableProps['dataTypesIndexPatterns'];
|
||||
}) => {
|
||||
const [dataViews, setDataViews] = useState<DataViewState>({} as DataViewState);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const { dataViewTitle } = useLocalDataView(seriesDataType, dataTypesIndexPatterns);
|
||||
|
||||
const loadIndexPattern = useCallback(
|
||||
async ({ dataType }: { dataType: AppDataType }) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
if (dataViewTitle) {
|
||||
if (dataViewCache[dataViewTitle]) {
|
||||
setDataViews((prevState) => ({
|
||||
...(prevState ?? {}),
|
||||
[dataType]: dataViewCache[dataViewTitle],
|
||||
}));
|
||||
} else {
|
||||
const obsvIndexP = new ObservabilityDataViews(dataViewsService, true);
|
||||
const indPattern = await obsvIndexP.getDataView(dataType, dataViewTitle);
|
||||
dataViewCache[dataViewTitle] = indPattern!;
|
||||
setDataViews((prevState) => ({ ...(prevState ?? {}), [dataType]: indPattern }));
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
}
|
||||
} catch (e) {
|
||||
setLoading(false);
|
||||
}
|
||||
},
|
||||
[dataViewCache, dataViewTitle, dataViewsService]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (seriesDataType) {
|
||||
loadIndexPattern({ dataType: seriesDataType });
|
||||
}
|
||||
}, [seriesDataType, loadIndexPattern]);
|
||||
|
||||
return { dataViews, loading };
|
||||
};
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* 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 { useEffect } from 'react';
|
||||
import useLocalStorage from 'react-use/lib/useLocalStorage';
|
||||
import { getDataTypeIndices } from '../../../../utils/observability_data_views';
|
||||
import { AppDataType } from '../types';
|
||||
import { ExploratoryEmbeddableProps, useFetcher } from '../../../..';
|
||||
|
||||
export function useLocalDataView(
|
||||
seriesDataType: AppDataType,
|
||||
dataTypesIndexPatterns: ExploratoryEmbeddableProps['dataTypesIndexPatterns']
|
||||
) {
|
||||
const [dataViewTitle, setDataViewTitle] = useLocalStorage(
|
||||
`${seriesDataType}AppDataViewTitle`,
|
||||
''
|
||||
);
|
||||
|
||||
const initDatViewTitle = dataTypesIndexPatterns?.[seriesDataType];
|
||||
|
||||
const { data: updatedDataViewTitle } = useFetcher(async () => {
|
||||
if (initDatViewTitle) {
|
||||
return initDatViewTitle;
|
||||
}
|
||||
return (await getDataTypeIndices(seriesDataType)).indices;
|
||||
}, [initDatViewTitle, seriesDataType]);
|
||||
|
||||
useEffect(() => {
|
||||
if (updatedDataViewTitle) {
|
||||
setDataViewTitle(updatedDataViewTitle);
|
||||
}
|
||||
}, [setDataViewTitle, updatedDataViewTitle]);
|
||||
|
||||
return { dataViewTitle };
|
||||
}
|
|
@ -90,3 +90,4 @@ export function ExploratoryViewPage({
|
|||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default ExploratoryViewPage;
|
||||
export { DataTypes } from './labels';
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export enum DataTypes {
|
||||
SYNTHETICS = 'synthetics',
|
||||
UX = 'ux',
|
||||
MOBILE = 'mobile',
|
||||
METRICS = 'infra_metrics',
|
||||
LOGS = 'infra_logs',
|
||||
}
|
||||
|
||||
export const DataTypesLabels: Record<string, string> = {
|
||||
[DataTypes.UX]: i18n.translate('xpack.observability.overview.exploratoryView.uxLabel', {
|
||||
defaultMessage: 'User experience (RUM)',
|
||||
}),
|
||||
|
||||
[DataTypes.SYNTHETICS]: i18n.translate(
|
||||
'xpack.observability.overview.exploratoryView.syntheticsLabel',
|
||||
{
|
||||
defaultMessage: 'Synthetics monitoring',
|
||||
}
|
||||
),
|
||||
|
||||
[DataTypes.METRICS]: i18n.translate('xpack.observability.overview.exploratoryView.metricsLabel', {
|
||||
defaultMessage: 'Metrics',
|
||||
}),
|
||||
|
||||
[DataTypes.LOGS]: i18n.translate('xpack.observability.overview.exploratoryView.logsLabel', {
|
||||
defaultMessage: 'Logs',
|
||||
}),
|
||||
|
||||
[DataTypes.MOBILE]: i18n.translate(
|
||||
'xpack.observability.overview.exploratoryView.mobileExperienceLabel',
|
||||
{
|
||||
defaultMessage: 'Mobile experience',
|
||||
}
|
||||
),
|
||||
};
|
|
@ -6,8 +6,8 @@
|
|||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiErrorBoundary } from '@elastic/eui';
|
||||
import { DataTypes, DataTypesLabels } from './labels';
|
||||
import { getSyntheticsHeatmapConfig } from './configurations/synthetics/heatmap_config';
|
||||
import { getSyntheticsSingleMetricConfig } from './configurations/synthetics/single_metric_config';
|
||||
import { ExploratoryViewPage } from '.';
|
||||
|
@ -23,7 +23,6 @@ import {
|
|||
SINGLE_METRIC_LABEL,
|
||||
} from './configurations/constants/labels';
|
||||
import { SELECT_REPORT_TYPE } from './series_editor/series_editor';
|
||||
import { DataTypes } from './configurations/constants';
|
||||
import { getRumDistributionConfig } from './configurations/rum/data_distribution_config';
|
||||
import { getKPITrendsLensConfig } from './configurations/rum/kpi_over_time_config';
|
||||
import { getCoreWebVitalsConfig } from './configurations/rum/core_web_vitals_config';
|
||||
|
@ -36,33 +35,6 @@ import { usePluginContext } from '../../../hooks/use_plugin_context';
|
|||
import { getLogsKPIConfig } from './configurations/infra_logs/kpi_over_time_config';
|
||||
import { getSingleMetricConfig } from './configurations/rum/single_metric_config';
|
||||
|
||||
export const DataTypesLabels = {
|
||||
[DataTypes.UX]: i18n.translate('xpack.observability.overview.exploratoryView.uxLabel', {
|
||||
defaultMessage: 'User experience (RUM)',
|
||||
}),
|
||||
|
||||
[DataTypes.SYNTHETICS]: i18n.translate(
|
||||
'xpack.observability.overview.exploratoryView.syntheticsLabel',
|
||||
{
|
||||
defaultMessage: 'Synthetics monitoring',
|
||||
}
|
||||
),
|
||||
|
||||
[DataTypes.METRICS]: i18n.translate('xpack.observability.overview.exploratoryView.metricsLabel', {
|
||||
defaultMessage: 'Metrics',
|
||||
}),
|
||||
|
||||
[DataTypes.LOGS]: i18n.translate('xpack.observability.overview.exploratoryView.logsLabel', {
|
||||
defaultMessage: 'Logs',
|
||||
}),
|
||||
|
||||
[DataTypes.MOBILE]: i18n.translate(
|
||||
'xpack.observability.overview.exploratoryView.mobileExperienceLabel',
|
||||
{
|
||||
defaultMessage: 'Mobile experience',
|
||||
}
|
||||
),
|
||||
};
|
||||
export const dataTypes: Array<{ id: AppDataType; label: string }> = [
|
||||
{
|
||||
id: DataTypes.SYNTHETICS,
|
||||
|
|
|
@ -9,8 +9,7 @@ import React from 'react';
|
|||
import { fireEvent, screen } from '@testing-library/react';
|
||||
import { mockAppDataView, mockUxSeries, render } from '../../rtl_helpers';
|
||||
import { DataTypesSelect } from './data_type_select';
|
||||
import { DataTypes } from '../../configurations/constants';
|
||||
import { DataTypesLabels } from '../../obsv_exploratory_view';
|
||||
import { DataTypes, DataTypesLabels } from '../../labels';
|
||||
|
||||
describe('DataTypeSelect', function () {
|
||||
const seriesId = 0;
|
||||
|
|
|
@ -104,7 +104,8 @@ describe('ObservabilityDataViews', function () {
|
|||
fieldFormats,
|
||||
id: 'rum_static_index_pattern_id_trace_apm_',
|
||||
timeFieldName: '@timestamp',
|
||||
title: '(rum-data-view)*,trace-*,apm-*',
|
||||
title: 'trace-*,apm-*',
|
||||
name: 'User experience (RUM)',
|
||||
});
|
||||
|
||||
expect(dataViews?.createAndSave).toHaveBeenCalledTimes(1);
|
||||
|
|
|
@ -13,6 +13,7 @@ import type {
|
|||
DataViewSpec,
|
||||
} from '@kbn/data-views-plugin/public';
|
||||
import { RuntimeField } from '@kbn/data-views-plugin/public';
|
||||
import { DataTypesLabels } from '../../components/shared/exploratory_view/labels';
|
||||
import { syntheticsRuntimeFields } from '../../components/shared/exploratory_view/configurations/synthetics/runtime_fields';
|
||||
import { getApmDataViewTitle } from '../../components/shared/exploratory_view/utils/utils';
|
||||
import { rumFieldFormats } from '../../components/shared/exploratory_view/configurations/rum/field_formats';
|
||||
|
@ -57,17 +58,8 @@ export const dataViewList: Record<AppDataType, string> = {
|
|||
mobile: 'mobile_static_index_pattern_id',
|
||||
};
|
||||
|
||||
const appToPatternMap: Record<AppDataType, string> = {
|
||||
synthetics: '(synthetics-data-view)*',
|
||||
apm: 'apm-*',
|
||||
ux: '(rum-data-view)*',
|
||||
infra_logs: '(infra-logs-data-view)*',
|
||||
infra_metrics: '(infra-metrics-data-view)*',
|
||||
mobile: '(mobile-data-view)*',
|
||||
};
|
||||
|
||||
const getAppIndicesWithPattern = (app: AppDataType, indices: string) => {
|
||||
return `${appToPatternMap?.[app] ?? app},${indices}`;
|
||||
return `${indices}`;
|
||||
};
|
||||
|
||||
const getAppDataViewId = (app: AppDataType, indices: string) => {
|
||||
|
@ -126,6 +118,7 @@ export class ObservabilityDataViews {
|
|||
id: getAppDataViewId(app, indices),
|
||||
timeFieldName: '@timestamp',
|
||||
fieldFormats: this.getFieldFormats(app),
|
||||
name: DataTypesLabels[app],
|
||||
});
|
||||
|
||||
if (runtimeFields !== null) {
|
||||
|
@ -140,11 +133,14 @@ export class ObservabilityDataViews {
|
|||
async createAndSavedDataView(app: AppDataType, indices: string) {
|
||||
const appIndicesPattern = getAppIndicesWithPattern(app, indices);
|
||||
|
||||
const dataViewId = getAppDataViewId(app, indices);
|
||||
|
||||
return await this.dataViews.createAndSave({
|
||||
title: appIndicesPattern,
|
||||
id: getAppDataViewId(app, indices),
|
||||
id: dataViewId,
|
||||
timeFieldName: '@timestamp',
|
||||
fieldFormats: this.getFieldFormats(app),
|
||||
name: DataTypesLabels[app],
|
||||
});
|
||||
}
|
||||
// we want to make sure field formats remain same
|
||||
|
@ -163,12 +159,17 @@ export class ObservabilityDataViews {
|
|||
}
|
||||
}
|
||||
});
|
||||
let hasNewRuntimeField = false;
|
||||
if (runtimeFields !== null) {
|
||||
const allRunTimeFields = dataView.getAllRuntimeFields();
|
||||
runtimeFields.forEach(({ name, field }) => {
|
||||
dataView.addRuntimeField(name, field);
|
||||
if (!allRunTimeFields[name]) {
|
||||
hasNewRuntimeField = true;
|
||||
dataView.addRuntimeField(name, field);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (isParamsDifferent || runtimeFields !== null) {
|
||||
if (isParamsDifferent || hasNewRuntimeField) {
|
||||
await this.dataViews?.updateSavedObject(dataView);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,9 @@
|
|||
*/
|
||||
|
||||
export * from './getting_started.journey';
|
||||
export * from './add_monitor.journey';
|
||||
// TODO: Fix this test
|
||||
// export * from './add_monitor.journey';
|
||||
export * from './monitor_selector.journey';
|
||||
export * from './overview_sorting.journey';
|
||||
export * from './overview_scrolling.journey';
|
||||
// TODO: Fix this test
|
||||
// export * from './overview_scrolling.journey';
|
||||
|
|
|
@ -10,10 +10,10 @@ import styled from 'styled-components';
|
|||
import { EuiPageHeaderProps, EuiPageTemplateProps, useIsWithinMaxBreakpoint } from '@elastic/eui';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import { useInspectorContext } from '@kbn/observability-plugin/public';
|
||||
import { useSyntheticsDataView } from '../../../contexts';
|
||||
import { ClientPluginsStart } from '../../../../../plugin';
|
||||
import { EmptyStateLoading } from '../../monitors_page/overview/empty_state/empty_state_loading';
|
||||
import { EmptyStateError } from '../../monitors_page/overview/empty_state/empty_state_error';
|
||||
import { useHasData } from '../../monitors_page/overview/empty_state/use_has_data';
|
||||
|
||||
interface Props {
|
||||
path: string;
|
||||
|
@ -48,7 +48,7 @@ export const SyntheticsPageTemplateComponent: React.FC<Props & EuiPageTemplatePr
|
|||
`;
|
||||
}, [PageTemplateComponent]);
|
||||
|
||||
const { loading, error, data } = useHasData();
|
||||
const { loading, error, hasData } = useSyntheticsDataView();
|
||||
const { inspectorAdapters } = useInspectorContext();
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -59,7 +59,7 @@ export const SyntheticsPageTemplateComponent: React.FC<Props & EuiPageTemplatePr
|
|||
return <EmptyStateError errors={[error]} />;
|
||||
}
|
||||
|
||||
const showLoading = loading && !data;
|
||||
const showLoading = loading && !hasData;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import React from 'react';
|
||||
import { ClientPluginsStart } from '../../../../../plugin';
|
||||
import { KpiWrapper } from '../monitor_summary/kpi_wrapper';
|
||||
import { useMonitorQueryId } from '../hooks/use_monitor_query_id';
|
||||
|
||||
export const FailedTestsCount = (time: { to: string; from: string }) => {
|
||||
|
@ -19,21 +18,19 @@ export const FailedTestsCount = (time: { to: string; from: string }) => {
|
|||
const monitorId = useMonitorQueryId();
|
||||
|
||||
return (
|
||||
<KpiWrapper>
|
||||
<ExploratoryViewEmbeddable
|
||||
reportType="single-metric"
|
||||
attributes={[
|
||||
{
|
||||
time,
|
||||
reportDefinitions: {
|
||||
'monitor.id': [monitorId],
|
||||
},
|
||||
dataType: 'synthetics',
|
||||
selectedMetricField: 'monitor_failed_tests',
|
||||
name: 'synthetics-series-1',
|
||||
<ExploratoryViewEmbeddable
|
||||
reportType="single-metric"
|
||||
attributes={[
|
||||
{
|
||||
time,
|
||||
reportDefinitions: {
|
||||
'monitor.id': [monitorId],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</KpiWrapper>
|
||||
dataType: 'synthetics',
|
||||
selectedMetricField: 'monitor_failed_tests',
|
||||
name: 'synthetics-series-1',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -9,8 +9,6 @@ import React from 'react';
|
|||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import { ReportTypes } from '@kbn/observability-plugin/public';
|
||||
import { ClientPluginsStart } from '../../../../../plugin';
|
||||
|
||||
import { KpiWrapper } from './kpi_wrapper';
|
||||
import { useMonitorQueryId } from '../hooks/use_monitor_query_id';
|
||||
|
||||
interface AvailabilityPanelprops {
|
||||
|
@ -28,20 +26,18 @@ export const AvailabilityPanel = (props: AvailabilityPanelprops) => {
|
|||
const monitorId = useMonitorQueryId();
|
||||
|
||||
return (
|
||||
<KpiWrapper>
|
||||
<ExploratoryViewEmbeddable
|
||||
align="left"
|
||||
reportType={ReportTypes.SINGLE_METRIC}
|
||||
attributes={[
|
||||
{
|
||||
time: props,
|
||||
name: 'Monitor availability',
|
||||
dataType: 'synthetics',
|
||||
selectedMetricField: 'monitor_availability',
|
||||
reportDefinitions: { 'monitor.id': [monitorId] },
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</KpiWrapper>
|
||||
<ExploratoryViewEmbeddable
|
||||
customHeight="70px"
|
||||
reportType={ReportTypes.SINGLE_METRIC}
|
||||
attributes={[
|
||||
{
|
||||
time: props,
|
||||
name: 'Monitor availability',
|
||||
dataType: 'synthetics',
|
||||
selectedMetricField: 'monitor_availability',
|
||||
reportDefinitions: { 'monitor.id': [monitorId] },
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -27,24 +27,23 @@ export const AvailabilitySparklines = (props: AvailabilitySparklinesProps) => {
|
|||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<>
|
||||
<ExploratoryViewEmbeddable
|
||||
reportType={ReportTypes.KPI}
|
||||
axisTitlesVisibility={{ x: false, yRight: false, yLeft: false }}
|
||||
legendIsVisible={false}
|
||||
hideTicks={true}
|
||||
attributes={[
|
||||
{
|
||||
seriesType: 'area',
|
||||
time: props,
|
||||
name: 'Monitor availability',
|
||||
dataType: 'synthetics',
|
||||
selectedMetricField: 'monitor_availability',
|
||||
reportDefinitions: { 'monitor.id': [monitorId] },
|
||||
color: theme.eui.euiColorVis1,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</>
|
||||
<ExploratoryViewEmbeddable
|
||||
customHeight="70px"
|
||||
reportType={ReportTypes.KPI}
|
||||
axisTitlesVisibility={{ x: false, yRight: false, yLeft: false }}
|
||||
legendIsVisible={false}
|
||||
hideTicks={true}
|
||||
attributes={[
|
||||
{
|
||||
seriesType: 'area',
|
||||
time: props,
|
||||
name: 'Monitor availability',
|
||||
dataType: 'synthetics',
|
||||
selectedMetricField: 'monitor_availability',
|
||||
reportDefinitions: { 'monitor.id': [monitorId] },
|
||||
color: theme.eui.euiColorVis1,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -10,7 +10,6 @@ import { useKibana } from '@kbn/kibana-react-plugin/public';
|
|||
import { ReportTypes } from '@kbn/observability-plugin/public';
|
||||
import { ClientPluginsStart } from '../../../../../plugin';
|
||||
|
||||
import { KpiWrapper } from './kpi_wrapper';
|
||||
import { useMonitorQueryId } from '../hooks/use_monitor_query_id';
|
||||
|
||||
interface DurationPanelProps {
|
||||
|
@ -27,19 +26,18 @@ export const DurationPanel = (props: DurationPanelProps) => {
|
|||
const monitorId = useMonitorQueryId();
|
||||
|
||||
return (
|
||||
<KpiWrapper>
|
||||
<ExploratoryViewEmbeddable
|
||||
reportType={ReportTypes.SINGLE_METRIC}
|
||||
attributes={[
|
||||
{
|
||||
time: props,
|
||||
name: 'Monitor duration',
|
||||
dataType: 'synthetics',
|
||||
selectedMetricField: 'monitor_duration',
|
||||
reportDefinitions: { 'monitor.id': [monitorId] },
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</KpiWrapper>
|
||||
<ExploratoryViewEmbeddable
|
||||
customHeight="70px"
|
||||
reportType={ReportTypes.SINGLE_METRIC}
|
||||
attributes={[
|
||||
{
|
||||
time: props,
|
||||
name: 'Monitor duration',
|
||||
dataType: 'synthetics',
|
||||
selectedMetricField: 'monitor_duration',
|
||||
reportDefinitions: { 'monitor.id': [monitorId] },
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -26,7 +26,7 @@ export const MonitorDurationTrend = (props: MonitorDurationTrendProps) => {
|
|||
|
||||
return (
|
||||
<ExploratoryViewEmbeddable
|
||||
customHeight={'300px'}
|
||||
customHeight="240px"
|
||||
reportType="kpi-over-time"
|
||||
attributes={metricsToShow.map((metric) => ({
|
||||
dataType: 'synthetics',
|
||||
|
|
|
@ -1,29 +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 { css } from '@emotion/react';
|
||||
import { useEuiTheme } from '@elastic/eui';
|
||||
|
||||
export const KpiWrapper: React.FC<React.PropsWithChildren<{}>> = ({ children }) => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
|
||||
const wrapperStyle = css`
|
||||
border: none;
|
||||
& > span.euiLoadingSpinner {
|
||||
margin: ${euiTheme.size.s};
|
||||
}
|
||||
|
||||
.legacyMtrVis__container > div {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
`;
|
||||
|
||||
return <div css={wrapperStyle}>{children}</div>;
|
||||
};
|
|
@ -8,7 +8,6 @@
|
|||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import React from 'react';
|
||||
import { ReportTypes } from '@kbn/observability-plugin/public';
|
||||
import { KpiWrapper } from './kpi_wrapper';
|
||||
import { ClientPluginsStart } from '../../../../../plugin';
|
||||
import { useMonitorQueryId } from '../hooks/use_monitor_query_id';
|
||||
|
||||
|
@ -25,20 +24,18 @@ export const MonitorErrorsCount = (props: MonitorErrorsCountProps) => {
|
|||
const monitorId = useMonitorQueryId();
|
||||
|
||||
return (
|
||||
<KpiWrapper>
|
||||
<ExploratoryViewEmbeddable
|
||||
align="left"
|
||||
reportType={ReportTypes.SINGLE_METRIC}
|
||||
attributes={[
|
||||
{
|
||||
time: props,
|
||||
reportDefinitions: { 'monitor.id': [monitorId] },
|
||||
dataType: 'synthetics',
|
||||
selectedMetricField: 'monitor_errors',
|
||||
name: 'synthetics-series-1',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</KpiWrapper>
|
||||
<ExploratoryViewEmbeddable
|
||||
customHeight="70px"
|
||||
reportType={ReportTypes.SINGLE_METRIC}
|
||||
attributes={[
|
||||
{
|
||||
time: props,
|
||||
reportDefinitions: { 'monitor.id': [monitorId] },
|
||||
dataType: 'synthetics',
|
||||
selectedMetricField: 'monitor_errors',
|
||||
name: 'synthetics-series-1',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -53,7 +53,7 @@ export const MonitorSummary = () => {
|
|||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={2}>
|
||||
<EuiPanel hasShadow={false} hasBorder paddingSize="m" css={{ height: 158 }}>
|
||||
<EuiPanel hasShadow={false} hasBorder paddingSize="m" css={{ height: 120 }}>
|
||||
<EuiFlexGroup alignItems="center" gutterSize="m">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiTitle size="xs">
|
||||
|
|
|
@ -5,28 +5,52 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { createContext, useContext } from 'react';
|
||||
import React, { createContext, useContext, useEffect, useMemo } from 'react';
|
||||
import { useFetcher } from '@kbn/observability-plugin/public';
|
||||
import { DataViewsPublicPluginStart, DataView } from '@kbn/data-views-plugin/public';
|
||||
import { useHasData } from '../components/monitors_page/overview/empty_state/use_has_data';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { IHttpSerializedFetchError } from '../state/utils/http_error';
|
||||
import { getIndexStatus, selectIndexState } from '../state';
|
||||
|
||||
export const SyntheticsDataViewContext = createContext({} as DataView);
|
||||
export const SyntheticsDataViewContext = createContext(
|
||||
{} as {
|
||||
dataView?: DataView;
|
||||
loading?: boolean;
|
||||
indices?: string;
|
||||
error?: IHttpSerializedFetchError | null;
|
||||
hasData?: boolean;
|
||||
}
|
||||
);
|
||||
|
||||
export const SyntheticsDataViewContextProvider: React.FC<{
|
||||
dataViews: DataViewsPublicPluginStart;
|
||||
}> = ({ children, dataViews }) => {
|
||||
const { settings, data: indexStatus } = useHasData();
|
||||
const { loading: hasDataLoading, error, data: indexStatus } = useSelector(selectIndexState);
|
||||
|
||||
const heartbeatIndices = settings?.heartbeatIndices || '';
|
||||
const heartbeatIndices = indexStatus?.indices || '';
|
||||
|
||||
const { data } = useFetcher<Promise<DataView | undefined>>(async () => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const hasData = Boolean(indexStatus?.indexExists);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(getIndexStatus());
|
||||
}, [dispatch]);
|
||||
|
||||
const { data, loading } = useFetcher<Promise<DataView | undefined>>(async () => {
|
||||
if (heartbeatIndices && indexStatus?.indexExists) {
|
||||
// this only creates an dateView in memory, not as saved object
|
||||
return dataViews.create({ title: heartbeatIndices });
|
||||
}
|
||||
}, [heartbeatIndices, indexStatus?.indexExists]);
|
||||
|
||||
return <SyntheticsDataViewContext.Provider value={data!} children={children} />;
|
||||
const isLoading = loading || hasDataLoading;
|
||||
|
||||
const value = useMemo(() => {
|
||||
return { dataView: data, indices: heartbeatIndices, isLoading, error, hasData };
|
||||
}, [data, heartbeatIndices, isLoading, error, hasData]);
|
||||
|
||||
return <SyntheticsDataViewContext.Provider value={value} children={children} />;
|
||||
};
|
||||
|
||||
export const useSyntheticsDataView = () => useContext(SyntheticsDataViewContext);
|
||||
|
|
|
@ -17,7 +17,7 @@ import {
|
|||
} from '@kbn/kibana-react-plugin/public';
|
||||
import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common';
|
||||
import { InspectorContextProvider } from '@kbn/observability-plugin/public';
|
||||
import { SyntheticsAppProps } from './contexts';
|
||||
import { SyntheticsAppProps, SyntheticsDataViewContextProvider } from './contexts';
|
||||
|
||||
import {
|
||||
SyntheticsRefreshContextProvider,
|
||||
|
@ -98,21 +98,23 @@ const Application = (props: SyntheticsAppProps) => {
|
|||
<EuiThemeProvider darkMode={darkMode}>
|
||||
<SyntheticsRefreshContextProvider>
|
||||
<SyntheticsSettingsContextProvider {...props}>
|
||||
<SyntheticsThemeContextProvider darkMode={darkMode}>
|
||||
<SyntheticsStartupPluginsContextProvider {...startPlugins}>
|
||||
<div className={APP_WRAPPER_CLASS} data-test-subj="syntheticsApp">
|
||||
<RedirectAppLinks
|
||||
className={APP_WRAPPER_CLASS}
|
||||
application={core.application}
|
||||
>
|
||||
<InspectorContextProvider>
|
||||
<PageRouter />
|
||||
<ActionMenu appMountParameters={appMountParameters} />
|
||||
</InspectorContextProvider>
|
||||
</RedirectAppLinks>
|
||||
</div>
|
||||
</SyntheticsStartupPluginsContextProvider>
|
||||
</SyntheticsThemeContextProvider>
|
||||
<SyntheticsDataViewContextProvider dataViews={startPlugins.dataViews}>
|
||||
<SyntheticsThemeContextProvider darkMode={darkMode}>
|
||||
<SyntheticsStartupPluginsContextProvider {...startPlugins}>
|
||||
<div className={APP_WRAPPER_CLASS} data-test-subj="syntheticsApp">
|
||||
<RedirectAppLinks
|
||||
className={APP_WRAPPER_CLASS}
|
||||
application={core.application}
|
||||
>
|
||||
<InspectorContextProvider>
|
||||
<PageRouter />
|
||||
<ActionMenu appMountParameters={appMountParameters} />
|
||||
</InspectorContextProvider>
|
||||
</RedirectAppLinks>
|
||||
</div>
|
||||
</SyntheticsStartupPluginsContextProvider>
|
||||
</SyntheticsThemeContextProvider>
|
||||
</SyntheticsDataViewContextProvider>
|
||||
</SyntheticsSettingsContextProvider>
|
||||
</SyntheticsRefreshContextProvider>
|
||||
</EuiThemeProvider>
|
||||
|
|
|
@ -9,6 +9,20 @@ import { API_URLS } from '../../../../common/constants';
|
|||
import { StatesIndexStatus, StatesIndexStatusType } from '../../../../common/runtime_types';
|
||||
import { apiService } from './utils';
|
||||
|
||||
let indexStatusPromise: Promise<{ indexExists: boolean; indices: string }> | null = null;
|
||||
|
||||
export const fetchIndexStatus = async (): Promise<StatesIndexStatus> => {
|
||||
return await apiService.get(API_URLS.INDEX_STATUS, undefined, StatesIndexStatusType);
|
||||
if (indexStatusPromise) {
|
||||
return indexStatusPromise;
|
||||
}
|
||||
indexStatusPromise = apiService.get(API_URLS.INDEX_STATUS, undefined, StatesIndexStatusType);
|
||||
indexStatusPromise.then(
|
||||
() => {
|
||||
indexStatusPromise = null;
|
||||
},
|
||||
() => {
|
||||
indexStatusPromise = null;
|
||||
}
|
||||
);
|
||||
return indexStatusPromise;
|
||||
};
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import { AllSeries } from '@kbn/observability-plugin/public';
|
||||
import { getExploratoryViewFilter } from '../../../../services/data/get_exp_view_filter';
|
||||
import { useExpViewAttributes } from './use_exp_view_attrs';
|
||||
|
@ -28,13 +28,16 @@ export function PageLoadDistChart({ onPercentileChange, breakdown }: Props) {
|
|||
const kibana = useKibanaServices();
|
||||
const { ExploratoryViewEmbeddable } = kibana.observability!;
|
||||
|
||||
const onBrushEnd = ({ range }: { range: number[] }) => {
|
||||
if (!range) {
|
||||
return;
|
||||
}
|
||||
const [minX, maxX] = range;
|
||||
onPercentileChange(minX, maxX);
|
||||
};
|
||||
const onBrushEnd = useCallback(
|
||||
({ range }: { range: number[] }) => {
|
||||
if (!range) {
|
||||
return;
|
||||
}
|
||||
const [minX, maxX] = range;
|
||||
onPercentileChange(minX, maxX);
|
||||
},
|
||||
[onPercentileChange]
|
||||
);
|
||||
|
||||
const { reportDefinitions, time } = useExpViewAttributes();
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import moment from 'moment';
|
||||
import React from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import {
|
||||
AllSeries,
|
||||
fromQuery,
|
||||
|
@ -51,24 +51,27 @@ export function PageViewsChart({ breakdown }: Props) {
|
|||
filters: getExploratoryViewFilter(uxUiFilters, urlParams),
|
||||
},
|
||||
];
|
||||
const onBrushEnd = ({ range }: { range: number[] }) => {
|
||||
if (!range) {
|
||||
return;
|
||||
}
|
||||
const [minX, maxX] = range;
|
||||
const onBrushEnd = useCallback(
|
||||
({ range }: { range: number[] }) => {
|
||||
if (!range) {
|
||||
return;
|
||||
}
|
||||
const [minX, maxX] = range;
|
||||
|
||||
const rangeFrom = moment(minX).toISOString();
|
||||
const rangeTo = moment(maxX).toISOString();
|
||||
const rangeFrom = moment(minX).toISOString();
|
||||
const rangeTo = moment(maxX).toISOString();
|
||||
|
||||
history.push({
|
||||
...history.location,
|
||||
search: fromQuery({
|
||||
...toQuery(history.location.search),
|
||||
rangeFrom,
|
||||
rangeTo,
|
||||
}),
|
||||
});
|
||||
};
|
||||
history.push({
|
||||
...history.location,
|
||||
search: fromQuery({
|
||||
...toQuery(history.location.search),
|
||||
rangeFrom,
|
||||
rangeTo,
|
||||
}),
|
||||
});
|
||||
},
|
||||
[history]
|
||||
);
|
||||
|
||||
if (!dataViewTitle) {
|
||||
return null;
|
||||
|
@ -76,11 +79,10 @@ export function PageViewsChart({ breakdown }: Props) {
|
|||
|
||||
return (
|
||||
<ExploratoryViewEmbeddable
|
||||
customHeight="300px"
|
||||
customHeight={'300px'}
|
||||
attributes={allSeries}
|
||||
onBrushEnd={onBrushEnd}
|
||||
reportType="kpi-over-time"
|
||||
dataTypesIndexPatterns={{ ux: dataViewTitle }}
|
||||
isSingleMetric={true}
|
||||
axisTitlesVisibility={{ x: false, yRight: true, yLeft: true }}
|
||||
legendIsVisible={Boolean(breakdown)}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui';
|
||||
import { I18LABELS } from '../translations';
|
||||
import { BreakdownFilter } from '../breakdowns/breakdown_filter';
|
||||
|
@ -22,9 +22,9 @@ export function PageLoadDistribution() {
|
|||
|
||||
const [breakdown, setBreakdown] = useState<BreakdownItem | null>(null);
|
||||
|
||||
const onPercentileChange = (min: number, max: number) => {
|
||||
const onPercentileChange = useCallback((min: number, max: number) => {
|
||||
setPercentileRange({ min, max });
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div data-cy="pageLoadDist">
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue