[8.8] [Infrastructure UI] Hosts view handle invalid KQL error (#156989) (#157418)

# Backport

This will backport the following commits from `main` to `8.8`:
- [[Infrastructure UI] Hosts view handle invalid KQL error
(#156989)](https://github.com/elastic/kibana/pull/156989)

<!--- Backport version: 8.9.7 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Carlos
Crespo","email":"crespocarlos@users.noreply.github.com"},"sourceCommit":{"committedDate":"2023-05-11T16:59:23Z","message":"[Infrastructure
UI] Hosts view handle invalid KQL error (#156989)\n\ncloses
[#987](https://github.com/elastic/obs-infraobs-team/issues/987)\r\n\r\n##
Summary\r\n\r\nThis PR changes the hosts view to graciously handle
exceptions caused by\r\ninvalid KQL submissions\r\n\r\n<img
width=\"1451\"
alt=\"image\"\r\nsrc=\"5bafc987-9a14-4b03-9038-53179f7b6735\">\r\n\r\n\r\nBesides,
it changes the way it was handling a fatal error that can\r\nhappen if
something wrong happens while creating an ad-hoc data-view -\r\nthis is
highly unlike to happen, but we're standardizing how we
display\r\nerrors on the hosts view\r\n\r\n_Previously:_\r\n<img
width=\"1439\"
alt=\"image\"\r\nsrc=\"https://user-images.githubusercontent.com/2767137/236833673-c994512f-cb73-441b-9783-506bab67ff4b.png\">\r\n\r\n_Now:_\r\n<img
width=\"1439\"
alt=\"image\"\r\nsrc=\"https://user-images.githubusercontent.com/2767137/236862216-fada9f50-5d27-45b9-a6f3-8ac497a3e048.png\">\r\n\r\n###
How to test\r\n- Go to hosts view\r\n- Type invalid KQL expressions on
the search bar\r\n\r\n### For reviewer\r\n\r\nIf the page is loaded with
a querystring containing an invalid
`query`\r\n(e.g:\r\n`_a=(dateRange:(from:now-15m,to:now),filters:!(),limit:20,panelFilters:!(),query:(language:kuery,query:%27%7D%27)`),\r\nthe
Control components will show an error. However, they can't
recover\r\nfrom fatal errors. So even after the user corrects the
mistake in the\r\nquery, the controls will remain in the error
state.\r\n\r\nA ticket has been opened to address this
problem:\r\nhttps://github.com/elastic/kibana/issues/156430\r\n\r\n---------\r\n\r\nCo-authored-by:
Kibana Machine
<42973632+kibanamachine@users.noreply.github.com>","sha":"8baff25966d45c1b351e96a5cc524372b4dbdb29","branchLabelMapping":{"^v8.9.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["Feature:Metrics
UI","Team:Infra Monitoring
UI","release_note:skip","backport:prev-minor","Feature:ObsHosts","v8.9.0"],"number":156989,"url":"https://github.com/elastic/kibana/pull/156989","mergeCommit":{"message":"[Infrastructure
UI] Hosts view handle invalid KQL error (#156989)\n\ncloses
[#987](https://github.com/elastic/obs-infraobs-team/issues/987)\r\n\r\n##
Summary\r\n\r\nThis PR changes the hosts view to graciously handle
exceptions caused by\r\ninvalid KQL submissions\r\n\r\n<img
width=\"1451\"
alt=\"image\"\r\nsrc=\"5bafc987-9a14-4b03-9038-53179f7b6735\">\r\n\r\n\r\nBesides,
it changes the way it was handling a fatal error that can\r\nhappen if
something wrong happens while creating an ad-hoc data-view -\r\nthis is
highly unlike to happen, but we're standardizing how we
display\r\nerrors on the hosts view\r\n\r\n_Previously:_\r\n<img
width=\"1439\"
alt=\"image\"\r\nsrc=\"https://user-images.githubusercontent.com/2767137/236833673-c994512f-cb73-441b-9783-506bab67ff4b.png\">\r\n\r\n_Now:_\r\n<img
width=\"1439\"
alt=\"image\"\r\nsrc=\"https://user-images.githubusercontent.com/2767137/236862216-fada9f50-5d27-45b9-a6f3-8ac497a3e048.png\">\r\n\r\n###
How to test\r\n- Go to hosts view\r\n- Type invalid KQL expressions on
the search bar\r\n\r\n### For reviewer\r\n\r\nIf the page is loaded with
a querystring containing an invalid
`query`\r\n(e.g:\r\n`_a=(dateRange:(from:now-15m,to:now),filters:!(),limit:20,panelFilters:!(),query:(language:kuery,query:%27%7D%27)`),\r\nthe
Control components will show an error. However, they can't
recover\r\nfrom fatal errors. So even after the user corrects the
mistake in the\r\nquery, the controls will remain in the error
state.\r\n\r\nA ticket has been opened to address this
problem:\r\nhttps://github.com/elastic/kibana/issues/156430\r\n\r\n---------\r\n\r\nCo-authored-by:
Kibana Machine
<42973632+kibanamachine@users.noreply.github.com>","sha":"8baff25966d45c1b351e96a5cc524372b4dbdb29"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v8.9.0","labelRegex":"^v8.9.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/156989","number":156989,"mergeCommit":{"message":"[Infrastructure
UI] Hosts view handle invalid KQL error (#156989)\n\ncloses
[#987](https://github.com/elastic/obs-infraobs-team/issues/987)\r\n\r\n##
Summary\r\n\r\nThis PR changes the hosts view to graciously handle
exceptions caused by\r\ninvalid KQL submissions\r\n\r\n<img
width=\"1451\"
alt=\"image\"\r\nsrc=\"5bafc987-9a14-4b03-9038-53179f7b6735\">\r\n\r\n\r\nBesides,
it changes the way it was handling a fatal error that can\r\nhappen if
something wrong happens while creating an ad-hoc data-view -\r\nthis is
highly unlike to happen, but we're standardizing how we
display\r\nerrors on the hosts view\r\n\r\n_Previously:_\r\n<img
width=\"1439\"
alt=\"image\"\r\nsrc=\"https://user-images.githubusercontent.com/2767137/236833673-c994512f-cb73-441b-9783-506bab67ff4b.png\">\r\n\r\n_Now:_\r\n<img
width=\"1439\"
alt=\"image\"\r\nsrc=\"https://user-images.githubusercontent.com/2767137/236862216-fada9f50-5d27-45b9-a6f3-8ac497a3e048.png\">\r\n\r\n###
How to test\r\n- Go to hosts view\r\n- Type invalid KQL expressions on
the search bar\r\n\r\n### For reviewer\r\n\r\nIf the page is loaded with
a querystring containing an invalid
`query`\r\n(e.g:\r\n`_a=(dateRange:(from:now-15m,to:now),filters:!(),limit:20,panelFilters:!(),query:(language:kuery,query:%27%7D%27)`),\r\nthe
Control components will show an error. However, they can't
recover\r\nfrom fatal errors. So even after the user corrects the
mistake in the\r\nquery, the controls will remain in the error
state.\r\n\r\nA ticket has been opened to address this
problem:\r\nhttps://github.com/elastic/kibana/issues/156430\r\n\r\n---------\r\n\r\nCo-authored-by:
Kibana Machine
<42973632+kibanamachine@users.noreply.github.com>","sha":"8baff25966d45c1b351e96a5cc524372b4dbdb29"}}]}]
BACKPORT-->

Co-authored-by: Carlos Crespo <crespocarlos@users.noreply.github.com>
This commit is contained in:
Kibana Machine 2023-05-11 14:08:49 -04:00 committed by GitHub
parent 9ac1170e09
commit 6593d2318f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 308 additions and 120 deletions

View file

@ -0,0 +1,124 @@
/*
* 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 {
EuiButton,
EuiEmptyPrompt,
EuiFlexGroup,
EuiFlexItem,
EuiSpacer,
EuiCodeBlock,
} from '@elastic/eui';
import { KQLSyntaxError } from '@kbn/es-query';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { useKibanaContextForPlugin } from '../../../../hooks/use_kibana';
interface Props {
error: Error;
titleOverride?: string;
messageOverride?: string;
hasDetailsModal?: boolean;
hasTryAgainButton?: boolean;
onTryAgainClick?: () => void;
}
export const ErrorCallout = ({
error,
titleOverride,
messageOverride,
hasDetailsModal = false,
hasTryAgainButton = false,
onTryAgainClick,
}: Props) => {
const {
services: { notifications },
} = useKibanaContextForPlugin();
const errorContent = getErrorContent(error);
const title = titleOverride ? titleOverride : errorContent.title;
const openDetails = () => {
notifications.showErrorDialog({ title, error });
};
return (
<EuiEmptyPrompt
iconType="error"
color="danger"
title={<h2>{title}</h2>}
data-test-subj="hostsViewErrorCallout"
body={
<>
{messageOverride ? <p>{messageOverride}</p> : errorContent.body}
<EuiFlexGroup justifyContent="center">
{hasDetailsModal && (
<EuiFlexItem grow={false}>
<EuiButton
data-test-subj="hostsViewErrorDetailsButton"
onClick={openDetails}
color="danger"
>
<FormattedMessage
id="xpack.infra.hostsViewPage.error.detailsButton"
defaultMessage="Error details"
/>
</EuiButton>
</EuiFlexItem>
)}
{hasTryAgainButton && (
<EuiFlexItem grow={false}>
<EuiButton
data-test-subj="hostsViewTryAgainButton"
onClick={onTryAgainClick}
iconType="refresh"
color="danger"
>
<FormattedMessage
id="xpack.infra.hostsViewPage.error.tryAgainButton"
defaultMessage="Try again"
/>
</EuiButton>
</EuiFlexItem>
)}
</EuiFlexGroup>
</>
}
/>
);
};
const getErrorContent = (error: Error): { title: string; body: JSX.Element } => {
if (error instanceof KQLSyntaxError) {
return {
title: i18n.translate('xpack.infra.hostsViewPage.error.kqlErrorTitle', {
defaultMessage: 'Invalid KQL expression',
}),
body: (
<>
<FormattedMessage
id="xpack.infra.hostsViewPage.error.kqlErrorMessage"
defaultMessage="We can't show any results because we couldn't apply your filter."
/>
<EuiSpacer size="s" />
<EuiCodeBlock transparentBackground paddingSize="s">
{error.message}
</EuiCodeBlock>
</>
),
};
}
return {
title: i18n.translate('xpack.infra.hostsViewPage.error.unknownErrorTitle', {
defaultMessage: 'An error occurred',
}),
body: <>{error.message}</>,
};
};

View file

@ -5,24 +5,20 @@
* 2.0.
*/
import React from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
import { EuiSpacer } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { InfraLoadingPanel } from '../../../../components/loading';
import { useMetricsDataViewContext } from '../hooks/use_data_view';
import { UnifiedSearchBar } from './search_bar/unified_search_bar';
import { HostsTable } from './hosts_table';
import { KPIGrid } from './kpis/kpi_grid';
import { Tabs } from './tabs/tabs';
import { AlertsQueryProvider } from '../hooks/use_alerts_query';
import { HostsViewProvider } from '../hooks/use_hosts_view';
import { HostsTableProvider } from '../hooks/use_hosts_table';
import { HostsContent } from './hosts_content';
import { ErrorCallout } from './error_callout';
export const HostContainer = () => {
const { dataView, loading, hasError } = useMetricsDataViewContext();
const { dataView, loading, error, metricAlias, loadDataView } = useMetricsDataViewContext();
const isLoading = loading || !dataView;
if (isLoading && !hasError) {
if (isLoading && !error) {
return (
<InfraLoadingPanel
height="100%"
@ -34,27 +30,25 @@ export const HostContainer = () => {
);
}
return hasError ? null : (
return error ? (
<ErrorCallout
error={error}
titleOverride={i18n.translate('xpack.infra.hostsViewPage.errorOnCreateOrLoadDataviewTitle', {
defaultMessage: 'Error creating Data View',
})}
messageOverride={i18n.translate('xpack.infra.hostsViewPage.errorOnCreateOrLoadDataview', {
defaultMessage:
'There was an error trying to create a Data View: {metricAlias}. Try reloading the page.',
values: { metricAlias },
})}
onTryAgainClick={loadDataView}
hasTryAgainButton
/>
) : (
<>
<UnifiedSearchBar />
<EuiSpacer />
<HostsViewProvider>
<HostsTableProvider>
<EuiFlexGroup direction="column">
<EuiFlexItem grow={false}>
<KPIGrid />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<HostsTable />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<AlertsQueryProvider>
<Tabs />
</AlertsQueryProvider>
</EuiFlexItem>
</EuiFlexGroup>
</HostsTableProvider>
</HostsViewProvider>
<HostsContent />
</>
);
};

View file

@ -0,0 +1,47 @@
/*
* 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 } from '@elastic/eui';
import { HostsTable } from './hosts_table';
import { KPIGrid } from './kpis/kpi_grid';
import { Tabs } from './tabs/tabs';
import { AlertsQueryProvider } from '../hooks/use_alerts_query';
import { HostsViewProvider } from '../hooks/use_hosts_view';
import { HostsTableProvider } from '../hooks/use_hosts_table';
import { ErrorCallout } from './error_callout';
import { useUnifiedSearchContext } from '../hooks/use_unified_search';
export const HostsContent = () => {
const { error } = useUnifiedSearchContext();
return (
<>
{error ? (
<ErrorCallout error={error} hasDetailsModal />
) : (
<HostsViewProvider>
<HostsTableProvider>
<EuiFlexGroup direction="column">
<EuiFlexItem grow={false}>
<KPIGrid />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<HostsTable />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<AlertsQueryProvider>
<Tabs />
</AlertsQueryProvider>
</EuiFlexItem>
</EuiFlexGroup>
</HostsTableProvider>
</HostsViewProvider>
)}
</>
);
};

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import React from 'react';
import React, { useState } from 'react';
import {
EuiButtonGroup,
EuiButtonGroupOptionProps,
@ -26,6 +26,11 @@ interface Props {
}
export const LimitOptions = ({ limit, onChange }: Props) => {
const [idSelected, setIdSelected] = useState(limit as number);
const onSelected = (value: number) => {
setIdSelected(value);
onChange(value);
};
return (
<EuiFlexGroup
direction="row"
@ -63,9 +68,9 @@ export const LimitOptions = ({ limit, onChange }: Props) => {
legend={i18n.translate('xpack.infra.hostsViewPage.tabs.alerts.alertStatusFilter.legend', {
defaultMessage: 'Filter by',
})}
idSelected={buildId(limit)}
idSelected={buildId(idSelected)}
options={options}
onChange={(_, value: number) => onChange(value)}
onChange={(_, value: number) => onSelected(value)}
/>
</EuiFlexItem>
</EuiFlexGroup>

View file

@ -6,7 +6,13 @@
*/
import React, { useMemo } from 'react';
import { compareFilters, COMPARE_ALL_OPTIONS, type Filter } from '@kbn/es-query';
import {
compareFilters,
COMPARE_ALL_OPTIONS,
type Query,
type TimeRange,
type Filter,
} from '@kbn/es-query';
import { i18n } from '@kbn/i18n';
import {
EuiFlexGrid,
@ -21,7 +27,6 @@ import { useKibanaContextForPlugin } from '../../../../../hooks/use_kibana';
import { useUnifiedSearchContext } from '../../hooks/use_unified_search';
import { ControlsContent } from './controls_content';
import { useMetricsDataViewContext } from '../../hooks/use_data_view';
import { HostsSearchPayload } from '../../hooks/use_unified_search_url_state';
import { LimitOptions } from './limit_options';
import { HostLimitOptions } from '../../types';
@ -44,7 +49,7 @@ export const UnifiedSearchBar = () => {
}
};
const handleRefresh = (payload: HostsSearchPayload, isUpdate?: boolean) => {
const handleRefresh = (payload: { query?: Query; dateRange: TimeRange }, isUpdate?: boolean) => {
// This makes sure `onQueryChange` is only called when the submit button is clicked
if (isUpdate === false) {
onSubmit(payload);

View file

@ -8,24 +8,20 @@
import { useDataView } from './use_data_view';
import { renderHook } from '@testing-library/react-hooks';
import { type KibanaReactContextValue, useKibana } from '@kbn/kibana-react-plugin/public';
import { coreMock, notificationServiceMock } from '@kbn/core/public/mocks';
import { coreMock } from '@kbn/core/public/mocks';
import type { DataView, DataViewsServicePublic } from '@kbn/data-views-plugin/public';
import type { InfraClientStartDeps } from '../../../../types';
import { CoreStart } from '@kbn/core/public';
jest.mock('@kbn/i18n');
jest.mock('@kbn/kibana-react-plugin/public');
let dataViewMock: jest.Mocked<DataViewsServicePublic>;
const useKibanaMock = useKibana as jest.MockedFunction<typeof useKibana>;
const notificationMock = notificationServiceMock.createStartContract();
const prop = { metricAlias: 'test' };
const mockUseKibana = () => {
useKibanaMock.mockReturnValue({
services: {
...coreMock.createStart(),
notifications: notificationMock,
dataViews: dataViewMock,
} as Partial<CoreStart> & Partial<InfraClientStartDeps>,
} as unknown as KibanaReactContextValue<Partial<CoreStart> & Partial<InfraClientStartDeps>>);
@ -43,34 +39,21 @@ const mockDataView = {
describe('useDataView hook', () => {
beforeEach(() => {
dataViewMock = {
create: jest.fn(),
find: jest.fn(),
create: jest.fn().mockImplementation(() => Promise.resolve(mockDataView)),
} as Partial<DataViewsServicePublic> as jest.Mocked<DataViewsServicePublic>;
mockUseKibana();
});
it('should create a new ad-hoc data view', async () => {
dataViewMock.create.mockReturnValue(Promise.resolve(mockDataView));
const { result, waitForNextUpdate } = renderHook(() => useDataView(prop));
const { result, waitForNextUpdate } = renderHook(() => useDataView({ metricAlias: 'test' }));
await waitForNextUpdate();
expect(result.current.loading).toEqual(false);
expect(result.current.hasError).toEqual(false);
expect(result.current.error).toBeUndefined();
expect(result.current.dataView).toEqual(mockDataView);
});
it('should display a toast when it fails to load the data view', async () => {
dataViewMock.create.mockReturnValue(Promise.reject());
const { result, waitForNextUpdate } = renderHook(() => useDataView(prop));
await waitForNextUpdate();
expect(result.current.loading).toEqual(false);
expect(result.current.hasError).toEqual(true);
expect(result.current.dataView).toBeUndefined();
expect(notificationMock.toasts.addDanger).toBeCalledTimes(1);
});
it('should create a dataview with unique id for metricAlias metrics', async () => {
const { waitForNextUpdate } = renderHook(() => useDataView({ metricAlias: 'metrics' }));

View file

@ -5,14 +5,10 @@
* 2.0.
*/
import { i18n } from '@kbn/i18n';
import { v5 as uuidv5 } from 'uuid';
import { useEffect, useMemo, useState } from 'react';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import createContainer from 'constate';
import type { DataView, DataViewSpec } from '@kbn/data-views-plugin/public';
import { useTrackedPromise } from '../../../../utils/use_tracked_promise';
import type { InfraClientStartDeps } from '../../../../types';
import useAsyncRetry from 'react-use/lib/useAsyncRetry';
import { useKibanaContextForPlugin } from '../../../../hooks/use_kibana';
import { DATA_VIEW_PREFIX, TIMESTAMP_FIELD } from '../constants';
export const generateDataViewId = (indexPattern: string) => {
@ -22,60 +18,25 @@ export const generateDataViewId = (indexPattern: string) => {
export const useDataView = ({ metricAlias }: { metricAlias: string }) => {
const {
services: { dataViews, notifications },
} = useKibana<InfraClientStartDeps>();
services: { dataViews },
} = useKibanaContextForPlugin();
const [dataView, setDataView] = useState<DataView>();
const [hasError, setHasError] = useState<boolean>(false);
const [createAdhocDataViewRequest, createAdhocDataView] = useTrackedPromise(
{
createPromise: (config: DataViewSpec): Promise<DataView> => {
return dataViews.create(config);
},
onResolve: (response: DataView) => {
setDataView(response);
setHasError(false);
},
onReject: () => {
setHasError(true);
},
cancelPreviousOn: 'creation',
},
[]
);
const loading = useMemo(
() =>
createAdhocDataViewRequest.state === 'pending' ||
createAdhocDataViewRequest.state === 'uninitialized',
[createAdhocDataViewRequest.state]
);
useEffect(() => {
createAdhocDataView({
const state = useAsyncRetry(() => {
return dataViews.create({
id: generateDataViewId(metricAlias),
title: metricAlias,
timeFieldName: TIMESTAMP_FIELD,
});
}, [createAdhocDataView, metricAlias]);
}, [metricAlias]);
useEffect(() => {
if (hasError && notifications) {
notifications.toasts.addDanger(
i18n.translate('xpack.infra.hostsViewPage.errorOnCreateOrLoadDataview', {
defaultMessage: 'There was an error trying to create a Data View: {metricAlias}',
values: { metricAlias },
})
);
}
}, [hasError, notifications, metricAlias]);
const { value, loading, error, retry } = state;
return {
metricAlias,
dataView,
dataView: value,
loading,
hasError,
loadDataView: retry,
error,
};
};

View file

@ -5,13 +5,14 @@
* 2.0.
*/
import createContainer from 'constate';
import { useCallback, useEffect } from 'react';
import { useCallback, useEffect, useState } from 'react';
import DateMath from '@kbn/datemath';
import { buildEsQuery, type Query } from '@kbn/es-query';
import { buildEsQuery, fromKueryExpression, type Query } from '@kbn/es-query';
import { map, skip, startWith } from 'rxjs/operators';
import { combineLatest } from 'rxjs';
import deepEqual from 'fast-deep-equal';
import useEffectOnce from 'react-use/lib/useEffectOnce';
import { useKibanaQuerySettings } from '../../../../utils/use_kibana_query_settings';
import { useKibanaContextForPlugin } from '../../../../hooks/use_kibana';
import { telemetryTimeRangeFormatter } from '../../../../../common/formatters/telemetry_time_range';
import { useMetricsDataViewContext } from './use_data_view';
@ -48,9 +49,12 @@ const getDefaultTimestamps = () => {
};
export const useUnifiedSearch = () => {
const [error, setError] = useState<Error | null>(null);
const [searchCriteria, setSearch] = useHostsUrlState();
const { dataView } = useMetricsDataViewContext();
const { services } = useKibanaContextForPlugin();
const kibanaQuerySettings = useKibanaQuerySettings();
const {
data: {
query: {
@ -62,7 +66,37 @@ export const useUnifiedSearch = () => {
telemetry,
} = services;
const onSubmit = (params?: HostsSearchPayload) => setSearch(params ?? {});
const validateQuery = useCallback(
(query: Query) => {
fromKueryExpression(query.query, kibanaQuerySettings);
},
[kibanaQuerySettings]
);
const onSubmit = useCallback(
(params?: HostsSearchPayload) => {
try {
setError(null);
/*
/ Validates the Search Bar input values before persisting them in the state.
/ Since the search can be triggered by components that are unaware of the Unified Search state (e.g Controls and Host Limit),
/ this will always validates the query bar value, regardless of whether it's been sent in the current event or not.
*/
validateQuery(params?.query ?? (queryStringService.getQuery() as Query));
setSearch(params ?? {});
} catch (err) {
/*
/ Persists in the state the params so they can be used in case the query bar is fixed by the user.
/ This is needed because the Unified Search observables are unnaware of the other componets in the search bar.
/ Invalid query isn't persisted because it breaks the Control component
*/
const { query, ...validParams } = params ?? {};
setSearch(validParams ?? {});
setError(err);
}
},
[queryStringService, setSearch, validateQuery]
);
const getParsedDateRange = useCallback(() => {
const defaults = getDefaultTimestamps();
@ -84,21 +118,38 @@ export const useUnifiedSearch = () => {
}, [getParsedDateRange]);
const buildQuery = useCallback(() => {
return buildEsQuery(dataView, searchCriteria.query, [
...searchCriteria.filters,
...searchCriteria.panelFilters,
]);
}, [dataView, searchCriteria.query, searchCriteria.filters, searchCriteria.panelFilters]);
return buildEsQuery(
dataView,
searchCriteria.query,
[...searchCriteria.filters, ...searchCriteria.panelFilters],
kibanaQuerySettings
);
}, [
dataView,
searchCriteria.query,
searchCriteria.filters,
searchCriteria.panelFilters,
kibanaQuerySettings,
]);
useEffectOnce(() => {
// Sync filtersService from state
// Sync filtersService from the URL state
if (!deepEqual(filterManagerService.getFilters(), searchCriteria.filters)) {
filterManagerService.setFilters(searchCriteria.filters);
}
// Sync queryService from state
// Sync queryService from the URL state
if (!deepEqual(queryStringService.getQuery(), searchCriteria.query)) {
queryStringService.setQuery(searchCriteria.query);
}
try {
// Validates the "query" object from the URL state
if (searchCriteria.query) {
validateQuery(searchCriteria.query);
}
} catch (err) {
setError(err);
}
});
useEffect(() => {
@ -117,12 +168,12 @@ export const useUnifiedSearch = () => {
query: query$,
})
.pipe(skip(1))
.subscribe(setSearch);
.subscribe(onSubmit);
return () => {
subscription.unsubscribe();
};
}, [filterManagerService, setSearch, queryStringService, timeFilterService.timefilter]);
}, [filterManagerService, onSubmit, queryStringService, timeFilterService.timefilter]);
// Track telemetry event on query/filter/date changes
useEffect(() => {
@ -133,6 +184,7 @@ export const useUnifiedSearch = () => {
}, [getDateRangeAsTimestamp, searchCriteria, telemetry]);
return {
error,
buildQuery,
onSubmit,
getParsedDateRange,

View file

@ -17129,6 +17129,10 @@
"xpack.infra.hostsPage.tellUsWhatYouThinkLink": "Dites-nous ce que vous pensez !",
"xpack.infra.hostsViewPage.experimentalBadgeDescription": "Cette fonctionnalité est en version d'évaluation technique et pourra être modifiée ou retirée complètement dans une future version. Elastic s'efforcera au maximum de corriger tout problème, mais les fonctionnalités en version d'évaluation technique ne sont pas soumises aux accords de niveau de service d'assistance des fonctionnalités officielles en disponibilité générale.",
"xpack.infra.hostsViewPage.experimentalBadgeLabel": "Version d'évaluation technique",
"xpack.infra.hostsViewPage.error.kqlErrorTitle": "Expression KQL non valide",
"xpack.infra.hostsViewPage.error.detailsButton": "Afficher les détails",
"xpack.infra.hostsViewPage.error.tryAgainButton": "Réessayer",
"xpack.infra.hostsViewPage.error.unknownErrorTitle": "Une erreur s'est produite",
"xpack.infra.hostsViewPage.landing.calloutReachOutToYourKibanaAdministrator": "Votre rôle d'utilisateur ne dispose pas des privilèges suffisants pour activer cette fonctionnalité - veuillez \n contacter votre administrateur Kibana et lui demander de visiter cette page pour activer la fonctionnalité.",
"xpack.infra.hostsViewPage.landing.enableHostsView": "Activer la vue des hôtes",
"xpack.infra.hostsViewPage.landing.introMessage": "Présentation de notre nouvelle fonctionnalité \"Hôtes\", maintenant disponible dans la version d'évaluation technique !\n À l'aide de ce puissant outil, vous pouvez facilement afficher et analyser vos hôtes et identifier tout\n problème afin de le corriger rapidement. Obtenez une vue détaillée des indicateurs pour vos hôtes, regardez\n ceux qui déclenchent le plus d'alertes et filtrez les hôtes que vous souhaitez analyser\n à l'aide de tout filtre KQL et de répartitions simples telles que le fournisseur cloud et le système d'exploitation.",
@ -37742,4 +37746,4 @@
"xpack.painlessLab.title": "Painless Lab",
"xpack.painlessLab.walkthroughButtonLabel": "Présentation"
}
}
}

View file

@ -17128,6 +17128,10 @@
"xpack.infra.hostsPage.tellUsWhatYouThinkLink": "ご意見をお聞かせください。",
"xpack.infra.hostsViewPage.experimentalBadgeDescription": "この機能はテクニカルプレビュー中であり、将来のリリースでは変更されたり完全に削除されたりする場合があります。Elasticは最善の努力を講じてすべての問題の修正に努めますが、テクニカルプレビュー中の機能には正式なGA機能のサポートSLAが適用されません。",
"xpack.infra.hostsViewPage.experimentalBadgeLabel": "テクニカルプレビュー",
"xpack.infra.hostsViewPage.error.kqlErrorTitle": "無効なKQL式",
"xpack.infra.hostsViewPage.error.detailsButton": "詳細を表示",
"xpack.infra.hostsViewPage.error.tryAgainButton": "再試行",
"xpack.infra.hostsViewPage.error.unknownErrorTitle": "エラーが発生しました",
"xpack.infra.hostsViewPage.landing.calloutReachOutToYourKibanaAdministrator": "ユーザーロールには、この機能を有効にするための十分な権限がありません。 \n この機能を有効にするために、Kibana管理者に連絡して、このページにアクセスするように依頼してください。",
"xpack.infra.hostsViewPage.landing.enableHostsView": "ホストビューを有効化",
"xpack.infra.hostsViewPage.landing.introMessage": "新機能「ホスト」のテクニカルプレビューを開始しました。\n この強力なツールを使えば、ホストを簡単に表示、分析し、あらゆる問題を特定して、\n 迅速に対処できます。ホストのメトリックの詳細ビューを表示します。\n 最も多くのアラートを発生させているホストを確認し、\n KQLフィルターと、クラウドプロバイダーやオペレーティングシステムなどの簡単な内訳を使用して、分析したいホストをフィルタリングします。",
@ -37710,4 +37714,4 @@
"xpack.painlessLab.title": "Painless Lab",
"xpack.painlessLab.walkthroughButtonLabel": "実地検証"
}
}
}

View file

@ -17130,6 +17130,10 @@
"xpack.infra.hostsPage.tellUsWhatYouThinkLink": "告诉我们您的看法!",
"xpack.infra.hostsViewPage.experimentalBadgeDescription": "此功能处于技术预览状态在未来版本中可能会更改或完全移除。Elastic 将尽最大努力来修复任何问题,但处于技术预览状态的功能不受正式 GA 功能支持 SLA 的约束。",
"xpack.infra.hostsViewPage.experimentalBadgeLabel": "技术预览",
"xpack.infra.hostsViewPage.error.kqlErrorTitle": "KQL 表达式无效",
"xpack.infra.hostsViewPage.error.detailsButton": "查看详情",
"xpack.infra.hostsViewPage.error.tryAgainButton": "重试",
"xpack.infra.hostsViewPage.error.unknownErrorTitle": "发生错误",
"xpack.infra.hostsViewPage.landing.calloutReachOutToYourKibanaAdministrator": "您的用户角色权限不足,无法启用此功能 - 请 \n 联系您的 Kibana 管理员,要求他们访问此页面以启用该功能。",
"xpack.infra.hostsViewPage.landing.enableHostsView": "启用主机视图",
"xpack.infra.hostsViewPage.landing.introMessage": "介绍目前在技术预览中可用的新“主机”功能!\n 使用这个强大的工具,您可以轻松查看并分析主机,并确定任何\n 问题以便快速予以解决。获取您主机的指标的详细视图,\n 查看哪些主机触发了最多告警,并使用任何 KQL 筛选\n 以及云提供商和操作系统等细目筛选您要分析的主机。",
@ -37738,4 +37742,4 @@
"xpack.painlessLab.title": "Painless 实验室",
"xpack.painlessLab.walkthroughButtonLabel": "指导"
}
}
}

View file

@ -544,6 +544,11 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
expect(cells.length).to.be(ALL_ALERTS * COLUMNS);
});
});
it('should show an error message when an invalid KQL is submitted', async () => {
await pageObjects.infraHostsView.submitQuery('cloud.provider="gcp" A');
await testSubjects.existOrFail('hostsViewErrorCallout');
});
});
describe('Pagination and Sorting', () => {

View file

@ -111,7 +111,7 @@ export function InfraHostsViewProvider({ getService }: FtrProviderContext) {
return testSubjects.find('hostsView-metricChart');
},
// MetricsTtab
// Metrics Tab
async getMetricsTab() {
return testSubjects.find('hostsView-tabs-metrics');
},