[Exploratory view] Improve/Polish components usage (#96782)

This commit is contained in:
Shahzad 2021-04-15 11:55:57 +02:00 committed by GitHub
parent 2fb8b92ec9
commit c68eab6298
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
53 changed files with 663 additions and 388 deletions

View file

@ -38,18 +38,20 @@ export const fetchUxOverviewDate = async ({
};
};
export async function hasRumData({
absoluteTime,
}: HasDataParams): Promise<UXHasDataResponse> {
export async function hasRumData(
params: HasDataParams
): Promise<UXHasDataResponse> {
return await callApmApi({
endpoint: 'GET /api/apm/observability_overview/has_rum_data',
signal: null,
params: {
query: {
start: new Date(absoluteTime.start).toISOString(),
end: new Date(absoluteTime.end).toISOString(),
uiFilters: '',
},
query: params?.absoluteTime
? {
start: new Date(params.absoluteTime.start).toISOString(),
end: new Date(params.absoluteTime.end).toISOString(),
uiFilters: '',
}
: undefined,
},
});
}

View file

@ -263,7 +263,7 @@ const rumJSErrors = createApmServerRoute({
const rumHasDataRoute = createApmServerRoute({
endpoint: 'GET /api/apm/observability_overview/has_rum_data',
params: t.type({
params: t.partial({
query: t.intersection([uiFiltersRt, rangeRt]),
}),
options: { tags: ['access:apm'] },

View file

@ -7,11 +7,13 @@
import React from 'react';
import { fireEvent, screen, waitFor } from '@testing-library/react';
import { mockIndexPattern, render } from '../rtl_helpers';
import { mockAppIndexPattern, mockIndexPattern, render } from '../rtl_helpers';
import { buildFilterLabel, FilterLabel } from './filter_label';
import * as useSeriesHook from '../hooks/use_series_filters';
describe('FilterLabel', function () {
mockAppIndexPattern();
const invertFilter = jest.fn();
jest.spyOn(useSeriesHook, 'useSeriesFilters').mockReturnValue({
invertFilter,

View file

@ -8,7 +8,7 @@
import React from 'react';
import { injectI18n } from '@kbn/i18n/react';
import { esFilters, Filter, IndexPattern } from '../../../../../../../../src/plugins/data/public';
import { useIndexPatternContext } from '../hooks/use_default_index_pattern';
import { useAppIndexPatternContext } from '../hooks/use_app_index_pattern';
import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public';
import { useSeriesFilters } from '../hooks/use_series_filters';
@ -58,7 +58,7 @@ export function FilterLabel({
}: Props) {
const FilterItem = injectI18n(esFilters.FilterItem);
const { indexPattern } = useIndexPatternContext();
const { indexPattern } = useAppIndexPatternContext();
const filter = buildFilterLabel({ field, value, label, indexPattern, negate });

View file

@ -0,0 +1,24 @@
/*
* 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 { FieldFormat } from '../../types';
import { TRANSACTION_DURATION } from '../constants/elasticsearch_fieldnames';
export const apmFieldFormats: FieldFormat[] = [
{
field: TRANSACTION_DURATION,
format: {
id: 'duration',
params: {
inputFormat: 'microseconds',
outputFormat: 'asMilliseconds',
outputPrecision: 0,
showSuffix: true,
},
},
},
];

View file

@ -8,6 +8,7 @@
import { ConfigProps, DataSeries } from '../../types';
import { FieldLabels } from '../constants';
import { buildPhraseFilter } from '../utils';
import { TRANSACTION_DURATION } from '../constants/elasticsearch_fieldnames';
export function getServiceLatencyLensConfig({ seriesId, indexPattern }: ConfigProps): DataSeries {
return {
@ -37,7 +38,7 @@ export function getServiceLatencyLensConfig({ seriesId, indexPattern }: ConfigPr
'user_agent.device.name',
],
filters: buildPhraseFilter('transaction.type', 'request', indexPattern),
labels: { ...FieldLabels },
labels: { ...FieldLabels, [TRANSACTION_DURATION]: 'Latency' },
reportDefinitions: [
{
field: 'service.name',

View file

@ -8,6 +8,7 @@
import { ConfigProps, DataSeries } from '../../types';
import { FieldLabels } from '../constants/constants';
import { buildPhraseFilter } from '../utils';
import { TRANSACTION_DURATION } from '../constants/elasticsearch_fieldnames';
export function getServiceThroughputLensConfig({
seriesId,
@ -40,7 +41,7 @@ export function getServiceThroughputLensConfig({
'user_agent.device.name',
],
filters: buildPhraseFilter('transaction.type', 'request', indexPattern),
labels: { ...FieldLabels },
labels: { ...FieldLabels, [TRANSACTION_DURATION]: 'Throughput' },
reportDefinitions: [
{
field: 'service.name',

View file

@ -61,10 +61,10 @@ export const ReportToDataTypeMap: Record<ReportViewTypeId, AppDataType> = {
upp: 'synthetics',
tpt: 'apm',
svl: 'apm',
kpi: 'rum',
pld: 'rum',
nwk: 'metrics',
mem: 'metrics',
logs: 'logs',
cpu: 'metrics',
kpi: 'ux',
pld: 'ux',
nwk: 'infra_metrics',
mem: 'infra_metrics',
logs: 'infra_logs',
cpu: 'infra_metrics',
};

View file

@ -6,12 +6,14 @@
*/
import { LensAttributes } from './lens_attributes';
import { mockIndexPattern } from '../rtl_helpers';
import { mockAppIndexPattern, mockIndexPattern } from '../rtl_helpers';
import { getDefaultConfigs } from './default_configs';
import { sampleAttribute } from './test_data/sample_attribute';
import { LCP_FIELD, SERVICE_NAME, USER_AGENT_NAME } from './constants/elasticsearch_fieldnames';
describe('Lens Attribute', () => {
mockAppIndexPattern();
const reportViewConfig = getDefaultConfigs({
reportType: 'pld',
indexPattern: mockIndexPattern,
@ -53,7 +55,6 @@ describe('Lens Attribute', () => {
readFromDocValues: true,
},
fieldName: 'transaction.type',
columnType: null,
})
);
});
@ -72,7 +73,6 @@ describe('Lens Attribute', () => {
readFromDocValues: true,
},
fieldName: 'transaction.duration.us',
columnType: null,
})
);
});

View file

@ -45,6 +45,34 @@ function buildNumberColumn(sourceField: string) {
};
}
export const parseCustomFieldName = (
sourceField: string,
reportViewConfig: DataSeries,
selectedDefinitions: Record<string, string>
) => {
let fieldName = sourceField;
let columnType;
const rdf = reportViewConfig.reportDefinitions ?? [];
const customField = rdf.find(({ field }) => field === fieldName);
if (customField) {
if (selectedDefinitions[fieldName]) {
fieldName = selectedDefinitions[fieldName];
if (customField?.options)
columnType = customField?.options?.find(({ field }) => field === fieldName)?.columnType;
} else if (customField.defaultValue) {
fieldName = customField.defaultValue;
} else if (customField.options?.[0].field) {
fieldName = customField.options?.[0].field;
columnType = customField.options?.[0].columnType;
}
}
return { fieldName, columnType };
};
export class LensAttributes {
indexPattern: IndexPattern;
layers: Record<string, PersistedIndexPatternLayer>;
@ -124,6 +152,18 @@ export class LensAttributes {
};
}
getNumberColumn(sourceField: string, columnType?: string, operationType?: string) {
if (columnType === 'operation' || operationType) {
if (operationType === 'median' || operationType === 'average') {
return this.getNumberOperationColumn(sourceField, operationType);
}
if (operationType?.includes('th')) {
return this.getPercentileNumberColumn(sourceField, operationType);
}
}
return this.getNumberRangeColumn(sourceField);
}
getNumberOperationColumn(
sourceField: string,
operationType: 'average' | 'median'
@ -149,7 +189,7 @@ export class LensAttributes {
...buildNumberColumn(sourceField),
label: i18n.translate('xpack.observability.expView.columns.label', {
defaultMessage: '{percentileValue} percentile of {sourceField}',
values: { sourceField, percentileValue },
values: { sourceField: this.reportViewConfig.labels[sourceField], percentileValue },
}),
operationType: 'percentile',
params: { percentile: Number(percentileValue.split('th')[0]) },
@ -186,15 +226,7 @@ export class LensAttributes {
return this.getDateHistogramColumn(fieldName);
}
if (fieldType === 'number') {
if (columnType === 'operation' || operationType) {
if (operationType === 'median' || operationType === 'average') {
return this.getNumberOperationColumn(fieldName, operationType);
}
if (operationType?.includes('th')) {
return this.getPercentileNumberColumn(sourceField, operationType);
}
}
return this.getNumberRangeColumn(fieldName);
return this.getNumberColumn(fieldName, columnType, operationType);
}
// FIXME review my approach again
@ -202,27 +234,7 @@ export class LensAttributes {
}
getCustomFieldName(sourceField: string) {
let fieldName = sourceField;
let columnType = null;
const rdf = this.reportViewConfig.reportDefinitions ?? [];
const customField = rdf.find(({ field }) => field === fieldName);
if (customField) {
if (this.reportDefinitions[fieldName]) {
fieldName = this.reportDefinitions[fieldName];
if (customField?.options)
columnType = customField?.options?.find(({ field }) => field === fieldName)?.columnType;
} else if (customField.defaultValue) {
fieldName = customField.defaultValue;
} else if (customField.options?.[0].field) {
fieldName = customField.options?.[0].field;
columnType = customField.options?.[0].columnType;
}
}
return { fieldName, columnType };
return parseCustomFieldName(sourceField, this.reportViewConfig, this.reportDefinitions);
}
getFieldMeta(sourceField: string) {

View file

@ -25,6 +25,7 @@ import {
USER_AGENT_OS,
USER_AGENT_VERSION,
TRANSACTION_TIME_TO_FIRST_BYTE,
TRANSACTION_URL,
} from '../constants/elasticsearch_fieldnames';
export function getKPITrendsLensConfig({ seriesId, indexPattern }: ConfigProps): DataSeries {
@ -42,6 +43,10 @@ export function getKPITrendsLensConfig({ seriesId, indexPattern }: ConfigProps):
},
hasOperationType: false,
defaultFilters: [
{
field: TRANSACTION_URL,
isNegated: false,
},
USER_AGENT_OS,
CLIENT_GEO_COUNTRY_NAME,
USER_AGENT_DEVICE,

View file

@ -21,6 +21,7 @@ import {
TRANSACTION_DURATION,
TRANSACTION_TIME_TO_FIRST_BYTE,
TRANSACTION_TYPE,
TRANSACTION_URL,
USER_AGENT_DEVICE,
USER_AGENT_NAME,
USER_AGENT_OS,
@ -42,6 +43,10 @@ export function getPerformanceDistLensConfig({ seriesId, indexPattern }: ConfigP
},
hasOperationType: false,
defaultFilters: [
{
field: TRANSACTION_URL,
isNegated: false,
},
USER_AGENT_OS,
CLIENT_GEO_COUNTRY_NAME,
USER_AGENT_DEVICE,

View file

@ -42,6 +42,6 @@ export function getMonitorDurationConfig({ seriesId }: Props): DataSeries {
field: 'monitor.id',
},
],
labels: { ...FieldLabels },
labels: { ...FieldLabels, 'monitor.duration.us': 'Monitor duration' },
};
}

View file

@ -7,12 +7,14 @@
import React from 'react';
import { fireEvent, screen, waitFor } from '@testing-library/dom';
import { render, mockUrlStorage, mockCore } from './rtl_helpers';
import { render, mockUrlStorage, mockCore, mockAppIndexPattern } from './rtl_helpers';
import { ExploratoryView } from './exploratory_view';
import { getStubIndexPattern } from '../../../../../../../src/plugins/data/public/test_utils';
import * as obsvInd from './utils/observability_index_patterns';
describe('ExploratoryView', () => {
mockAppIndexPattern();
beforeEach(() => {
const indexPattern = getStubIndexPattern(
'apm-*',

View file

@ -15,6 +15,8 @@ import { useUrlStorage } from './hooks/use_url_storage';
import { useLensAttributes } from './hooks/use_lens_attributes';
import { EmptyView } from './components/empty_view';
import { TypedLensByValueInput } from '../../../../../lens/public';
import { useAppIndexPatternContext } from './hooks/use_app_index_pattern';
import { ReportToDataTypeMap } from './configurations/constants';
export function ExploratoryView() {
const {
@ -25,6 +27,8 @@ export function ExploratoryView() {
null
);
const { loadIndexPattern } = useAppIndexPatternContext();
const LensComponent = lens?.EmbeddableComponent;
const { firstSeriesId: seriesId, firstSeries: series } = useUrlStorage();
@ -33,13 +37,19 @@ export function ExploratoryView() {
seriesId,
});
useEffect(() => {
if (series?.reportType || series?.dataType) {
loadIndexPattern({ dataType: series?.dataType ?? ReportToDataTypeMap[series?.reportType] });
}
}, [series?.reportType, series?.dataType, loadIndexPattern]);
useEffect(() => {
setLensAttributes(lensAttributesT);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [JSON.stringify(lensAttributesT ?? {}), series?.reportType, series?.time?.from]);
return (
<EuiPanel style={{ maxWidth: 1800, minWidth: 1200, margin: '0 auto' }}>
<EuiPanel style={{ maxWidth: 1800, minWidth: 800, margin: '0 auto' }}>
{lens ? (
<>
<ExploratoryViewHeader lensAttributes={lensAttributes} seriesId={seriesId} />

View file

@ -0,0 +1,115 @@
/*
* 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, { createContext, useContext, Context, useState, useCallback, useMemo } from 'react';
import { IndexPattern } from '../../../../../../../../src/plugins/data/common';
import { AppDataType } from '../types';
import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public';
import { ObservabilityPublicPluginsStart } from '../../../../plugin';
import { ObservabilityIndexPatterns } from '../utils/observability_index_patterns';
import { getDataHandler } from '../../../../data_handler';
import { UXHasDataResponse } from '../../../../typings/fetch_overview_data';
export interface IIndexPatternContext {
loading: boolean;
selectedApp: AppDataType;
indexPatterns: IndexPatternState;
hasAppData: HasAppDataState;
loadIndexPattern: (params: { dataType: AppDataType }) => void;
}
export const IndexPatternContext = createContext<Partial<IIndexPatternContext>>({});
interface ProviderProps {
children: JSX.Element;
}
type HasAppDataState = Record<AppDataType, boolean | null>;
type IndexPatternState = Record<AppDataType, IndexPattern>;
export function IndexPatternContextProvider({ children }: ProviderProps) {
const [loading, setLoading] = useState(false);
const [selectedApp, setSelectedApp] = useState<AppDataType>();
const [indexPatterns, setIndexPatterns] = useState<IndexPatternState>({} as IndexPatternState);
const [hasAppData, setHasAppData] = useState<HasAppDataState>({
infra_metrics: null,
infra_logs: null,
synthetics: null,
ux: null,
apm: null,
} as HasAppDataState);
const {
services: { data },
} = useKibana<ObservabilityPublicPluginsStart>();
const checkIfAppHasData = async (dataType: AppDataType) => {
const handler = getDataHandler(dataType === 'synthetics' ? 'uptime' : dataType);
return handler?.hasData();
};
const loadIndexPattern: IIndexPatternContext['loadIndexPattern'] = useCallback(
async ({ dataType }) => {
setSelectedApp(dataType);
if (hasAppData[dataType] === null) {
setLoading(true);
try {
let hasDataT = await checkIfAppHasData(dataType);
if (dataType === 'ux') {
hasDataT = (hasDataT as UXHasDataResponse).hasData as boolean;
}
setHasAppData((prevState) => ({ ...prevState, [dataType]: hasDataT }));
if (hasDataT || hasAppData?.[dataType]) {
const obsvIndexP = new ObservabilityIndexPatterns(data);
const indPattern = await obsvIndexP.getIndexPattern(dataType);
setIndexPatterns((prevState) => ({ ...prevState, [dataType]: indPattern }));
}
setLoading(false);
} catch (e) {
setLoading(false);
}
}
},
[data, hasAppData]
);
return (
<IndexPatternContext.Provider
value={{
loading,
hasAppData,
selectedApp,
indexPatterns,
loadIndexPattern,
}}
>
{children}
</IndexPatternContext.Provider>
);
}
export const useAppIndexPatternContext = () => {
const { selectedApp, loading, hasAppData, loadIndexPattern, indexPatterns } = useContext(
(IndexPatternContext as unknown) as Context<IIndexPatternContext>
);
return useMemo(() => {
return {
hasAppData,
selectedApp,
loading,
indexPattern: indexPatterns?.[selectedApp],
hasData: hasAppData?.[selectedApp],
loadIndexPattern,
};
}, [hasAppData, indexPatterns, loadIndexPattern, loading, selectedApp]);
};

View file

@ -1,62 +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, { createContext, useContext, Context, useState, useEffect } from 'react';
import { IndexPattern } from '../../../../../../../../src/plugins/data/common';
import { AppDataType } from '../types';
import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public';
import { ObservabilityPublicPluginsStart } from '../../../../plugin';
import { ObservabilityIndexPatterns } from '../utils/observability_index_patterns';
export interface IIndexPatternContext {
indexPattern: IndexPattern;
loadIndexPattern: (dataType: AppDataType) => void;
}
export const IndexPatternContext = createContext<Partial<IIndexPatternContext>>({});
interface ProviderProps {
indexPattern?: IndexPattern;
children: JSX.Element;
}
export function IndexPatternContextProvider({
children,
indexPattern: initialIndexPattern,
}: ProviderProps) {
const [indexPattern, setIndexPattern] = useState(initialIndexPattern);
useEffect(() => {
setIndexPattern(initialIndexPattern);
}, [initialIndexPattern]);
const {
services: { data },
} = useKibana<ObservabilityPublicPluginsStart>();
const loadIndexPattern = async (dataType: AppDataType) => {
setIndexPattern(undefined);
const obsvIndexP = new ObservabilityIndexPatterns(data);
const indPattern = await obsvIndexP.getIndexPattern(dataType);
setIndexPattern(indPattern!);
};
return (
<IndexPatternContext.Provider
value={{
indexPattern,
loadIndexPattern,
}}
>
{children}
</IndexPatternContext.Provider>
);
}
export const useIndexPatternContext = () => {
return useContext((IndexPatternContext as unknown) as Context<IIndexPatternContext>);
};

View file

@ -1,47 +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 { useFetcher } from '../../../..';
import { IKbnUrlStateStorage } from '../../../../../../../../src/plugins/kibana_utils/public';
import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public';
import { ObservabilityPublicPluginsStart } from '../../../../plugin';
import { AllShortSeries } from './use_url_storage';
import { ReportToDataTypeMap } from '../configurations/constants';
import { DataType, ObservabilityIndexPatterns } from '../utils/observability_index_patterns';
export const useInitExploratoryView = (storage: IKbnUrlStateStorage) => {
const {
services: { data },
} = useKibana<ObservabilityPublicPluginsStart>();
const allSeriesKey = 'sr';
const allSeries = storage.get<AllShortSeries>(allSeriesKey) ?? {};
const allSeriesIds = Object.keys(allSeries);
const firstSeriesId = allSeriesIds?.[0];
const firstSeries = allSeries[firstSeriesId];
let dataType: DataType = firstSeries?.dataType ?? 'rum';
if (firstSeries?.rt) {
dataType = ReportToDataTypeMap[firstSeries?.rt];
}
const { data: indexPattern, error } = useFetcher(() => {
const obsvIndexP = new ObservabilityIndexPatterns(data);
return obsvIndexP.getIndexPattern(dataType);
}, [dataType, data]);
if (error) {
throw error;
}
return indexPattern;
};

View file

@ -12,7 +12,7 @@ import { useUrlStorage } from './use_url_storage';
import { getDefaultConfigs } from '../configurations/default_configs';
import { DataSeries, SeriesUrl, UrlFilter } from '../types';
import { useIndexPatternContext } from './use_default_index_pattern';
import { useAppIndexPatternContext } from './use_app_index_pattern';
interface Props {
seriesId: string;
@ -43,7 +43,7 @@ export const useLensAttributes = ({
const { breakdown, seriesType, operationType, reportType, reportDefinitions = {} } = series ?? {};
const { indexPattern } = useIndexPatternContext();
const { indexPattern } = useAppIndexPatternContext();
return useMemo(() => {
if (!indexPattern || !reportType) {

View file

@ -56,7 +56,7 @@ interface ShortUrlSeries {
export type AllShortSeries = Record<string, ShortUrlSeries>;
export type AllSeries = Record<string, SeriesUrl>;
export const NEW_SERIES_KEY = 'newSeriesKey';
export const NEW_SERIES_KEY = 'new-series-key';
export function useUrlStorage(seriesId?: string) {
const allSeriesKey = 'sr';

View file

@ -13,13 +13,12 @@ import { ExploratoryView } from './exploratory_view';
import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
import { ObservabilityPublicPluginsStart } from '../../../plugin';
import { useBreadcrumbs } from '../../../hooks/use_breadcrumbs';
import { IndexPatternContextProvider } from './hooks/use_default_index_pattern';
import { IndexPatternContextProvider } from './hooks/use_app_index_pattern';
import {
createKbnUrlStateStorage,
withNotifyOnErrors,
} from '../../../../../../../src/plugins/kibana_utils/public/';
import { UrlStorageContextProvider } from './hooks/use_url_storage';
import { useInitExploratoryView } from './hooks/use_init_exploratory_view';
import { WithHeaderLayout } from '../../app/layout/with_header';
export function ExploratoryViewPage() {
@ -45,20 +44,16 @@ export function ExploratoryViewPage() {
...withNotifyOnErrors(notifications!.toasts),
});
const indexPattern = useInitExploratoryView(kbnUrlStateStorage);
return (
<WithHeaderLayout
headerColor={theme.eui.euiColorEmptyShade}
bodyColor={theme.eui.euiPageBackgroundColor}
>
{indexPattern ? (
<IndexPatternContextProvider indexPattern={indexPattern!}>
<UrlStorageContextProvider storage={kbnUrlStateStorage}>
<ExploratoryView />
</UrlStorageContextProvider>
</IndexPatternContextProvider>
) : null}
<IndexPatternContextProvider>
<UrlStorageContextProvider storage={kbnUrlStateStorage}>
<ExploratoryView />
</UrlStorageContextProvider>
</IndexPatternContextProvider>
</WithHeaderLayout>
);
}

View file

@ -22,7 +22,7 @@ import {
import { ObservabilityPublicPluginsStart } from '../../../plugin';
import { EuiThemeProvider } from '../../../../../../../src/plugins/kibana_react/common';
import { lensPluginMock } from '../../../../../lens/public/mocks';
import { IndexPatternContextProvider } from './hooks/use_default_index_pattern';
import { IndexPatternContextProvider } from './hooks/use_app_index_pattern';
import { AllSeries, UrlStorageContextProvider } from './hooks/use_url_storage';
import {
withNotifyOnErrors,
@ -33,6 +33,7 @@ import * as useUrlHook from './hooks/use_url_storage';
import * as useSeriesFilterHook from './hooks/use_series_filters';
import * as useHasDataHook from '../../../hooks/use_has_data';
import * as useValuesListHook from '../../../hooks/use_values_list';
import * as useAppIndexPatternHook from './hooks/use_app_index_pattern';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { getStubIndexPattern } from '../../../../../../../src/plugins/data/public/index_patterns/index_pattern.stub';
@ -148,7 +149,7 @@ export function MockKibanaProvider<ExtraCore extends Partial<CoreStart>>({
<KibanaContextProvider services={{ ...core }} {...kibanaProps}>
<EuiThemeProvider darkMode={false}>
<I18nProvider>
<IndexPatternContextProvider indexPattern={indexPattern}>
<IndexPatternContextProvider>
<UrlStorageContextProvider storage={kbnUrlStateStorage}>
{children}
</UrlStorageContextProvider>
@ -234,6 +235,19 @@ export const mockUseHasData = () => {
return { spy, onRefreshTimeRange };
};
export const mockAppIndexPattern = () => {
const loadIndexPattern = jest.fn();
const spy = jest.spyOn(useAppIndexPatternHook, 'useAppIndexPatternContext').mockReturnValue({
indexPattern: mockIndexPattern,
selectedApp: 'ux',
hasData: true,
loading: false,
hasAppData: { ux: true } as any,
loadIndexPattern,
});
return { spy, loadIndexPattern };
};
export const mockUseValuesList = (values?: string[]) => {
const onRefreshTimeRange = jest.fn();
const spy = jest.spyOn(useValuesListHook, 'useValuesList').mockReturnValue({

View file

@ -66,7 +66,7 @@ export function XYChartTypesSelect({
const { data = [], loading } = useFetcher(() => lens.getXyVisTypes(), [lens]);
let vizTypes = data ?? [];
let vizTypes = data;
if ((excludeChartTypes ?? []).length > 0) {
vizTypes = vizTypes.filter(({ id }) => !excludeChartTypes?.includes(id as SeriesType));
@ -95,6 +95,7 @@ export function XYChartTypesSelect({
return (
<EuiSuperSelect
compressed
prepend="Chart type"
valueOfSelected={value}
isLoading={loading}
options={options}

View file

@ -7,11 +7,13 @@
import React from 'react';
import { fireEvent, screen } from '@testing-library/react';
import { mockUrlStorage, render } from '../../rtl_helpers';
import { mockAppIndexPattern, mockUrlStorage, render } from '../../rtl_helpers';
import { dataTypes, DataTypesCol } from './data_types_col';
import { NEW_SERIES_KEY } from '../../hooks/use_url_storage';
describe('DataTypesCol', function () {
mockAppIndexPattern();
it('should render properly', function () {
const { getByText } = render(<DataTypesCol />);
@ -28,11 +30,11 @@ describe('DataTypesCol', function () {
fireEvent.click(screen.getByText(/user experience\(rum\)/i));
expect(setSeries).toHaveBeenCalledTimes(1);
expect(setSeries).toHaveBeenCalledWith('newSeriesKey', { dataType: 'rum' });
expect(setSeries).toHaveBeenCalledWith(NEW_SERIES_KEY, { dataType: 'ux' });
});
it('should set series on change on already selected', function () {
const { removeSeries } = mockUrlStorage({
mockUrlStorage({
data: {
[NEW_SERIES_KEY]: {
dataType: 'synthetics',
@ -50,10 +52,5 @@ describe('DataTypesCol', function () {
});
expect(button.classList).toContain('euiButton--fill');
fireEvent.click(button);
// undefined on click selected
expect(removeSeries).toHaveBeenCalledWith('newSeriesKey');
});
});

View file

@ -7,27 +7,25 @@
import React from 'react';
import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import styled from 'styled-components';
import { AppDataType } from '../../types';
import { useIndexPatternContext } from '../../hooks/use_default_index_pattern';
import { useAppIndexPatternContext } from '../../hooks/use_app_index_pattern';
import { NEW_SERIES_KEY, useUrlStorage } from '../../hooks/use_url_storage';
export const dataTypes: Array<{ id: AppDataType; label: string }> = [
{ id: 'synthetics', label: 'Synthetic Monitoring' },
{ id: 'rum', label: 'User Experience(RUM)' },
{ id: 'logs', label: 'Logs' },
{ id: 'metrics', label: 'Metrics' },
{ id: 'apm', label: 'APM' },
{ id: 'ux', label: 'User Experience(RUM)' },
// { id: 'infra_logs', label: 'Logs' },
// { id: 'infra_metrics', label: 'Metrics' },
// { id: 'apm', label: 'APM' },
];
export function DataTypesCol() {
const { series, setSeries, removeSeries } = useUrlStorage(NEW_SERIES_KEY);
const { loadIndexPattern, indexPattern } = useIndexPatternContext();
const { loading } = useAppIndexPatternContext();
const onDataTypeChange = (dataType?: AppDataType) => {
if (dataType) {
loadIndexPattern(dataType);
}
if (!dataType) {
removeSeries(NEW_SERIES_KEY);
} else {
@ -38,7 +36,7 @@ export function DataTypesCol() {
const selectedDataType = series.dataType;
return (
<EuiFlexGroup direction="column" gutterSize="xs">
<FlexGroup direction="column" gutterSize="xs">
{dataTypes.map(({ id: dataTypeId, label }) => (
<EuiFlexItem key={dataTypeId}>
<EuiButton
@ -47,16 +45,20 @@ export function DataTypesCol() {
iconType="arrowRight"
color={selectedDataType === dataTypeId ? 'primary' : 'text'}
fill={selectedDataType === dataTypeId}
isDisabled={!indexPattern}
isLoading={!indexPattern && selectedDataType === dataTypeId}
isDisabled={loading}
isLoading={loading && selectedDataType === dataTypeId}
onClick={() => {
onDataTypeChange(dataTypeId === selectedDataType ? undefined : dataTypeId);
onDataTypeChange(dataTypeId);
}}
>
{label}
</EuiButton>
</EuiFlexItem>
))}
</EuiFlexGroup>
</FlexGroup>
);
}
const FlexGroup = styled(EuiFlexGroup)`
width: 100%;
`;

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 React from 'react';
import { SeriesDatePicker } from '../../series_date_picker';
interface Props {
seriesId: string;
}
export function DatePickerCol({ seriesId }: Props) {
return (
<div>
<SeriesDatePicker seriesId={seriesId} />
</div>
);
}

View file

@ -29,7 +29,9 @@ export function OperationTypeSelect({
useEffect(() => {
setSeries(seriesId, { ...series, operationType: operationType || defaultOperationType });
}, [defaultOperationType, seriesId, operationType, setSeries, series]);
// We only want to call this when defaultOperationType changes
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [defaultOperationType]);
const options = [
{
@ -72,6 +74,7 @@ export function OperationTypeSelect({
return (
<EuiSuperSelect
prepend="Calculation"
data-test-subj="operationTypeSelect"
compressed
valueOfSelected={operationType || defaultOperationType}

View file

@ -45,7 +45,7 @@ describe('Series Builder ReportBreakdowns', function () {
fireEvent.click(screen.getByText(/operating system/i));
expect(setSeries).toHaveBeenCalledTimes(1);
expect(setSeries).toHaveBeenCalledWith('newSeriesKey', {
expect(setSeries).toHaveBeenCalledWith(NEW_SERIES_KEY, {
breakdown: USER_AGENT_OS,
reportType: 'pld',
time: { from: 'now-15m', to: 'now' },
@ -66,7 +66,7 @@ describe('Series Builder ReportBreakdowns', function () {
fireEvent.click(screen.getByText(/no breakdown/i));
expect(setSeries).toHaveBeenCalledTimes(1);
expect(setSeries).toHaveBeenCalledWith('newSeriesKey', {
expect(setSeries).toHaveBeenCalledWith(NEW_SERIES_KEY, {
breakdown: undefined,
reportType: 'pld',
time: { from: 'now-15m', to: 'now' },

View file

@ -8,12 +8,20 @@
import React from 'react';
import { fireEvent, screen } from '@testing-library/react';
import { getDefaultConfigs } from '../../configurations/default_configs';
import { mockIndexPattern, mockUrlStorage, mockUseValuesList, render } from '../../rtl_helpers';
import {
mockAppIndexPattern,
mockIndexPattern,
mockUrlStorage,
mockUseValuesList,
render,
} from '../../rtl_helpers';
import { NEW_SERIES_KEY } from '../../hooks/use_url_storage';
import { ReportDefinitionCol } from './report_definition_col';
import { SERVICE_NAME } from '../../configurations/constants/elasticsearch_fieldnames';
describe('Series Builder ReportDefinitionCol', function () {
mockAppIndexPattern();
const dataViewSeries = getDefaultConfigs({
reportType: 'pld',
indexPattern: mockIndexPattern,
@ -23,7 +31,7 @@ describe('Series Builder ReportDefinitionCol', function () {
const { setSeries } = mockUrlStorage({
data: {
'performance-dist': {
dataType: 'rum',
dataType: 'ux',
reportType: 'pld',
time: { from: 'now-30d', to: 'now' },
reportDefinitions: { [SERVICE_NAME]: 'elastic-co' },
@ -54,8 +62,8 @@ describe('Series Builder ReportDefinitionCol', function () {
fireEvent.click(removeBtn);
expect(setSeries).toHaveBeenCalledTimes(1);
expect(setSeries).toHaveBeenCalledWith('newSeriesKey', {
dataType: 'rum',
expect(setSeries).toHaveBeenCalledWith(NEW_SERIES_KEY, {
dataType: 'ux',
reportDefinitions: {},
reportType: 'pld',
time: { from: 'now-30d', to: 'now' },

View file

@ -7,16 +7,32 @@
import React from 'react';
import { EuiBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { useIndexPatternContext } from '../../hooks/use_default_index_pattern';
import styled from 'styled-components';
import { useAppIndexPatternContext } from '../../hooks/use_app_index_pattern';
import { NEW_SERIES_KEY, useUrlStorage } from '../../hooks/use_url_storage';
import { CustomReportField } from '../custom_report_field';
import FieldValueSuggestions from '../../../field_value_suggestions';
import { DataSeries } from '../../types';
import { SeriesChartTypesSelect } from './chart_types';
import { OperationTypeSelect } from './operation_type_select';
import { DatePickerCol } from './date_picker_col';
import { parseCustomFieldName } from '../../configurations/lens_attributes';
function getColumnType(dataView: DataSeries, selectedDefinition: Record<string, string>) {
const { reportDefinitions } = dataView;
const customColumn = reportDefinitions.find((item) => item.custom);
if (customColumn?.field && selectedDefinition[customColumn?.field]) {
const { columnType } = parseCustomFieldName(customColumn.field, dataView, selectedDefinition);
return columnType;
}
return null;
}
const MaxWidthStyle = { maxWidth: 250 };
export function ReportDefinitionCol({ dataViewSeries }: { dataViewSeries: DataSeries }) {
const { indexPattern } = useIndexPatternContext();
const { indexPattern } = useAppIndexPatternContext();
const { series, setSeries } = useUrlStorage(NEW_SERIES_KEY);
@ -54,14 +70,19 @@ export function ReportDefinitionCol({ dataViewSeries }: { dataViewSeries: DataSe
});
};
const columnType = getColumnType(dataViewSeries, rtd);
return (
<EuiFlexGroup direction="column" gutterSize="s">
<FlexGroup direction="column" gutterSize="s">
<EuiFlexItem>
<DatePickerCol seriesId={NEW_SERIES_KEY} />
</EuiFlexItem>
{indexPattern &&
reportDefinitions.map(({ field, custom, options, defaultValue }) => (
<EuiFlexItem key={field}>
{!custom ? (
<EuiFlexGroup justifyContent="flexStart" gutterSize="s" alignItems="center">
<EuiFlexItem grow={false}>
<EuiFlexGroup justifyContent="flexStart" gutterSize="s" alignItems="center" wrap>
<EuiFlexItem grow={false} style={{ flexBasis: 250 }}>
<FieldValueSuggestions
label={labels[field]}
sourceField={field}
@ -70,7 +91,7 @@ export function ReportDefinitionCol({ dataViewSeries }: { dataViewSeries: DataSe
onChange={(val?: string) => onChange(field, val)}
filters={(filters ?? []).map(({ query }) => query)}
time={series.time}
width={200}
fullWidth={true}
/>
</EuiFlexItem>
{rtd?.[field] && (
@ -100,17 +121,21 @@ export function ReportDefinitionCol({ dataViewSeries }: { dataViewSeries: DataSe
)}
</EuiFlexItem>
))}
<EuiFlexItem style={{ width: 200 }}>
<SeriesChartTypesSelect seriesId={NEW_SERIES_KEY} defaultChartType={defaultSeriesType} />
</EuiFlexItem>
{hasOperationType && (
<EuiFlexItem style={{ width: 200 }}>
{(hasOperationType || columnType === 'operation') && (
<EuiFlexItem style={MaxWidthStyle}>
<OperationTypeSelect
seriesId={NEW_SERIES_KEY}
defaultOperationType={yAxisColumn.operationType}
/>
</EuiFlexItem>
)}
</EuiFlexGroup>
<EuiFlexItem style={MaxWidthStyle}>
<SeriesChartTypesSelect seriesId={NEW_SERIES_KEY} defaultChartType={defaultSeriesType} />
</EuiFlexItem>
</FlexGroup>
);
}
const FlexGroup = styled(EuiFlexGroup)`
width: 100%;
`;

View file

@ -7,14 +7,17 @@
import React from 'react';
import { fireEvent, screen } from '@testing-library/react';
import { mockUrlStorage, render } from '../../rtl_helpers';
import { mockAppIndexPattern, mockUrlStorage, render } from '../../rtl_helpers';
import { ReportTypesCol, SELECTED_DATA_TYPE_FOR_REPORT } from './report_types_col';
import { ReportTypes } from '../series_builder';
import { DEFAULT_TIME } from '../../configurations/constants';
import { NEW_SERIES_KEY } from '../../hooks/use_url_storage';
describe('ReportTypesCol', function () {
mockAppIndexPattern();
it('should render properly', function () {
render(<ReportTypesCol reportTypes={ReportTypes.rum} />);
render(<ReportTypesCol reportTypes={ReportTypes.ux} />);
screen.getByText('Performance distribution');
screen.getByText('KPI over time');
});
@ -30,7 +33,7 @@ describe('ReportTypesCol', function () {
fireEvent.click(screen.getByText(/monitor duration/i));
expect(setSeries).toHaveBeenCalledWith('newSeriesKey', {
expect(setSeries).toHaveBeenCalledWith(NEW_SERIES_KEY, {
breakdown: 'user_agent.name',
reportDefinitions: {},
reportType: 'upd',
@ -42,7 +45,7 @@ describe('ReportTypesCol', function () {
it('should set selected as filled', function () {
const { setSeries } = mockUrlStorage({
data: {
newSeriesKey: {
[NEW_SERIES_KEY]: {
dataType: 'synthetics',
reportType: 'upp',
breakdown: 'monitor.status',
@ -61,7 +64,7 @@ describe('ReportTypesCol', function () {
fireEvent.click(button);
// undefined on click selected
expect(setSeries).toHaveBeenCalledWith('newSeriesKey', {
expect(setSeries).toHaveBeenCalledWith(NEW_SERIES_KEY, {
dataType: 'synthetics',
time: DEFAULT_TIME,
});

View file

@ -7,11 +7,13 @@
import React from 'react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui';
import styled from 'styled-components';
import { ReportViewTypeId, SeriesUrl } from '../../types';
import { NEW_SERIES_KEY, useUrlStorage } from '../../hooks/use_url_storage';
import { DEFAULT_TIME } from '../../configurations/constants';
import { useIndexPatternContext } from '../../hooks/use_default_index_pattern';
import { useAppIndexPatternContext } from '../../hooks/use_app_index_pattern';
interface Props {
reportTypes: Array<{ id: ReportViewTypeId; label: string }>;
@ -23,19 +25,29 @@ export function ReportTypesCol({ reportTypes }: Props) {
setSeries,
} = useUrlStorage(NEW_SERIES_KEY);
const { indexPattern } = useIndexPatternContext();
const { loading, hasData, selectedApp } = useAppIndexPatternContext();
if (!loading && !hasData && selectedApp) {
return (
<FormattedMessage
id="xpack.observability.reportTypeCol.nodata"
defaultMessage="No data available"
/>
);
}
return reportTypes?.length > 0 ? (
<EuiFlexGroup direction="column" gutterSize="xs">
<FlexGroup direction="column" gutterSize="xs">
{reportTypes.map(({ id: reportType, label }) => (
<EuiFlexItem key={reportType}>
<EuiButton
fullWidth
size="s"
iconSide="right"
iconType="arrowRight"
color={selectedReportType === reportType ? 'primary' : 'text'}
fill={selectedReportType === reportType}
isDisabled={!indexPattern}
isDisabled={loading}
onClick={() => {
if (reportType === selectedReportType) {
setSeries(NEW_SERIES_KEY, {
@ -56,7 +68,7 @@ export function ReportTypesCol({ reportTypes }: Props) {
</EuiButton>
</EuiFlexItem>
))}
</EuiFlexGroup>
</FlexGroup>
) : (
<EuiText color="subdued">{SELECTED_DATA_TYPE_FOR_REPORT}</EuiText>
);
@ -66,3 +78,7 @@ export const SELECTED_DATA_TYPE_FOR_REPORT = i18n.translate(
'xpack.observability.expView.reportType.noDataType',
{ defaultMessage: 'Select a data type to start building a series.' }
);
const FlexGroup = styled(EuiFlexGroup)`
width: 100%;
`;

View file

@ -28,18 +28,18 @@ export function CustomReportField({ field, seriesId, options: opts, defaultValue
const { reportDefinitions } = series;
const NO_SELECT = 'no_select';
const options = [{ label: 'Select metric', field: NO_SELECT }, ...(opts ?? [])];
const options = opts ?? [];
return (
<div style={{ maxWidth: 200 }}>
<div style={{ maxWidth: 250 }}>
<EuiSuperSelect
compressed
prepend={'Metric'}
options={options.map(({ label, field: fd, description }) => ({
value: fd,
inputDisplay: label,
}))}
valueOfSelected={reportDefinitions?.[field] || defaultValue || NO_SELECT}
valueOfSelected={reportDefinitions?.[field] || defaultValue || options?.[0].field}
onChange={(value) => onChange(value)}
/>
</div>

View file

@ -5,11 +5,10 @@
* 2.0.
*/
import React, { useState } from 'react';
import React, { useEffect, useState } from 'react';
import { i18n } from '@kbn/i18n';
import { EuiButton, EuiBasicTable, EuiSpacer, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import styled from 'styled-components';
import { AppDataType, ReportViewTypeId, ReportViewTypes, SeriesUrl } from '../types';
import { DataTypesCol } from './columns/data_types_col';
import { ReportTypesCol } from './columns/report_types_col';
@ -17,7 +16,7 @@ import { ReportDefinitionCol } from './columns/report_definition_col';
import { ReportFilters } from './columns/report_filters';
import { ReportBreakdowns } from './columns/report_breakdowns';
import { NEW_SERIES_KEY, useUrlStorage } from '../hooks/use_url_storage';
import { useIndexPatternContext } from '../hooks/use_default_index_pattern';
import { useAppIndexPatternContext } from '../hooks/use_app_index_pattern';
import { getDefaultConfigs } from '../configurations/default_configs';
export const ReportTypes: Record<AppDataType, Array<{ id: ReportViewTypeId; label: string }>> = {
@ -25,7 +24,7 @@ export const ReportTypes: Record<AppDataType, Array<{ id: ReportViewTypeId; labe
{ id: 'upd', label: 'Monitor duration' },
{ id: 'upp', label: 'Pings histogram' },
],
rum: [
ux: [
{ id: 'pld', label: 'Performance distribution' },
{ id: 'kpi', label: 'KPI over time' },
],
@ -33,13 +32,13 @@ export const ReportTypes: Record<AppDataType, Array<{ id: ReportViewTypeId; labe
{ id: 'svl', label: 'Latency' },
{ id: 'tpt', label: 'Throughput' },
],
logs: [
infra_logs: [
{
id: 'logs',
label: 'Logs Frequency',
},
],
metrics: [
infra_metrics: [
{ id: 'cpu', label: 'CPU usage' },
{ id: 'mem', label: 'Memory usage' },
{ id: 'nwk', label: 'Network activity' },
@ -56,11 +55,13 @@ export function SeriesBuilder() {
reportDefinitions = {},
filters = [],
operationType,
breakdown,
time,
} = series;
const [isFlyoutVisible, setIsFlyoutVisible] = useState(!!series.dataType);
const { indexPattern } = useIndexPatternContext();
const { indexPattern, loading, hasData } = useAppIndexPatternContext();
const getDataViewSeries = () => {
return getDefaultConfigs({
@ -70,19 +71,23 @@ export function SeriesBuilder() {
});
};
useEffect(() => {
setIsFlyoutVisible(!!series.dataType);
}, [series.dataType]);
const columns = [
{
name: i18n.translate('xpack.observability.expView.seriesBuilder.dataType', {
defaultMessage: 'Data Type',
}),
width: '20%',
width: '15%',
render: (val: string) => <DataTypesCol />,
},
{
name: i18n.translate('xpack.observability.expView.seriesBuilder.report', {
defaultMessage: 'Report',
}),
width: '20%',
width: '15%',
render: (val: string) => (
<ReportTypesCol reportTypes={dataType ? ReportTypes[dataType] : []} />
),
@ -92,16 +97,25 @@ export function SeriesBuilder() {
defaultMessage: 'Definition',
}),
width: '30%',
render: (val: string) =>
reportType && indexPattern ? (
<ReportDefinitionCol dataViewSeries={getDataViewSeries()} />
) : null,
render: (val: string) => {
if (dataType && hasData) {
return loading ? (
INITIATING_VIEW
) : reportType ? (
<ReportDefinitionCol dataViewSeries={getDataViewSeries()} />
) : (
SELECT_REPORT_TYPE
);
}
return null;
},
},
{
name: i18n.translate('xpack.observability.expView.seriesBuilder.filters', {
defaultMessage: 'Filters',
}),
width: '25%',
width: '20%',
render: (val: string) =>
reportType && indexPattern ? <ReportFilters dataViewSeries={getDataViewSeries()} /> : null,
},
@ -109,7 +123,7 @@ export function SeriesBuilder() {
name: i18n.translate('xpack.observability.expView.seriesBuilder.breakdown', {
defaultMessage: 'Breakdowns',
}),
width: '25%',
width: '20%',
field: 'id',
render: (val: string) =>
reportType && indexPattern ? (
@ -126,14 +140,15 @@ export function SeriesBuilder() {
ReportViewTypes[reportType]
}`;
const newSeriesN = {
const newSeriesN: SeriesUrl = {
time,
filters,
breakdown,
reportType,
seriesType,
filters,
reportDefinitions,
operationType,
time: { from: 'now-30m', to: 'now' },
} as SeriesUrl;
reportDefinitions,
};
setSeries(newSeriesId, newSeriesN).then(() => {
removeSeries(NEW_SERIES_KEY);
@ -148,7 +163,7 @@ export function SeriesBuilder() {
if (isFlyoutVisible) {
flyout = (
<BottomFlyout aria-labelledby="flyoutTitle">
<>
<EuiBasicTable
items={items as any}
columns={columns}
@ -157,7 +172,14 @@ export function SeriesBuilder() {
<EuiSpacer size="xs" />
<EuiFlexGroup justifyContent="flexEnd">
<EuiFlexItem grow={false}>
<EuiButton fill iconType="plus" color="primary" onClick={addSeries}>
<EuiButton
fill
iconType="plus"
color="primary"
onClick={addSeries}
size="s"
isDisabled={!series?.reportType}
>
{i18n.translate('xpack.observability.expView.seriesBuilder.add', {
defaultMessage: 'Add',
})}
@ -165,6 +187,7 @@ export function SeriesBuilder() {
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton
size="s"
iconType="cross"
color="text"
onClick={() => {
@ -178,7 +201,7 @@ export function SeriesBuilder() {
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
</BottomFlyout>
</>
);
}
@ -205,6 +228,13 @@ export function SeriesBuilder() {
);
}
const BottomFlyout = styled.div`
height: 300px;
`;
const INITIATING_VIEW = i18n.translate('xpack.observability.expView.seriesBuilder.initView', {
defaultMessage: 'Initiating view ...',
});
const SELECT_REPORT_TYPE = i18n.translate(
'xpack.observability.expView.seriesBuilder.selectReportType',
{
defaultMessage: 'Select a report type to define visualization.',
}
);

View file

@ -15,7 +15,7 @@ interface Props {
series: DataSeries;
}
export function ActionsCol({ series }: Props) {
export function ChartOptions({ series }: Props) {
return (
<EuiFlexGroup direction="column" gutterSize="s" justifyContent="center">
<EuiFlexItem grow={false}>

View file

@ -13,7 +13,8 @@ import {
EuiLoadingSpinner,
EuiFilterGroup,
} from '@elastic/eui';
import { useIndexPatternContext } from '../../hooks/use_default_index_pattern';
import styled from 'styled-components';
import { useAppIndexPatternContext } from '../../hooks/use_app_index_pattern';
import { useUrlStorage } from '../../hooks/use_url_storage';
import { UrlFilter } from '../../types';
import { FilterValueButton } from './filter_value_btn';
@ -23,12 +24,13 @@ interface Props {
seriesId: string;
label: string;
field: string;
isNegated?: boolean;
goBack: () => void;
nestedField?: string;
}
export function FilterExpanded({ seriesId, field, label, goBack, nestedField }: Props) {
const { indexPattern } = useIndexPatternContext();
export function FilterExpanded({ seriesId, field, label, goBack, nestedField, isNegated }: Props) {
const { indexPattern } = useAppIndexPatternContext();
const [value, setValue] = useState('');
@ -37,9 +39,10 @@ export function FilterExpanded({ seriesId, field, label, goBack, nestedField }:
const { series } = useUrlStorage(seriesId);
const { values, loading } = useValuesList({
query: value,
indexPattern,
sourceField: field,
time: series.time,
indexPattern,
});
const filters = series?.filters ?? [];
@ -51,7 +54,7 @@ export function FilterExpanded({ seriesId, field, label, goBack, nestedField }:
);
return (
<>
<Wrapper>
<EuiButtonEmpty iconType="arrowLeft" color="text" onClick={() => goBack()}>
{label}
</EuiButtonEmpty>
@ -71,16 +74,18 @@ export function FilterExpanded({ seriesId, field, label, goBack, nestedField }:
{displayValues.map((opt) => (
<Fragment key={opt}>
<EuiFilterGroup fullWidth={true} color="primary">
<FilterValueButton
field={field}
value={opt}
allSelectedValues={currFilter?.notValues}
negate={true}
nestedField={nestedField}
seriesId={seriesId}
isNestedOpen={isOpen}
setIsNestedOpen={setIsOpen}
/>
{isNegated !== false && (
<FilterValueButton
field={field}
value={opt}
allSelectedValues={currFilter?.notValues}
negate={true}
nestedField={nestedField}
seriesId={seriesId}
isNestedOpen={isOpen}
setIsNestedOpen={setIsOpen}
/>
)}
<FilterValueButton
field={field}
value={opt}
@ -95,6 +100,10 @@ export function FilterExpanded({ seriesId, field, label, goBack, nestedField }:
<EuiSpacer size="s" />
</Fragment>
))}
</>
</Wrapper>
);
}
const Wrapper = styled.div`
max-width: 400px;
`;

View file

@ -7,7 +7,7 @@
import { i18n } from '@kbn/i18n';
import React, { useMemo } from 'react';
import { EuiFilterButton, hexToRgb } from '@elastic/eui';
import { useIndexPatternContext } from '../../hooks/use_default_index_pattern';
import { useAppIndexPatternContext } from '../../hooks/use_app_index_pattern';
import { useUrlStorage } from '../../hooks/use_url_storage';
import { useSeriesFilters } from '../../hooks/use_series_filters';
import { euiStyled } from '../../../../../../../../../src/plugins/kibana_react/common';
@ -39,7 +39,7 @@ export function FilterValueButton({
}: Props) {
const { series } = useUrlStorage(seriesId);
const { indexPattern } = useIndexPatternContext();
const { indexPattern } = useAppIndexPatternContext();
const { setFilter, removeFilter } = useSeriesFilters({ seriesId });

View file

@ -8,18 +8,17 @@
import { i18n } from '@kbn/i18n';
import React from 'react';
import { EuiButtonIcon } from '@elastic/eui';
import { DataSeries } from '../../types';
import { useUrlStorage } from '../../hooks/use_url_storage';
interface Props {
series: DataSeries;
seriesId: string;
}
export function RemoveSeries({ series }: Props) {
export function RemoveSeries({ seriesId }: Props) {
const { removeSeries } = useUrlStorage();
const onClick = () => {
removeSeries(series.id);
removeSeries(seriesId);
};
return (
<EuiButtonIcon

View file

@ -0,0 +1,43 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { RemoveSeries } from './remove_series';
import { NEW_SERIES_KEY, useUrlStorage } from '../../hooks/use_url_storage';
import { ReportToDataTypeMap } from '../../configurations/constants';
interface Props {
seriesId: string;
}
export function SeriesActions({ seriesId }: Props) {
const { series, removeSeries, setSeries } = useUrlStorage(seriesId);
const onEdit = () => {
removeSeries(seriesId);
setSeries(NEW_SERIES_KEY, { ...series, dataType: ReportToDataTypeMap[series.reportType] });
};
return (
<EuiFlexGroup alignItems="center" gutterSize="xs" justifyContent="center">
<EuiFlexItem grow={false}>
<EuiButtonIcon
iconType={'documentEdit'}
aria-label={i18n.translate('xpack.observability.seriesEditor.edit', {
defaultMessage: 'Edit series',
})}
size="m"
onClick={onEdit}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<RemoveSeries seriesId={seriesId} />
</EuiFlexItem>
</EuiFlexGroup>
);
}

View file

@ -32,6 +32,7 @@ export interface Field {
label: string;
field: string;
nested?: string;
isNegated?: boolean;
}
export function SeriesFilter({ series, isNew, seriesId, defaultFilters = [] }: Props) {
@ -39,11 +40,17 @@ export function SeriesFilter({ series, isNew, seriesId, defaultFilters = [] }: P
const [selectedField, setSelectedField] = useState<Field | undefined>();
const options = defaultFilters.map((field) => {
const options: Field[] = defaultFilters.map((field) => {
if (typeof field === 'string') {
return { label: FieldLabels[field], field };
}
return { label: FieldLabels[field.field], field: field.field, nested: field.nested };
return {
label: FieldLabels[field.field],
field: field.field,
nested: field.nested,
isNegated: field.isNegated,
};
});
const disabled = seriesId === NEW_SERIES_KEY && !isNew;
@ -92,6 +99,7 @@ export function SeriesFilter({ series, isNew, seriesId, defaultFilters = [] }: P
field={selectedField.field}
label={selectedField.label}
nestedField={selectedField.nested}
isNegated={selectedField.isNegated}
goBack={() => {
setSelectedField(undefined);
}}

View file

@ -7,13 +7,15 @@
import React from 'react';
import { screen, waitFor } from '@testing-library/react';
import { mockIndexPattern, mockUrlStorage, render } from '../rtl_helpers';
import { mockAppIndexPattern, mockIndexPattern, mockUrlStorage, render } from '../rtl_helpers';
import { SelectedFilters } from './selected_filters';
import { getDefaultConfigs } from '../configurations/default_configs';
import { NEW_SERIES_KEY } from '../hooks/use_url_storage';
import { USER_AGENT_NAME } from '../configurations/constants/elasticsearch_fieldnames';
describe('SelectedFilters', function () {
mockAppIndexPattern();
const dataViewSeries = getDefaultConfigs({
reportType: 'pld',
indexPattern: mockIndexPattern,

View file

@ -10,7 +10,7 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { NEW_SERIES_KEY, useUrlStorage } from '../hooks/use_url_storage';
import { FilterLabel } from '../components/filter_label';
import { DataSeries, UrlFilter } from '../types';
import { useIndexPatternContext } from '../hooks/use_default_index_pattern';
import { useAppIndexPatternContext } from '../hooks/use_app_index_pattern';
import { useSeriesFilters } from '../hooks/use_series_filters';
import { getFiltersFromDefs } from '../hooks/use_lens_attributes';
@ -37,7 +37,7 @@ export function SelectedFilters({ seriesId, isNew, series: dataSeries }: Props)
const { removeFilter } = useSeriesFilters({ seriesId });
const { indexPattern } = useIndexPatternContext();
const { indexPattern } = useAppIndexPatternContext();
return (filters.length > 0 || definitionFilters.length > 0) && indexPattern ? (
<EuiFlexItem>
@ -45,7 +45,7 @@ export function SelectedFilters({ seriesId, isNew, series: dataSeries }: Props)
{filters.map(({ field, values, notValues }) => (
<Fragment key={field}>
{(values ?? []).map((val) => (
<EuiFlexItem key={field + val} grow={false}>
<EuiFlexItem key={field + val} grow={false} style={{ maxWidth: 300 }}>
<FilterLabel
seriesId={seriesId}
field={field}
@ -57,7 +57,7 @@ export function SelectedFilters({ seriesId, isNew, series: dataSeries }: Props)
</EuiFlexItem>
))}
{(notValues ?? []).map((val) => (
<EuiFlexItem key={field + val} grow={false}>
<EuiFlexItem key={field + val} grow={false} style={{ maxWidth: 300 }}>
<FilterLabel
seriesId={seriesId}
field={field}

View file

@ -8,16 +8,17 @@
import React from 'react';
import { i18n } from '@kbn/i18n';
import { EuiBasicTable, EuiIcon, EuiSpacer, EuiText } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { SeriesFilter } from './columns/series_filter';
import { ActionsCol } from './columns/actions_col';
import { Breakdowns } from './columns/breakdowns';
import { DataSeries } from '../types';
import { SeriesBuilder } from '../series_builder/series_builder';
import { NEW_SERIES_KEY, useUrlStorage } from '../hooks/use_url_storage';
import { getDefaultConfigs } from '../configurations/default_configs';
import { DatePickerCol } from './columns/date_picker_col';
import { RemoveSeries } from './columns/remove_series';
import { useIndexPatternContext } from '../hooks/use_default_index_pattern';
import { useAppIndexPatternContext } from '../hooks/use_app_index_pattern';
import { ChartOptions } from './columns/chart_options';
import { SeriesActions } from './columns/series_actions';
export function SeriesEditor() {
const { allSeries, firstSeriesId } = useUrlStorage();
@ -32,7 +33,7 @@ export function SeriesEditor() {
render: (val: string) => (
<EuiText>
<EuiIcon type="dot" color="green" size="l" />{' '}
{val === NEW_SERIES_KEY ? 'new-series-preview' : val}
{val === NEW_SERIES_KEY ? 'series-preview' : val}
</EuiText>
),
},
@ -63,34 +64,30 @@ export function SeriesEditor() {
align: 'center' as const,
width: '15%',
field: 'id',
render: (val: string, item: DataSeries) => <ActionsCol series={item} />,
render: (val: string, item: DataSeries) => <ChartOptions series={item} />,
},
{
name: (
<div>
<FormattedMessage
id="xpack.observability.expView.seriesEditor.time"
defaultMessage="Time"
/>
</div>
),
width: '20%',
field: 'id',
align: 'right' as const,
render: (val: string, item: DataSeries) => <DatePickerCol seriesId={item.id} />,
},
]
: []),
{
name: (
<div>
{i18n.translate('xpack.observability.expView.seriesEditor.time', {
defaultMessage: 'Time',
})}
</div>
),
width: '20%',
field: 'id',
align: 'right' as const,
render: (val: string, item: DataSeries) => <DatePickerCol seriesId={item.id} />,
},
...(firstSeriesId !== NEW_SERIES_KEY
? [
{
name: i18n.translate('xpack.observability.expView.seriesEditor.actions', {
defaultMessage: 'Actions',
}),
align: 'center' as const,
width: '5%',
width: '8%',
field: 'id',
render: (val: string, item: DataSeries) => <RemoveSeries series={item} />,
render: (val: string, item: DataSeries) => <SeriesActions seriesId={item.id} />,
},
]
: []),
@ -100,7 +97,7 @@ export function SeriesEditor() {
const items: DataSeries[] = [];
const { indexPattern } = useIndexPatternContext();
const { indexPattern } = useAppIndexPatternContext();
allSeriesKeys.forEach((seriesKey) => {
const series = allSeries[seriesKey];

View file

@ -57,7 +57,7 @@ export interface DataSeries {
breakdowns: string[];
defaultSeriesType: SeriesType;
defaultFilters: Array<string | { field: string; nested: string }>;
defaultFilters: Array<string | { field: string; nested?: string; isNegated?: boolean }>;
seriesTypes: SeriesType[];
filters?: PersistableFilter[];
reportDefinitions: ReportDefinition[];
@ -91,7 +91,7 @@ export interface ConfigProps {
indexPattern: IIndexPattern;
}
export type AppDataType = 'synthetics' | 'rum' | 'logs' | 'metrics' | 'apm';
export type AppDataType = 'synthetics' | 'ux' | 'infra_logs' | 'infra_metrics' | 'apm';
type FormatType = 'duration' | 'number';
type InputFormat = 'microseconds' | 'milliseconds' | 'seconds';

View file

@ -40,17 +40,17 @@ const fieldFormats = {
describe('ObservabilityIndexPatterns', function () {
const { data } = mockCore();
data!.indexPatterns.get = jest.fn().mockReturnValue({ title: 'index-*' });
data!.indexPatterns.createAndSave = jest.fn().mockReturnValue({ id: indexPatternList.rum });
data!.indexPatterns.createAndSave = jest.fn().mockReturnValue({ id: indexPatternList.ux });
data!.indexPatterns.updateSavedObject = jest.fn();
it('should return index pattern for app', async function () {
const obsv = new ObservabilityIndexPatterns(data!);
const indexP = await obsv.getIndexPattern('rum');
const indexP = await obsv.getIndexPattern('ux');
expect(indexP).toEqual({ title: 'index-*' });
expect(data?.indexPatterns.get).toHaveBeenCalledWith(indexPatternList.rum);
expect(data?.indexPatterns.get).toHaveBeenCalledWith(indexPatternList.ux);
expect(data?.indexPatterns.get).toHaveBeenCalledTimes(1);
});
@ -61,9 +61,9 @@ describe('ObservabilityIndexPatterns', function () {
const obsv = new ObservabilityIndexPatterns(data!);
const indexP = await obsv.getIndexPattern('rum');
const indexP = await obsv.getIndexPattern('ux');
expect(indexP).toEqual({ id: indexPatternList.rum });
expect(indexP).toEqual({ id: indexPatternList.ux });
expect(data?.indexPatterns.createAndSave).toHaveBeenCalledWith({
fieldFormats,
@ -77,7 +77,7 @@ describe('ObservabilityIndexPatterns', function () {
it('should return getFieldFormats', function () {
const obsv = new ObservabilityIndexPatterns(data!);
expect(obsv.getFieldFormats('rum')).toEqual(fieldFormats);
expect(obsv.getFieldFormats('ux')).toEqual(fieldFormats);
});
it('should validate field formats', async function () {
@ -85,7 +85,7 @@ describe('ObservabilityIndexPatterns', function () {
const obsv = new ObservabilityIndexPatterns(data!);
await obsv.validateFieldFormats('rum', mockIndexPattern);
await obsv.validateFieldFormats('ux', mockIndexPattern);
expect(data?.indexPatterns.updateSavedObject).toHaveBeenCalledTimes(1);
expect(data?.indexPatterns.updateSavedObject).toHaveBeenCalledWith(

View file

@ -14,36 +14,35 @@ import {
} from '../../../../../../../../src/plugins/data/public';
import { rumFieldFormats } from '../configurations/rum/field_formats';
import { syntheticsFieldFormats } from '../configurations/synthetics/field_formats';
import { FieldFormat, FieldFormatParams } from '../types';
import { AppDataType, FieldFormat, FieldFormatParams } from '../types';
import { apmFieldFormats } from '../configurations/apm/field_formats';
const appFieldFormats: Record<DataType, FieldFormat[] | null> = {
rum: rumFieldFormats,
apm: null,
logs: null,
metrics: null,
const appFieldFormats: Record<AppDataType, FieldFormat[] | null> = {
infra_logs: null,
infra_metrics: null,
ux: rumFieldFormats,
apm: apmFieldFormats,
synthetics: syntheticsFieldFormats,
};
function getFieldFormatsForApp(app: DataType) {
function getFieldFormatsForApp(app: AppDataType) {
return appFieldFormats[app];
}
export type DataType = 'synthetics' | 'apm' | 'logs' | 'metrics' | 'rum';
export const indexPatternList: Record<DataType, string> = {
export const indexPatternList: Record<AppDataType, string> = {
synthetics: 'synthetics_static_index_pattern_id',
apm: 'apm_static_index_pattern_id',
rum: 'rum_static_index_pattern_id',
logs: 'logs_static_index_pattern_id',
metrics: 'metrics_static_index_pattern_id',
ux: 'rum_static_index_pattern_id',
infra_logs: 'infra_logs_static_index_pattern_id',
infra_metrics: 'infra_metrics_static_index_pattern_id',
};
const appToPatternMap: Record<DataType, string> = {
const appToPatternMap: Record<AppDataType, string> = {
synthetics: '(synthetics-data-view)*,heartbeat-*,synthetics-*',
apm: 'apm-*',
rum: '(rum-data-view)*,apm-*',
logs: 'logs-*,filebeat-*',
metrics: 'metrics-*,metricbeat-*',
ux: '(rum-data-view)*,apm-*',
infra_logs: 'logs-*,filebeat-*',
infra_metrics: 'metrics-*,metricbeat-*',
};
export function isParamsSame(param1: IFieldFormat['_params'], param2: FieldFormatParams) {
@ -66,7 +65,7 @@ export class ObservabilityIndexPatterns {
this.data = data;
}
async createIndexPattern(app: DataType) {
async createIndexPattern(app: AppDataType) {
if (!this.data) {
throw new Error('data is not defined');
}
@ -81,7 +80,7 @@ export class ObservabilityIndexPatterns {
});
}
// we want to make sure field formats remain same
async validateFieldFormats(app: DataType, indexPattern: IndexPattern) {
async validateFieldFormats(app: AppDataType, indexPattern: IndexPattern) {
const defaultFieldFormats = getFieldFormatsForApp(app);
if (defaultFieldFormats && defaultFieldFormats.length > 0) {
let isParamsDifferent = false;
@ -99,7 +98,7 @@ export class ObservabilityIndexPatterns {
}
}
getFieldFormats(app: DataType) {
getFieldFormats(app: AppDataType) {
const fieldFormatMap: IndexPatternSpec['fieldFormats'] = {};
(appFieldFormats?.[app] ?? []).forEach(({ field, format }) => {
@ -109,7 +108,7 @@ export class ObservabilityIndexPatterns {
return fieldFormatMap;
}
async getIndexPattern(app: DataType): Promise<IndexPattern | undefined> {
async getIndexPattern(app: AppDataType): Promise<IndexPattern | undefined> {
if (!this.data) {
throw new Error('data is not defined');
}

View file

@ -12,7 +12,8 @@ import { CoreStart } from 'src/core/public';
import { text } from '@storybook/addon-knobs';
import { EuiThemeProvider } from '../../../../../../../../src/plugins/kibana_react/common';
import { createKibanaReactContext } from '../../../../../../../../src/plugins/kibana_react/public';
import { FieldValueSelection, FieldValueSelectionProps } from '../field_value_selection';
import { FieldValueSelectionProps } from '../types';
import { FieldValueSelection } from '../field_value_selection';
const KibanaReactContext = createKibanaReactContext(({
uiSettings: { get: () => {}, get$: () => new Observable() },

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import React, { FormEvent, Fragment, useEffect, useState, Dispatch, SetStateAction } from 'react';
import React, { FormEvent, useEffect, useState } from 'react';
import {
EuiButton,
EuiPopover,
@ -15,20 +15,8 @@ import {
EuiSelectableOption,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { PopoverAnchorPosition } from '@elastic/eui/src/components/popover/popover';
export interface FieldValueSelectionProps {
value?: string;
label: string;
loading?: boolean;
onChange: (val?: string) => void;
values?: string[];
setQuery: Dispatch<SetStateAction<string>>;
anchorPosition?: PopoverAnchorPosition;
forceOpen?: boolean;
button?: JSX.Element;
width?: number;
}
import styled from 'styled-components';
import { FieldValueSelectionProps } from './types';
const formatOptions = (values?: string[], value?: string): EuiSelectableOption[] => {
return (values ?? []).map((val) => ({
@ -38,6 +26,7 @@ const formatOptions = (values?: string[], value?: string): EuiSelectableOption[]
};
export function FieldValueSelection({
fullWidth,
label,
value,
loading,
@ -47,6 +36,7 @@ export function FieldValueSelection({
width,
forceOpen,
anchorPosition,
singleSelection,
onChange: onSelectionChange,
}: FieldValueSelectionProps) {
const [options, setOptions] = useState<EuiSelectableOption[]>(formatOptions(values, value));
@ -81,13 +71,14 @@ export function FieldValueSelection({
iconSide="right"
onClick={onButtonClick}
data-test-subj={'fieldValueSelectionBtn'}
fullWidth={fullWidth}
>
{label}
</EuiButton>
);
return (
<Fragment>
<Wrapper>
<EuiPopover
id="popover"
panelPaddingSize="none"
@ -95,10 +86,11 @@ export function FieldValueSelection({
isOpen={isPopoverOpen || forceOpen}
closePopover={closePopover}
anchorPosition={anchorPosition}
style={{ width: '100%' }}
>
<EuiSelectable
searchable
singleSelection
singleSelection={singleSelection}
searchProps={{
placeholder: i18n.translate('xpack.observability.fieldValueSelection.placeholder', {
defaultMessage: 'Filter {label}',
@ -138,6 +130,18 @@ export function FieldValueSelection({
)}
</EuiSelectable>
</EuiPopover>
</Fragment>
</Wrapper>
);
}
const Wrapper = styled.div`
&&& {
div.euiPopover__anchor {
width: 100%;
max-width: 250px;
.euiButton {
width: 100%;
}
}
}
`;

View file

@ -8,27 +8,12 @@
import React, { useState } from 'react';
import { useDebounce } from 'react-use';
import { PopoverAnchorPosition } from '@elastic/eui/src/components/popover/popover';
import { useValuesList } from '../../../hooks/use_values_list';
import { IndexPattern } from '../../../../../../../src/plugins/data/common';
import { FieldValueSelection } from './field_value_selection';
import { ESFilter } from '../../../../../../../typings/elasticsearch';
export interface FieldValueSuggestionsProps {
value?: string;
label: string;
indexPattern: IndexPattern;
sourceField: string;
onChange: (val?: string) => void;
filters: ESFilter[];
anchorPosition?: PopoverAnchorPosition;
time?: { from: string; to: string };
forceOpen?: boolean;
button?: JSX.Element;
width?: number;
}
import { FieldValueSuggestionsProps } from './types';
export function FieldValueSuggestions({
fullWidth,
sourceField,
label,
indexPattern,
@ -39,6 +24,7 @@ export function FieldValueSuggestions({
width,
forceOpen,
anchorPosition,
singleSelection,
onChange: onSelectionChange,
}: FieldValueSuggestionsProps) {
const [query, setQuery] = useState('');
@ -56,6 +42,8 @@ export function FieldValueSuggestions({
return (
<FieldValueSelection
fullWidth={fullWidth}
singleSelection={singleSelection}
values={values as string[]}
label={label}
onChange={onSelectionChange}

View file

@ -0,0 +1,37 @@
/*
* 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 { PopoverAnchorPosition } from '@elastic/eui';
import { Dispatch, SetStateAction } from 'react';
import { ESFilter } from 'typings/elasticsearch';
import { IndexPattern } from '../../../../../../../src/plugins/data/common/index_patterns/index_patterns';
interface CommonProps {
value?: string;
label: string;
button?: JSX.Element;
width?: number;
singleSelection?: boolean;
forceOpen?: boolean;
anchorPosition?: PopoverAnchorPosition;
fullWidth?: boolean;
}
export type FieldValueSuggestionsProps = CommonProps & {
indexPattern: IndexPattern;
sourceField: string;
onChange: (val?: string) => void;
filters: ESFilter[];
time?: { from: string; to: string };
};
export type FieldValueSelectionProps = CommonProps & {
loading?: boolean;
onChange: (val?: string) => void;
values?: string[];
setQuery: Dispatch<SetStateAction<string>>;
};

View file

@ -7,7 +7,7 @@
import React, { lazy, Suspense } from 'react';
import type { CoreVitalProps, HeaderMenuPortalProps } from './types';
import type { FieldValueSuggestionsProps } from './field_value_suggestions';
import type { FieldValueSuggestionsProps } from './field_value_suggestions/types';
const CoreVitalsLazy = lazy(() => import('./core_web_vitals/index'));

View file

@ -125,6 +125,7 @@ export interface ObservabilityFetchDataResponse {
apm: ApmFetchDataResponse;
infra_metrics: MetricsFetchDataResponse;
infra_logs: LogsFetchDataResponse;
synthetics: UptimeFetchDataResponse;
uptime: UptimeFetchDataResponse;
ux: UxFetchDataResponse;
}
@ -134,5 +135,6 @@ export interface ObservabilityHasDataResponse {
infra_metrics: boolean;
infra_logs: boolean;
uptime: boolean;
synthetics: boolean;
ux: UXHasDataResponse;
}

View file

@ -9,7 +9,9 @@ export type ObservabilityApp =
| 'infra_metrics'
| 'infra_logs'
| 'apm'
// we will remove uptime in future to replace to be replace by synthetics
| 'uptime'
| 'synthetics'
| 'observability-overview'
| 'stack_monitoring'
| 'ux';